This commit is contained in:
2026-04-20 20:40:48 +02:00
parent 463c391d14
commit 53dd49612d
19 changed files with 2435 additions and 4 deletions
+160
View File
@@ -0,0 +1,160 @@
<script>
import { onMount } from 'svelte'
import { api } from '../lib/api.js'
import { notify } from '../lib/store.js'
const providers = [
{ key: 'finnhub_api_key', label: 'Finnhub', provider: 'finnhub', placeholder: 'c_xxxxxxxxxxxxxxxx' },
{ key: 'alphavantage_key', label: 'Alpha Vantage', provider: 'alphavantage', placeholder: 'XXXXXXXXXXXX' },
{ key: 'etoro_api_key', label: 'eToro', provider: 'etoro', placeholder: 'eToro API key' },
]
let values = {}
let testing = {}
let testResults = {}
let saving = false
onMount(async () => {
try {
const data = await api.getSettings()
for (const p of providers) {
values[p.key] = data[p.key] === '********' ? '' : (data[p.key] ?? '')
}
} catch(e) {
notify('Impossible de charger les settings : ' + e.message, 'error')
}
})
async function save() {
saving = true
try {
const body = {}
for (const p of providers) {
if (values[p.key]) body[p.key] = values[p.key]
}
await api.saveSettings(body)
notify('Settings sauvegardés', 'success')
} catch(e) {
notify('Erreur sauvegarde : ' + e.message, 'error')
} finally {
saving = false
}
}
async function testKey(p) {
testing[p.provider] = true
testResults[p.provider] = null
try {
const r = await api.testKey(p.provider)
testResults[p.provider] = r.status === 'ok' ? 'ok' : 'error'
notify(`${p.label} : ${r.status === 'ok' ? 'clé valide' : r.message}`, r.status === 'ok' ? 'success' : 'error')
} catch(e) {
testResults[p.provider] = 'error'
notify(`${p.label} : erreur de connexion`, 'error')
} finally {
testing[p.provider] = false
}
}
</script>
<div class="page">
<h1>Settings</h1>
<form on:submit|preventDefault={save}>
<section>
<h2>Clés API</h2>
<p class="hint">Les clés sont stockées chiffrées (AES-256). Les champs vides conservent la valeur existante.</p>
{#each providers as p}
<div class="field">
<label for={p.key}>{p.label}</label>
<div class="input-row">
<input
id={p.key}
type="password"
bind:value={values[p.key]}
placeholder={p.placeholder}
autocomplete="off"
/>
<button
type="button"
class="btn-test"
class:ok={testResults[p.provider] === 'ok'}
class:error={testResults[p.provider] === 'error'}
disabled={testing[p.provider]}
on:click={() => testKey(p)}
>
{testing[p.provider] ? '…' : testResults[p.provider] === 'ok' ? '✓' : testResults[p.provider] === 'error' ? '✗' : 'Tester'}
</button>
</div>
</div>
{/each}
</section>
<div class="actions">
<button type="submit" class="btn-primary" disabled={saving}>
{saving ? 'Sauvegarde…' : 'Sauvegarder'}
</button>
</div>
</form>
</div>
<style>
.page h1 { color: #e6edf3; font-size: 1.4rem; }
form { max-width: 560px; }
section { margin-bottom: 2rem; }
h2 { font-size: 0.875rem; text-transform: uppercase; letter-spacing: 0.08em; color: #8b949e; border-bottom: 1px solid #21262d; padding-bottom: 0.5rem; margin-bottom: 1.25rem; }
.hint { font-size: 0.8rem; color: #484f58; margin: -0.75rem 0 1.25rem; }
.field { margin-bottom: 1.25rem; }
label { display: block; font-size: 0.875rem; color: #8b949e; margin-bottom: 0.4rem; }
.input-row { display: flex; gap: 0.5rem; }
input {
flex: 1;
background: #161b22;
border: 1px solid #30363d;
border-radius: 6px;
color: #e6edf3;
padding: 0.5rem 0.75rem;
font-size: 0.875rem;
outline: none;
transition: border-color 0.15s;
}
input:focus { border-color: #58a6ff; }
input::placeholder { color: #484f58; }
.btn-test {
background: #21262d;
border: 1px solid #30363d;
border-radius: 6px;
color: #8b949e;
padding: 0.5rem 0.75rem;
font-size: 0.8rem;
min-width: 64px;
transition: background 0.15s, color 0.15s;
}
.btn-test:hover:not(:disabled) { background: #30363d; color: #e6edf3; }
.btn-test.ok { color: #3fb950; border-color: #3fb950; }
.btn-test.error { color: #f85149; border-color: #f85149; }
.btn-test:disabled { opacity: 0.5; }
.actions { padding-top: 1rem; }
.btn-primary {
background: #1f6feb;
border: none;
border-radius: 6px;
color: #fff;
padding: 0.6rem 1.5rem;
font-size: 0.9rem;
font-weight: 500;
transition: background 0.15s;
}
.btn-primary:hover:not(:disabled) { background: #388bfd; }
.btn-primary:disabled { opacity: 0.5; }
</style>