diff --git a/frontend/src/lib/grid-items.ts b/frontend/src/lib/grid-items.ts index 2ab6404..ffcc677 100644 --- a/frontend/src/lib/grid-items.ts +++ b/frontend/src/lib/grid-items.ts @@ -100,4 +100,5 @@ export const previewTargetTypeItems = (): GridItem[] => [ export const providerTypeItems = (): GridItem[] => [ { value: 'immich', icon: 'mdiCamera', label: t('providers.typeImmich') }, { value: 'gitea', icon: 'mdiGit', label: t('providers.typeGitea') }, + { value: 'scheduler', icon: 'mdiClockOutline', label: t('providers.typeScheduler') }, ]; diff --git a/frontend/src/lib/i18n/en.json b/frontend/src/lib/i18n/en.json index e76bae3..eefe3fa 100644 --- a/frontend/src/lib/i18n/en.json +++ b/frontend/src/lib/i18n/en.json @@ -107,6 +107,7 @@ "checking": "Checking...", "typeImmich": "Immich", "typeGitea": "Gitea", + "typeScheduler": "Scheduler", "loadError": "Failed to load providers.", "externalDomain": "External Domain", "optional": "optional", @@ -134,6 +135,14 @@ "eventTypes": "Event Types", "notificationTargets": "Notification Targets", "scanInterval": "Scan Interval (seconds)", + "scheduleType": "Schedule", + "intervalMode": "Interval", + "cronMode": "Cron expression", + "cronExpression": "Cron expression", + "cronHint": "Standard 5-field cron: minute hour day month weekday. Example: 0 9 * * 1-5 (weekdays at 9:00)", + "customVariables": "Custom Variables", + "customVariablesHint": "Define key-value pairs available in templates as {{ key }}.", + "addVariable": "Add variable", "createTracker": "Create Tracker", "noTrackers": "No trackers yet. Add a provider first, then create a tracker.", "active": "Active", @@ -310,6 +319,7 @@ "rateSearch": "Search cooldown", "rateFind": "Find cooldown", "rateDefault": "Default cooldown", + "noCommandsForProvider": "This provider type does not support bot commands.", "syncCommands": "Sync with Telegram", "discoverChats": "Discover chats from Telegram", "clickToCopy": "Click to copy chat ID", @@ -365,6 +375,7 @@ "prMerged": "PR merged", "prCommented": "PR commented", "releasePublished": "Release published", + "scheduledMessage": "Scheduled message", "trackImages": "Track images", "trackVideos": "Track videos", "favoritesOnly": "Favorites only", diff --git a/frontend/src/lib/i18n/ru.json b/frontend/src/lib/i18n/ru.json index 97e0b3d..bd35f4c 100644 --- a/frontend/src/lib/i18n/ru.json +++ b/frontend/src/lib/i18n/ru.json @@ -107,6 +107,7 @@ "checking": "Проверка...", "typeImmich": "Immich", "typeGitea": "Gitea", + "typeScheduler": "Планировщик", "loadError": "Не удалось загрузить провайдеры.", "externalDomain": "Внешний домен", "optional": "необязательно", @@ -134,6 +135,14 @@ "eventTypes": "Типы событий", "notificationTargets": "Получатели уведомлений", "scanInterval": "Интервал проверки (секунды)", + "scheduleType": "Расписание", + "intervalMode": "Интервал", + "cronMode": "Cron выражение", + "cronExpression": "Cron выражение", + "cronHint": "Стандартный 5-полевой cron: минута час день месяц день_недели. Пример: 0 9 * * 1-5 (будни в 9:00)", + "customVariables": "Пользовательские переменные", + "customVariablesHint": "Определите пары ключ-значение, доступные в шаблонах как {{ ключ }}.", + "addVariable": "Добавить переменную", "createTracker": "Создать трекер", "noTrackers": "Трекеров пока нет. Сначала добавьте провайдер, затем создайте трекер.", "active": "Активен", @@ -310,6 +319,7 @@ "rateSearch": "Кулдаун поиска", "rateFind": "Кулдаун поиска файлов", "rateDefault": "Кулдаун по умолчанию", + "noCommandsForProvider": "Этот тип провайдера не поддерживает команды бота.", "syncCommands": "Синхронизировать с Telegram", "discoverChats": "Обнаружить чаты из Telegram", "clickToCopy": "Нажмите, чтобы скопировать ID чата", @@ -365,6 +375,7 @@ "prMerged": "PR влит", "prCommented": "Комментарий к PR", "releasePublished": "Релиз опубликован", + "scheduledMessage": "Запланированное сообщение", "trackImages": "Фото", "trackVideos": "Видео", "favoritesOnly": "Только избранные", diff --git a/frontend/src/lib/stores/caches.svelte.ts b/frontend/src/lib/stores/caches.svelte.ts index 84ea118..8843905 100644 --- a/frontend/src/lib/stores/caches.svelte.ts +++ b/frontend/src/lib/stores/caches.svelte.ts @@ -53,6 +53,23 @@ export const commandTemplateConfigsCache = createEntityCache('/command-trackers'); +/** Provider capabilities — used by Template Configs, Command Configs. */ +export const capabilitiesCache = (() => { + let data = $state>({}); + let fetchedAt = $state(0); + const TTL = 60_000; // 1 minute + return { + get items() { return data; }, + async fetch(force = false): Promise> { + if (!force && Object.keys(data).length > 0 && Date.now() - fetchedAt < TTL) return data; + const { api } = await import('$lib/api'); + data = await api('/providers/capabilities'); + fetchedAt = Date.now(); + return data; + }, + }; +})(); + /** * All caches keyed by entity type — for search palette and crosslink resolution. */ diff --git a/frontend/src/routes/command-configs/+page.svelte b/frontend/src/routes/command-configs/+page.svelte index 120b900..86a1fde 100644 --- a/frontend/src/routes/command-configs/+page.svelte +++ b/frontend/src/routes/command-configs/+page.svelte @@ -2,7 +2,7 @@ import { onMount } from 'svelte'; import { api } from '$lib/api'; import { t } from '$lib/i18n'; - import { commandConfigsCache, commandTemplateConfigsCache } from '$lib/stores/caches.svelte'; + import { commandConfigsCache, commandTemplateConfigsCache, capabilitiesCache } from '$lib/stores/caches.svelte'; import PageHeader from '$lib/components/PageHeader.svelte'; import Card from '$lib/components/Card.svelte'; import Loading from '$lib/components/Loading.svelte'; @@ -37,22 +37,23 @@ let submitting = $state(false); let confirmDelete = $state(null); - const allCommands = [ - { key: 'help', icon: 'mdiHelpCircle' }, - { key: 'status', icon: 'mdiChartBox' }, - { key: 'albums', icon: 'mdiImageMultiple' }, - { key: 'events', icon: 'mdiPulse' }, - { key: 'summary', icon: 'mdiFileDocumentEdit' }, - { key: 'latest', icon: 'mdiImagePlus' }, - { key: 'memory', icon: 'mdiHistory' }, - { key: 'random', icon: 'mdiDice3' }, - { key: 'search', icon: 'mdiMagnify' }, - { key: 'find', icon: 'mdiFileSearch' }, - { key: 'person', icon: 'mdiAccount' }, - { key: 'place', icon: 'mdiMapMarker' }, - { key: 'favorites', icon: 'mdiStar' }, - { key: 'people', icon: 'mdiAccountGroup' }, - ]; + // Immich command icons — used as fallback when capabilities don't specify icons + const commandIcons: Record = { + help: 'mdiHelpCircle', status: 'mdiChartBox', albums: 'mdiImageMultiple', + events: 'mdiPulse', summary: 'mdiFileDocumentEdit', latest: 'mdiImagePlus', + memory: 'mdiHistory', random: 'mdiDice3', search: 'mdiMagnify', + find: 'mdiFileSearch', person: 'mdiAccount', place: 'mdiMapMarker', + favorites: 'mdiStar', people: 'mdiAccountGroup', + }; + + let allCapabilities = $derived(capabilitiesCache.items); + let providerCommands = $derived<{key: string, icon: string}[]>( + (allCapabilities[form.provider_type]?.commands || []).map((c: any) => ({ + key: c.name, + icon: commandIcons[c.name] || 'mdiConsole', + })) + ); + let hasCommands = $derived(providerCommands.length > 0); const defaultForm = () => ({ name: '', @@ -72,6 +73,7 @@ await Promise.all([ commandConfigsCache.fetch(true), commandTemplateConfigsCache.fetch(), + capabilitiesCache.fetch(), ]); } catch (err: any) { error = err.message || t('common.loadError'); snackError(error); } finally { loaded = true; highlightFromUrl(); } @@ -170,11 +172,12 @@ {/if} + {#if hasCommands}

{t('commandConfig.enabledCommands')}

- {#each allCommands as cmd} + {#each providerCommands as cmd}
+ {:else} +
+ {t('commandConfig.noCommandsForProvider')} +
+ {/if}
edit(tracker)} /> - { try { await api(`/notification-trackers/${tracker.id}/trigger`, { method: 'POST' }); snackSuccess(t('snack.targetTestSent')); } catch (err) { snackError((err as any).message); } }} /> toggle(tracker)} disabled={toggling[tracker.id]} /> +
+ {/each} + + + {:else}
@@ -87,6 +171,7 @@
+ {/if}