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)]" />