ba199f24bd
- Defer quiet-hours dispatches into new deferred_dispatch table; drain job + periodic catch-up scan re-fire at window end with coalescing on (link, event_type, collection_id). - Add ON DELETE SET NULL migration on event_log_id and partial unique index on (link_id, collection_id, event_type) WHERE status='pending'. - Add release-check provider abstraction (Gitea/GitHub) with SSRF-safe URL validation, settings UI cassette, and scheduled polling. - Replace importlib-only version lookup with version.py helper that prefers the higher of installed metadata vs source pyproject so stale editable dev installs stop misreporting. - Aurora frontend polish: MetaStrip component, ReleaseCassette, EventDetailModal expansion, and i18n additions.
109 lines
3.0 KiB
Svelte
109 lines
3.0 KiB
Svelte
<script lang="ts">
|
|
import { onMount, onDestroy } from 'svelte';
|
|
import { t } from '$lib/i18n';
|
|
import PageHeader, { type HeaderPill } from '$lib/components/PageHeader.svelte';
|
|
import { releaseStatusCache } from '$lib/stores/caches.svelte';
|
|
|
|
type Tone = 'mint' | 'sky' | 'orchid' | 'coral' | 'citrus' | 'primary';
|
|
|
|
interface Settings {
|
|
external_url: string;
|
|
timezone: string;
|
|
supported_locales: string;
|
|
log_level: string;
|
|
log_format: string;
|
|
}
|
|
|
|
interface Props {
|
|
settings: Settings;
|
|
}
|
|
|
|
let { settings }: Props = $props();
|
|
|
|
// Live tick so the timezone pill shows the current local HH:MM.
|
|
let now = $state(new Date());
|
|
let tick: ReturnType<typeof setInterval> | null = null;
|
|
onMount(() => { tick = setInterval(() => { now = new Date(); }, 30_000); });
|
|
onDestroy(() => { if (tick) clearInterval(tick); });
|
|
|
|
function fmtClock(tz: string): string {
|
|
try {
|
|
return new Intl.DateTimeFormat('en-GB', {
|
|
timeZone: tz || 'UTC',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
hour12: false,
|
|
}).format(now);
|
|
} catch { return '--:--'; }
|
|
}
|
|
|
|
function hostFromUrl(url: string): string {
|
|
if (!url) return '';
|
|
try { return new URL(url).host; }
|
|
catch { return url.replace(/^https?:\/\//, '').replace(/\/$/, ''); }
|
|
}
|
|
|
|
function localeCount(csv: string): number {
|
|
if (!csv) return 0;
|
|
return csv.split(',').map(s => s.trim()).filter(Boolean).length;
|
|
}
|
|
|
|
const SEVERITY_TONE: Record<string, Tone> = {
|
|
DEBUG: 'sky',
|
|
INFO: 'mint',
|
|
WARNING: 'citrus',
|
|
ERROR: 'coral',
|
|
};
|
|
|
|
const pills = $derived.by<HeaderPill[]>(() => {
|
|
const out: HeaderPill[] = [];
|
|
|
|
const host = hostFromUrl(settings.external_url);
|
|
out.push(host
|
|
? { label: host, tone: 'sky' }
|
|
: { label: t('settings.heroNoUrl') }
|
|
);
|
|
|
|
const tz = settings.timezone || 'UTC';
|
|
out.push({ label: `${tz} · ${fmtClock(tz)}`, tone: 'primary' });
|
|
|
|
const locales = settings.supported_locales || '';
|
|
const count = localeCount(locales);
|
|
out.push({
|
|
label: count > 0
|
|
? locales.split(',').map(s => s.trim()).filter(Boolean).map(s => s.toUpperCase()).join(' · ')
|
|
: t('settings.heroNoLocales'),
|
|
tone: 'orchid',
|
|
});
|
|
|
|
const lvl = (settings.log_level || 'INFO').toUpperCase();
|
|
out.push({
|
|
label: `${lvl} · ${settings.log_format || 'text'}`,
|
|
tone: SEVERITY_TONE[lvl] ?? 'mint',
|
|
});
|
|
|
|
const rs = releaseStatusCache.value;
|
|
if (rs) {
|
|
if (rs.provider === 'disabled') {
|
|
out.push({ label: t('settings.release.statusDisabled'), tone: 'sky' });
|
|
} else if (rs.error && rs.error !== 'provider_changed') {
|
|
out.push({ label: t('settings.release.statusError'), tone: 'coral' });
|
|
} else if (rs.update_available && rs.latest) {
|
|
out.push({ label: `v${rs.latest} ${t('settings.release.heroAvailable')}`, tone: 'citrus' });
|
|
} else if (rs.latest) {
|
|
out.push({ label: t('settings.release.statusUpToDate'), tone: 'mint' });
|
|
}
|
|
}
|
|
|
|
return out;
|
|
});
|
|
</script>
|
|
|
|
<PageHeader
|
|
title={t('settings.title')}
|
|
emphasis={t('settings.titleEmphasis')}
|
|
description={t('settings.description')}
|
|
crumb={t('crumbs.systemConfiguration')}
|
|
{pills}
|
|
/>
|