feat: UX improvements — secure webhooks, locale fixes, dynamic languages, UI polish

- Remove top paginator from dashboard events, keep only bottom
- Fix test message locale: pass UI locale to email/matrix bot tests
- Convert webhook auth mode from text input to icon grid selector
- Generate secure UUID tokens for webhook URLs instead of sequential IDs
- Move Recent Payloads into per-provider expandable container (lazy-loaded)
- Make template config languages dynamic via app settings instead of hardcoded
- Change default dev port to 5175
This commit is contained in:
2026-04-11 02:14:15 +03:00
parent 6b2211353d
commit 734e5c9340
29 changed files with 278 additions and 154 deletions
+11 -7
View File
@@ -14,7 +14,9 @@
import IconButton from '$lib/components/IconButton.svelte';
import ErrorBanner from '$lib/components/ErrorBanner.svelte';
import IconGridSelect from '$lib/components/IconGridSelect.svelte';
import { providerTypeItems, providerDefaultIcon } from '$lib/grid-items';
import { providerTypeItems, providerDefaultIcon, webhookAuthModeItems } from '$lib/grid-items';
const gridItemSources: Record<string, () => any[]> = { webhookAuthModeItems };
import { globalProviderFilter } from '$lib/stores/provider-filter.svelte';
import { snackSuccess, snackError } from '$lib/stores/snackbar.svelte';
import { highlightFromUrl } from '$lib/highlight';
@@ -189,7 +191,9 @@
{t(editing && field.editLabel ? field.editLabel : field.label)}
{#if field.optional}<span class="text-xs text-[var(--color-muted-foreground)]">({t('providers.optional')})</span>{/if}
</label>
{#if field.type === 'number'}
{#if field.type === 'grid-select' && field.gridItems}
<IconGridSelect items={gridItemSources[field.gridItems]()} bind:value={form[field.key]} columns={field.gridColumns ?? 2} compact />
{:else if field.type === 'number'}
<input id="prv-{field.key}" type="number" bind:value={form[field.key]}
min={field.min} max={field.max}
class="w-full px-3 py-2 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />
@@ -207,7 +211,7 @@
{#if descriptor?.webhookUrlPattern && editing}
<div class="bg-[var(--color-muted)] rounded-md p-3">
<label class="block text-sm font-medium mb-1">{t('providers.webhookUrl')}</label>
<code class="text-xs select-all break-all">{descriptor.webhookUrlPattern.replace('{id}', String(editing))}</code>
<code class="text-xs select-all break-all">{descriptor.webhookUrlPattern.replace('{token}', providers.find(p => p.id === editing)?.webhook_token ?? '')}</code>
<p class="text-xs text-[var(--color-muted-foreground)] mt-1">{t('providers.webhookUrlHint')}</p>
</div>
{/if}
@@ -254,7 +258,7 @@
<p class="text-xs text-[var(--color-muted-foreground)] font-mono">{provider.config.host}:{provider.config.port || 3493}</p>
{/if}
{#if provDesc?.webhookUrlPattern}
<p class="text-xs text-[var(--color-muted-foreground)] font-mono mt-0.5">{t('providers.webhookUrl')}: <span class="select-all">{provDesc.webhookUrlPattern.replace('{id}', String(provider.id))}</span></p>
<p class="text-xs text-[var(--color-muted-foreground)] font-mono mt-0.5">{t('providers.webhookUrl')}: <span class="select-all">{provDesc.webhookUrlPattern.replace('{token}', provider.webhook_token)}</span></p>
{/if}
</div>
</div>
@@ -263,10 +267,10 @@
<IconButton icon="mdiDelete" title={t('common.delete')} onclick={() => startDelete(provider)} variant="danger" />
</div>
</div>
{#if provDesc?.payloadHistory && !showForm}
<WebhookPayloadHistory providerId={provider.id} />
{/if}
</Card>
{#if provDesc?.payloadHistory && !showForm}
<WebhookPayloadHistory providerId={provider.id} />
{/if}
{/each}
</div>
{/if}