feat(tracking): per-config quiet hours with app-level IANA timezone
Add quiet_hours_enabled/start/end to TrackingConfig (HH:MM strings interpreted in the app-level timezone AppSetting). The dispatch path loads the app timezone once per run and passes it through event_allowed_by_config -> in_quiet_hours, so overnight windows like 22:00-07:00 work correctly in any IANA tz. Frontend exposes a Timezone field under Settings and a Quiet Hours section on the Immich tracking-config form with time-picker inputs.
This commit is contained in:
@@ -523,6 +523,9 @@
|
||||
"memorySource": "Memory source",
|
||||
"memorySourceAlbums": "Scan tracked albums",
|
||||
"memorySourceNative": "Immich native memories",
|
||||
"quietHours": "Quiet hours",
|
||||
"quietHoursStart": "Start",
|
||||
"quietHoursEnd": "End",
|
||||
"test": "Test",
|
||||
"confirmDelete": "Delete this tracking config?",
|
||||
"sortNone": "None",
|
||||
@@ -670,6 +673,8 @@
|
||||
"webhookSecretHint": "Secret token to verify webhook requests from Telegram",
|
||||
"cacheTtl": "Media Cache TTL (hours)",
|
||||
"cacheTtlHint": "How long to cache uploaded Telegram file_ids before re-uploading",
|
||||
"timezone": "Timezone",
|
||||
"timezoneHint": "IANA timezone (e.g. UTC, Europe/Warsaw, America/New_York). Used to interpret HH:MM fields like quiet hours.",
|
||||
"locales": "Template Languages",
|
||||
"supportedLocales": "Supported Locales",
|
||||
"supportedLocalesHint": "Comma-separated locale codes for template editing (e.g. en,ru,de,fr)",
|
||||
@@ -680,6 +685,7 @@
|
||||
"scheduledAssets": "Sends random or selected photos from tracked albums on a schedule. Like a daily photo pick.",
|
||||
"memoryMode": "\"On This Day\" — sends photos taken on this date in previous years. Nostalgic flashbacks.",
|
||||
"memorySource": "Albums: scans tracked albums for date-matching assets. Native: uses Immich's built-in memories (covers entire library, optionally filtered by tracked albums).",
|
||||
"quietHours": "Suppress all notifications during this HH:MM window (interpreted in the app timezone). Overnight windows like 22:00–07:00 are supported.",
|
||||
"favoritesOnly": "Only include assets marked as favorites.",
|
||||
"maxAssets": "Maximum number of asset details to include in a single notification message.",
|
||||
"periodicStartDate": "The reference date for calculating periodic intervals. Summaries are sent every N days from this date.",
|
||||
|
||||
@@ -523,6 +523,9 @@
|
||||
"memorySource": "Источник воспоминаний",
|
||||
"memorySourceAlbums": "Сканировать альбомы",
|
||||
"memorySourceNative": "Встроенные воспоминания Immich",
|
||||
"quietHours": "Тихие часы",
|
||||
"quietHoursStart": "Начало",
|
||||
"quietHoursEnd": "Конец",
|
||||
"test": "Тест",
|
||||
"confirmDelete": "Удалить эту конфигурацию отслеживания?",
|
||||
"sortNone": "Нет",
|
||||
@@ -670,6 +673,8 @@
|
||||
"webhookSecretHint": "Секретный токен для проверки запросов вебхука от Telegram",
|
||||
"cacheTtl": "TTL кэша медиа (часы)",
|
||||
"cacheTtlHint": "Сколько хранить кэш Telegram file_id перед повторной загрузкой",
|
||||
"timezone": "Часовой пояс",
|
||||
"timezoneHint": "Часовой пояс IANA (например UTC, Europe/Warsaw, America/New_York). Используется для интерпретации полей HH:MM, таких как тихие часы.",
|
||||
"locales": "Языки шаблонов",
|
||||
"supportedLocales": "Поддерживаемые локали",
|
||||
"supportedLocalesHint": "Коды локалей через запятую для редактирования шаблонов (например en,ru,de,fr)",
|
||||
@@ -680,6 +685,7 @@
|
||||
"scheduledAssets": "Отправляет случайные или выбранные фото из альбомов по расписанию. Как ежедневная подборка фото.",
|
||||
"memoryMode": "\"В этот день\" — отправляет фото, сделанные в этот день в прошлые годы. Ностальгические воспоминания.",
|
||||
"memorySource": "Альбомы: сканирует отслеживаемые альбомы по дате. Встроенные: использует воспоминания Immich (вся библиотека, с фильтрацией по альбомам).",
|
||||
"quietHours": "Подавляет все уведомления в указанном HH:MM окне (по часовому поясу приложения). Поддерживаются окна через полночь, например 22:00–07:00.",
|
||||
"favoritesOnly": "Включать только ассеты, отмеченные как избранные.",
|
||||
"maxAssets": "Максимальное количество ассетов в одном уведомлении.",
|
||||
"periodicStartDate": "Опорная дата для расчёта интервалов. Сводки отправляются каждые N дней от этой даты.",
|
||||
|
||||
@@ -88,6 +88,14 @@ export const immichDescriptor: ProviderDescriptor = {
|
||||
{ key: 'memory_source', label: 'trackingConfig.memorySource', type: 'grid-select', gridItems: 'memorySourceItems', gridColumns: 2, defaultValue: 'albums' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'quietHours', legend: 'trackingConfig.quietHours', legendHint: 'hints.quietHours',
|
||||
enabledField: 'quiet_hours_enabled', enabledDefault: false,
|
||||
fields: [
|
||||
{ key: 'quiet_hours_start', label: 'trackingConfig.quietHoursStart', type: 'number', defaultValue: '22:00' },
|
||||
{ key: 'quiet_hours_end', label: 'trackingConfig.quietHoursEnd', type: 'number', defaultValue: '07:00' },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
collectionMeta: {
|
||||
|
||||
@@ -192,6 +192,9 @@ export interface TrackingConfig {
|
||||
memory_favorite_only: boolean;
|
||||
memory_asset_type: string;
|
||||
memory_min_rating: number;
|
||||
quiet_hours_enabled: boolean;
|
||||
quiet_hours_start: string | null;
|
||||
quiet_hours_end: string | null;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
telegram_webhook_secret: '',
|
||||
telegram_cache_ttl_hours: '48',
|
||||
supported_locales: 'en,ru',
|
||||
timezone: 'UTC',
|
||||
});
|
||||
|
||||
onMount(async () => {
|
||||
@@ -57,6 +58,11 @@
|
||||
<input bind:value={settings.external_url} placeholder="https://notify.example.com"
|
||||
class="w-full max-w-md px-3 py-1.5 text-sm border border-[var(--color-border)] rounded-md bg-[var(--color-background)] font-mono" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium mb-1">{t('settings.timezone')}<Hint text={t('settings.timezoneHint')} /></label>
|
||||
<input bind:value={settings.timezone} placeholder="UTC"
|
||||
class="w-full max-w-md px-3 py-1.5 text-sm border border-[var(--color-border)] rounded-md bg-[var(--color-background)] font-mono" />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
|
||||
@@ -181,9 +181,12 @@
|
||||
{:else if field.type === 'grid-select' && field.gridItems}
|
||||
<IconGridSelect items={gridItemSources[field.gridItems]()} bind:value={form[field.key]} columns={field.gridColumns ?? 2} compact />
|
||||
{:else}
|
||||
<input type={field.key.includes('date') ? 'date' : field.key.includes('times') ? 'text' : 'number'}
|
||||
<input type={field.key.includes('date') ? 'date'
|
||||
: field.key.startsWith('quiet_hours_') ? 'time'
|
||||
: field.key.includes('times') ? 'text'
|
||||
: 'number'}
|
||||
bind:value={form[field.key]} min={field.min} max={field.max}
|
||||
placeholder={field.key.includes('times') ? String(field.defaultValue ?? '') : ''}
|
||||
placeholder={field.key.includes('times') || field.key.startsWith('quiet_hours_') ? String(field.defaultValue ?? '') : ''}
|
||||
class="w-full px-2 py-1 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user