feat: deferred dispatch, release-check provider, settings polish

- 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.
This commit is contained in:
2026-05-12 02:58:07 +03:00
parent bb5afcc222
commit ba199f24bd
47 changed files with 5627 additions and 290 deletions
@@ -22,6 +22,7 @@
import { globalProviderFilter } from '$lib/stores/provider-filter.svelte';
import ErrorBanner from '$lib/components/ErrorBanner.svelte';
import { getDescriptor } from '$lib/providers';
import MetaStrip, { type MetaTile } from '$lib/components/MetaStrip.svelte';
import type { CommandConfig } from '$lib/types';
function templateName(id: number | null): string {
@@ -108,6 +109,42 @@
finally { loaded = true; highlightFromUrl(); }
}
function commandConfigTiles(cfg: CommandConfig): MetaTile[] {
const tiles: MetaTile[] = [];
tiles.push({
icon: 'mdiServer',
label: cfg.provider_type,
tone: 'lavender',
mono: true,
});
const cmdCount = (cfg.enabled_commands || []).length;
tiles.push({
icon: 'mdiSlashForward',
value: String(cmdCount),
label: t('commandConfig.commands'),
tone: cmdCount > 0 ? 'mint' : 'coral',
});
tiles.push({
icon: cfg.response_mode === 'media' ? 'mdiImageOutline' : 'mdiTextBoxOutline',
label: cfg.response_mode === 'media' ? t('commandConfig.modeMedia') : t('commandConfig.modeText'),
tone: 'sky',
});
tiles.push({
icon: 'mdiNumeric',
value: String(cfg.default_count),
label: t('commandConfig.defaultCount'),
tone: 'citrus',
});
if (cfg.command_template_config_id) {
tiles.push({
icon: 'mdiCodeBracesBox',
label: templateName(cfg.command_template_config_id),
tone: 'orchid',
});
}
return tiles;
}
function openNew() {
form = defaultForm();
// Auto-select first provider type with commands
@@ -316,22 +353,20 @@
<EmptyState icon="mdiFilterOff" message={t('common.noFilterResults')} />
</Card>
{:else}
<div class="space-y-3 stagger-children">
<div class="list-stack stagger-children">
{#each configs as cfg}
<Card hover entityId={cfg.id}>
<div class="flex items-center justify-between">
<div>
<div class="flex items-center gap-2">
<span style="color: var(--color-primary);"><MdiIcon name={cfg.icon || 'mdiConsoleLine'} size={20} /></span>
<p class="font-medium">{cfg.name}</p>
<span class="text-xs px-1.5 py-0.5 rounded bg-[var(--color-muted)] text-[var(--color-muted-foreground)] font-mono">{cfg.provider_type}</span>
<span class="text-xs px-1.5 py-0.5 rounded bg-[var(--color-success-bg)] text-[var(--color-success-fg)] font-mono">
{(cfg.enabled_commands || []).length} {t('commandConfig.commands')}
</span>
<div class="list-row">
<div class="list-row__identity">
<div class="flex items-center gap-2 min-w-0">
<span style="color: var(--color-primary);" class="shrink-0"><MdiIcon name={cfg.icon || 'mdiConsoleLine'} size={20} /></span>
<p class="font-medium truncate">{cfg.name}</p>
<span class="text-xs px-1.5 py-0.5 rounded bg-[var(--color-muted)] text-[var(--color-muted-foreground)] font-mono shrink-0">{cfg.provider_type}</span>
</div>
<div class="flex items-center gap-2 mt-0.5">
<div class="flex items-center gap-2 mt-0.5 list-row__secondary">
<span class="text-xs text-[var(--color-muted-foreground)]">
{t('commandConfig.responseMode')}: {cfg.response_mode === 'media' ? t('commandConfig.modeMedia') : t('commandConfig.modeText')}
{(cfg.enabled_commands || []).length} {t('commandConfig.commands')}
&middot; {t('commandConfig.responseMode')}: {cfg.response_mode === 'media' ? t('commandConfig.modeMedia') : t('commandConfig.modeText')}
&middot; {t('commandConfig.defaultCount')}: {cfg.default_count}
</span>
{#if cfg.command_template_config_id}
@@ -339,7 +374,8 @@
{/if}
</div>
</div>
<div class="flex items-center gap-1">
<MetaStrip tiles={commandConfigTiles(cfg)} />
<div class="list-row__actions">
<IconButton icon="mdiPencil" title={t('common.edit')} onclick={() => editConfig(cfg)} />
<IconButton icon="mdiDelete" title={t('common.delete')} onclick={() => remove(cfg)} variant="danger" />
</div>