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:
@@ -3,6 +3,7 @@
|
||||
from notify_bridge_core.providers.immich import ImmichServiceProvider
|
||||
from notify_bridge_core.providers.gitea import GiteaServiceProvider
|
||||
from notify_bridge_core.providers.planka import PlankaServiceProvider
|
||||
from notify_bridge_core.providers.nut import NutServiceProvider
|
||||
|
||||
from ..database.models import ServiceProvider
|
||||
|
||||
@@ -39,3 +40,15 @@ def make_planka_provider(http_session, provider: ServiceProvider) -> PlankaServi
|
||||
config.get("api_key", ""),
|
||||
provider.name,
|
||||
)
|
||||
|
||||
|
||||
def make_nut_provider(provider: ServiceProvider) -> NutServiceProvider:
|
||||
"""Create a NutServiceProvider from a DB provider model."""
|
||||
config = provider.config or {}
|
||||
return NutServiceProvider(
|
||||
host=config.get("host", "localhost"),
|
||||
port=config.get("port", 3493),
|
||||
username=config.get("username"),
|
||||
password=config.get("password"),
|
||||
name=provider.name,
|
||||
)
|
||||
|
||||
@@ -83,6 +83,15 @@ def event_allowed_by_config(event: ServiceEvent, tc: TrackingConfig) -> bool:
|
||||
"task_completed": tc.track_task_completed,
|
||||
# Scheduler events
|
||||
"scheduled_message": tc.track_scheduled_message,
|
||||
# NUT (UPS) events
|
||||
"ups_online": tc.track_ups_online,
|
||||
"ups_on_battery": tc.track_ups_on_battery,
|
||||
"ups_low_battery": tc.track_ups_low_battery,
|
||||
"ups_battery_restored": tc.track_ups_battery_restored,
|
||||
"ups_comms_lost": tc.track_ups_comms_lost,
|
||||
"ups_comms_restored": tc.track_ups_comms_restored,
|
||||
"ups_replace_battery": tc.track_ups_replace_battery,
|
||||
"ups_overload": tc.track_ups_overload,
|
||||
}
|
||||
return flag_map.get(event_type, True)
|
||||
|
||||
|
||||
@@ -123,6 +123,16 @@ async def check_tracker(tracker_id: int) -> dict[str, Any]:
|
||||
custom_variables=custom_vars,
|
||||
)
|
||||
events, new_state = await sched.poll(collection_ids, state_dict)
|
||||
elif provider_type == "nut":
|
||||
from notify_bridge_core.providers.nut import NutServiceProvider
|
||||
nut = NutServiceProvider(
|
||||
host=provider_config.get("host", "localhost"),
|
||||
port=provider_config.get("port", 3493),
|
||||
username=provider_config.get("username"),
|
||||
password=provider_config.get("password"),
|
||||
name=provider_name,
|
||||
)
|
||||
events, new_state = await nut.poll(collection_ids, state_dict)
|
||||
else:
|
||||
return {"status": "error", "reason": f"unsupported provider type: {provider_type}"}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user