diff --git a/frontend/src/routes/actions/+page.svelte b/frontend/src/routes/actions/+page.svelte index 6ece433..dbc879e 100644 --- a/frontend/src/routes/actions/+page.svelte +++ b/frontend/src/routes/actions/+page.svelte @@ -40,7 +40,19 @@ schedule_type: 'interval', schedule_interval: 3600, schedule_cron: '', enabled: false, }); + let nameManuallyEdited = $state(false); let error = $state(''); + + function actionTypeLabel(at: string): string { + return at.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase()); + } + $effect(() => { + if (showForm && !nameManuallyEdited && !editing) { + const provider = providers.find((p: any) => p.id === form.provider_id); + const at = actionTypeLabel(form.action_type || ''); + form.name = provider ? `${provider.name} ${at}`.trim() : at || 'Action'; + } + }); let loadError = $state(''); let submitting = $state(false); let loaded = $state(false); @@ -98,6 +110,7 @@ config: {}, schedule_type: 'interval', schedule_interval: 3600, schedule_cron: '', enabled: false, }; + nameManuallyEdited = false; editing = null; showForm = true; } @@ -109,6 +122,7 @@ schedule_interval: action.schedule_interval, schedule_cron: action.schedule_cron, enabled: action.enabled, }; + nameManuallyEdited = true; editing = action.id; showForm = true; } @@ -245,7 +259,7 @@
form.icon = v} /> - nameManuallyEdited = true} required class="flex-1 px-3 py-2 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />
diff --git a/frontend/src/routes/bots/EmailBotTab.svelte b/frontend/src/routes/bots/EmailBotTab.svelte index 6964a87..d44cbee 100644 --- a/frontend/src/routes/bots/EmailBotTab.svelte +++ b/frontend/src/routes/bots/EmailBotTab.svelte @@ -30,8 +30,16 @@ smtp_username: '', smtp_password: '', smtp_use_tls: true, }); let emailForm = $state(defaultEmailForm()); + let nameManuallyEdited = $state(false); - function openNewEmail() { emailForm = defaultEmailForm(); editingEmail = null; showEmailForm = true; } + const DEFAULT_BOT_NAME = 'Email Bot'; + $effect(() => { + if (showEmailForm && !nameManuallyEdited && !editingEmail) { + emailForm.name = DEFAULT_BOT_NAME; + } + }); + + function openNewEmail() { emailForm = defaultEmailForm(); nameManuallyEdited = false; editingEmail = null; showEmailForm = true; } function editEmailBot(bot: EmailBot) { emailForm = { name: bot.name, icon: bot.icon || '', email: bot.email, @@ -39,6 +47,7 @@ smtp_username: bot.smtp_username, smtp_password: '', smtp_use_tls: bot.smtp_use_tls, }; + nameManuallyEdited = true; editingEmail = bot.id; showEmailForm = true; } @@ -54,7 +63,7 @@ await api('/email-bots', { method: 'POST', body: JSON.stringify(body) }); snackSuccess(t('snack.emailBotCreated')); } - emailForm = defaultEmailForm(); showEmailForm = false; editingEmail = null; await onreload(); + emailForm = defaultEmailForm(); nameManuallyEdited = false; showEmailForm = false; editingEmail = null; await onreload(); } catch (err: any) { error = err.message; snackError(err.message); } finally { emailSubmitting = false; } } @@ -107,7 +116,7 @@
emailForm.icon = v} /> - nameManuallyEdited = true} required placeholder={t('emailBot.namePlaceholder')} class="flex-1 px-3 py-2 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />
diff --git a/frontend/src/routes/bots/MatrixBotTab.svelte b/frontend/src/routes/bots/MatrixBotTab.svelte index 7cd335e..e441d7c 100644 --- a/frontend/src/routes/bots/MatrixBotTab.svelte +++ b/frontend/src/routes/bots/MatrixBotTab.svelte @@ -29,14 +29,23 @@ name: '', icon: '', homeserver_url: '', access_token: '', display_name: '', }); let matrixForm = $state(defaultMatrixForm()); + let nameManuallyEdited = $state(false); - function openNewMatrix() { matrixForm = defaultMatrixForm(); editingMatrix = null; showMatrixForm = true; } + const DEFAULT_BOT_NAME = 'Matrix Bot'; + $effect(() => { + if (showMatrixForm && !nameManuallyEdited && !editingMatrix) { + matrixForm.name = DEFAULT_BOT_NAME; + } + }); + + function openNewMatrix() { matrixForm = defaultMatrixForm(); nameManuallyEdited = false; editingMatrix = null; showMatrixForm = true; } function editMatrixBot(bot: MatrixBot) { matrixForm = { name: bot.name, icon: bot.icon || '', homeserver_url: bot.homeserver_url, access_token: '', display_name: bot.display_name || '', }; + nameManuallyEdited = true; editingMatrix = bot.id; showMatrixForm = true; } @@ -52,7 +61,7 @@ await api('/matrix-bots', { method: 'POST', body: JSON.stringify(body) }); snackSuccess(t('snack.matrixBotCreated')); } - matrixForm = defaultMatrixForm(); showMatrixForm = false; editingMatrix = null; await onreload(); + matrixForm = defaultMatrixForm(); nameManuallyEdited = false; showMatrixForm = false; editingMatrix = null; await onreload(); } catch (err: any) { error = err.message; snackError(err.message); } finally { matrixSubmitting = false; } } @@ -105,7 +114,7 @@
matrixForm.icon = v} /> - nameManuallyEdited = true} required placeholder={t('matrixBot.namePlaceholder')} class="flex-1 px-3 py-2 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />
diff --git a/frontend/src/routes/bots/TelegramBotTab.svelte b/frontend/src/routes/bots/TelegramBotTab.svelte index a7d0253..05b97b9 100644 --- a/frontend/src/routes/bots/TelegramBotTab.svelte +++ b/frontend/src/routes/bots/TelegramBotTab.svelte @@ -29,10 +29,18 @@ let showForm = $state(false); let editing = $state(null); let form = $state({ name: '', icon: '', token: '' }); + let nameManuallyEdited = $state(false); let error = $state(''); let submitting = $state(false); let confirmDelete = $state<{ id: number; onconfirm: () => Promise } | null>(null); + const DEFAULT_BOT_NAME = 'Telegram Bot'; + $effect(() => { + if (showForm && !nameManuallyEdited && !editing) { + form.name = DEFAULT_BOT_NAME; + } + }); + // Per-bot expandable sections let chats = $state>({}); let chatsLoading = $state>({}); @@ -52,8 +60,8 @@ let botListenerStatus = $state>({}); let botListenerLoading = $state>({}); - function openNew() { form = { name: '', icon: '', token: '' }; editing = null; showForm = true; } - function editBot(bot: TelegramBot) { form = { name: bot.name, icon: bot.icon || '', token: '' }; editing = bot.id; showForm = true; } + function openNew() { form = { name: '', icon: '', token: '' }; nameManuallyEdited = false; editing = null; showForm = true; } + function editBot(bot: TelegramBot) { form = { name: bot.name, icon: bot.icon || '', token: '' }; nameManuallyEdited = true; editing = bot.id; showForm = true; } async function saveBot(e: SubmitEvent) { e.preventDefault(); error = ''; submitting = true; @@ -65,7 +73,7 @@ await api('/telegram-bots', { method: 'POST', body: JSON.stringify(form) }); snackSuccess(t('snack.botRegistered')); } - form = { name: '', icon: '', token: '' }; showForm = false; editing = null; await onreload(); + form = { name: '', icon: '', token: '' }; nameManuallyEdited = false; showForm = false; editing = null; await onreload(); } catch (err: any) { error = err.message; snackError(err.message); } finally { submitting = false; } } @@ -312,7 +320,7 @@
form.icon = v} /> - nameManuallyEdited = true} required placeholder={t('telegramBot.namePlaceholder')} class="flex-1 px-3 py-2 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />
diff --git a/frontend/src/routes/command-configs/+page.svelte b/frontend/src/routes/command-configs/+page.svelte index 06431a3..8d71108 100644 --- a/frontend/src/routes/command-configs/+page.svelte +++ b/frontend/src/routes/command-configs/+page.svelte @@ -21,6 +21,7 @@ import { highlightFromUrl } from '$lib/highlight'; import { globalProviderFilter } from '$lib/stores/provider-filter.svelte'; import ErrorBanner from '$lib/components/ErrorBanner.svelte'; + import { getDescriptor } from '$lib/providers'; import type { CommandConfig } from '$lib/types'; function templateName(id: number | null): string { @@ -69,6 +70,14 @@ command_template_config_id: null as number | null, }); let form = $state(defaultForm()); + let nameManuallyEdited = $state(false); + + $effect(() => { + if (showForm && !nameManuallyEdited && !editing) { + const desc = getDescriptor(form.provider_type); + form.name = desc ? `${desc.defaultName} Commands` : 'Commands'; + } + }); let allCapabilities = $derived(capabilitiesCache.items); let providerCommands = $derived<{key: string, icon: string}[]>( @@ -107,6 +116,7 @@ // Auto-select first matching template for the chosen provider_type const match = cmdTemplateConfigs.find((c) => c.provider_type === form.provider_type); if (match) form.command_template_config_id = match.id; + nameManuallyEdited = false; editing = null; showForm = true; } @@ -142,6 +152,7 @@ rate_limits: { search: cfg.rate_limits?.search ?? 30, default: cfg.rate_limits?.default ?? 10 }, command_template_config_id: cfg.command_template_config_id ?? null, }; + nameManuallyEdited = true; editing = cfg.id; showForm = true; } @@ -165,7 +176,7 @@ await api('/command-configs', { method: 'POST', body }); snackSuccess(t('snack.commandConfigSaved')); } - form = defaultForm(); showForm = false; editing = null; await load(); + form = defaultForm(); nameManuallyEdited = false; showForm = false; editing = null; await load(); } catch (err: any) { error = err.message; snackError(err.message); } finally { submitting = false; } } @@ -214,7 +225,7 @@
form.icon = v} /> - nameManuallyEdited = true} required placeholder={t('commandConfig.namePlaceholder')} class="flex-1 px-3 py-2 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />
diff --git a/frontend/src/routes/command-template-configs/+page.svelte b/frontend/src/routes/command-template-configs/+page.svelte index 0fa18c5..768e6c2 100644 --- a/frontend/src/routes/command-template-configs/+page.svelte +++ b/frontend/src/routes/command-template-configs/+page.svelte @@ -26,6 +26,7 @@ import { snackSuccess, snackError } from '$lib/stores/snackbar.svelte'; import { highlightFromUrl } from '$lib/highlight'; import { globalProviderFilter } from '$lib/stores/provider-filter.svelte'; + import { getDescriptor } from '$lib/providers'; interface CmdTemplateConfig { id: number; @@ -120,6 +121,14 @@ slots: {} as Record>, }); let form = $state(defaultForm()); + let nameManuallyEdited = $state(false); + + $effect(() => { + if (showForm && !nameManuallyEdited && !editing) { + const desc = getDescriptor(form.provider_type); + form.name = desc ? `${desc.defaultName} Command Templates` : 'Command Templates'; + } + }); // Provider capabilities let allCapabilities = $state>({}); @@ -257,6 +266,7 @@ form = defaultForm(); const typesWithCmdSlots = providerTypes.filter(t => (allCapabilities[t]?.command_slots?.length || 0) > 0); if (typesWithCmdSlots.length > 0) form.provider_type = typesWithCmdSlots[0]; + nameManuallyEdited = false; editing = null; showForm = true; activeLocale = primaryLocale; @@ -280,6 +290,7 @@ icon: c.icon || '', slots: slotsCopy, }; + nameManuallyEdited = true; editing = c.id; showForm = true; activeLocale = primaryLocale; @@ -432,7 +443,7 @@
form.icon = v} /> - nameManuallyEdited = true} required placeholder={t('cmdTemplateConfig.namePlaceholder')} class="flex-1 px-3 py-2 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />
diff --git a/frontend/src/routes/command-trackers/+page.svelte b/frontend/src/routes/command-trackers/+page.svelte index f78650b..c40f0fb 100644 --- a/frontend/src/routes/command-trackers/+page.svelte +++ b/frontend/src/routes/command-trackers/+page.svelte @@ -61,6 +61,14 @@ enabled: true, }); let form = $state(defaultForm()); + let nameManuallyEdited = $state(false); + + $effect(() => { + if (showForm && !nameManuallyEdited && !editing) { + const provider = providers.find(p => p.id === form.provider_id); + form.name = provider ? `${provider.name} Commands` : 'Commands'; + } + }); // Filter command configs by selected provider's type let filteredConfigs = $derived.by(() => { @@ -110,6 +118,7 @@ const firstCfg = commandConfigs.find(c => c.provider_type === ptype); if (firstCfg) form.command_config_id = firstCfg.id; } + nameManuallyEdited = false; editing = null; showForm = true; } @@ -141,6 +150,7 @@ command_config_id: trk.command_config_id, enabled: trk.enabled, }; + nameManuallyEdited = true; editing = trk.id; showForm = true; } @@ -156,7 +166,7 @@ await api('/command-trackers', { method: 'POST', body }); snackSuccess(t('snack.commandTrackerCreated')); } - form = defaultForm(); showForm = false; editing = null; await load(); + form = defaultForm(); nameManuallyEdited = false; showForm = false; editing = null; await load(); } catch (err: any) { error = err.message; snackError(err.message); } finally { submitting = false; } } @@ -288,7 +298,7 @@
form.icon = v} /> - nameManuallyEdited = true} required placeholder={t('commandTracker.namePlaceholder')} class="flex-1 px-3 py-2 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />
diff --git a/frontend/src/routes/notification-trackers/+page.svelte b/frontend/src/routes/notification-trackers/+page.svelte index bd7fe12..46f4d2e 100644 --- a/frontend/src/routes/notification-trackers/+page.svelte +++ b/frontend/src/routes/notification-trackers/+page.svelte @@ -70,11 +70,19 @@ filters: {} as Record, }); let form = $state(defaultForm()); + let nameManuallyEdited = $state(false); let selectedProviderType = $derived( providers.find(p => p.id === form.provider_id)?.type || '' ); let error = $state(''); + $effect(() => { + if (showForm && !nameManuallyEdited && !editing) { + const provider = providers.find(p => p.id === form.provider_id); + form.name = provider ? `${provider.name} Tracker` : 'Tracker'; + } + }); + // Linked targets management let expandedTracker = $state(null); let addingTarget = $state>({}); @@ -210,6 +218,7 @@ form = defaultForm(); // Auto-select first provider if any if (providers.length > 0) form.provider_id = providers[0].id; + nameManuallyEdited = false; editing = null; showForm = true; collections = []; users = []; previousCollectionIds = []; } @@ -224,6 +233,7 @@ filters: trk.filters || {}, }; previousCollectionIds = [...(trk.collection_ids || [])]; + nameManuallyEdited = true; editing = trk.id; showForm = true; if (form.provider_id) { await Promise.all([loadCollections(), loadUsers()]); @@ -491,6 +501,7 @@ onsave={save} ontoggleCollection={toggleCollection} {formatDate} + onnameinput={() => nameManuallyEdited = true} /> {/if} diff --git a/frontend/src/routes/notification-trackers/TrackerForm.svelte b/frontend/src/routes/notification-trackers/TrackerForm.svelte index b8ecabc..ca262e1 100644 --- a/frontend/src/routes/notification-trackers/TrackerForm.svelte +++ b/frontend/src/routes/notification-trackers/TrackerForm.svelte @@ -35,6 +35,7 @@ onsave: (e: SubmitEvent) => void; ontoggleCollection?: (collectionId: string) => void; formatDate?: (dateStr: string) => string; + onnameinput?: () => void; } let { @@ -53,6 +54,7 @@ onsave, ontoggleCollection, formatDate, + onnameinput, }: Props = $props(); let descriptor = $derived(getDescriptor(providerType)); @@ -95,7 +97,7 @@
form.icon = v} /> - + onnameinput?.()} required placeholder={t('notificationTracker.namePlaceholder')} class="flex-1 px-3 py-2 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />
diff --git a/frontend/src/routes/targets/+page.svelte b/frontend/src/routes/targets/+page.svelte index f231f2b..c3f366e 100644 --- a/frontend/src/routes/targets/+page.svelte +++ b/frontend/src/routes/targets/+page.svelte @@ -129,6 +129,7 @@ child_target_ids: [] as number[], }); let form = $state(defaultForm()); + let nameManuallyEdited = $state(false); let error = $state(''); let loaded = $state(false); let submitting = $state(false); @@ -137,6 +138,17 @@ let confirmDelete = $state(null); let formEl = $state(); + const TARGET_TYPE_DEFAULT_NAMES: Record = { + telegram: 'Telegram', webhook: 'Webhook', email: 'Email', + discord: 'Discord', slack: 'Slack', ntfy: 'ntfy', matrix: 'Matrix', + broadcast: 'Broadcast', + }; + $effect(() => { + if (showForm && !nameManuallyEdited && !editing) { + form.name = TARGET_TYPE_DEFAULT_NAMES[formType] ?? ''; + } + }); + async function scrollToForm() { await tick(); formEl?.scrollIntoView({ behavior: 'smooth', block: 'start' }); @@ -213,6 +225,7 @@ if (formType === 'telegram' && telegramBots.length > 0) form.bot_id = telegramBots[0].id; if (formType === 'email' && emailBots.length > 0) form.email_bot_id = emailBots[0].id; if (formType === 'matrix' && matrixBots.length > 0) form.matrix_bot_id = matrixBots[0].id; + nameManuallyEdited = false; editing = null; showTelegramSettings = false; showForm = true; @@ -242,6 +255,7 @@ // broadcast child_target_ids: c.child_target_ids || [], }; + nameManuallyEdited = true; editing = tgt.id; showTelegramSettings = false; showForm = true; @@ -476,6 +490,7 @@ bind:showTelegramSettings onsave={save} ontoggleTelegramSettings={() => showTelegramSettings = !showTelegramSettings} + onnameinput={() => nameManuallyEdited = true} /> {/if} diff --git a/frontend/src/routes/targets/TargetForm.svelte b/frontend/src/routes/targets/TargetForm.svelte index 133d8ba..1276711 100644 --- a/frontend/src/routes/targets/TargetForm.svelte +++ b/frontend/src/routes/targets/TargetForm.svelte @@ -49,6 +49,7 @@ showTelegramSettings: boolean; onsave: (e: SubmitEvent) => void; ontoggleTelegramSettings: () => void; + onnameinput?: () => void; } let { @@ -70,6 +71,7 @@ showTelegramSettings = $bindable(), onsave, ontoggleTelegramSettings, + onnameinput, }: Props = $props(); @@ -87,7 +89,7 @@
form.icon = v} /> - + onnameinput?.()} required placeholder={t('targets.namePlaceholder')} class="flex-1 px-3 py-2 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />
{#if formType === 'telegram'} diff --git a/frontend/src/routes/template-configs/+page.svelte b/frontend/src/routes/template-configs/+page.svelte index e29e06f..651f9f2 100644 --- a/frontend/src/routes/template-configs/+page.svelte +++ b/frontend/src/routes/template-configs/+page.svelte @@ -28,6 +28,7 @@ import { globalProviderFilter } from '$lib/stores/provider-filter.svelte'; import ErrorBanner from '$lib/components/ErrorBanner.svelte'; import Button from '$lib/components/Button.svelte'; + import { getDescriptor } from '$lib/providers'; import type { TemplateConfig } from '$lib/types'; let allTemplateConfigs = $derived(templateConfigsCache.items); @@ -194,8 +195,16 @@ date_only_format: '%d.%m.%Y', }); let form = $state(defaultForm()); + let nameManuallyEdited = $state(false); let previewTargetType = $state('telegram'); + $effect(() => { + if (showForm && !nameManuallyEdited && !editing) { + const desc = getDescriptor(form.provider_type); + form.name = desc ? `${desc.defaultName} Templates` : 'Templates'; + } + }); + // Provider capabilities: from shared cache let allCapabilities = $derived(capabilitiesCache.items); let providerTypes = $derived(Object.keys(allCapabilities)); @@ -291,6 +300,7 @@ function openNew() { form = defaultForm(); if (providerTypes.length > 0) form.provider_type = providerTypes[0]; + nameManuallyEdited = false; editing = null; showForm = true; activeLocale = primaryLocale; slotPreview = {}; slotErrors = {}; dateFormatPreview = {}; expandedSlots = new Set(); showPreviewFor = new Set(); slotFilter = ''; refreshDateFormatPreview(); } @@ -304,6 +314,7 @@ date_format: c.date_format || '%d.%m.%Y, %H:%M UTC', date_only_format: c.date_only_format || '%d.%m.%Y', }; + nameManuallyEdited = true; editing = c.id; showForm = true; activeLocale = primaryLocale; slotPreview = {}; slotErrors = {}; dateFormatPreview = {}; expandedSlots = new Set(); showPreviewFor = new Set(); slotFilter = ''; @@ -439,7 +450,7 @@
form.icon = v} /> - nameManuallyEdited = true} required placeholder={t('templateConfig.namePlaceholder')} class="flex-1 px-3 py-2 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />
diff --git a/frontend/src/routes/tracking-configs/+page.svelte b/frontend/src/routes/tracking-configs/+page.svelte index 1c232cb..6cabb1d 100644 --- a/frontend/src/routes/tracking-configs/+page.svelte +++ b/frontend/src/routes/tracking-configs/+page.svelte @@ -190,6 +190,14 @@ }); let form: Record = $state(defaultForm()); let descriptor = $derived(getDescriptor(form.provider_type)); + let nameManuallyEdited = $state(false); + + $effect(() => { + if (showForm && !nameManuallyEdited && !editing) { + const desc = getDescriptor(form.provider_type); + form.name = desc ? `${desc.defaultName} Tracking` : 'Tracking'; + } + }); onMount(() => { topbarAction.set({ @@ -230,9 +238,10 @@ window.history.replaceState(null, '', cleanUrl); } - function openNew() { form = defaultForm(); editing = null; showForm = true; } + function openNew() { form = defaultForm(); nameManuallyEdited = false; editing = null; showForm = true; } function edit(c: any) { form = { ...defaultForm(), ...c }; + nameManuallyEdited = true; editing = c.id; showForm = true; } @@ -288,7 +297,7 @@
form.icon = v} /> - nameManuallyEdited = true} required placeholder={t('trackingConfig.namePlaceholder')} class="flex-1 px-3 py-2 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />