feat(controller): remove enterprise switch + feat: filter devices

This commit is contained in:
leandrofars 2024-09-05 10:05:57 -03:00
parent 32acb8e9ad
commit badeaf9820
13 changed files with 1197 additions and 481 deletions

View File

@ -24,7 +24,6 @@ type Api struct {
db db.Database
kv jetstream.KeyValue
ctx context.Context
enterpise config.Enterprise
}
const REQUEST_TIMEOUT = time.Second * 30
@ -38,16 +37,10 @@ func NewApi(c *config.Config, js jetstream.JetStream, nc *nats.Conn, bridge brid
bridge: bridge,
db: d,
kv: kv,
enterpise: c.Enterprise,
}
}
func (a *Api) StartApi() {
if a.enterpise.SupportPassword != "" && a.enterpise.SupportEmail != "" {
go registerEnterpriseSupport(a.enterpise.SupportEmail, a.enterpise.SupportPassword, a.db)
}
r := mux.NewRouter()
authentication := r.PathPrefix("/api/auth").Subrouter()
authentication.HandleFunc("/login", a.generateToken).Methods("PUT")
@ -57,13 +50,6 @@ func (a *Api) StartApi() {
authentication.HandleFunc("/password", a.changePassword).Methods("PUT")
authentication.HandleFunc("/admin/register", a.registerAdminUser).Methods("POST")
authentication.HandleFunc("/admin/exists", a.adminUserExists).Methods("GET")
if a.enterpise.Enable {
mapRoutes := r.PathPrefix("/api/map").Subrouter()
mapRoutes.HandleFunc("", a.devicesLocation).Methods("GET")
mapRoutes.Use(func(handler http.Handler) http.Handler {
return middleware.Middleware(handler)
})
}
iot := r.PathPrefix("/api/device").Subrouter()
iot.HandleFunc("/alias", a.setDeviceAlias).Methods("PUT")
iot.HandleFunc("/auth", a.deviceAuth).Methods("GET", "POST", "DELETE")
@ -74,7 +60,7 @@ func (a *Api) StartApi() {
iot.HandleFunc("/cwmp/{sn}/addObject", a.cwmpAddObjectMsg).Methods("PUT")
iot.HandleFunc("/cwmp/{sn}/deleteObject", a.cwmpDeleteObjectMsg).Methods("PUT")
iot.HandleFunc("", a.retrieveDevices).Methods("GET")
iot.HandleFunc("/{id}", a.retrieveDevices).Methods("GET")
iot.HandleFunc("/filterOptions", a.filterOptions).Methods("GET")
iot.HandleFunc("/{sn}/{mtp}/get", a.deviceGetMsg).Methods("PUT")
iot.HandleFunc("/{sn}/{mtp}/add", a.deviceCreateMsg).Methods("PUT")
iot.HandleFunc("/{sn}/{mtp}/del", a.deviceDeleteMsg).Methods("PUT")
@ -84,13 +70,6 @@ func (a *Api) StartApi() {
iot.HandleFunc("/{sn}/{mtp}/instances", a.deviceGetParameterInstances).Methods("PUT")
iot.HandleFunc("/{sn}/{mtp}/operate", a.deviceOperateMsg).Methods("PUT")
iot.HandleFunc("/{sn}/{mtp}/fw_update", a.deviceFwUpdate).Methods("PUT") //TODO: put it to work and generalize for usp and cwmp
if a.enterpise.Enable {
iot.HandleFunc("/{sn}/sitesurvey", a.deviceSiteSurvey).Methods("GET")
iot.HandleFunc("/{sn}/connecteddevices", a.deviceConnectedDevices).Methods("GET")
iot.HandleFunc("/{sn}/traceroute", a.deviceTraceRoute).Methods("GET", "PUT")
iot.HandleFunc("/{sn}/speedtest", a.deviceSpeedTest).Methods("PUT")
iot.HandleFunc("/{sn}/ping", a.devicePing).Methods("PUT", "GET")
}
iot.HandleFunc("/{sn}/wifi", a.deviceWifi).Methods("PUT", "GET")
dash := r.PathPrefix("/api/info").Subrouter()
dash.HandleFunc("/vendors", a.vendorsInfo).Methods("GET")
@ -131,32 +110,3 @@ func (a *Api) StartApi() {
}()
log.Println("Running REST API at port", a.port)
}
func registerEnterpriseSupport(email, password string, d db.Database) {
user := db.User{
Email: email,
Password: password,
Name: "Enterprise Support",
Level: db.AdminUser,
}
for {
if err := user.HashPassword(password); err != nil {
return
}
err := d.RegisterUser(user)
if err != nil {
if err == db.ErrorUserExists {
log.Println("Enterprise support user already registered.")
return
}
log.Println("Error to register enterprise support user:", err)
time.Sleep(time.Second * 5)
continue
}
log.Println("Enterprise support user registered successfully.")
return
}
}

View File

@ -6,9 +6,11 @@ import (
"log"
"net/http"
"strconv"
"strings"
"github.com/leandrofars/oktopus/internal/bridge"
"github.com/leandrofars/oktopus/internal/db"
"github.com/leandrofars/oktopus/internal/entity"
local "github.com/leandrofars/oktopus/internal/nats"
"github.com/leandrofars/oktopus/internal/utils"
"github.com/nats-io/nats.go/jetstream"
@ -16,6 +18,33 @@ import (
)
func (a *Api) retrieveDevices(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodDelete {
id := r.URL.Query().Get("id")
if id == "" {
w.WriteHeader(http.StatusBadRequest)
err := json.NewEncoder(w).Encode("No id provided")
if err != nil {
log.Println(err)
}
return
}
ids := strings.Split(id, ",")
msg, err := bridge.NatsReq[int64](local.NATS_ADAPTER_SUBJECT+"devices.delete", utils.Marshall(ids), w, a.nc)
if err != nil {
return
}
err = json.NewEncoder(w).Encode(map[string]interface{}{
"number_of_deleted_devices": msg.Msg,
})
if err != nil {
log.Println(err)
}
return
}
const PAGE_SIZE_LIMIT = 50
const PAGE_SIZE_DEFAULT = 20
@ -33,9 +62,36 @@ func (a *Api) retrieveDevices(w http.ResponseWriter, r *http.Request) {
return
}
statusOrderFromUser := r.URL.Query().Get("statusOrder")
var statusOrder int
if statusOrderFromUser != "" {
if statusOrderFromUser == "asc" {
statusOrder = 1
} else if statusOrderFromUser == "desc" {
statusOrder = -1
} else {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode("Status order must be 'asc' or 'desc'")
return
}
} else {
statusOrder = 1
}
sort := bson.M{}
sort["status"] = statusOrder
version := r.URL.Query().Get("version")
vendor := r.URL.Query().Get("vendor")
productClass := r.URL.Query().Get("type")
alias := r.URL.Query().Get("alias")
model := r.URL.Query().Get("model")
status := r.URL.Query().Get("status")
// Get devices with pagination
page_n := r.URL.Query().Get("page_number")
page_s := r.URL.Query().Get("page_size")
var err error
var page_number int64
@ -70,52 +126,69 @@ func (a *Api) retrieveDevices(w http.ResponseWriter, r *http.Request) {
page_size = PAGE_SIZE_DEFAULT
}
total, err := getDeviceCount(w, a.nc)
if err != nil {
return
}
skip := page_number * (page_size - 1)
if total < page_size {
skip = 0
filter := map[string]interface{}{
"status_order": statusOrder,
"limit": page_size,
"skip": skip,
}
//TODO: fix status ordering
statusOrder := r.URL.Query().Get("status")
if statusOrder != "" {
if statusOrder == "asc" {
statusOrder = "1"
} else if statusOrder == "desc" {
statusOrder = "-1"
} else {
if version != "" {
filter["version"] = version
}
if vendor != "" {
filter["vendor"] = vendor
}
if productClass != "" {
filter["productClass"] = productClass
}
if alias != "" {
filter["alias"] = alias
}
if model != "" {
filter["model"] = model
}
if status != "" {
fmtStatus, err := strconv.Atoi(status)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode("Status order must be 'asc' or 'desc'")
json.NewEncoder(w).Encode("Status must be an integer")
return
}
}
sort := bson.M{}
sort["status"] = 1
//TODO: Create filters
filter := bson.A{
//bson.M{"$match": filter},
bson.M{"$sort": sort}, // shows online devices first
bson.M{"$skip": skip},
bson.M{"$limit": page_size},
filter["status"] = fmtStatus
}
devices, err := getDevices(w, filter, a.nc)
if err != nil {
log.Println("Error getting devices", err)
return
}
if devices.Total == 0 {
w.WriteHeader(http.StatusNotFound)
err := json.NewEncoder(w).Encode("No devices found")
if err != nil {
log.Println(err)
}
return
}
if skip >= devices.Total {
w.WriteHeader(http.StatusBadRequest)
err := json.NewEncoder(w).Encode("Page number is out of range")
if err != nil {
log.Println(err)
}
return
}
err = json.NewEncoder(w).Encode(map[string]interface{}{
"pages": total / page_size,
"pages": devices.Total / page_size,
"page": page_number,
"size": page_size,
"devices": devices,
"devices": devices.Devices,
"total": devices.Total,
})
if err != nil {
log.Println(err)
@ -283,3 +356,14 @@ func (a *Api) setDeviceAlias(w http.ResponseWriter, r *http.Request) {
return
}
}
func (a *Api) filterOptions(w http.ResponseWriter, r *http.Request) {
resp, err := bridge.NatsReq[entity.FilterOptions](local.NATS_ADAPTER_SUBJECT+"devices.filterOptions", nil, w, a.nc)
if err != nil {
return
}
w.WriteHeader(resp.Code)
w.Write(utils.Marshall(resp.Msg))
}

View File

@ -1,205 +0,0 @@
package api
import (
"io"
"net/http"
"github.com/gorilla/mux"
"github.com/leandrofars/oktopus/internal/bridge"
"github.com/leandrofars/oktopus/internal/entity"
"github.com/leandrofars/oktopus/internal/utils"
)
func (a *Api) getEnterpriseResource(
resource string,
action string,
device *entity.Device,
sn string,
w http.ResponseWriter,
body []byte,
protocol, datamodel string,
) error {
model, err := cwmpGetDeviceModel(device, w)
if err != nil {
return err
}
err = bridge.NatsEnterpriseInteraction("enterprise.v1."+protocol+"."+datamodel+"."+model+"."+sn+"."+resource+"."+action, body, w, a.nc)
return err
}
func (a *Api) getMapsResource(
action string,
w http.ResponseWriter,
body []byte,
) error {
err := bridge.NatsEnterpriseInteraction("geolocation.v1."+action, body, w, a.nc)
return err
}
func (a *Api) devicesLocation(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
a.getMapsResource("get", w, []byte{})
}
}
func (a *Api) deviceSiteSurvey(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
sn := vars["sn"]
device, err := getDeviceInfo(w, sn, a.nc)
if err != nil {
return
}
if r.Method == http.MethodGet {
if device.Cwmp == entity.Online {
a.getEnterpriseResource("sitesurvey", "get", device, sn, w, []byte{}, "cwmp", "098")
return
}
if device.Mqtt == entity.Online || device.Stomp == entity.Online || device.Websockets == entity.Online {
w.WriteHeader(http.StatusNotImplemented)
w.Write(utils.Marshall("This feature is only working with CWMP devices"))
return
}
w.WriteHeader(http.StatusBadRequest)
w.Write(utils.Marshall("Device is Offline"))
}
}
func (a *Api) deviceConnectedDevices(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
sn := vars["sn"]
device, err := getDeviceInfo(w, sn, a.nc)
if err != nil {
return
}
if r.Method == http.MethodGet {
if device.Cwmp == entity.Online {
a.getEnterpriseResource("connecteddevices", "get", device, sn, w, []byte{}, "cwmp", "098")
return
}
if device.Mqtt == entity.Online || device.Stomp == entity.Online || device.Websockets == entity.Online {
w.WriteHeader(http.StatusNotImplemented)
w.Write(utils.Marshall("This feature is only working with CWMP devices"))
return
}
w.WriteHeader(http.StatusBadRequest)
w.Write(utils.Marshall("Device is Offline"))
}
}
func (a *Api) deviceTraceRoute(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
sn := vars["sn"]
device, err := getDeviceInfo(w, sn, a.nc)
if err != nil {
return
}
if r.Method == http.MethodGet {
if device.Cwmp == entity.Online {
w.WriteHeader(http.StatusNotImplemented)
w.Write(utils.Marshall("Get traceroute configuration is not implemented yet"))
}
}
if r.Method == http.MethodPut {
if device.Cwmp == entity.Online {
a.getEnterpriseResource("traceroute", "set", device, sn, w, []byte{}, "cwmp", "098")
return
}
}
if device.Mqtt == entity.Online || device.Stomp == entity.Online || device.Websockets == entity.Online {
w.WriteHeader(http.StatusNotImplemented)
w.Write(utils.Marshall("This feature is only working with CWMP devices"))
return
}
w.WriteHeader(http.StatusBadRequest)
w.Write(utils.Marshall("Device is Offline"))
}
func (a *Api) deviceSpeedTest(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
sn := vars["sn"]
device, err := getDeviceInfo(w, sn, a.nc)
if err != nil {
return
}
payload, err := io.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write(utils.Marshall("Error reading request body"))
return
}
if device.Cwmp == entity.Online {
a.getEnterpriseResource("speedTest", "set", device, sn, w, payload, "cwmp", "098")
return
}
if device.Mqtt == entity.Online || device.Stomp == entity.Online || device.Websockets == entity.Online {
w.WriteHeader(http.StatusNotImplemented)
w.Write(utils.Marshall("This feature is only working with CWMP devices"))
return
}
w.WriteHeader(http.StatusBadRequest)
w.Write(utils.Marshall("Device is Offline"))
}
func (a *Api) devicePing(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
sn := vars["sn"]
device, err := getDeviceInfo(w, sn, a.nc)
if err != nil {
return
}
if device.Cwmp != entity.Online {
w.WriteHeader(http.StatusBadRequest)
w.Write(utils.Marshall("Device is Offline"))
}
if r.Method == http.MethodGet {
if device.Cwmp == entity.Online {
a.getEnterpriseResource("ping", "get", device, sn, w, []byte{}, "cwmp", "098")
return
}
} else {
payload, err := io.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write(utils.Marshall("Error reading request body"))
return
}
if device.Cwmp == entity.Online {
a.getEnterpriseResource("ping", "set", device, sn, w, payload, "cwmp", "098")
return
}
}
if device.Mqtt == entity.Online || device.Stomp == entity.Online || device.Websockets == entity.Online {
w.WriteHeader(http.StatusNotImplemented)
w.Write(utils.Marshall("This feature is only working with CWMP devices"))
return
}
}

View File

@ -176,7 +176,7 @@ func (a *Api) registerAdminUser(w http.ResponseWriter, r *http.Request) {
utils.MarshallEncoder(err, w)
}
if !adminUserExists(users, a.enterpise.SupportEmail) {
if !adminUserExists(users) {
var user db.User
err = json.NewDecoder(r.Body).Decode(&user)
if err != nil {
@ -235,14 +235,14 @@ func (a *Api) registerAdminUser(w http.ResponseWriter, r *http.Request) {
}
}
func adminUserExists(users []map[string]interface{}, supportEmail string) bool {
func adminUserExists(users []map[string]interface{}) bool {
if len(users) == 0 {
return false
}
for _, x := range users {
if db.UserLevels(x["level"].(int32)) == db.AdminUser && x["email"].(string) != supportEmail {
if db.UserLevels(x["level"].(int32)) == db.AdminUser {
return true
}
}
@ -257,7 +257,7 @@ func (a *Api) adminUserExists(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
return
}
adminExits := adminUserExists(users, a.enterpise.SupportEmail)
adminExits := adminUserExists(users)
json.NewEncoder(w).Encode(adminExits)
return
}

View File

@ -10,7 +10,6 @@ import (
local "github.com/leandrofars/oktopus/internal/nats"
"github.com/leandrofars/oktopus/internal/utils"
"github.com/nats-io/nats.go"
"go.mongodb.org/mongo-driver/bson/primitive"
)
var errInvalidMtp = errors.New("Invalid MTP, valid options are: mqtt, ws, stomp")
@ -105,8 +104,8 @@ func getDeviceCount(w http.ResponseWriter, nc *nats.Conn) (int64, error) {
return msg.Msg, err
}
func getDevices(w http.ResponseWriter, filter primitive.A, nc *nats.Conn) (*[]entity.Device, error) {
msg, err := bridge.NatsReq[[]entity.Device](
func getDevices(w http.ResponseWriter, filter map[string]interface{}, nc *nats.Conn) (*entity.DevicesList, error) {
msg, err := bridge.NatsReq[entity.DevicesList](
local.NATS_ADAPTER_SUBJECT+"devices.retrieve",
utils.Marshall(filter),
w,

View File

@ -1,7 +1,6 @@
package api
import (
"io"
"log"
"net/http"
"strings"
@ -213,11 +212,6 @@ func (a *Api) deviceWifi(w http.ResponseWriter, r *http.Request) {
if device.Cwmp == entity.Online {
if a.enterpise.Enable {
a.getEnterpriseResource("wifi", "get", device, sn, w, []byte{}, "cwmp", "098")
return
}
var (
NUMBER_OF_WIFI_PARAMS_TO_GET = 5
)
@ -347,17 +341,6 @@ func (a *Api) deviceWifi(w http.ResponseWriter, r *http.Request) {
if device.Cwmp == entity.Online {
if a.enterpise.Enable {
payload, err := io.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write(utils.Marshall(err.Error()))
return
}
a.getEnterpriseResource("wifi", "set", device, sn, w, payload, "cwmp", "098")
return
}
var body []WiFi
err := utils.MarshallDecoder(&body, r.Body)

View File

@ -153,7 +153,6 @@ func NatsReq[T entity.DataType](
err = json.Unmarshal(msg.Data, &answer)
if err != nil {
var errMsg *entity.MsgAnswer[*string]
err = json.Unmarshal(msg.Data, &errMsg)

View File

@ -30,17 +30,10 @@ type RestApi struct {
Ctx context.Context
}
type Enterprise struct {
Enable bool
SupportPassword string
SupportEmail string
}
type Config struct {
RestApi RestApi
Nats Nats
Mongo Mongo
Enterprise Enterprise
}
type Tls struct {
@ -62,9 +55,6 @@ func NewConfig() *Config {
serverCA := flag.String("server_ca", lookupEnvOrString("SERVER_CA", "rootCA.pem"), "server CA file to TLS connection")
flApiPort := flag.String("api_port", lookupEnvOrString("REST_API_PORT", "8000"), "Rest api port")
mongoUri := flag.String("mongo_uri", lookupEnvOrString("MONGO_URI", "mongodb://localhost:27017"), "uri for mongodb server")
enterpise := flag.Bool("enterprise", lookupEnvOrBool("ENTERPRISE", false), "enterprise version enable")
enterprise_support_password := flag.String("enterprise_support_password", lookupEnvOrString("ENTERPRISE_SUPPORT_PASSWORD", ""), "enterprise support password")
enterpise_support_email := flag.String("enterprise_support_email", lookupEnvOrString("ENTERPRISE_SUPPORT_EMAIL", ""), "enterprise support email")
flHelp := flag.Bool("help", false, "Help")
/*
@ -103,11 +93,6 @@ func NewConfig() *Config {
Uri: *mongoUri,
Ctx: ctx,
},
Enterprise: Enterprise{
Enable: *enterpise,
SupportPassword: *enterprise_support_password,
SupportEmail: *enterpise_support_email,
},
}
}

View File

@ -29,3 +29,15 @@ type StatusCount struct {
Status int `bson:"_id" json:"status"`
Count int `bson:"count" json:"count"`
}
type DevicesList struct {
Devices []Device `json:"devices" bson:"documents"`
Total int64 `json:"total"`
}
type FilterOptions struct {
Models []string `json:"models"`
ProductClasses []string `json:"productClasses"`
Vendors []string `json:"vendors"`
Versions []string `json:"versions"`
}

View File

@ -3,7 +3,7 @@ package entity
import "time"
type DataType interface {
[]map[string]interface{} | *string | Device | int64 | []Device | []VendorsCount | []ProductClassCount | []StatusCount | time.Duration | []byte
[]map[string]interface{} | *string | Device | int64 | []Device | []VendorsCount | []ProductClassCount | []StatusCount | time.Duration | []byte | []string | FilterOptions | DevicesList
}
type MsgAnswer[T DataType] struct {

View File

@ -4,6 +4,7 @@ import (
"log"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
@ -41,6 +42,18 @@ type Device struct {
Cwmp Status
}
type DevicesList struct {
Devices []Device `json:"devices" bson:"documents"`
Total int64 `json:"total" bson:"totalCount"`
}
type FilterOptions struct {
Models []string `json:"models"`
ProductClasses []string `json:"productClasses"`
Vendors []string `json:"vendors"`
Versions []string `json:"versions"`
}
func (d *Database) CreateDevice(device Device) error {
var result bson.M
var deviceExistent Device
@ -100,32 +113,86 @@ func (d *Database) CreateDevice(device Device) error {
}
return err
}
func (d *Database) RetrieveDevices(filter bson.A) ([]Device, error) {
func (d *Database) RetrieveDevices(filter bson.A) (*DevicesList, error) {
var results []Device
var results []DevicesList
cursor, err := d.devices.Aggregate(d.ctx, filter)
if err != nil {
return results, err
return nil, err
}
if cursor.Err() != nil {
return results, cursor.Err()
return nil, cursor.Err()
}
defer cursor.Close(d.ctx)
if err := cursor.All(d.ctx, &results); err != nil {
log.Println(err)
return nil, err
}
for cursor.Next(d.ctx) {
var device Device
//log.Printf("results: %++v", results)
err := cursor.Decode(&device)
return &results[0], err
}
func (d *Database) RetrieveDeviceFilterOptions() (FilterOptions, error) {
filter := bson.A{
bson.D{
{"$group",
bson.D{
{"_id", primitive.Null{}},
{"vendors", bson.D{{"$addToSet", "$vendor"}}},
{"versions", bson.D{{"$addToSet", "$version"}}},
{"productClasses", bson.D{{"$addToSet", "$productclass"}}},
{"models", bson.D{{"$addToSet", "$model"}}},
},
},
},
bson.D{
{"$project",
bson.D{
{"_id", 0},
{"vendors", 1},
{"versions", 1},
{"productClasses", 1},
{"models", 1},
},
},
},
}
var results []FilterOptions
cursor, err := d.devices.Aggregate(d.ctx, filter)
if err != nil {
log.Println("Error to decode device info fields")
continue
log.Println(err)
return FilterOptions{}, err
}
defer cursor.Close(d.ctx)
if err := cursor.All(d.ctx, &results); err != nil {
log.Println(err)
return FilterOptions{}, err
}
results = append(results, device)
if len(results) > 0 {
return results[0], nil
} else {
return FilterOptions{
Models: []string{},
ProductClasses: []string{},
Vendors: []string{},
Versions: []string{},
}, nil
}
}
return results, err
func (d *Database) DeleteDevices(filter bson.D) (int64, error) {
result, err := d.devices.DeleteMany(d.ctx, filter)
if err != nil {
log.Println(err)
}
return result.DeletedCount, err
}
func (d *Database) RetrieveDevice(sn string) (Device, error) {

View File

@ -52,18 +52,135 @@ func StartRequestsListener(ctx context.Context, nc *nats.Conn, db db.Database) {
nc.QueueSubscribe(local.ADAPTER_SUBJECT+"devices.retrieve", local.ADAPTER_QUEUE, func(msg *nats.Msg) {
var filter bson.A
var criteria map[string]interface{}
err := json.Unmarshal(msg.Data, &filter)
err := json.Unmarshal(msg.Data, &criteria)
if err != nil {
respondMsg(msg.Respond, 500, err.Error())
}
//log.Println(criteria)
propertiesFilter := bson.D{{}}
vendorFilter := criteria["vendor"]
if vendorFilter != nil {
log.Println("Vendor filter", vendorFilter)
propertiesFilter = append(propertiesFilter, bson.E{Key: "vendor", Value: vendorFilter})
}
versionFilter := criteria["version"]
if versionFilter != nil {
log.Println("Version filter", versionFilter)
propertiesFilter = append(propertiesFilter, bson.E{Key: "version", Value: versionFilter})
}
typeFilter := criteria["productClass"]
if typeFilter != nil {
log.Println("Type filter", typeFilter)
propertiesFilter = append(propertiesFilter, bson.E{Key: "productclass", Value: typeFilter})
}
aliasFilter := criteria["alias"]
if aliasFilter != nil {
log.Println("Type filter", aliasFilter)
propertiesFilter = append(propertiesFilter, bson.E{Key: "alias", Value: aliasFilter})
}
modelFilter := criteria["model"]
if modelFilter != nil {
log.Println("Model filter", modelFilter)
propertiesFilter = append(propertiesFilter, bson.E{Key: "model", Value: modelFilter})
}
statusFilter := criteria["status"]
if statusFilter != nil {
log.Println("Status filter", statusFilter)
propertiesFilter = append(propertiesFilter, bson.E{Key: "status", Value: statusFilter})
}
filter := bson.A{
bson.D{
{"$match",
propertiesFilter,
},
},
bson.D{
{"$facet",
bson.D{
{"totalCount",
bson.A{
bson.D{{"$count", "count"}},
},
},
{"documents",
bson.A{
bson.D{{"$sort", bson.D{{"status", criteria["status_order"]}}}},
bson.D{{"$skip", criteria["skip"]}},
bson.D{{"$limit", criteria["limit"]}},
},
},
},
},
},
bson.D{
{"$project",
bson.D{
{"totalCount",
bson.D{
{"$arrayElemAt",
bson.A{
"$totalCount.count",
0,
},
},
},
},
{"documents", 1},
},
},
},
}
devicesList, err := db.RetrieveDevices(filter)
if err != nil {
respondMsg(msg.Respond, 500, err.Error())
}
respondMsg(msg.Respond, 200, devicesList)
respondMsg(msg.Respond, 200, &devicesList)
})
nc.QueueSubscribe(local.ADAPTER_SUBJECT+"devices.delete", local.ADAPTER_QUEUE, func(msg *nats.Msg) {
var serialNumbersList []string
err := json.Unmarshal(msg.Data, &serialNumbersList)
if err != nil {
respondMsg(msg.Respond, 500, err.Error())
}
var criteria bson.A
for _, sn := range serialNumbersList {
criteria = append(criteria, bson.D{{"sn", sn}})
}
// Create the filter with the $or operator
filter := bson.D{
{"$or", criteria},
}
deletedCount, err := db.DeleteDevices(filter)
if err != nil {
respondMsg(msg.Respond, 500, err.Error())
}
respondMsg(msg.Respond, 200, deletedCount)
})
nc.QueueSubscribe(local.ADAPTER_SUBJECT+"devices.filterOptions", local.ADAPTER_QUEUE, func(msg *nats.Msg) {
result, err := db.RetrieveDeviceFilterOptions()
if err != nil {
respondMsg(msg.Respond, 500, err.Error())
}
respondMsg(msg.Respond, 200, result)
})
nc.QueueSubscribe(local.ADAPTER_SUBJECT+"devices.class", local.ADAPTER_QUEUE, func(msg *nats.Msg) {

View File

@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react';
import Head from 'next/head';
import {
Box,
Container,
@ -10,25 +11,197 @@ import {
SvgIcon,
Stack,
Pagination,
CircularProgress
CircularProgress,
Button,
Menu,
MenuItem,
Checkbox,
ListItemText,
SpeedDial,
SpeedDialAction,
SpeedDialIcon,
CardHeader,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
FormControl,
Tooltip,
Chip,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
InputLabel,
Input,
TextField,
Select,
DialogContentText,
TableContainer,
TablePagination
} from '@mui/material';
import ViewColumnsIcon from '@heroicons/react/24/outline/ViewColumnsIcon';
import ArrowTopRightOnSquareIcon from '@heroicons/react/24/solid/ArrowTopRightOnSquareIcon';
import FunnelIcon from "@heroicons/react/24/outline/FunnelIcon";
import PencilIcon from '@heroicons/react/24/outline/PencilIcon';
import TagIcon from '@heroicons/react/24/outline/TagIcon';
import ShareIcon from '@heroicons/react/24/outline/ShareIcon';
import CommandLineIcon from '@heroicons/react/24/outline/CommandLineIcon';
import ChevronUpIcon from '@heroicons/react/24/outline/ChevronUpIcon';
import ChevronDownIcon from '@heroicons/react/24/outline/ChevronDownIcon';
import MagnifyingGlassIcon from '@heroicons/react/24/solid/MagnifyingGlassIcon';
import TrashIcon from '@heroicons/react/24/outline/TrashIcon';
import { Scrollbar } from 'src/components/scrollbar';
import { Layout as DashboardLayout } from 'src/layouts/dashboard/layout';
import { OverviewLatestOrders } from 'src/sections/overview/overview-latest-orders';
import { useAuth } from 'src/hooks/use-auth';
import { useRouter } from 'next/router';
import { useTheme } from '@emotion/react';
const Page = () => {
const theme = useTheme();
const router = useRouter()
const auth = useAuth();
const [devices, setDevices] = useState([]);
const [total, setTotal] = useState(null);
const [deviceFound, setDeviceFound] = useState(true)
const [pages, setPages] = useState(0);
const [page, setPage] = useState(null);
const [page, setPage] = useState(0);
const [Loading, setLoading] = useState(true);
const [anchorEl, setAnchorEl] = useState(null);
const [statusOrder, setStatusOrder] = useState("desc");
const [filterOptions, setFilterOptions] = useState(null);
const defaultFiltersList = {
alias: "",
model: "",
vendor: "",
version: "",
status: "",
type: "",
}
const [filtersList, setFiltersList] = useState(defaultFiltersList);
const [newFiltersList, setNewFiltersList] = useState(defaultFiltersList);
const cleanFilters = () => {
setFiltersList(defaultFiltersList)
}
const rowsPerPageOptions = [20,30,40];
const [rowsPerPage, setRowsPerPage] = useState(20);
const [showSetDeviceAlias, setShowSetDeviceAlias] = useState(false);
const [deviceAlias, setDeviceAlias] = useState(null);
const [deviceToBeChanged, setDeviceToBeChanged] = useState(null);
const [showFilter, setShowFilter] = useState(false);
const [selected, setSelected] = useState([]);
const [selectAll, setSelectAll] = useState(false);
const [columns, setColumns] = useState({
version: true,
sn: true,
alias: true,
model: true,
vendor: true,
status: true,
actions: true,
label: false
});
const [showSpeedDial, setShowSpeedDial] = useState(false);
const getColumns = () => {
localStorage.getItem("columns") ? setColumns(JSON.parse(localStorage.getItem("columns"))) : setColumns({
version: true,
sn: true,
alias: false,
model: true,
vendor: true,
status: true,
actions: true,
label: false
})
}
const changeColumn = (column) => {
console.log("columns old:", columns)
setColumns({ ...columns, [column]: !columns[column] })
localStorage.setItem("columns", JSON.stringify({ ...columns, [column]: !columns[column] }))
}
function objsEqual(obj1,obj2){
return JSON.stringify(obj1)===JSON.stringify(obj2);
}
useEffect(() => {
if (selected.length > 0) {
let speedDial = false
selected.map((s) => {
if (s == true) {
speedDial = true
} else if (s == false) {
setSelectAll(false)
}
})
setShowSpeedDial(speedDial)
return
}
}, [selected])
const statusMap = {
1: 'warning',
2: 'success',
0: 'error'
};
const status = (s) => {
if (s == 0) {
return "Offline"
} else if (s == 1) {
return "Associating"
} else if (s == 2) {
return "Online"
} else {
return "Unknown"
}
}
const getDeviceProtocol = (order) => {
if (order.Cwmp != null) {
return "cwmp"
} else {
return "usp"
}
}
const open = Boolean(anchorEl);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const actions = [
/*{ icon: <SvgIcon><PencilIcon /></SvgIcon>, name: 'Alias', onClickEvent: () => {
console.log("edit device alias")
// setDeviceToBeChanged(selected.indexOf(true))
// setDeviceAlias(orders[selected.indexOf(true)].Alias)
// setShowSetDeviceAlias(true)
} },*/
//{ icon: <SvgIcon><TagIcon /></SvgIcon>, name: 'Label' },
//{ icon: <SvgIcon><ShareIcon /></SvgIcon>, name: 'Share' },
{ icon: <SvgIcon><TrashIcon /></SvgIcon>, name: 'Remove' },
{ icon: <SvgIcon><CommandLineIcon /></SvgIcon>, name: 'Action' },
];
useEffect(() => {
getColumns()
setLoading(true)
if (auth.user.token) {
console.log("auth.user.token =", auth.user.token)
@ -46,32 +219,102 @@ const Page = () => {
redirect: 'follow'
}
fetch(`${process.env.NEXT_PUBLIC_REST_ENDPOINT || ""}/api/device`, requestOptions)
let status;
fetch(`${process.env.NEXT_PUBLIC_REST_ENDPOINT || ""}/api/device?statusOrder=${statusOrder}&page_number=${page}&page_size=${rowsPerPage}&vendor=${filtersList["vendor"]}&version=${filtersList["version"]}&alias=${filtersList["alias"]}&type=${filtersList["type"]}&status=${filtersList["status"]}&model=${filtersList["model"]}`, requestOptions)
.then(response => {
if (response.status === 401)
if (response.status === 401){
router.push("/auth/login")
}
status = response.status
return response.json()
})
.then(json => {
if (status == 404) {
console.log("device not found")
setLoading(false)
setDeviceFound(false)
return
}
console.log("Status:", status)
setPages(json.pages + 1)
setPage(json.page + 1)
setTotal(json.total)
setDevices(json.devices)
setSelected(new Array(json.devices.length).fill(false))
setLoading(false)
return setDeviceFound(true)
})
.catch(error => {
return console.error('Error:', error)
});
fetch(`${process.env.NEXT_PUBLIC_REST_ENDPOINT || ""}/api/device/filterOptions`, requestOptions)
.then(response => {
if (response.status === 401)
router.push("/auth/login")
return response.json()
})
.then(json => {
return setFilterOptions(json)
})
.catch(error => {
return console.error('Error:', error)
});
}, [auth.user]);
const handleChangePage = (event, value) => {
console.log("new page: ", value)
setPage(value)
fetchDevicePerPage(value)
const setNewDeviceAlias = async (alias, sn) => {
var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
myHeaders.append("Authorization", localStorage.getItem("token"));
var requestOptions = {
method: 'PUT',
headers: myHeaders,
body: alias,
redirect: 'follow'
};
let result = await fetch(`${process.env.NEXT_PUBLIC_REST_ENDPOINT || ""}/api/device/alias?id=${sn}`, requestOptions)
console.log("result:", result)
if (result.status === 401) {
router.push("/auth/login")
} else if (result.status != 200) {
console.log("Status:", result.status)
let content = await result.json()
console.log("Message:", content)
setShowSetDeviceAlias(false)
setDeviceAlias(null)
setDeviceToBeChanged(null)
} else {
let content = await result.json()
console.log("set alias result:", content)
setShowSetDeviceAlias(false)
setDeviceAlias(null)
orders[deviceToBeChanged].Alias = alias
setDeviceToBeChanged(null)
}
// .then(response => {
// if (response.status === 401) {
// router.push("/auth/login")
// }
// return response.json()
// })
// .then(result => {
// console.log("alias result:", result)
// setShowSetDeviceAlias(false)
// setDeviceAlias(null)
// })
// .catch(error => {
// console.log('error:', error)
// setShowSetDeviceAlias(false)
// setDeviceAlias(null)
// })
}
const fetchDevicePerPage = async (p) => {
setLoading(true)
const fetchDevicePerPage = async (p, s, localFilterList, page_size) => {
//setLoading(true)
var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
@ -83,18 +326,29 @@ const Page = () => {
redirect: 'follow'
}
if (localFilterList == undefined) {
localFilterList = filtersList
}
if (page_size == undefined) {
page_size = rowsPerPage
}
p = p - 1
p = p.toString()
fetch(`${process.env.NEXT_PUBLIC_REST_ENDPOINT || ""}/api/device?page_number=+${p}`, requestOptions)
fetch(`${process.env.NEXT_PUBLIC_REST_ENDPOINT || ""}/api/device?page_number=${p}&page_size=${page_size}&statusOrder=${s}&vendor=${localFilterList["vendor"]}&version=${localFilterList["version"]}&alias=${localFilterList["alias"]}&type=${localFilterList["type"]}&status=${localFilterList["status"]}&model=${localFilterList["model"]}`, requestOptions)
.then(response => {
if (response.status === 401)
router.push("/auth/login")
return response.json()
})
.then(json => {
setTotal(json.total)
setDevices(json.devices)
setLoading(false)
setPages(json.pages + 1)
setPage(json.page + 1)
//setLoading(false)
return
})
.catch(error => {
@ -116,7 +370,7 @@ const Page = () => {
}
if (id == "") {
return fetch(`${process.env.NEXT_PUBLIC_REST_ENDPOINT || ""}/api/device`, requestOptions)
return fetch(`${process.env.NEXT_PUBLIC_REST_ENDPOINT || ""}/api/device?vendor=${filtersList["vendor"]}&version=${filtersList["version"]}&alias=${filtersList["alias"]}&type=${filtersList["type"]}&status=${filtersList["status"]}&model=${filtersList["model"]}`, requestOptions)
.then(response => {
if (response.status === 401)
router.push("/auth/login")
@ -124,7 +378,8 @@ const Page = () => {
})
.then(json => {
setPages(json.pages + 1)
setPage(json.page)
setPage(json.page + 1)
setTotal(json.total)
setDevices(json.devices)
setLoading(false)
return setDeviceFound(true)
@ -140,16 +395,17 @@ const Page = () => {
let json = await response.json()
if (json.SN != undefined) {
setDevices([json])
setTotal(1)
setDeviceFound(true)
setLoading(false)
setPages(1)
setPage(1)
} else {
setDeviceFound(false)
setDevices([])
setPages(1)
setPage(1)
setLoading(false)
setDevices(null)
setTotal(null)
setPages(null)
setPage(null)
}
}
@ -166,16 +422,16 @@ const Page = () => {
component="main"
sx={{
flexGrow: 1,
py: 8,
}}
>
<Container maxWidth="xl" >
<Stack spacing={3}>
<Grid
container
spacing={2}
py={1}
>
<Grid xs={8}>
</Grid>
<Grid xs={4} item>
<OutlinedInput
xs={4}
defaultValue=""
@ -200,40 +456,509 @@ const Page = () => {
sx={{ maxWidth: 500 }}
/>
</Grid>
{deviceFound ?
( !Loading ?
<OverviewLatestOrders
orders={devices}
sx={{ height: '100%' }}
/> : <CircularProgress></CircularProgress>
)
:
<Box
sx={{
display: 'flex',
justifyContent: 'center'
<Grid xs={4} item>
<Button
sx={{ backgroundColor: "rgba(48, 109, 111, 0.04)" }}
onClick={() => { setShowFilter(true) }}
>
<SvgIcon>
<FunnelIcon />
</SvgIcon>
</Button>
{Object.keys(filtersList).map((key) => (
(filtersList[key] &&
<Chip label={`${key} : ${filtersList[key]}`} sx={{ml:1, mt:1}} onDelete={()=>{
setFiltersList({ ...filtersList, [key]: "" })
setNewFiltersList({ ...newFiltersList, [key]: ""})
fetchDevicePerPage(1, statusOrder, { ...filtersList, [key]: "" })
}}/>)
))}
</Grid>
</Grid>
<div style={{ display: "flex", justifyContent: "flex-end" }}>
<Button
id="basic-button"
aria-controls={open ? 'basic-menu' : undefined}
aria-haspopup="true"
aria-expanded={open ? 'true' : undefined}
onClick={handleClick}
>
<SvgIcon>
<ViewColumnsIcon />
</SvgIcon>
</Button>
<Menu
id="basic-menu"
anchorEl={anchorEl}
open={open}
onClose={handleClose}
MenuListProps={{
'aria-labelledby': 'basic-button',
}}
anchorOrigin={{
vertical: "bottom",
horizontal: "center"
}}
transformOrigin={{
vertical: "top",
horizontal: "center"
}}
>
<p>Device Not Found</p>
</Box>
<MenuItem dense onClick={() => changeColumn("sn")}><Checkbox checked={columns["sn"]} /*onChange={()=>changeColumn("sn")}*/ /><ListItemText primary="Serial Number" /></MenuItem>
<MenuItem dense onClick={() => changeColumn("alias")}><Checkbox checked={columns["alias"]} /*onChange={()=>changeColumn("alias")}*/ /><ListItemText primary="Alias" /></MenuItem>
<MenuItem dense onClick={() => changeColumn("model")}><Checkbox checked={columns["model"]} /*onChange={() => changeColumn("model")}*/ /><ListItemText primary="Model" /></MenuItem>
<MenuItem dense onClick={() => changeColumn("vendor")}><Checkbox checked={columns["vendor"]} /*onChange={() => changeColumn("vendor")}*/ /><ListItemText primary="Vendor" /></MenuItem>
<MenuItem dense onClick={() => changeColumn("version")}><Checkbox checked={columns["version"]} /*onChange={() => changeColumn("version")}*/ /><ListItemText primary="Version" /></MenuItem>
<MenuItem dense onClick={() => changeColumn("status")}><Checkbox checked={columns["status"]} /*onChange={() => changeColumn("status")}*/ /><ListItemText primary="Status" /></MenuItem>
<MenuItem dense onClick={() => changeColumn("actions")}><Checkbox checked={columns["actions"]} /*onChange={() => changeColumn("actions")}*/ /><ListItemText primary="Actions" /></MenuItem>
{/* <MenuItem dense onClick={() => changeColumn("label")}><Checkbox checked={columns["label"]} /><ListItemText primary="Labels" /></MenuItem> */}
</Menu>
</div>
<div>
<Card sx={{ height: "100%" }}>
<CardHeader title="Devices" />
<Scrollbar sx={{ flexGrow: 1 }}>
<Box sx={{ minWidth: 800 }}>
<TableContainer sx={{ maxHeight: 600 }}>
<Table stickyHeader>
<TableHead>
<TableRow>
{/* <TableCell align="center">
<Checkbox
size="small"
style={{ margin: 0, padding: 0, color: theme.palette.primary.lightest }}
onChange={(e) => {
setSelected(new Array(devices.length).fill(e.target.checked))
//console.log("selected:", selected)
setSelectAll(e.target.checked)
}}
checked={selectAll}
/>
</TableCell> */}
{columns["sn"] && <TableCell align="center">
Serial Number
</TableCell>}
{columns["alias"] && <TableCell>
Alias
</TableCell>}
{
columns["label"] && <TableCell >
Labels
</TableCell>
}
<Box
sx={{
display: 'flex',
justifyContent: 'center'
{columns["model"] && <TableCell>
Model
</TableCell>}
{columns["vendor"] && <TableCell>
Vendor
</TableCell>}
{columns["version"] && <TableCell>
Version
</TableCell>}
{columns["status"] &&
<TableCell>
{/*//TODO: create function to fetch devices by status order*/}
<Tooltip title="Change status display order" placement="top">
<span style={{ cursor: "pointer" }} onClick={() => {
if (statusOrder == "asc") {
setStatusOrder("desc")
fetchDevicePerPage(page, "desc")
} else {
setStatusOrder("asc")
fetchDevicePerPage(page, "asc")
}
}}>Status </span>
</Tooltip>
{/* <Box >
<Tooltip title="Change status display order" placement="top">
<SvgIcon fontSize='small' style={{
marginLeft: "10px",
cursor: "pointer"
}}>
<ArrowsUpDownIcon />
</SvgIcon>
</Tooltip>
</Box> */}
</TableCell>}
{columns["actions"] && <TableCell align="center">
Actions
</TableCell>}
</TableRow>
</TableHead>
{!Loading ? <TableBody>
{devices && devices.map((order, index) => {
return (
<TableRow
hover
key={order.SN}
>
{/* <TableCell align="center">
<FormControl>
<Checkbox
size="small"
onChange={(e) => {
let newData = [...selected]
newData.splice(index, 1, e.target.checked);
console.log("newData:", newData)
setSelected(newData)
}}
checked={selected[index]}
/>
</FormControl>
</TableCell> */}
{columns["sn"] && <TableCell align="center">
{order.SN}
</TableCell>}
{columns["alias"] && <TableCell>
{order.Alias}
</TableCell>}
{
columns["label"] && <TableCell>
<Chip label="Teste1" />
<Chip label="Teste2" />
<Chip label="Teste3" />
</TableCell>
}
{columns["model"] && <TableCell>
{order.Model || order.ProductClass}
</TableCell>}
{columns["vendor"] && <TableCell>
{order.Vendor}
</TableCell>}
{columns["version"] && <TableCell>
{order.Version}
</TableCell>}
{columns["status"] && <TableCell>
{/* <SeverityPill color={statusMap[order.Status]}>
{status(order.Status)}
</SeverityPill> */}
<Chip label={status(order.Status)} color={statusMap[order.Status]} />
</TableCell>}
{columns["actions"] && <TableCell align="center">
{order.Status == 2 &&
<Tooltip title="Access the device">
<Button
onClick={() => {
router.push("devices/" + getDeviceProtocol(order) + "/" + order.SN)
}}
>
{pages ? <Pagination
<SvgIcon
fontSize="small"
sx={{ cursor: 'pointer' }}
>
<ArrowTopRightOnSquareIcon />
</SvgIcon>
</Button>
</Tooltip>}
{/* <Tooltip title="Edit the device alias">
<Button
onClick={()=>{
setDeviceToBeChanged(index)
setDeviceAlias(order.Alias)
setShowSetDeviceAlias(true)
}}
>
<SvgIcon
fontSize="small"
sx={{cursor: 'pointer'}}
>
<PencilIcon />
</SvgIcon>
</Button>
</Tooltip>
<Tooltip title="Edit device labels">
<Button
onClick={()=>{
setDeviceToBeChanged(index)
}}
>
<SvgIcon
fontSize="small"
sx={{cursor: 'pointer'}}
>
<TagIcon />
</SvgIcon>
</Button>
</Tooltip> */}
</TableCell>}
</TableRow>
);
})}
</TableBody>:
<TableBody>
<TableRow>
<TableCell colSpan={7} align="center">
{
deviceFound ? <CircularProgress/> : "No device found"
}
</TableCell>
</TableRow>
</TableBody>
}
</Table>
</TableContainer>
{(pages.lenght > 0) && total && <TablePagination
rowsPerPageOptions={rowsPerPageOptions}
component="div"
count={total}
rowsPerPage={rowsPerPage}
page={page-1}
onPageChange={(e, newPage)=>{
setPage(newPage+1)
fetchDevicePerPage(newPage+1, statusOrder, filtersList, rowsPerPage)
}}
onRowsPerPageChange={(e)=>{
setRowsPerPage(e.target.value)
setPage(1)
fetchDevicePerPage(1, statusOrder, filtersList, e.target.value)
}}
/>}
</Box>
</Scrollbar>
</Card>
{showSetDeviceAlias &&
<Dialog open={showSetDeviceAlias}>
<DialogContent>
<InputLabel>Device Alias</InputLabel>
<Input value={deviceAlias} onChange={(e) => { setDeviceAlias(e.target.value) }}
onKeyUp={e => {
if (e.key === 'Enter') {
setNewDeviceAlias(deviceAlias, orders[deviceToBeChanged].SN)
}
}}>
</Input>
</DialogContent>
<DialogActions>
<Button onClick={() => {
setShowSetDeviceAlias(false)
setDeviceAlias(null)
setDeviceToBeChanged(null)
}}>Cancel</Button>
<Button onClick={() => {
setNewDeviceAlias(deviceAlias, orders[deviceToBeChanged].SN)
}}>Save</Button>
</DialogActions>
</Dialog>}
</div>
{/* / :
// <Box
// sx={{
// display: 'flex',
// justifyContent: 'center'
// }}
// >
// <p>Device Not Found</p>
// </Box>
// }
// <Box
// sx={{
// display: 'flex',
// justifyContent: 'center'
// }}
// > */}
{/* {pages ? <Pagination
count={pages}
size="small"
page={page}
onChange={handleChangePage}
/>: null}
/> : null} */}
{/* //TODO: show loading */}
</Box>
{/* </Box> */}
</Stack>
</Container>
</Box>
<SpeedDial
ariaLabel="SpeedDial basic example"
hidden={!showSpeedDial}
//FabProps={{size: 'small'}}
sx={{ position: 'fixed', bottom: 16, right: 16, }}
icon={<SpeedDialIcon icon={<SvgIcon ><ChevronDownIcon /></SvgIcon>}
openIcon={<SvgIcon><ChevronUpIcon /></SvgIcon>} />}
>
{actions.map((action) => (
<SpeedDialAction
key={action.name}
icon={action.icon}
tooltipTitle={action.name}
onClick={action.onClickEvent}
/>
))}
</SpeedDial>
<Dialog open={showFilter} fullWidth>
<DialogTitle>
<SvgIcon style={{ marginRight: "10px", marginBottom: "-5px" }}>
<FunnelIcon />
</SvgIcon>
Filter
</DialogTitle>
{filterOptions && <DialogContent>
<Stack spacing={2} marginTop={1} minWidth={400}>
<Stack
spacing={2}
direction={'row'}
>
<TextField label="Alias" variant="filled" sx={{minWidth:"48%"}}
value={newFiltersList["alias"]}
onChange={(e) => setNewFiltersList({ ...newFiltersList, "alias": e.target.value })}
/>
<FormControl variant="filled" sx={{ minWidth: "48%" }}>
<InputLabel>Type</InputLabel>
<Select
value={newFiltersList["type"]}
onChange={(e) => setNewFiltersList({ ...newFiltersList, "type": e.target.value })}
fullWidth
>
{
filterOptions.productClasses.map((v) => {
return <MenuItem value={v}>{v}</MenuItem>
})
}
</Select>
</FormControl>
{/* <TextField label="Serial Number" variant="filled" /> */}
</Stack>
<Stack
spacing={2}
direction={'row'}
>
<FormControl variant="filled" sx={{ minWidth: "48%" }}>
<InputLabel>Vendor</InputLabel>
<Select
value={newFiltersList["vendor"]}
onChange={(e) => setNewFiltersList({ ...newFiltersList, "vendor": e.target.value })}
fullWidth
>
{
filterOptions.vendors.map((v) => {
return <MenuItem value={v}>{v}</MenuItem>
})
}
</Select>
</FormControl>
<FormControl variant="filled" sx={{ minWidth: "48%" }}>
<InputLabel>Version</InputLabel>
<Select
value={newFiltersList["version"]}
onChange={(e) => setNewFiltersList({ ...newFiltersList, "version": e.target.value })}
fullWidth
>
{
filterOptions.versions.map((v) => {
return <MenuItem value={v}>{v}</MenuItem>
})
}
</Select>
</FormControl>
</Stack>
<Stack
spacing={2}
direction={'row'}
>
<FormControl variant="filled" sx={{ minWidth: "48%" }}>
<InputLabel>Status</InputLabel>
<Select
value={
newFiltersList["status"]
// ()=>{
// if (newFiltersList["status"] == 2) {
// return "online"
// }else if (newFiltersList["status"] == 0) {
// return "offline"
// }else {
// return ""
// }
// }
}
onChange={(e) => {
// let value = 0
// if (e.target.value == "online") {
// value = 2
// }else if (e.target.value == "offline") {
// value = 0
// }else {
// return
// }
setNewFiltersList({ ...newFiltersList, "status": e.target.value })
}}
fullWidth
>
<MenuItem value={"2"}>Online</MenuItem>
<MenuItem value={"0"}>Offline</MenuItem>
</Select>
</FormControl>
{/* <FormControl variant="filled" sx={{ minWidth: "48%" }}>
<InputLabel>Label</InputLabel>
<Select
value={labelFilter}
onChange={(e)=> setLabelFilter(e.target.value)}
fullWidth
>
{
filterOptions.labels.map((v) => {
return <MenuItem value={v}>{v}</MenuItem>
})
}
</Select>
</FormControl> */}
<FormControl variant="filled" sx={{ minWidth: "48%" }}>
<InputLabel>Model</InputLabel>
<Select
value={newFiltersList["model"]}
onChange={(e) => setNewFiltersList({ ...newFiltersList, "model": e.target.value })}
fullWidth
>
{
filterOptions.models.map((v) => {
return <MenuItem value={v}>{v}</MenuItem>
})
}
</Select>
</FormControl>
</Stack>
{/* <Stack
spacing={2}
direction={'row'}
>
<FormControl variant="filled" sx={{ minWidth: "48%" }}>
<InputLabel>Type</InputLabel>
<Select
value={typeFilter}
onChange={(e) => setTypeFilter(e.target.value)}
fullWidth
>
{
filterOptions.productClasses.map((v) => {
return <MenuItem value={v}>{v}</MenuItem>
})
}
</Select>
</FormControl>
</Stack> */}
</Stack>
</DialogContent>}
<DialogActions>
<Button onClick={() => {
setNewFiltersList(filtersList)
//cleanFilters()
setShowFilter(false)
//if (!objsEqual(filtersList,defaultFiltersList)) {
/* fetchDevicePerPage(1, statusOrder, defaultFiltersList)
}
if (!objsEqual(filtersList, newFiltersList)){
} */
//setFiltersList(defaultFiltersList)
}}>Cancel</Button>
<Button onClick={() => {
setFiltersList(newFiltersList)
setShowFilter(false)
console.log("filters list:", filtersList)
fetchDevicePerPage(1, statusOrder, newFiltersList)
}}>Apply</Button>
</DialogActions>
</Dialog>
</>
)
}