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:
@@ -13,8 +13,11 @@
|
||||
import ConfirmModal from '$lib/components/ConfirmModal.svelte';
|
||||
import IconButton from '$lib/components/IconButton.svelte';
|
||||
import EntitySelect from '$lib/components/EntitySelect.svelte';
|
||||
import CrossLink from '$lib/components/CrossLink.svelte';
|
||||
import { snackSuccess, snackError } from '$lib/stores/snackbar.svelte';
|
||||
import { highlightFromUrl } from '$lib/highlight';
|
||||
import { globalProviderFilter } from '$lib/stores/provider-filter.svelte';
|
||||
import { providerDefaultIcon } from '$lib/grid-items';
|
||||
import RuleEditor from './RuleEditor.svelte';
|
||||
import ExecutionHistory from './ExecutionHistory.svelte';
|
||||
import type { Action, ActionRule } from '$lib/types';
|
||||
@@ -23,7 +26,8 @@
|
||||
let providers = $derived(providersCache.items);
|
||||
let filterText = $state('');
|
||||
let actions = $derived(allActions.filter((a: Action) =>
|
||||
!filterText || a.name.toLowerCase().includes(filterText.toLowerCase()) || a.action_type.toLowerCase().includes(filterText.toLowerCase())
|
||||
(!filterText || a.name.toLowerCase().includes(filterText.toLowerCase()) || a.action_type.toLowerCase().includes(filterText.toLowerCase())) &&
|
||||
(!globalProviderFilter.id || a.provider_id === globalProviderFilter.id)
|
||||
));
|
||||
|
||||
let showForm = $state(false);
|
||||
@@ -49,7 +53,7 @@
|
||||
}));
|
||||
|
||||
let providerItems = $derived(actionProviders.map((p: any) => ({
|
||||
value: p.id, label: p.name, icon: p.icon || 'mdiServer', desc: p.type,
|
||||
value: p.id, label: p.name, icon: providerDefaultIcon(p), desc: p.type,
|
||||
})));
|
||||
|
||||
// Action types for selected provider
|
||||
@@ -136,8 +140,12 @@
|
||||
executing = { ...executing, [id]: false };
|
||||
}
|
||||
|
||||
function getProvider(providerId: number) {
|
||||
return providers.find((p: any) => p.id === providerId);
|
||||
}
|
||||
|
||||
function getProviderName(providerId: number): string {
|
||||
return providers.find((p: any) => p.id === providerId)?.name || '?';
|
||||
return getProvider(providerId)?.name || '?';
|
||||
}
|
||||
|
||||
function formatSchedule(action: Action): string {
|
||||
@@ -297,7 +305,7 @@
|
||||
<span class="text-xs px-1.5 py-0.5 rounded bg-[var(--color-muted)] text-[var(--color-muted-foreground)]">{action.action_type}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 text-xs text-[var(--color-muted-foreground)]">
|
||||
<span>{getProviderName(action.provider_id)}</span>
|
||||
<CrossLink href="/providers" icon={providerDefaultIcon(getProvider(action.provider_id) || {})} label={getProviderName(action.provider_id)} entityId={action.provider_id} />
|
||||
<span>{formatSchedule(action)}</span>
|
||||
<span>{action.rules?.length || 0} {t('actions.rules')}</span>
|
||||
{#if action.last_run_status}
|
||||
|
||||
@@ -112,6 +112,7 @@
|
||||
name: '',
|
||||
rule_config: {
|
||||
criteria: { person_ids: [], person_names: [], query: '', asset_type: 'all', date_from: '', date_to: '', favorite_only: false },
|
||||
target_album_ids: [] as string[], target_album_names: [] as string[],
|
||||
target_album_id: '', target_album_name: '',
|
||||
create_album_if_missing: false, create_album_name: '',
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user