feat: broadcast notification target + UX improvements

Add broadcast target type that fans out notifications to multiple
child targets. Dispatch expands broadcast into children in
load_link_data() — dispatcher stays unaware. Children can be
toggled on/off via disabled_child_ids in config.

Also: dashboard provider card smaller font for names, scroll-to-form
on target edit, broadcast nav tab with counter, flag_modified fix
for JSON column updates, CLAUDE.md nav tree docs.
This commit is contained in:
2026-03-24 15:15:41 +03:00
parent 8cb836e16c
commit d8ecb60073
13 changed files with 327 additions and 102 deletions
@@ -6,6 +6,7 @@
import Hint from '$lib/components/Hint.svelte';
import IconGridSelect from '$lib/components/IconGridSelect.svelte';
import EntitySelect from '$lib/components/EntitySelect.svelte';
import MultiEntitySelect from '$lib/components/MultiEntitySelect.svelte';
import type { EntityItem } from '$lib/components/EntitySelect.svelte';
import type { GridItem } from '$lib/components/IconGridSelect.svelte';
@@ -28,6 +29,7 @@
auth_token: string;
matrix_bot_id: number;
email_bot_id: number;
child_target_ids: number[];
};
formType: string;
activeType: string | null;
@@ -36,6 +38,7 @@
emailBotItems: EntityItem[];
matrixBotItems: EntityItem[];
chatActionItems: GridItem[];
broadcastChildItems?: { value: number; label: string; icon: string; desc: string }[];
telegramBotCount: number;
emailBotCount: number;
matrixBotCount: number;
@@ -56,6 +59,7 @@
emailBotItems,
matrixBotItems,
chatActionItems,
broadcastChildItems = [],
telegramBotCount,
emailBotCount,
matrixBotCount,
@@ -160,6 +164,20 @@
<p class="text-xs text-[var(--color-muted-foreground)] mt-1">{t('matrixBot.noBots')} <a href="/bots?tab=matrix" class="underline"></a></p>
{/if}
</div>
{:else if formType === 'broadcast'}
{@const childIds = (form.child_target_ids || []).map(String)}
<div>
<label class="block text-sm font-medium mb-1">{t('targets.selectChildTargets')}</label>
<MultiEntitySelect
items={broadcastChildItems?.map(i => ({ value: String(i.value), label: i.label, icon: i.icon, desc: i.desc })) ?? []}
values={childIds}
onchange={(vals) => form.child_target_ids = vals.map(Number)}
placeholder={t('targets.selectChildTargets')}
/>
{#if broadcastChildItems?.length === 0}
<p class="text-xs text-[var(--color-muted-foreground)] mt-1">{t('targets.noChildTargetsAvailable')}</p>
{/if}
</div>
{/if}
{#if formType === 'telegram'}