feat(api): dashboard and general info [ close #125 ]

This commit is contained in:
leandrofars 2023-11-01 01:50:12 -03:00
parent 2d9257f21c
commit 8843cd9dab
7 changed files with 270 additions and 12 deletions

View File

@ -62,7 +62,12 @@ func StartApi(a Api) {
iot.HandleFunc("/{sn}/fw_update", a.deviceFwUpdate).Methods("PUT")
iot.HandleFunc("/{sn}/wifi", a.deviceWifi).Methods("PUT", "GET")
mtp := r.PathPrefix("/api/mtp").Subrouter()
mtp.HandleFunc("/mqtt", a.MqttInfo).Methods("GET")
mtp.HandleFunc("", a.mtpInfo).Methods("GET")
dash := r.PathPrefix("/api/info").Subrouter()
dash.HandleFunc("/vendors", a.vendorsInfo).Methods("GET")
dash.HandleFunc("/status", a.statusInfo).Methods("GET")
dash.HandleFunc("/device_class", a.productClassInfo).Methods("GET")
dash.HandleFunc("/general", a.generalInfo).Methods("GET")
users := r.PathPrefix("/api/users").Subrouter()
users.HandleFunc("", a.retrieveUsers).Methods("GET")
@ -75,6 +80,10 @@ func StartApi(a Api) {
return middleware.Middleware(handler)
})
dash.Use(func(handler http.Handler) http.Handler {
return middleware.Middleware(handler)
})
users.Use(func(handler http.Handler) http.Handler {
return middleware.Middleware(handler)
})

View File

@ -0,0 +1,146 @@
package api
import (
"encoding/json"
"log"
"net"
"net/http"
"time"
"github.com/leandrofars/oktopus/internal/db"
"github.com/leandrofars/oktopus/internal/utils"
)
type StatusCount struct {
Online int
Offline int
}
type GeneralInfo struct {
MqttRtt time.Duration
ProductClassCount []db.ProductClassCount
StatusCount StatusCount
VendorsCount []db.VendorsCount
}
func (a *Api) generalInfo(w http.ResponseWriter, r *http.Request) {
var result GeneralInfo
productclasscount, err := a.Db.RetrieveProductsClassInfo()
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
vendorcount, err := a.Db.RetrieveVendorsInfo()
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
statuscount, err := a.Db.RetrieveStatusInfo()
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
for _, v := range statuscount {
switch v.Status {
case utils.Online:
result.StatusCount.Online = v.Count
case utils.Offline:
result.StatusCount.Offline = v.Count
}
}
result.VendorsCount = vendorcount
result.ProductClassCount = productclasscount
/* ------------ TODO: [mqtt rtt] create common function for this ------------ */
//TODO: address with value from env or something like that
conn, err := net.Dial("tcp", "127.0.0.1:1883")
if err != nil {
json.NewEncoder(w).Encode("Error to connect to broker")
w.WriteHeader(http.StatusInternalServerError)
return
}
defer conn.Close()
info, err := tcpInfo(conn.(*net.TCPConn))
if err != nil {
json.NewEncoder(w).Encode("Error to get TCP socket info")
w.WriteHeader(http.StatusInternalServerError)
return
}
rtt := time.Duration(info.Rtt) * time.Microsecond
/* -------------------------------------------------------------------------- */
result.MqttRtt = rtt / 1000
err = json.NewEncoder(w).Encode(result)
if err != nil {
log.Println(err)
}
return
}
func (a *Api) vendorsInfo(w http.ResponseWriter, r *http.Request) {
vendors, err := a.Db.RetrieveVendorsInfo()
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
err = json.NewEncoder(w).Encode(vendors)
if err != nil {
log.Println(err)
}
return
}
func (a *Api) productClassInfo(w http.ResponseWriter, r *http.Request) {
vendors, err := a.Db.RetrieveProductsClassInfo()
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
err = json.NewEncoder(w).Encode(vendors)
if err != nil {
log.Println(err)
}
return
}
func (a *Api) statusInfo(w http.ResponseWriter, r *http.Request) {
vendors, err := a.Db.RetrieveStatusInfo()
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
var status StatusCount
for _, v := range vendors {
switch v.Status {
case utils.Online:
status.Online = v.Count
case utils.Offline:
status.Offline = v.Count
}
}
err = json.NewEncoder(w).Encode(status)
if err != nil {
log.Println(err)
}
return
}

View File

@ -10,10 +10,10 @@ import (
)
type mqttInfo struct {
Rtt time.Duration
MqttRtt time.Duration
}
func (a *Api) MqttInfo(w http.ResponseWriter, r *http.Request) {
func (a *Api) mtpInfo(w http.ResponseWriter, r *http.Request) {
//TODO: address with value from env or something like that
conn, err := net.Dial("tcp", "127.0.0.1:1883")
if err != nil {
@ -31,7 +31,7 @@ func (a *Api) MqttInfo(w http.ResponseWriter, r *http.Request) {
}
rtt := time.Duration(info.Rtt) * time.Microsecond
json.NewEncoder(w).Encode(mqttInfo{
Rtt: rtt / 1000,
MqttRtt: rtt / 1000,
})
}

View File

@ -23,6 +23,7 @@ type Device struct {
Customer string
Vendor string
Version string
ProductClass string
Status uint8
MTP []map[string]string
}

View File

@ -0,0 +1,97 @@
package db
import (
"log"
"go.mongodb.org/mongo-driver/bson"
)
type VendorsCount struct {
Vendor string `bson:"_id" json:"vendor"`
Count int `bson:"count" json:"count"`
}
type ProductClassCount struct {
ProductClass string `bson:"_id" json:"productClass"`
Count int `bson:"count" json:"count"`
}
type StatusCount struct {
Status int `bson:"_id" json:"status"`
Count int `bson:"count" json:"count"`
}
func (d *Database) RetrieveVendorsInfo() ([]VendorsCount, error) {
var results []VendorsCount
cursor, err := d.devices.Aggregate(d.ctx, []bson.M{
{
"$group": bson.M{
"_id": "$vendor",
"count": bson.M{"$sum": 1},
},
},
})
if err != nil {
log.Println(err)
return nil, err
}
defer cursor.Close(d.ctx)
if err := cursor.All(d.ctx, &results); err != nil {
log.Println(err)
return nil, err
}
for _, result := range results {
log.Println(result)
}
return results, nil
}
func (d *Database) RetrieveStatusInfo() ([]StatusCount, error) {
var results []StatusCount
cursor, err := d.devices.Aggregate(d.ctx, []bson.M{
{
"$group": bson.M{
"_id": "$status",
"count": bson.M{"$sum": 1},
},
},
})
if err != nil {
log.Println(err)
return nil, err
}
defer cursor.Close(d.ctx)
if err := cursor.All(d.ctx, &results); err != nil {
log.Println(err)
return nil, err
}
for _, result := range results {
log.Println(result)
}
return results, nil
}
func (d *Database) RetrieveProductsClassInfo() ([]ProductClassCount, error) {
var results []ProductClassCount
cursor, err := d.devices.Aggregate(d.ctx, []bson.M{
{
"$group": bson.M{
"_id": "$productclass",
"count": bson.M{"$sum": 1},
},
},
})
if err != nil {
log.Println(err)
return nil, err
}
defer cursor.Close(d.ctx)
if err := cursor.All(d.ctx, &results); err != nil {
log.Println(err)
return nil, err
}
for _, result := range results {
log.Println(result)
}
return results, nil
}

View File

@ -1,11 +1,13 @@
package db
import (
"log"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"log"
)
// TODO: fix this function to also change device status at different mtp
func (d *Database) UpdateStatus(sn string, status uint8) error {
var result bson.M
err := d.devices.FindOneAndUpdate(d.ctx, bson.D{{"sn", sn}}, bson.D{{"$set", bson.D{{"status", status}}}}).Decode(&result)

View File

@ -247,6 +247,7 @@ func (m *Mqtt) handleNewDevice(deviceMac string) {
"Device.DeviceInfo.ModelName",
"Device.DeviceInfo.SoftwareVersion",
"Device.DeviceInfo.SerialNumber",
"Device.DeviceInfo.ProductClass",
},
MaxDepth: 1,
},
@ -284,6 +285,7 @@ func (m *Mqtt) handleNewDevicesResponse(p []byte, sn string) {
device.Vendor = msg.ReqPathResults[0].ResolvedPathResults[0].ResultParams["Manufacturer"]
device.Model = msg.ReqPathResults[1].ResolvedPathResults[0].ResultParams["ModelName"]
device.Version = msg.ReqPathResults[2].ResolvedPathResults[0].ResultParams["SoftwareVersion"]
device.ProductClass = msg.ReqPathResults[4].ResolvedPathResults[0].ResultParams["ProductClass"]
device.SN = sn
mtp := map[string]string{
@ -291,6 +293,7 @@ func (m *Mqtt) handleNewDevicesResponse(p []byte, sn string) {
}
device.MTP = append(device.MTP, mtp)
device.Status = utils.Online
err = m.DB.CreateDevice(device)
if err != nil {