fix(settings): replace console.error with handleError + Promise.allSettled
- All 6 settings components: save handlers use handleError with user intent - onMount loaders migrated from Promise.all to Promise.allSettled (partial recovery) - loadError $state + inline warning banner on full load failure - JSON parse catches use handleInfraError with explicit fallback comments - Secret operations (reveal/store/delete) use handleError for user feedback
This commit is contained in:
parent
c7292e9e54
commit
365c420901
6 changed files with 106 additions and 60 deletions
|
|
@ -4,6 +4,7 @@
|
|||
import { getPluginEntries, setPluginEnabled, reloadAllPlugins } from '../../stores/plugins.svelte';
|
||||
import { checkForUpdates, getCurrentVersion, getLastCheckTimestamp } from '../../utils/updater';
|
||||
import type { UpdateInfo } from '../../utils/updater';
|
||||
import { handleError } from '../../utils/handle-error';
|
||||
|
||||
let pluginEntries = $derived(getPluginEntries());
|
||||
let pluginAutoUpdate = $state(false);
|
||||
|
|
@ -20,9 +21,10 @@
|
|||
let otlpEndpoint = $state('');
|
||||
|
||||
let importFileInput: HTMLInputElement | undefined = $state();
|
||||
let loadError = $state('');
|
||||
|
||||
onMount(async () => {
|
||||
const [version, rawAutoUpdate, rawRelays, rawTimeout, rawLog, rawOtlp] = await Promise.all([
|
||||
const results = await Promise.allSettled([
|
||||
getCurrentVersion(),
|
||||
getSetting('plugin_auto_update'),
|
||||
getSetting('relay_urls'),
|
||||
|
|
@ -30,19 +32,21 @@
|
|||
getSetting('log_level'),
|
||||
getSetting('otlp_endpoint'),
|
||||
]);
|
||||
appVersion = version;
|
||||
pluginAutoUpdate = rawAutoUpdate === 'true';
|
||||
relayUrls = rawRelays ?? '';
|
||||
connectionTimeout = rawTimeout ? parseInt(rawTimeout, 10) : 30;
|
||||
if (rawLog === 'trace' || rawLog === 'debug' || rawLog === 'info' || rawLog === 'warn' || rawLog === 'error') logLevel = rawLog;
|
||||
otlpEndpoint = rawOtlp ?? '';
|
||||
let failCount = 0;
|
||||
if (results[0].status === 'fulfilled') appVersion = results[0].value; else failCount++;
|
||||
if (results[1].status === 'fulfilled') pluginAutoUpdate = results[1].value === 'true'; else failCount++;
|
||||
if (results[2].status === 'fulfilled') relayUrls = results[2].value ?? ''; else failCount++;
|
||||
if (results[3].status === 'fulfilled') connectionTimeout = results[3].value ? parseInt(results[3].value, 10) : 30; else failCount++;
|
||||
if (results[4].status === 'fulfilled') { const v = results[4].value; if (v === 'trace' || v === 'debug' || v === 'info' || v === 'warn' || v === 'error') logLevel = v; } else failCount++;
|
||||
if (results[5].status === 'fulfilled') otlpEndpoint = results[5].value ?? ''; else failCount++;
|
||||
if (failCount === results.length) loadError = 'Could not load settings. Displaying defaults.';
|
||||
|
||||
const ts = getLastCheckTimestamp();
|
||||
if (ts) updateLastCheck = new Date(ts).toLocaleString();
|
||||
});
|
||||
|
||||
async function save(key: string, value: string) {
|
||||
try { await setSetting(key, value); } catch (e) { console.error(`Failed to save ${key}:`, e); }
|
||||
try { await setSetting(key, value); } catch (e) { handleError(e, `settings.save.${key}`, 'save your settings'); }
|
||||
}
|
||||
|
||||
async function handleCheckForUpdates() {
|
||||
|
|
@ -52,14 +56,14 @@
|
|||
const ts = getLastCheckTimestamp();
|
||||
if (ts) updateLastCheck = new Date(ts).toLocaleString();
|
||||
} catch (e) {
|
||||
console.error('Update check failed:', e);
|
||||
handleError(e, 'settings.updates.check', 'check for updates');
|
||||
} finally {
|
||||
updateChecking = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleReloadPlugins() {
|
||||
try { await reloadAllPlugins(); } catch (e) { console.error('Plugin reload failed:', e); }
|
||||
try { await reloadAllPlugins(); } catch (e) { handleError(e, 'settings.plugins.reload', 'reload plugins'); }
|
||||
}
|
||||
|
||||
async function handleExport() {
|
||||
|
|
@ -97,12 +101,13 @@
|
|||
if (typeof value === 'string') await setSetting(key, value);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Settings import failed:', err);
|
||||
handleError(err, 'settings.import', 'import settings');
|
||||
}
|
||||
input.value = '';
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if loadError}<p class="load-error">{loadError}</p>{/if}
|
||||
<section class="section">
|
||||
<h2>Plugins</h2>
|
||||
{#if pluginEntries.length === 0}
|
||||
|
|
@ -239,6 +244,7 @@
|
|||
</section>
|
||||
|
||||
<style>
|
||||
.load-error { font-size: 0.75rem; color: var(--ctp-peach); margin: 0 0 0.25rem; }
|
||||
h2 { font-size: 0.875rem; font-weight: 600; color: var(--ctp-text); margin: 0 0 0.625rem; padding-bottom: 0.375rem; border-bottom: 1px solid var(--ctp-surface0); }
|
||||
.section { margin-bottom: 1.25rem; }
|
||||
.fields { display: flex; flex-direction: column; gap: 0.625rem; }
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
import { getProviders } from '../../providers/registry.svelte';
|
||||
import type { ProviderSettings } from '../../providers/types';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { handleError, handleInfraError } from '../../utils/handle-error';
|
||||
|
||||
let defaultShell = $state('');
|
||||
let defaultCwd = $state('');
|
||||
|
|
@ -17,29 +18,35 @@
|
|||
let contextPressureCritical = $state(90);
|
||||
let monthlyTokenBudget = $state('');
|
||||
let routerProfile = $state<'cost_saver' | 'balanced' | 'quality_first'>('balanced');
|
||||
let loadError = $state('');
|
||||
|
||||
onMount(async () => {
|
||||
const [shell, cwd, saveOnBlur, rawProv, rawPerm, rawPrompt, rawWarn, rawCrit, rawBudget, rawRouter] = await Promise.all([
|
||||
const results = await Promise.allSettled([
|
||||
getSetting('default_shell'), getSetting('default_cwd'), getSetting('files_save_on_blur'),
|
||||
getSetting('provider_settings'), getSetting('default_permission_mode'),
|
||||
getSetting('system_prompt_template'), getSetting('context_pressure_warn'),
|
||||
getSetting('context_pressure_critical'), getSetting('budget_monthly_tokens'),
|
||||
getSetting('router_profile'),
|
||||
]);
|
||||
defaultShell = shell ?? '';
|
||||
defaultCwd = cwd ?? '';
|
||||
filesSaveOnBlur = saveOnBlur === 'true';
|
||||
try { if (rawProv) providerSettings = JSON.parse(rawProv); } catch { providerSettings = {}; }
|
||||
if (rawPerm === 'bypassPermissions' || rawPerm === 'default' || rawPerm === 'plan') permissionMode = rawPerm;
|
||||
systemPromptTemplate = rawPrompt ?? '';
|
||||
contextPressureWarn = rawWarn ? parseInt(rawWarn, 10) : 75;
|
||||
contextPressureCritical = rawCrit ? parseInt(rawCrit, 10) : 90;
|
||||
monthlyTokenBudget = rawBudget ?? '';
|
||||
if (rawRouter === 'cost_saver' || rawRouter === 'balanced' || rawRouter === 'quality_first') routerProfile = rawRouter;
|
||||
let failCount = 0;
|
||||
if (results[0].status === 'fulfilled') defaultShell = results[0].value ?? ''; else failCount++;
|
||||
if (results[1].status === 'fulfilled') defaultCwd = results[1].value ?? ''; else failCount++;
|
||||
if (results[2].status === 'fulfilled') filesSaveOnBlur = results[2].value === 'true'; else failCount++;
|
||||
if (results[3].status === 'fulfilled') {
|
||||
const rawProv = results[3].value;
|
||||
try { if (rawProv) providerSettings = JSON.parse(rawProv); } catch (e) { handleInfraError(e, 'settings.parse.provider_settings'); providerSettings = {}; }
|
||||
} else failCount++;
|
||||
if (results[4].status === 'fulfilled') { const v = results[4].value; if (v === 'bypassPermissions' || v === 'default' || v === 'plan') permissionMode = v; } else failCount++;
|
||||
if (results[5].status === 'fulfilled') systemPromptTemplate = results[5].value ?? ''; else failCount++;
|
||||
if (results[6].status === 'fulfilled') contextPressureWarn = results[6].value ? parseInt(results[6].value, 10) : 75; else failCount++;
|
||||
if (results[7].status === 'fulfilled') contextPressureCritical = results[7].value ? parseInt(results[7].value, 10) : 90; else failCount++;
|
||||
if (results[8].status === 'fulfilled') monthlyTokenBudget = results[8].value ?? ''; else failCount++;
|
||||
if (results[9].status === 'fulfilled') { const v = results[9].value; if (v === 'cost_saver' || v === 'balanced' || v === 'quality_first') routerProfile = v; } else failCount++;
|
||||
if (failCount === results.length) loadError = 'Could not load settings. Displaying defaults.';
|
||||
});
|
||||
|
||||
async function save(key: string, value: string) {
|
||||
try { await setSetting(key, value); } catch (e) { console.error(`Failed to save ${key}:`, e); }
|
||||
try { await setSetting(key, value); } catch (e) { handleError(e, `settings.save.${key}`, 'save your settings'); }
|
||||
}
|
||||
|
||||
async function browseDirectory(): Promise<string | null> {
|
||||
|
|
@ -65,6 +72,7 @@
|
|||
function isProviderEnabled(id: string): boolean { return providerSettings[id]?.enabled ?? true; }
|
||||
</script>
|
||||
|
||||
{#if loadError}<p class="load-error">{loadError}</p>{/if}
|
||||
<section class="section">
|
||||
<h2>Defaults</h2>
|
||||
<div class="fields">
|
||||
|
|
@ -206,6 +214,7 @@
|
|||
</section>
|
||||
|
||||
<style>
|
||||
.load-error { font-size: 0.75rem; color: var(--ctp-peach); margin: 0 0 0.25rem; }
|
||||
h2 { font-size: 0.875rem; font-weight: 600; color: var(--ctp-text); margin: 0 0 0.625rem; padding-bottom: 0.375rem; border-bottom: 1px solid var(--ctp-surface0); }
|
||||
.section { margin-bottom: 1.25rem; }
|
||||
.fields { display: flex; flex-direction: column; gap: 0.625rem; }
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { getCurrentTheme, setTheme } from '../../stores/theme.svelte';
|
||||
import { THEME_LIST, getPalette, type ThemeId } from '../../styles/themes';
|
||||
import { getSetting, setSetting } from '../../adapters/settings-bridge';
|
||||
import { handleError } from '../../utils/handle-error';
|
||||
|
||||
const UI_FONTS = [
|
||||
{ value: '', label: 'System Default' },
|
||||
|
|
@ -36,6 +37,7 @@
|
|||
let cursorStyle = $state('block');
|
||||
let cursorBlink = $state(true);
|
||||
let scrollbackLines = $state('1000');
|
||||
let loadError = $state('');
|
||||
let themeOpen = $state(false);
|
||||
let uiFontOpen = $state(false);
|
||||
let termFontOpen = $state(false);
|
||||
|
|
@ -55,48 +57,53 @@
|
|||
function css(prop: string, val: string) { document.documentElement.style.setProperty(prop, val); }
|
||||
|
||||
onMount(async () => {
|
||||
const [font, size, tfont, tsize, cursor, blink, scroll] = await Promise.all([
|
||||
const results = await Promise.allSettled([
|
||||
getSetting('ui_font_family'), getSetting('ui_font_size'),
|
||||
getSetting('term_font_family'), getSetting('term_font_size'),
|
||||
getSetting('term_cursor_style'), getSetting('term_cursor_blink'),
|
||||
getSetting('term_scrollback'),
|
||||
]);
|
||||
if (font) uiFont = font;
|
||||
if (size) uiFontSize = size;
|
||||
if (tfont) termFont = tfont;
|
||||
if (tsize) termFontSize = tsize;
|
||||
if (cursor) cursorStyle = cursor;
|
||||
if (blink !== null) cursorBlink = blink !== 'false';
|
||||
if (scroll) scrollbackLines = scroll;
|
||||
let failCount = 0;
|
||||
if (results[0].status === 'fulfilled' && results[0].value) uiFont = results[0].value; else failCount++;
|
||||
if (results[1].status === 'fulfilled' && results[1].value) uiFontSize = results[1].value; else failCount++;
|
||||
if (results[2].status === 'fulfilled' && results[2].value) termFont = results[2].value; else failCount++;
|
||||
if (results[3].status === 'fulfilled' && results[3].value) termFontSize = results[3].value; else failCount++;
|
||||
if (results[4].status === 'fulfilled' && results[4].value) cursorStyle = results[4].value; else failCount++;
|
||||
if (results[5].status === 'fulfilled') cursorBlink = results[5].value !== 'false'; else failCount++;
|
||||
if (results[6].status === 'fulfilled' && results[6].value) scrollbackLines = results[6].value; else failCount++;
|
||||
if (failCount === results.length) loadError = 'Could not load settings. Displaying defaults.';
|
||||
});
|
||||
|
||||
function pickTheme(id: ThemeId) { selectedTheme = id; themeOpen = false; setTheme(id); }
|
||||
async function save(key: string, value: string) {
|
||||
try { await setSetting(key, value); } catch (e) { handleError(e, `settings.save.${key}`, 'save your settings'); }
|
||||
}
|
||||
function pickUiFont(val: string) {
|
||||
uiFont = val; uiFontOpen = false;
|
||||
css('--ui-font-family', val ? `'${val}', sans-serif` : 'system-ui, sans-serif');
|
||||
setSetting('ui_font_family', val);
|
||||
save('ui_font_family', val);
|
||||
}
|
||||
function pickTermFont(val: string) {
|
||||
termFont = val; termFontOpen = false;
|
||||
css('--term-font-family', val ? `'${val}', monospace` : `'JetBrains Mono', monospace`);
|
||||
setSetting('term_font_family', val);
|
||||
save('term_font_family', val);
|
||||
}
|
||||
function stepUiSize(delta: number) {
|
||||
const n = parseInt(uiFontSize, 10) + delta;
|
||||
if (n < 8 || n > 24) return;
|
||||
uiFontSize = String(n); css('--ui-font-size', `${n}px`); setSetting('ui_font_size', String(n));
|
||||
uiFontSize = String(n); css('--ui-font-size', `${n}px`); save('ui_font_size', String(n));
|
||||
}
|
||||
function stepTermSize(delta: number) {
|
||||
const n = parseInt(termFontSize, 10) + delta;
|
||||
if (n < 8 || n > 24) return;
|
||||
termFontSize = String(n); css('--term-font-size', `${n}px`); setSetting('term_font_size', String(n));
|
||||
termFontSize = String(n); css('--term-font-size', `${n}px`); save('term_font_size', String(n));
|
||||
}
|
||||
function setCursor(style: string) { cursorStyle = style; setSetting('term_cursor_style', style); }
|
||||
function toggleBlink() { cursorBlink = !cursorBlink; setSetting('term_cursor_blink', String(cursorBlink)); }
|
||||
function setCursor(style: string) { cursorStyle = style; save('term_cursor_style', style); }
|
||||
function toggleBlink() { cursorBlink = !cursorBlink; save('term_cursor_blink', String(cursorBlink)); }
|
||||
function setScrollback(val: string) {
|
||||
const n = parseInt(val, 10);
|
||||
if (isNaN(n) || n < 100 || n > 100000) return;
|
||||
scrollbackLines = val; setSetting('term_scrollback', val);
|
||||
scrollbackLines = val; save('term_scrollback', val);
|
||||
}
|
||||
|
||||
function closeDropdowns() { themeOpen = false; uiFontOpen = false; termFontOpen = false; }
|
||||
|
|
@ -112,6 +119,7 @@
|
|||
</script>
|
||||
|
||||
<div class="appearance" onclick={handleClick} onkeydown={handleKey}>
|
||||
{#if loadError}<p class="load-error">{loadError}</p>{/if}
|
||||
<h3>Theme</h3>
|
||||
<div class="field" id="setting-theme">
|
||||
<div class="custom-dropdown">
|
||||
|
|
@ -206,6 +214,7 @@
|
|||
</div>
|
||||
|
||||
<style>
|
||||
.load-error { font-size: 0.75rem; color: var(--ctp-peach); margin: 0 0 0.25rem; }
|
||||
.appearance { display: flex; flex-direction: column; gap: 0.75rem; }
|
||||
h3 { font-size: 0.75rem; font-weight: 600; color: var(--ctp-subtext0); text-transform: uppercase; letter-spacing: 0.05em; margin: 0.5rem 0 0.125rem; }
|
||||
.field { position: relative; }
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { getSetting, setSetting } from '../../adapters/settings-bridge';
|
||||
import { ANCHOR_BUDGET_SCALES, ANCHOR_BUDGET_SCALE_LABELS, type AnchorBudgetScale } from '../../types/anchors';
|
||||
import { WAKE_STRATEGIES, WAKE_STRATEGY_LABELS, WAKE_STRATEGY_DESCRIPTIONS, type WakeStrategy } from '../../types/wake';
|
||||
import { handleError, handleInfraError } from '../../utils/handle-error';
|
||||
|
||||
let stallThreshold = $state(15);
|
||||
let anchorBudget = $state<AnchorBudgetScale>('medium');
|
||||
|
|
@ -13,28 +14,34 @@
|
|||
let notifTypes = $state<Set<string>>(new Set(['complete', 'error', 'crash', 'stall']));
|
||||
let memoryTtl = $state('');
|
||||
let memoryExtract = $state(false);
|
||||
let loadError = $state('');
|
||||
|
||||
const NOTIF_TYPE_OPTIONS = ['complete', 'error', 'crash', 'stall'] as const;
|
||||
|
||||
onMount(async () => {
|
||||
const [rawStall, rawBudget, rawAutoAnchor, rawWake, rawThresh, rawDesktop, rawTypes, rawTtl, rawExtract] = await Promise.all([
|
||||
const results = await Promise.allSettled([
|
||||
getSetting('stall_threshold'), getSetting('anchor_budget_scale'), getSetting('auto_anchor'),
|
||||
getSetting('wake_strategy'), getSetting('wake_threshold'), getSetting('notif_desktop'),
|
||||
getSetting('notif_types'), getSetting('memory_ttl'), getSetting('memory_extract'),
|
||||
]);
|
||||
if (rawStall) { const n = parseInt(rawStall, 10); if (!isNaN(n)) stallThreshold = n; }
|
||||
if (rawBudget && ANCHOR_BUDGET_SCALES.includes(rawBudget as AnchorBudgetScale)) anchorBudget = rawBudget as AnchorBudgetScale;
|
||||
autoAnchor = rawAutoAnchor !== 'false';
|
||||
if (rawWake && WAKE_STRATEGIES.includes(rawWake as WakeStrategy)) wakeStrategy = rawWake as WakeStrategy;
|
||||
if (rawThresh) { const n = parseInt(rawThresh, 10); if (!isNaN(n)) wakeThreshold = n; }
|
||||
notifDesktop = rawDesktop !== 'false';
|
||||
if (rawTypes) { try { const arr = JSON.parse(rawTypes); if (Array.isArray(arr)) notifTypes = new Set(arr); } catch { /* keep default */ } }
|
||||
memoryTtl = rawTtl ?? '';
|
||||
memoryExtract = rawExtract === 'true';
|
||||
let failCount = 0;
|
||||
if (results[0].status === 'fulfilled') { const v = results[0].value; if (v) { const n = parseInt(v, 10); if (!isNaN(n)) stallThreshold = n; } } else failCount++;
|
||||
if (results[1].status === 'fulfilled') { const v = results[1].value; if (v && ANCHOR_BUDGET_SCALES.includes(v as AnchorBudgetScale)) anchorBudget = v as AnchorBudgetScale; } else failCount++;
|
||||
if (results[2].status === 'fulfilled') autoAnchor = results[2].value !== 'false'; else failCount++;
|
||||
if (results[3].status === 'fulfilled') { const v = results[3].value; if (v && WAKE_STRATEGIES.includes(v as WakeStrategy)) wakeStrategy = v as WakeStrategy; } else failCount++;
|
||||
if (results[4].status === 'fulfilled') { const v = results[4].value; if (v) { const n = parseInt(v, 10); if (!isNaN(n)) wakeThreshold = n; } } else failCount++;
|
||||
if (results[5].status === 'fulfilled') notifDesktop = results[5].value !== 'false'; else failCount++;
|
||||
if (results[6].status === 'fulfilled') {
|
||||
const v = results[6].value;
|
||||
if (v) { try { const arr = JSON.parse(v); if (Array.isArray(arr)) notifTypes = new Set(arr); } catch (e) { handleInfraError(e, 'settings.parse.notif_types'); /* keep default */ } }
|
||||
} else failCount++;
|
||||
if (results[7].status === 'fulfilled') memoryTtl = results[7].value ?? ''; else failCount++;
|
||||
if (results[8].status === 'fulfilled') memoryExtract = results[8].value === 'true'; else failCount++;
|
||||
if (failCount === results.length) loadError = 'Could not load settings. Displaying defaults.';
|
||||
});
|
||||
|
||||
async function save(key: string, value: string) {
|
||||
try { await setSetting(key, value); } catch (e) { console.error(`Failed to save ${key}:`, e); }
|
||||
try { await setSetting(key, value); } catch (e) { handleError(e, `settings.save.${key}`, 'save your settings'); }
|
||||
}
|
||||
|
||||
function toggleNotifType(type: string) {
|
||||
|
|
@ -45,6 +52,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
{#if loadError}<p class="load-error">{loadError}</p>{/if}
|
||||
<section class="section">
|
||||
<h2>Health Monitoring</h2>
|
||||
<div class="fields">
|
||||
|
|
@ -174,6 +182,7 @@
|
|||
</section>
|
||||
|
||||
<style>
|
||||
.load-error { font-size: 0.75rem; color: var(--ctp-peach); margin: 0 0 0.25rem; }
|
||||
h2 { font-size: 0.875rem; font-weight: 600; color: var(--ctp-text); margin: 0 0 0.625rem; padding-bottom: 0.375rem; border-bottom: 1px solid var(--ctp-surface0); }
|
||||
.section { margin-bottom: 1.25rem; }
|
||||
.fields { display: flex; flex-direction: column; gap: 0.625rem; }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { handleError } from '../../utils/handle-error';
|
||||
import {
|
||||
getActiveGroupId, getActiveGroup, getAllGroups,
|
||||
addProject, removeProject, addGroup, removeGroup, switchGroup,
|
||||
|
|
@ -41,7 +41,7 @@
|
|||
|
||||
async function browseDir(): Promise<string | null> {
|
||||
try { return await invoke<string | null>('pick_directory'); }
|
||||
catch { return null; }
|
||||
catch (e) { handleError(e, 'settings.browseDir', 'browse for a directory'); return null; }
|
||||
}
|
||||
|
||||
async function browseProjectCwd() {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import { onMount } from 'svelte';
|
||||
import { getSetting, setSetting } from '../../adapters/settings-bridge';
|
||||
import { storeSecret, getSecret, deleteSecret, listSecrets, hasKeyring, knownSecretKeys, SECRET_KEY_LABELS } from '../../adapters/secrets-bridge';
|
||||
import { handleError, handleInfraError } from '../../utils/handle-error';
|
||||
|
||||
let keyringAvailable = $state(false);
|
||||
let storedKeys = $state<string[]>([]);
|
||||
|
|
@ -17,6 +18,7 @@
|
|||
let newBranchAction = $state<'block' | 'warn'>('warn');
|
||||
let telemetryEnabled = $state(false);
|
||||
let retentionDays = $state(90);
|
||||
let loadError = $state('');
|
||||
|
||||
let availableKeysForAdd = $derived(knownKeys.filter(k => !storedKeys.includes(k)));
|
||||
let newSecretKeyLabel = $derived(newSecretKey ? getSecretKeyLabel(newSecretKey) : 'Select key...');
|
||||
|
|
@ -25,35 +27,44 @@
|
|||
try {
|
||||
keyringAvailable = await hasKeyring();
|
||||
if (keyringAvailable) { storedKeys = await listSecrets(); knownKeys = await knownSecretKeys(); }
|
||||
} catch { keyringAvailable = false; }
|
||||
const [rawPolicies, rawTelemetry, rawRetention] = await Promise.all([
|
||||
} catch (e) {
|
||||
// Keyring unavailable is expected on some systems — set state explicitly
|
||||
handleInfraError(e, 'settings.keyring.init');
|
||||
keyringAvailable = false;
|
||||
}
|
||||
const results = await Promise.allSettled([
|
||||
getSetting('branch_policies'), getSetting('telemetry_enabled'), getSetting('data_retention_days'),
|
||||
]);
|
||||
if (rawPolicies) { try { branchPolicies = JSON.parse(rawPolicies); } catch { branchPolicies = []; } }
|
||||
telemetryEnabled = rawTelemetry === 'true';
|
||||
retentionDays = rawRetention ? parseInt(rawRetention, 10) : 90;
|
||||
let failCount = 0;
|
||||
if (results[0].status === 'fulfilled') {
|
||||
const v = results[0].value;
|
||||
if (v) { try { branchPolicies = JSON.parse(v); } catch (e) { handleInfraError(e, 'settings.parse.branch_policies'); branchPolicies = []; } }
|
||||
} else failCount++;
|
||||
if (results[1].status === 'fulfilled') telemetryEnabled = results[1].value === 'true'; else failCount++;
|
||||
if (results[2].status === 'fulfilled') retentionDays = results[2].value ? parseInt(results[2].value, 10) : 90; else failCount++;
|
||||
if (failCount === results.length) loadError = 'Could not load settings. Displaying defaults.';
|
||||
});
|
||||
|
||||
async function save(key: string, value: string) {
|
||||
try { await setSetting(key, value); } catch (e) { console.error(`Failed to save ${key}:`, e); }
|
||||
try { await setSetting(key, value); } catch (e) { handleError(e, `settings.save.${key}`, 'save your settings'); }
|
||||
}
|
||||
function getSecretKeyLabel(key: string): string { return SECRET_KEY_LABELS[key] ?? key; }
|
||||
|
||||
async function handleRevealSecret(key: string) {
|
||||
if (revealedKey === key) { revealedKey = null; revealedValue = ''; return; }
|
||||
try { const val = await getSecret(key); revealedKey = key; revealedValue = val ?? ''; }
|
||||
catch (e) { console.error(`Failed to reveal secret '${key}':`, e); }
|
||||
catch (e) { handleError(e, `settings.secrets.reveal.${key}`, 'reveal the secret'); }
|
||||
}
|
||||
async function handleSaveSecret() {
|
||||
if (!newSecretKey || !newSecretValue) return;
|
||||
secretsSaving = true;
|
||||
try { await storeSecret(newSecretKey, newSecretValue); storedKeys = await listSecrets(); newSecretKey = ''; newSecretValue = ''; revealedKey = null; revealedValue = ''; }
|
||||
catch (e) { console.error('Failed to store secret:', e); }
|
||||
catch (e) { handleError(e, 'settings.secrets.store', 'store the secret'); }
|
||||
finally { secretsSaving = false; }
|
||||
}
|
||||
async function handleDeleteSecret(key: string) {
|
||||
try { await deleteSecret(key); storedKeys = await listSecrets(); if (revealedKey === key) { revealedKey = null; revealedValue = ''; } }
|
||||
catch (e) { console.error(`Failed to delete secret '${key}':`, e); }
|
||||
catch (e) { handleError(e, `settings.secrets.delete.${key}`, 'delete the secret'); }
|
||||
}
|
||||
function addBranchPolicy() {
|
||||
if (!newBranchPattern.trim()) return;
|
||||
|
|
@ -71,6 +82,7 @@
|
|||
|
||||
<svelte:window onclick={handleClickOutside} onkeydown={handleKeydown} />
|
||||
|
||||
{#if loadError}<p class="load-error">{loadError}</p>{/if}
|
||||
<section class="section">
|
||||
<h2>Secrets</h2>
|
||||
<div class="secrets-status">
|
||||
|
|
@ -194,6 +206,7 @@
|
|||
</section>
|
||||
|
||||
<style>
|
||||
.load-error { font-size: 0.75rem; color: var(--ctp-peach); margin: 0 0 0.25rem; }
|
||||
h2 { font-size: 0.875rem; font-weight: 600; color: var(--ctp-text); margin: 0 0 0.625rem; padding-bottom: 0.375rem; border-bottom: 1px solid var(--ctp-surface0); }
|
||||
.section { margin-bottom: 1.25rem; }
|
||||
.fields { display: flex; flex-direction: column; gap: 0.625rem; }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue