Merge pull request #44 from unifi-poller/dn2_uxg_plus

Add UXG data struct, storage for UDM, and other minor improvements.
This commit is contained in:
David Newhall 2021-03-07 22:15:43 -08:00 committed by GitHub
commit ca6239efa9
18 changed files with 686 additions and 426 deletions

View File

@ -1,9 +1,9 @@
language: go
go:
- 1.14.x
- 1.15.x
before_install:
# download super-linter: golangci-lint
- curl -sL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin latest
script:
- golangci-lint run --enable-all
- golangci-lint run --enable-all -D exhaustivestruct,nlreturn
- go test ./...

View File

@ -73,7 +73,7 @@ func (u *Unifi) GetAlarms(sites []*Site) ([]*Alarm, error) {
// GetAlarmsSite retreives the Alarms for a single Site.
func (u *Unifi) GetAlarmsSite(site *Site) ([]*Alarm, error) {
if site == nil || site.Name == "" {
return nil, errNoSiteProvided
return nil, ErrNoSiteProvided
}
u.DebugLog("Polling Controller for Alarms, site %s (%s)", site.Name, site.Desc)

View File

@ -44,7 +44,7 @@ func (u *Unifi) GetAnomalies(sites []*Site, timeRange ...time.Time) ([]*Anomaly,
// GetAnomaliesSite retreives the Anomalies for a single Site.
func (u *Unifi) GetAnomaliesSite(site *Site, timeRange ...time.Time) ([]*Anomaly, error) {
if site == nil || site.Name == "" {
return nil, errNoSiteProvided
return nil, ErrNoSiteProvided
}
u.DebugLog("Polling Controller for Anomalies, site %s (%s)", site.Name, site.Desc)
@ -119,7 +119,7 @@ func makeAnomalyParams(scale string, timeRange ...time.Time) (string, error) {
end := timeRange[1].Unix() * int64(time.Microsecond)
out = append(out, "end="+strconv.FormatInt(end, 10), "start="+strconv.FormatInt(start, 10))
default:
return "", errInvalidTimeRange
return "", ErrInvalidTimeRange
}
if len(out) == 0 {

View File

@ -36,42 +36,27 @@ func (u *Unifi) parseDevices(data []json.RawMessage, siteName string) *Devices {
for _, r := range data {
// Loop each item in the raw JSON message, detect its type and unmarshal it.
assetType := "<type key missing>"
if o := make(map[string]interface{}); u.unmarshalDevice("map", r, &o) != nil {
o := make(map[string]interface{})
if u.unmarshalDevice("map", r, &o) != nil {
u.ErrorLog("unknown asset type - cannot find asset type in payload - skipping")
continue
} else if t, ok := o["type"].(string); ok {
assetType = t
}
assetType, _ := o["type"].(string)
u.DebugLog("Unmarshalling Device Type: %v, site %s ", assetType, siteName)
// Choose which type to unmarshal into based on the "type" json key.
switch assetType { // Unmarshal again into the correct type..
case "uap":
dev := &UAP{SiteName: siteName, SourceName: u.URL}
if u.unmarshalDevice(assetType, r, dev) == nil {
dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac))
devices.UAPs = append(devices.UAPs, dev)
}
u.unmarshallUAP(siteName, r, devices)
case "ugw", "usg": // in case they ever fix the name in the api.
dev := &USG{SiteName: siteName, SourceName: u.URL}
if u.unmarshalDevice(assetType, r, dev) == nil {
dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac))
devices.USGs = append(devices.USGs, dev)
}
u.unmarshallUSG(siteName, r, devices)
case "usw":
dev := &USW{SiteName: siteName, SourceName: u.URL}
if u.unmarshalDevice(assetType, r, dev) == nil {
dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac))
devices.USWs = append(devices.USWs, dev)
}
u.unmarshallUSW(siteName, r, devices)
case "udm":
dev := &UDM{SiteName: siteName, SourceName: u.URL}
if u.unmarshalDevice(assetType, r, dev) == nil {
dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac))
devices.UDMs = append(devices.UDMs, dev)
}
u.unmarshallUDM(siteName, r, devices)
case "uxg":
u.unmarshallUXG(siteName, r, devices)
default:
u.ErrorLog("unknown asset type - %v - skipping", assetType)
}
@ -80,6 +65,46 @@ func (u *Unifi) parseDevices(data []json.RawMessage, siteName string) *Devices {
return devices
}
func (u *Unifi) unmarshallUAP(siteName string, payload json.RawMessage, devices *Devices) {
dev := &UAP{SiteName: siteName, SourceName: u.URL}
if u.unmarshalDevice("uap", payload, dev) == nil {
dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac))
devices.UAPs = append(devices.UAPs, dev)
}
}
func (u *Unifi) unmarshallUSG(siteName string, payload json.RawMessage, devices *Devices) {
dev := &USG{SiteName: siteName, SourceName: u.URL}
if u.unmarshalDevice("ugw", payload, dev) == nil {
dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac))
devices.USGs = append(devices.USGs, dev)
}
}
func (u *Unifi) unmarshallUSW(siteName string, payload json.RawMessage, devices *Devices) {
dev := &USW{SiteName: siteName, SourceName: u.URL}
if u.unmarshalDevice("usw", payload, dev) == nil {
dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac))
devices.USWs = append(devices.USWs, dev)
}
}
func (u *Unifi) unmarshallUXG(siteName string, payload json.RawMessage, devices *Devices) {
dev := &UXG{SiteName: siteName, SourceName: u.URL}
if u.unmarshalDevice("uxg", payload, dev) == nil {
dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac))
devices.UXGs = append(devices.UXGs, dev)
}
}
func (u *Unifi) unmarshallUDM(siteName string, payload json.RawMessage, devices *Devices) {
dev := &UDM{SiteName: siteName, SourceName: u.URL}
if u.unmarshalDevice("udm", payload, dev) == nil {
dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac))
devices.UDMs = append(devices.UDMs, dev)
}
}
// unmarshalDevice handles logging for the unmarshal operations in parseDevices().
func (u *Unifi) unmarshalDevice(dev string, data json.RawMessage, v interface{}) (err error) {
if err = json.Unmarshal(data, v); err != nil {
@ -92,7 +117,11 @@ func (u *Unifi) unmarshalDevice(dev string, data json.RawMessage, v interface{})
u.DebugLog("==- https://github.com/unifi-poller/unifi/issues/new -==")
}
return err
if err != nil {
return fmt.Errorf("json unmarshal: %w", err)
}
return nil
}
// pick returns the first non empty string in a list.

