feat: NUT (Network UPS Tools) service provider + provider-agnostic UI

Add full NUT support as a polling-based service provider:
- Async TCP client for upsd protocol (port 3493, configurable)
- 8 event types: online, on_battery, low_battery, battery_restored,
  comms_lost, comms_restored, replace_battery, overload
- 3 bot commands: /status, /devices, /battery
- 38 Jinja2 templates (EN+RU notification + command templates)
- Database: tracking config fields, migration, seeds
- Frontend: provider form with host/port/credentials, grid items, i18n

Provider-agnostic UI improvements:
- Remove hardcoded 'immich' defaults from all config forms
- Dynamic collection labels per provider type (Albums/Repos/Boards/UPS Devices)
- Capability-driven test types instead of provider type checks
- Template variable helpers for all providers (was Immich-only)
- Guard Immich-only shared link check to Immich providers
- Auto-clear stale global provider filter from localStorage
- EntitySelect search placeholder shows current selection
- Fix noneLabel in linked target config selectors

New CLAUDE.md rule #8: no provider-specific hardcoding
This commit is contained in:
2026-03-23 23:23:58 +03:00
parent c451f3dd72
commit 68ac13b452
73 changed files with 1385 additions and 45 deletions
+16 -1
View File
@@ -111,6 +111,7 @@
"typeGitea": "Gitea",
"typePlanka": "Planka",
"typeScheduler": "Планировщик",
"typeNut": "NUT (ИБП)",
"loadError": "Не удалось загрузить провайдеры.",
"externalDomain": "Внешний домен",
"optional": "необязательно",
@@ -127,6 +128,13 @@
"apiTokenHint": "Необязательно. Нужен для проверки подключения и получения списка репозиториев.",
"webhookUrl": "URL вебхука",
"webhookUrlHint": "Укажите этот URL в настройках вебхука Gitea (относительно хоста bridge).",
"nutHost": "Хост NUT-сервера",
"nutHostPlaceholder": "192.168.1.100 или ups.local",
"nutPort": "Порт NUT-сервера",
"nutUsername": "Имя пользователя",
"nutPassword": "Пароль",
"nutUsernameHint": "Необязательно — только если upsd требует аутентификации",
"nutPasswordHint": "Необязательно — пароль пользователя upsd",
"testAndSave": "Проверить и сохранить",
"saveWithoutTest": "Сохранить без проверки"
},
@@ -141,6 +149,12 @@
"selectServer": "Выберите провайдер...",
"albums": "Альбомы",
"selectAlbums": "Выберите альбомы...",
"repositories": "Репозитории",
"selectRepositories": "Выберите репозитории...",
"boards": "Доски",
"selectBoards": "Выберите доски...",
"upsDevices": "ИБП устройства",
"selectUpsDevices": "Выберите ИБП...",
"eventTypes": "Типы событий",
"notificationTargets": "Получатели уведомлений",
"scanInterval": "Интервал проверки (секунды)",
@@ -826,7 +840,8 @@
"providerImmich": "Фотосервер для самостоятельного размещения",
"providerGitea": "Git-сервер для самостоятельного размещения",
"providerPlanka": "Канбан-доска для самостоятельного размещения",
"providerScheduler": "Запланированные сообщения по расписанию"
"providerScheduler": "Запланированные сообщения по расписанию",
"providerNut": "Мониторинг ИБП через NUT"
},
"error": {
"notFound": "Страница не найдена",