fix: rewrite sync settings UI with dark theme inputs, add auto-sync and interval settings
This commit is contained in:
parent
95aa600704
commit
695c1c83d0
|
|
@ -6,67 +6,102 @@
|
||||||
let errorMsg = ''
|
let errorMsg = ''
|
||||||
let resultMsg = ''
|
let resultMsg = ''
|
||||||
let resultKind = ''
|
let resultKind = ''
|
||||||
|
let connectionResult = ''
|
||||||
|
let connectionOk = null
|
||||||
|
|
||||||
let serverUrl = ''
|
let serverUrl = ''
|
||||||
let username = ''
|
let username = ''
|
||||||
let password = ''
|
let password = ''
|
||||||
let syncInterval = 0
|
let syncInterval = 5
|
||||||
let autoSync = false
|
let autoSync = false
|
||||||
let showDisconnectConfirm = false
|
|
||||||
let showResetKeyConfirm = false
|
const INPUT_STYLE = 'width:100%;background:#0f3460;border:1px solid #1a3a5c;color:#e0e0f0;padding:8px 10px;border-radius:4px;font-size:0.85rem;box-sizing:border-box;height:36px;'
|
||||||
let connectionOk = null
|
const INPUT_FOCUS_STYLE = INPUT_STYLE + 'outline:none;border-color:#4ecca3;'
|
||||||
|
|
||||||
function sanitizeError(msg) {
|
function sanitizeError(msg) {
|
||||||
if (!msg) return 'Unknown error'
|
if (!msg) return 'Unknown error'
|
||||||
let s = String(msg)
|
let s = String(msg).replace(/<[^>]+>/g, '')
|
||||||
s = s.replace(/<[^>]+>/g, '')
|
|
||||||
if (s.length > 200) s = s.substring(0, 200) + '...'
|
if (s.length > 200) s = s.substring(0, 200) + '...'
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
async function backendCall(method, ...args) {
|
async function backendCall(method, ...args) {
|
||||||
if (api && api.backend && typeof api.backend.call === 'function') {
|
if (api?.backend && typeof api.backend.call === 'function') {
|
||||||
return await api.backend.call(method, ...args)
|
return await api.backend.call(method, ...args)
|
||||||
}
|
}
|
||||||
throw new Error('Plugin API backend.call not available')
|
throw new Error('Plugin API backend.call not available')
|
||||||
}
|
}
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
|
try {
|
||||||
|
if (api?.settings?.read) {
|
||||||
|
const saved = await api.settings.read()
|
||||||
|
if (saved) {
|
||||||
|
serverUrl = saved.serverUrl || ''
|
||||||
|
username = saved.username || ''
|
||||||
|
autoSync = !!saved.autoSync
|
||||||
|
syncInterval = saved.syncInterval || 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
try {
|
try {
|
||||||
settings = await backendCall('SyncStatus')
|
settings = await backendCall('SyncStatus')
|
||||||
if (settings) {
|
if (settings) {
|
||||||
serverUrl = settings.serverUrl || ''
|
if (settings.serverUrl) serverUrl = settings.serverUrl
|
||||||
syncInterval = settings.syncInterval || 0
|
if (settings.syncInterval != null) syncInterval = settings.syncInterval
|
||||||
autoSync = settings.syncInterval > 0
|
if (settings.syncInterval > 0) autoSync = true
|
||||||
}
|
}
|
||||||
} catch (e) { settings = null }
|
} catch (_) { settings = null }
|
||||||
}
|
}
|
||||||
|
|
||||||
load()
|
load()
|
||||||
|
|
||||||
async function testConnection() {
|
async function saveSettings() {
|
||||||
|
if (syncInterval < 1 || syncInterval > 1440) {
|
||||||
|
errorMsg = 'Sync interval must be between 1 and 1440 minutes.'
|
||||||
|
return
|
||||||
|
}
|
||||||
loading = true
|
loading = true
|
||||||
errorMsg = ''
|
errorMsg = ''
|
||||||
resultKind = ''
|
resultMsg = ''
|
||||||
|
try {
|
||||||
|
if (api?.settings?.write) {
|
||||||
|
await api.settings.write({ serverUrl, username, autoSync, syncInterval })
|
||||||
|
}
|
||||||
|
resultMsg = 'Settings saved.'
|
||||||
|
resultKind = ''
|
||||||
|
} catch (e) {
|
||||||
|
errorMsg = sanitizeError(e.message || e)
|
||||||
|
}
|
||||||
|
loading = false
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testConnection() {
|
||||||
|
if (!serverUrl) { errorMsg = 'Server URL is required.'; return }
|
||||||
|
loading = true
|
||||||
|
connectionResult = ''
|
||||||
connectionOk = null
|
connectionOk = null
|
||||||
|
errorMsg = ''
|
||||||
try {
|
try {
|
||||||
await backendCall('SyncTestConnection', serverUrl, username, password)
|
await backendCall('SyncTestConnection', serverUrl, username, password)
|
||||||
connectionOk = true
|
connectionOk = true
|
||||||
resultMsg = 'connection ok'
|
connectionResult = 'Connection successful.'
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
connectionOk = false
|
connectionOk = false
|
||||||
resultMsg = 'connection failed: ' + sanitizeError(e.message || e)
|
connectionResult = 'Connection failed: ' + sanitizeError(e.message || e)
|
||||||
}
|
}
|
||||||
loading = false
|
loading = false
|
||||||
}
|
}
|
||||||
|
|
||||||
async function configureSync() {
|
async function configureSync() {
|
||||||
|
if (!serverUrl) { errorMsg = 'Server URL is required.'; return }
|
||||||
loading = true
|
loading = true
|
||||||
errorMsg = ''
|
errorMsg = ''
|
||||||
resultKind = ''
|
connectionResult = ''
|
||||||
try {
|
try {
|
||||||
await backendCall('SyncConfigure', serverUrl, username, password)
|
await backendCall('SyncConfigure', serverUrl, username, password)
|
||||||
resultMsg = 'configured'
|
connectionResult = 'Connected successfully.'
|
||||||
|
connectionOk = true
|
||||||
username = ''
|
username = ''
|
||||||
password = ''
|
password = ''
|
||||||
await load()
|
await load()
|
||||||
|
|
@ -80,22 +115,18 @@
|
||||||
const conflicts = Array.isArray(result?.conflicts) ? result.conflicts : []
|
const conflicts = Array.isArray(result?.conflicts) ? result.conflicts : []
|
||||||
const applyErrors = Array.isArray(result?.applyErrors) ? result.applyErrors : []
|
const applyErrors = Array.isArray(result?.applyErrors) ? result.applyErrors : []
|
||||||
const parts = []
|
const parts = []
|
||||||
if (conflicts.length > 0) {
|
if (conflicts.length > 0) parts.push(conflicts.length + ' conflict(s)')
|
||||||
parts.push(conflicts.length + ' conflict(s)')
|
if (applyErrors.length > 0) parts.push(applyErrors.length + ' error(s)')
|
||||||
}
|
|
||||||
if (applyErrors.length > 0) {
|
|
||||||
parts.push(applyErrors.length + ' error(s)')
|
|
||||||
}
|
|
||||||
return parts.join(' · ')
|
return parts.join(' · ')
|
||||||
}
|
}
|
||||||
|
|
||||||
async function runSyncNow() {
|
async function runSyncNow() {
|
||||||
loading = true
|
loading = true
|
||||||
errorMsg = ''
|
errorMsg = ''
|
||||||
resultKind = ''
|
resultMsg = ''
|
||||||
try {
|
try {
|
||||||
const r = await backendCall('SyncNow')
|
const r = await backendCall('SyncNow')
|
||||||
const summary = 'pushed ' + (r?.pushed || 0) + ', pulled ' + (r?.pulled || 0)
|
const summary = 'Pushed ' + (r?.pushed || 0) + ', pulled ' + (r?.pulled || 0)
|
||||||
const warning = syncResultWarning(r)
|
const warning = syncResultWarning(r)
|
||||||
resultMsg = warning ? summary + ' · ' + warning : summary
|
resultMsg = warning ? summary + ' · ' + warning : summary
|
||||||
resultKind = warning ? 'warning' : ''
|
resultKind = warning ? 'warning' : ''
|
||||||
|
|
@ -106,420 +137,105 @@
|
||||||
loading = false
|
loading = false
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveInterval() {
|
function toggleAutoSync() {
|
||||||
loading = true
|
autoSync = !autoSync
|
||||||
errorMsg = ''
|
if (autoSync && syncInterval < 1) syncInterval = 5
|
||||||
resultKind = ''
|
saveSettings()
|
||||||
try {
|
|
||||||
await backendCall('SyncSetInterval', syncInterval)
|
|
||||||
resultMsg = 'settings saved'
|
|
||||||
resultKind = ''
|
|
||||||
} catch (e) {
|
|
||||||
errorMsg = sanitizeError(e.message || e)
|
|
||||||
}
|
|
||||||
loading = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setAutoSync() {
|
function saveInterval() {
|
||||||
const minutes = autoSync ? 5 : 0
|
if (syncInterval < 1 || syncInterval > 1440) {
|
||||||
syncInterval = minutes
|
errorMsg = 'Sync interval must be between 1 and 1440 minutes.'
|
||||||
loading = true
|
return
|
||||||
errorMsg = ''
|
|
||||||
resultKind = ''
|
|
||||||
try {
|
|
||||||
await backendCall('SyncSetInterval', minutes)
|
|
||||||
resultMsg = autoSync ? 'auto-sync enabled' : 'auto-sync disabled'
|
|
||||||
resultKind = ''
|
|
||||||
} catch (e) {
|
|
||||||
errorMsg = sanitizeError(e.message || e)
|
|
||||||
}
|
}
|
||||||
loading = false
|
autoSync = syncInterval > 0
|
||||||
}
|
saveSettings()
|
||||||
|
|
||||||
function confirmDisconnect() {
|
|
||||||
showDisconnectConfirm = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function doDisconnect() {
|
async function doDisconnect() {
|
||||||
showDisconnectConfirm = false
|
|
||||||
loading = true
|
loading = true
|
||||||
resultKind = ''
|
errorMsg = ''
|
||||||
|
resultMsg = ''
|
||||||
try {
|
try {
|
||||||
await backendCall('SyncDisconnect')
|
await backendCall('SyncDisconnect')
|
||||||
resultMsg = 'disconnected'
|
resultMsg = 'Disconnected from server.'
|
||||||
|
resultKind = ''
|
||||||
|
settings = null
|
||||||
await load()
|
await load()
|
||||||
} catch (e) { errorMsg = sanitizeError(e.message || e) }
|
} catch (e) {
|
||||||
loading = false
|
errorMsg = sanitizeError(e.message || e)
|
||||||
}
|
|
||||||
|
|
||||||
function confirmResetKey() {
|
|
||||||
showResetKeyConfirm = true
|
|
||||||
}
|
|
||||||
|
|
||||||
async function doResetKey() {
|
|
||||||
showResetKeyConfirm = false
|
|
||||||
loading = true
|
|
||||||
resultKind = ''
|
|
||||||
try {
|
|
||||||
await backendCall('ResetSyncKey')
|
|
||||||
resultMsg = 'key reset'
|
|
||||||
await load()
|
|
||||||
} catch (e) { errorMsg = sanitizeError(e.message || e) }
|
|
||||||
loading = false
|
|
||||||
}
|
|
||||||
|
|
||||||
function statusLabel(s) {
|
|
||||||
if (!s) return 'not configured'
|
|
||||||
const labels = {
|
|
||||||
'connected': 'connected',
|
|
||||||
'disconnected': 'disconnected',
|
|
||||||
'disabled': 'not configured',
|
|
||||||
'error': 'error',
|
|
||||||
'revoked': 'revoked',
|
|
||||||
}
|
}
|
||||||
return labels[s] || s
|
loading = false
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="settings-section">
|
<div style="padding:1.5rem;max-width:500px;">
|
||||||
<h2>Sync</h2>
|
<h2 style="margin:0 0 0.25rem;color:#e0e0f0;font-size:1.2rem;">Sync</h2>
|
||||||
<p class="section-desc">Synchronize your vault across devices.</p>
|
<p style="color:#a0a0b8;font-size:0.85rem;margin-bottom:1.25rem;">Synchronize your vault across devices.</p>
|
||||||
|
|
||||||
{#if errorMsg}
|
{#if errorMsg}
|
||||||
<div class="error-msg">{errorMsg}</div>
|
<div style="padding:0.5rem 0.75rem;margin-bottom:0.75rem;background:rgba(255,107,107,0.1);border:1px solid rgba(255,107,107,0.3);border-radius:6px;color:#ff6b6b;font-size:0.85rem;">{errorMsg}</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if resultMsg && !errorMsg}
|
{#if resultMsg && !errorMsg}
|
||||||
<div class="result-msg" class:warning={resultKind === 'warning'}>{resultMsg}</div>
|
<div style="padding:0.5rem 0.75rem;margin-bottom:0.75rem;border-radius:6px;font-size:0.85rem;{resultKind === 'warning' ? 'background:rgba(245,158,11,0.1);border:1px solid rgba(245,158,11,0.3);color:#f59e0b;' : 'background:rgba(52,211,153,0.1);border:1px solid rgba(52,211,153,0.3);color:#34d399;'}">{resultMsg}</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if settings && settings.configured}
|
<div style="background:#16213e;border:1px solid #0f3460;border-radius:8px;padding:1rem 1.25rem;margin-bottom:1rem;">
|
||||||
<div class="settings-card">
|
<h3 style="margin:0 0 0.75rem;color:#e0e0f0;font-size:0.95rem;">Server</h3>
|
||||||
<div class="sync-info">
|
|
||||||
<div class="info-row">
|
<div style="margin-bottom:0.75rem;">
|
||||||
<span class="info-label">Status</span>
|
<label style="display:block;color:#a0a0b8;font-size:0.85rem;margin-bottom:0.35rem;">Server URL</label>
|
||||||
<span class="info-value" class:status-ok={settings.statusLabel === 'connected'} class:status-err={settings.statusLabel === 'error' || settings.statusLabel === 'revoked'}>
|
<input type="text" style={INPUT_STYLE} on:focus={e => e.target.style.cssText = INPUT_FOCUS_STYLE} on:blur={e => e.target.style.cssText = INPUT_STYLE} bind:value={serverUrl} placeholder="https://example.com" />
|
||||||
{statusLabel(settings.statusLabel)}
|
</div>
|
||||||
</span>
|
|
||||||
</div>
|
<div style="margin-bottom:0.75rem;">
|
||||||
{#if settings.serverUrl}
|
<label style="display:block;color:#a0a0b8;font-size:0.85rem;margin-bottom:0.35rem;">Username</label>
|
||||||
<div class="info-row">
|
<input type="text" style={INPUT_STYLE} on:focus={e => e.target.style.cssText = INPUT_FOCUS_STYLE} on:blur={e => e.target.style.cssText = INPUT_STYLE} bind:value={username} />
|
||||||
<span class="info-label">Server URL</span>
|
</div>
|
||||||
<span class="info-value mono">{settings.serverUrl}</span>
|
|
||||||
</div>
|
<div style="margin-bottom:0.75rem;">
|
||||||
{/if}
|
<label style="display:block;color:#a0a0b8;font-size:0.85rem;margin-bottom:0.35rem;">Password</label>
|
||||||
{#if settings.deviceName}
|
<input type="password" style={INPUT_STYLE} on:focus={e => e.target.style.cssText = INPUT_FOCUS_STYLE} on:blur={e => e.target.style.cssText = INPUT_STYLE} bind:value={password} />
|
||||||
<div class="info-row">
|
</div>
|
||||||
<span class="info-label">Device Name</span>
|
|
||||||
<span class="info-value">{settings.deviceName}</span>
|
<div style="display:flex;gap:0.5rem;margin-top:1rem;">
|
||||||
</div>
|
<button style="background:#1a1a2e;color:#e0e0f0;border:1px solid #1a3a5c;padding:0.4rem 0.75rem;border-radius:4px;cursor:pointer;font-size:0.85rem;" on:click={testConnection} disabled={loading || !serverUrl}>Test Connection</button>
|
||||||
{/if}
|
<button style="background:#4ecca3;color:#1a1a2e;border:1px solid #4ecca3;padding:0.4rem 0.75rem;border-radius:4px;cursor:pointer;font-size:0.85rem;font-weight:600;" on:click={configureSync} disabled={loading || !serverUrl}>Connect</button>
|
||||||
{#if settings.deviceId}
|
</div>
|
||||||
<div class="info-row">
|
|
||||||
<span class="info-label">Device ID</span>
|
{#if connectionResult}
|
||||||
<span class="info-value mono">{settings.deviceId}</span>
|
<div style="margin-top:0.75rem;padding:0.5rem 0.75rem;border-radius:6px;font-size:0.85rem;{connectionOk ? 'background:rgba(52,211,153,0.1);border:1px solid rgba(52,211,153,0.3);color:#34d399;' : 'background:rgba(255,107,107,0.1);border:1px solid rgba(255,107,107,0.3);color:#ff6b6b;'}">{connectionResult}</div>
|
||||||
</div>
|
{/if}
|
||||||
{/if}
|
</div>
|
||||||
{#if settings.lastSyncAt}
|
|
||||||
<div class="info-row">
|
<div style="background:#16213e;border:1px solid #0f3460;border-radius:8px;padding:1rem 1.25rem;margin-bottom:1rem;">
|
||||||
<span class="info-label">Last Sync</span>
|
<h3 style="margin:0 0 0.75rem;color:#e0e0f0;font-size:0.95rem;">Sync Behavior</h3>
|
||||||
<span class="info-value">{settings.lastSyncAt}</span>
|
|
||||||
</div>
|
<div style="margin-bottom:0.75rem;display:flex;align-items:center;gap:0.5rem;">
|
||||||
{/if}
|
<input type="checkbox" id="auto-sync" bind:checked={autoSync} on:change={toggleAutoSync} style="width:16px;height:16px;accent-color:#4ecca3;" />
|
||||||
{#if settings.lastError}
|
<label for="auto-sync" style="color:#e0e0f0;font-size:0.9rem;cursor:pointer;">Enable auto-sync</label>
|
||||||
<div class="info-row">
|
</div>
|
||||||
<span class="info-label">Last Error</span>
|
|
||||||
<span class="info-value error">{settings.lastError}</span>
|
<div style="margin-bottom:0.75rem;">
|
||||||
</div>
|
<label style="display:block;color:#a0a0b8;font-size:0.85rem;margin-bottom:0.35rem;">Sync interval</label>
|
||||||
{/if}
|
<div style="display:flex;align-items:center;gap:0.5rem;">
|
||||||
|
<input type="number" min="1" max="1440" bind:value={syncInterval} on:change={saveInterval} style="width:100px;background:#0f3460;border:1px solid #1a3a5c;color:#e0e0f0;padding:8px 10px;border-radius:4px;font-size:0.85rem;height:36px;" />
|
||||||
|
<span style="color:#a0a0b8;font-size:0.85rem;">minutes</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sync-actions">
|
{#if settings && settings.lastSyncAt}
|
||||||
<button class="btn btn-primary" on:click={runSyncNow} disabled={loading}>
|
<div style="color:#a0a0b8;font-size:0.85rem;">
|
||||||
Sync Now
|
Last sync: {settings.lastSyncAt}
|
||||||
</button>
|
|
||||||
<button class="btn" on:click={confirmDisconnect} disabled={loading}>
|
|
||||||
Disconnect
|
|
||||||
</button>
|
|
||||||
<button class="btn" on:click={confirmResetKey} disabled={loading}>
|
|
||||||
Reset Key
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="sync-interval">
|
|
||||||
<label class="field-label" for="sync-interval">Sync Interval (minutes)</label>
|
|
||||||
<div class="interval-row">
|
|
||||||
<input id="sync-interval" type="number" bind:value={syncInterval} min="0" placeholder="0" />
|
|
||||||
<button class="btn btn-sm" on:click={saveInterval} disabled={loading}>Save</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="auto-sync-toggle">
|
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;">
|
||||||
<label class="toggle-label">
|
<button style="background:#4ecca3;color:#1a1a2e;border:1px solid #4ecca3;padding:0.4rem 0.75rem;border-radius:4px;cursor:pointer;font-size:0.85rem;font-weight:600;" on:click={saveSettings} disabled={loading}>Save</button>
|
||||||
<input type="checkbox" bind:checked={autoSync} on:change={setAutoSync} disabled={loading} />
|
{#if settings && settings.configured}
|
||||||
<span>Auto-sync</span>
|
<button style="background:#1a1a2e;color:#e0e0f0;border:1px solid #1a3a5c;padding:0.4rem 0.75rem;border-radius:4px;cursor:pointer;font-size:0.85rem;" on:click={runSyncNow} disabled={loading}>Sync Now</button>
|
||||||
</label>
|
<button style="background:#e94560;color:#fff;border:1px solid #e94560;padding:0.4rem 0.75rem;border-radius:4px;cursor:pointer;font-size:0.85rem;" on:click={doDisconnect} disabled={loading}>Disconnect</button>
|
||||||
</div>
|
{/if}
|
||||||
{:else}
|
</div>
|
||||||
<div class="settings-card">
|
|
||||||
<div class="sync-setup">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="field-label" for="sync-url">Server URL</label>
|
|
||||||
<input id="sync-url" type="text" class="field-input" placeholder="https://example.com" bind:value={serverUrl} />
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="field-label" for="sync-user">Username</label>
|
|
||||||
<input id="sync-user" type="text" class="field-input" bind:value={username} />
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="field-label" for="sync-pass">Password</label>
|
|
||||||
<input id="sync-pass" type="password" class="field-input" bind:value={password} />
|
|
||||||
</div>
|
|
||||||
<div class="sync-setup-actions">
|
|
||||||
<button class="btn" on:click={testConnection} disabled={loading || !serverUrl}>
|
|
||||||
Test Connection
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-primary" on:click={configureSync}
|
|
||||||
disabled={loading || !serverUrl || !username || !password}>
|
|
||||||
Connect
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{#if connectionOk !== null}
|
|
||||||
<div class="connection-result" class:ok={connectionOk} class:fail={!connectionOk}>
|
|
||||||
{connectionOk ? 'Test OK' : 'Connection failed'}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if showDisconnectConfirm}
|
|
||||||
<button class="modal-overlay" on:click={() => showDisconnectConfirm = false}>
|
|
||||||
<div class="modal">
|
|
||||||
<h3>Disconnect from Sync Server</h3>
|
|
||||||
<p class="modal-desc">This will disconnect this device from the sync server and revoke the device token. You can reconnect later.</p>
|
|
||||||
<div class="modal-actions">
|
|
||||||
<button class="btn btn-danger" on:click={doDisconnect}>Disconnect</button>
|
|
||||||
<button class="btn" on:click={() => showDisconnectConfirm = false}>Cancel</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if showResetKeyConfirm}
|
|
||||||
<button class="modal-overlay" on:click={() => showResetKeyConfirm = false}>
|
|
||||||
<div class="modal">
|
|
||||||
<h3>Reset Sync Key</h3>
|
|
||||||
<p class="modal-desc">This will remove the stored device token. You will need to re-pair this device with the server.</p>
|
|
||||||
<div class="modal-actions">
|
|
||||||
<button class="btn btn-danger" on:click={doResetKey}>Reset Key</button>
|
|
||||||
<button class="btn" on:click={() => showResetKeyConfirm = false}>Cancel</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.settings-section {
|
|
||||||
padding: 1.5rem;
|
|
||||||
max-width: 600px;
|
|
||||||
}
|
|
||||||
.settings-section h2 {
|
|
||||||
margin: 0 0 0.25rem 0;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
color: #e0e0f0;
|
|
||||||
}
|
|
||||||
.section-desc {
|
|
||||||
color: #a0a0b8;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
margin-bottom: 1.25rem;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
.settings-card {
|
|
||||||
background: #16213e;
|
|
||||||
border: 1px solid #0f3460;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 1rem 1.25rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
.field-label {
|
|
||||||
display: block;
|
|
||||||
color: #a0a0b8;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
margin-bottom: 0.35rem;
|
|
||||||
}
|
|
||||||
.field-input {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
background: #0f3460;
|
|
||||||
border: 1px solid #1a3a5c;
|
|
||||||
color: #e0e0f0;
|
|
||||||
padding: 0.4rem 0.6rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
.field-input:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: #4ecca3;
|
|
||||||
}
|
|
||||||
.info-row {
|
|
||||||
display: flex;
|
|
||||||
padding: 0.4rem 0;
|
|
||||||
border-bottom: 1px solid #0f3460;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
.info-row:last-child { border-bottom: none; }
|
|
||||||
.info-label {
|
|
||||||
width: 180px;
|
|
||||||
min-width: 180px;
|
|
||||||
color: #a0a0b8;
|
|
||||||
}
|
|
||||||
.info-value { color: #e0e0f0; word-break: break-all; }
|
|
||||||
.info-value.mono { font-family: monospace; font-size: 0.85rem; }
|
|
||||||
.info-value.error { color: #ff6b6b; }
|
|
||||||
.status-ok { color: #34d399; font-weight: 600; }
|
|
||||||
.status-err { color: #ff6b6b; }
|
|
||||||
.sync-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5rem;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
.sync-interval {
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
}
|
|
||||||
.interval-row {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5rem;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.interval-row input {
|
|
||||||
width: 100px;
|
|
||||||
background: #0f3460;
|
|
||||||
border: 1px solid #1a3a5c;
|
|
||||||
color: #e0e0f0;
|
|
||||||
padding: 0.35rem 0.5rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
.interval-row input:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: #4ecca3;
|
|
||||||
}
|
|
||||||
.auto-sync-toggle {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
.toggle-label {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: #e0e0f0;
|
|
||||||
}
|
|
||||||
.toggle-label input[type="checkbox"] {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
accent-color: #4ecca3;
|
|
||||||
}
|
|
||||||
.sync-setup .form-group { margin-bottom: 1rem; }
|
|
||||||
.sync-setup .form-group:last-of-type { margin-bottom: 0; }
|
|
||||||
.sync-setup-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5rem;
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
.connection-result {
|
|
||||||
margin-top: 0.75rem;
|
|
||||||
padding: 0.4rem 0.75rem;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
.connection-result.ok {
|
|
||||||
background: rgba(52, 211, 153, 0.1);
|
|
||||||
border: 1px solid rgba(52, 211, 153, 0.3);
|
|
||||||
color: #34d399;
|
|
||||||
}
|
|
||||||
.connection-result.fail {
|
|
||||||
background: rgba(255, 107, 107, 0.1);
|
|
||||||
border: 1px solid rgba(255, 107, 107, 0.3);
|
|
||||||
color: #ff6b6b;
|
|
||||||
}
|
|
||||||
.error-msg {
|
|
||||||
padding: 0.5rem 0.75rem;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
background: rgba(255, 107, 107, 0.1);
|
|
||||||
border: 1px solid rgba(255, 107, 107, 0.3);
|
|
||||||
border-radius: 6px;
|
|
||||||
color: #ff6b6b;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
.result-msg {
|
|
||||||
padding: 0.5rem 0.75rem;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
background: rgba(52, 211, 153, 0.1);
|
|
||||||
border: 1px solid rgba(52, 211, 153, 0.3);
|
|
||||||
border-radius: 6px;
|
|
||||||
color: #34d399;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
.result-msg.warning {
|
|
||||||
background: rgba(245, 158, 11, 0.1);
|
|
||||||
border-color: rgba(245, 158, 11, 0.3);
|
|
||||||
color: #f59e0b;
|
|
||||||
}
|
|
||||||
.btn {
|
|
||||||
background: #1a1a2e;
|
|
||||||
color: #e0e0f0;
|
|
||||||
border: 1px solid #1a3a5c;
|
|
||||||
padding: 0.4rem 0.75rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
.btn:hover:not(:disabled) { border-color: #4ecca3; }
|
|
||||||
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
||||||
.btn-sm { padding: 0.3rem 0.6rem; font-size: 0.8rem; }
|
|
||||||
.btn-primary {
|
|
||||||
background: #4ecca3;
|
|
||||||
color: #1a1a2e;
|
|
||||||
border-color: #4ecca3;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.btn-primary:hover:not(:disabled) { background: #3dbb92; border-color: #3dbb92; }
|
|
||||||
.btn-danger {
|
|
||||||
background: #e94560;
|
|
||||||
color: #fff;
|
|
||||||
border-color: #e94560;
|
|
||||||
}
|
|
||||||
.btn-danger:hover:not(:disabled) { background: #d63851; border-color: #d63851; }
|
|
||||||
.modal-overlay {
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
background: rgba(0,0,0,0.6);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 1000;
|
|
||||||
border: none;
|
|
||||||
padding: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
width: 100%;
|
|
||||||
color: inherit;
|
|
||||||
font: inherit;
|
|
||||||
}
|
|
||||||
.modal {
|
|
||||||
background: #16213e;
|
|
||||||
border: 1px solid #0f3460;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 1.5rem;
|
|
||||||
max-width: 420px;
|
|
||||||
width: 90%;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
.modal h3 { margin: 0 0 0.75rem 0; color: #e0e0f0; }
|
|
||||||
.modal-desc { color: #a0a0b8; font-size: 0.9rem; line-height: 1.5; margin-bottom: 1rem; }
|
|
||||||
.modal-actions { display: flex; gap: 0.5rem; justify-content: flex-end; }
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue