APC UPS Dashboard
A lightweight FastAPI + Redis based dashboard for multiple APC UPS devices using the apcaccess CLI (apcupsd Network Information Server mode). Shows real-time metrics, lightweight charts, and maintains 7 days of historical snapshots.
Features
- Multiple UPS managed dynamically (stored in Redis; add/update/delete via API/UI)
- Polling via
apcaccessCLI against apcupsd NIS (default port 3551) - Connection test: TCP port reachability (no protocol parsing)
- Real-time dashboard (Server Sent Events) updating key metrics
- 7-day retention of snapshots in Redis lists
- Simple Chart.js load percentage sparkline
- Docker & docker-compose deployment (image bundles apcupsd + apcaccess)
- SMTP alerting (high load, low battery %, on battery, low runtime) with cooldown
Configuration
Configuration is persisted in Redis (key ups:config:json). Use the web UI or the REST API to manage UPS entries and SMTP settings. A legacy config/ups.yaml (or path set via UPS_CONFIG_PATH) is imported once on first startup if Redis has no configuration; after migration the file is no longer written or read.
UPS fields:
- name (unique)
- host (apcupsd server hostname / IP)
- port (default 3551)
- interval_seconds (polling interval)
- Optional alert thresholds: alert_loadpct_high, alert_bcharge_low, alert_on_battery, alert_runtime_low_minutes
SMTP fields (optional): host, port, username, password (or env SMTP_PASSWORD), use_tls, use_ssl, from_addr, to_addrs[], subject_prefix.
Environment secret: set SMTP_PASSWORD instead of storing cleartext.
Alert suppression: identical alert per UPS suppressed for 30 minutes (cooldown).
apcupsd Server Requirements
Your remote APC UPS hosts must be running apcupsd with the Network Information Server (NIS) enabled. Typical steps (on Linux):
- Install apcupsd (example for Debian/Ubuntu):
sudo apt-get install apcupsd
- Edit
/etc/apcupsd/apcupsd.confand confirm at least:
UPSTYPE usb # or 'net' / 'snmp' depending on your setup
DEVICE # usually blank for USB
NISIP 0.0.0.0 # listen on all interfaces (restrict in firewalled env)
NISPORT 3551 # must match the configured port (default 3551)
NETSERVER on # enable network server
# Optional: restrict access (recommended)
ACCESS 192.168.1.0/24 # Only allow your monitoring subnet (supported on some builds)
- Restart service:
sudo systemctl restart apcupsd
- Test locally:
apcaccess status
nc -vz <server-ip> 3551
If you receive connectivity errors in logs:
- Verify
NETSERVER onis set. - Check host firewall (e.g.,
ufw allow 3551/tcp). - Confirm the port is correct and reachable from the container network.
- Test manually:
nc -vz <server-ip> 3551
apcaccess -h <server-ip>:3551 status
Connection Testing Logic
Simplified: only a raw TCP connect test. If the port is reachable it's reported as success. Polling uses the apcaccess CLI for data collection.
Run (docker-compose)
docker compose up --build
Visit http://localhost:8000
Persistence
Configuration and historical metrics live in Redis. The provided docker-compose.yml now mounts a named volume (redis-data) at /data inside the Redis container and enables Append Only File (AOF) with --appendonly yes.
Data will persist across docker compose down / up cycles as long as you do NOT remove the volume. To explicitly remove all persisted configuration and history you must prune the volume:
docker compose down
docker volume rm apcupsd-client_redis-data # volume name may be prefixed by folder/project
If you had been losing configuration previously, ensure you pulled the updated compose file and that the redis service includes:
redis:
volumes:
- redis-data:/data
command: ["redis-server", "--appendonly", "yes", "--appendfsync", "everysec"]
You can inspect Redis persistence files locally by running:
docker compose exec redis ls -lh /data
If you want to enforce periodic RDB snapshots as well, you can leave default save settings (remove the --save "" override). The current configuration uses AOF every second for a balance of durability and write performance.
Data Storage
Redis stores:
- Latest snapshot hash:
ups:snap:<name> - History list (JSON {ts,data}):
ups:hist:<name> - Per-minute watts averages:
ups:watts:permin:<name> - Energy (watt-seconds) daily totals:
ups:energy:<name>:YYYYMMDD - Events list:
ups:event:list:<name> - Recent alerts:
ups:alerts:recent:<name> - Voltage deviation samples:
ups:volt:dev:samples:<name>
A pruning task runs hourly removing entries older than 7 days.
Extending
- Add more charts: query
/api/ups/<name>/history - Add gauges: integrate a JS gauge lib in
dashboard.html - Alerts: create background task checking thresholds
License
MIT
Security & Hardening Notes
| Area | Current | Recommendation |
|---|---|---|
| Authentication | None (open dashboard) | Add reverse proxy auth or FastAPI auth if exposed beyond LAN |
| Config Storage | Redis (JSON) | Protect Redis with auth / network policy |
| Network to apcupsd | Plain TCP | Use network segmentation / firewall; protocol has no encryption |
| Redis | No auth configured | Enable AUTH / TLS if crossing trust boundaries |
| Input Validation | Pydantic for config schema | Add stricter hostname/IP validation if multi-tenant |
| Dependency Versions | Pinned | Review periodically for CVEs |
| Logging | Polling errors logged | Avoid logging secrets; sanitize future additions |
Additional Notes
- Legacy YAML migration (one-time) to Redis; file no longer updated afterward.
- Dynamic poller reconciles tasks on config change (no restart needed).
- Alert cooldown prevents email flood.
- AOF-based Redis persistence keeps configuration across container rebuilds.
Suggested Future Enhancements
- Optional Basic Auth / OIDC for web UI.
- Rate limiting on config mutation endpoints.
- CSRF protection if cookies/session auth added later.
- Health endpoint (
/healthz) returning Redis + config status. - Structured logging (JSON) for production observability.