feat: add http, snmp, docker plugins; new widgets and routes for traefik/unifi/ups

- Add HTTP monitor plugin: endpoint checking with status history, latency tracking, time-range views
- Add SNMP plugin: OID polling, device management, metric recording
- Add Docker plugin: container status, resource usage, widget
- Traefik: access log widget, request chart widget, expanded routes
- UniFi: clients widget, devices widget, expanded poll routes
- UPS: history widget, additional routes
- Update plugin index with new entries

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-23 17:40:36 -04:00
parent d9886e8680
commit be4654fc72
45 changed files with 2701 additions and 1 deletions
+47
View File
@@ -123,3 +123,50 @@ async def widget():
on_battery_since=_on_battery_since,
shutdown_triggered=_shutdown_triggered,
)
@ups_bp.get("/widget/history")
@require_role(UserRole.viewer)
async def widget_history():
"""HTMX dashboard widget: Chart.js multi-line history (battery%, load%, input voltage)."""
from datetime import timedelta
import json as _json
hours = max(1, min(24, int(request.args.get("hours", 6) or 6)))
widget_id = request.args.get("wid", "0")
since = datetime.now(timezone.utc) - timedelta(hours=hours)
b_secs = max(60, (hours * 3600) // 80) # ~80 buckets
bucket_col = (
cast(func.strftime('%s', UpsStatus.scraped_at), Integer) / b_secs
).label("bucket")
async with current_app.db_sessionmaker() as db:
result = await db.execute(
select(
func.avg(UpsStatus.battery_charge_pct).label("battery"),
func.avg(UpsStatus.load_pct).label("load"),
func.avg(UpsStatus.input_voltage).label("voltage"),
func.min(UpsStatus.scraped_at).label("scraped_at"),
bucket_col,
)
.where(UpsStatus.scraped_at >= since)
.group_by(bucket_col)
.order_by(bucket_col)
)
history = result.all()
labels = list(range(len(history)))
battery = [round(r.battery or 0, 1) for r in history]
load = [round(r.load or 0, 1) for r in history]
voltage = [round(r.voltage or 0, 1) for r in history]
return await render_template(
"ups/widget_history.html",
labels_json=_json.dumps(labels),
battery_json=_json.dumps(battery),
load_json=_json.dumps(load),
voltage_json=_json.dumps(voltage),
widget_id=widget_id,
hours=hours,
)