Merge pull request #999 from unpoller/upgrade/unifi-v5.26.0
feat: upgrade unifi to v5.26.0 with Integration/v1 + new legacy metrics
This commit is contained in:
commit
bbc33006ee
4
go.mod
4
go.mod
|
|
@ -12,7 +12,7 @@ require (
|
|||
github.com/prometheus/common v0.67.5
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/unpoller/unifi/v5 v5.25.0
|
||||
github.com/unpoller/unifi/v5 v5.26.0
|
||||
go.opentelemetry.io/otel v1.43.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0
|
||||
|
|
@ -36,7 +36,7 @@ require (
|
|||
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
golang.org/x/net v0.52.0 // indirect
|
||||
golang.org/x/net v0.53.0 // indirect
|
||||
golang.org/x/text v0.36.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
||||
|
|
|
|||
8
go.sum
8
go.sum
|
|
@ -87,8 +87,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
|||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/unpoller/unifi/v5 v5.25.0 h1:smX4nXSnCoZ7JenZdD9fktpja/yVUHUlXojYqQ7Be+Q=
|
||||
github.com/unpoller/unifi/v5 v5.25.0/go.mod h1:0R6t/SKaS8eoOrTkSYwzVb292KG5eQfbKEuevuES0So=
|
||||
github.com/unpoller/unifi/v5 v5.26.0 h1:W4wGiWsw+BnA+8Ozykwv7zneQkbq5ozTtLJnxjA/mbI=
|
||||
github.com/unpoller/unifi/v5 v5.26.0/go.mod h1:G1PKfGQsflTd4nVgoLFUkvce+wcdd8tu5ICS42X3MJY=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
|
|
@ -120,8 +120,8 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
|
|
|||
|
|
@ -337,6 +337,90 @@ func (u *DatadogUnifi) loopPoints(r report) {
|
|||
for _, v := range m.VPNMeshes {
|
||||
u.switchExport(r, v)
|
||||
}
|
||||
|
||||
for _, v := range m.PortForwards {
|
||||
u.switchExport(r, v)
|
||||
}
|
||||
|
||||
for _, v := range m.SSLCertificates {
|
||||
u.switchExport(r, v)
|
||||
}
|
||||
|
||||
for _, v := range m.UPSDevices {
|
||||
u.switchExport(r, v)
|
||||
}
|
||||
|
||||
for _, v := range m.WANStatuses {
|
||||
u.switchExport(r, v)
|
||||
}
|
||||
|
||||
for _, v := range m.IntegrationDevStats {
|
||||
u.switchExport(r, v)
|
||||
}
|
||||
|
||||
for _, v := range m.WifiBroadcasts {
|
||||
u.switchExport(r, v)
|
||||
}
|
||||
|
||||
for _, v := range m.FirewallZones {
|
||||
u.switchExport(r, v)
|
||||
}
|
||||
|
||||
for _, v := range m.ACLRules {
|
||||
u.switchExport(r, v)
|
||||
}
|
||||
|
||||
for _, v := range m.VPNServers {
|
||||
u.switchExport(r, v)
|
||||
}
|
||||
|
||||
for _, v := range m.SiteToSiteTunnels {
|
||||
u.switchExport(r, v)
|
||||
}
|
||||
|
||||
for _, v := range m.LAGs {
|
||||
u.switchExport(r, v)
|
||||
}
|
||||
|
||||
for _, v := range m.MCLAGDomains {
|
||||
u.switchExport(r, v)
|
||||
}
|
||||
|
||||
for _, v := range m.SwitchStacks {
|
||||
u.switchExport(r, v)
|
||||
}
|
||||
|
||||
for _, v := range m.DNSPolicies {
|
||||
u.switchExport(r, v)
|
||||
}
|
||||
|
||||
for _, v := range m.RADIUSProfiles {
|
||||
u.switchExport(r, v)
|
||||
}
|
||||
|
||||
for _, v := range m.TrafficMatchingLists {
|
||||
u.switchExport(r, v)
|
||||
}
|
||||
|
||||
for _, v := range m.HotspotVouchers {
|
||||
u.switchExport(r, v)
|
||||
}
|
||||
|
||||
for _, v := range m.DPIApplications {
|
||||
u.switchExport(r, v)
|
||||
}
|
||||
|
||||
for _, v := range m.DPICategories {
|
||||
u.switchExport(r, v)
|
||||
}
|
||||
|
||||
for _, v := range m.PendingDevices {
|
||||
u.switchExport(r, v)
|
||||
}
|
||||
|
||||
for _, v := range m.Countries {
|
||||
u.switchExport(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *DatadogUnifi) switchExport(r report, v any) { //nolint:cyclop
|
||||
|
|
@ -385,6 +469,48 @@ func (u *DatadogUnifi) switchExport(r report, v any) { //nolint:cyclop
|
|||
u.batchPortAnomaly(r, v)
|
||||
case *unifi.MagicSiteToSiteVPN:
|
||||
u.batchMagicSiteToSiteVPN(r, v)
|
||||
case *unifi.PortForward:
|
||||
u.batchPortForward(r, v)
|
||||
case *unifi.SSLCertificate:
|
||||
u.batchSSLCertificate(r, v)
|
||||
case *unifi.UPSDeviceSelector:
|
||||
u.batchUPSDevice(r, v)
|
||||
case *unifi.WANStatus:
|
||||
u.batchWANStatus(r, v)
|
||||
case *unifi.IntegrationDeviceStats:
|
||||
u.batchIntegrationDevStats(r, v)
|
||||
case *unifi.WifiBroadcast:
|
||||
u.batchWifiBroadcast(r, v)
|
||||
case *unifi.FirewallZone:
|
||||
u.batchFirewallZone(r, v)
|
||||
case *unifi.ACLRule:
|
||||
u.batchACLRule(r, v)
|
||||
case *unifi.VPNServer:
|
||||
u.batchVPNServer(r, v)
|
||||
case *unifi.SiteToSiteTunnel:
|
||||
u.batchSiteToSiteTunnel(r, v)
|
||||
case *unifi.LAG:
|
||||
u.batchLAG(r, v)
|
||||
case *unifi.MCLAGDomain:
|
||||
u.batchMCLAGDomain(r, v)
|
||||
case *unifi.SwitchStack:
|
||||
u.batchSwitchStack(r, v)
|
||||
case *unifi.DNSPolicy:
|
||||
u.batchDNSPolicy(r, v)
|
||||
case *unifi.RADIUSProfile:
|
||||
u.batchRADIUSProfile(r, v)
|
||||
case *unifi.TrafficMatchingList:
|
||||
u.batchTrafficMatchingList(r, v)
|
||||
case *unifi.HotspotVoucher:
|
||||
u.batchHotspotVoucher(r, v)
|
||||
case *unifi.DPIApplication:
|
||||
u.batchDPIApplication(r, v)
|
||||
case *unifi.DPICategory:
|
||||
u.batchDPICategory(r, v)
|
||||
case *unifi.PendingDevice:
|
||||
u.batchPendingDevice(r, v)
|
||||
case *unifi.Country:
|
||||
u.batchCountry(r, v)
|
||||
default:
|
||||
if u.Collector != nil && u.Collector.Poller().LogUnknownTypes {
|
||||
u.LogDebugf("unknown export type: %T", v)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
package datadogunifi
|
||||
|
||||
import (
|
||||
"github.com/unpoller/unifi/v5"
|
||||
)
|
||||
|
||||
// batchIntegrationDevStats generates integration device statistics datapoints for Datadog.
|
||||
func (u *DatadogUnifi) batchIntegrationDevStats(r report, ids *unifi.IntegrationDeviceStats) {
|
||||
if ids == nil {
|
||||
return
|
||||
}
|
||||
|
||||
metricName := metricNamespace("integration_device")
|
||||
|
||||
tags := cleanTags(map[string]string{
|
||||
"device_id": ids.DeviceID,
|
||||
})
|
||||
|
||||
tagSlice := tagMapToTags(tags)
|
||||
|
||||
_ = r.reportGauge(metricName("cpu_utilization_pct"), ids.CPUUtilizationPct.Val, tagSlice)
|
||||
_ = r.reportGauge(metricName("memory_utilization_pct"), ids.MemoryUtilizationPct.Val, tagSlice)
|
||||
_ = r.reportGauge(metricName("uptime_sec"), ids.UptimeSec.Val, tagSlice)
|
||||
_ = r.reportGauge(metricName("load_average_1min"), ids.LoadAverage1Min.Val, tagSlice)
|
||||
_ = r.reportGauge(metricName("load_average_5min"), ids.LoadAverage5Min.Val, tagSlice)
|
||||
_ = r.reportGauge(metricName("load_average_15min"), ids.LoadAverage15Min.Val, tagSlice)
|
||||
|
||||
radioMetric := metricNamespace("integration_device_radio")
|
||||
|
||||
for i := range ids.Radios {
|
||||
radio := &ids.Radios[i]
|
||||
|
||||
radioTags := append(tagSlice,
|
||||
tag("frequency_ghz", radio.FrequencyGHz.Val),
|
||||
)
|
||||
|
||||
_ = r.reportGauge(radioMetric("tx_retries_pct"), radio.TxRetriesPct.Val, radioTags)
|
||||
}
|
||||
|
||||
uplinkMetric := metricNamespace("integration_device_uplink")
|
||||
|
||||
for i := range ids.Uplinks {
|
||||
uplink := &ids.Uplinks[i]
|
||||
|
||||
uplinkTags := append(tagSlice,
|
||||
tag("uplink_index", i),
|
||||
)
|
||||
|
||||
_ = r.reportGauge(uplinkMetric("rx_rate_bps"), uplink.RxRateBps.Val, uplinkTags)
|
||||
_ = r.reportGauge(uplinkMetric("tx_rate_bps"), uplink.TxRateBps.Val, uplinkTags)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package datadogunifi
|
||||
|
||||
import (
|
||||
"github.com/unpoller/unifi/v5"
|
||||
)
|
||||
|
||||
// batchDPIApplication generates DPI application catalogue datapoints for Datadog.
|
||||
// DPIApplications are global (no site scope).
|
||||
func (u *DatadogUnifi) batchDPIApplication(r report, da *unifi.DPIApplication) {
|
||||
if da == nil {
|
||||
return
|
||||
}
|
||||
|
||||
metricName := metricNamespace("dpi_application")
|
||||
|
||||
tags := cleanTags(map[string]string{
|
||||
"name": da.Name,
|
||||
})
|
||||
|
||||
_ = r.reportGauge(metricName("id"), da.ID.Val, tagMapToTags(tags))
|
||||
}
|
||||
|
||||
// batchDPICategory generates DPI category catalogue datapoints for Datadog.
|
||||
// DPICategories are global (no site scope).
|
||||
func (u *DatadogUnifi) batchDPICategory(r report, dc *unifi.DPICategory) {
|
||||
if dc == nil {
|
||||
return
|
||||
}
|
||||
|
||||
metricName := metricNamespace("dpi_category")
|
||||
|
||||
tags := cleanTags(map[string]string{
|
||||
"name": dc.Name,
|
||||
})
|
||||
|
||||
_ = r.reportGauge(metricName("id"), dc.ID.Val, tagMapToTags(tags))
|
||||
}
|
||||
|
||||
// batchPendingDevice generates PendingDevice (adoption queue) datapoints for Datadog.
|
||||
// PendingDevices are global (no site scope).
|
||||
func (u *DatadogUnifi) batchPendingDevice(r report, pd *unifi.PendingDevice) {
|
||||
if pd == nil {
|
||||
return
|
||||
}
|
||||
|
||||
metricName := metricNamespace("pending_device")
|
||||
|
||||
tags := cleanTags(map[string]string{
|
||||
"mac_address": pd.MACAddress,
|
||||
"ip_address": pd.IPAddress,
|
||||
"model": pd.Model,
|
||||
"state": pd.State,
|
||||
})
|
||||
|
||||
_ = r.reportGauge(metricName("supported"), boolToFloat64(pd.Supported), tagMapToTags(tags))
|
||||
_ = r.reportGauge(metricName("firmware_updatable"), boolToFloat64(pd.FirmwareUpdatable), tagMapToTags(tags))
|
||||
}
|
||||
|
||||
// batchCountry generates Country datapoints for Datadog.
|
||||
// Countries are global (no site scope).
|
||||
func (u *DatadogUnifi) batchCountry(r report, co *unifi.Country) {
|
||||
if co == nil {
|
||||
return
|
||||
}
|
||||
|
||||
metricName := metricNamespace("country")
|
||||
|
||||
tags := cleanTags(map[string]string{
|
||||
"code": co.Code,
|
||||
"name": co.Name,
|
||||
})
|
||||
|
||||
// Emit a presence gauge (1.0 = country entry exists in the catalogue).
|
||||
_ = r.reportGauge(metricName("present"), 1.0, tagMapToTags(tags))
|
||||
}
|
||||
|
|
@ -0,0 +1,235 @@
|
|||
package datadogunifi
|
||||
|
||||
import (
|
||||
"github.com/unpoller/unifi/v5"
|
||||
)
|
||||
|
||||
// batchWifiBroadcast generates WifiBroadcast (SSID) datapoints for Datadog.
|
||||
func (u *DatadogUnifi) batchWifiBroadcast(r report, wb *unifi.WifiBroadcast) {
|
||||
if wb == nil {
|
||||
return
|
||||
}
|
||||
|
||||
metricName := metricNamespace("wifi_broadcast")
|
||||
|
||||
tags := cleanTags(map[string]string{
|
||||
"site_name": wb.SiteName,
|
||||
"id": wb.ID,
|
||||
"name": wb.Name,
|
||||
"network": wb.Network,
|
||||
"security_type": wb.SecurityConfiguration.Type,
|
||||
})
|
||||
|
||||
_ = r.reportGauge(metricName("enabled"), boolToFloat64(wb.Enabled), tagMapToTags(tags))
|
||||
}
|
||||
|
||||
// batchFirewallZone generates FirewallZone datapoints for Datadog.
|
||||
func (u *DatadogUnifi) batchFirewallZone(r report, fz *unifi.FirewallZone) {
|
||||
if fz == nil {
|
||||
return
|
||||
}
|
||||
|
||||
metricName := metricNamespace("firewall_zone")
|
||||
|
||||
tags := cleanTags(map[string]string{
|
||||
"site_name": fz.SiteName,
|
||||
"id": fz.ID,
|
||||
"name": fz.Name,
|
||||
"origin": fz.Metadata.Origin,
|
||||
})
|
||||
|
||||
_ = r.reportGauge(metricName("network_count"), float64(len(fz.NetworkIDs)), tagMapToTags(tags))
|
||||
}
|
||||
|
||||
// batchACLRule generates ACLRule datapoints for Datadog.
|
||||
func (u *DatadogUnifi) batchACLRule(r report, acl *unifi.ACLRule) {
|
||||
if acl == nil {
|
||||
return
|
||||
}
|
||||
|
||||
metricName := metricNamespace("acl_rule")
|
||||
|
||||
tags := cleanTags(map[string]string{
|
||||
"site_name": acl.SiteName,
|
||||
"id": acl.ID,
|
||||
"name": acl.Name,
|
||||
"action": acl.Action,
|
||||
"source_filter": acl.SourceFilter,
|
||||
})
|
||||
|
||||
_ = r.reportGauge(metricName("enabled"), boolToFloat64(acl.Enabled), tagMapToTags(tags))
|
||||
_ = r.reportGauge(metricName("index"), acl.Index.Val, tagMapToTags(tags))
|
||||
_ = r.reportGauge(metricName("enforcing_device_count"), float64(len(acl.EnforcingDeviceFilter)), tagMapToTags(tags))
|
||||
}
|
||||
|
||||
// batchVPNServer generates VPNServer datapoints for Datadog.
|
||||
func (u *DatadogUnifi) batchVPNServer(r report, vs *unifi.VPNServer) {
|
||||
if vs == nil {
|
||||
return
|
||||
}
|
||||
|
||||
metricName := metricNamespace("vpn_server")
|
||||
|
||||
tags := cleanTags(map[string]string{
|
||||
"site_name": vs.SiteName,
|
||||
"id": vs.ID,
|
||||
"name": vs.Name,
|
||||
"type": vs.Type,
|
||||
"origin": vs.Metadata.Origin,
|
||||
})
|
||||
|
||||
_ = r.reportGauge(metricName("enabled"), boolToFloat64(vs.Enabled), tagMapToTags(tags))
|
||||
}
|
||||
|
||||
// batchSiteToSiteTunnel generates SiteToSiteTunnel datapoints for Datadog.
|
||||
func (u *DatadogUnifi) batchSiteToSiteTunnel(r report, tun *unifi.SiteToSiteTunnel) {
|
||||
if tun == nil {
|
||||
return
|
||||
}
|
||||
|
||||
metricName := metricNamespace("site_to_site_tunnel")
|
||||
|
||||
tags := cleanTags(map[string]string{
|
||||
"site_name": tun.SiteName,
|
||||
"id": tun.ID,
|
||||
"name": tun.Name,
|
||||
"type": tun.Type,
|
||||
"origin": tun.Metadata.Origin,
|
||||
})
|
||||
|
||||
// Emit a presence gauge (1.0 = tunnel is configured).
|
||||
_ = r.reportGauge(metricName("present"), 1.0, tagMapToTags(tags))
|
||||
}
|
||||
|
||||
// batchLAG generates LAG (link aggregation group) datapoints for Datadog.
|
||||
func (u *DatadogUnifi) batchLAG(r report, lag *unifi.LAG) {
|
||||
if lag == nil {
|
||||
return
|
||||
}
|
||||
|
||||
metricName := metricNamespace("lag")
|
||||
|
||||
tags := cleanTags(map[string]string{
|
||||
"site_name": lag.SiteName,
|
||||
"id": lag.ID,
|
||||
"type": lag.Type,
|
||||
"origin": lag.Metadata.Origin,
|
||||
})
|
||||
|
||||
_ = r.reportGauge(metricName("member_count"), float64(len(lag.Members)), tagMapToTags(tags))
|
||||
}
|
||||
|
||||
// batchMCLAGDomain generates MCLAGDomain datapoints for Datadog.
|
||||
func (u *DatadogUnifi) batchMCLAGDomain(r report, mcd *unifi.MCLAGDomain) {
|
||||
if mcd == nil {
|
||||
return
|
||||
}
|
||||
|
||||
metricName := metricNamespace("mclag_domain")
|
||||
|
||||
tags := cleanTags(map[string]string{
|
||||
"site_name": mcd.SiteName,
|
||||
"id": mcd.ID,
|
||||
"name": mcd.Name,
|
||||
"origin": mcd.Metadata.Origin,
|
||||
})
|
||||
|
||||
_ = r.reportGauge(metricName("peer_count"), float64(len(mcd.Peers)), tagMapToTags(tags))
|
||||
_ = r.reportGauge(metricName("lag_count"), float64(len(mcd.LAGs)), tagMapToTags(tags))
|
||||
}
|
||||
|
||||
// batchSwitchStack generates SwitchStack datapoints for Datadog.
|
||||
func (u *DatadogUnifi) batchSwitchStack(r report, ss *unifi.SwitchStack) {
|
||||
if ss == nil {
|
||||
return
|
||||
}
|
||||
|
||||
metricName := metricNamespace("switch_stack")
|
||||
|
||||
tags := cleanTags(map[string]string{
|
||||
"site_name": ss.SiteName,
|
||||
"id": ss.ID,
|
||||
"name": ss.Name,
|
||||
"origin": ss.Metadata.Origin,
|
||||
})
|
||||
|
||||
_ = r.reportGauge(metricName("member_count"), float64(len(ss.Members)), tagMapToTags(tags))
|
||||
_ = r.reportGauge(metricName("lag_count"), float64(len(ss.LAGs)), tagMapToTags(tags))
|
||||
}
|
||||
|
||||
// batchDNSPolicy generates DNSPolicy datapoints for Datadog.
|
||||
func (u *DatadogUnifi) batchDNSPolicy(r report, dp *unifi.DNSPolicy) {
|
||||
if dp == nil {
|
||||
return
|
||||
}
|
||||
|
||||
metricName := metricNamespace("dns_policy")
|
||||
|
||||
tags := cleanTags(map[string]string{
|
||||
"site_name": dp.SiteName,
|
||||
"id": dp.ID,
|
||||
"type": dp.Type,
|
||||
"domain": dp.Domain,
|
||||
})
|
||||
|
||||
_ = r.reportGauge(metricName("enabled"), boolToFloat64(dp.Enabled), tagMapToTags(tags))
|
||||
}
|
||||
|
||||
// batchRADIUSProfile generates RADIUSProfile datapoints for Datadog.
|
||||
func (u *DatadogUnifi) batchRADIUSProfile(r report, rp *unifi.RADIUSProfile) {
|
||||
if rp == nil {
|
||||
return
|
||||
}
|
||||
|
||||
metricName := metricNamespace("radius_profile")
|
||||
|
||||
tags := cleanTags(map[string]string{
|
||||
"site_name": rp.SiteName,
|
||||
"id": rp.ID,
|
||||
"name": rp.Name,
|
||||
"origin": rp.Metadata.Origin,
|
||||
})
|
||||
|
||||
// Emit a presence gauge (1.0 = profile exists).
|
||||
_ = r.reportGauge(metricName("present"), 1.0, tagMapToTags(tags))
|
||||
}
|
||||
|
||||
// batchTrafficMatchingList generates TrafficMatchingList datapoints for Datadog.
|
||||
func (u *DatadogUnifi) batchTrafficMatchingList(r report, tml *unifi.TrafficMatchingList) {
|
||||
if tml == nil {
|
||||
return
|
||||
}
|
||||
|
||||
metricName := metricNamespace("traffic_matching_list")
|
||||
|
||||
tags := cleanTags(map[string]string{
|
||||
"site_name": tml.SiteName,
|
||||
"id": tml.ID,
|
||||
"name": tml.Name,
|
||||
"type": tml.Type,
|
||||
})
|
||||
|
||||
// Emit a presence gauge (1.0 = list exists).
|
||||
_ = r.reportGauge(metricName("present"), 1.0, tagMapToTags(tags))
|
||||
}
|
||||
|
||||
// batchHotspotVoucher generates HotspotVoucher datapoints for Datadog.
|
||||
func (u *DatadogUnifi) batchHotspotVoucher(r report, hv *unifi.HotspotVoucher) {
|
||||
if hv == nil {
|
||||
return
|
||||
}
|
||||
|
||||
metricName := metricNamespace("hotspot_voucher")
|
||||
|
||||
tags := cleanTags(map[string]string{
|
||||
"site_name": hv.SiteName,
|
||||
"id": hv.ID,
|
||||
"name": hv.Name,
|
||||
"expires_at": hv.ExpiresAt,
|
||||
})
|
||||
|
||||
_ = r.reportGauge(metricName("authorized_guest_count"), hv.AuthorizedGuestCount.Val, tagMapToTags(tags))
|
||||
_ = r.reportGauge(metricName("authorized_guest_limit"), hv.AuthorizedGuestLimit.Val, tagMapToTags(tags))
|
||||
_ = r.reportGauge(metricName("data_usage_limit_mbytes"), hv.DataUsageLimitMBytes.Val, tagMapToTags(tags))
|
||||
_ = r.reportGauge(metricName("time_limit_minutes"), hv.TimeLimitMinutes.Val, tagMapToTags(tags))
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package datadogunifi
|
||||
|
||||
import (
|
||||
"github.com/unpoller/unifi/v5"
|
||||
)
|
||||
|
||||
// batchPortForward generates PortForward datapoints for Datadog.
|
||||
func (u *DatadogUnifi) batchPortForward(r report, pf *unifi.PortForward) {
|
||||
if pf == nil {
|
||||
return
|
||||
}
|
||||
|
||||
metricName := metricNamespace("port_forward")
|
||||
|
||||
tags := cleanTags(map[string]string{
|
||||
"site_name": pf.SiteName,
|
||||
"source": pf.SourceName,
|
||||
"id": pf.ID,
|
||||
"name": pf.Name,
|
||||
"proto": pf.Proto,
|
||||
"fwd_ip": pf.FwdIP,
|
||||
"fwd_port": pf.FwdPort,
|
||||
"dst_port": pf.DstPort,
|
||||
"pf_iface": pf.PfwdPf,
|
||||
})
|
||||
|
||||
_ = r.reportGauge(metricName("enabled"), boolToFloat64(pf.Enabled.Val), tagMapToTags(tags))
|
||||
_ = r.reportGauge(metricName("log"), boolToFloat64(pf.Log.Val), tagMapToTags(tags))
|
||||
}
|
||||
|
||||
// batchSSLCertificate generates SSLCertificate datapoints for Datadog.
|
||||
func (u *DatadogUnifi) batchSSLCertificate(r report, cert *unifi.SSLCertificate) {
|
||||
if cert == nil || cert.ID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
metricName := metricNamespace("ssl_cert")
|
||||
|
||||
tags := cleanTags(map[string]string{
|
||||
"site_name": cert.SiteName,
|
||||
"id": cert.ID,
|
||||
"cert_type": cert.CertType,
|
||||
"status": cert.Status,
|
||||
"issuer": cert.Issuer,
|
||||
"subject": cert.Subject,
|
||||
"fingerprint": cert.Fingerprint,
|
||||
})
|
||||
|
||||
_ = r.reportGauge(metricName("is_active"), boolToFloat64(cert.IsActive.Val), tagMapToTags(tags))
|
||||
_ = r.reportGauge(metricName("is_valid"), boolToFloat64(cert.IsValid.Val), tagMapToTags(tags))
|
||||
_ = r.reportGauge(metricName("valid_from"), cert.ValidFrom.Val, tagMapToTags(tags))
|
||||
_ = r.reportGauge(metricName("valid_to"), cert.ValidTo.Val, tagMapToTags(tags))
|
||||
_ = r.reportGauge(metricName("chain_length"), float64(len(cert.Chain)), tagMapToTags(tags))
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package datadogunifi
|
||||
|
||||
import (
|
||||
"github.com/unpoller/unifi/v5"
|
||||
)
|
||||
|
||||
// batchUPSDevice generates UPS device selector datapoints for Datadog.
|
||||
func (u *DatadogUnifi) batchUPSDevice(r report, ups *unifi.UPSDeviceSelector) {
|
||||
if ups == nil {
|
||||
return
|
||||
}
|
||||
|
||||
metricName := metricNamespace("ups_device")
|
||||
|
||||
tags := cleanTags(map[string]string{
|
||||
"site_name": ups.SiteName,
|
||||
"source": ups.SourceName,
|
||||
"id": ups.ID,
|
||||
"mac": ups.MAC,
|
||||
"label": ups.Label,
|
||||
})
|
||||
|
||||
// Emit a presence gauge (1.0 = device exists in the adoption list).
|
||||
_ = r.reportGauge(metricName("present"), 1.0, tagMapToTags(tags))
|
||||
}
|
||||
|
||||
// batchWANStatus generates WAN status datapoints for Datadog.
|
||||
func (u *DatadogUnifi) batchWANStatus(r report, ws *unifi.WANStatus) {
|
||||
if ws == nil {
|
||||
return
|
||||
}
|
||||
|
||||
metricName := metricNamespace("wan_status")
|
||||
|
||||
for i := range ws.WANInterfaces {
|
||||
iface := &ws.WANInterfaces[i]
|
||||
|
||||
active := 0.0
|
||||
if iface.State == "ACTIVE" {
|
||||
active = 1.0
|
||||
}
|
||||
|
||||
tags := cleanTags(map[string]string{
|
||||
"site_name": ws.SiteName,
|
||||
"wan_name": iface.Name,
|
||||
"wan_networkgroup": iface.WANNetworkgroup,
|
||||
"state": iface.State,
|
||||
})
|
||||
|
||||
_ = r.reportGauge(metricName("active"), active, tagMapToTags(tags))
|
||||
}
|
||||
}
|
||||
|
|
@ -450,6 +450,133 @@ func (u *InfluxUnifi) loopPoints(r report) {
|
|||
for _, v := range m.VPNMeshes {
|
||||
u.switchExport(r, v)
|
||||
}
|
||||
|
||||
// v5.26.0 additions.
|
||||
for _, ds := range m.IntegrationDevStats {
|
||||
if d, ok := ds.(*unifi.IntegrationDeviceStats); ok {
|
||||
u.batchIntegrationDeviceStats(r, d)
|
||||
}
|
||||
}
|
||||
|
||||
for _, ws := range m.WANStatuses {
|
||||
if w, ok := ws.(*unifi.WANStatus); ok {
|
||||
u.batchWANStatus(r, w)
|
||||
}
|
||||
}
|
||||
|
||||
for _, pf := range m.PortForwards {
|
||||
if v, ok := pf.(*unifi.PortForward); ok {
|
||||
u.batchPortForward(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, sc := range m.SSLCertificates {
|
||||
if v, ok := sc.(*unifi.SSLCertificate); ok {
|
||||
u.batchSSLCertificate(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, ud := range m.UPSDevices {
|
||||
if v, ok := ud.(*unifi.UPSDeviceSelector); ok {
|
||||
u.batchUPSDevice(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, wb := range m.WifiBroadcasts {
|
||||
if v, ok := wb.(*unifi.WifiBroadcast); ok {
|
||||
u.batchWifiBroadcast(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, fz := range m.FirewallZones {
|
||||
if v, ok := fz.(*unifi.FirewallZone); ok {
|
||||
u.batchFirewallZone(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, ar := range m.ACLRules {
|
||||
if v, ok := ar.(*unifi.ACLRule); ok {
|
||||
u.batchACLRule(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, vs := range m.VPNServers {
|
||||
if v, ok := vs.(*unifi.VPNServer); ok {
|
||||
u.batchVPNServer(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, st := range m.SiteToSiteTunnels {
|
||||
if v, ok := st.(*unifi.SiteToSiteTunnel); ok {
|
||||
u.batchSiteToSiteTunnel(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, lag := range m.LAGs {
|
||||
if v, ok := lag.(*unifi.LAG); ok {
|
||||
u.batchLAG(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, md := range m.MCLAGDomains {
|
||||
if v, ok := md.(*unifi.MCLAGDomain); ok {
|
||||
u.batchMCLAGDomain(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, ss := range m.SwitchStacks {
|
||||
if v, ok := ss.(*unifi.SwitchStack); ok {
|
||||
u.batchSwitchStack(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, dp := range m.DNSPolicies {
|
||||
if v, ok := dp.(*unifi.DNSPolicy); ok {
|
||||
u.batchDNSPolicy(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, rp := range m.RADIUSProfiles {
|
||||
if v, ok := rp.(*unifi.RADIUSProfile); ok {
|
||||
u.batchRADIUSProfile(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, tml := range m.TrafficMatchingLists {
|
||||
if v, ok := tml.(*unifi.TrafficMatchingList); ok {
|
||||
u.batchTrafficMatchingList(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, hv := range m.HotspotVouchers {
|
||||
if v, ok := hv.(*unifi.HotspotVoucher); ok {
|
||||
u.batchHotspotVoucher(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, da := range m.DPIApplications {
|
||||
if v, ok := da.(*unifi.DPIApplication); ok {
|
||||
u.batchDPIApplication(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, dc := range m.DPICategories {
|
||||
if v, ok := dc.(*unifi.DPICategory); ok {
|
||||
u.batchDPICategory(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, pd := range m.PendingDevices {
|
||||
if v, ok := pd.(*unifi.PendingDevice); ok {
|
||||
u.batchPendingDevice(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, co := range m.Countries {
|
||||
if v, ok := co.(*unifi.Country); ok {
|
||||
u.batchCountry(r, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (u *InfluxUnifi) switchExport(r report, v any) { //nolint:cyclop
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
package influxunifi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/unpoller/unifi/v5"
|
||||
)
|
||||
|
||||
// batchIntegrationDeviceStats generates InfluxDB points for Integration/v1 device statistics.
|
||||
func (u *InfluxUnifi) batchIntegrationDeviceStats(r report, ds *unifi.IntegrationDeviceStats) {
|
||||
if ds == nil {
|
||||
return
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"device_id": ds.DeviceID,
|
||||
}
|
||||
|
||||
fields := map[string]any{
|
||||
"cpu_utilization_pct": ds.CPUUtilizationPct.Val,
|
||||
"memory_utilization_pct": ds.MemoryUtilizationPct.Val,
|
||||
"load_average_1min": ds.LoadAverage1Min.Val,
|
||||
"load_average_5min": ds.LoadAverage5Min.Val,
|
||||
"load_average_15min": ds.LoadAverage15Min.Val,
|
||||
"uptime_sec": ds.UptimeSec.Val,
|
||||
}
|
||||
|
||||
r.send(&metric{Table: "integration_device_stats", Tags: tags, Fields: fields})
|
||||
|
||||
for _, radio := range ds.Radios {
|
||||
radioTags := map[string]string{
|
||||
"device_id": ds.DeviceID,
|
||||
"frequency_ghz": radio.FrequencyGHz.Txt,
|
||||
}
|
||||
|
||||
r.send(&metric{
|
||||
Table: "integration_device_radio",
|
||||
Tags: radioTags,
|
||||
Fields: map[string]any{
|
||||
"tx_retries_pct": radio.TxRetriesPct.Val,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
for i, uplink := range ds.Uplinks {
|
||||
uplinkTags := map[string]string{
|
||||
"device_id": ds.DeviceID,
|
||||
"uplink_index": fmt.Sprint(i),
|
||||
}
|
||||
|
||||
r.send(&metric{
|
||||
Table: "integration_device_uplink",
|
||||
Tags: uplinkTags,
|
||||
Fields: map[string]any{
|
||||
"rx_rate_bps": uplink.RxRateBps.Val,
|
||||
"tx_rate_bps": uplink.TxRateBps.Val,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
package influxunifi
|
||||
|
||||
import (
|
||||
"github.com/unpoller/unifi/v5"
|
||||
)
|
||||
|
||||
// batchDPIApplication generates InfluxDB points for a DPI application catalogue entry.
|
||||
// These are global (no site) reference records; we emit a presence gauge keyed by ID.
|
||||
func (u *InfluxUnifi) batchDPIApplication(r report, app *unifi.DPIApplication) {
|
||||
if app == nil {
|
||||
return
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"app_id": app.ID.Txt,
|
||||
"app_name": app.Name,
|
||||
}
|
||||
|
||||
r.send(&metric{
|
||||
Table: "dpi_application",
|
||||
Tags: tags,
|
||||
Fields: map[string]any{"app_id_val": app.ID.Val},
|
||||
})
|
||||
}
|
||||
|
||||
// batchDPICategory generates InfluxDB points for a DPI category catalogue entry.
|
||||
func (u *InfluxUnifi) batchDPICategory(r report, cat *unifi.DPICategory) {
|
||||
if cat == nil {
|
||||
return
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"cat_id": cat.ID.Txt,
|
||||
"cat_name": cat.Name,
|
||||
}
|
||||
|
||||
r.send(&metric{
|
||||
Table: "dpi_category",
|
||||
Tags: tags,
|
||||
Fields: map[string]any{"cat_id_val": cat.ID.Val},
|
||||
})
|
||||
}
|
||||
|
||||
// batchPendingDevice generates InfluxDB points for a device awaiting adoption.
|
||||
func (u *InfluxUnifi) batchPendingDevice(r report, d *unifi.PendingDevice) {
|
||||
if d == nil {
|
||||
return
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"mac": d.MACAddress,
|
||||
"model": d.Model,
|
||||
"state": d.State,
|
||||
"ip": d.IPAddress,
|
||||
}
|
||||
|
||||
firmwareUpdatable := 0
|
||||
if d.FirmwareUpdatable {
|
||||
firmwareUpdatable = 1
|
||||
}
|
||||
|
||||
supported := 0
|
||||
if d.Supported {
|
||||
supported = 1
|
||||
}
|
||||
|
||||
fields := map[string]any{
|
||||
"firmware_updatable": firmwareUpdatable,
|
||||
"supported": supported,
|
||||
"feature_count": len(d.Features),
|
||||
}
|
||||
|
||||
r.send(&metric{Table: "pending_device", Tags: tags, Fields: fields})
|
||||
}
|
||||
|
||||
// batchCountry generates InfluxDB points for a country entry used in geo-based firewall filters.
|
||||
// Countries are global reference data with no numeric metrics; we emit a presence gauge.
|
||||
func (u *InfluxUnifi) batchCountry(r report, c *unifi.Country) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"code": c.Code,
|
||||
"name": c.Name,
|
||||
}
|
||||
|
||||
r.send(&metric{
|
||||
Table: "country",
|
||||
Tags: tags,
|
||||
Fields: map[string]any{"present": 1},
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,302 @@
|
|||
package influxunifi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/unpoller/unifi/v5"
|
||||
)
|
||||
|
||||
// batchWifiBroadcast generates InfluxDB points for a WiFi SSID broadcast configuration.
|
||||
func (u *InfluxUnifi) batchWifiBroadcast(r report, wb *unifi.WifiBroadcast) {
|
||||
if wb == nil {
|
||||
return
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"site_name": wb.SiteName,
|
||||
"broadcast_id": wb.ID,
|
||||
"broadcast_name": wb.Name,
|
||||
"network": wb.Network,
|
||||
"security_type": wb.SecurityConfiguration.Type,
|
||||
}
|
||||
|
||||
enabled := 0
|
||||
if wb.Enabled {
|
||||
enabled = 1
|
||||
}
|
||||
|
||||
fields := map[string]any{
|
||||
"enabled": enabled,
|
||||
"broadcasting_device_count": len(wb.BroadcastingDeviceFilter),
|
||||
}
|
||||
|
||||
r.send(&metric{Table: "wifi_broadcast", Tags: tags, Fields: fields})
|
||||
}
|
||||
|
||||
// batchFirewallZone generates InfluxDB points for a firewall zone.
|
||||
func (u *InfluxUnifi) batchFirewallZone(r report, fz *unifi.FirewallZone) {
|
||||
if fz == nil {
|
||||
return
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"site_name": fz.SiteName,
|
||||
"zone_id": fz.ID,
|
||||
"zone_name": fz.Name,
|
||||
"origin": fz.Metadata.Origin,
|
||||
}
|
||||
|
||||
fields := map[string]any{
|
||||
"network_count": len(fz.NetworkIDs),
|
||||
}
|
||||
|
||||
r.send(&metric{Table: "firewall_zone", Tags: tags, Fields: fields})
|
||||
}
|
||||
|
||||
// batchACLRule generates InfluxDB points for an ACL rule.
|
||||
func (u *InfluxUnifi) batchACLRule(r report, rule *unifi.ACLRule) {
|
||||
if rule == nil {
|
||||
return
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"site_name": rule.SiteName,
|
||||
"rule_id": rule.ID,
|
||||
"rule_name": rule.Name,
|
||||
"action": rule.Action,
|
||||
"source_filter": rule.SourceFilter,
|
||||
}
|
||||
|
||||
enabled := 0
|
||||
if rule.Enabled {
|
||||
enabled = 1
|
||||
}
|
||||
|
||||
fields := map[string]any{
|
||||
"enabled": enabled,
|
||||
"index": rule.Index.Val,
|
||||
"enforcing_device_count": len(rule.EnforcingDeviceFilter),
|
||||
}
|
||||
|
||||
r.send(&metric{Table: "acl_rule", Tags: tags, Fields: fields})
|
||||
}
|
||||
|
||||
// batchVPNServer generates InfluxDB points for a VPN server configuration.
|
||||
func (u *InfluxUnifi) batchVPNServer(r report, vs *unifi.VPNServer) {
|
||||
if vs == nil {
|
||||
return
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"site_name": vs.SiteName,
|
||||
"server_id": vs.ID,
|
||||
"server_name": vs.Name,
|
||||
"vpn_type": vs.Type,
|
||||
"origin": vs.Metadata.Origin,
|
||||
}
|
||||
|
||||
enabled := 0
|
||||
if vs.Enabled {
|
||||
enabled = 1
|
||||
}
|
||||
|
||||
fields := map[string]any{
|
||||
"enabled": enabled,
|
||||
}
|
||||
|
||||
r.send(&metric{Table: "vpn_server", Tags: tags, Fields: fields})
|
||||
}
|
||||
|
||||
// batchSiteToSiteTunnel generates InfluxDB points for a site-to-site VPN tunnel.
|
||||
func (u *InfluxUnifi) batchSiteToSiteTunnel(r report, t *unifi.SiteToSiteTunnel) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"site_name": t.SiteName,
|
||||
"tunnel_id": t.ID,
|
||||
"tunnel_name": t.Name,
|
||||
"tunnel_type": t.Type,
|
||||
"origin": t.Metadata.Origin,
|
||||
}
|
||||
|
||||
r.send(&metric{
|
||||
Table: "site_to_site_tunnel",
|
||||
Tags: tags,
|
||||
Fields: map[string]any{"present": 1},
|
||||
})
|
||||
}
|
||||
|
||||
// batchLAG generates InfluxDB points for a link aggregation group.
|
||||
func (u *InfluxUnifi) batchLAG(r report, lag *unifi.LAG) {
|
||||
if lag == nil {
|
||||
return
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"site_name": lag.SiteName,
|
||||
"lag_id": lag.ID,
|
||||
"lag_type": lag.Type,
|
||||
"origin": lag.Metadata.Origin,
|
||||
}
|
||||
|
||||
totalPorts := 0
|
||||
|
||||
for _, m := range lag.Members {
|
||||
totalPorts += len(m.PortIndexes)
|
||||
}
|
||||
|
||||
fields := map[string]any{
|
||||
"member_count": len(lag.Members),
|
||||
"port_count": totalPorts,
|
||||
}
|
||||
|
||||
r.send(&metric{Table: "lag", Tags: tags, Fields: fields})
|
||||
}
|
||||
|
||||
// batchMCLAGDomain generates InfluxDB points for a multi-chassis LAG domain.
|
||||
func (u *InfluxUnifi) batchMCLAGDomain(r report, d *unifi.MCLAGDomain) {
|
||||
if d == nil {
|
||||
return
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"site_name": d.SiteName,
|
||||
"domain_id": d.ID,
|
||||
"domain_name": d.Name,
|
||||
"origin": d.Metadata.Origin,
|
||||
}
|
||||
|
||||
fields := map[string]any{
|
||||
"peer_count": len(d.Peers),
|
||||
"lag_count": len(d.LAGs),
|
||||
}
|
||||
|
||||
r.send(&metric{Table: "mclag_domain", Tags: tags, Fields: fields})
|
||||
|
||||
for i, peer := range d.Peers {
|
||||
peerTags := map[string]string{
|
||||
"site_name": d.SiteName,
|
||||
"domain_id": d.ID,
|
||||
"domain_name": d.Name,
|
||||
"peer_index": fmt.Sprint(i),
|
||||
"device_id": peer.DeviceID,
|
||||
"role": peer.Role,
|
||||
}
|
||||
|
||||
r.send(&metric{
|
||||
Table: "mclag_peer",
|
||||
Tags: peerTags,
|
||||
Fields: map[string]any{"link_port_count": len(peer.LinkPorts)},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// batchSwitchStack generates InfluxDB points for a switch stack.
|
||||
func (u *InfluxUnifi) batchSwitchStack(r report, ss *unifi.SwitchStack) {
|
||||
if ss == nil {
|
||||
return
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"site_name": ss.SiteName,
|
||||
"stack_id": ss.ID,
|
||||
"stack_name": ss.Name,
|
||||
"origin": ss.Metadata.Origin,
|
||||
}
|
||||
|
||||
fields := map[string]any{
|
||||
"member_count": len(ss.Members),
|
||||
"lag_count": len(ss.LAGs),
|
||||
}
|
||||
|
||||
r.send(&metric{Table: "switch_stack", Tags: tags, Fields: fields})
|
||||
}
|
||||
|
||||
// batchDNSPolicy generates InfluxDB points for a DNS policy.
|
||||
func (u *InfluxUnifi) batchDNSPolicy(r report, p *unifi.DNSPolicy) {
|
||||
if p == nil {
|
||||
return
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"site_name": p.SiteName,
|
||||
"policy_id": p.ID,
|
||||
"policy_type": p.Type,
|
||||
"domain": p.Domain,
|
||||
}
|
||||
|
||||
enabled := 0
|
||||
if p.Enabled {
|
||||
enabled = 1
|
||||
}
|
||||
|
||||
fields := map[string]any{
|
||||
"enabled": enabled,
|
||||
}
|
||||
|
||||
r.send(&metric{Table: "dns_policy", Tags: tags, Fields: fields})
|
||||
}
|
||||
|
||||
// batchRADIUSProfile generates InfluxDB points for a RADIUS profile.
|
||||
func (u *InfluxUnifi) batchRADIUSProfile(r report, p *unifi.RADIUSProfile) {
|
||||
if p == nil {
|
||||
return
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"site_name": p.SiteName,
|
||||
"profile_id": p.ID,
|
||||
"profile_name": p.Name,
|
||||
"origin": p.Metadata.Origin,
|
||||
}
|
||||
|
||||
r.send(&metric{
|
||||
Table: "radius_profile",
|
||||
Tags: tags,
|
||||
Fields: map[string]any{"present": 1},
|
||||
})
|
||||
}
|
||||
|
||||
// batchTrafficMatchingList generates InfluxDB points for a traffic matching list.
|
||||
func (u *InfluxUnifi) batchTrafficMatchingList(r report, l *unifi.TrafficMatchingList) {
|
||||
if l == nil {
|
||||
return
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"site_name": l.SiteName,
|
||||
"list_id": l.ID,
|
||||
"list_name": l.Name,
|
||||
"list_type": l.Type,
|
||||
}
|
||||
|
||||
r.send(&metric{
|
||||
Table: "traffic_matching_list",
|
||||
Tags: tags,
|
||||
Fields: map[string]any{"present": 1},
|
||||
})
|
||||
}
|
||||
|
||||
// batchHotspotVoucher generates InfluxDB points for a hotspot voucher.
|
||||
func (u *InfluxUnifi) batchHotspotVoucher(r report, v *unifi.HotspotVoucher) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"site_name": v.SiteName,
|
||||
"voucher_id": v.ID,
|
||||
"voucher_name": v.Name,
|
||||
}
|
||||
|
||||
fields := map[string]any{
|
||||
"authorized_guest_count": v.AuthorizedGuestCount.Val,
|
||||
"authorized_guest_limit": v.AuthorizedGuestLimit.Val,
|
||||
"data_usage_limit_mbytes": v.DataUsageLimitMBytes.Val,
|
||||
"time_limit_minutes": v.TimeLimitMinutes.Val,
|
||||
}
|
||||
|
||||
r.send(&metric{Table: "hotspot_voucher", Tags: tags, Fields: fields})
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
package influxunifi
|
||||
|
||||
import (
|
||||
"github.com/unpoller/unifi/v5"
|
||||
)
|
||||
|
||||
// batchPortForward generates InfluxDB points for a port forwarding rule.
|
||||
func (u *InfluxUnifi) batchPortForward(r report, pf *unifi.PortForward) {
|
||||
if pf == nil {
|
||||
return
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"site_name": pf.SiteName,
|
||||
"source": pf.SourceName,
|
||||
"rule_id": pf.ID,
|
||||
"rule_name": pf.Name,
|
||||
"proto": pf.Proto,
|
||||
}
|
||||
|
||||
enabled := 0
|
||||
if pf.Enabled.Val {
|
||||
enabled = 1
|
||||
}
|
||||
|
||||
logged := 0
|
||||
if pf.Log.Val {
|
||||
logged = 1
|
||||
}
|
||||
|
||||
fields := map[string]any{
|
||||
"enabled": enabled,
|
||||
"logged": logged,
|
||||
}
|
||||
|
||||
r.send(&metric{Table: "port_forward", Tags: tags, Fields: fields})
|
||||
}
|
||||
|
||||
// batchSSLCertificate generates InfluxDB points for an SSL certificate.
|
||||
func (u *InfluxUnifi) batchSSLCertificate(r report, cert *unifi.SSLCertificate) {
|
||||
if cert == nil || cert.ID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"site_name": cert.SiteName,
|
||||
"cert_id": cert.ID,
|
||||
"cert_type": cert.CertType,
|
||||
"status": cert.Status,
|
||||
"fingerprint": cert.Fingerprint,
|
||||
}
|
||||
|
||||
isActive := 0
|
||||
if cert.IsActive.Val {
|
||||
isActive = 1
|
||||
}
|
||||
|
||||
isValid := 0
|
||||
if cert.IsValid.Val {
|
||||
isValid = 1
|
||||
}
|
||||
|
||||
fields := map[string]any{
|
||||
"is_active": isActive,
|
||||
"is_valid": isValid,
|
||||
"valid_from": cert.ValidFrom.Val,
|
||||
"valid_to": cert.ValidTo.Val,
|
||||
"chain_len": len(cert.Chain),
|
||||
}
|
||||
|
||||
r.send(&metric{Table: "ssl_certificate", Tags: tags, Fields: fields})
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package influxunifi
|
||||
|
||||
import (
|
||||
"github.com/unpoller/unifi/v5"
|
||||
)
|
||||
|
||||
// batchUPSDevice generates InfluxDB points for a UPS device selector entry.
|
||||
// UPSDeviceSelector is a lightweight inventory record; the only meaningful
|
||||
// time-series value is a presence/count gauge (1 per device per poll cycle).
|
||||
func (u *InfluxUnifi) batchUPSDevice(r report, d *unifi.UPSDeviceSelector) {
|
||||
if d == nil {
|
||||
return
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"site_name": d.SiteName,
|
||||
"source": d.SourceName,
|
||||
"device_id": d.ID,
|
||||
"mac": d.MAC,
|
||||
"label": d.Label,
|
||||
}
|
||||
|
||||
r.send(&metric{
|
||||
Table: "ups_device",
|
||||
Tags: tags,
|
||||
Fields: map[string]any{"present": 1},
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package influxunifi
|
||||
|
||||
import (
|
||||
"github.com/unpoller/unifi/v5"
|
||||
)
|
||||
|
||||
// batchWANStatus generates InfluxDB points for WAN interface state.
|
||||
func (u *InfluxUnifi) batchWANStatus(r report, ws *unifi.WANStatus) {
|
||||
if ws == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, iface := range ws.WANInterfaces {
|
||||
tags := map[string]string{
|
||||
"site_name": ws.SiteName,
|
||||
"wan_interface": iface.Name,
|
||||
"wan_networkgroup": iface.WANNetworkgroup,
|
||||
}
|
||||
|
||||
r.send(&metric{
|
||||
Table: "wan_status",
|
||||
Tags: tags,
|
||||
Fields: map[string]any{"state": iface.State},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -39,9 +39,15 @@ func (u *InputUnifi) collectControllerEvents(c *Controller) ([]any, error) {
|
|||
|
||||
for _, call := range []caller{u.collectIDs, u.collectAnomalies, u.collectAlarms, u.collectEvents, u.collectSyslog, u.collectProtectLogs} {
|
||||
if newLogs, err = call(logs, sites, c); err != nil {
|
||||
if c.Remote && errors.Is(err, unifi.ErrInvalidStatusCode) {
|
||||
// The remote API (api.ui.com) does not support all event endpoints (e.g. /stat/event
|
||||
// returns 404). Log a warning and continue so other collectors still run.
|
||||
if c.Remote && (errors.Is(err, unifi.ErrInvalidStatusCode) || errors.Is(err, unifi.ErrEndpointNotFound)) {
|
||||
// The remote API (api.ui.com) does not support all event endpoints.
|
||||
// ErrInvalidStatusCode is retained for backward compatibility: before
|
||||
// ErrEndpointNotFound was split out, all non-200 remote responses were
|
||||
// soft-skipped via ErrInvalidStatusCode.
|
||||
// Note: most inner callers (collectAlarms, collectAnomalies, collectEvents,
|
||||
// collectIDs, collectProtectLogs) handle ErrEndpointNotFound internally and
|
||||
// return nil, so this branch fires primarily for collectSyslog and for
|
||||
// ErrInvalidStatusCode cases on remote controllers.
|
||||
u.Logf("Failed to collect events from controller %s: %v (endpoint may not be supported by the remote API)", c.URL, err)
|
||||
|
||||
continue
|
||||
|
|
@ -121,6 +127,14 @@ func (u *InputUnifi) collectAlarms(logs []any, sites []*unifi.Site, c *Controlle
|
|||
|
||||
for _, s := range sites {
|
||||
events, err := c.Unifi.GetAlarmsSite(s)
|
||||
if errors.Is(err, unifi.ErrEndpointNotFound) {
|
||||
// /stat/alarm is removed controller-wide in Network 10.x+; once the first
|
||||
// site returns 404 all remaining sites on the same controller will too.
|
||||
u.LogDebugf("[%s] Alarms endpoint not available (Network 10.x+): %v", c.URL, err)
|
||||
|
||||
return logs, nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return logs, fmt.Errorf("unifi.GetAlarms(): %w", err)
|
||||
}
|
||||
|
|
@ -150,17 +164,21 @@ func (u *InputUnifi) collectAnomalies(logs []any, sites []*unifi.Site, c *Contro
|
|||
|
||||
for _, s := range sites {
|
||||
events, err := c.Unifi.GetAnomaliesSite(s)
|
||||
if errors.Is(err, unifi.ErrEndpointNotFound) {
|
||||
// /stat/anomaly is removed controller-wide in Network 10.x+; once the first
|
||||
// site returns 404 all remaining sites on the same controller will too.
|
||||
u.LogDebugf("[%s] Anomalies endpoint not available (Network 10.x+): %v", c.URL, err)
|
||||
|
||||
return logs, nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return logs, fmt.Errorf("unifi.GetAnomalies(): %w", err)
|
||||
}
|
||||
|
||||
for _, e := range events {
|
||||
// Apply site name override for anomalies if configured
|
||||
if c.DefaultSiteNameOverride != "" {
|
||||
lower := strings.ToLower(e.SiteName)
|
||||
if lower == "default" || strings.Contains(lower, "default") {
|
||||
e.SiteName = c.DefaultSiteNameOverride
|
||||
}
|
||||
if c.DefaultSiteNameOverride != "" && isDefaultSiteName(e.SiteName) {
|
||||
e.SiteName = c.DefaultSiteNameOverride
|
||||
}
|
||||
|
||||
logs = append(logs, e)
|
||||
|
|
@ -183,6 +201,15 @@ func (u *InputUnifi) collectEvents(logs []any, sites []*unifi.Site, c *Controlle
|
|||
|
||||
for _, s := range sites {
|
||||
events, err := c.Unifi.GetSiteEvents(s, time.Hour)
|
||||
if errors.Is(err, unifi.ErrEndpointNotFound) {
|
||||
// stat/event was removed in Network 10.x. The path is per-site but the
|
||||
// removal is controller-wide: if the first site returns 404, every site
|
||||
// on the same controller will too. No replacement exists in integration/v1.
|
||||
u.Logf("[%s] Events endpoint removed (Network 10.x+): use save_syslog instead", c.URL)
|
||||
|
||||
return logs, nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return logs, fmt.Errorf("unifi.GetEvents(): %w", err)
|
||||
}
|
||||
|
|
@ -242,6 +269,12 @@ func (u *InputUnifi) collectProtectLogs(logs []any, _ []*unifi.Site, c *Controll
|
|||
|
||||
entries, err := c.Unifi.GetProtectLogs(req)
|
||||
if err != nil {
|
||||
if errors.Is(err, unifi.ErrEndpointNotFound) {
|
||||
u.Logf("[%s] Protect logs endpoint not available (404) — ensure UniFi Protect is installed, or disable save_protect_logs", c.URL)
|
||||
|
||||
return logs, nil
|
||||
}
|
||||
|
||||
return logs, fmt.Errorf("unifi.GetProtectLogs(): %w", err)
|
||||
}
|
||||
|
||||
|
|
@ -257,10 +290,11 @@ func (u *InputUnifi) collectProtectLogs(logs []any, _ []*unifi.Site, c *Controll
|
|||
thumbID = thumbID[2:]
|
||||
}
|
||||
|
||||
if thumbData, err := c.Unifi.GetProtectEventThumbnail(thumbID); err == nil {
|
||||
e.ThumbnailBase64 = base64.StdEncoding.EncodeToString(thumbData)
|
||||
thumbData, thumbErr := c.Unifi.GetProtectEventThumbnail(thumbID)
|
||||
if thumbErr != nil {
|
||||
u.LogDebugf("Failed to fetch thumbnail for event %s (thumb: %s): %v", e.ID, thumbID, thumbErr)
|
||||
} else {
|
||||
u.LogDebugf("Failed to fetch thumbnail for event %s (thumb: %s): %v", e.ID, thumbID, err)
|
||||
e.ThumbnailBase64 = base64.StdEncoding.EncodeToString(thumbData)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -289,6 +323,15 @@ func (u *InputUnifi) collectIDs(logs []any, sites []*unifi.Site, c *Controller)
|
|||
|
||||
for _, s := range sites {
|
||||
events, err := c.Unifi.GetIDSSite(s)
|
||||
if errors.Is(err, unifi.ErrEndpointNotFound) {
|
||||
// stat/ips/event was removed in Network 10.x. The path is per-site but
|
||||
// the removal is controller-wide: if the first site returns 404, every
|
||||
// site on the same controller will too. No replacement exists.
|
||||
u.Logf("[%s] IDS/IPS endpoint removed (Network 10.x+): no replacement available", c.URL)
|
||||
|
||||
return logs, nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return logs, fmt.Errorf("unifi.GetIDS(): %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package inputunifi
|
|||
// nolint: gosec
|
||||
import (
|
||||
"crypto/md5"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
|
@ -78,7 +79,7 @@ func (u *InputUnifi) collectController(c *Controller) (*poller.Metrics, error) {
|
|||
|
||||
metrics, err := u.pollController(c)
|
||||
if err != nil {
|
||||
u.Logf("Re-authenticating to UniFi Controller: %s", c.URL)
|
||||
u.Logf("Re-authenticating to UniFi Controller %s (poll error: %v)", c.URL, err)
|
||||
|
||||
if authErr := u.getUnifi(c); authErr != nil {
|
||||
return metrics, fmt.Errorf("re-authenticating to %s: %w", c.URL, authErr)
|
||||
|
|
@ -262,6 +263,14 @@ func (u *InputUnifi) pollController(c *Controller) (*poller.Metrics, error) {
|
|||
u.LogDebugf("Found %d VPNMeshes entries", len(m.VPNMeshes))
|
||||
}
|
||||
|
||||
// Legacy API additions (v5.26.0) — available on most firmware, no API key required.
|
||||
u.collectLegacyPerSite(c, sites, m)
|
||||
|
||||
// Integration/v1 API additions (v5.26.0) — require API key and Network 9.3.43+.
|
||||
if c.APIKey != "" {
|
||||
u.collectIntegrationV1(c, sites, m)
|
||||
}
|
||||
|
||||
// Update web UI only on success; call explicitly so we never run with nil c/c.Unifi (no defer).
|
||||
// Recover so a panic in updateWeb (e.g. old image, race) never kills the poller.
|
||||
if c != nil && c.Unifi != nil {
|
||||
|
|
@ -458,6 +467,182 @@ func (u *InputUnifi) convertToSiteDPI(clientUsageByApp []*unifi.ClientUsageByApp
|
|||
}
|
||||
}
|
||||
|
||||
// collectLegacyPerSite collects v5.26.0 additions that use the legacy API (no API key needed).
|
||||
// Failures are non-fatal: older firmware may not expose these endpoints.
|
||||
func (u *InputUnifi) collectLegacyPerSite(c *Controller, sites []*unifi.Site, m *Metrics) {
|
||||
for _, site := range sites {
|
||||
if wan, err := c.Unifi.GetWANStatus(site); err != nil {
|
||||
u.LogDebugf("unifi.GetWANStatus(%s, %s): %v (continuing)", c.URL, site.Name, err)
|
||||
} else {
|
||||
m.WANStatuses = append(m.WANStatuses, wan)
|
||||
}
|
||||
|
||||
if forwards, err := c.Unifi.GetPortForwards(site); err != nil {
|
||||
u.LogDebugf("unifi.GetPortForwards(%s, %s): %v (continuing)", c.URL, site.Name, err)
|
||||
} else {
|
||||
m.PortForwards = append(m.PortForwards, forwards...)
|
||||
}
|
||||
|
||||
if cert, err := c.Unifi.GetSSLCertificate(site); err != nil {
|
||||
u.LogDebugf("unifi.GetSSLCertificate(%s, %s): %v (continuing)", c.URL, site.Name, err)
|
||||
} else if cert.ID != "" {
|
||||
m.SSLCertificates = append(m.SSLCertificates, cert)
|
||||
}
|
||||
|
||||
if upsList, err := c.Unifi.GetUPSDeviceList(site); err != nil {
|
||||
u.LogDebugf("unifi.GetUPSDeviceList(%s, %s): %v (continuing)", c.URL, site.Name, err)
|
||||
} else {
|
||||
m.UPSDevices = append(m.UPSDevices, upsList...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// collectIntegrationV1 collects all Integration/v1 endpoints (Network 9.3.43+, API key required).
|
||||
// Only called when c.APIKey != "", so ErrAPIKeyRequired will not be returned.
|
||||
// ErrEndpointNotFound is expected on firmware older than Network 9.3.43.
|
||||
//
|
||||
//nolint:cyclop,funlen
|
||||
func (u *InputUnifi) collectIntegrationV1(c *Controller, sites []*unifi.Site, m *Metrics) {
|
||||
// Fetch integration sites — required for all per-site Integration/v1 calls.
|
||||
integrationSites, err := c.Unifi.GetIntegrationSites()
|
||||
if err != nil {
|
||||
if errors.Is(err, unifi.ErrEndpointNotFound) {
|
||||
// Integration/v1 requires Network 9.3.43+. Controllers below that return 404.
|
||||
u.LogDebugf("unifi.GetIntegrationSites(%s): Integration/v1 not available (Network 9.3.43+ required)", c.URL)
|
||||
} else {
|
||||
// Unexpected failure (auth expiry, network error, 500) while an API key is configured.
|
||||
// All per-site Integration/v1 data will be absent until this resolves.
|
||||
u.Logf("unifi.GetIntegrationSites(%s): %v (skipping Integration/v1 per-site collection)", c.URL, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
u.LogDebugf("Found %d IntegrationSites", len(integrationSites))
|
||||
|
||||
// Build a map from legacy site name → IntegrationSite to match user-configured sites.
|
||||
// IntegrationSite.InternalReference is the same short name used in the legacy API (e.g. "default").
|
||||
intSiteByName := make(map[string]*unifi.IntegrationSite, len(integrationSites))
|
||||
for _, is := range integrationSites {
|
||||
intSiteByName[is.InternalReference] = is
|
||||
}
|
||||
|
||||
// Per-site Integration/v1 collections — only for user-configured sites.
|
||||
for _, site := range sites {
|
||||
is, ok := intSiteByName[site.Name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if devStats, err := c.Unifi.GetAllIntegrationDeviceStats(is); err != nil {
|
||||
u.LogDebugf("unifi.GetAllIntegrationDeviceStats(%s, %s): %v (continuing)", c.URL, is.Name, err)
|
||||
} else {
|
||||
m.IntegrationDevStats = append(m.IntegrationDevStats, devStats...)
|
||||
}
|
||||
|
||||
if broadcasts, err := c.Unifi.GetWifiBroadcasts(is); err != nil {
|
||||
u.LogDebugf("unifi.GetWifiBroadcasts(%s, %s): %v (continuing)", c.URL, is.Name, err)
|
||||
} else {
|
||||
m.WifiBroadcasts = append(m.WifiBroadcasts, broadcasts...)
|
||||
}
|
||||
|
||||
if zones, err := c.Unifi.GetFirewallZones(is); err != nil {
|
||||
u.LogDebugf("unifi.GetFirewallZones(%s, %s): %v (continuing)", c.URL, is.Name, err)
|
||||
} else {
|
||||
m.FirewallZones = append(m.FirewallZones, zones...)
|
||||
}
|
||||
|
||||
if rules, err := c.Unifi.GetACLRules(is); err != nil {
|
||||
u.LogDebugf("unifi.GetACLRules(%s, %s): %v (continuing)", c.URL, is.Name, err)
|
||||
} else {
|
||||
m.ACLRules = append(m.ACLRules, rules...)
|
||||
}
|
||||
|
||||
if servers, err := c.Unifi.GetVPNServers(is); err != nil {
|
||||
u.LogDebugf("unifi.GetVPNServers(%s, %s): %v (continuing)", c.URL, is.Name, err)
|
||||
} else {
|
||||
m.VPNServers = append(m.VPNServers, servers...)
|
||||
}
|
||||
|
||||
if tunnels, err := c.Unifi.GetSiteToSiteTunnels(is); err != nil {
|
||||
u.LogDebugf("unifi.GetSiteToSiteTunnels(%s, %s): %v (continuing)", c.URL, is.Name, err)
|
||||
} else {
|
||||
m.SiteToSiteTunnels = append(m.SiteToSiteTunnels, tunnels...)
|
||||
}
|
||||
|
||||
if lags, err := c.Unifi.GetLAGs(is); err != nil {
|
||||
u.LogDebugf("unifi.GetLAGs(%s, %s): %v (continuing)", c.URL, is.Name, err)
|
||||
} else {
|
||||
m.LAGs = append(m.LAGs, lags...)
|
||||
}
|
||||
|
||||
if mclags, err := c.Unifi.GetMCLAGDomains(is); err != nil {
|
||||
u.LogDebugf("unifi.GetMCLAGDomains(%s, %s): %v (continuing)", c.URL, is.Name, err)
|
||||
} else {
|
||||
m.MCLAGDomains = append(m.MCLAGDomains, mclags...)
|
||||
}
|
||||
|
||||
if stacks, err := c.Unifi.GetSwitchStacks(is); err != nil {
|
||||
u.LogDebugf("unifi.GetSwitchStacks(%s, %s): %v (continuing)", c.URL, is.Name, err)
|
||||
} else {
|
||||
m.SwitchStacks = append(m.SwitchStacks, stacks...)
|
||||
}
|
||||
|
||||
if policies, err := c.Unifi.GetDNSPolicies(is); err != nil {
|
||||
u.LogDebugf("unifi.GetDNSPolicies(%s, %s): %v (continuing)", c.URL, is.Name, err)
|
||||
} else {
|
||||
m.DNSPolicies = append(m.DNSPolicies, policies...)
|
||||
}
|
||||
|
||||
if profiles, err := c.Unifi.GetRADIUSProfiles(is); err != nil {
|
||||
u.LogDebugf("unifi.GetRADIUSProfiles(%s, %s): %v (continuing)", c.URL, is.Name, err)
|
||||
} else {
|
||||
m.RADIUSProfiles = append(m.RADIUSProfiles, profiles...)
|
||||
}
|
||||
|
||||
if lists, err := c.Unifi.GetTrafficMatchingLists(is); err != nil {
|
||||
u.LogDebugf("unifi.GetTrafficMatchingLists(%s, %s): %v (continuing)", c.URL, is.Name, err)
|
||||
} else {
|
||||
m.TrafficMatchingLists = append(m.TrafficMatchingLists, lists...)
|
||||
}
|
||||
|
||||
if vouchers, err := c.Unifi.GetHotspotVouchers(is); err != nil {
|
||||
u.LogDebugf("unifi.GetHotspotVouchers(%s, %s): %v (continuing)", c.URL, is.Name, err)
|
||||
} else {
|
||||
m.HotspotVouchers = append(m.HotspotVouchers, vouchers...)
|
||||
}
|
||||
}
|
||||
|
||||
// Global Integration/v1 collections (not per-site).
|
||||
if apps, err := c.Unifi.GetDPIApplications(); err != nil {
|
||||
u.LogDebugf("unifi.GetDPIApplications(%s): %v (continuing)", c.URL, err)
|
||||
} else {
|
||||
m.DPIApplications = append(m.DPIApplications, apps...)
|
||||
u.LogDebugf("Found %d DPIApplications", len(apps))
|
||||
}
|
||||
|
||||
if cats, err := c.Unifi.GetDPICategories(); err != nil {
|
||||
u.LogDebugf("unifi.GetDPICategories(%s): %v (continuing)", c.URL, err)
|
||||
} else {
|
||||
m.DPICategories = append(m.DPICategories, cats...)
|
||||
u.LogDebugf("Found %d DPICategories", len(cats))
|
||||
}
|
||||
|
||||
if pending, err := c.Unifi.GetPendingDevices(); err != nil {
|
||||
u.LogDebugf("unifi.GetPendingDevices(%s): %v (continuing)", c.URL, err)
|
||||
} else {
|
||||
m.PendingDevices = append(m.PendingDevices, pending...)
|
||||
u.LogDebugf("Found %d PendingDevices", len(pending))
|
||||
}
|
||||
|
||||
if countries, err := c.Unifi.GetCountries(); err != nil {
|
||||
u.LogDebugf("unifi.GetCountries(%s): %v (continuing)", c.URL, err)
|
||||
} else {
|
||||
m.Countries = append(m.Countries, countries...)
|
||||
u.LogDebugf("Found %d Countries", len(countries))
|
||||
}
|
||||
}
|
||||
|
||||
// augmentMetrics is our middleware layer between collecting metrics and writing them.
|
||||
// This is where we can manipuate the returned data or make arbitrary decisions.
|
||||
// This method currently adds parent device names to client metrics and hashes PII.
|
||||
|
|
@ -612,6 +797,156 @@ func (u *InputUnifi) augmentMetrics(c *Controller, metrics *Metrics) *poller.Met
|
|||
m.VPNMeshes = append(m.VPNMeshes, mesh)
|
||||
}
|
||||
|
||||
// v5.26.0 additions — pass through with site name override applied.
|
||||
for _, ws := range metrics.WANStatuses {
|
||||
if c.DefaultSiteNameOverride != "" && isDefaultSiteName(ws.SiteName) {
|
||||
ws.SiteName = c.DefaultSiteNameOverride
|
||||
}
|
||||
|
||||
m.WANStatuses = append(m.WANStatuses, ws)
|
||||
}
|
||||
|
||||
for _, pf := range metrics.PortForwards {
|
||||
if c.DefaultSiteNameOverride != "" && isDefaultSiteName(pf.SiteName) {
|
||||
pf.SiteName = c.DefaultSiteNameOverride
|
||||
}
|
||||
|
||||
m.PortForwards = append(m.PortForwards, pf)
|
||||
}
|
||||
|
||||
for _, cert := range metrics.SSLCertificates {
|
||||
if c.DefaultSiteNameOverride != "" && isDefaultSiteName(cert.SiteName) {
|
||||
cert.SiteName = c.DefaultSiteNameOverride
|
||||
}
|
||||
|
||||
m.SSLCertificates = append(m.SSLCertificates, cert)
|
||||
}
|
||||
|
||||
for _, ups := range metrics.UPSDevices {
|
||||
if c.DefaultSiteNameOverride != "" && isDefaultSiteName(ups.SiteName) {
|
||||
ups.SiteName = c.DefaultSiteNameOverride
|
||||
}
|
||||
|
||||
m.UPSDevices = append(m.UPSDevices, ups)
|
||||
}
|
||||
|
||||
for _, ds := range metrics.IntegrationDevStats {
|
||||
m.IntegrationDevStats = append(m.IntegrationDevStats, ds)
|
||||
}
|
||||
|
||||
for _, wb := range metrics.WifiBroadcasts {
|
||||
if c.DefaultSiteNameOverride != "" && isDefaultSiteName(wb.SiteName) {
|
||||
wb.SiteName = c.DefaultSiteNameOverride
|
||||
}
|
||||
|
||||
m.WifiBroadcasts = append(m.WifiBroadcasts, wb)
|
||||
}
|
||||
|
||||
for _, fz := range metrics.FirewallZones {
|
||||
if c.DefaultSiteNameOverride != "" && isDefaultSiteName(fz.SiteName) {
|
||||
fz.SiteName = c.DefaultSiteNameOverride
|
||||
}
|
||||
|
||||
m.FirewallZones = append(m.FirewallZones, fz)
|
||||
}
|
||||
|
||||
for _, rule := range metrics.ACLRules {
|
||||
if c.DefaultSiteNameOverride != "" && isDefaultSiteName(rule.SiteName) {
|
||||
rule.SiteName = c.DefaultSiteNameOverride
|
||||
}
|
||||
|
||||
m.ACLRules = append(m.ACLRules, rule)
|
||||
}
|
||||
|
||||
for _, vs := range metrics.VPNServers {
|
||||
if c.DefaultSiteNameOverride != "" && isDefaultSiteName(vs.SiteName) {
|
||||
vs.SiteName = c.DefaultSiteNameOverride
|
||||
}
|
||||
|
||||
m.VPNServers = append(m.VPNServers, vs)
|
||||
}
|
||||
|
||||
for _, t := range metrics.SiteToSiteTunnels {
|
||||
if c.DefaultSiteNameOverride != "" && isDefaultSiteName(t.SiteName) {
|
||||
t.SiteName = c.DefaultSiteNameOverride
|
||||
}
|
||||
|
||||
m.SiteToSiteTunnels = append(m.SiteToSiteTunnels, t)
|
||||
}
|
||||
|
||||
for _, lag := range metrics.LAGs {
|
||||
if c.DefaultSiteNameOverride != "" && isDefaultSiteName(lag.SiteName) {
|
||||
lag.SiteName = c.DefaultSiteNameOverride
|
||||
}
|
||||
|
||||
m.LAGs = append(m.LAGs, lag)
|
||||
}
|
||||
|
||||
for _, mc := range metrics.MCLAGDomains {
|
||||
if c.DefaultSiteNameOverride != "" && isDefaultSiteName(mc.SiteName) {
|
||||
mc.SiteName = c.DefaultSiteNameOverride
|
||||
}
|
||||
|
||||
m.MCLAGDomains = append(m.MCLAGDomains, mc)
|
||||
}
|
||||
|
||||
for _, ss := range metrics.SwitchStacks {
|
||||
if c.DefaultSiteNameOverride != "" && isDefaultSiteName(ss.SiteName) {
|
||||
ss.SiteName = c.DefaultSiteNameOverride
|
||||
}
|
||||
|
||||
m.SwitchStacks = append(m.SwitchStacks, ss)
|
||||
}
|
||||
|
||||
for _, dp := range metrics.DNSPolicies {
|
||||
if c.DefaultSiteNameOverride != "" && isDefaultSiteName(dp.SiteName) {
|
||||
dp.SiteName = c.DefaultSiteNameOverride
|
||||
}
|
||||
|
||||
m.DNSPolicies = append(m.DNSPolicies, dp)
|
||||
}
|
||||
|
||||
for _, rp := range metrics.RADIUSProfiles {
|
||||
if c.DefaultSiteNameOverride != "" && isDefaultSiteName(rp.SiteName) {
|
||||
rp.SiteName = c.DefaultSiteNameOverride
|
||||
}
|
||||
|
||||
m.RADIUSProfiles = append(m.RADIUSProfiles, rp)
|
||||
}
|
||||
|
||||
for _, tl := range metrics.TrafficMatchingLists {
|
||||
if c.DefaultSiteNameOverride != "" && isDefaultSiteName(tl.SiteName) {
|
||||
tl.SiteName = c.DefaultSiteNameOverride
|
||||
}
|
||||
|
||||
m.TrafficMatchingLists = append(m.TrafficMatchingLists, tl)
|
||||
}
|
||||
|
||||
for _, hv := range metrics.HotspotVouchers {
|
||||
if c.DefaultSiteNameOverride != "" && isDefaultSiteName(hv.SiteName) {
|
||||
hv.SiteName = c.DefaultSiteNameOverride
|
||||
}
|
||||
|
||||
m.HotspotVouchers = append(m.HotspotVouchers, hv)
|
||||
}
|
||||
|
||||
// Global types — no site name to override.
|
||||
for _, app := range metrics.DPIApplications {
|
||||
m.DPIApplications = append(m.DPIApplications, app)
|
||||
}
|
||||
|
||||
for _, cat := range metrics.DPICategories {
|
||||
m.DPICategories = append(m.DPICategories, cat)
|
||||
}
|
||||
|
||||
for _, pd := range metrics.PendingDevices {
|
||||
m.PendingDevices = append(m.PendingDevices, pd)
|
||||
}
|
||||
|
||||
for _, co := range metrics.Countries {
|
||||
m.Countries = append(m.Countries, co)
|
||||
}
|
||||
|
||||
// Apply default_site_name_override to all metrics if configured.
|
||||
// This must be done AFTER all metrics are added to m, so everything is included.
|
||||
// This allows us to use the console name for Cloud Gateways while keeping
|
||||
|
|
@ -772,6 +1107,103 @@ func applySiteNameOverride(m *poller.Metrics, overrideName string) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// v5.26.0 additions.
|
||||
for i := range m.WANStatuses {
|
||||
if ws, ok := m.WANStatuses[i].(*unifi.WANStatus); ok && isDefaultSiteName(ws.SiteName) {
|
||||
ws.SiteName = overrideName
|
||||
}
|
||||
}
|
||||
|
||||
for i := range m.PortForwards {
|
||||
if pf, ok := m.PortForwards[i].(*unifi.PortForward); ok && isDefaultSiteName(pf.SiteName) {
|
||||
pf.SiteName = overrideName
|
||||
}
|
||||
}
|
||||
|
||||
for i := range m.SSLCertificates {
|
||||
if cert, ok := m.SSLCertificates[i].(*unifi.SSLCertificate); ok && isDefaultSiteName(cert.SiteName) {
|
||||
cert.SiteName = overrideName
|
||||
}
|
||||
}
|
||||
|
||||
for i := range m.UPSDevices {
|
||||
if ups, ok := m.UPSDevices[i].(*unifi.UPSDeviceSelector); ok && isDefaultSiteName(ups.SiteName) {
|
||||
ups.SiteName = overrideName
|
||||
}
|
||||
}
|
||||
|
||||
for i := range m.WifiBroadcasts {
|
||||
if wb, ok := m.WifiBroadcasts[i].(*unifi.WifiBroadcast); ok && isDefaultSiteName(wb.SiteName) {
|
||||
wb.SiteName = overrideName
|
||||
}
|
||||
}
|
||||
|
||||
for i := range m.FirewallZones {
|
||||
if fz, ok := m.FirewallZones[i].(*unifi.FirewallZone); ok && isDefaultSiteName(fz.SiteName) {
|
||||
fz.SiteName = overrideName
|
||||
}
|
||||
}
|
||||
|
||||
for i := range m.ACLRules {
|
||||
if r, ok := m.ACLRules[i].(*unifi.ACLRule); ok && isDefaultSiteName(r.SiteName) {
|
||||
r.SiteName = overrideName
|
||||
}
|
||||
}
|
||||
|
||||
for i := range m.VPNServers {
|
||||
if vs, ok := m.VPNServers[i].(*unifi.VPNServer); ok && isDefaultSiteName(vs.SiteName) {
|
||||
vs.SiteName = overrideName
|
||||
}
|
||||
}
|
||||
|
||||
for i := range m.SiteToSiteTunnels {
|
||||
if t, ok := m.SiteToSiteTunnels[i].(*unifi.SiteToSiteTunnel); ok && isDefaultSiteName(t.SiteName) {
|
||||
t.SiteName = overrideName
|
||||
}
|
||||
}
|
||||
|
||||
for i := range m.LAGs {
|
||||
if lag, ok := m.LAGs[i].(*unifi.LAG); ok && isDefaultSiteName(lag.SiteName) {
|
||||
lag.SiteName = overrideName
|
||||
}
|
||||
}
|
||||
|
||||
for i := range m.MCLAGDomains {
|
||||
if mc, ok := m.MCLAGDomains[i].(*unifi.MCLAGDomain); ok && isDefaultSiteName(mc.SiteName) {
|
||||
mc.SiteName = overrideName
|
||||
}
|
||||
}
|
||||
|
||||
for i := range m.SwitchStacks {
|
||||
if ss, ok := m.SwitchStacks[i].(*unifi.SwitchStack); ok && isDefaultSiteName(ss.SiteName) {
|
||||
ss.SiteName = overrideName
|
||||
}
|
||||
}
|
||||
|
||||
for i := range m.DNSPolicies {
|
||||
if dp, ok := m.DNSPolicies[i].(*unifi.DNSPolicy); ok && isDefaultSiteName(dp.SiteName) {
|
||||
dp.SiteName = overrideName
|
||||
}
|
||||
}
|
||||
|
||||
for i := range m.RADIUSProfiles {
|
||||
if rp, ok := m.RADIUSProfiles[i].(*unifi.RADIUSProfile); ok && isDefaultSiteName(rp.SiteName) {
|
||||
rp.SiteName = overrideName
|
||||
}
|
||||
}
|
||||
|
||||
for i := range m.TrafficMatchingLists {
|
||||
if tl, ok := m.TrafficMatchingLists[i].(*unifi.TrafficMatchingList); ok && isDefaultSiteName(tl.SiteName) {
|
||||
tl.SiteName = overrideName
|
||||
}
|
||||
}
|
||||
|
||||
for i := range m.HotspotVouchers {
|
||||
if hv, ok := m.HotspotVouchers[i].(*unifi.HotspotVoucher); ok && isDefaultSiteName(hv.SiteName) {
|
||||
hv.SiteName = overrideName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this is a helper function for augmentMetrics.
|
||||
|
|
|
|||
|
|
@ -94,6 +94,28 @@ type Metrics struct {
|
|||
Topologies []*unifi.Topology
|
||||
PortAnomalies []*unifi.PortAnomaly
|
||||
VPNMeshes []*unifi.MagicSiteToSiteVPN
|
||||
// Added in v5.26.0 integration:
|
||||
WANStatuses []*unifi.WANStatus
|
||||
PortForwards []*unifi.PortForward
|
||||
SSLCertificates []*unifi.SSLCertificate
|
||||
UPSDevices []*unifi.UPSDeviceSelector
|
||||
IntegrationDevStats []*unifi.IntegrationDeviceStats
|
||||
WifiBroadcasts []*unifi.WifiBroadcast
|
||||
FirewallZones []*unifi.FirewallZone
|
||||
ACLRules []*unifi.ACLRule
|
||||
VPNServers []*unifi.VPNServer
|
||||
SiteToSiteTunnels []*unifi.SiteToSiteTunnel
|
||||
LAGs []*unifi.LAG
|
||||
MCLAGDomains []*unifi.MCLAGDomain
|
||||
SwitchStacks []*unifi.SwitchStack
|
||||
DNSPolicies []*unifi.DNSPolicy
|
||||
RADIUSProfiles []*unifi.RADIUSProfile
|
||||
TrafficMatchingLists []*unifi.TrafficMatchingList
|
||||
HotspotVouchers []*unifi.HotspotVoucher
|
||||
DPIApplications []*unifi.DPIApplication
|
||||
DPICategories []*unifi.DPICategory
|
||||
PendingDevices []*unifi.PendingDevice
|
||||
Countries []*unifi.Country
|
||||
}
|
||||
|
||||
func init() { // nolint: gochecknoinits
|
||||
|
|
|
|||
|
|
@ -107,6 +107,28 @@ type Metrics struct {
|
|||
PortAnomalies []any
|
||||
VPNMeshes []any
|
||||
ControllerStatuses []ControllerStatus
|
||||
// Added in v5.26.0 integration:
|
||||
WANStatuses []any // *unifi.WANStatus — per-site WAN failover state
|
||||
PortForwards []any // *unifi.PortForward — port forwarding rules
|
||||
SSLCertificates []any // *unifi.SSLCertificate — controller SSL cert info
|
||||
UPSDevices []any // *unifi.UPSDeviceSelector — UPS selector list
|
||||
IntegrationDevStats []any // *unifi.IntegrationDeviceStats — CPU/mem/radio/uplink per device
|
||||
WifiBroadcasts []any // *unifi.WifiBroadcast — WiFi SSID broadcasts
|
||||
FirewallZones []any // *unifi.FirewallZone — firewall zones
|
||||
ACLRules []any // *unifi.ACLRule — access control rules
|
||||
VPNServers []any // *unifi.VPNServer — VPN server inventory
|
||||
SiteToSiteTunnels []any // *unifi.SiteToSiteTunnel — site-to-site VPN tunnels
|
||||
LAGs []any // *unifi.LAG — link aggregation groups
|
||||
MCLAGDomains []any // *unifi.MCLAGDomain — multi-chassis LAG domains
|
||||
SwitchStacks []any // *unifi.SwitchStack — switch stacks
|
||||
DNSPolicies []any // *unifi.DNSPolicy — DNS policies
|
||||
RADIUSProfiles []any // *unifi.RADIUSProfile — RADIUS profiles
|
||||
TrafficMatchingLists []any // *unifi.TrafficMatchingList — traffic matching lists
|
||||
HotspotVouchers []any // *unifi.HotspotVoucher — hotspot vouchers
|
||||
DPIApplications []any // *unifi.DPIApplication — DPI app catalogue (global)
|
||||
DPICategories []any // *unifi.DPICategory — DPI category catalogue (global)
|
||||
PendingDevices []any // *unifi.PendingDevice — devices awaiting adoption (global)
|
||||
Countries []any // *unifi.Country — country list for geo-filters (global)
|
||||
}
|
||||
|
||||
// Events defines the type for log entries.
|
||||
|
|
|
|||
|
|
@ -282,6 +282,27 @@ func AppendMetrics(existing *Metrics, m *Metrics) *Metrics {
|
|||
existing.PortAnomalies = append(existing.PortAnomalies, m.PortAnomalies...)
|
||||
existing.VPNMeshes = append(existing.VPNMeshes, m.VPNMeshes...)
|
||||
existing.ControllerStatuses = append(existing.ControllerStatuses, m.ControllerStatuses...)
|
||||
existing.WANStatuses = append(existing.WANStatuses, m.WANStatuses...)
|
||||
existing.PortForwards = append(existing.PortForwards, m.PortForwards...)
|
||||
existing.SSLCertificates = append(existing.SSLCertificates, m.SSLCertificates...)
|
||||
existing.UPSDevices = append(existing.UPSDevices, m.UPSDevices...)
|
||||
existing.IntegrationDevStats = append(existing.IntegrationDevStats, m.IntegrationDevStats...)
|
||||
existing.WifiBroadcasts = append(existing.WifiBroadcasts, m.WifiBroadcasts...)
|
||||
existing.FirewallZones = append(existing.FirewallZones, m.FirewallZones...)
|
||||
existing.ACLRules = append(existing.ACLRules, m.ACLRules...)
|
||||
existing.VPNServers = append(existing.VPNServers, m.VPNServers...)
|
||||
existing.SiteToSiteTunnels = append(existing.SiteToSiteTunnels, m.SiteToSiteTunnels...)
|
||||
existing.LAGs = append(existing.LAGs, m.LAGs...)
|
||||
existing.MCLAGDomains = append(existing.MCLAGDomains, m.MCLAGDomains...)
|
||||
existing.SwitchStacks = append(existing.SwitchStacks, m.SwitchStacks...)
|
||||
existing.DNSPolicies = append(existing.DNSPolicies, m.DNSPolicies...)
|
||||
existing.RADIUSProfiles = append(existing.RADIUSProfiles, m.RADIUSProfiles...)
|
||||
existing.TrafficMatchingLists = append(existing.TrafficMatchingLists, m.TrafficMatchingLists...)
|
||||
existing.HotspotVouchers = append(existing.HotspotVouchers, m.HotspotVouchers...)
|
||||
existing.DPIApplications = append(existing.DPIApplications, m.DPIApplications...)
|
||||
existing.DPICategories = append(existing.DPICategories, m.DPICategories...)
|
||||
existing.PendingDevices = append(existing.PendingDevices, m.PendingDevices...)
|
||||
existing.Countries = append(existing.Countries, m.Countries...)
|
||||
|
||||
return existing
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,24 +36,45 @@ const (
|
|||
var ErrMetricFetchFailed = fmt.Errorf("metric fetch failed")
|
||||
|
||||
type promUnifi struct {
|
||||
*Config `json:"prometheus" toml:"prometheus" xml:"prometheus" yaml:"prometheus"`
|
||||
Client *uclient
|
||||
Device *unifiDevice
|
||||
UAP *uap
|
||||
USG *usg
|
||||
USW *usw
|
||||
PDU *pdu
|
||||
Site *site
|
||||
RogueAP *rogueap
|
||||
SpeedTest *speedtest
|
||||
CountryTraffic *ucountrytraffic
|
||||
DHCPLease *dhcplease
|
||||
WAN *wan
|
||||
Controller *controller
|
||||
FirewallPolicy *firewallpolicy
|
||||
Topology *topology
|
||||
PortAnomaly *portanomaly
|
||||
VPNMesh *vpnmesh
|
||||
*Config `json:"prometheus" toml:"prometheus" xml:"prometheus" yaml:"prometheus"`
|
||||
Client *uclient
|
||||
Device *unifiDevice
|
||||
UAP *uap
|
||||
USG *usg
|
||||
USW *usw
|
||||
PDU *pdu
|
||||
Site *site
|
||||
RogueAP *rogueap
|
||||
SpeedTest *speedtest
|
||||
CountryTraffic *ucountrytraffic
|
||||
DHCPLease *dhcplease
|
||||
WAN *wan
|
||||
Controller *controller
|
||||
FirewallPolicy *firewallpolicy
|
||||
Topology *topology
|
||||
PortAnomaly *portanomaly
|
||||
VPNMesh *vpnmesh
|
||||
IntegrationDevice *integrationDevice
|
||||
WANStatus *wanStatus
|
||||
PortForward *portForward
|
||||
SSLCertificate *sslCertificate
|
||||
UPSDevice *upsDevice
|
||||
WifiBroadcast *wifiBroadcast
|
||||
FirewallZone *firewallZone
|
||||
ACLRule *aclRule
|
||||
VPNServer *vpnServer
|
||||
SiteToSiteTunnel *siteToSiteTunnel
|
||||
LAG *lag
|
||||
MCLAGDomain *mclagDomain
|
||||
SwitchStack *switchStack
|
||||
DNSPolicy *dnsPolicy
|
||||
RADIUSProfile *radiusProfile
|
||||
TrafficMatchingList *trafficMatchingList
|
||||
HotspotVoucher *hotspotVoucher
|
||||
DPIApplication *dpiApplication
|
||||
DPICategory *dpiCategory
|
||||
PendingDevice *pendingDevice
|
||||
Country *country
|
||||
// controllerUp tracks per-controller poll success (1) or failure (0).
|
||||
controllerUp *prometheus.GaugeVec
|
||||
// This interface is passed to the Collect() method. The Collect method uses
|
||||
|
|
@ -223,6 +244,27 @@ func (u *promUnifi) Run(c poller.Collect) error {
|
|||
u.Topology = descTopology(u.Namespace + "_")
|
||||
u.PortAnomaly = descPortAnomaly(u.Namespace + "_")
|
||||
u.VPNMesh = descVPNMesh(u.Namespace + "_")
|
||||
u.IntegrationDevice = descIntegrationDevice(u.Namespace + "_device_")
|
||||
u.WANStatus = descWANStatus(u.Namespace + "_")
|
||||
u.PortForward = descPortForward(u.Namespace + "_")
|
||||
u.SSLCertificate = descSSLCertificate(u.Namespace + "_")
|
||||
u.UPSDevice = descUPSDevice(u.Namespace + "_")
|
||||
u.WifiBroadcast = descWifiBroadcast(u.Namespace + "_")
|
||||
u.FirewallZone = descFirewallZone(u.Namespace + "_")
|
||||
u.ACLRule = descACLRule(u.Namespace + "_")
|
||||
u.VPNServer = descVPNServer(u.Namespace + "_")
|
||||
u.SiteToSiteTunnel = descSiteToSiteTunnel(u.Namespace + "_")
|
||||
u.LAG = descLAG(u.Namespace + "_")
|
||||
u.MCLAGDomain = descMCLAGDomain(u.Namespace + "_")
|
||||
u.SwitchStack = descSwitchStack(u.Namespace + "_")
|
||||
u.DNSPolicy = descDNSPolicy(u.Namespace + "_")
|
||||
u.RADIUSProfile = descRADIUSProfile(u.Namespace + "_")
|
||||
u.TrafficMatchingList = descTrafficMatchingList(u.Namespace + "_")
|
||||
u.HotspotVoucher = descHotspotVoucher(u.Namespace + "_")
|
||||
u.DPIApplication = descDPIApplication(u.Namespace + "_")
|
||||
u.DPICategory = descDPICategory(u.Namespace + "_")
|
||||
u.PendingDevice = descPendingDevice(u.Namespace + "_")
|
||||
u.Country = descCountry(u.Namespace + "_")
|
||||
u.controllerUp = prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Name: u.Namespace + "_controller_up",
|
||||
Help: "Whether the last poll of the UniFi controller succeeded (1) or failed (0).",
|
||||
|
|
@ -311,7 +353,16 @@ func (t *target) Describe(ch chan<- *prometheus.Desc) {
|
|||
// Describe satisfies the prometheus Collector. This returns all of the
|
||||
// metric descriptions that this packages produces.
|
||||
func (u *promUnifi) Describe(ch chan<- *prometheus.Desc) {
|
||||
for _, f := range []any{u.Client, u.Device, u.UAP, u.USG, u.USW, u.PDU, u.Site, u.SpeedTest, u.DHCPLease, u.WAN, u.FirewallPolicy, u.Topology, u.PortAnomaly, u.VPNMesh} {
|
||||
for _, f := range []any{
|
||||
u.Client, u.Device, u.UAP, u.USG, u.USW, u.PDU, u.Site, u.SpeedTest,
|
||||
u.DHCPLease, u.WAN, u.FirewallPolicy, u.Topology, u.PortAnomaly, u.VPNMesh,
|
||||
u.IntegrationDevice, u.WANStatus,
|
||||
u.PortForward, u.SSLCertificate, u.UPSDevice, u.WifiBroadcast,
|
||||
u.FirewallZone, u.ACLRule, u.VPNServer, u.SiteToSiteTunnel,
|
||||
u.LAG, u.MCLAGDomain, u.SwitchStack, u.DNSPolicy, u.RADIUSProfile,
|
||||
u.TrafficMatchingList, u.HotspotVoucher,
|
||||
u.DPIApplication, u.DPICategory, u.PendingDevice, u.Country,
|
||||
} {
|
||||
v := reflect.Indirect(reflect.ValueOf(f))
|
||||
|
||||
// Loop each struct member and send it to the provided channel.
|
||||
|
|
@ -510,6 +561,133 @@ func (u *promUnifi) loopExports(r report) {
|
|||
}
|
||||
}
|
||||
|
||||
// v5.26.0 additions.
|
||||
for _, ds := range m.IntegrationDevStats {
|
||||
if d, ok := ds.(*unifi.IntegrationDeviceStats); ok {
|
||||
u.exportIntegrationDeviceStats(r, d)
|
||||
}
|
||||
}
|
||||
|
||||
for _, ws := range m.WANStatuses {
|
||||
if w, ok := ws.(*unifi.WANStatus); ok {
|
||||
u.exportWANStatus(r, w)
|
||||
}
|
||||
}
|
||||
|
||||
for _, pf := range m.PortForwards {
|
||||
if v, ok := pf.(*unifi.PortForward); ok {
|
||||
u.exportPortForward(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, sc := range m.SSLCertificates {
|
||||
if v, ok := sc.(*unifi.SSLCertificate); ok {
|
||||
u.exportSSLCertificate(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, ud := range m.UPSDevices {
|
||||
if v, ok := ud.(*unifi.UPSDeviceSelector); ok {
|
||||
u.exportUPSDevice(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, wb := range m.WifiBroadcasts {
|
||||
if v, ok := wb.(*unifi.WifiBroadcast); ok {
|
||||
u.exportWifiBroadcast(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, fz := range m.FirewallZones {
|
||||
if v, ok := fz.(*unifi.FirewallZone); ok {
|
||||
u.exportFirewallZone(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, ar := range m.ACLRules {
|
||||
if v, ok := ar.(*unifi.ACLRule); ok {
|
||||
u.exportACLRule(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, vs := range m.VPNServers {
|
||||
if v, ok := vs.(*unifi.VPNServer); ok {
|
||||
u.exportVPNServer(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, st := range m.SiteToSiteTunnels {
|
||||
if v, ok := st.(*unifi.SiteToSiteTunnel); ok {
|
||||
u.exportSiteToSiteTunnel(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, l := range m.LAGs {
|
||||
if v, ok := l.(*unifi.LAG); ok {
|
||||
u.exportLAG(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, md := range m.MCLAGDomains {
|
||||
if v, ok := md.(*unifi.MCLAGDomain); ok {
|
||||
u.exportMCLAGDomain(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, ss := range m.SwitchStacks {
|
||||
if v, ok := ss.(*unifi.SwitchStack); ok {
|
||||
u.exportSwitchStack(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, dp := range m.DNSPolicies {
|
||||
if v, ok := dp.(*unifi.DNSPolicy); ok {
|
||||
u.exportDNSPolicy(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, rp := range m.RADIUSProfiles {
|
||||
if v, ok := rp.(*unifi.RADIUSProfile); ok {
|
||||
u.exportRADIUSProfile(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, tml := range m.TrafficMatchingLists {
|
||||
if v, ok := tml.(*unifi.TrafficMatchingList); ok {
|
||||
u.exportTrafficMatchingList(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, hv := range m.HotspotVouchers {
|
||||
if v, ok := hv.(*unifi.HotspotVoucher); ok {
|
||||
u.exportHotspotVoucher(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, app := range m.DPIApplications {
|
||||
if v, ok := app.(*unifi.DPIApplication); ok {
|
||||
u.exportDPIApplication(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, cat := range m.DPICategories {
|
||||
if v, ok := cat.(*unifi.DPICategory); ok {
|
||||
u.exportDPICategory(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, pd := range m.PendingDevices {
|
||||
if v, ok := pd.(*unifi.PendingDevice); ok {
|
||||
u.exportPendingDevice(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range m.Countries {
|
||||
if v, ok := c.(*unifi.Country); ok {
|
||||
u.exportCountry(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
u.exportClientDPItotals(r, appTotal, catTotal)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
package promunifi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/unpoller/unifi/v5"
|
||||
)
|
||||
|
||||
type integrationDevice struct {
|
||||
CPUUtilizationPct *prometheus.Desc
|
||||
MemoryUtilizationPct *prometheus.Desc
|
||||
LoadAverage1Min *prometheus.Desc
|
||||
LoadAverage5Min *prometheus.Desc
|
||||
LoadAverage15Min *prometheus.Desc
|
||||
UptimeSec *prometheus.Desc
|
||||
RadioTxRetriesPct *prometheus.Desc
|
||||
UplinkRxRateBps *prometheus.Desc
|
||||
UplinkTxRateBps *prometheus.Desc
|
||||
}
|
||||
|
||||
func descIntegrationDevice(ns string) *integrationDevice {
|
||||
labels := []string{"device_id"}
|
||||
radioLabels := []string{"device_id", "frequency_ghz"}
|
||||
uplinkLabels := []string{"device_id", "uplink_index"}
|
||||
|
||||
return &integrationDevice{
|
||||
CPUUtilizationPct: prometheus.NewDesc(ns+"cpu_utilization_pct", "Device CPU utilization percentage (Integration/v1)", labels, nil),
|
||||
MemoryUtilizationPct: prometheus.NewDesc(ns+"memory_utilization_pct", "Device memory utilization percentage (Integration/v1)", labels, nil),
|
||||
LoadAverage1Min: prometheus.NewDesc(ns+"load_average_1min", "Device 1-minute load average (Integration/v1)", labels, nil),
|
||||
LoadAverage5Min: prometheus.NewDesc(ns+"load_average_5min", "Device 5-minute load average (Integration/v1)", labels, nil),
|
||||
LoadAverage15Min: prometheus.NewDesc(ns+"load_average_15min", "Device 15-minute load average (Integration/v1)", labels, nil),
|
||||
UptimeSec: prometheus.NewDesc(ns+"uptime_seconds", "Device uptime in seconds (Integration/v1)", labels, nil),
|
||||
RadioTxRetriesPct: prometheus.NewDesc(ns+"radio_tx_retries_pct", "Per-radio TX retry percentage (Integration/v1)", radioLabels, nil),
|
||||
UplinkRxRateBps: prometheus.NewDesc(ns+"uplink_rx_rate_bps", "Per-uplink receive rate in bps (Integration/v1)", uplinkLabels, nil),
|
||||
UplinkTxRateBps: prometheus.NewDesc(ns+"uplink_tx_rate_bps", "Per-uplink transmit rate in bps (Integration/v1)", uplinkLabels, nil),
|
||||
}
|
||||
}
|
||||
|
||||
func (u *promUnifi) exportIntegrationDeviceStats(r report, ds *unifi.IntegrationDeviceStats) {
|
||||
if ds == nil {
|
||||
return
|
||||
}
|
||||
|
||||
labels := []string{ds.DeviceID}
|
||||
|
||||
r.send([]*metric{
|
||||
{u.IntegrationDevice.CPUUtilizationPct, gauge, ds.CPUUtilizationPct, labels},
|
||||
{u.IntegrationDevice.MemoryUtilizationPct, gauge, ds.MemoryUtilizationPct, labels},
|
||||
{u.IntegrationDevice.LoadAverage1Min, gauge, ds.LoadAverage1Min, labels},
|
||||
{u.IntegrationDevice.LoadAverage5Min, gauge, ds.LoadAverage5Min, labels},
|
||||
{u.IntegrationDevice.LoadAverage15Min, gauge, ds.LoadAverage15Min, labels},
|
||||
{u.IntegrationDevice.UptimeSec, gauge, ds.UptimeSec, labels},
|
||||
})
|
||||
|
||||
for _, radio := range ds.Radios {
|
||||
radioLabels := []string{ds.DeviceID, radio.FrequencyGHz.Txt}
|
||||
|
||||
r.send([]*metric{
|
||||
{u.IntegrationDevice.RadioTxRetriesPct, gauge, radio.TxRetriesPct, radioLabels},
|
||||
})
|
||||
}
|
||||
|
||||
for i, uplink := range ds.Uplinks {
|
||||
uplinkLabels := []string{ds.DeviceID, fmt.Sprint(i)}
|
||||
|
||||
r.send([]*metric{
|
||||
{u.IntegrationDevice.UplinkRxRateBps, gauge, uplink.RxRateBps, uplinkLabels},
|
||||
{u.IntegrationDevice.UplinkTxRateBps, gauge, uplink.TxRateBps, uplinkLabels},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
package promunifi
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/unpoller/unifi/v5"
|
||||
)
|
||||
|
||||
// dpiApplication holds Prometheus descriptors for DPI application catalogue metrics.
|
||||
// No site label — this is a global catalogue.
|
||||
type dpiApplication struct {
|
||||
Presence *prometheus.Desc
|
||||
}
|
||||
|
||||
func descDPIApplication(ns string) *dpiApplication {
|
||||
labels := []string{"app_id", "name"}
|
||||
|
||||
return &dpiApplication{
|
||||
Presence: prometheus.NewDesc(ns+"dpi_application_present",
|
||||
"DPI application catalogue entry present (always 1)",
|
||||
labels, nil),
|
||||
}
|
||||
}
|
||||
|
||||
func (u *promUnifi) exportDPIApplication(r report, app *unifi.DPIApplication) {
|
||||
if app == nil {
|
||||
return
|
||||
}
|
||||
|
||||
labels := []string{app.ID.Txt, app.Name}
|
||||
|
||||
r.send([]*metric{
|
||||
{u.DPIApplication.Presence, gauge, 1.0, labels},
|
||||
})
|
||||
}
|
||||
|
||||
// dpiCategory holds Prometheus descriptors for DPI category catalogue metrics.
|
||||
// No site label — this is a global catalogue.
|
||||
type dpiCategory struct {
|
||||
Presence *prometheus.Desc
|
||||
}
|
||||
|
||||
func descDPICategory(ns string) *dpiCategory {
|
||||
labels := []string{"cat_id", "name"}
|
||||
|
||||
return &dpiCategory{
|
||||
Presence: prometheus.NewDesc(ns+"dpi_category_present",
|
||||
"DPI category catalogue entry present (always 1)",
|
||||
labels, nil),
|
||||
}
|
||||
}
|
||||
|
||||
func (u *promUnifi) exportDPICategory(r report, cat *unifi.DPICategory) {
|
||||
if cat == nil {
|
||||
return
|
||||
}
|
||||
|
||||
labels := []string{cat.ID.Txt, cat.Name}
|
||||
|
||||
r.send([]*metric{
|
||||
{u.DPICategory.Presence, gauge, 1.0, labels},
|
||||
})
|
||||
}
|
||||
|
||||
// pendingDevice holds Prometheus descriptors for pending-adoption device metrics.
|
||||
// No site label — these are controller-global.
|
||||
type pendingDevice struct {
|
||||
FirmwareUpdatable *prometheus.Desc
|
||||
Supported *prometheus.Desc
|
||||
}
|
||||
|
||||
func descPendingDevice(ns string) *pendingDevice {
|
||||
labels := []string{"mac_address", "model", "state", "firmware_version"}
|
||||
|
||||
return &pendingDevice{
|
||||
FirmwareUpdatable: prometheus.NewDesc(ns+"pending_device_firmware_updatable",
|
||||
"Pending device has a firmware update available (1=yes, 0=no)",
|
||||
labels, nil),
|
||||
Supported: prometheus.NewDesc(ns+"pending_device_supported",
|
||||
"Pending device model is supported by the controller (1=yes, 0=no)",
|
||||
labels, nil),
|
||||
}
|
||||
}
|
||||
|
||||
func (u *promUnifi) exportPendingDevice(r report, pd *unifi.PendingDevice) {
|
||||
if pd == nil {
|
||||
return
|
||||
}
|
||||
|
||||
firmwareUpdatable := 0.0
|
||||
if pd.FirmwareUpdatable {
|
||||
firmwareUpdatable = 1.0
|
||||
}
|
||||
|
||||
supported := 0.0
|
||||
if pd.Supported {
|
||||
supported = 1.0
|
||||
}
|
||||
|
||||
labels := []string{pd.MACAddress, pd.Model, pd.State, pd.FirmwareVersion}
|
||||
|
||||
r.send([]*metric{
|
||||
{u.PendingDevice.FirmwareUpdatable, gauge, firmwareUpdatable, labels},
|
||||
{u.PendingDevice.Supported, gauge, supported, labels},
|
||||
})
|
||||
}
|
||||
|
||||
// country holds Prometheus descriptors for country list metrics.
|
||||
// No site label — this is a global geo-filter catalogue.
|
||||
type country struct {
|
||||
Presence *prometheus.Desc
|
||||
}
|
||||
|
||||
func descCountry(ns string) *country {
|
||||
labels := []string{"code", "name"}
|
||||
|
||||
return &country{
|
||||
Presence: prometheus.NewDesc(ns+"country_present",
|
||||
"Country entry present in geo-filter catalogue (always 1)",
|
||||
labels, nil),
|
||||
}
|
||||
}
|
||||
|
||||
func (u *promUnifi) exportCountry(r report, c *unifi.Country) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
|
||||
labels := []string{c.Code, c.Name}
|
||||
|
||||
r.send([]*metric{
|
||||
{u.Country.Presence, gauge, 1.0, labels},
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,375 @@
|
|||
package promunifi
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/unpoller/unifi/v5"
|
||||
)
|
||||
|
||||
// wifiBroadcast holds Prometheus descriptors for WiFi SSID broadcast metrics.
|
||||
type wifiBroadcast struct {
|
||||
Enabled *prometheus.Desc
|
||||
}
|
||||
|
||||
func descWifiBroadcast(ns string) *wifiBroadcast {
|
||||
labels := []string{"site_name", "name", "network", "security_type"}
|
||||
|
||||
return &wifiBroadcast{
|
||||
Enabled: prometheus.NewDesc(ns+"wifi_broadcast_enabled",
|
||||
"WiFi SSID broadcast enabled (1=enabled, 0=disabled)",
|
||||
labels, nil),
|
||||
}
|
||||
}
|
||||
|
||||
func (u *promUnifi) exportWifiBroadcast(r report, wb *unifi.WifiBroadcast) {
|
||||
if wb == nil {
|
||||
return
|
||||
}
|
||||
|
||||
enabled := 0.0
|
||||
if wb.Enabled {
|
||||
enabled = 1.0
|
||||
}
|
||||
|
||||
labels := []string{wb.SiteName, wb.Name, wb.Network, wb.SecurityConfiguration.Type}
|
||||
|
||||
r.send([]*metric{
|
||||
{u.WifiBroadcast.Enabled, gauge, enabled, labels},
|
||||
})
|
||||
}
|
||||
|
||||
// firewallZone holds Prometheus descriptors for firewall zone metrics.
|
||||
type firewallZone struct {
|
||||
NetworkCount *prometheus.Desc
|
||||
}
|
||||
|
||||
func descFirewallZone(ns string) *firewallZone {
|
||||
labels := []string{"site_name", "name", "origin"}
|
||||
|
||||
return &firewallZone{
|
||||
NetworkCount: prometheus.NewDesc(ns+"firewall_zone_network_count",
|
||||
"Number of networks assigned to the firewall zone",
|
||||
labels, nil),
|
||||
}
|
||||
}
|
||||
|
||||
func (u *promUnifi) exportFirewallZone(r report, fz *unifi.FirewallZone) {
|
||||
if fz == nil {
|
||||
return
|
||||
}
|
||||
|
||||
labels := []string{fz.SiteName, fz.Name, fz.Metadata.Origin}
|
||||
|
||||
r.send([]*metric{
|
||||
{u.FirewallZone.NetworkCount, gauge, float64(len(fz.NetworkIDs)), labels},
|
||||
})
|
||||
}
|
||||
|
||||
// aclRule holds Prometheus descriptors for ACL rule metrics.
|
||||
type aclRule struct {
|
||||
Enabled *prometheus.Desc
|
||||
Index *prometheus.Desc
|
||||
}
|
||||
|
||||
func descACLRule(ns string) *aclRule {
|
||||
labels := []string{"site_name", "name", "action"}
|
||||
|
||||
return &aclRule{
|
||||
Enabled: prometheus.NewDesc(ns+"acl_rule_enabled",
|
||||
"ACL rule enabled (1=enabled, 0=disabled)",
|
||||
labels, nil),
|
||||
Index: prometheus.NewDesc(ns+"acl_rule_index",
|
||||
"ACL rule evaluation order index",
|
||||
labels, nil),
|
||||
}
|
||||
}
|
||||
|
||||
func (u *promUnifi) exportACLRule(r report, ar *unifi.ACLRule) {
|
||||
if ar == nil {
|
||||
return
|
||||
}
|
||||
|
||||
enabled := 0.0
|
||||
if ar.Enabled {
|
||||
enabled = 1.0
|
||||
}
|
||||
|
||||
labels := []string{ar.SiteName, ar.Name, ar.Action}
|
||||
|
||||
r.send([]*metric{
|
||||
{u.ACLRule.Enabled, gauge, enabled, labels},
|
||||
{u.ACLRule.Index, gauge, ar.Index, labels},
|
||||
})
|
||||
}
|
||||
|
||||
// vpnServer holds Prometheus descriptors for VPN server metrics.
|
||||
type vpnServer struct {
|
||||
Enabled *prometheus.Desc
|
||||
}
|
||||
|
||||
func descVPNServer(ns string) *vpnServer {
|
||||
labels := []string{"site_name", "name", "vpn_type", "origin"}
|
||||
|
||||
return &vpnServer{
|
||||
Enabled: prometheus.NewDesc(ns+"vpn_server_enabled",
|
||||
"VPN server enabled (1=enabled, 0=disabled)",
|
||||
labels, nil),
|
||||
}
|
||||
}
|
||||
|
||||
func (u *promUnifi) exportVPNServer(r report, vs *unifi.VPNServer) {
|
||||
if vs == nil {
|
||||
return
|
||||
}
|
||||
|
||||
enabled := 0.0
|
||||
if vs.Enabled {
|
||||
enabled = 1.0
|
||||
}
|
||||
|
||||
labels := []string{vs.SiteName, vs.Name, vs.Type, vs.Metadata.Origin}
|
||||
|
||||
r.send([]*metric{
|
||||
{u.VPNServer.Enabled, gauge, enabled, labels},
|
||||
})
|
||||
}
|
||||
|
||||
// siteToSiteTunnel holds Prometheus descriptors for site-to-site VPN tunnel metrics.
|
||||
type siteToSiteTunnel struct {
|
||||
Presence *prometheus.Desc
|
||||
}
|
||||
|
||||
func descSiteToSiteTunnel(ns string) *siteToSiteTunnel {
|
||||
labels := []string{"site_name", "name", "tunnel_type", "origin"}
|
||||
|
||||
return &siteToSiteTunnel{
|
||||
Presence: prometheus.NewDesc(ns+"site_to_site_tunnel_present",
|
||||
"Site-to-site VPN tunnel configured (always 1 when present)",
|
||||
labels, nil),
|
||||
}
|
||||
}
|
||||
|
||||
func (u *promUnifi) exportSiteToSiteTunnel(r report, t *unifi.SiteToSiteTunnel) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
labels := []string{t.SiteName, t.Name, t.Type, t.Metadata.Origin}
|
||||
|
||||
r.send([]*metric{
|
||||
{u.SiteToSiteTunnel.Presence, gauge, 1.0, labels},
|
||||
})
|
||||
}
|
||||
|
||||
// lag holds Prometheus descriptors for Link Aggregation Group metrics.
|
||||
type lag struct {
|
||||
MemberCount *prometheus.Desc
|
||||
}
|
||||
|
||||
func descLAG(ns string) *lag {
|
||||
labels := []string{"site_name", "lag_id", "lag_type", "origin"}
|
||||
|
||||
return &lag{
|
||||
MemberCount: prometheus.NewDesc(ns+"lag_member_count",
|
||||
"Number of member entries in the LAG",
|
||||
labels, nil),
|
||||
}
|
||||
}
|
||||
|
||||
func (u *promUnifi) exportLAG(r report, l *unifi.LAG) {
|
||||
if l == nil {
|
||||
return
|
||||
}
|
||||
|
||||
labels := []string{l.SiteName, l.ID, l.Type, l.Metadata.Origin}
|
||||
|
||||
r.send([]*metric{
|
||||
{u.LAG.MemberCount, gauge, float64(len(l.Members)), labels},
|
||||
})
|
||||
}
|
||||
|
||||
// mclagDomain holds Prometheus descriptors for MC-LAG domain metrics.
|
||||
type mclagDomain struct {
|
||||
LAGCount *prometheus.Desc
|
||||
PeerCount *prometheus.Desc
|
||||
}
|
||||
|
||||
func descMCLAGDomain(ns string) *mclagDomain {
|
||||
labels := []string{"site_name", "name", "origin"}
|
||||
|
||||
return &mclagDomain{
|
||||
LAGCount: prometheus.NewDesc(ns+"mclag_domain_lag_count",
|
||||
"Number of LAGs in the MC-LAG domain",
|
||||
labels, nil),
|
||||
PeerCount: prometheus.NewDesc(ns+"mclag_domain_peer_count",
|
||||
"Number of peer devices in the MC-LAG domain",
|
||||
labels, nil),
|
||||
}
|
||||
}
|
||||
|
||||
func (u *promUnifi) exportMCLAGDomain(r report, d *unifi.MCLAGDomain) {
|
||||
if d == nil {
|
||||
return
|
||||
}
|
||||
|
||||
labels := []string{d.SiteName, d.Name, d.Metadata.Origin}
|
||||
|
||||
r.send([]*metric{
|
||||
{u.MCLAGDomain.LAGCount, gauge, float64(len(d.LAGs)), labels},
|
||||
{u.MCLAGDomain.PeerCount, gauge, float64(len(d.Peers)), labels},
|
||||
})
|
||||
}
|
||||
|
||||
// switchStack holds Prometheus descriptors for switch stack metrics.
|
||||
type switchStack struct {
|
||||
MemberCount *prometheus.Desc
|
||||
}
|
||||
|
||||
func descSwitchStack(ns string) *switchStack {
|
||||
labels := []string{"site_name", "name", "origin"}
|
||||
|
||||
return &switchStack{
|
||||
MemberCount: prometheus.NewDesc(ns+"switch_stack_member_count",
|
||||
"Number of member devices in the switch stack",
|
||||
labels, nil),
|
||||
}
|
||||
}
|
||||
|
||||
func (u *promUnifi) exportSwitchStack(r report, s *unifi.SwitchStack) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
|
||||
labels := []string{s.SiteName, s.Name, s.Metadata.Origin}
|
||||
|
||||
r.send([]*metric{
|
||||
{u.SwitchStack.MemberCount, gauge, float64(len(s.Members)), labels},
|
||||
})
|
||||
}
|
||||
|
||||
// dnsPolicy holds Prometheus descriptors for DNS policy metrics.
|
||||
type dnsPolicy struct {
|
||||
Enabled *prometheus.Desc
|
||||
}
|
||||
|
||||
func descDNSPolicy(ns string) *dnsPolicy {
|
||||
labels := []string{"site_name", "domain", "policy_type"}
|
||||
|
||||
return &dnsPolicy{
|
||||
Enabled: prometheus.NewDesc(ns+"dns_policy_enabled",
|
||||
"DNS policy enabled (1=enabled, 0=disabled)",
|
||||
labels, nil),
|
||||
}
|
||||
}
|
||||
|
||||
func (u *promUnifi) exportDNSPolicy(r report, dp *unifi.DNSPolicy) {
|
||||
if dp == nil {
|
||||
return
|
||||
}
|
||||
|
||||
enabled := 0.0
|
||||
if dp.Enabled {
|
||||
enabled = 1.0
|
||||
}
|
||||
|
||||
labels := []string{dp.SiteName, dp.Domain, dp.Type}
|
||||
|
||||
r.send([]*metric{
|
||||
{u.DNSPolicy.Enabled, gauge, enabled, labels},
|
||||
})
|
||||
}
|
||||
|
||||
// radiusProfile holds Prometheus descriptors for RADIUS profile metrics.
|
||||
type radiusProfile struct {
|
||||
Presence *prometheus.Desc
|
||||
}
|
||||
|
||||
func descRADIUSProfile(ns string) *radiusProfile {
|
||||
labels := []string{"site_name", "name", "origin"}
|
||||
|
||||
return &radiusProfile{
|
||||
Presence: prometheus.NewDesc(ns+"radius_profile_present",
|
||||
"RADIUS profile configured (always 1 when present)",
|
||||
labels, nil),
|
||||
}
|
||||
}
|
||||
|
||||
func (u *promUnifi) exportRADIUSProfile(r report, rp *unifi.RADIUSProfile) {
|
||||
if rp == nil {
|
||||
return
|
||||
}
|
||||
|
||||
labels := []string{rp.SiteName, rp.Name, rp.Metadata.Origin}
|
||||
|
||||
r.send([]*metric{
|
||||
{u.RADIUSProfile.Presence, gauge, 1.0, labels},
|
||||
})
|
||||
}
|
||||
|
||||
// trafficMatchingList holds Prometheus descriptors for traffic matching list metrics.
|
||||
type trafficMatchingList struct {
|
||||
Presence *prometheus.Desc
|
||||
}
|
||||
|
||||
func descTrafficMatchingList(ns string) *trafficMatchingList {
|
||||
labels := []string{"site_name", "name", "list_type"}
|
||||
|
||||
return &trafficMatchingList{
|
||||
Presence: prometheus.NewDesc(ns+"traffic_matching_list_present",
|
||||
"Traffic matching list configured (always 1 when present)",
|
||||
labels, nil),
|
||||
}
|
||||
}
|
||||
|
||||
func (u *promUnifi) exportTrafficMatchingList(r report, tml *unifi.TrafficMatchingList) {
|
||||
if tml == nil {
|
||||
return
|
||||
}
|
||||
|
||||
labels := []string{tml.SiteName, tml.Name, tml.Type}
|
||||
|
||||
r.send([]*metric{
|
||||
{u.TrafficMatchingList.Presence, gauge, 1.0, labels},
|
||||
})
|
||||
}
|
||||
|
||||
// hotspotVoucher holds Prometheus descriptors for hotspot voucher metrics.
|
||||
type hotspotVoucher struct {
|
||||
AuthorizedGuestCount *prometheus.Desc
|
||||
AuthorizedGuestLimit *prometheus.Desc
|
||||
DataUsageLimitMBytes *prometheus.Desc
|
||||
TimeLimitMinutes *prometheus.Desc
|
||||
}
|
||||
|
||||
func descHotspotVoucher(ns string) *hotspotVoucher {
|
||||
labels := []string{"site_name", "name", "code"}
|
||||
|
||||
return &hotspotVoucher{
|
||||
AuthorizedGuestCount: prometheus.NewDesc(ns+"hotspot_voucher_authorized_guest_count",
|
||||
"Number of guests currently authorized with this voucher",
|
||||
labels, nil),
|
||||
AuthorizedGuestLimit: prometheus.NewDesc(ns+"hotspot_voucher_authorized_guest_limit",
|
||||
"Maximum number of guests allowed with this voucher (0=unlimited)",
|
||||
labels, nil),
|
||||
DataUsageLimitMBytes: prometheus.NewDesc(ns+"hotspot_voucher_data_usage_limit_mbytes",
|
||||
"Data usage cap for the voucher in MBytes (0=no limit)",
|
||||
labels, nil),
|
||||
TimeLimitMinutes: prometheus.NewDesc(ns+"hotspot_voucher_time_limit_minutes",
|
||||
"Time limit for the voucher in minutes (0=no limit)",
|
||||
labels, nil),
|
||||
}
|
||||
}
|
||||
|
||||
func (u *promUnifi) exportHotspotVoucher(r report, hv *unifi.HotspotVoucher) {
|
||||
if hv == nil {
|
||||
return
|
||||
}
|
||||
|
||||
labels := []string{hv.SiteName, hv.Name, hv.Code}
|
||||
|
||||
r.send([]*metric{
|
||||
{u.HotspotVoucher.AuthorizedGuestCount, gauge, hv.AuthorizedGuestCount, labels},
|
||||
{u.HotspotVoucher.AuthorizedGuestLimit, gauge, hv.AuthorizedGuestLimit, labels},
|
||||
{u.HotspotVoucher.DataUsageLimitMBytes, gauge, hv.DataUsageLimitMBytes, labels},
|
||||
{u.HotspotVoucher.TimeLimitMinutes, gauge, hv.TimeLimitMinutes, labels},
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
package promunifi
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/unpoller/unifi/v5"
|
||||
)
|
||||
|
||||
type portForward struct {
|
||||
Enabled *prometheus.Desc
|
||||
Log *prometheus.Desc
|
||||
}
|
||||
|
||||
func descPortForward(ns string) *portForward {
|
||||
labels := []string{"site_name", "name", "proto", "fwd_ip", "fwd_port", "dst_port"}
|
||||
|
||||
return &portForward{
|
||||
Enabled: prometheus.NewDesc(ns+"port_forward_enabled",
|
||||
"Port forward rule enabled (1=enabled, 0=disabled)",
|
||||
labels, nil),
|
||||
Log: prometheus.NewDesc(ns+"port_forward_log",
|
||||
"Port forward rule logging enabled (1=on, 0=off)",
|
||||
labels, nil),
|
||||
}
|
||||
}
|
||||
|
||||
func (u *promUnifi) exportPortForward(r report, pf *unifi.PortForward) {
|
||||
if pf == nil {
|
||||
return
|
||||
}
|
||||
|
||||
labels := []string{pf.SiteName, pf.Name, pf.Proto, pf.FwdIP, pf.FwdPort, pf.DstPort}
|
||||
|
||||
r.send([]*metric{
|
||||
{u.PortForward.Enabled, gauge, pf.Enabled.Val, labels},
|
||||
{u.PortForward.Log, gauge, pf.Log.Val, labels},
|
||||
})
|
||||
}
|
||||
|
||||
// sslCertificate holds Prometheus descriptors for SSL certificate metrics.
|
||||
type sslCertificate struct {
|
||||
IsActive *prometheus.Desc
|
||||
IsValid *prometheus.Desc
|
||||
ValidFrom *prometheus.Desc
|
||||
ValidTo *prometheus.Desc
|
||||
}
|
||||
|
||||
func descSSLCertificate(ns string) *sslCertificate {
|
||||
labels := []string{"site_name", "cert_type", "subject", "issuer", "status"}
|
||||
|
||||
return &sslCertificate{
|
||||
IsActive: prometheus.NewDesc(ns+"ssl_cert_active",
|
||||
"SSL certificate is the active certificate (1=active, 0=inactive)",
|
||||
labels, nil),
|
||||
IsValid: prometheus.NewDesc(ns+"ssl_cert_valid",
|
||||
"SSL certificate passes validity checks (1=valid, 0=invalid)",
|
||||
labels, nil),
|
||||
ValidFrom: prometheus.NewDesc(ns+"ssl_cert_valid_from_seconds",
|
||||
"SSL certificate validity start time (Unix epoch)",
|
||||
labels, nil),
|
||||
ValidTo: prometheus.NewDesc(ns+"ssl_cert_valid_to_seconds",
|
||||
"SSL certificate expiry time (Unix epoch)",
|
||||
labels, nil),
|
||||
}
|
||||
}
|
||||
|
||||
func (u *promUnifi) exportSSLCertificate(r report, cert *unifi.SSLCertificate) {
|
||||
if cert == nil || cert.ID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
labels := []string{cert.SiteName, cert.CertType, cert.Subject, cert.Issuer, cert.Status}
|
||||
|
||||
r.send([]*metric{
|
||||
{u.SSLCertificate.IsActive, gauge, cert.IsActive.Val, labels},
|
||||
{u.SSLCertificate.IsValid, gauge, cert.IsValid.Val, labels},
|
||||
{u.SSLCertificate.ValidFrom, gauge, cert.ValidFrom, labels},
|
||||
{u.SSLCertificate.ValidTo, gauge, cert.ValidTo, labels},
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package promunifi
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/unpoller/unifi/v5"
|
||||
)
|
||||
|
||||
// upsDevice holds Prometheus descriptors for UPS device selector metrics.
|
||||
// UPSDeviceSelector has no numeric fields — we emit a presence gauge so the
|
||||
// device appears in Prometheus at all.
|
||||
type upsDevice struct {
|
||||
Presence *prometheus.Desc
|
||||
}
|
||||
|
||||
func descUPSDevice(ns string) *upsDevice {
|
||||
labels := []string{"site_name", "mac", "label"}
|
||||
|
||||
return &upsDevice{
|
||||
Presence: prometheus.NewDesc(ns+"ups_device_present",
|
||||
"UPS device detected on site (always 1 when present)",
|
||||
labels, nil),
|
||||
}
|
||||
}
|
||||
|
||||
func (u *promUnifi) exportUPSDevice(r report, d *unifi.UPSDeviceSelector) {
|
||||
if d == nil {
|
||||
return
|
||||
}
|
||||
|
||||
labels := []string{d.SiteName, d.MAC, d.Label}
|
||||
|
||||
r.send([]*metric{
|
||||
{u.UPSDevice.Presence, gauge, 1.0, labels},
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package promunifi
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/unpoller/unifi/v5"
|
||||
)
|
||||
|
||||
type wanStatus struct {
|
||||
InterfaceState *prometheus.Desc
|
||||
}
|
||||
|
||||
func descWANStatus(ns string) *wanStatus {
|
||||
labels := []string{"site_name", "wan_interface", "wan_networkgroup"}
|
||||
|
||||
return &wanStatus{
|
||||
InterfaceState: prometheus.NewDesc(ns+"wan_interface_state",
|
||||
"WAN interface state: 1=ACTIVE, 0=other",
|
||||
labels, nil),
|
||||
}
|
||||
}
|
||||
|
||||
func wanStateValue(state string) float64 {
|
||||
if state == "ACTIVE" {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (u *promUnifi) exportWANStatus(r report, ws *unifi.WANStatus) {
|
||||
if ws == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, iface := range ws.WANInterfaces {
|
||||
labels := []string{ws.SiteName, iface.Name, iface.WANNetworkgroup}
|
||||
|
||||
r.send([]*metric{
|
||||
{u.WANStatus.InterfaceState, gauge, wanStateValue(iface.State), labels},
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue