feat: locale-aware notification templates + UX improvements
- Add locale support to notification templates (matching command template
pattern): TemplateSlot now has locale field with (config_id, slot_name,
locale) uniqueness, nested API format {slot: {locale: template}}
- Migration merges separate EN/RU system configs into unified per-provider
configs; seeds create one config per provider with multi-locale slots
- Locale-aware dispatch with EN fallback in NotificationDispatcher
- Frontend locale tabs (EN/RU) on template config editor
- Fix tracking config cards not showing default provider icons
- Global provider filter, search palette, and various UX polish
This commit is contained in:
@@ -10,14 +10,15 @@
|
||||
import EventChart from '$lib/components/EventChart.svelte';
|
||||
import IconGridSelect from '$lib/components/IconGridSelect.svelte';
|
||||
import EntitySelect from '$lib/components/EntitySelect.svelte';
|
||||
import { eventTypeFilterItems, sortFilterItems } from '$lib/grid-items';
|
||||
import { eventTypeFilterItems, sortFilterItems, providerDefaultIcon } from '$lib/grid-items';
|
||||
import { globalProviderFilter } from '$lib/stores/provider-filter.svelte';
|
||||
|
||||
import type { DashboardStatus } from '$lib/types';
|
||||
let status = $state<DashboardStatus | null>(null);
|
||||
let providers = $derived(providersCache.items);
|
||||
const providerFilterItems = $derived([
|
||||
{ value: '', label: t('dashboard.allProviders'), icon: 'mdiFilterOff' },
|
||||
...providers.map(p => ({ value: p.id, label: p.name, icon: p.icon || 'mdiServer', desc: p.type })),
|
||||
...providers.map(p => ({ value: p.id, label: p.name, icon: providerDefaultIcon(p), desc: p.type })),
|
||||
]);
|
||||
let chartDays = $state<{ date: string; [eventType: string]: string | number }[]>([]);
|
||||
let loaded = $state(false);
|
||||
@@ -31,6 +32,7 @@
|
||||
// Event filters
|
||||
let filterEventType = $state('');
|
||||
let filterProviderId = $state('');
|
||||
let effectiveProviderId = $derived(globalProviderFilter.id ? String(globalProviderFilter.id) : filterProviderId);
|
||||
let filterSearch = $state('');
|
||||
let filterSort = $state('newest');
|
||||
let eventsLimit = $state(calcPageSize());
|
||||
@@ -66,7 +68,7 @@
|
||||
function buildFilterParams(): URLSearchParams {
|
||||
const params = new URLSearchParams();
|
||||
if (filterEventType) params.set('event_type', filterEventType);
|
||||
if (filterProviderId) params.set('provider_id', filterProviderId);
|
||||
if (effectiveProviderId) params.set('provider_id', effectiveProviderId);
|
||||
if (filterSearch) params.set('search', filterSearch);
|
||||
return params;
|
||||
}
|
||||
@@ -99,7 +101,7 @@
|
||||
// Auto-apply when filter values change (via IconGridSelect bind:value)
|
||||
let _prevFilterKey = '';
|
||||
$effect(() => {
|
||||
const key = `${filterEventType}|${filterProviderId}|${filterSort}`;
|
||||
const key = `${filterEventType}|${effectiveProviderId}|${filterSort}`;
|
||||
if (loaded && key !== _prevFilterKey && _prevFilterKey !== '') {
|
||||
applyFilters();
|
||||
}
|
||||
@@ -260,7 +262,7 @@
|
||||
class="w-full px-3 py-1.5 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />
|
||||
</div>
|
||||
<div class="w-40"><IconGridSelect items={eventTypeFilterItems()} bind:value={filterEventType} columns={3} compact /></div>
|
||||
<div class="w-40"><IconGridSelect items={providerFilterItems} bind:value={filterProviderId} columns={2} compact /></div>
|
||||
{#if !globalProviderFilter.id}<div class="w-40"><IconGridSelect items={providerFilterItems} bind:value={filterProviderId} columns={2} compact /></div>{/if}
|
||||
<div class="w-36"><IconGridSelect items={sortFilterItems()} bind:value={filterSort} columns={2} compact /></div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user