The Site Speed Test poll against the controller's aggregated-dashboard
endpoint has always run unconditionally, so operators whose controllers
misbehave on that endpoint have no way to skip the request. Every other
optional collection already has a save_* flag; this adds the missing one.
Defaults to true in all three default paths (local defaults,
per-controller defaults and remote discovery) so existing setups keep
behaving exactly as before.
Refs #1030
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
golangci-lint auto-fixes across multiple packages:
- wsl_v5: blank lines between logical blocks
- nlreturn: newlines before return statements
- tagalign: struct field tag alignment
No logic changes.
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Add a per-controller `<namespace>_controller_up` Prometheus GaugeVec with
a `source` label (controller URL or configured ID). The gauge is set to 1
after each successful poll and 0 on failure, giving operators a standard
metric to alert on controller connectivity issues.
Changes:
- pkg/poller/config.go: add ControllerStatus type and ControllerStatuses
field to Metrics so any output plugin can consume per-controller health.
- pkg/poller/inputs.go: merge ControllerStatuses when AppendMetrics is
called (multiple input sources).
- pkg/inputunifi/interface.go: populate ControllerStatuses with Up=true
on success and Up=false (while still continuing) on per-controller error.
- pkg/promunifi/collector.go: declare and register a prometheus.GaugeVec
`<namespace>_controller_up`; set the gauge for each controller status
after every Collect cycle.
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* Fix panic when remote discovery fails and no controllers are configured
Call setDefaults(&u.Default) before logController(&u.Default) when
len(u.Controllers) == 0 so HashPII, DropPII, etc. are initialized
and logController does not dereference nil pointers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore: trigger CI re-run
Co-authored-by: Cursor <cursoragent@cursor.com>
* ci: use golangci-lint v2.9 for Go 1.26-compatible deps
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Fixes#425
When polling multiple controllers, if one controller was down or
unreachable, unpoller would stop collecting data from ALL controllers.
This caused complete data loss across all sites when just one was down.
Root Cause:
Both Metrics() and Events() methods would immediately return an error
when any controller failed, skipping all remaining controllers in the
loop.
Changes:
- Log errors from failed controllers but continue to next controller
- Track collection errors separately from successful data collection
- Only return error if ALL controllers failed and no data was collected
- Return success if at least one controller provided data
This allows unpoller to continue monitoring healthy controllers even
when some are temporarily unreachable due to network issues, timeouts,
or maintenance.
Example behavior:
- Controller 1: Down (timeout) - logs error, continues
- Controller 2: Up - collects data successfully
- Controller 3: Up - collects data successfully
- Result: Returns data from controllers 2 and 3
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add remote API mode with automatic controller discovery
- Discover consoles via /v1/hosts endpoint
- Auto-discover sites for each console via integration API
- Use console name from hosts response as site name override for Cloud Gateways
- Support both config-level and per-controller remote mode
- Add example configs for YAML, JSON, and TOML formats
- Remote API uses api.ui.com with X-API-Key authentication
- Automatically discovers all consoles when remote=true and remote_api_key is set
This enables monitoring multiple UniFi Cloud Gateways through a single
API key without requiring direct network access to each controller.
The UniFi controller HTTP client was created without a timeout, causing
unpoller to hang indefinitely when the controller becomes unresponsive.
This resulted in random stops where polling would cease until the
container was restarted.
Changes:
- Add Timeout field to Controller struct (cnfg.Duration)
- Set default timeout of 60 seconds
- Pass timeout to unifi.Config when creating the client
- Log timeout value on startup for visibility
The timeout can be configured via:
- Config file: timeout = "60s"
- Environment: UP_UNIFI_DEFAULT_TIMEOUT=60s
Fixes issue where container would hang overnight:
2025/12/22 22:29:27 - Requesting https://unifi/.../stat/sta
[~2 hour gap - request hung indefinitely]
2025/12/23 00:17:57 - Unmarshalling Device Type: udm...
- Add SaveProtectLogs config option to enable Protect log collection
- Add ProtectThumbnails config option to fetch event thumbnails
- Add collectProtectLogs function with 24h default fetch window
- Add ProtectLogEvent for Loki reporting with separate thumbnail log lines
- Add PII redaction for Protect log entries
- Filter thumbnail fetching to camera events only (motion, smartDetect*, etc.)
- Update log output to show Protect logs status
Add new save_syslog config option to collect events from the v2 UniFi
system-log API (/v2/api/site/{site}/system-log/all).
Changes:
- Add SaveSyslog field to Controller struct
- Add collectSyslog() function using v2 API
- Keep collectEvents() using v1 API for backwards compatibility
- Add RedactIPPII() helper for PII redaction
- Update lokiunifi to log raw JSON (parseable with Loki | json)
- Reduce indexed labels to low-cardinality fields only
- Add SystemLogEntry handler in lokiunifi report
Config: save_syslog (v2 API) vs save_events (v1 API)
Env: UP_UNIFI_DEFAULT_SAVE_SYSLOG=true