diff --git a/frontend/src/lib/i18n/en.json b/frontend/src/lib/i18n/en.json index c135fa3..41c143e 100644 --- a/frontend/src/lib/i18n/en.json +++ b/frontend/src/lib/i18n/en.json @@ -16,7 +16,17 @@ "cmdTemplateConfigs": "Cmd Templates", "users": "Users", "settings": "Settings", - "logout": "Logout" + "logout": "Logout", + "notification": "Notification", + "commands": "Commands", + "bots": "Bots", + "trackers": "Trackers", + "configs": "Configs", + "templates": "Templates", + "telegram": "Telegram", + "email": "Email", + "matrix": "Matrix", + "common": "Common" }, "auth": { "signIn": "Sign in", @@ -39,7 +49,7 @@ "providers": "Providers", "activeTrackers": "Active Trackers", "targets": "Targets", - "recentEvents": "Recent Events", + "recentEvents": "Events", "noEvents": "No events yet. Create a tracker to start monitoring.", "loading": "Loading...", "justNow": "just now", @@ -253,7 +263,7 @@ "enabledCommands": "Enabled commands", "defaultCount": "Default count", "responseMode": "Response mode", - "modeMedia": "Media (photos)", + "modeMedia": "Media (files)", "modeText": "Text only", "botLocale": "Bot language", "rateLimits": "Rate Limits", @@ -396,13 +406,27 @@ "invalidFormat": "Invalid format string" }, "templateVars": { - "message_assets_added": { "description": "Notification when new assets are added to an album" }, - "message_assets_removed": { "description": "Notification when assets are removed from an album" }, - "message_album_renamed": { "description": "Notification when an album is renamed" }, - "message_album_deleted": { "description": "Notification when an album is deleted" }, - "periodic_summary_message": { "description": "Periodic album summary (scheduler not yet implemented)" }, - "scheduled_assets_message": { "description": "Scheduled asset delivery (scheduler not yet implemented)" }, - "memory_mode_message": { "description": "\"On This Day\" memories (scheduler not yet implemented)" }, + "message_assets_added": { + "description": "Notification when new assets are added to an album" + }, + "message_assets_removed": { + "description": "Notification when assets are removed from an album" + }, + "message_album_renamed": { + "description": "Notification when an album is renamed" + }, + "message_album_deleted": { + "description": "Notification when an album is deleted" + }, + "periodic_summary_message": { + "description": "Periodic album summary (scheduler not yet implemented)" + }, + "scheduled_assets_message": { + "description": "Scheduled asset delivery (scheduler not yet implemented)" + }, + "memory_mode_message": { + "description": "\"On This Day\" memories (scheduler not yet implemented)" + }, "album_id": "Album ID (UUID)", "album_name": "Album name", "album_url": "Public share URL (empty if not shared)", @@ -544,7 +568,7 @@ "enabledCommands": "Enabled Commands", "locale": "Locale", "responseMode": "Response Mode", - "modeMedia": "Media (photos)", + "modeMedia": "Media (files)", "modeText": "Text only", "defaultCount": "Default Count", "rateLimits": "Rate Limits", @@ -662,4 +686,4 @@ "line": "line", "add": "Add" } -} +} \ No newline at end of file diff --git a/frontend/src/lib/i18n/ru.json b/frontend/src/lib/i18n/ru.json index b55bbcf..cf6e5d0 100644 --- a/frontend/src/lib/i18n/ru.json +++ b/frontend/src/lib/i18n/ru.json @@ -16,7 +16,17 @@ "cmdTemplateConfigs": "Шаблоны команд", "users": "Пользователи", "settings": "Настройки", - "logout": "Выход" + "logout": "Выход", + "notification": "Уведомления", + "commands": "Команды", + "bots": "Боты", + "trackers": "Трекеры", + "configs": "Настройки", + "templates": "Шаблоны", + "telegram": "Telegram", + "email": "Email", + "matrix": "Matrix", + "common": "Общие" }, "auth": { "signIn": "Войти", @@ -39,7 +49,7 @@ "providers": "Провайдеры", "activeTrackers": "Активные трекеры", "targets": "Получатели", - "recentEvents": "Последние события", + "recentEvents": "События", "noEvents": "Событий пока нет. Создайте трекер для отслеживания.", "loading": "Загрузка...", "justNow": "только что", @@ -253,7 +263,7 @@ "enabledCommands": "Включённые команды", "defaultCount": "Кол-во по умолчанию", "responseMode": "Режим ответа", - "modeMedia": "Медиа (фото)", + "modeMedia": "Медиа (файлы)", "modeText": "Только текст", "botLocale": "Язык бота", "rateLimits": "Ограничения частоты", @@ -396,13 +406,27 @@ "invalidFormat": "Некорректная строка формата" }, "templateVars": { - "message_assets_added": { "description": "Уведомление о добавлении файлов в альбом" }, - "message_assets_removed": { "description": "Уведомление об удалении файлов из альбома" }, - "message_album_renamed": { "description": "Уведомление о переименовании альбома" }, - "message_album_deleted": { "description": "Уведомление об удалении альбома" }, - "periodic_summary_message": { "description": "Периодическая сводка альбомов (планировщик не реализован)" }, - "scheduled_assets_message": { "description": "Запланированная подборка фото (планировщик не реализован)" }, - "memory_mode_message": { "description": "«В этот день» — воспоминания (планировщик не реализован)" }, + "message_assets_added": { + "description": "Уведомление о добавлении файлов в альбом" + }, + "message_assets_removed": { + "description": "Уведомление об удалении файлов из альбома" + }, + "message_album_renamed": { + "description": "Уведомление о переименовании альбома" + }, + "message_album_deleted": { + "description": "Уведомление об удалении альбома" + }, + "periodic_summary_message": { + "description": "Периодическая сводка альбомов (планировщик не реализован)" + }, + "scheduled_assets_message": { + "description": "Запланированная подборка фото (планировщик не реализован)" + }, + "memory_mode_message": { + "description": "«В этот день» — воспоминания (планировщик не реализован)" + }, "album_id": "ID альбома (UUID)", "album_name": "Название альбома", "album_url": "Публичная ссылка (пусто, если не расшарен)", @@ -544,7 +568,7 @@ "enabledCommands": "Включённые команды", "locale": "Язык", "responseMode": "Режим ответа", - "modeMedia": "Медиа (фото)", + "modeMedia": "Медиа (файлы)", "modeText": "Только текст", "defaultCount": "Кол-во по умолчанию", "rateLimits": "Ограничения частоты", @@ -662,4 +686,4 @@ "line": "строка", "add": "Добавить" } -} +} \ No newline at end of file diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte index 2ff3f60..0485739 100644 --- a/frontend/src/routes/+layout.svelte +++ b/frontend/src/routes/+layout.svelte @@ -26,7 +26,7 @@ e.preventDefault(); pwdMsg = ''; pwdSuccess = false; try { await api('/auth/password', { method: 'PUT', body: JSON.stringify({ current_password: pwdCurrent, new_password: pwdNew }) }); - pwdMsg = t('common.passwordChanged'); + pwdMsg = t('common.changePassword'); pwdSuccess = true; pwdCurrent = ''; pwdNew = ''; snackSuccess(t('snack.passwordChanged')); @@ -36,23 +36,102 @@ let collapsed = $state(false); - const baseNavItems = [ + // Nav counts for badges + let navCounts = $state>({}); + + interface NavItem { + href: string; + key: string; + icon: string; + countKey?: string; + } + + interface NavGroup { + key: string; + icon: string; + children: NavItem[]; + countKeys?: string[]; + } + + type NavEntry = NavItem | NavGroup; + + function isGroup(entry: NavEntry): entry is NavGroup { + return 'children' in entry; + } + + const baseNavEntries: NavEntry[] = [ { href: '/', key: 'nav.dashboard', icon: 'mdiViewDashboard' }, - { href: '/providers', key: 'nav.providers', icon: 'mdiServer' }, - { href: '/notification-trackers', key: 'nav.notificationTrackers', icon: 'mdiRadar' }, - { href: '/tracking-configs', key: 'nav.trackingConfigs', icon: 'mdiCog' }, - { href: '/template-configs', key: 'nav.templateConfigs', icon: 'mdiFileDocumentEdit' }, - { href: '/telegram-bots', key: 'nav.telegramBots', icon: 'mdiRobot' }, - { href: '/targets', key: 'nav.targets', icon: 'mdiTarget' }, - { href: '/command-trackers', key: 'nav.commandTrackers', icon: 'mdiConsoleLine' }, - { href: '/command-configs', key: 'nav.commandConfigs', icon: 'mdiCog' }, - { href: '/command-template-configs', key: 'nav.cmdTemplateConfigs', icon: 'mdiCodeBracesBox' }, + { href: '/providers', key: 'nav.providers', icon: 'mdiServer', countKey: 'providers' }, + { + key: 'nav.notification', icon: 'mdiBellOutline', + countKeys: ['notification_trackers', 'tracking_configs', 'template_configs'], + children: [ + { href: '/notification-trackers', key: 'nav.trackers', icon: 'mdiRadar', countKey: 'notification_trackers' }, + { href: '/tracking-configs', key: 'nav.configs', icon: 'mdiCog', countKey: 'tracking_configs' }, + { href: '/template-configs', key: 'nav.templates', icon: 'mdiFileDocumentEdit', countKey: 'template_configs' }, + ], + }, + { + key: 'nav.commands', icon: 'mdiConsoleLine', + countKeys: ['command_trackers', 'command_configs', 'command_template_configs'], + children: [ + { href: '/command-trackers', key: 'nav.trackers', icon: 'mdiRadar', countKey: 'command_trackers' }, + { href: '/command-configs', key: 'nav.configs', icon: 'mdiCog', countKey: 'command_configs' }, + { href: '/command-template-configs', key: 'nav.templates', icon: 'mdiCodeBracesBox', countKey: 'command_template_configs' }, + ], + }, + { + key: 'nav.bots', icon: 'mdiRobot', + countKeys: ['telegram_bots'], + children: [ + { href: '/telegram-bots', key: 'nav.telegram', icon: 'mdiSendCircle', countKey: 'telegram_bots' }, + ], + }, + { href: '/targets', key: 'nav.targets', icon: 'mdiTarget', countKey: 'targets' }, ]; - const navItems = $derived(auth.isAdmin - ? [...baseNavItems, { href: '/users', key: 'nav.users', icon: 'mdiAccountGroup' }, { href: '/settings', key: 'nav.settings', icon: 'mdiCogOutline' }] - : baseNavItems + + const navEntries = $derived(auth.isAdmin + ? [ + ...baseNavEntries, + { + key: 'nav.settings', icon: 'mdiCogOutline', + children: [ + { href: '/settings', key: 'nav.common', icon: 'mdiCogOutline' }, + { href: '/users', key: 'nav.users', icon: 'mdiAccountGroup' }, + ], + }, + ] + : baseNavEntries ); + // Track which groups are expanded (persisted in localStorage) + let expandedGroups = $state>({}); + + function toggleGroup(key: string) { + expandedGroups = { ...expandedGroups, [key]: !expandedGroups[key] }; + if (typeof localStorage !== 'undefined') { + localStorage.setItem('nav_expanded', JSON.stringify(expandedGroups)); + } + } + + function isGroupActive(group: NavGroup): boolean { + return group.children.some(c => page.url.pathname === c.href); + } + + function groupCount(group: NavGroup): number { + if (!group.countKeys) return 0; + return group.countKeys.reduce((s, k) => s + (navCounts[k] || 0), 0); + } + + // Mobile: flatten nav for bottom bar + const mobileNavItems = $derived([ + { href: '/', key: 'nav.dashboard', icon: 'mdiViewDashboard' }, + { href: '/notification-trackers', key: 'nav.notification', icon: 'mdiBellOutline' }, + { href: '/command-trackers', key: 'nav.commands', icon: 'mdiConsoleLine' }, + { href: '/targets', key: 'nav.targets', icon: 'mdiTarget' }, + { href: '/telegram-bots', key: 'nav.bots', icon: 'mdiRobot' }, + ]); + const isAuthPage = $derived( page.url.pathname === '/login' || page.url.pathname === '/setup' ); @@ -61,11 +140,28 @@ initTheme(); if (typeof localStorage !== 'undefined') { collapsed = localStorage.getItem('sidebar_collapsed') === 'true'; + try { + const saved = localStorage.getItem('nav_expanded'); + if (saved) expandedGroups = JSON.parse(saved); + } catch {} } await loadUser(); if (!auth.user && !isAuthPage) { window.location.href = '/login'; } + // Load nav counts + if (auth.user) { + try { navCounts = await api('/status/counts'); } catch {} + } + }); + + // Auto-expand group containing the active page + $effect(() => { + for (const entry of navEntries) { + if (isGroup(entry) && isGroupActive(entry) && !expandedGroups[entry.key]) { + expandedGroups = { ...expandedGroups, [entry.key]: true }; + } + } }); function cycleTheme() { @@ -128,21 +224,74 @@ @@ -217,7 +366,7 @@