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
@@ -67,9 +67,9 @@
const defaultForm = () => ({
name: '',
icon: '',
provider_type: 'immich',
enabled_commands: ['help', 'status', 'albums', 'events', 'latest', 'random', 'favorites', 'summary', 'memory'] as string[],
response_mode: 'media',
provider_type: '',
enabled_commands: [] as string[],
response_mode: 'text',
default_count: 5,
rate_limits: { search: 30, default: 10 },
command_template_config_id: null as number | null,
@@ -100,7 +100,7 @@
form = {
name: cfg.name,
icon: cfg.icon || '',
provider_type: cfg.provider_type || 'immich',
provider_type: cfg.provider_type || '',
enabled_commands: [...(cfg.enabled_commands || [])],
response_mode: cfg.response_mode || 'media',
default_count: cfg.default_count ?? 5,