View File

@ -21,6 +21,17 @@ type DPIData struct {
TxBytes int64 `json:"tx_bytes"`
RxPackets int64 `json:"rx_packets"`
TxPackets int64 `json:"tx_packets"`
Clients []*DPIClient `json:"clients,omitempty"`
KnownClients FlexInt `json:"known_clients,omitempty"`
}
// DPIClient data is sometimes included in ByApp output.
type DPIClient struct {
Mac string `json:"mac"`
RxBytes FlexInt `json:"rx_bytes"`
TxBytes FlexInt `json:"tx_bytes"`
RxPackets FlexInt `json:"rx_packets"`
TxPackets FlexInt `json:"tx_packets"`
}
// DPIMap allows binding methods to the DPICat and DPIApps variables.

View File

@ -8,8 +8,8 @@ import (
)
var (
errNoSiteProvided = fmt.Errorf("site must not be nil or empty")
errInvalidTimeRange = fmt.Errorf("only 0, 1 or 2 times may be provided to timeRange")
ErrNoSiteProvided = fmt.Errorf("site must not be nil or empty")
ErrInvalidTimeRange = fmt.Errorf("only 0, 1 or 2 times may be provided to timeRange")
)
const (
@ -35,7 +35,7 @@ func (u *Unifi) GetEvents(sites []*Site, hours time.Duration) ([]*Event, error)
// GetSiteEvents retrieves the last 1 hour's worth of events from a single site.
func (u *Unifi) GetSiteEvents(site *Site, hours time.Duration) ([]*Event, error) {
if site == nil || site.Name == "" {
return nil, errNoSiteProvided
return nil, ErrNoSiteProvided
}
if hours < time.Hour {
@ -196,5 +196,9 @@ func (v *IPGeo) UnmarshalJSON(data []byte) error {
v.CountryName = g.CountryName
v.Organization = g.Organization
return err
if err != nil {
return fmt.Errorf("json unmarshal: %w", err)
}
return nil
}

View File

@ -1,10 +1,10 @@
module github.com/unifi-poller/unifi
go 1.14
go 1.15
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.4.0
golang.org/x/net v0.0.0-20200625001655-4c5254603344
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
)

View File

@ -13,10 +13,16 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=

View File

@ -76,7 +76,7 @@ func (u *Unifi) GetIDS(sites []*Site, timeRange ...time.Time) ([]*IDS, error) {
// Events between start and end are returned. End defaults to time.Now().
func (u *Unifi) GetIDSSite(site *Site, timeRange ...time.Time) ([]*IDS, error) {
if site == nil || site.Name == "" {
return nil, errNoSiteProvided
return nil, ErrNoSiteProvided
}
u.DebugLog("Polling Controller for IDS Events, site %s (%s)", site.Name, site.Desc)
@ -126,12 +126,15 @@ func makeEventParams(timeRange ...time.Time) (string, error) {
rp.Start = timeRange[0].Unix() * int64(time.Microsecond)
rp.End = timeRange[1].Unix() * int64(time.Microsecond)
default:
return "", errInvalidTimeRange
return "", ErrInvalidTimeRange
}
params, err := json.Marshal(&rp)
if err != nil {
return "", fmt.Errorf("json marshal: %w", err)
}
return string(params), err
return string(params), nil
}
type idsList []*IDS

View File

@ -38,7 +38,7 @@ func (u *Unifi) parseNetwork(data json.RawMessage, siteName string) (*Network, e
return network, u.unmarshalDevice(siteName, data, network)
}
// Network is metadata about a network managed by a UniFi controller
// Network is metadata about a network managed by a UniFi controller.
type Network struct {
DhcpdDNSEnabled FlexBool `json:"dhcpd_dns_enabled"`
DhcpdEnabled FlexBool `json:"dhcpd_enabled"`

View File

@ -5,9 +5,7 @@ import (
"strings"
)
var (
errDPIDataBug = fmt.Errorf("dpi data table contains more than 1 item; please open a bug report")
)
var ErrDPIDataBug = fmt.Errorf("dpi data table contains more than 1 item; please open a bug report")
// GetSites returns a list of configured sites on the UniFi controller.
func (u *Unifi) GetSites() ([]*Site, error) {
@ -53,7 +51,7 @@ func (u *Unifi) GetSiteDPI(sites []*Site) ([]*DPITable, error) {
}
if l := len(response.Data); l > 1 {
return nil, errDPIDataBug
return nil, ErrDPIDataBug
} else if l == 0 {
u.DebugLog("Site DPI data missing! Is DPI enabled in UniFi controller? Site %s (%s) ", site.Name, site.Desc)
continue

View File

@ -6,13 +6,10 @@ import (
"net/http"
"strconv"
"strings"
"github.com/pkg/errors"
"time"
)
var (
errCannotUnmarshalFlexInt = fmt.Errorf("cannot unmarshal to FlexInt")
)
var ErrCannotUnmarshalFlexInt = fmt.Errorf("cannot unmarshal to FlexInt")
// This is a list of unifi API paths.
// The %s in each string must be replaced with a Site.Name.
@ -27,17 +24,17 @@ const (
APISiteDPI string = "/api/s/%s/stat/sitedpi"
// APISiteDPI is site DPI data.
APIClientDPI string = "/api/s/%s/stat/stadpi"
// APIClientPath is Unifi Clients API Path
// APIClientPath is Unifi Clients API Path.
APIClientPath string = "/api/s/%s/stat/sta"
// APINetworkPath is where we get data about Unifi networks.
APINetworkPath string = "/api/s/%s/rest/networkconf"
// APIDevicePath is where we get data about Unifi devices.
APIDevicePath string = "/api/s/%s/stat/device"
// APILoginPath is Unifi Controller Login API Path
// APILoginPath is Unifi Controller Login API Path.
APILoginPath string = "/api/login"
// APILoginPathNew is how we log into UDM 5.12.55+
// APILoginPathNew is how we log into UDM 5.12.55+.
APILoginPathNew string = "/api/auth/login"
// APIEventPathIDS returns Intrusion Detection/Prevention Systems Events
// APIEventPathIDS returns Intrusion Detection/Prevention Systems Events.
APIEventPathIDS string = "/api/s/%s/stat/ips/event"
// APIEventPathAlarms contains the site alarms.
APIEventPathAlarms string = "/api/s/%s/list/alarm"
@ -79,6 +76,7 @@ type Devices struct {
USGs []*USG
USWs []*USW
UDMs []*UDM
UXGs []*UXG
}
// Config is the data passed into our library. This configures things and allows
@ -91,6 +89,7 @@ type Config struct {
New bool
ErrorLog Logger
DebugLog Logger
Timeout time.Duration // how long to wait for replies, default: forever.
}
// Unifi is what you get in return for providing a password! Unifi represents
@ -124,7 +123,7 @@ func (f *FlexInt) UnmarshalJSON(b []byte) error {
var unk interface{}
if err := json.Unmarshal(b, &unk); err != nil {
return err
return fmt.Errorf("json unmarshal: %w", err)
}
switch i := unk.(type) {
@ -138,7 +137,7 @@ func (f *FlexInt) UnmarshalJSON(b []byte) error {
f.Txt = "0"
f.Val = 0
default:
return errors.Wrapf(errCannotUnmarshalFlexInt, "%v", b)
return fmt.Errorf("%v: %w", b, ErrCannotUnmarshalFlexInt)
}
return nil

View File

@ -28,10 +28,7 @@ type UDM struct {
LastSeen FlexInt `json:"last_seen"`
AdoptableWhenUpgraded FlexBool `json:"adoptable_when_upgraded"`
Cfgversion string `json:"cfgversion"`
ConfigNetwork struct {
Type string `json:"type"`
IP string `json:"ip"`
} `json:"config_network"`
ConfigNetwork *ConfigNetwork `json:"config_network"`
VwireTable []interface{} `json:"vwire_table"`
Dot1XPortctrlEnabled FlexBool `json:"dot1x_portctrl_enabled"`
JumboframeEnabled FlexBool `json:"jumboframe_enabled"`
@ -49,16 +46,9 @@ type UDM struct {
InformIP string `json:"inform_ip"`
RequiredVersion string `json:"required_version"`
BoardRev FlexInt `json:"board_rev"`
EthernetTable []struct {
Mac string `json:"mac"`
NumPort FlexInt `json:"num_port"`
Name string `json:"name"`
} `json:"ethernet_table"`
EthernetTable []*EthernetTable `json:"ethernet_table"`
PortTable []Port `json:"port_table"`
EthernetOverrides []struct {
Ifname string `json:"ifname"`
Networkgroup string `json:"networkgroup"`
} `json:"ethernet_overrides"`
EthernetOverrides []*EthernetOverrides `json:"ethernet_overrides"`
UsgCaps FlexInt `json:"usg_caps"`
HasSpeaker FlexBool `json:"has_speaker"`
HasEth1 FlexBool `json:"has_eth1"`
@ -96,7 +86,7 @@ type UDM struct {
Uplink Uplink `json:"uplink"`
ConnectRequestIP string `json:"connect_request_ip"`
ConnectRequestPort string `json:"connect_request_port"`
DownlinkTable []interface{} `json:"downlink_table"`
DownlinkTable []*DownlinkTable `json:"downlink_table"`
WlangroupIDNa string `json:"wlangroup_id_na"`
WlangroupIDNg string `json:"wlangroup_id_ng"`
BandsteeringMode string `json:"bandsteering_mode"`
@ -110,6 +100,7 @@ type UDM struct {
PortconfID string `json:"portconf_id"`
} `json:"port_overrides"`
Stat UDMStat `json:"stat"`
Storage []*Storage `json:"storage"`
TxBytes FlexInt `json:"tx_bytes"`
RxBytes FlexInt `json:"rx_bytes"`
Bytes FlexInt `json:"bytes"`
@ -131,7 +122,19 @@ type UDM struct {
NumHandheld FlexInt `json:"num_handheld"` // USG
}
type EthernetOverrides struct {
Ifname string `json:"ifname"`
Networkgroup string `json:"networkgroup"`
}
type EthernetTable struct {
Mac string `json:"mac"`
NumPort FlexInt `json:"num_port"`
Name string `json:"name"`
}
// NetworkTable is the list of networks on a gateway.
// Not all gateways have all features.
type NetworkTable []struct {
ID string `json:"_id"`
AttrNoDelete FlexBool `json:"attr_no_delete"`
@ -150,6 +153,7 @@ type NetworkTable []struct {
Dhcpdv6Enabled FlexBool `json:"dhcpdv6_enabled"`
Ipv6RaEnabled FlexBool `json:"ipv6_ra_enabled"`
LteLanEnabled FlexBool `json:"lte_lan_enabled"`
AutoScaleEnabled FlexBool `json:"auto_scale_enabled"`
Networkgroup string `json:"networkgroup"`
DhcpdLeasetime FlexInt `json:"dhcpd_leasetime"`
DhcpdDNSEnabled FlexBool `json:"dhcpd_dns_enabled"`
@ -167,6 +171,9 @@ type NetworkTable []struct {
IsGuest FlexBool `json:"is_guest"`
IP string `json:"ip"`
Up FlexBool `json:"up"`
ActiveDhcpLeaseCount int `json:"active_dhcp_lease_count"`
GatewayInterfaceName string `json:"gateway_interface_name"`
DPIStatsTable *DPITable `json:"dpistats_table"`
NumSta FlexInt `json:"num_sta"`
RxBytes FlexInt `json:"rx_bytes"`
RxPackets FlexInt `json:"rx_packets"`
@ -174,6 +181,15 @@ type NetworkTable []struct {
TxPackets FlexInt `json:"tx_packets"`
}
// Storage is hard drive into for a device with storage.
type Storage struct {
MountPoint string `json:"mount_point"`
Name string `json:"name"`
Size FlexInt `json:"size"`
Type string `json:"type"`
Used FlexInt `json:"used"`
}
type Temperature struct {
Name string `json:"name"`
Type string `json:"type"`

View File

@ -7,6 +7,7 @@ package unifi
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"fmt"
@ -18,13 +19,13 @@ import (
"strings"
"time"
"github.com/pkg/errors"
"golang.org/x/net/publicsuffix"
)
var (
errAuthenticationFailed = fmt.Errorf("authentication failed")
errInvalidStatusCode = fmt.Errorf("invalid status code from server")
ErrAuthenticationFailed = fmt.Errorf("authentication failed")
ErrInvalidStatusCode = fmt.Errorf("invalid status code from server")
ErrNoParams = fmt.Errorf("requedted PUT with no parameters")
)
// NewUnifi creates a http.Client with authenticated cookies.
@ -33,7 +34,7 @@ var (
func NewUnifi(config *Config) (*Unifi, error) {
jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
if err != nil {
return nil, err
return nil, fmt.Errorf("creating cookiejar: %w", err)
}
config.URL = strings.TrimRight(config.URL, "/")
@ -46,8 +47,10 @@ func NewUnifi(config *Config) (*Unifi, error) {
config.DebugLog = discardLogs
}
u := &Unifi{Config: config,
u := &Unifi{
Config: config,
Client: &http.Client{
Timeout: config.Timeout,
Jar: jar,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: !config.VerifySSL}, // nolint: gosec
@ -64,7 +67,7 @@ func NewUnifi(config *Config) (*Unifi, error) {
}
if err := u.GetServerData(); err != nil {
return u, errors.Wrap(err, "unable to get server version")
return u, fmt.Errorf("unable to get server version: %w", err)
}
return u, nil
@ -82,7 +85,7 @@ func (u *Unifi) Login() error {
resp, err := u.Do(req)
if err != nil {
return err
return fmt.Errorf("making request: %w", err)
}
defer resp.Body.Close() // we need no data here.
@ -91,8 +94,8 @@ func (u *Unifi) Login() error {
req.URL, time.Since(start).Round(time.Millisecond), resp.ContentLength)
if resp.StatusCode != http.StatusOK {
return errors.Wrapf(errAuthenticationFailed, "(user: %s): %s (status: %s)",
u.User, req.URL, resp.Status)
return fmt.Errorf("(user: %s): %s (status: %s): %w",
u.User, req.URL, resp.Status, ErrAuthenticationFailed)
}
return nil
@ -103,11 +106,21 @@ func (u *Unifi) Login() error {
// check if this is a newer controller or not. If it is, we set new to true.
// Setting new to true makes the path() method return different (new) paths.
func (u *Unifi) checkNewStyleAPI() error {
var (
ctx = context.Background()
cancel func()
)
if u.Config.Timeout != 0 {
ctx, cancel = context.WithTimeout(ctx, u.Config.Timeout)
defer cancel()
}
u.DebugLog("Requesting %s/ to determine API paths", u.URL)
req, err := http.NewRequest("GET", u.URL+"/", nil)
req, err := http.NewRequestWithContext(ctx, "GET", u.URL+"/", nil)
if err != nil {
return err
return fmt.Errorf("creating request: %w", err)
}
// We can't share these cookies with other requests, so make a new client.
@ -123,7 +136,7 @@ func (u *Unifi) checkNewStyleAPI() error {
resp, err := client.Do(req)
if err != nil {
return err
return fmt.Errorf("making request: %w", err)
}
defer resp.Body.Close() // we need no data here.
@ -185,39 +198,44 @@ func (u *Unifi) PutData(apiPath string, v interface{}, params ...string) error {
// Use this if you're unmarshalling UniFi data into custom types.
// And if you're doing that... sumbut a pull request with your new struct. :)
// This is a helper method that is exposed for convenience.
func (u *Unifi) UniReq(apiPath string, params string) (req *http.Request, err error) {
func (u *Unifi) UniReq(apiPath string, params string) (*http.Request, error) {
var (
req *http.Request
err error
)
switch apiPath = u.path(apiPath); params {
case "":
req, err = http.NewRequest("GET", u.URL+apiPath, nil)
req, err = http.NewRequest(http.MethodGet, u.URL+apiPath, nil)
default:
req, err = http.NewRequest("POST", u.URL+apiPath, bytes.NewBufferString(params))
req, err = http.NewRequest(http.MethodPost, u.URL+apiPath, bytes.NewBufferString(params))
}
if err != nil {
return
return nil, fmt.Errorf("creating request: %w", err)
}
u.setHeaders(req, params)
return
return req, nil
}
// UniReqPut is the Put call equivalent to UniReq
func (u *Unifi) UniReqPut(apiPath string, params string) (req *http.Request, err error) {
switch apiPath = u.path(apiPath); params {
case "":
err = fmt.Errorf("Put with no parameters. Use UniReq()")
default:
req, err = http.NewRequest("PUT", u.URL+apiPath, bytes.NewBufferString(params))
// UniReqPut is the Put call equivalent to UniReq.
func (u *Unifi) UniReqPut(apiPath string, params string) (*http.Request, error) {
if params == "" {
return nil, ErrNoParams
}
apiPath = u.path(apiPath)
req, err := http.NewRequest(http.MethodPut, u.URL+apiPath, bytes.NewBufferString(params)) //nolint:noctx
if err != nil {
return
return nil, fmt.Errorf("creating request: %w", err)
}
u.setHeaders(req, params)
return
return req, nil
}
// GetJSON returns the raw JSON from a path. This is useful for debugging.
@ -231,7 +249,7 @@ func (u *Unifi) GetJSON(apiPath string, params ...string) ([]byte, error) {
}
// PutJSON uses a PUT call and returns the raw JSON in the same way as GetData
// Use this if you want to change data via the REST API
// Use this if you want to change data via the REST API.
func (u *Unifi) PutJSON(apiPath string, params ...string) ([]byte, error) {
req, err := u.UniReqPut(apiPath, strings.Join(params, " "))
if err != nil {
@ -242,16 +260,26 @@ func (u *Unifi) PutJSON(apiPath string, params ...string) ([]byte, error) {
}
func (u *Unifi) do(req *http.Request) ([]byte, error) {
resp, err := u.Do(req)
var (
cancel func()
ctx = context.Background()
)
if u.Config.Timeout != 0 {
ctx, cancel = context.WithTimeout(ctx, u.Config.Timeout)
defer cancel()
}
resp, err := u.Do(req.WithContext(ctx))
if err != nil {
return []byte{}, err
return []byte{}, fmt.Errorf("making request: %w", err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return body, err
return body, fmt.Errorf("reading response: %w", err)
}
// Save the returned CSRF header.
@ -260,7 +288,7 @@ func (u *Unifi) do(req *http.Request) ([]byte, error) {
}
if resp.StatusCode != http.StatusOK {
err = errors.Wrapf(errInvalidStatusCode, "%s: %s", req.URL, resp.Status)
err = fmt.Errorf("%s: %s: %w", req.URL, resp.Status, ErrInvalidStatusCode)
}
return body, err

View File

@ -30,9 +30,11 @@ func TestUniReq(t *testing.T) {
a := assert.New(t)
p := "/test/path"
u := "http://some.url:8443"
// Test empty parameters.
authReq := &Unifi{Client: &http.Client{}, Config: &Config{URL: u, DebugLog: discardLogs}}
r, err := authReq.UniReq(p, "")
a.Nil(err, "newrequest must not produce an error")
a.EqualValues(p, r.URL.Path,
"the provided apiPath was not added to http request")
@ -45,11 +47,13 @@ func TestUniReq(t *testing.T) {
authReq = &Unifi{Client: &http.Client{}, Config: &Config{URL: "http://some.url:8443", DebugLog: discardLogs}}
r, err = authReq.UniReq(p, k)
a.Nil(err, "newrequest must not produce an error")
a.EqualValues(p, r.URL.Path,
"the provided apiPath was not added to http request")
a.EqualValues(u, r.URL.Scheme+"://"+r.URL.Host, "URL improperly encoded")
a.EqualValues("POST", r.Method, "with parameters the method must be POST")
a.EqualValues("application/json", r.Header.Get("Accept"), "Accept header must be set to application/json")
// Check the parameters.
d, err := ioutil.ReadAll(r.Body)
a.Nil(err, "problem reading request body, POST parameters may be malformed")
@ -61,28 +65,31 @@ func TestUniReqPut(t *testing.T) {
a := assert.New(t)
p := "/test/path"
u := "http://some.url:8443"
// Test empty parameters.
authReq := &Unifi{Client: &http.Client{}, Config: &Config{URL: u, DebugLog: discardLogs}}
r, err := authReq.UniReqPut(p, "")
_, err := authReq.UniReqPut(p, "")
a.NotNil(err, "empty params must produce an error")
// Test with parameters
k := "key1=value9&key2=value7"
authReq = &Unifi{Client: &http.Client{}, Config: &Config{URL: "http://some.url:8443", DebugLog: discardLogs}}
r, err = authReq.UniReqPut(p, k)
r, err := authReq.UniReqPut(p, k)
a.Nil(err, "newrequest must not produce an error")
a.EqualValues(p, r.URL.Path,
"the provided apiPath was not added to http request")
a.EqualValues(u, r.URL.Scheme+"://"+r.URL.Host, "URL improperly encoded")
a.EqualValues("PUT", r.Method, "with parameters the method must be POST")
a.EqualValues("application/json", r.Header.Get("Accept"), "Accept header must be set to application/json")
// Check the parameters.
d, err := ioutil.ReadAll(r.Body)
a.Nil(err, "problem reading request body, PUT parameters may be malformed")
a.EqualValues(k, string(d), "PUT parameters improperly encoded")
}
/* NOT DONE: OPEN web server, check parameters posted, more. This test is incomplete.
/* NOT DONE: OPEN web server, check parameters posted, more. These tests are incomplete.
a.EqualValues(`{"username": "user1","password": "pass2"}`, string(post_params),
"user/pass json parameters improperly encoded")
*/

View File

@ -11,15 +11,8 @@ type USG struct {
ID string `json:"_id"`
Adopted FlexBool `json:"adopted"`
Cfgversion string `json:"cfgversion"`
ConfigNetwork struct {
Type string `json:"type"`
IP string `json:"ip"`
} `json:"config_network"`
EthernetTable []struct {
Mac string `json:"mac"`
NumPort FlexInt `json:"num_port"`
Name string `json:"name"`
} `json:"ethernet_table"`
ConfigNetwork *ConfigNetwork `json:"config_network"`
EthernetTable []*EthernetTable `json:"ethernet_table"`
FwCaps FlexInt `json:"fw_caps"`
InformIP string `json:"inform_ip"`
InformURL string `json:"inform_url"`
@ -37,10 +30,7 @@ type USG struct {
UsgCaps FlexInt `json:"usg_caps"`
Version string `json:"version"`
RequiredVersion string `json:"required_version"`
EthernetOverrides []struct {
Ifname string `json:"ifname"`
Networkgroup string `json:"networkgroup"`
} `json:"ethernet_overrides"`
EthernetOverrides []*EthernetOverrides `json:"ethernet_overrides"`
HwCaps FlexInt `json:"hw_caps"`
BoardRev FlexInt `json:"board_rev"`
Unsupported FlexBool `json:"unsupported"`
@ -57,34 +47,14 @@ type USG struct {
ConnectRequestIP string `json:"connect_request_ip"`
ConnectRequestPort string `json:"connect_request_port"`
SysStats SysStats `json:"sys_stats"`
Temperatures []Temperature `json:"temperatures,omitempty"`
SystemStats SystemStats `json:"system-stats"`
GuestToken string `json:"guest_token"`
SpeedtestStatus SpeedtestStatus `json:"speedtest-status"`
SpeedtestStatusSaved FlexBool `json:"speedtest-status-saved"`
Wan1 Wan `json:"wan1"`
Wan2 Wan `json:"wan2"`
PortTable []struct {
Name string `json:"name"`
Ifname string `json:"ifname"`
IP string `json:"ip"`
Netmask string `json:"netmask"`
Mac string `json:"mac"`
Up FlexBool `json:"up"`
Speed FlexInt `json:"speed"`
FullDuplex FlexBool `json:"full_duplex"`
RxBytes FlexInt `json:"rx_bytes"`
RxDropped FlexInt `json:"rx_dropped"`
RxErrors FlexInt `json:"rx_errors"`
RxPackets FlexInt `json:"rx_packets"`
TxBytes FlexInt `json:"tx_bytes"`
TxDropped FlexInt `json:"tx_dropped"`
TxErrors FlexInt `json:"tx_errors"`
TxPackets FlexInt `json:"tx_packets"`
RxMulticast FlexInt `json:"rx_multicast"`
Enable FlexBool `json:"enable"`
DNS []string `json:"dns,omitempty"`
Gateway string `json:"gateway,omitempty"`
} `json:"port_table"`
PortTable []*Port `json:"port_table"`
NetworkTable NetworkTable `json:"network_table"`
Uplink Uplink `json:"uplink"`
Stat USGStat `json:"stat"`
@ -114,12 +84,15 @@ type Uplink struct {
Nameservers []string `json:"nameservers"`
Netmask string `json:"netmask"`
NumPort FlexInt `json:"num_port"`
Media string `json:"media"`
PortIdx FlexInt `json:"port_idx"`
RxBytes FlexInt `json:"rx_bytes"`
RxBytesR FlexInt `json:"rx_bytes-r"`
RxDropped FlexInt `json:"rx_dropped"`
RxErrors FlexInt `json:"rx_errors"`
RxMulticast FlexInt `json:"rx_multicast"`
RxPackets FlexInt `json:"rx_packets"`
RxRate FlexInt `json:"rx_rate"`
Speed FlexInt `json:"speed"`
SpeedtestLastrun FlexInt `json:"speedtest_lastrun,omitempty"`
SpeedtestPing FlexInt `json:"speedtest_ping,omitempty"`
@ -129,6 +102,7 @@ type Uplink struct {
TxDropped FlexInt `json:"tx_dropped"`
TxErrors FlexInt `json:"tx_errors"`
TxPackets FlexInt `json:"tx_packets"`
TxRate FlexInt `json:"tx_rate"`
Type string `json:"type"`
Up FlexBool `json:"up"`
Uptime FlexInt `json:"uptime"`
@ -140,7 +114,7 @@ type Uplink struct {
type Wan struct {
Autoneg FlexBool `json:"autoneg"`
BytesR FlexInt `json:"bytes-r"`
DNS []string `json:"dns"`
DNS []string `json:"dns"` // may be deprecated
Enable FlexBool `json:"enable"`
FlowctrlRx FlexBool `json:"flowctrl_rx"`
FlowctrlTx FlexBool `json:"flowctrl_tx"`
@ -164,7 +138,9 @@ type Wan struct {
RxErrors FlexInt `json:"rx_errors"`
RxMulticast FlexInt `json:"rx_multicast"`
RxPackets FlexInt `json:"rx_packets"`
RxRate FlexInt `json:"rx_rate"`
Speed FlexInt `json:"speed"`
SpeedCaps FlexInt `json:"speed_caps"`
TxBroadcast FlexInt `json:"tx_broadcast"`
TxBytes FlexInt `json:"tx_bytes"`
TxBytesR FlexInt `json:"tx_bytes-r"`
@ -172,6 +148,7 @@ type Wan struct {
TxErrors FlexInt `json:"tx_errors"`
TxMulticast FlexInt `json:"tx_multicast"`
TxPackets FlexInt `json:"tx_packets"`
TxRate FlexInt `json:"tx_rate"`
Type string `json:"type"`
Up FlexBool `json:"up"`
}
@ -182,6 +159,8 @@ type SpeedtestStatus struct {
Rundate FlexInt `json:"rundate"`
Runtime FlexInt `json:"runtime"`
ServerDesc string `json:"server_desc,omitempty"`
Server *SpeedtestServer `json:"server"`
SourceInterface string `json:"source_interface"`
StatusDownload FlexInt `json:"status_download"`
StatusPing FlexInt `json:"status_ping"`
StatusSummary FlexInt `json:"status_summary"`
@ -190,6 +169,22 @@ type SpeedtestStatus struct {
XputUpload FlexInt `json:"xput_upload"`
}
type SpeedtestServer struct {
Cc string `json:"cc"`
City string `json:"city"`
Country string `json:"country"`
Lat FlexInt `json:"lat"`
Lon FlexInt `json:"lon"`
Provider string `json:"provider"`
ProviderURL string `json:"provider_url"`
}
// ConfigNetwork comes from gateways.
type ConfigNetwork struct {
Type string `json:"type"`
IP string `json:"ip"`
}
// SystemStats is system info for a UDM, USG, USW.
type SystemStats struct {
CPU FlexInt `json:"cpu"`
@ -233,6 +228,7 @@ type Gw struct {
LanTxBytes FlexInt `json:"lan-tx_bytes"`
LanRxDropped FlexInt `json:"lan-rx_dropped"`
WanRxErrors FlexInt `json:"wan-rx_errors,omitempty"`
LanRxErrors FlexInt `json:"lan-rx_errors,omitempty"`
}
// UnmarshalJSON unmarshalls 5.10 or 5.11 formatted Gateway Stat data.

View File

@ -13,16 +13,9 @@ type USW struct {
Adopted FlexBool `json:"adopted"`
BoardRev FlexInt `json:"board_rev"`
Cfgversion string `json:"cfgversion"`
ConfigNetwork struct {
Type string `json:"type"`
IP string `json:"ip"`
} `json:"config_network"`
ConfigNetwork *ConfigNetwork `json:"config_network"`
Dot1XPortctrlEnabled FlexBool `json:"dot1x_portctrl_enabled"`
EthernetTable []struct {
Mac string `json:"mac"`
NumPort FlexInt `json:"num_port,omitempty"`
Name string `json:"name"`
} `json:"ethernet_table"`
EthernetTable []*EthernetTable `json:"ethernet_table"`
FlowctrlEnabled FlexBool `json:"flowctrl_enabled"`
FwCaps FlexInt `json:"fw_caps"`
HasFan FlexBool `json:"has_fan"`
@ -51,11 +44,7 @@ type USW struct {
Type string `json:"type"`
Version string `json:"version"`
RequiredVersion string `json:"required_version"`
SwitchCaps struct {
FeatureCaps FlexInt `json:"feature_caps"`
MaxMirrorSessions FlexInt `json:"max_mirror_sessions"`
MaxAggregateSessions FlexInt `json:"max_aggregate_sessions"`
} `json:"switch_caps"`
SwitchCaps *SwitchCaps `json:"switch_caps"`
HwCaps FlexInt `json:"hw_caps"`
Unsupported FlexBool `json:"unsupported"`
UnsupportedReason FlexInt `json:"unsupported_reason"`
@ -77,12 +66,7 @@ type USW struct {
GeneralTemperature FlexInt `json:"general_temperature"`
Overheating FlexBool `json:"overheating"`
TotalMaxPower FlexInt `json:"total_max_power"`
DownlinkTable []struct {
PortIdx FlexInt `json:"port_idx"`
Speed FlexInt `json:"speed"`
FullDuplex FlexBool `json:"full_duplex"`
Mac string `json:"mac"`
} `json:"downlink_table"`
DownlinkTable []*DownlinkTable `json:"downlink_table"`
Uplink Uplink `json:"uplink"`
LastUplink struct {
UplinkMac string `json:"uplink_mac"`
@ -97,7 +81,24 @@ type USW struct {
GuestNumSta FlexInt `json:"guest-num_sta"`
}
// Port is a physical connection on a USW or UDM.
type SwitchCaps struct {
FeatureCaps FlexInt `json:"feature_caps"`
MaxMirrorSessions FlexInt `json:"max_mirror_sessions"`
MaxAggregateSessions FlexInt `json:"max_aggregate_sessions"`
}
// MacTable is a newer feature on some switched ports.
type MacTable struct {
Age int64 `json:"age"`
Authorized FlexBool `json:"authorized"`
Hostname string `json:"hostname"`
IP string `json:"ip"`
LastReachable int64 `json:"lastReachable"`
Mac string `json:"mac"`
}
// Port is a physical connection on a USW or Gateway.
// Not every port has the same capabilities.
type Port struct {
AggregatedBy FlexBool `json:"aggregated_by"`
Autoneg FlexBool `json:"autoneg,omitempty"`
@ -113,11 +114,13 @@ type Port struct {
Ifname string `json:"ifname,omitempty"`
IsUplink FlexBool `json:"is_uplink"`
Mac string `json:"mac,omitempty"`
MacTable []MacTable `json:"mac_table,omitempty"`
Jumbo FlexBool `json:"jumbo,omitempty"`
Masked FlexBool `json:"masked"`
Media string `json:"media"`
Name string `json:"name"`
NetworkName string `json:"network_name,omitempty"`
Netmask string `json:"netmask,omitempty"`
NumPort int `json:"num_port,omitempty"`
OpMode string `json:"op_mode"`
PoeCaps FlexInt `json:"poe_caps"`
@ -130,6 +133,7 @@ type Port struct {
PoeVoltage FlexInt `json:"poe_voltage,omitempty"`
PortDelta struct {
TimeDelta int64 `json:"time_delta"`
TimeDeltaActivity int64 `json:"time_delta_activity"`
} `json:"port_delta,omitempty"`
PortIdx FlexInt `json:"port_idx"`
PortPoe FlexBool `json:"port_poe"`
@ -141,6 +145,7 @@ type Port struct {
RxErrors FlexInt `json:"rx_errors"`
RxMulticast FlexInt `json:"rx_multicast"`
RxPackets FlexInt `json:"rx_packets"`
RxRate FlexInt `json:"rx_rate,omitempty"`
Satisfaction FlexInt `json:"satisfaction,omitempty"`
SfpFound FlexBool `json:"sfp_found,omitempty"`
Speed FlexInt `json:"speed"`
@ -154,6 +159,7 @@ type Port struct {
TxErrors FlexInt `json:"tx_errors"`
TxMulticast FlexInt `json:"tx_multicast"`
TxPackets FlexInt `json:"tx_packets"`
TxRate FlexInt `json:"tx_rate,omitempty"`
Type string `json:"type,omitempty"`
Up FlexBool `json:"up"`
}

157
core/unifi/uxg.go Normal file
View File

@ -0,0 +1,157 @@
package unifi
// UXG represents all the data from the Ubiquiti Controller for a UniFi 10Gb Gateway.
// The UDM shares several structs/type-data with USW and USG.
type UXG struct {
SourceName string `json:"-"`
SiteName string `json:"-"`
ID string `json:"_id"`
IP string `json:"ip"`
Mac string `json:"mac"`
Model string `json:"model"`
ModelInLts FlexBool `json:"model_in_lts"`
ModelInEol FlexBool `json:"model_in_eol"`
Type string `json:"type"`
Version string `json:"version"`
Adopted FlexBool `json:"adopted"`
SiteID string `json:"site_id"`
Cfgversion string `json:"cfgversion"`
SyslogKey string `json:"syslog_key"`
ConfigNetwork *ConfigNetwork `json:"config_network"`
SetupID string `json:"setup_id"`
LicenseState string `json:"license_state"`
ConfigNetworkLan *ConfigNetworkLan `json:"config_network_lan"`
InformURL string `json:"inform_url"`
InformIP string `json:"inform_ip"`
RequiredVersion string `json:"required_version"`
KernelVersion string `json:"kernel_version"`
Architecture string `json:"architecture"`
BoardRev FlexInt `json:"board_rev"`
ManufacturerID FlexInt `json:"manufacturer_id"`
Internet FlexBool `json:"internet"`
ModelIncompatible FlexBool `json:"model_incompatible"`
EthernetTable []*EthernetTable `json:"ethernet_table"`
PortTable []*Port `json:"port_table"`
EthernetOverrides []*EthernetOverrides `json:"ethernet_overrides"`
UsgCaps FlexInt `json:"usg_caps"`
HasSpeaker FlexBool `json:"has_speaker"`
HasEth1 FlexBool `json:"has_eth1"`
FwCaps FlexInt `json:"fw_caps"`
HwCaps FlexInt `json:"hw_caps"`
WifiCaps FlexInt `json:"wifi_caps"`
SwitchCaps *SwitchCaps `json:"switch_caps"`
HasFan FlexBool `json:"has_fan"`
HasTemperature FlexBool `json:"has_temperature"`
Temperatures []*Temperature `json:"temperatures"`
Storage []*Storage `json:"storage"`
RulesetInterfaces interface{} `json:"ruleset_interfaces"`
ConnectedAt FlexInt `json:"connected_at"`
ProvisionedAt FlexInt `json:"provisioned_at"`
LedOverride string `json:"led_override"`
LedOverrideColor string `json:"led_override_color"`
LedOverrideColorBrightness FlexInt `json:"led_override_color_brightness"`
OutdoorModeOverride string `json:"outdoor_mode_override"`
LcmBrightnessOverride FlexBool `json:"lcm_brightness_override"`
LcmIdleTimeoutOverride FlexBool `json:"lcm_idle_timeout_override"`
Name string `json:"name"`
Unsupported FlexBool `json:"unsupported"`
UnsupportedReason FlexInt `json:"unsupported_reason"`
Serial string `json:"serial"`
HashID string `json:"hash_id"`
TwoPhaseAdopt FlexBool `json:"two_phase_adopt"`
DeviceID string `json:"device_id"`
State FlexInt `json:"state"`
StartDisconnectedMillis FlexInt `json:"start_disconnected_millis"`
UpgradeState FlexInt `json:"upgrade_state"`
StartConnectedMillis FlexInt `json:"start_connected_millis"`
LastSeen FlexInt `json:"last_seen"`
Uptime FlexInt `json:"uptime"`
UnderscoreUptime FlexInt `json:"_uptime"`
Locating FlexBool `json:"locating"`
SysStats *SysStats `json:"sys_stats"`
SystemStats *SystemStats `json:"system-stats"`
GuestKicks FlexInt `json:"guest_kicks"`
GuestToken string `json:"guest_token"`
UptimeStats map[string]*UptimeStats `json:"uptime_stats"`
Overheating FlexBool `json:"overheating"`
GeoInfo map[string]*GeoInfo `json:"geo_info"`
LedState *LedState `json:"led_state"`
SpeedtestStatus *SpeedtestStatus `json:"speedtest-status"`
SpeedtestStatusSaved FlexBool `json:"speedtest-status-saved"`
Wan1 *Wan `json:"wan1"`
Wan2 *Wan `json:"wan2"`
Uplink *Uplink `json:"uplink"`
DownlinkTable []*DownlinkTable `json:"downlink_table"`
NetworkTable []*NetworkTable `json:"network_table"`
KnownCfgversion string `json:"known_cfgversion"`
ConnectRequestIP string `json:"connect_request_ip"`
ConnectRequestPort string `json:"connect_request_port"`
NextInterval FlexInt `json:"next_interval"`
NextHeartbeatAt FlexInt `json:"next_heartbeat_at"`
ConsideredLostAt FlexInt `json:"considered_lost_at"`
Stat *UXGStat `json:"stat"`
TxBytes FlexInt `json:"tx_bytes"`
RxBytes FlexInt `json:"rx_bytes"`
Bytes FlexInt `json:"bytes"`
NumSta FlexInt `json:"num_sta"`
WlanNumSta FlexInt `json:"wlan-num_sta"`
LanNumSta FlexInt `json:"lan-num_sta"`
UserWlanNumSta FlexInt `json:"user-wlan-num_sta"`
UserLanNumSta FlexInt `json:"user-lan-num_sta"`
UserNumSta FlexInt `json:"user-num_sta"`
GuestWlanNumSta FlexInt `json:"guest-wlan-num_sta"`
GuestLanNumSta FlexInt `json:"guest-lan-num_sta"`
GuestNumSta FlexInt `json:"guest-num_sta"`
NumDesktop FlexInt `json:"num_desktop"`
NumMobile FlexInt `json:"num_mobile"`
NumHandheld FlexInt `json:"num_handheld"`
}
// ConfigNetworkLan is part of a UXG, maybe others.
type ConfigNetworkLan struct {
DhcpEnabled FlexBool `json:"dhcp_enabled"`
Vlan int `json:"vlan"`
}
// DownlinkTable is part of a UXG and UDM output.
type DownlinkTable struct {
PortIdx FlexInt `json:"port_idx"`
Speed FlexInt `json:"speed"`
FullDuplex FlexBool `json:"full_duplex"`
Mac string `json:"mac"`
}
// LedState is incuded with newer devices.
type LedState struct {
Pattern string `json:"pattern"`
Tempo FlexInt `json:"tempo"`
}
// GeoInfo is incuded with certain devices.
type GeoInfo struct {
Accuracy FlexInt `json:"accuracy"`
Address string `json:"address"`
Asn FlexInt `json:"asn"`
City string `json:"city"`
ContinentCode string `json:"continent_code"`
CountryCode string `json:"country_code"`
CountryName string `json:"country_name"`
IspName string `json:"isp_name"`
IspOrganization string `json:"isp_organization"`
Latitude FlexInt `json:"latitude"`
Longitude FlexInt `json:"longitude"`
Timezone string `json:"timezone"`
}
// UptimeStats is incuded with certain devices.
type UptimeStats struct {
Availability FlexInt `json:"availability"`
LatencyAverage FlexInt `json:"latency_average"`
TimePeriod FlexInt `json:"time_period"`
}
// UXGStat holds the "stat" data for a 10Gb gateway.
type UXGStat struct {
*Gw `json:"gw"`
*Sw `json:"sw"`
}