package main import ( "bytes" "encoding/json" "fmt" "io" "log" "net/http" "strconv" "strings" "time" client "github.com/influxdata/influxdb1-client/v2" ) // Unifi Controller API processing func processUnifiAPI(s int) ([]node, []link, error) { //get list of Unifi devices to display var nodes []node var links []link d := getDevices(conf.Unifi[s].UCDevicesURL) //call Unifi Controller ucAPI := UnifiNewAPI(conf.Unifi[s].User, conf.Unifi[s].Password, conf.Unifi[s].APIURL) //login if err := ucAPI.ucLogin(); err != nil { return nil, nil, err } //get all Sites from Controller sites, err := ucAPI.ucGetSites() if err != nil { return nil, nil, err } //get all devices in all sites devices, err := ucAPI.ucGetDevices(sites) if err != nil { return nil, nil, err } //build nodes struct //mvJson, err := getMeshviewerJSON() for _, jsonDevice := range d.Devices { var currentDevice ucDevice var currentJSONDevice device for _, device := range devices { if strings.EqualFold(device.Mac, jsonDevice.MAC) { currentDevice = device currentJSONDevice = jsonDevice } } if isRemoteMACpublished(jsonDevice.MAC, d.Devices) { //hier muss gecheckt werden ob der link valide ist if checkMeshviewerLink(jsonDevice.LinkedTo) { links = UnifiAddLink(jsonDevice, links) } } isOnline := currentDevice.State == 1 var load float64 var mem float64 var cpu float64 if isOnline { load, err = strconv.ParseFloat(currentDevice.Sysstats.CPU, 64) cpu = load * 100 if err != nil { log.Println("Error psrsing CPU of device ", currentDevice.Name) log.Println(err) load = 0 cpu = 0 } mem, err = strconv.ParseFloat(currentDevice.Sysstats.Memory, 64) if err != nil { log.Println("Error parsing Memory of device ", currentDevice.Name) log.Println(err) mem = 0 } } var model = lookupModels(currentDevice.Model) var clients int if conf.Unifi[s].DisplayUsers { clients = currentDevice.Users } //// INFLUX START // fields := map[string]interface{}{} fields := make(map[string]any) tags := map[string]string{ "hostname": strings.ReplaceAll(currentDevice.Name, " ", "-"), "nodeid": strings.ReplaceAll(currentDevice.Mac, ":", ""), } // Generate fields for all network interfaces (not availible for Unifi Nodes) //for eth := range details.Interfaces { // interface_name_rx := ("rate.rx" + "_" + details.Interfaces[eth].Identification.Name) // interface_name_tx := ("rate.tx" + "_" + details.Interfaces[eth].Identification.Name) // fields[interface_name_rx] = details.Interfaces[eth].Statistics.Rxrate // fields[interface_name_tx] = details.Interfaces[eth].Statistics.Txrate //} // set default values if we can't get statistics fields["cpu"] = 0 fields["load"] = float64(0) fields["ram"] = 0 if isOnline { // Generate fields for all Statistics //load := (float64(load) / float64(100)) fields["cpu"] = int(cpu) fields["load"] = load fields["ram"] = int(mem) } // Generate field for DHCP Leases fields["clients.total"] = clients fields["time.up"] = currentDevice.Uptime // Generate Dataponts point, err := client.NewPoint( "node", tags, fields, time.Now(), ) if err != nil { log.Fatalln("Error: ", err) } if conf.General.InfluxEnabled { sendInfluxBatchDataPoint(point, conf.General.FreifunkInfluxPort) } // INFLUX STOP //log.Println(currentDevice.Mac) if currentDevice.Mac != "" { nodes = append(nodes, node{ Firstseen: "0", Lastseen: time.Unix(int64(currentDevice.LastSeen), 0).Format(iso8601), IsOnline: itob(currentDevice.State), IsGateway: false, Clients: clients, ClientsWifi24: 0, ClientsWifi5: 0, ClientsOther: clients, RootFSUsage: 0, LoadAVG: load / 100, MemoryUsage: mem / 100, Uptime: time.Now().Add(-1 * time.Second * time.Duration(currentDevice.Uptime)).Format(iso8601), GatewayNexthop: currentJSONDevice.GatewayNexthop, Gateway: currentJSONDevice.Gateway, Location: ¤tJSONDevice.Location, NodeID: strings.ReplaceAll(currentDevice.Mac, ":", ""), MAC: currentDevice.Mac, Adresses: []string{currentDevice.IP}, Domain: currentJSONDevice.Domain, Hostname: "[Unifi] " + currentDevice.Name, Owner: "Freifunk Rhein-Sieg", Firmware: firmware{ Base: "Ubiquiti - Stock", Release: currentDevice.Version, }, Autoupdater: autoupdater{ Enabled: false, Branch: "stable", }, NProc: 1, Model: model, }) } } return nodes, links, err } func UnifiNewAPI(user string, pass string, baseURL string) UnifiAPIData { return UnifiAPIData{ user: user, pass: pass, baseURL: baseURL, client: httpClient(), } } func (u *UnifiAPIData) ucCallAPI(url string, method string, body *bytes.Buffer, output interface{}) error { req, err := http.NewRequest(method, u.baseURL+url, body) if err != nil { return fmt.Errorf("can't set request %s", u.baseURL+url) } req.Header.Set("Content-Type", "application/json") response, err := u.client.Do(req) if err != nil { return fmt.Errorf("can't login %s", u.baseURL+url) } defer response.Body.Close() if response.StatusCode != 200 { return fmt.Errorf("login failed %s", u.baseURL+url) } data, err := io.ReadAll(response.Body) if err != nil { return err } err = json.Unmarshal(data, &output) if err != nil { return err } return nil } func (u *UnifiAPIData) ucLogin() error { var loginData = []byte(`{"username":"` + u.user + `","password":"` + u.pass + `"}`) url := "/api/login" err := u.ucCallAPI(url, http.MethodPost, bytes.NewBuffer(loginData), nil) if err != nil { return err } return nil } func (u *UnifiAPIData) ucGetSites() ([]ucSite, error) { var d struct { Data []ucSite `json:"data"` } url := "/api/self/sites" err := u.ucCallAPI(url, http.MethodGet, bytes.NewBuffer([]byte{}), &d) if err != nil { return []ucSite{}, err } return d.Data, nil } func (u *UnifiAPIData) ucGetDevices(sites []ucSite) ([]ucDevice, error) { var d struct { Data []ucDevice `json:"data"` } var s []ucDevice for _, site := range sites { url := "/api/s/" + site.ID + "/stat/device" u.ucCallAPI(url, http.MethodGet, bytes.NewBuffer([]byte{}), &d) s = append(s, d.Data...) } return s, nil } func UnifiAddLink(dev device, links []link) []link { for i := range links { if links[i].SourceAddr == dev.MAC { // link already exists return links } } if dev.LinkedTo == "" { //no LinkedTo in ucDevices.json return links } links = append(links, link{ Type: "cable", Source: strings.ReplaceAll(dev.MAC, ":", ""), Target: strings.ReplaceAll(dev.GatewayNexthop, ":", ""), SourceTQ: 1, TargetTQ: 1, SourceAddr: dev.MAC, TargetAddr: dev.LinkedTo, }) return links } func findNodeID(NodeID string) bool { for s := range conf.Unifi { ucDev := getDevices(conf.Unifi[s].UCDevicesURL) for i := range ucDev.Devices { if ucDev.Devices[i].GatewayNexthop == NodeID { return true } } } return false }