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
+34 -8
View File
@@ -13,6 +13,7 @@
import { snackSuccess, snackError } from '$lib/stores/snackbar.svelte';
import Button from '$lib/components/Button.svelte';
import ErrorBanner from '$lib/components/ErrorBanner.svelte';
import MetaStrip, { type MetaTile } from '$lib/components/MetaStrip.svelte';
import type { EmailBot } from '$lib/types';
let { onreload }: { onreload: () => Promise<void> } = $props();
@@ -39,6 +40,30 @@
}
});
function emailBotTiles(bot: EmailBot): MetaTile[] {
const tiles: MetaTile[] = [];
tiles.push({
icon: 'mdiEmailOutline',
label: bot.email,
tone: 'lavender',
mono: true,
});
tiles.push({
icon: 'mdiServerNetwork',
label: `${bot.smtp_host}:${bot.smtp_port}`,
tone: 'sky',
mono: true,
});
if (bot.smtp_use_tls) {
tiles.push({
icon: 'mdiLockOutline',
label: 'TLS',
tone: 'mint',
});
}
return tiles;
}
function openNewEmail() { emailForm = defaultEmailForm(); nameManuallyEdited = false; editingEmail = null; showEmailForm = true; }
function editEmailBot(bot: EmailBot) {
emailForm = {
@@ -165,16 +190,16 @@
<EmptyState icon="mdiEmailOutline" message={t('emailBot.noBots')} />
</Card>
{:else}
<div class="space-y-3 stagger-children">
<div class="list-stack stagger-children">
{#each emailBots as bot}
<Card hover entityId={bot.id}>
<div class="flex items-center justify-between">
<div>
<div class="flex items-center gap-2">
<span style="color: var(--color-primary);"><MdiIcon name={bot.icon || 'mdiEmailOutline'} size={20} /></span>
<p class="font-medium">{bot.name}</p>
<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={bot.icon || 'mdiEmailOutline'} size={20} /></span>
<p class="font-medium truncate">{bot.name}</p>
</div>
<div class="flex items-center gap-2 mt-1 flex-wrap">
<div class="flex items-center gap-2 mt-1 flex-wrap list-row__secondary">
<span class="text-xs text-[var(--color-muted-foreground)] font-mono">{bot.email}</span>
<span class="text-xs px-1.5 py-0.5 rounded bg-[var(--color-muted)] text-[var(--color-muted-foreground)]">{bot.smtp_host}:{bot.smtp_port}</span>
{#if bot.smtp_use_tls}
@@ -182,7 +207,8 @@
{/if}
</div>
</div>
<div class="flex items-center gap-1">
<MetaStrip tiles={emailBotTiles(bot)} />
<div class="list-row__actions">
<IconButton icon="mdiSend" title={t('emailBot.testConnection')} onclick={() => testEmailBot(bot.id)} disabled={emailTesting[bot.id]} />
<IconButton icon="mdiPencil" title={t('common.edit')} onclick={() => editEmailBot(bot)} />
<IconButton icon="mdiDelete" title={t('common.delete')} onclick={() => removeEmail(bot.id)} variant="danger" />