From 266857187efc46243cb9ff42968ca80c69e71c2f Mon Sep 17 00:00:00 2001 From: leandrofars Date: Thu, 4 Jul 2024 16:56:01 -0300 Subject: [PATCH 01/28] feat(controller): add maps api --- backend/services/controller/internal/api/api.go | 7 +++++++ .../controller/internal/api/enterprise.go | 16 ++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/backend/services/controller/internal/api/api.go b/backend/services/controller/internal/api/api.go index 4046436..404e900 100644 --- a/backend/services/controller/internal/api/api.go +++ b/backend/services/controller/internal/api/api.go @@ -57,6 +57,13 @@ 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") diff --git a/backend/services/controller/internal/api/enterprise.go b/backend/services/controller/internal/api/enterprise.go index f7ce633..1e09d44 100644 --- a/backend/services/controller/internal/api/enterprise.go +++ b/backend/services/controller/internal/api/enterprise.go @@ -28,6 +28,22 @@ func (a *Api) getEnterpriseResource( 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"] From 58417cddca9d74f8adc956c3d5199c598c596176 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Thu, 4 Jul 2024 16:56:29 -0300 Subject: [PATCH 02/28] feat(adapter): publish message if new device was registered --- .../mtp/adapter/internal/events/cwmp_handler/info.go | 5 +++++ .../services/mtp/adapter/internal/events/usp_handler/info.go | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/backend/services/mtp/adapter/internal/events/cwmp_handler/info.go b/backend/services/mtp/adapter/internal/events/cwmp_handler/info.go index d46a895..8ce31f1 100644 --- a/backend/services/mtp/adapter/internal/events/cwmp_handler/info.go +++ b/backend/services/mtp/adapter/internal/events/cwmp_handler/info.go @@ -1,6 +1,7 @@ package cwmp_handler import ( + "encoding/json" "encoding/xml" "log" @@ -12,6 +13,10 @@ func (h *Handler) HandleDeviceInfo(device string, data []byte, ack func()) { defer ack() log.Printf("Device %s info", device) deviceInfo := parseDeviceInfoMsg(data) + if deviceExists, _ := h.db.DeviceExists(deviceInfo.SN); !deviceExists { + fmtDeviceInfo, _ := json.Marshal(deviceInfo) + h.nc.Publish("device.v1.new", fmtDeviceInfo) + } err := h.db.CreateDevice(deviceInfo) if err != nil { log.Printf("Failed to create device: %v", err) diff --git a/backend/services/mtp/adapter/internal/events/usp_handler/info.go b/backend/services/mtp/adapter/internal/events/usp_handler/info.go index 07acca7..ce2bd66 100644 --- a/backend/services/mtp/adapter/internal/events/usp_handler/info.go +++ b/backend/services/mtp/adapter/internal/events/usp_handler/info.go @@ -1,6 +1,7 @@ package usp_handler import ( + "encoding/json" "log" "github.com/OktopUSP/oktopus/backend/services/mtp/adapter/internal/db" @@ -14,6 +15,10 @@ func (h *Handler) HandleDeviceInfo(device, subject string, data []byte, mtp stri defer ack() log.Printf("Device %s info, mtp: %s", device, mtp) deviceInfo := parseDeviceInfoMsg(device, subject, data, getMtp(mtp)) + if deviceExists, _ := h.db.DeviceExists(deviceInfo.SN); !deviceExists { + fmtDeviceInfo, _ := json.Marshal(deviceInfo) + h.nc.Publish("device.v1.new", fmtDeviceInfo) + } err := h.db.CreateDevice(deviceInfo) if err != nil { log.Printf("Failed to create device: %v", err) From 0462d74b156538a0992c49c2985b8e260723b453 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Thu, 4 Jul 2024 20:18:20 -0300 Subject: [PATCH 03/28] feat(file-server): build and release --- backend/services/utils/file-server/.gitignore | 3 +- .../utils/file-server/build/Dockerfile | 8 +++ .../services/utils/file-server/build/Makefile | 61 +++++++++++++++++++ build/Makefile | 2 + 4 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 backend/services/utils/file-server/build/Dockerfile create mode 100644 backend/services/utils/file-server/build/Makefile diff --git a/backend/services/utils/file-server/.gitignore b/backend/services/utils/file-server/.gitignore index 0835f35..943b9ca 100644 --- a/backend/services/utils/file-server/.gitignore +++ b/backend/services/utils/file-server/.gitignore @@ -1,2 +1,3 @@ firmwares/ -.env.local \ No newline at end of file +.env.local +images/ \ No newline at end of file diff --git a/backend/services/utils/file-server/build/Dockerfile b/backend/services/utils/file-server/build/Dockerfile new file mode 100644 index 0000000..25700be --- /dev/null +++ b/backend/services/utils/file-server/build/Dockerfile @@ -0,0 +1,8 @@ +FROM golang:1.22.2@sha256:450e3822c7a135e1463cd83e51c8e2eb03b86a02113c89424e6f0f8344bb4168 as builder +WORKDIR /app +COPY ../ . +RUN CGO_ENABLED=0 GOOS=linux go build -o file-server main.go + +FROM alpine:3.14@sha256:0f2d5c38dd7a4f4f733e688e3a6733cb5ab1ac6e3cb4603a5dd564e5bfb80eed +COPY --from=builder /app/file-server / +ENTRYPOINT ["/file-server"] \ No newline at end of file diff --git a/backend/services/utils/file-server/build/Makefile b/backend/services/utils/file-server/build/Makefile new file mode 100644 index 0000000..a35adde --- /dev/null +++ b/backend/services/utils/file-server/build/Makefile @@ -0,0 +1,61 @@ +.PHONY: help build push start stop release remove delete run logs bash + +DOCKER_USER ?= oktopusp +DOCKER_APP ?= file-server +DOCKER_TAG ?= $(shell git log --format="%h" -n 1) +CONTAINER_SHELL ?= /bin/sh + +.DEFAULT_GOAL := help + +help: + @echo "Makefile arguments:" + @echo "" + @echo "DOCKER_USER - docker user to build image" + @echo "DOCKER_APP - docker image name" + @echo "DOCKER_TAG - docker image tag" + @echo "CONTAINER_SHELL - container shell e.g:'/bin/bash'" + @echo "" + @echo "Makefile commands:" + @echo "" + @echo "build - docker image build" + @echo "push - push docker iamge to registry" + @echo "run - create and start docker container with the image" + @echo "start - start existent docker container with the image" + @echo "stop - stop docker container running the image" + @echo "remove - remove docker container running the image" + @echo "delete - delete docker image" + @echo "logs - show logs of docker container" + @echo "bash - access container shell" + @echo "release - tag image as latest and push to registry" + +build: + @docker build -t ${DOCKER_USER}/${DOCKER_APP}:${DOCKER_TAG} -f Dockerfile ../ + +run: + @docker run -d --name ${DOCKER_USER}-${DOCKER_APP} ${DOCKER_USER}/${DOCKER_APP}:${DOCKER_TAG} + +stop: + @docker stop ${DOCKER_USER}-${DOCKER_APP} + +remove: stop + @docker rm ${DOCKER_USER}-${DOCKER_APP} + +delete: + @docker rmi ${DOCKER_USER}/${DOCKER_APP}:${DOCKER_TAG} + +start: + @docker start ${DOCKER_USER}-${DOCKER_APP} + +push: + @docker push ${DOCKER_USER}/${DOCKER_APP}:${DOCKER_TAG} + +logs: + @docker logs -f ${DOCKER_USER}-${DOCKER_APP} + +bash: + @docker exec -it ${DOCKER_USER}-${DOCKER_APP} ${CONTAINER_SHELL} + +release: build + @docker push ${DOCKER_USER}/${DOCKER_APP}:${DOCKER_TAG} + @docker tag ${DOCKER_USER}/${DOCKER_APP}:${DOCKER_TAG} ${DOCKER_USER}/${DOCKER_APP}:latest + @docker push ${DOCKER_USER}/${DOCKER_APP}:latest \ No newline at end of file diff --git a/build/Makefile b/build/Makefile index c87bf00..3e41492 100644 --- a/build/Makefile +++ b/build/Makefile @@ -26,6 +26,7 @@ build-backend: @make build -C ../backend/services/controller/build/ DOCKER_USER=${DOCKER_USER} @make build -C ../backend/services/acs/build/ DOCKER_USER=${DOCKER_USER} @make build -C ../backend/services/utils/socketio/build/ DOCKER_USER=${DOCKER_USER} + @make build -C ../backend/services/utils/file-server/build/ DOCKER_USER=${DOCKER_USER} @make build -C ../backend/services/mtp/adapter/build/ DOCKER_USER=${DOCKER_USER} @make build -C ../backend/services/mtp/ws-adapter/build/ DOCKER_USER=${DOCKER_USER} @make build -C ../backend/services/mtp/ws/build/ DOCKER_USER=${DOCKER_USER} @@ -43,6 +44,7 @@ release-backend: @make release -C ../backend/services/controller/build/ DOCKER_USER=${DOCKER_USER} @make release -C ../backend/services/acs/build/ DOCKER_USER=${DOCKER_USER} @make release -C ../backend/services/utils/socketio/build/ DOCKER_USER=${DOCKER_USER} + @make release -C ../backend/services/utils/file-server/build/ DOCKER_USER=${DOCKER_USER} @make release -C ../backend/services/mtp/adapter/build/ DOCKER_USER=${DOCKER_USER} @make release -C ../backend/services/mtp/ws-adapter/build/ DOCKER_USER=${DOCKER_USER} @make release -C ../backend/services/mtp/ws/build/ DOCKER_USER=${DOCKER_USER} From d1a44765fed2cdf542eab9e548f34f20376926a8 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Thu, 4 Jul 2024 20:18:52 -0300 Subject: [PATCH 04/28] chore(file-server): default folder files hosting --- backend/services/utils/file-server/.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/services/utils/file-server/.env b/backend/services/utils/file-server/.env index 8eb288c..10e120d 100644 --- a/backend/services/utils/file-server/.env +++ b/backend/services/utils/file-server/.env @@ -1,2 +1,2 @@ -DIRECTORY_PATH="./firmwares" +DIRECTORY_PATH="." SERVER_PORT=":8004" \ No newline at end of file From 1da6678c093a65524ede2b66080ed996d3a5ee64 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Fri, 5 Jul 2024 14:01:38 -0300 Subject: [PATCH 05/28] feat: file-server host logo image + add config to docker compose --- deploy/compose/.env.file-server | 2 ++ deploy/compose/.gitignore | 6 ++++- deploy/compose/docker-compose.yaml | 24 +++++++++++++++--- deploy/compose/firmwares/.gitkeep | 0 .../compose/images/logo.png | Bin deploy/compose/nginx.conf | 9 ++++--- frontend/src/layouts/dashboard/side-nav.js | 5 ++-- 7 files changed, 35 insertions(+), 11 deletions(-) create mode 100644 deploy/compose/.env.file-server create mode 100644 deploy/compose/firmwares/.gitkeep rename frontend/public/assets/oktopus.png => deploy/compose/images/logo.png (100%) diff --git a/deploy/compose/.env.file-server b/deploy/compose/.env.file-server new file mode 100644 index 0000000..10e120d --- /dev/null +++ b/deploy/compose/.env.file-server @@ -0,0 +1,2 @@ +DIRECTORY_PATH="." +SERVER_PORT=":8004" \ No newline at end of file diff --git a/deploy/compose/.gitignore b/deploy/compose/.gitignore index af5be97..93a4138 100644 --- a/deploy/compose/.gitignore +++ b/deploy/compose/.gitignore @@ -3,4 +3,8 @@ portainer_data/* mongo_data/* !mongo_data/.gitkeep nats_data/* -!nats_data/.gitkeep \ No newline at end of file +!nats_data/.gitkeep +firmwares/* +!firmwares/.gitkeep +images/* +!images/logo.png \ No newline at end of file diff --git a/deploy/compose/docker-compose.yaml b/deploy/compose/docker-compose.yaml index 5fdbddd..6695559 100644 --- a/deploy/compose/docker-compose.yaml +++ b/deploy/compose/docker-compose.yaml @@ -191,15 +191,31 @@ services: container_name: nginx ports: - 80:80 - depends_on: - - frontend - - controller - - socketio + # depends_on: + # - frontend + # - controller + # - socketio volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf + extra_hosts: + - "host.docker.internal:host-gateway" networks: usp_network: ipv4_address: 172.16.235.17 + + file-server: + image: oktopusp/file-server + container_name: file-server + ports: + - 8004:8004 + volumes: + - ./firmwares:/app/firmwares + - ./images:/app/images + env_file: + - .env.file-server + networks: + usp_network: + ipv4_address: 172.16.235.18 networks: usp_network: diff --git a/deploy/compose/firmwares/.gitkeep b/deploy/compose/firmwares/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/frontend/public/assets/oktopus.png b/deploy/compose/images/logo.png similarity index 100% rename from frontend/public/assets/oktopus.png rename to deploy/compose/images/logo.png diff --git a/deploy/compose/nginx.conf b/deploy/compose/nginx.conf index 1bba7d8..4a762b8 100644 --- a/deploy/compose/nginx.conf +++ b/deploy/compose/nginx.conf @@ -11,19 +11,19 @@ server { } location / { - proxy_pass http://frontend:3000; + proxy_pass http://host.docker.internal:3000; proxy_read_timeout 60; proxy_connect_timeout 60; proxy_redirect off; } location /api { - proxy_pass http://controller:8000; + proxy_pass http://host.docker.internal:8000; proxy_read_timeout 60; proxy_connect_timeout 60; proxy_redirect off; } location /socket.io { - proxy_pass http://socketio:5000; + proxy_pass http://host.docker.internal:5000; proxy_read_timeout 60; proxy_connect_timeout 60; proxy_redirect off; @@ -34,6 +34,9 @@ server { proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } + location /companylink { + return 301 https://oktopus.app.br/controller; + } error_page 500 502 503 504 /50x.html; location = /50x.html { diff --git a/frontend/src/layouts/dashboard/side-nav.js b/frontend/src/layouts/dashboard/side-nav.js index d67d4d5..ce2e646 100644 --- a/frontend/src/layouts/dashboard/side-nav.js +++ b/frontend/src/layouts/dashboard/side-nav.js @@ -34,7 +34,6 @@ export const SideNav = (props) => { } return false; - //TODO: test frontend with color of the landing page } const content = ( @@ -80,9 +79,9 @@ export const SideNav = (props) => { p: '12px' }} > - +
-
From 10f950190ff8d007c833c70ea9a010d9a1212c27 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Fri, 5 Jul 2024 14:04:09 -0300 Subject: [PATCH 06/28] feat(frontend): dinamyc logo and company link --- frontend/src/layouts/auth/layout.js | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/frontend/src/layouts/auth/layout.js b/frontend/src/layouts/auth/layout.js index 203d38d..3d1cad8 100644 --- a/frontend/src/layouts/auth/layout.js +++ b/frontend/src/layouts/auth/layout.js @@ -4,8 +4,6 @@ import Link from 'next/link' import { Box, Typography, Unstable_Grid2 as Grid } from '@mui/material'; import { Logo } from 'src/components/logo'; -// TODO: Change subtitle text - export const Layout = (props) => { const { children } = props; @@ -70,17 +68,12 @@ export const Layout = (props) => { }} > - - - - - + + + From 1adbabe654bdcafe8ba3c4e1135b6fd024c8d3ed Mon Sep 17 00:00:00 2001 From: leandrofars Date: Fri, 5 Jul 2024 14:38:06 -0300 Subject: [PATCH 07/28] chore(file-server): print api server url port starter --- backend/services/utils/file-server/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/services/utils/file-server/main.go b/backend/services/utils/file-server/main.go index 82e7705..e3dd389 100644 --- a/backend/services/utils/file-server/main.go +++ b/backend/services/utils/file-server/main.go @@ -42,7 +42,7 @@ func main() { http.Handle("/", fileServer) port := os.Getenv("SERVER_PORT") - fmt.Printf("Server started at http://localhost:%s\n", port) + fmt.Printf("Server started at http://localhost%s\n", port) err = http.ListenAndServe(port, nil) if err != nil { fmt.Printf("Error starting server: %s\n", err) From 3cd47b155c2ff45d86685d2c4771b6871db79c53 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Fri, 5 Jul 2024 15:50:50 -0300 Subject: [PATCH 08/28] feat(frontend): maps mvp | close #283 --- frontend/src/layouts/dashboard/config.js | 18 +-- frontend/src/layouts/dashboard/side-nav.js | 3 + frontend/src/pages/_app.js | 1 + frontend/src/pages/map.js | 177 ++++++++++++++++++--- frontend/src/utils/map.css | 47 ++++++ 5 files changed, 211 insertions(+), 35 deletions(-) create mode 100644 frontend/src/utils/map.css diff --git a/frontend/src/layouts/dashboard/config.js b/frontend/src/layouts/dashboard/config.js index 171482a..f2e7fff 100644 --- a/frontend/src/layouts/dashboard/config.js +++ b/frontend/src/layouts/dashboard/config.js @@ -35,15 +35,15 @@ export const items = [ // // ) // }, - // { - // title: 'Map', - // path: '/map', - // icon: ( - // - // - // - // ), - // }, + { + title: 'Map', + path: '/map', + icon: ( + + + + ), + }, { title: 'Credentials', path: '/credentials', diff --git a/frontend/src/layouts/dashboard/side-nav.js b/frontend/src/layouts/dashboard/side-nav.js index ce2e646..e378c2a 100644 --- a/frontend/src/layouts/dashboard/side-nav.js +++ b/frontend/src/layouts/dashboard/side-nav.js @@ -112,6 +112,9 @@ export const SideNav = (props) => { }} > {items.map((item) => { + if (item.title == "Map" && process.env.NEXT_PUBLIC_ENTERPRISE_VERSION != "true"){ + return + } const active = isItemActive(pathname, item.path); return ( diff --git a/frontend/src/pages/_app.js b/frontend/src/pages/_app.js index 7bc21fb..3c4bdc9 100644 --- a/frontend/src/pages/_app.js +++ b/frontend/src/pages/_app.js @@ -10,6 +10,7 @@ import { createTheme } from 'src/theme'; import { createEmotionCache } from 'src/utils/create-emotion-cache'; import 'simplebar-react/dist/simplebar.min.css'; import { WsProvider } from 'src/contexts/socketio-context'; +import '../utils/map.css'; const clientSideEmotionCache = createEmotionCache(); diff --git a/frontend/src/pages/map.js b/frontend/src/pages/map.js index 24a111c..18fd812 100644 --- a/frontend/src/pages/map.js +++ b/frontend/src/pages/map.js @@ -1,39 +1,120 @@ import Head from 'next/head'; -import { GoogleMap, useLoadScript } from "@react-google-maps/api" +import { GoogleMap, useLoadScript, Marker, OverlayView } from "@react-google-maps/api" import { Layout as DashboardLayout } from 'src/layouts/dashboard/layout'; import { useEffect, useMemo, useState } from 'react'; import mapStyles from '../utils/mapStyles.json'; +const getPixelPositionOffset = pixelOffset => (width, height) => ({ + x: -(width / 2) + pixelOffset.x, + y: -(height / 2) + pixelOffset.y +}); + +const Popup = props => { + return ( + +
+
+
{props.content}
+
+
+
+ ); +}; + const Page = () => { const libraries = useMemo(() => ['places'], []); const [mapCenter, setMapCenter] = useState(null); + const [markers, setMarkers] = useState([]); + const [activeMarker, setActiveMarker] = useState(null); + const [activeMarkerdata, setActiveMarkerdata] = useState(null); + + const fetchMarkers = async () => { + + var myHeaders = new Headers(); + myHeaders.append("Content-Type", "application/json"); + myHeaders.append("Authorization", localStorage.getItem("token")); + + var requestOptions = { + method: 'GET', + headers: myHeaders, + redirect: 'follow' + }; + + let result = await fetch(`${process.env.NEXT_PUBLIC_REST_ENDPOINT || ""}/api/map`, requestOptions) + + if (result.status == 200) { + const content = await result.json() + setMarkers(content) + }else if (result.status == 403) { + console.log("num tenx permissão, seu boca de sandália") + return router.push("/403") + }else if (result.status == 401){ + console.log("taix nem autenticado, sai fora oh") + return router.push("/auth/login") + } else { + console.log("agora quebrasse ux córno mô quiridu") + const content = await result.json() + throw new Error(content); + } + } + + const fetchActiveMarkerData = async (id) => { + var myHeaders = new Headers(); + myHeaders.append("Content-Type", "application/json"); + myHeaders.append("Authorization", localStorage.getItem("token")); + + var requestOptions = { + method: 'GET', + headers: myHeaders, + redirect: 'follow' + }; + + let result = await fetch(`${process.env.NEXT_PUBLIC_REST_ENDPOINT || ""}/api/device?id=`+id, requestOptions) + + if (result.status == 200) { + const content = await result.json() + setActiveMarkerdata(content) + }else if (result.status == 403) { + return router.push("/403") + }else if (result.status == 401){ + return router.push("/auth/login") + } else { + console.log("no device info found") + const content = await result.json() + } + } useEffect(()=> { - // Check if geolocation is supported by the browser - if ("geolocation" in navigator) { - // Prompt user for permission to access their location - navigator.geolocation.getCurrentPosition( - // Get the user's latitude and longitude coordinates - // Success callback function - function(position) { - // Update the map with the user's new location - setMapCenter({ - lat: position.coords.latitude, - lng: position.coords.longitude, - }) - }, - // Error callback function - function(error) { - // Handle errors, e.g. user denied location sharing permissions - console.error("Error getting user location:", error); - } - ); - } else { - // Geolocation is not supported by the browser - console.error("Geolocation is not supported by this browser."); - } + fetchMarkers(); + // Check if geolocation is supported by the browser + if ("geolocation" in navigator) { + // Prompt user for permission to access their location + navigator.geolocation.getCurrentPosition( + // Get the user's latitude and longitude coordinates + // Success callback function + function(position) { + // Update the map with the user's new location + setMapCenter({ + lat: position.coords.latitude, + lng: position.coords.longitude, + }) + }, + // Error callback function + function(error) { + // Handle errors, e.g. user denied location sharing permissions + console.error("Error getting user location:", error); + } + ); + } else { + // Geolocation is not supported by the browser + console.error("Geolocation is not supported by this browser."); + } },[]) const mapOptions = useMemo( @@ -59,7 +140,7 @@ const Page = () => { return

Loading...

; } - return ( mapCenter && + return ( mapCenter && markers && <> @@ -73,7 +154,51 @@ const Page = () => { mapContainerStyle={{ width: '100%', height: '100%' }} onLoad={() => console.log('Map Component Loaded...')} clickableIcons={false} - /> + > + { + markers.map((marker, index) => ( + <Marker + key={index} + position={{ lat: marker.coordinates.lat, lng: marker.coordinates.lng }} + icon={{ + url: marker.img, + scaledSize: new window.google.maps.Size(50, 50), + anchor: new window.google.maps.Point(25, 25), + }} + draggable={false} + clickable={true} + onClick={() => { + setActiveMarkerdata(null); + if (activeMarker?.sn === marker.sn) { + setActiveMarker(null); + return; + } + fetchActiveMarkerData(marker.sn); + setActiveMarker({ + sn: marker.sn, + position: { lat: marker.coordinates.lat, lng: marker.coordinates.lng } + }); + }} + > + </Marker> + )) + } + {activeMarker && + <Popup + anchorPosition={activeMarker.position} + markerPixelOffset={{ x: 0, y: -32 }} + content={activeMarkerdata ? + <div> + <div>SN: {activeMarker.sn}</div> + <div> + <div>Model: {activeMarkerdata.Model?activeMarkerdata.Model:activeMarkerdata.ProductClass}</div> + <div>Alias: {activeMarkerdata.Alias}</div> + <div>Status: {activeMarkerdata.Status == 2 ? <span style={{color:"green"}}>online</span> : <span style={{color:"green"}}>offline</span>}</div> + </div> + </div> + : <p>no device info found</p>} + />} + </GoogleMap> </> )}; diff --git a/frontend/src/utils/map.css b/frontend/src/utils/map.css new file mode 100644 index 0000000..55a1725 --- /dev/null +++ b/frontend/src/utils/map.css @@ -0,0 +1,47 @@ +/* The location pointed to by the popup tip. */ +.popup-tip-anchor { + height: 0; + position: absolute; + /* The max width of the info window. */ + width: 200px; + } + /* The bubble is anchored above the tip. */ + .popup-bubble-anchor { + position: absolute; + width: 100%; + bottom: /* TIP_HEIGHT= */ 8px; + left: 0; + } + /* Draw the tip. */ + .popup-bubble-anchor::after { + content: ""; + position: absolute; + top: 0; + left: 0; + /* Center the tip horizontally. */ + transform: translate(-50%, 0); + /* The tip is a https://css-tricks.com/snippets/css/css-triangle/ */ + width: 0; + height: 0; + /* The tip is 8px high, and 12px wide. */ + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-top: /* TIP_HEIGHT= */ 8px solid white; + } + /* The popup bubble itself. */ + .popup-bubble-content { + position: absolute; + top: 0; + left: 0; + transform: translate(-50%, -100%); + /* Style the info window. */ + background-color: white; + padding: 5px; + border-radius: 5px; + font-family: sans-serif; + font-size: 14px ; + overflow-y: auto; + max-height: 80px; + box-shadow: 0px 2px 10px 1px rgba(0, 0, 0, 0.5); + } + \ No newline at end of file From 291d15b183cd7ff8d7efa663b1d25fe8a2db5ad4 Mon Sep 17 00:00:00 2001 From: Adriano Chiesa <citeb.adriano@intelbras.com.br> Date: Fri, 5 Jul 2024 16:29:51 -0300 Subject: [PATCH 09/28] Add TLS to NATS connection --- .../services/acs/internal/config/config.go | 33 ++++++++++++++----- backend/services/acs/internal/nats/nats.go | 6 ++-- .../bulkdata/http/internal/config/config.go | 33 ++++++++++++++----- .../bulkdata/http/internal/nats/nats.go | 6 ++-- .../controller/internal/config/config.go | 33 ++++++++++++++----- .../services/controller/internal/nats/nats.go | 6 ++-- .../mtp/adapter/internal/config/config.go | 33 ++++++++++++++----- .../mtp/adapter/internal/nats/nats.go | 6 ++-- .../mqtt-adapter/internal/config/config.go | 33 ++++++++++++++----- .../mtp/mqtt-adapter/internal/nats/nats.go | 6 ++-- .../mtp/mqtt/internal/config/config.go | 33 ++++++++++++++----- .../services/mtp/mqtt/internal/nats/nats.go | 6 ++-- .../stomp-adapter/internal/config/config.go | 33 ++++++++++++++----- .../mtp/stomp-adapter/internal/nats/nats.go | 7 ++-- .../mtp/ws-adapter/internal/config/config.go | 33 ++++++++++++++----- .../mtp/ws-adapter/internal/nats/nats.go | 6 ++-- .../services/mtp/ws/internal/config/config.go | 33 ++++++++++++++----- backend/services/mtp/ws/internal/nats/nats.go | 6 ++-- 18 files changed, 252 insertions(+), 100 deletions(-) diff --git a/backend/services/acs/internal/config/config.go b/backend/services/acs/internal/config/config.go index e6e6325..1f5fd5e 100644 --- a/backend/services/acs/internal/config/config.go +++ b/backend/services/acs/internal/config/config.go @@ -14,10 +14,11 @@ import ( const LOCAL_ENV = ".env.local" type Nats struct { - Url string - Name string - VerifyCertificates bool - Ctx context.Context + Url string + Name string + EnableTls bool + Cert Tls + Ctx context.Context } type Acs struct { @@ -40,6 +41,12 @@ type Config struct { Nats Nats } +type Tls struct { + CertFile string + KeyFile string + CaFile string +} + func NewConfig() *Config { loadEnvVariables() @@ -47,7 +54,10 @@ func NewConfig() *Config { natsUrl := flag.String("nats_url", lookupEnvOrString("NATS_URL", "nats://localhost:4222"), "url for nats server") natsName := flag.String("nats_name", lookupEnvOrString("NATS_NAME", "adapter"), "name for nats client") - natsVerifyCertificates := flag.Bool("nats_verify_certificates", lookupEnvOrBool("NATS_VERIFY_CERTIFICATES", false), "verify validity of certificates from nats server") + natsEnableTls := flag.Bool("nats_enable_tls", lookupEnvOrBool("NATS_ENABLE_TLS", false), "enbale TLS to nats server") + clientCrt := flag.String("client_crt", lookupEnvOrString("CLIENT_CRT", "cert.pem"), "client certificate file to TLS connection") + clientKey := flag.String("client_key", lookupEnvOrString("CLIENT_KEY", "key.pem"), "client key file to TLS connection") + serverCA := flag.String("server_ca", lookupEnvOrString("SERVER_CA", "rootCA.pem"), "server CA file to TLS connection") acsPort := flag.String("acs_port", lookupEnvOrString("ACS_PORT", ":9292"), "port for acs server") acsRoute := flag.String("acs_route", lookupEnvOrString("ACS_ROUTE", "/acs"), "route for acs server") connReqUser := flag.String("connrq_user", lookupEnvOrString("CONN_RQ_USER", ""), "Connection Request Username") @@ -77,10 +87,15 @@ func NewConfig() *Config { return &Config{ Nats: Nats{ - Url: *natsUrl, - Name: *natsName, - VerifyCertificates: *natsVerifyCertificates, - Ctx: ctx, + Url: *natsUrl, + Name: *natsName, + EnableTls: *natsEnableTls, + Ctx: ctx, + Cert: Tls{ + CertFile: *clientCrt, + KeyFile: *clientKey, + CaFile: *serverCA, + }, }, Acs: Acs{ Port: *acsPort, diff --git a/backend/services/acs/internal/nats/nats.go b/backend/services/acs/internal/nats/nats.go index 0b1e803..d4d8a6e 100644 --- a/backend/services/acs/internal/nats/nats.go +++ b/backend/services/acs/internal/nats/nats.go @@ -141,8 +141,10 @@ func defineOptions(c config.Nats) []nats.Option { opts = append(opts, nats.ClosedHandler(func(nc *nats.Conn) { log.Printf("Connection closed. Reason: %q\n", nc.LastError()) })) - if c.VerifyCertificates { - opts = append(opts, nats.RootCAs()) + if c.EnableTls { + log.Printf("Load certificates: %s and %s\n", c.Cert.CertFile, c.Cert.KeyFile) + opts = append(opts, nats.RootCAs(c.Cert.CaFile)) + opts = append(opts, nats.ClientCert(c.Cert.CertFile, c.Cert.KeyFile)) } return opts diff --git a/backend/services/bulkdata/http/internal/config/config.go b/backend/services/bulkdata/http/internal/config/config.go index 8375aca..fff17d3 100644 --- a/backend/services/bulkdata/http/internal/config/config.go +++ b/backend/services/bulkdata/http/internal/config/config.go @@ -13,10 +13,11 @@ import ( const LOCAL_ENV = ".env.local" type Nats struct { - Url string - Name string - VerifyCertificates bool - Ctx context.Context + Url string + Name string + EnableTls bool + Cert Tls + Ctx context.Context } type RestApi struct { @@ -29,6 +30,12 @@ type Config struct { Nats Nats } +type Tls struct { + CertFile string + KeyFile string + CaFile string +} + func NewConfig() *Config { loadEnvVariables() @@ -36,7 +43,10 @@ func NewConfig() *Config { natsUrl := flag.String("nats_url", lookupEnvOrString("NATS_URL", "nats://localhost:4222"), "url for nats server") natsName := flag.String("nats_name", lookupEnvOrString("NATS_NAME", "adapter"), "name for nats client") - natsVerifyCertificates := flag.Bool("nats_verify_certificates", lookupEnvOrBool("NATS_VERIFY_CERTIFICATES", false), "verify validity of certificates from nats server") + natsEnableTls := flag.Bool("nats_enable_tls", lookupEnvOrBool("NATS_ENABLE_TLS", false), "enbale TLS to nats server") + clientCrt := flag.String("client_crt", lookupEnvOrString("CLIENT_CRT", "cert.pem"), "client certificate file to TLS connection") + clientKey := flag.String("client_key", lookupEnvOrString("CLIENT_KEY", "key.pem"), "client key file to TLS connection") + 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", "4000"), "Rest api port") flHelp := flag.Bool("help", false, "Help") @@ -62,10 +72,15 @@ func NewConfig() *Config { Ctx: ctx, }, Nats: Nats{ - Url: *natsUrl, - Name: *natsName, - VerifyCertificates: *natsVerifyCertificates, - Ctx: ctx, + Url: *natsUrl, + Name: *natsName, + EnableTls: *natsEnableTls, + Ctx: ctx, + Cert: Tls{ + CertFile: *clientCrt, + KeyFile: *clientKey, + CaFile: *serverCA, + }, }, } } diff --git a/backend/services/bulkdata/http/internal/nats/nats.go b/backend/services/bulkdata/http/internal/nats/nats.go index 0c77ac4..dd58bf8 100644 --- a/backend/services/bulkdata/http/internal/nats/nats.go +++ b/backend/services/bulkdata/http/internal/nats/nats.go @@ -70,8 +70,10 @@ func defineOptions(c config.Nats) []nats.Option { opts = append(opts, nats.ClosedHandler(func(nc *nats.Conn) { log.Printf("Connection closed. Reason: %q\n", nc.LastError()) })) - if c.VerifyCertificates { - opts = append(opts, nats.RootCAs()) + if c.EnableTls { + log.Printf("Load certificates: %s and %s\n", c.Cert.CertFile, c.Cert.KeyFile) + opts = append(opts, nats.RootCAs(c.Cert.CaFile)) + opts = append(opts, nats.ClientCert(c.Cert.CertFile, c.Cert.KeyFile)) } return opts diff --git a/backend/services/controller/internal/config/config.go b/backend/services/controller/internal/config/config.go index ff97dd6..e90dc5e 100644 --- a/backend/services/controller/internal/config/config.go +++ b/backend/services/controller/internal/config/config.go @@ -13,10 +13,11 @@ import ( const LOCAL_ENV = ".env.local" type Nats struct { - Url string - Name string - VerifyCertificates bool - Ctx context.Context + Url string + Name string + EnableTls bool + Cert Tls + Ctx context.Context } type Mongo struct { @@ -42,6 +43,12 @@ type Config struct { Enterprise Enterprise } +type Tls struct { + CertFile string + KeyFile string + CaFile string +} + func NewConfig() *Config { loadEnvVariables() @@ -49,7 +56,10 @@ func NewConfig() *Config { natsUrl := flag.String("nats_url", lookupEnvOrString("NATS_URL", "nats://localhost:4222"), "url for nats server") natsName := flag.String("nats_name", lookupEnvOrString("NATS_NAME", "controller"), "name for nats client") - natsVerifyCertificates := flag.Bool("nats_verify_certificates", lookupEnvOrBool("NATS_VERIFY_CERTIFICATES", false), "verify validity of certificates from nats server") + natsEnableTls := flag.Bool("nats_enable_tls", lookupEnvOrBool("NATS_ENABLE_TLS", false), "enbale TLS to nats server") + clientCrt := flag.String("client_crt", lookupEnvOrString("CLIENT_CRT", "cert.pem"), "client certificate file to TLS connection") + clientKey := flag.String("client_key", lookupEnvOrString("CLIENT_KEY", "key.pem"), "client key file to TLS connection") + 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") @@ -79,10 +89,15 @@ func NewConfig() *Config { Ctx: ctx, }, Nats: Nats{ - Url: *natsUrl, - Name: *natsName, - VerifyCertificates: *natsVerifyCertificates, - Ctx: ctx, + Url: *natsUrl, + Name: *natsName, + EnableTls: *natsEnableTls, + Ctx: ctx, + Cert: Tls{ + CertFile: *clientCrt, + KeyFile: *clientKey, + CaFile: *serverCA, + }, }, Mongo: Mongo{ Uri: *mongoUri, diff --git a/backend/services/controller/internal/nats/nats.go b/backend/services/controller/internal/nats/nats.go index 3786604..394a1f2 100644 --- a/backend/services/controller/internal/nats/nats.go +++ b/backend/services/controller/internal/nats/nats.go @@ -77,8 +77,10 @@ func defineOptions(c config.Nats) []nats.Option { opts = append(opts, nats.ClosedHandler(func(nc *nats.Conn) { log.Printf("Connection closed. Reason: %q\n", nc.LastError()) })) - if c.VerifyCertificates { - opts = append(opts, nats.RootCAs()) + if c.EnableTls { + log.Printf("Load certificates: %s and %s\n", c.Cert.CertFile, c.Cert.KeyFile) + opts = append(opts, nats.RootCAs(c.Cert.CaFile)) + opts = append(opts, nats.ClientCert(c.Cert.CertFile, c.Cert.KeyFile)) } return opts diff --git a/backend/services/mtp/adapter/internal/config/config.go b/backend/services/mtp/adapter/internal/config/config.go index 7562b36..25b0840 100644 --- a/backend/services/mtp/adapter/internal/config/config.go +++ b/backend/services/mtp/adapter/internal/config/config.go @@ -13,10 +13,17 @@ import ( const LOCAL_ENV = ".env.local" type Nats struct { - Url string - Name string - VerifyCertificates bool - Ctx context.Context + Url string + Name string + EnableTls bool + Cert Tls + Ctx context.Context +} + +type Tls struct { + CertFile string + KeyFile string + CaFile string } type Mongo struct { @@ -42,7 +49,10 @@ func NewConfig() *Config { natsUrl := flag.String("nats_url", lookupEnvOrString("NATS_URL", "nats://localhost:4222"), "url for nats server") natsName := flag.String("nats_name", lookupEnvOrString("NATS_NAME", "adapter"), "name for nats client") - natsVerifyCertificates := flag.Bool("nats_verify_certificates", lookupEnvOrBool("NATS_VERIFY_CERTIFICATES", false), "verify validity of certificates from nats server") + natsEnableTls := flag.Bool("nats_enable_tls", lookupEnvOrBool("NATS_ENABLE_TLS", false), "enbale TLS to nats server") + clientCrt := flag.String("client_crt", lookupEnvOrString("CLIENT_CRT", "cert.pem"), "client certificate file to TLS connection") + clientKey := flag.String("client_key", lookupEnvOrString("CLIENT_KEY", "key.pem"), "client key file to TLS connection") + serverCA := flag.String("server_ca", lookupEnvOrString("SERVER_CA", "rootCA.pem"), "server CA file to TLS connection") mongoUri := flag.String("mongo_uri", lookupEnvOrString("MONGO_URI", "mongodb://localhost:27017"), "uri for mongodb server") controllerId := flag.String("controller_id", lookupEnvOrString("CONTROLLER_ID", "oktopusController"), "usp controller endpoint id") controllerPassword := flag.String("controller_passwd", lookupEnvOrString("CONTROLLER_PASSWORD", ""), "usp controller endpoint password to connect to") @@ -66,10 +76,15 @@ func NewConfig() *Config { return &Config{ Nats: Nats{ - Url: *natsUrl, - Name: *natsName, - VerifyCertificates: *natsVerifyCertificates, - Ctx: ctx, + Url: *natsUrl, + Name: *natsName, + EnableTls: *natsEnableTls, + Ctx: ctx, + Cert: Tls{ + CertFile: *clientCrt, + KeyFile: *clientKey, + CaFile: *serverCA, + }, }, Mongo: Mongo{ Uri: *mongoUri, diff --git a/backend/services/mtp/adapter/internal/nats/nats.go b/backend/services/mtp/adapter/internal/nats/nats.go index 7985275..9396d7f 100644 --- a/backend/services/mtp/adapter/internal/nats/nats.go +++ b/backend/services/mtp/adapter/internal/nats/nats.go @@ -150,8 +150,10 @@ func defineOptions(c config.Nats) []nats.Option { opts = append(opts, nats.ClosedHandler(func(nc *nats.Conn) { log.Printf("Connection closed. Reason: %q\n", nc.LastError()) })) - if c.VerifyCertificates { - opts = append(opts, nats.RootCAs()) + if c.EnableTls { + log.Printf("Load certificates: %s and %s\n", c.Cert.CertFile, c.Cert.KeyFile) + opts = append(opts, nats.RootCAs(c.Cert.CaFile)) + opts = append(opts, nats.ClientCert(c.Cert.CertFile, c.Cert.KeyFile)) } return opts diff --git a/backend/services/mtp/mqtt-adapter/internal/config/config.go b/backend/services/mtp/mqtt-adapter/internal/config/config.go index 9bac4a7..0cfd6c6 100644 --- a/backend/services/mtp/mqtt-adapter/internal/config/config.go +++ b/backend/services/mtp/mqtt-adapter/internal/config/config.go @@ -13,10 +13,17 @@ import ( const LOCAL_ENV = ".env.local" type Nats struct { - Url string - Name string - VerifyCertificates bool - Ctx context.Context + Url string + Name string + EnableTls bool + Cert Tls + Ctx context.Context +} + +type Tls struct { + CertFile string + KeyFile string + CaFile string } type Mqtt struct { @@ -42,7 +49,10 @@ func NewConfig() *Config { natsUrl := flag.String("nats_url", lookupEnvOrString("NATS_URL", "nats://localhost:4222"), "url for nats server") natsName := flag.String("nats_name", lookupEnvOrString("NATS_NAME", "mqtt-adapter"), "name for nats client") - natsVerifyCertificates := flag.Bool("nats_verify_certificates", lookupEnvOrBool("NATS_VERIFY_CERTIFICATES", false), "verify validity of certificates from nats server") + natsEnableTls := flag.Bool("nats_enable_tls", lookupEnvOrBool("NATS_ENABLE_TLS", false), "enbale TLS to nats server") + clientCrt := flag.String("client_crt", lookupEnvOrString("CLIENT_CRT", "cert.pem"), "client certificate file to TLS connection") + clientKey := flag.String("client_key", lookupEnvOrString("CLIENT_KEY", "key.pem"), "client key file to TLS connection") + serverCA := flag.String("server_ca", lookupEnvOrString("SERVER_CA", "rootCA.pem"), "server CA file to TLS connection") mqttUrl := flag.String("mqtt_url", lookupEnvOrString("MQTT_URL", "tcp://localhost:1883"), "url for mqtt server") mqttsUrl := flag.String("mqtts_url", lookupEnvOrString("MQTTS_URL", ""), "url for mqtts server") mqttsSkipVerify := flag.Bool("mqtts_skip_verify", lookupEnvOrBool("MQTTS_SKIP_VERIFY", false), "skip verification of server certificate for mqtts") @@ -69,10 +79,15 @@ func NewConfig() *Config { return &Config{ Nats: Nats{ - Url: *natsUrl, - Name: *natsName, - VerifyCertificates: *natsVerifyCertificates, - Ctx: ctx, + Url: *natsUrl, + Name: *natsName, + EnableTls: *natsEnableTls, + Ctx: ctx, + Cert: Tls{ + CertFile: *clientCrt, + KeyFile: *clientKey, + CaFile: *serverCA, + }, }, Mqtt: Mqtt{ Url: *mqttUrl, diff --git a/backend/services/mtp/mqtt-adapter/internal/nats/nats.go b/backend/services/mtp/mqtt-adapter/internal/nats/nats.go index 7698d76..4e96605 100644 --- a/backend/services/mtp/mqtt-adapter/internal/nats/nats.go +++ b/backend/services/mtp/mqtt-adapter/internal/nats/nats.go @@ -92,8 +92,10 @@ func defineOptions(c config.Nats) []nats.Option { opts = append(opts, nats.ClosedHandler(func(nc *nats.Conn) { log.Printf("Connection closed. Reason: %q\n", nc.LastError()) })) - if c.VerifyCertificates { - opts = append(opts, nats.RootCAs()) + if c.EnableTls { + log.Printf("Load certificates: %s and %s\n", c.Cert.CertFile, c.Cert.KeyFile) + opts = append(opts, nats.RootCAs(c.Cert.CaFile)) + opts = append(opts, nats.ClientCert(c.Cert.CertFile, c.Cert.KeyFile)) } return opts diff --git a/backend/services/mtp/mqtt/internal/config/config.go b/backend/services/mtp/mqtt/internal/config/config.go index 805d4ae..e701117 100644 --- a/backend/services/mtp/mqtt/internal/config/config.go +++ b/backend/services/mtp/mqtt/internal/config/config.go @@ -32,10 +32,17 @@ type Config struct { } type Nats struct { - Url string - Name string - VerifyCertificates bool - Ctx context.Context + Url string + Name string + EnableTls bool + Cert Tls + Ctx context.Context +} + +type Tls struct { + CertFile string + KeyFile string + CaFile string } func NewConfig() Config { @@ -66,7 +73,10 @@ func NewConfig() Config { logLevel := flag.Int("log_level", lookupEnvOrInt("LOG_LEVEL", 1), "0=DEBUG, 1=INFO, 2=WARNING, 3=ERROR") natsUrl := flag.String("nats_url", lookupEnvOrString("NATS_URL", "nats://localhost:4222"), "url for nats server") natsName := flag.String("nats_name", lookupEnvOrString("NATS_NAME", "adapter"), "name for nats client") - natsVerifyCertificates := flag.Bool("nats_verify_certificates", lookupEnvOrBool("NATS_VERIFY_CERTIFICATES", false), "verify validity of certificates from nats server") + natsEnableTls := flag.Bool("nats_enable_tls", lookupEnvOrBool("NATS_ENABLE_TLS", false), "enbale TLS to nats server") + clientCrt := flag.String("client_crt", lookupEnvOrString("CLIENT_CRT", "cert.pem"), "client certificate file to TLS connection") + clientKey := flag.String("client_key", lookupEnvOrString("CLIENT_KEY", "key.pem"), "client key file to TLS connection") + serverCA := flag.String("server_ca", lookupEnvOrString("SERVER_CA", "rootCA.pem"), "server CA file to TLS connection") flag.Parse() flHelp := flag.Bool("help", false, "Help") @@ -99,10 +109,15 @@ func NewConfig() Config { HttpPort: *httpPort, LogLevel: *logLevel, Nats: Nats{ - Url: *natsUrl, - Name: *natsName, - VerifyCertificates: *natsVerifyCertificates, - Ctx: ctx, + Url: *natsUrl, + Name: *natsName, + EnableTls: *natsEnableTls, + Ctx: ctx, + Cert: Tls{ + CertFile: *clientCrt, + KeyFile: *clientKey, + CaFile: *serverCA, + }, }, } diff --git a/backend/services/mtp/mqtt/internal/nats/nats.go b/backend/services/mtp/mqtt/internal/nats/nats.go index 1eb51a4..d1548a3 100644 --- a/backend/services/mtp/mqtt/internal/nats/nats.go +++ b/backend/services/mtp/mqtt/internal/nats/nats.go @@ -74,8 +74,10 @@ func defineOptions(c config.Nats) []nats.Option { opts = append(opts, nats.ClosedHandler(func(nc *nats.Conn) { log.Printf("Connection closed. Reason: %q\n", nc.LastError()) })) - if c.VerifyCertificates { - opts = append(opts, nats.RootCAs()) + if c.EnableTls { + log.Printf("Load certificates: %s and %s\n", c.Cert.CertFile, c.Cert.KeyFile) + opts = append(opts, nats.RootCAs(c.Cert.CaFile)) + opts = append(opts, nats.ClientCert(c.Cert.CertFile, c.Cert.KeyFile)) } return opts diff --git a/backend/services/mtp/stomp-adapter/internal/config/config.go b/backend/services/mtp/stomp-adapter/internal/config/config.go index f79de27..2b54a40 100644 --- a/backend/services/mtp/stomp-adapter/internal/config/config.go +++ b/backend/services/mtp/stomp-adapter/internal/config/config.go @@ -13,10 +13,17 @@ import ( const LOCAL_ENV = ".env.local" type Nats struct { - Url string - Name string - VerifyCertificates bool - Ctx context.Context + Url string + Name string + EnableTls bool + Cert Tls + Ctx context.Context +} + +type Tls struct { + CertFile string + KeyFile string + CaFile string } type Stomp struct { @@ -37,7 +44,10 @@ func NewConfig() *Config { natsUrl := flag.String("nats_url", lookupEnvOrString("NATS_URL", "nats://localhost:4222"), "url for nats server") natsName := flag.String("nats_name", lookupEnvOrString("NATS_NAME", "mqtt-adapter"), "name for nats client") - natsVerifyCertificates := flag.Bool("nats_verify_certificates", lookupEnvOrBool("NATS_VERIFY_CERTIFICATES", false), "verify validity of certificates from nats server") + natsEnableTls := flag.Bool("nats_enable_tls", lookupEnvOrBool("NATS_ENABLE_TLS", false), "enbale TLS to nats server") + clientCrt := flag.String("client_crt", lookupEnvOrString("CLIENT_CRT", "cert.pem"), "client certificate file to TLS connection") + clientKey := flag.String("client_key", lookupEnvOrString("CLIENT_KEY", "key.pem"), "client key file to TLS connection") + serverCA := flag.String("server_ca", lookupEnvOrString("SERVER_CA", "rootCA.pem"), "server CA file to TLS connection") stompAddr := flag.String("stomp_server", lookupEnvOrString("STOMP_SERVER", "localhost:61613"), "STOMP server endpoint") stompUser := flag.String("stomp_user", lookupEnvOrString("STOMP_USER", ""), "stomp server user") stompPassword := flag.String("stomp_passsword", lookupEnvOrString("STOMP_PASSWD", ""), "stomp server password") @@ -61,10 +71,15 @@ func NewConfig() *Config { return &Config{ Nats: Nats{ - Url: *natsUrl, - Name: *natsName, - VerifyCertificates: *natsVerifyCertificates, - Ctx: ctx, + Url: *natsUrl, + Name: *natsName, + EnableTls: *natsEnableTls, + Ctx: ctx, + Cert: Tls{ + CertFile: *clientCrt, + KeyFile: *clientKey, + CaFile: *serverCA, + }, }, Stomp: Stomp{ Url: *stompAddr, diff --git a/backend/services/mtp/stomp-adapter/internal/nats/nats.go b/backend/services/mtp/stomp-adapter/internal/nats/nats.go index ad739fd..774ba86 100644 --- a/backend/services/mtp/stomp-adapter/internal/nats/nats.go +++ b/backend/services/mtp/stomp-adapter/internal/nats/nats.go @@ -81,9 +81,10 @@ func defineOptions(c config.Nats) []nats.Option { opts = append(opts, nats.ClosedHandler(func(nc *nats.Conn) { log.Printf("Connection closed. Reason: %q\n", nc.LastError()) })) - if c.VerifyCertificates { - opts = append(opts, nats.RootCAs()) + if c.EnableTls { + log.Printf("Load certificates: %s and %s\n", c.Cert.CertFile, c.Cert.KeyFile) + opts = append(opts, nats.RootCAs(c.Cert.CaFile)) + opts = append(opts, nats.ClientCert(c.Cert.CertFile, c.Cert.KeyFile)) } - return opts } diff --git a/backend/services/mtp/ws-adapter/internal/config/config.go b/backend/services/mtp/ws-adapter/internal/config/config.go index c78e4dd..cf4231b 100644 --- a/backend/services/mtp/ws-adapter/internal/config/config.go +++ b/backend/services/mtp/ws-adapter/internal/config/config.go @@ -13,10 +13,17 @@ import ( const LOCAL_ENV = ".env.local" type Nats struct { - Url string - Name string - VerifyCertificates bool - Ctx context.Context + Url string + Name string + EnableTls bool + Cert Tls + Ctx context.Context +} + +type Tls struct { + CertFile string + KeyFile string + CaFile string } type Ws struct { @@ -42,7 +49,10 @@ func NewConfig() *Config { natsUrl := flag.String("nats_url", lookupEnvOrString("NATS_URL", "nats://localhost:4222"), "url for nats server") natsName := flag.String("nats_name", lookupEnvOrString("NATS_NAME", "ws-adapter"), "name for nats client") - natsVerifyCertificates := flag.Bool("nats_verify_certificates", lookupEnvOrBool("NATS_VERIFY_CERTIFICATES", false), "verify validity of certificates from nats server") + natsEnableTls := flag.Bool("nats_enable_tls", lookupEnvOrBool("NATS_ENABLE_TLS", false), "enbale TLS to nats server") + clientCrt := flag.String("client_crt", lookupEnvOrString("CLIENT_CRT", "cert.pem"), "client certificate file to TLS connection") + clientKey := flag.String("client_key", lookupEnvOrString("CLIENT_KEY", "key.pem"), "client key file to TLS connection") + serverCA := flag.String("server_ca", lookupEnvOrString("SERVER_CA", "rootCA.pem"), "server CA file to TLS connection") wsAuthEnable := flag.Bool("ws_auth_enable", lookupEnvOrBool("WS_AUTH_ENABLE", false), "enable authentication for websocket server") wsAddr := flag.String("ws_addr", lookupEnvOrString("WS_ADDR", "localhost"), "websocket server address (domain or ip)") wsPort := flag.String("ws_port", lookupEnvOrString("WS_PORT", ":8080"), "websocket server port") @@ -68,10 +78,15 @@ func NewConfig() *Config { return &Config{ Nats: Nats{ - Url: *natsUrl, - Name: *natsName, - VerifyCertificates: *natsVerifyCertificates, - Ctx: ctx, + Url: *natsUrl, + Name: *natsName, + EnableTls: *natsEnableTls, + Ctx: ctx, + Cert: Tls{ + CertFile: *clientCrt, + KeyFile: *clientKey, + CaFile: *serverCA, + }, }, Ws: Ws{ AuthEnable: *wsAuthEnable, diff --git a/backend/services/mtp/ws-adapter/internal/nats/nats.go b/backend/services/mtp/ws-adapter/internal/nats/nats.go index c5a1de4..729129c 100644 --- a/backend/services/mtp/ws-adapter/internal/nats/nats.go +++ b/backend/services/mtp/ws-adapter/internal/nats/nats.go @@ -91,8 +91,10 @@ func defineOptions(c config.Nats) []nats.Option { opts = append(opts, nats.ClosedHandler(func(nc *nats.Conn) { log.Printf("Connection closed. Reason: %q\n", nc.LastError()) })) - if c.VerifyCertificates { - opts = append(opts, nats.RootCAs()) + if c.EnableTls { + log.Printf("Load certificates: %s and %s\n", c.Cert.CertFile, c.Cert.KeyFile) + opts = append(opts, nats.RootCAs(c.Cert.CaFile)) + opts = append(opts, nats.ClientCert(c.Cert.CertFile, c.Cert.KeyFile)) } return opts diff --git a/backend/services/mtp/ws/internal/config/config.go b/backend/services/mtp/ws/internal/config/config.go index f79f176..3080571 100644 --- a/backend/services/mtp/ws/internal/config/config.go +++ b/backend/services/mtp/ws/internal/config/config.go @@ -24,10 +24,17 @@ type Config struct { } type Nats struct { - Url string - Name string - VerifyCertificates bool - Ctx context.Context + Url string + Name string + EnableTls bool + Cert Tls + Ctx context.Context +} + +type Tls struct { + CertFile string + KeyFile string + CaFile string } func NewConfig() Config { @@ -47,7 +54,10 @@ func NewConfig() Config { /* ------------------------------ define flags ------------------------------ */ natsUrl := flag.String("nats_url", lookupEnvOrString("NATS_URL", "nats://localhost:4222"), "url for nats server") natsName := flag.String("nats_name", lookupEnvOrString("NATS_NAME", "ws-adapter"), "name for nats client") - natsVerifyCertificates := flag.Bool("nats_verify_certificates", lookupEnvOrBool("NATS_VERIFY_CERTIFICATES", false), "verify validity of certificates from nats server") + natsEnableTls := flag.Bool("nats_enable_tls", lookupEnvOrBool("NATS_ENABLE_TLS", false), "enbale TLS to nats server") + clientCrt := flag.String("client_crt", lookupEnvOrString("CLIENT_CRT", "cert.pem"), "client certificate file to TLS connection") + clientKey := flag.String("client_key", lookupEnvOrString("CLIENT_KEY", "key.pem"), "client key file to TLS connection") + serverCA := flag.String("server_ca", lookupEnvOrString("SERVER_CA", "rootCA.pem"), "server CA file to TLS connection") flPort := flag.String("port", lookupEnvOrString("SERVER_PORT", ":8080"), "Server port") flAuth := flag.Bool("auth", lookupEnvOrBool("SERVER_AUTH_ENABLE", false), "Server auth enable/disable") flControllerEid := flag.String("controller-eid", lookupEnvOrString("CONTROLLER_EID", "oktopusController"), "Controller eid") @@ -81,10 +91,15 @@ func NewConfig() Config { FullChain: *flFullchain, PrivateKey: *flPrivKey, Nats: Nats{ - Url: *natsUrl, - Name: *natsName, - VerifyCertificates: *natsVerifyCertificates, - Ctx: ctx, + Url: *natsUrl, + Name: *natsName, + EnableTls: *natsEnableTls, + Ctx: ctx, + Cert: Tls{ + CertFile: *clientCrt, + KeyFile: *clientKey, + CaFile: *serverCA, + }, }, } } diff --git a/backend/services/mtp/ws/internal/nats/nats.go b/backend/services/mtp/ws/internal/nats/nats.go index a5a557d..2afc947 100644 --- a/backend/services/mtp/ws/internal/nats/nats.go +++ b/backend/services/mtp/ws/internal/nats/nats.go @@ -66,8 +66,10 @@ func defineOptions(c config.Nats) []nats.Option { opts = append(opts, nats.ClosedHandler(func(nc *nats.Conn) { log.Printf("Connection closed. Reason: %q\n", nc.LastError()) })) - if c.VerifyCertificates { - opts = append(opts, nats.RootCAs()) + if c.EnableTls { + log.Printf("Load certificates: %s and %s\n", c.Cert.CertFile, c.Cert.KeyFile) + opts = append(opts, nats.RootCAs(c.Cert.CaFile)) + opts = append(opts, nats.ClientCert(c.Cert.CertFile, c.Cert.KeyFile)) } return opts From bfdf5e125d356592eee64237cf88cc0917fe5155 Mon Sep 17 00:00:00 2001 From: Adriano Chiesa <citeb.adriano@intelbras.com.br> Date: Sat, 6 Jul 2024 13:26:43 -0300 Subject: [PATCH 10/28] Add TLS config to local deploy compose To not run with TLS, you need to set the NATS_ENABLE_TLS flag to false and remove the TLS configuration from deploy/compose/nats_config/nats.cfg --- deploy/compose/.env.acs | 6 +- deploy/compose/.env.adapter | 8 +- deploy/compose/.env.controller | 9 +- deploy/compose/.env.mqtt | 6 +- deploy/compose/.env.mqtt-adapter | 8 +- deploy/compose/.env.nats | 3 + deploy/compose/.env.socketio | 6 +- deploy/compose/.env.stomp-adapter | 6 +- deploy/compose/.env.ws | 6 +- deploy/compose/.env.ws-adapter | 8 +- deploy/compose/docker-compose.yaml | 113 +++++++++++++++----------- deploy/compose/nats_config/cert.pem | 26 ++++++ deploy/compose/nats_config/key.pem | 28 +++++++ deploy/compose/nats_config/nats.cfg | 18 ++++ deploy/compose/nats_config/rootCA.pem | 29 +++++++ 15 files changed, 220 insertions(+), 60 deletions(-) create mode 100644 deploy/compose/.env.nats create mode 100644 deploy/compose/nats_config/cert.pem create mode 100644 deploy/compose/nats_config/key.pem create mode 100644 deploy/compose/nats_config/nats.cfg create mode 100644 deploy/compose/nats_config/rootCA.pem diff --git a/deploy/compose/.env.acs b/deploy/compose/.env.acs index 9af7128..472de27 100644 --- a/deploy/compose/.env.acs +++ b/deploy/compose/.env.acs @@ -1 +1,5 @@ -NATS_URL=nats://msg_broker:4222 \ No newline at end of file +NATS_URL=nats://oktopususer:oktopuspw@msg_broker:4222 +NATS_ENABLE_TLS="true" +CLIENT_CRT=/tmp/nats/config/cert.pem +CLIENT_KEY=/tmp/nats/config/key.pem +SERVER_CA=/tmp/nats/config/rootCA.pem diff --git a/deploy/compose/.env.adapter b/deploy/compose/.env.adapter index 70fd6fe..d3dcd0d 100644 --- a/deploy/compose/.env.adapter +++ b/deploy/compose/.env.adapter @@ -1,2 +1,6 @@ -NATS_URL=nats://msg_broker:4222 -MONGO_URI=mongodb://mongo_usp:27017 \ No newline at end of file +MONGO_URI=mongodb://mongo_usp:27017 +NATS_URL=nats://oktopususer:oktopuspw@msg_broker:4222 +NATS_ENABLE_TLS="true" +CLIENT_CRT=/tmp/nats/config/cert.pem +CLIENT_KEY=/tmp/nats/config/key.pem +SERVER_CA=/tmp/nats/config/rootCA.pem diff --git a/deploy/compose/.env.controller b/deploy/compose/.env.controller index 70fd6fe..3f82d05 100644 --- a/deploy/compose/.env.controller +++ b/deploy/compose/.env.controller @@ -1,2 +1,7 @@ -NATS_URL=nats://msg_broker:4222 -MONGO_URI=mongodb://mongo_usp:27017 \ No newline at end of file +MONGO_URI=mongodb://mongo_usp:27017 +ENTERPRISE="true" +NATS_URL=nats://oktopususer:oktopuspw@msg_broker:4222 +NATS_ENABLE_TLS="true" +CLIENT_CRT=/tmp/nats/config/cert.pem +CLIENT_KEY=/tmp/nats/config/key.pem +SERVER_CA=/tmp/nats/config/rootCA.pem diff --git a/deploy/compose/.env.mqtt b/deploy/compose/.env.mqtt index e0c2d35..4f467a1 100644 --- a/deploy/compose/.env.mqtt +++ b/deploy/compose/.env.mqtt @@ -1,3 +1,7 @@ REDIS_ENABLE=false REDIS_ADDR=redis_usp:6379 -NATS_URL=nats://msg_broker:4222 \ No newline at end of file +NATS_URL=nats://oktopususer:oktopuspw@msg_broker:4222 +NATS_ENABLE_TLS="true" +CLIENT_CRT=/tmp/nats/config/cert.pem +CLIENT_KEY=/tmp/nats/config/key.pem +SERVER_CA=/tmp/nats/config/rootCA.pem diff --git a/deploy/compose/.env.mqtt-adapter b/deploy/compose/.env.mqtt-adapter index e567b54..569032b 100644 --- a/deploy/compose/.env.mqtt-adapter +++ b/deploy/compose/.env.mqtt-adapter @@ -1,2 +1,6 @@ -NATS_URL=nats://msg_broker:4222 -MQTT_URL=tcp://mqtt:1883 \ No newline at end of file +MQTT_URL=tcp://mqtt:1883 +NATS_URL=nats://oktopususer:oktopuspw@msg_broker:4222 +NATS_ENABLE_TLS="true" +CLIENT_CRT=/tmp/nats/config/cert.pem +CLIENT_KEY=/tmp/nats/config/key.pem +SERVER_CA=/tmp/nats/config/rootCA.pem diff --git a/deploy/compose/.env.nats b/deploy/compose/.env.nats new file mode 100644 index 0000000..95fa9bc --- /dev/null +++ b/deploy/compose/.env.nats @@ -0,0 +1,3 @@ +NATS_NAME=oktopus +NATS_USER=oktopususer +NATS_PW=oktopuspw diff --git a/deploy/compose/.env.socketio b/deploy/compose/.env.socketio index 9af7128..472de27 100644 --- a/deploy/compose/.env.socketio +++ b/deploy/compose/.env.socketio @@ -1 +1,5 @@ -NATS_URL=nats://msg_broker:4222 \ No newline at end of file +NATS_URL=nats://oktopususer:oktopuspw@msg_broker:4222 +NATS_ENABLE_TLS="true" +CLIENT_CRT=/tmp/nats/config/cert.pem +CLIENT_KEY=/tmp/nats/config/key.pem +SERVER_CA=/tmp/nats/config/rootCA.pem diff --git a/deploy/compose/.env.stomp-adapter b/deploy/compose/.env.stomp-adapter index cdd100a..8754e23 100644 --- a/deploy/compose/.env.stomp-adapter +++ b/deploy/compose/.env.stomp-adapter @@ -1,2 +1,6 @@ -NATS_URL=nats://msg_broker:4222 STOMP_SERVER=stomp:61613 +NATS_URL=nats://oktopususer:oktopuspw@msg_broker:4222 +NATS_ENABLE_TLS="true" +CLIENT_CRT=/tmp/nats/config/cert.pem +CLIENT_KEY=/tmp/nats/config/key.pem +SERVER_CA=/tmp/nats/config/rootCA.pem diff --git a/deploy/compose/.env.ws b/deploy/compose/.env.ws index 9af7128..472de27 100644 --- a/deploy/compose/.env.ws +++ b/deploy/compose/.env.ws @@ -1 +1,5 @@ -NATS_URL=nats://msg_broker:4222 \ No newline at end of file +NATS_URL=nats://oktopususer:oktopuspw@msg_broker:4222 +NATS_ENABLE_TLS="true" +CLIENT_CRT=/tmp/nats/config/cert.pem +CLIENT_KEY=/tmp/nats/config/key.pem +SERVER_CA=/tmp/nats/config/rootCA.pem diff --git a/deploy/compose/.env.ws-adapter b/deploy/compose/.env.ws-adapter index 13fcdca..8454cce 100644 --- a/deploy/compose/.env.ws-adapter +++ b/deploy/compose/.env.ws-adapter @@ -1,2 +1,6 @@ -NATS_URL=nats://msg_broker:4222 -WS_ADDR=ws \ No newline at end of file +WS_ADDR=ws +NATS_URL=nats://oktopususer:oktopuspw@msg_broker:4222 +NATS_ENABLE_TLS="true" +CLIENT_CRT=/tmp/nats/config/cert.pem +CLIENT_KEY=/tmp/nats/config/key.pem +SERVER_CA=/tmp/nats/config/rootCA.pem diff --git a/deploy/compose/docker-compose.yaml b/deploy/compose/docker-compose.yaml index 6695559..3c59caa 100644 --- a/deploy/compose/docker-compose.yaml +++ b/deploy/compose/docker-compose.yaml @@ -1,38 +1,42 @@ services: - -#/* ----------------------------- Message Broker ----------------------------- */ + #/* ----------------------------- Message Broker ----------------------------- */ msg_broker: - image: 'nats:latest' + image: "nats:latest" container_name: nats ports: - 4222:4222 - 8222:8222 - command: -n oktopus -m 8222 -js + command: -c /tmp/nats/config/nats.cfg + env_file: + - .env.nats volumes: - ./nats_data:/tmp/nats/jetstream + - ./nats_config:/tmp/nats/config networks: usp_network: ipv4_address: 172.16.235.2 profiles: [nats] -#/* -------------------------------------------------------------------------- */ + #/* -------------------------------------------------------------------------- */ -#/* ------------------------ API REST / USP Controller ----------------------- */ + #/* ------------------------ API REST / USP Controller ----------------------- */ controller: - image: 'oktopusp/controller' + image: "oktopusp/controller" container_name: controller ports: - - 8000:8000 + - 8000:8000 depends_on: - - mongo_usp + - mongo_usp env_file: - .env.controller + volumes: + - ./nats_config:/tmp/nats/config networks: usp_network: ipv4_address: 172.16.235.3 profiles: [controller] -#/* -------------------------------------------------------------------------- */ + #/* -------------------------------------------------------------------------- */ -#/* ---------------------------- Databases / Cache --------------------------- */ + #/* ---------------------------- Databases / Cache --------------------------- */ mongo_usp: image: mongo container_name: mongo_usp @@ -43,115 +47,128 @@ services: ipv4_address: 172.16.235.4 volumes: - ./mongo_data:/data/db - profiles: [controller,adapter] + profiles: [controller, adapter] -#/* -------------------------------------------------------------------------- */ + #/* -------------------------------------------------------------------------- */ -#/* ----------------------- Message Transfer Protocols ----------------------- */ + #/* ----------------------- Message Transfer Protocols ----------------------- */ mqtt: - image: 'oktopusp/mqtt' + image: "oktopusp/mqtt" container_name: mqtt ports: - - 1883:1883 - - 8883:8883 + - 1883:1883 + - 8883:8883 env_file: - .env.mqtt + volumes: + - ./nats_config:/tmp/nats/config networks: usp_network: ipv4_address: 172.16.235.6 profiles: [mqtt] ws: - image: 'oktopusp/ws' + image: "oktopusp/ws" container_name: websockets ports: - - 8080:8080 + - 8080:8080 env_file: - .env.ws + volumes: + - ./nats_config:/tmp/nats/config networks: usp_network: ipv4_address: 172.16.235.7 profiles: [ws] - + stomp: - image: 'oktopusp/stomp' + image: "oktopusp/stomp" container_name: stomp ports: - - 61613:61613 + - 61613:61613 networks: usp_network: ipv4_address: 172.16.235.8 profiles: [stomp] -#/* -------------------------------------------------------------------------- */ + #/* -------------------------------------------------------------------------- */ -#/* --------------- Message transfer Protocols Adapters to NATS -------------- */ + #/* --------------- Message transfer Protocols Adapters to NATS -------------- */ mqtt-adapter: - image: 'oktopusp/mqtt-adapter' + image: "oktopusp/mqtt-adapter" container_name: mqtt-adapter depends_on: - mqtt env_file: - .env.mqtt-adapter + volumes: + - ./nats_config:/tmp/nats/config networks: usp_network: ipv4_address: 172.16.235.9 profiles: [mqtt] - ws-adapter: - image: 'oktopusp/ws-adapter' + image: "oktopusp/ws-adapter" container_name: ws-adapter depends_on: - ws env_file: - .env.ws-adapter + volumes: + - ./nats_config:/tmp/nats/config networks: usp_network: ipv4_address: 172.16.235.10 profiles: [ws] - + stomp-adapter: - image: 'oktopusp/stomp-adapter' + image: "oktopusp/stomp-adapter" container_name: stomp-adapter depends_on: - stomp env_file: - .env.stomp-adapter + volumes: + - ./nats_config:/tmp/nats/config networks: usp_network: ipv4_address: 172.16.235.11 profiles: [stomp] adapter: - image: 'oktopusp/adapter' + image: "oktopusp/adapter" container_name: adapter depends_on: - mongo_usp env_file: - .env.adapter + volumes: + - ./nats_config:/tmp/nats/config networks: usp_network: ipv4_address: 172.16.235.12 profiles: [adapter] -#/* -------------------------------------------------------------------------- */ + #/* -------------------------------------------------------------------------- */ -#/* ------------- SocketIO Real Time Communication With Frontend ------------- */ + #/* ------------- SocketIO Real Time Communication With Frontend ------------- */ socketio: - image: 'oktopusp/socketio' + image: "oktopusp/socketio" container_name: socketio ports: - - 5000:5000 + - 5000:5000 env_file: - .env.socketio + volumes: + - ./nats_config:/tmp/nats/config networks: usp_network: ipv4_address: 172.16.235.13 profiles: [frontend] -#/* -------------------------------------------------------------------------- */ + #/* -------------------------------------------------------------------------- */ -#/* -------------------------------- Frontend -------------------------------- */ + #/* -------------------------------- Frontend -------------------------------- */ frontend: - image: 'oktopusp/frontend-ce' + image: "oktopusp/frontend-ce" container_name: frontend ports: - 3000:3000 @@ -159,7 +176,7 @@ services: usp_network: ipv4_address: 172.16.235.14 profiles: [frontend] -#/* -------------------------------------------------------------------------- */ + #/* -------------------------------------------------------------------------- */ portainer: image: portainer/portainer-ce:latest @@ -173,28 +190,30 @@ services: volumes: - /var/run/docker.sock:/var/run/docker.sock - ./portainer_data:/data - + acs: - image: oktopusp/acs + image: "oktopusp/acs" container_name: acs ports: - 9292:9292 env_file: - .env.acs + volumes: + - ./nats_config:/tmp/nats/config networks: usp_network: ipv4_address: 172.16.235.16 profiles: [cwmp] - + nginx: image: nginx:latest container_name: nginx ports: - 80:80 # depends_on: - # - frontend - # - controller - # - socketio + # - frontend + # - controller + # - socketio volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf extra_hosts: @@ -202,7 +221,7 @@ services: networks: usp_network: ipv4_address: 172.16.235.17 - + file-server: image: oktopusp/file-server container_name: file-server @@ -218,9 +237,9 @@ services: ipv4_address: 172.16.235.18 networks: - usp_network: + usp_network: driver: bridge - ipam: + ipam: driver: default config: - subnet: 172.16.235.0/24 diff --git a/deploy/compose/nats_config/cert.pem b/deploy/compose/nats_config/cert.pem new file mode 100644 index 0000000..0cf14cb --- /dev/null +++ b/deploy/compose/nats_config/cert.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEeTCCAuGgAwIBAgIRAJenDys60PD3Cjh9VXeAzsowDQYJKoZIhvcNAQELBQAw +gY0xHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTExMC8GA1UECwwoY2hp +ZXNhQEFkcmlhbm9NYWMubG9jYWwgKEFkcmlhbm8gQ2hpZXNhKTE4MDYGA1UEAwwv +bWtjZXJ0IGNoaWVzYUBBZHJpYW5vTWFjLmxvY2FsIChBZHJpYW5vIENoaWVzYSkw +HhcNMjQwNzA2MTQzODQ2WhcNMjYxMDA2MTQzODQ2WjBcMScwJQYDVQQKEx5ta2Nl +cnQgZGV2ZWxvcG1lbnQgY2VydGlmaWNhdGUxMTAvBgNVBAsMKGNoaWVzYUBBZHJp +YW5vTWFjLmxvY2FsIChBZHJpYW5vIENoaWVzYSkwggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQCdQCgwn7M5jtkNzYv/yUelUV84ehsYjPTQspCXCjDdUltO +E6jafRM/2SopUWwzPTorIZkerIpqq/avi77e6wSdCKJZBA5DsrMSOmFjjMqG4H2b +euzr5b+ZKzKnw4nF/CuB2cmWkz/qAiluqVdRl+Gef/Ouyuer/NuhJhK6Mgy1VuAd +ynwizGsAgQ/oAGDuHWmcMhGw/2NVTnXqHei5ntr+F4r08YOgXnRsGId+16nJU5xd +Xs80sw2dFl+1zQiFJxWjVQk65CyZfLDcwC7e3PAMsT+8obzuBKCn6fW3JiIZ68tc +o8GZClHSkeS7IMp+/wtjMXf1iipE0bRWzzdl1xQnAgMBAAGjgYMwgYAwDgYDVR0P +AQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMB8GA1UdIwQYMBaAFH2KEsMw +S7/Qu1eE1DxRhZTI0Z7RMDgGA1UdEQQxMC+CCm1zZ19icm9rZXKCCWxvY2FsaG9z +dIcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAYEAVr06 +Kg/FaWxUbD6Q+e5OLzckeeFZOKcb/2mmEH+e99HebnCuydiiGebZVWcLsoOw+gmb +WG/4rFyp9D/ZYFiQxWNRm/nCcxiHIV+JHDxhBn3vKENxYNNbr+WTMBTqmaYqPARK +SelnX+XDRTZUnoRBo03wZFYwOYwHUFWrlMFz+o6jOo2KZa4J+pcEDbttkJEoVhiV +C8EKIxSsG9TuW4lh5UySfB62ZfQBNd8SyhD4LF5JHFbBV9ysnylg/8gVvavjHWtM +FleOk/lNi9oj2n8uQEGbBT6FpTaKKJDiF85E3J4fuv8iNiFy2nWhCpJFF9K5/hu6 +s41Y2ZRb7luBoUj+CQWlTpGjCJPzzWmSkZUjf5kRP6b01bAXoLBzBBn50KWf8DWw +Rx7VaHtzbBRxfzESQxslGRgZvz2KxjY/x2jH6xdW+bzt93aHkgeGkDrjpY9q/uQF +SyEJYHIDt+FZupcc3KjvTY2p+icbckOrwQAtVWXzEjCsnDjf33+O7VGdACDm +-----END CERTIFICATE----- diff --git a/deploy/compose/nats_config/key.pem b/deploy/compose/nats_config/key.pem new file mode 100644 index 0000000..83486a9 --- /dev/null +++ b/deploy/compose/nats_config/key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCdQCgwn7M5jtkN +zYv/yUelUV84ehsYjPTQspCXCjDdUltOE6jafRM/2SopUWwzPTorIZkerIpqq/av +i77e6wSdCKJZBA5DsrMSOmFjjMqG4H2beuzr5b+ZKzKnw4nF/CuB2cmWkz/qAilu +qVdRl+Gef/Ouyuer/NuhJhK6Mgy1VuAdynwizGsAgQ/oAGDuHWmcMhGw/2NVTnXq +Hei5ntr+F4r08YOgXnRsGId+16nJU5xdXs80sw2dFl+1zQiFJxWjVQk65CyZfLDc +wC7e3PAMsT+8obzuBKCn6fW3JiIZ68tco8GZClHSkeS7IMp+/wtjMXf1iipE0bRW +zzdl1xQnAgMBAAECggEAMRDQuYNLJ/2DioQFV/WVDmdaf8PR6pIo3WmqJga/An/t +D2qg+DOoqvZ26leGnGJRYR3lqiWKNwibO2EuWF4anWkRRxc14DfFGj3vH2HR2832 +Q2pSvLR+WSuabbBcr9MkPCsZdItTmQ+9n9Lk9QegFZW1Emgra4XFff3kQAbX4kjR +ATyEsvZsgnkK5jHi5bzVHEKY730MKyhYzmHmUgxH1ibD5x+tnFtRiBGK+qY24erW +rw+wu0YKu1ESp62orYDnaA8MH7aWlFZLniPB4liA0h0bElrgHA8Zndx+cEtXqF4m +5+igR3MjXbg2at80ah++z6EGUTtEPccRSpcA7qFqMQKBgQDFVp1msdn45Xwquw+8 +CSZ45KAurvQEPNxtK/GMHtR5LjRZlKQEpNy3AVjz56azv72kqUfhNsP+ZNokMQTH +NTMvdkLzcwKe3qCnsfBRlSkRBVCwan266oCJW8SSX8pdaoME9lzc2BfMVAxJ1n+Y +289ZHXyyPYdNd+L4FxjreJDkKQKBgQDL/uD+kCSKZ/dYVZqwF06DIzYiBv1Xjne3 +3H90J0h/2iU9/yX9UsCS7D5RWbVROUyMh64fpH0f2lVFT8Xo3SjBWISrMovOxEzl +C2wZwM2VQmrfhiUg9h46drJSl/JL9/V/QOnpuQe35bVWjROtFStaZagUK/3vx1hh +xCV4iVS/zwKBgQC2Ea3zvA/x9jlTa3ee84pNbBLmP4DgEA8Hos2fjCpZC+o85ElY +B4ukRVf+4TILEdM1AwJQpii6o+4oChnwegMZvTEUUH6QebMcRa4Gd2qGS7MgsYAD +XqztDoAU1NBu1ADCKVOQZse+O6WC0qazL8rk27Ha+a3GKeB9KUJSrtBv0QKBgCi5 +IftPlSvYI2WL+Uxr6q19KwJR+OMwuq+Goh7y9KMpTkP5GoFesrjh1nLxAKRNVv26 +3ETO1ne0Y09p5G1fMRKf9CQk/Anz4BHdXOArQB8q2iDzK5hP6arsJR8d3C3UOzsD +H28cE/FfNvsnQKVN05DBOHOGcLQcTIV/3acZa0S7AoGBAJqSujzhVkewH7wJWOOh +92F66vMnCsE4Qn10h84GQQWORgjr8z8d8UbW7VrobVfZP74IBqo3ZUBYm85+lUFu +FWHjTkXoZzwP8IvMW0gE2iC3W4QSr2Z96a6RlRJdxC70LB+Pm6FFwLTloKBGK8Bf +Kp53gUDjsvJcQGv6nOty0uHQ +-----END PRIVATE KEY----- diff --git a/deploy/compose/nats_config/nats.cfg b/deploy/compose/nats_config/nats.cfg new file mode 100644 index 0000000..ae6618f --- /dev/null +++ b/deploy/compose/nats_config/nats.cfg @@ -0,0 +1,18 @@ +server_name: $NATS_NAME +port: 4222 +http_port: 8222 +authorization: { + users: [ + {user: $NATS_USER, password: $NATS_PW} + ] +} +tls: { + cert_file: "/tmp/nats/config/cert.pem" + key_file: "/tmp/nats/config/key.pem" + ca_file: "/tmp/nats/config/rootCA.pem" +} +// enables jetstream, an empty block will enable and use defaults +jetstream { + // jetstream data will be in /data/nats-server/jetstream + store_dir: "/tmp/nats/jetstream" +} diff --git a/deploy/compose/nats_config/rootCA.pem b/deploy/compose/nats_config/rootCA.pem new file mode 100644 index 0000000..30288b2 --- /dev/null +++ b/deploy/compose/nats_config/rootCA.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE6zCCA1OgAwIBAgIQcw3hWvi1nfQFSkUzrcdrhTANBgkqhkiG9w0BAQsFADCB +jTEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMTEwLwYDVQQLDChjaGll +c2FAQWRyaWFub01hYy5sb2NhbCAoQWRyaWFubyBDaGllc2EpMTgwNgYDVQQDDC9t +a2NlcnQgY2hpZXNhQEFkcmlhbm9NYWMubG9jYWwgKEFkcmlhbm8gQ2hpZXNhKTAe +Fw0yMjA4MTIxNTM1NDdaFw0zMjA4MTIxNTM1NDdaMIGNMR4wHAYDVQQKExVta2Nl +cnQgZGV2ZWxvcG1lbnQgQ0ExMTAvBgNVBAsMKGNoaWVzYUBBZHJpYW5vTWFjLmxv +Y2FsIChBZHJpYW5vIENoaWVzYSkxODA2BgNVBAMML21rY2VydCBjaGllc2FAQWRy +aWFub01hYy5sb2NhbCAoQWRyaWFubyBDaGllc2EpMIIBojANBgkqhkiG9w0BAQEF +AAOCAY8AMIIBigKCAYEA4HBEZqDbKPB0StZX/ILPU1q2Tm/U4j32/HvaWOGt528q +15y5eqa4OqpYQ7waVhkSR+M6258yAk9BCCOGrI+oXcllO7LKv4bg/y+HVhfEg4E0 +fnZS583+VWrIcIKOENQQX1cbFnIqLldBL/ph6YNMDT8wOoLqtQ2vp1EiXFA3ghQs +fK3iqDL31SQ7cZhdw7hnNACgvIM6MipwFLYbMsm27eMa9fCL4ZB3xokhbYVBmEcT +RTdhGvcRBs+QF8fcP/Y6uC9y7q2ddeIYEAf5xCVc15QepPqVskuAM3rktzVpBKEl +lzJGtW3EWu3kojrrW8dYZkZFJkHy9ymZVNUPjpAGiIIylp1Rik/o9VttXi+BQmrV +l1tpkrU1JfhLcktjBtRxtvRJTARpSkMM8gVc1rA2KgNsULzF4Zaas12wUh7r/VzW +4lwKiV3vgAM0wE5Fo4NLmv53/j3w7yX9GtSq0Ck/cKu2k0cOe+PTlG9aQ5MdhLCx ++BwMij/ohBfh0qtgPVPdAgMBAAGjRTBDMA4GA1UdDwEB/wQEAwICBDASBgNVHRMB +Af8ECDAGAQH/AgEAMB0GA1UdDgQWBBR9ihLDMEu/0LtXhNQ8UYWUyNGe0TANBgkq +hkiG9w0BAQsFAAOCAYEAyGNw197jniIGfCpcArC2lHbzNIQCq97BXsOj4h+CaUB+ +ZfTpTOdtS5DK2fSQ4jwH6nzm7VQvrnHcbP2pCgVDWW9da+Acnh1U6dYXI1VPDDVA +KZGDiK63M6PneX3IDl2N2fubRDP1ALkZQ6zwjtJLC2nT6IVjAJdRNt8egbCUAyMb +nOb4F+Zrr0L2SILM0jHq9diJVpX4Lxqggldmd+5HdVGHoR8BGzw4MReuoRWOZtf/ +H+opFtJ2f8GNTWsWtmN7FsjrGOOBgREB/Tz7WCWgGEeXQveJEfkOIddVVgb/n9Ly +mEOTbtJj36o4JJzv0v42SdT1WUkdvc6AmLVmYFn0uaLohNLVelJmU+aGavuVFpFb +08uVsSl8JMBWwVO23CpuvZFbI/1BFVix85UZgDj8BnpAWlsJxPYV1HqxS2oJJJbn +zRf8/0xzFPrLirXzebIr4jiCRVBRamacQTxlvPUz6c6FLTQLaqQdUV/IZaXsXX38 +byH1ABmJPGjXLm1r2RWD +-----END CERTIFICATE----- From ba2c2f6318f957110f1446463916d0339d20bc48 Mon Sep 17 00:00:00 2001 From: leandrofars <leandrofars@gmail.com> Date: Sat, 6 Jul 2024 15:43:14 -0300 Subject: [PATCH 11/28] fix(compose): add ngnix depends_on --- deploy/compose/docker-compose.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/deploy/compose/docker-compose.yaml b/deploy/compose/docker-compose.yaml index 3c59caa..5261ff0 100644 --- a/deploy/compose/docker-compose.yaml +++ b/deploy/compose/docker-compose.yaml @@ -210,10 +210,10 @@ services: container_name: nginx ports: - 80:80 - # depends_on: - # - frontend - # - controller - # - socketio + depends_on: + - frontend + - controller + - socketio volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf extra_hosts: From 1ac139134bbdca299b9bd4f9e68fd63a8459999d Mon Sep 17 00:00:00 2001 From: leandrofars <leandrofars@gmail.com> Date: Mon, 8 Jul 2024 10:11:25 -0300 Subject: [PATCH 12/28] fix(frontend): map offline device status color --- frontend/src/pages/map.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/map.js b/frontend/src/pages/map.js index 18fd812..9aa7247 100644 --- a/frontend/src/pages/map.js +++ b/frontend/src/pages/map.js @@ -193,7 +193,7 @@ const Page = () => { <div> <div>Model: {activeMarkerdata.Model?activeMarkerdata.Model:activeMarkerdata.ProductClass}</div> <div>Alias: {activeMarkerdata.Alias}</div> - <div>Status: {activeMarkerdata.Status == 2 ? <span style={{color:"green"}}>online</span> : <span style={{color:"green"}}>offline</span>}</div> + <div>Status: {activeMarkerdata.Status == 2 ? <span style={{color:"green"}}>online</span> : <span style={{color:"red"}}>offline</span>}</div> </div> </div> : <p>no device info found</p>} From fd33b2e291fcec475eb8b78f5ff850e205ffe2ed Mon Sep 17 00:00:00 2001 From: leandrofars <leandrofars@gmail.com> Date: Mon, 8 Jul 2024 11:55:44 -0300 Subject: [PATCH 13/28] chore(nginx): add white label route + add websockets support for frontend --- deploy/compose/nginx.conf | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/deploy/compose/nginx.conf b/deploy/compose/nginx.conf index 4a762b8..64e9648 100644 --- a/deploy/compose/nginx.conf +++ b/deploy/compose/nginx.conf @@ -15,6 +15,12 @@ server { proxy_read_timeout 60; proxy_connect_timeout 60; proxy_redirect off; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; } location /api { proxy_pass http://host.docker.internal:8000; @@ -22,6 +28,12 @@ server { proxy_connect_timeout 60; proxy_redirect off; } + location /custom-frontend { + proxy_pass http://host.docker.internal:8005; + proxy_read_timeout 60; + proxy_connect_timeout 60; + proxy_redirect off; + } location /socket.io { proxy_pass http://host.docker.internal:5000; proxy_read_timeout 60; From 85f0a6b2cd1578dd99d8d86b45fd1f276f547c1d Mon Sep 17 00:00:00 2001 From: leandrofars <leandrofars@gmail.com> Date: Mon, 8 Jul 2024 14:39:00 -0300 Subject: [PATCH 14/28] feat(frontend) custom colors | close #296 --- frontend/src/layouts/auth/layout.js | 4 +- frontend/src/layouts/dashboard/side-nav.js | 10 ++-- frontend/src/pages/_app.js | 8 ++- .../overview/overview-tasks-progress.js | 2 +- .../src/sections/overview/overview-traffic.js | 1 - frontend/src/theme/colors.js | 45 +++++++++----- frontend/src/theme/create-palette.js | 58 +++++++++++++++---- 7 files changed, 93 insertions(+), 35 deletions(-) diff --git a/frontend/src/layouts/auth/layout.js b/frontend/src/layouts/auth/layout.js index 3d1cad8..bc85919 100644 --- a/frontend/src/layouts/auth/layout.js +++ b/frontend/src/layouts/auth/layout.js @@ -3,9 +3,11 @@ import NextLink from 'next/link'; import Link from 'next/link' import { Box, Typography, Unstable_Grid2 as Grid } from '@mui/material'; import { Logo } from 'src/components/logo'; +import { useTheme } from '@mui/material' export const Layout = (props) => { const { children } = props; + const theme = useTheme(); return ( <Box @@ -58,7 +60,7 @@ export const Layout = (props) => { lg={6} sx={{ alignItems: 'center', - background: 'radial-gradient(50% 50% at 50% 50%, #306D6F 0%, #255355 100%)', + background: `radial-gradient(50% 50% at 50% 50%, ${theme.palette.primary.main} 0%, ${theme.palette.primary.dark } 100%)`, color: 'white', display: 'flex', justifyContent: 'center', diff --git a/frontend/src/layouts/dashboard/side-nav.js b/frontend/src/layouts/dashboard/side-nav.js index e378c2a..cce1de6 100644 --- a/frontend/src/layouts/dashboard/side-nav.js +++ b/frontend/src/layouts/dashboard/side-nav.js @@ -2,8 +2,6 @@ import NextLink from 'next/link'; import { usePathname } from 'next/navigation'; import PropTypes from 'prop-types'; import Link from 'next/link' -import ArrowTopRightOnSquareIcon from '@heroicons/react/24/solid/ArrowTopRightOnSquareIcon'; -import ChevronUpDownIcon from '@heroicons/react/24/solid/ChevronUpDownIcon'; import { Box, Button, @@ -18,12 +16,15 @@ import { Logo } from 'src/components/logo'; import { Scrollbar } from 'src/components/scrollbar'; import { items } from './config'; import { SideNavItem } from './side-nav-item'; +import { useTheme } from '@mui/material'; export const SideNav = (props) => { const { open, onClose } = props; const pathname = usePathname(); const lgUp = useMediaQuery((theme) => theme.breakpoints.up('lg')); + const theme = useTheme(); + const isItemActive = (currentPath, itemPath) => { if (currentPath === itemPath) { return true; @@ -131,7 +132,6 @@ export const SideNav = (props) => { })} </Stack> </Box> - <Divider sx={{ borderColor: 'neutral.700' }} /> </Box> </Scrollbar> ); @@ -143,7 +143,7 @@ export const SideNav = (props) => { open PaperProps={{ sx: { - backgroundColor: 'neutral.800', + background: `linear-gradient(0deg, ${theme.palette.primary.main} 0%, ${theme.palette.primary.dark} 90%);`, color: 'common.white', width: 280 } @@ -162,7 +162,7 @@ export const SideNav = (props) => { open={open} PaperProps={{ sx: { - backgroundColor: 'neutral.800', + background: `linear-gradient(0deg, ${theme.palette.primary.main} 0%, ${theme.palette.primary.dark} 90%);`, color: 'common.white', width: 280 } diff --git a/frontend/src/pages/_app.js b/frontend/src/pages/_app.js index 3c4bdc9..06c636d 100644 --- a/frontend/src/pages/_app.js +++ b/frontend/src/pages/_app.js @@ -11,21 +11,25 @@ import { createEmotionCache } from 'src/utils/create-emotion-cache'; import 'simplebar-react/dist/simplebar.min.css'; import { WsProvider } from 'src/contexts/socketio-context'; import '../utils/map.css'; +import { useEffect, useState } from 'react'; const clientSideEmotionCache = createEmotionCache(); const SplashScreen = () => null; const App = (props) => { + const [theme, setTheme] = useState(null); const { Component, emotionCache = clientSideEmotionCache, pageProps } = props; useNProgress(); const getLayout = Component.getLayout ?? ((page) => page); - const theme = createTheme(); + useEffect(() => { + setTheme(createTheme()); + }, []); - return ( + return theme && ( <CacheProvider value={emotionCache}> <Head> <title> diff --git a/frontend/src/sections/overview/overview-tasks-progress.js b/frontend/src/sections/overview/overview-tasks-progress.js index da8beaf..bfb3c0f 100644 --- a/frontend/src/sections/overview/overview-tasks-progress.js +++ b/frontend/src/sections/overview/overview-tasks-progress.js @@ -97,7 +97,7 @@ const showIcon = (mtpType) => { </Stack> <Avatar sx={{ - backgroundColor: '#f28950', + backgroundColor: 'primary.darkest', height: 56, width: 56 }} diff --git a/frontend/src/sections/overview/overview-traffic.js b/frontend/src/sections/overview/overview-traffic.js index 19b05a3..6fc4bbf 100644 --- a/frontend/src/sections/overview/overview-traffic.js +++ b/frontend/src/sections/overview/overview-traffic.js @@ -70,7 +70,6 @@ const useChartOptions = (labels,title) => { } else{ options.colors = [ - theme.palette.primary.main, theme.palette.info.main, theme.palette.warning.main, theme.palette.graphics.lightest, diff --git a/frontend/src/theme/colors.js b/frontend/src/theme/colors.js index 74ef40e..7d6589e 100644 --- a/frontend/src/theme/colors.js +++ b/frontend/src/theme/colors.js @@ -1,5 +1,5 @@ import { alpha } from '@mui/material/styles'; - + const withAlphas = (color) => { return { ...color, @@ -11,8 +11,16 @@ const withAlphas = (color) => { }; }; -export const neutral = { - 50: '#306d6f', +export const neutral = (colors) => { + console.log("neutral colors:", colors); + let parsedColors = JSON.parse(colors); + + let tableColor = parsedColors["tables"] + let sidebarColorInitial = parsedColors["sidebar_initial"] + let wordsOutsideSidebarColor = parsedColors["words_outside_sidebar"] + + return { + 50: tableColor, 100: '#F3F4F6', 200: '#E5E7EB', 300: '#D2D6DB', @@ -20,18 +28,29 @@ export const neutral = { 500: '#6C737F', 600: '#4D5761', 700: '#FFFFFF', - 800: '#306d6f', - 900: '#30596f' + 800: sidebarColorInitial, + 900: wordsOutsideSidebarColor, +} }; -export const indigo = withAlphas({ - lightest: '#FFFFFF', - light: '#306D6F', - main: '#306D6F', - dark: '#255355', - darkest: '#00a0b8', - contrastText: '#FFFFFF' -}); +export const indigo = (colors) => { + + console.log("indigo colors:", colors); + let parsedColors = JSON.parse(colors); + + let buttonColor = parsedColors["buttons"] + let sidebarColorEnd = parsedColors["sidebar_end"] + let mtpsColor = parsedColors["connected_mtps_color"] + + return withAlphas({ + lightest: '#FFFFFF', + light: '#ff3383', + main: buttonColor, + dark: sidebarColorEnd, + darkest: mtpsColor, + contrastText: '#FFFFFF' + }); +} export const success = withAlphas({ lightest: '#F0FDF9', diff --git a/frontend/src/theme/create-palette.js b/frontend/src/theme/create-palette.js index 43e67f4..39a114f 100644 --- a/frontend/src/theme/create-palette.js +++ b/frontend/src/theme/create-palette.js @@ -2,15 +2,49 @@ import { common } from '@mui/material/colors'; import { alpha } from '@mui/material/styles'; import { error, indigo, info, neutral, success, warning, graphics } from './colors'; +const getColorScheme = async () => { + + let result = await fetch('http://localhost/custom-frontend/colors').catch((error) => { + console.log('Error fetching colors'); + sessionStorage.setItem('colors', JSON.stringify({ + "buttons": "#306d6f", + "sidebar_end": "#306d6f", + "sidebar_initial": "#306d6f", + "tables": "#306d6f", + "words_outside_sidebar": "#30596f", + "connected_mtps_color": "#f28950" + })); + location.reload(); + return + }); + + let response = await result.json(); + let fmtresponse = JSON.stringify(response); + sessionStorage.setItem('colors', fmtresponse); + location.reload(); +} + export function createPalette() { - return { + + let colors = sessionStorage.getItem('colors'); + + if (colors !== null) { + console.log('colors already fetched'); + } else { + getColorScheme(); + } + console.log("colors scheme:", colors); + + let neutralColors = neutral(colors); + + return { action: { - active: neutral[500], - disabled: alpha(neutral[900], 0.38), - disabledBackground: alpha(neutral[900], 0.12), - focus: alpha(neutral[900], 0.16), - hover: alpha(neutral[900], 0.04), - selected: alpha(neutral[900], 0.12) + active: neutralColors[500], + disabled: alpha(neutralColors[900], 0.38), + disabledBackground: alpha(neutralColors[900], 0.12), + focus: alpha(neutralColors[900], 0.16), + hover: alpha(neutralColors[900], 0.04), + selected: alpha(neutralColors[900], 0.12) }, background: { default: common.white, @@ -21,13 +55,13 @@ export function createPalette() { graphics, info, mode: 'light', - neutral, - primary: indigo, + neutral: neutralColors, + primary: indigo(colors), success, text: { - primary: neutral[900], - secondary: neutral[500], - disabled: alpha(neutral[900], 0.38) + primary: neutralColors[900], + secondary: neutralColors[500], + disabled: alpha(neutralColors[900], 0.38) }, warning }; From 40804f29e4f9ec199344983e9f65e0cfad90e0a8 Mon Sep 17 00:00:00 2001 From: leandrofars <leandrofars@gmail.com> Date: Mon, 8 Jul 2024 15:06:43 -0300 Subject: [PATCH 15/28] chore(compose): nginx remove depends_on --- deploy/compose/docker-compose.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/deploy/compose/docker-compose.yaml b/deploy/compose/docker-compose.yaml index 5261ff0..410fe46 100644 --- a/deploy/compose/docker-compose.yaml +++ b/deploy/compose/docker-compose.yaml @@ -210,10 +210,6 @@ services: container_name: nginx ports: - 80:80 - depends_on: - - frontend - - controller - - socketio volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf extra_hosts: From 9a47652b0d7bcb269e831d28a66923e305cc1d7c Mon Sep 17 00:00:00 2001 From: leandrofars <leandrofars@gmail.com> Date: Mon, 8 Jul 2024 16:30:15 -0300 Subject: [PATCH 16/28] fix(frontend): custom colors set error when using default mode --- frontend/src/theme/create-palette.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/frontend/src/theme/create-palette.js b/frontend/src/theme/create-palette.js index 39a114f..7768322 100644 --- a/frontend/src/theme/create-palette.js +++ b/frontend/src/theme/create-palette.js @@ -17,6 +17,19 @@ const getColorScheme = async () => { location.reload(); return }); + if (result.status!=200) { + console.log('Error fetching colors'); + sessionStorage.setItem('colors', JSON.stringify({ + "buttons": "#306d6f", + "sidebar_end": "#306d6f", + "sidebar_initial": "#306d6f", + "tables": "#306d6f", + "words_outside_sidebar": "#30596f", + "connected_mtps_color": "#f28950" + })); + location.reload(); + return + } let response = await result.json(); let fmtresponse = JSON.stringify(response); From fe2a384de2aa6b624398a5dfdf91c9fa6cb02ecf Mon Sep 17 00:00:00 2001 From: leandrofars <leandrofars@gmail.com> Date: Mon, 8 Jul 2024 16:31:42 -0300 Subject: [PATCH 17/28] feat(frontend): add powered by message --- frontend/public/assets/logo.png | Bin 0 -> 19631 bytes frontend/src/layouts/auth/layout.js | 24 +++++++++++++++++++-- frontend/src/layouts/dashboard/layout.js | 2 -- frontend/src/layouts/dashboard/side-nav.js | 18 ++++++++++++++++ 4 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 frontend/public/assets/logo.png diff --git a/frontend/public/assets/logo.png b/frontend/public/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..5352dbaa1a782fc414b8590911382ca67f569c2b GIT binary patch literal 19631 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 literal 0 HcmV?d00001 diff --git a/frontend/src/layouts/auth/layout.js b/frontend/src/layouts/auth/layout.js index bc85919..b8d2872 100644 --- a/frontend/src/layouts/auth/layout.js +++ b/frontend/src/layouts/auth/layout.js @@ -1,14 +1,17 @@ import PropTypes from 'prop-types'; import NextLink from 'next/link'; import Link from 'next/link' -import { Box, Typography, Unstable_Grid2 as Grid } from '@mui/material'; +import { Box, Typography, Unstable_Grid2 as Grid, Stack } from '@mui/material'; import { Logo } from 'src/components/logo'; -import { useTheme } from '@mui/material' +import { useTheme, useMediaQuery } from '@mui/material' export const Layout = (props) => { const { children } = props; + const lgUp = useMediaQuery((theme) => theme.breakpoints.up('lg')); const theme = useTheme(); + console.log("logUp", lgUp) + return ( <Box component="main" @@ -79,6 +82,23 @@ export const Layout = (props) => { </Box> </Grid> </Grid> + <Stack style={{position:"absolute", bottom:"2px", left:"2px"}} direction={"row"} spacing={"1"}> + <Typography + align="center" + color={lgUp ? 'neutral[900]' : 'primary.contrastText'} + component="footer" + variant="body2" + sx={{ p: 2 }} + > + Powered by + </Typography> + </Stack> + <a href='https://oktopus.app.br' style={{position:"absolute", bottom:"10px", left:"100px"}} target='_blank'> + <img + src="/assets/logo.png" + alt="Oktopus logo image" + width={80}/> + </a> </Box> ); }; diff --git a/frontend/src/layouts/dashboard/layout.js b/frontend/src/layouts/dashboard/layout.js index 2753fa6..104db95 100644 --- a/frontend/src/layouts/dashboard/layout.js +++ b/frontend/src/layouts/dashboard/layout.js @@ -4,8 +4,6 @@ import { styled } from '@mui/material/styles'; import { withAuthGuard } from 'src/hocs/with-auth-guard'; import { SideNav } from './side-nav'; import { TopNav } from './top-nav'; -import Image from 'next/image' -import { Link } from '@mui/material'; const SIDE_NAV_WIDTH = 280; diff --git a/frontend/src/layouts/dashboard/side-nav.js b/frontend/src/layouts/dashboard/side-nav.js index cce1de6..6f30221 100644 --- a/frontend/src/layouts/dashboard/side-nav.js +++ b/frontend/src/layouts/dashboard/side-nav.js @@ -132,6 +132,24 @@ export const SideNav = (props) => { })} </Stack> </Box> + <Stack style={{position:"absolute", bottom:"2px", left:"2px"}} direction={"row"} spacing={"1"} zIndex={9999}> + <Typography + align="center" + color="primary.contrastText" + component="footer" + variant="body2" + sx={{ p: 2 }} + > + Powered by + </Typography> + </Stack> + <a href='https://oktopus.app.br' style={{position:"absolute", bottom:"10px", left:"100px"}} target='_blank'> + <img + src="/assets/logo.png" + alt="Oktopus logo image" + width={80} + /> + </a> </Box> </Scrollbar> ); From 3bcb3cb8846c639c8317f0b1ae356947a675df53 Mon Sep 17 00:00:00 2001 From: leandrofars <leandrofars@gmail.com> Date: Mon, 8 Jul 2024 20:59:03 -0300 Subject: [PATCH 18/28] fix(frontend): sidebar palette --- frontend/src/layouts/dashboard/side-nav.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/layouts/dashboard/side-nav.js b/frontend/src/layouts/dashboard/side-nav.js index 6f30221..d09aed5 100644 --- a/frontend/src/layouts/dashboard/side-nav.js +++ b/frontend/src/layouts/dashboard/side-nav.js @@ -161,7 +161,7 @@ export const SideNav = (props) => { open PaperProps={{ sx: { - background: `linear-gradient(0deg, ${theme.palette.primary.main} 0%, ${theme.palette.primary.dark} 90%);`, + background: `linear-gradient(0deg, ${theme.palette.neutral["800"]} 0%, ${theme.palette.primary.dark} 90%);`, color: 'common.white', width: 280 } From e47011b12d0efa739a5bb4363d12f1019a11b540 Mon Sep 17 00:00:00 2001 From: leandrofars <leandrofars@gmail.com> Date: Mon, 8 Jul 2024 20:59:43 -0300 Subject: [PATCH 19/28] fix(controller): remove default falg enterprise true --- deploy/compose/.env.controller | 1 - 1 file changed, 1 deletion(-) diff --git a/deploy/compose/.env.controller b/deploy/compose/.env.controller index 3f82d05..d3dcd0d 100644 --- a/deploy/compose/.env.controller +++ b/deploy/compose/.env.controller @@ -1,5 +1,4 @@ MONGO_URI=mongodb://mongo_usp:27017 -ENTERPRISE="true" NATS_URL=nats://oktopususer:oktopuspw@msg_broker:4222 NATS_ENABLE_TLS="true" CLIENT_CRT=/tmp/nats/config/cert.pem From 2424c1d49e733109ee443c5d20bf3706c1531878 Mon Sep 17 00:00:00 2001 From: leandrofars <leandrofars@gmail.com> Date: Mon, 8 Jul 2024 21:00:23 -0300 Subject: [PATCH 20/28] fix(frontend): remove rest endpoint env var --- frontend/.env | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/.env b/frontend/.env index 2116eeb..293b49b 100644 --- a/frontend/.env +++ b/frontend/.env @@ -1,6 +1,5 @@ # ----------------------------- Local Environment ---------------------------- # -NEXT_PUBLIC_REST_ENDPOINT="http://localhost:8000" NEXT_PUBLIC_WS_ENDPOINT="http://localhost:5000/" NEXT_PUBLIC_ENTERPRISE_VERSION="false" NEXT_PUBLIC_GOOGLE_MAPS_KEY="" From 3de72880143a96d366d1dec53c45b4e737b16710 Mon Sep 17 00:00:00 2001 From: leandrofars <leandrofars@gmail.com> Date: Mon, 8 Jul 2024 21:01:19 -0300 Subject: [PATCH 21/28] feat(frontend): map save last place visited and zoom --- frontend/src/pages/map.js | 66 +++++++++++++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 9 deletions(-) diff --git a/frontend/src/pages/map.js b/frontend/src/pages/map.js index 9aa7247..6ba92ea 100644 --- a/frontend/src/pages/map.js +++ b/frontend/src/pages/map.js @@ -3,6 +3,7 @@ import { GoogleMap, useLoadScript, Marker, OverlayView } from "@react-google-map import { Layout as DashboardLayout } from 'src/layouts/dashboard/layout'; import { useEffect, useMemo, useState } from 'react'; import mapStyles from '../utils/mapStyles.json'; +import { useRouter } from 'next/router'; const getPixelPositionOffset = pixelOffset => (width, height) => ({ x: -(width / 2) + pixelOffset.x, @@ -29,10 +30,35 @@ const Page = () => { const libraries = useMemo(() => ['places'], []); + const router = useRouter(); + + const [mapRef, setMapRef] = useState(null); + const [mapCenter, setMapCenter] = useState(null); const [markers, setMarkers] = useState([]); const [activeMarker, setActiveMarker] = useState(null); const [activeMarkerdata, setActiveMarkerdata] = useState(null); + const [zoom, setZoom] = useState(null) + + const handleDragEnd = () => { + if (mapRef) { + const newCenter = mapRef.getCenter(); + console.log("newCenter:",newCenter.lat(), newCenter.lng()); + localStorage.setItem("mapCenter", JSON.stringify({"lat":newCenter.lat(),"lng":newCenter.lng()})) + } + } + + const handleZoomChange = () => { + if (mapRef) { + const newZoom = mapRef.getZoom(); + console.log("new zoom", newZoom) + localStorage.setItem("zoom", newZoom) + } + } + + const handleOnLoad = map => { + setMapRef(map); + }; const fetchMarkers = async () => { @@ -58,9 +84,8 @@ const Page = () => { console.log("taix nem autenticado, sai fora oh") return router.push("/auth/login") } else { - console.log("agora quebrasse ux córno mô quiridu") - const content = await result.json() - throw new Error(content); + setMarkers([]) + console.log("error to get map markers") } } @@ -92,6 +117,24 @@ const Page = () => { useEffect(()=> { fetchMarkers(); + + let zoomFromLocalStorage = localStorage.getItem("zoom") + if (zoomFromLocalStorage) { + setZoom(Number(zoomFromLocalStorage)) + }else{ + setZoom(25) + } + + let mapCenterFromLocalStorage = localStorage.getItem("mapCenter") + if (mapCenterFromLocalStorage){ + let fmtMapCenter = JSON.parse(localStorage.getItem("mapCenter")) + console.log("mapCenterFromLocalStorage:", fmtMapCenter) + setMapCenter({ + lat: Number(fmtMapCenter.lat), + lng: Number(fmtMapCenter.lng), + }) + return + } // Check if geolocation is supported by the browser if ("geolocation" in navigator) { // Prompt user for permission to access their location @@ -108,12 +151,12 @@ const Page = () => { // Error callback function function(error) { // Handle errors, e.g. user denied location sharing permissions - console.error("Error getting user location:", error); + console.log("Error getting user location:", error); } ); } else { // Geolocation is not supported by the browser - console.error("Geolocation is not supported by this browser."); + console.log("Geolocation is not supported by this browser, or the user denied access"); } },[]) @@ -140,7 +183,7 @@ const Page = () => { return <p>Loading...</p>; } - return ( mapCenter && markers && + return ( markers && zoom && <> <Head> <title> @@ -149,11 +192,16 @@ const Page = () => { </Head> <GoogleMap options={mapOptions} - zoom={14} - center={mapCenter} + zoom={zoom} + center={mapCenter ? mapCenter : { + lat: 0.0, + lng: 0.0, + }} mapContainerStyle={{ width: '100%', height: '100%' }} - onLoad={() => console.log('Map Component Loaded...')} + onLoad={handleOnLoad} clickableIcons={false} + onDragEnd={handleDragEnd} + onZoomChanged={handleZoomChange} > { markers.map((marker, index) => ( From 36b57b8f21a4b67f97ebbfede795a3447a81cc16 Mon Sep 17 00:00:00 2001 From: leandrofars <leandrofars@gmail.com> Date: Mon, 8 Jul 2024 21:08:23 -0300 Subject: [PATCH 22/28] chore(controller): remove polluting log --- backend/services/controller/internal/api/user.go | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/services/controller/internal/api/user.go b/backend/services/controller/internal/api/user.go index 5484a06..b67d4b5 100644 --- a/backend/services/controller/internal/api/user.go +++ b/backend/services/controller/internal/api/user.go @@ -243,7 +243,6 @@ func adminUserExists(users []map[string]interface{}, supportEmail string) bool { for _, x := range users { if db.UserLevels(x["level"].(int32)) == db.AdminUser && x["email"].(string) != supportEmail { - log.Println("Admin exists") return true } } From 868a97f9d55a363c20800696b01061e56f12fe8e Mon Sep 17 00:00:00 2001 From: leandrofars <leandrofars@gmail.com> Date: Tue, 9 Jul 2024 10:52:23 -0300 Subject: [PATCH 23/28] feat(frontend): wifi channels --- .../src/sections/devices/cwmp/devices-wifi.js | 360 ++++++++++-------- 1 file changed, 192 insertions(+), 168 deletions(-) diff --git a/frontend/src/sections/devices/cwmp/devices-wifi.js b/frontend/src/sections/devices/cwmp/devices-wifi.js index e9047f2..f0ed7e5 100644 --- a/frontend/src/sections/devices/cwmp/devices-wifi.js +++ b/frontend/src/sections/devices/cwmp/devices-wifi.js @@ -1,31 +1,31 @@ import { useCallback, useEffect, useState } from 'react'; import { - Button, - Card, - CardActions, - CardContent, - CardHeader, - Divider, - Stack, - TextField, - InputLabel, - MenuItem, - Select, - FormControl, - SvgIcon, - Dialog, - DialogTitle, - DialogContent, - DialogContentText, - DialogActions, - Box, - IconButton, - Icon, - SnackbarContent, - Snackbar, - Checkbox, - FormControlLabel, - useTheme, + Button, + Card, + CardActions, + CardContent, + CardHeader, + Divider, + Stack, + TextField, + InputLabel, + MenuItem, + Select, + FormControl, + SvgIcon, + Dialog, + DialogTitle, + DialogContent, + DialogContentText, + DialogActions, + Box, + IconButton, + Icon, + SnackbarContent, + Snackbar, + Checkbox, + FormControlLabel, + useTheme, } from '@mui/material'; import XMarkIcon from '@heroicons/react/24/outline/XMarkIcon'; import Check from '@heroicons/react/24/outline/CheckIcon'; @@ -52,44 +52,44 @@ export const DevicesWiFi = () => { var myHeaders = new Headers(); myHeaders.append("Content-Type", "application/json"); myHeaders.append("Authorization", localStorage.getItem("token")); - + var requestOptions = { - method: 'GET', - headers: myHeaders, - redirect: 'follow' + method: 'GET', + headers: myHeaders, + redirect: 'follow' }; fetch(`${process.env.NEXT_PUBLIC_REST_ENDPOINT || ""}/api/device/${router.query.id[0]}/wifi`, requestOptions) - .then(response => { - if (response.status === 401) { - router.push("/auth/login") - } - return response.json() - }) - .then(result => { - console.log("wifi content", result) - result.map((item) => { - let contentToApply = { - hasChanges: false, - path: item.path, + .then(response => { + if (response.status === 401) { + router.push("/auth/login") } - setApplyContent(oldValue => [...oldValue, contentToApply]) + return response.json() }) - setContent(result) - }) - .catch(error => console.log('error', error)); + .then(result => { + console.log("wifi content", result) + result.map((item) => { + let contentToApply = { + hasChanges: false, + path: item.path, + } + setApplyContent(oldValue => [...oldValue, contentToApply]) + }) + setContent(result) + }) + .catch(error => console.log('error', error)); }; - useEffect(()=>{ + useEffect(() => { fetchWifiData() - },[]) + }, []) return (<div> - <Stack - direction="row" - spacing={2} - justifyContent="center" - alignItems="center" + <Stack + direction="row" + spacing={2} + justifyContent="center" + alignItems="center" > {content.length > 1 ? (content.map((item, index) => { @@ -99,25 +99,25 @@ export const DevicesWiFi = () => { title={item.name.value} avatar={ <SvgIcon> - <GlobeAltIcon/> + <GlobeAltIcon /> </SvgIcon> } /> <CardContent> <Stack spacing={3}> - { item.enable.value != null && - <FormControlLabel control={<Checkbox defaultChecked={item.enable.value == 1 ? true : false} - onChange={(e) => { - let enable = item.enable.value == 1 ? "0" : "1" - console.log(enable) - applyContent[index].hasChanges = true - applyContent[index].enable = { - value : enable - } - setApplyContent([...applyContent]) - item.enable.value = enable - }}/>} - label="Enabled" />} + {item.enable.value != null && + <FormControlLabel control={<Checkbox defaultChecked={item.enable.value == 1 ? true : false} + onChange={(e) => { + let enable = item.enable.value == 1 ? "0" : "1" + console.log(enable) + applyContent[index].hasChanges = true + applyContent[index].enable = { + value: enable + } + setApplyContent([...applyContent]) + item.enable.value = enable + }} />} + label="Enabled" />} {item.ssid.value != null && <TextField fullWidth label="SSID" @@ -126,67 +126,91 @@ export const DevicesWiFi = () => { onChange={(e) => { applyContent[index].hasChanges = true applyContent[index].ssid = { - value : e.target.value + value: e.target.value } setApplyContent([...applyContent]) item.ssid.value = e.target.value }} />} {item.securityCapabilities && - <TextField - fullWidth - label="Encryption" - value={""} - />} + <TextField + fullWidth + label="Encryption" + value={""} + />} {item.password.value != null && - <TextField - fullWidth - type="password" - label="Password" - disabled={!item.password.writable} - value={item.password.value} - onChange={(e) => { - if (e.target.value.length >= 8) { - applyContent[index].hasChanges = true - }else{ - applyContent[index].hasChanges = false - } - applyContent[index].password = { - value : e.target.value - } - setApplyContent([...applyContent]) - item.password.value = e.target.value - console.log("applyContent: ", applyContent) - }} - />} + <TextField + fullWidth + type="password" + label="Password" + disabled={!item.password.writable} + value={item.password.value} + onChange={(e) => { + if (e.target.value.length >= 8) { + applyContent[index].hasChanges = true + } else { + applyContent[index].hasChanges = false + } + applyContent[index].password = { + value: e.target.value + } + setApplyContent([...applyContent]) + item.password.value = e.target.value + console.log("applyContent: ", applyContent) + }} + />} + {item.channel?.value != null && item.possibleChannels?.value != null && + <FormControl variant='filled'> + <InputLabel id="channel">Channel</InputLabel> + <Select + fullWidth + defaultValue={item.channel.value} + value={item.channel.value} + onChange={(e) => { + applyContent[index].hasChanges = true + applyContent[index].channel = { + value: e.target.value + } + setApplyContent([...applyContent]) + item.channel.value = e.target.value + }} + label="Channel" + > + {item.possibleChannels.value.map((channel, index) => { + return ( + <MenuItem key={index} value={channel}>{channel}</MenuItem>) + })} + </Select> + </FormControl> + } {item.standard.value != null && - <TextField - fullWidth - label="Standard" - disabled={!item.standard.writable} - value={item.standard.value} - onChange={(e) => { - applyContent[index].hasChanges = true - applyContent[index].standard = { - value : e.target.value - } - setApplyContent([...applyContent]) - item.standard.value = e.target.value - }} - />} + <TextField + fullWidth + label="Standard" + disabled={!item.standard.writable} + value={item.standard.value} + onChange={(e) => { + applyContent[index].hasChanges = true + applyContent[index].standard = { + value: e.target.value + } + setApplyContent([...applyContent]) + item.standard.value = e.target.value + }} + />} </Stack> - <CardActions sx={{display:"flex", justifyContent:"flex-end"}}> - <Button - variant="contained" + <CardActions sx={{ display: "flex", justifyContent: "flex-end" }}> + <Button + variant="contained" disabled={!applyContent[index].hasChanges} - endIcon={<SvgIcon><Check /></SvgIcon>} + endIcon={<SvgIcon><Check /></SvgIcon>} onClick={ - ()=>{ + () => { setApply(true) var myHeaders = new Headers(); myHeaders.append("Content-Type", "application/json"); myHeaders.append("Authorization", localStorage.getItem("token")); - + delete applyContent[index].hasChanges let contentToApply = [applyContent[index]] console.log("contentToApply: ", contentToApply) @@ -198,38 +222,38 @@ export const DevicesWiFi = () => { redirect: 'follow' }; fetch(`${process.env.NEXT_PUBLIC_REST_ENDPOINT || ""}/api/device/${router.query.id[0]}/wifi`, requestOptions) - .then(response => { - if (response.status === 401) { - router.push("/auth/login") - } - if (response.status == 500) { - setErrorModal(true) - } - return response.json() - }) - .then(result => { - if (errorModal) { - setErrorModalText(result) - } - setApply(false) - if (result == 1) { - setErrorModalText("This change could not be applied, or It's gonna be applied later on") - setErrorModal(true) - //TODO: fetch wifi data again - } - }) - .catch(error => console.log('error', error)); + .then(response => { + if (response.status === 401) { + router.push("/auth/login") + } + if (response.status == 500) { + setErrorModal(true) + } + return response.json() + }) + .then(result => { + if (errorModal) { + setErrorModalText(result) + } + setApply(false) + if (result == 1) { + setErrorModalText("This change could not be applied, or It's gonna be applied later on") + setErrorModal(true) + //TODO: fetch wifi data again + } + }) + .catch(error => console.log('error', error)); } } - sx={{mt:'25px', mb:'-15px'}} - > + sx={{ mt: '25px', mb: '-15px' }} + > Apply </Button> </CardActions> </CardContent> </Card> ) - })): + })) : <CircularProgress /> } </Stack> @@ -239,44 +263,44 @@ export const DevicesWiFi = () => { > <CircularProgress color="inherit" /> </Backdrop> - <Dialog open={errorModal && errorModalText != ""} - slotProps={{ backdrop: { style: { backgroundColor: 'rgba(255,255,255,0.5)' } } }} - fullWidth={ true } - maxWidth={"md"} - scroll={"paper"} - aria-labelledby="scroll-dialog-title" - aria-describedby="scroll-dialog-description" - > - <DialogTitle id="scroll-dialog-title"> + <Dialog open={errorModal && errorModalText != ""} + slotProps={{ backdrop: { style: { backgroundColor: 'rgba(255,255,255,0.5)' } } }} + fullWidth={true} + maxWidth={"md"} + scroll={"paper"} + aria-labelledby="scroll-dialog-title" + aria-describedby="scroll-dialog-description" + > + <DialogTitle id="scroll-dialog-title"> <Box display="flex" alignItems="center"> <Box flexGrow={1} >Response</Box> <Box> - <IconButton onClick={()=>{ - setErrorModalText("") - setErrorModal(false) - }}> - <SvgIcon - > - < XMarkIcon/> + <IconButton onClick={() => { + setErrorModalText("") + setErrorModal(false) + }}> + <SvgIcon + > + < XMarkIcon /> </SvgIcon> </IconButton> </Box> </Box> - </DialogTitle> - <DialogContent dividers={scroll === 'paper'}> - <DialogContentText id="scroll-dialog-description"tabIndex={-1}> - <pre style={{color: 'black'}}> + </DialogTitle> + <DialogContent dividers={scroll === 'paper'}> + <DialogContentText id="scroll-dialog-description" tabIndex={-1}> + <pre style={{ color: 'black' }}> {errorModalText} </pre> - </DialogContentText> - </DialogContent> - <DialogActions> - <Button onClick={()=>{ - setErrorModalText("") - setErrorModal(false) - }}>OK</Button> - </DialogActions> - </Dialog> - </div> - ); + </DialogContentText> + </DialogContent> + <DialogActions> + <Button onClick={() => { + setErrorModalText("") + setErrorModal(false) + }}>OK</Button> + </DialogActions> + </Dialog> + </div> + ); }; From ad90ea6a4fadb48a515d52b6500b725776d0fd2c Mon Sep 17 00:00:00 2001 From: leandrofars <leandrofars@gmail.com> Date: Wed, 10 Jul 2024 11:12:42 -0300 Subject: [PATCH 24/28] feat(frontend): site survey --- frontend/package-lock.json | 46 +- frontend/package.json | 4 +- .../src/sections/devices/cwmp/site-survey.js | 434 ++++++++++++------ 3 files changed, 316 insertions(+), 168 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index cf2561d..12297bb 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -18,14 +18,14 @@ "@mui/system": "5.11.9", "@mui/x-date-pickers": "5.0.19", "@react-google-maps/api": "^2.19.3", - "apexcharts": "3.37.0", + "apexcharts": "^3.37.0", "date-fns": "2.29.3", "formik": "2.2.9", "next": "^14.2.4", "nprogress": "0.2.0", "prop-types": "15.8.1", "react": "^18.3.1", - "react-apexcharts": "1.4.0", + "react-apexcharts": "^1.4.0", "react-dom": "^18.3.1", "simple-peer": "^9.11.1", "simplebar-react": "^3.2.1", @@ -1480,6 +1480,11 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@yr/monotone-cubic-spline": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz", + "integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==" + }, "node_modules/acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", @@ -1538,10 +1543,11 @@ } }, "node_modules/apexcharts": { - "version": "3.37.0", - "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.37.0.tgz", - "integrity": "sha512-0mg1gDKUo3JG00Q//LK0jEXBS6OLjpuglqZ8ec9cqfA5oP8owopD9n5EhfARbWROb5o8GSPzFuohTJiCm2ecWw==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.50.0.tgz", + "integrity": "sha512-LJT1PNAm+NoIU3aogL2P+ViC0y/Cjik54FdzzGV54UNnGQLBoLe5ok3fxsJDTgyez45BGYT8gqNpYKqhdfy5sg==", "dependencies": { + "@yr/monotone-cubic-spline": "^1.0.3", "svg.draggable.js": "^2.2.2", "svg.easing.js": "^2.0.0", "svg.filter.js": "^2.0.2", @@ -4722,14 +4728,14 @@ } }, "node_modules/react-apexcharts": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/react-apexcharts/-/react-apexcharts-1.4.0.tgz", - "integrity": "sha512-DrcMV4aAMrUG+n6412yzyATWEyCDWlpPBBhVbpzBC4PDeuYU6iF84SmExbck+jx5MUm4U5PM3/T307Mc3kzc9Q==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/react-apexcharts/-/react-apexcharts-1.4.1.tgz", + "integrity": "sha512-G14nVaD64Bnbgy8tYxkjuXEUp/7h30Q0U33xc3AwtGFijJB9nHqOt1a6eG0WBn055RgRg+NwqbKGtqPxy15d0Q==", "dependencies": { - "prop-types": "^15.5.7" + "prop-types": "^15.8.1" }, "peerDependencies": { - "apexcharts": "^3.18.0", + "apexcharts": "^3.41.0", "react": ">=0.13" } }, @@ -7102,6 +7108,11 @@ "eslint-visitor-keys": "^3.3.0" } }, + "@yr/monotone-cubic-spline": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz", + "integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==" + }, "acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", @@ -7142,10 +7153,11 @@ } }, "apexcharts": { - "version": "3.37.0", - "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.37.0.tgz", - "integrity": "sha512-0mg1gDKUo3JG00Q//LK0jEXBS6OLjpuglqZ8ec9cqfA5oP8owopD9n5EhfARbWROb5o8GSPzFuohTJiCm2ecWw==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.50.0.tgz", + "integrity": "sha512-LJT1PNAm+NoIU3aogL2P+ViC0y/Cjik54FdzzGV54UNnGQLBoLe5ok3fxsJDTgyez45BGYT8gqNpYKqhdfy5sg==", "requires": { + "@yr/monotone-cubic-spline": "^1.0.3", "svg.draggable.js": "^2.2.2", "svg.easing.js": "^2.0.0", "svg.filter.js": "^2.0.2", @@ -9444,11 +9456,11 @@ } }, "react-apexcharts": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/react-apexcharts/-/react-apexcharts-1.4.0.tgz", - "integrity": "sha512-DrcMV4aAMrUG+n6412yzyATWEyCDWlpPBBhVbpzBC4PDeuYU6iF84SmExbck+jx5MUm4U5PM3/T307Mc3kzc9Q==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/react-apexcharts/-/react-apexcharts-1.4.1.tgz", + "integrity": "sha512-G14nVaD64Bnbgy8tYxkjuXEUp/7h30Q0U33xc3AwtGFijJB9nHqOt1a6eG0WBn055RgRg+NwqbKGtqPxy15d0Q==", "requires": { - "prop-types": "^15.5.7" + "prop-types": "^15.8.1" } }, "react-dom": { diff --git a/frontend/package.json b/frontend/package.json index 7347046..02747e2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -24,14 +24,14 @@ "@mui/system": "5.11.9", "@mui/x-date-pickers": "5.0.19", "@react-google-maps/api": "^2.19.3", - "apexcharts": "3.37.0", + "apexcharts": "^3.37.0", "date-fns": "2.29.3", "formik": "2.2.9", "next": "^14.2.4", "nprogress": "0.2.0", "prop-types": "15.8.1", "react": "^18.3.1", - "react-apexcharts": "1.4.0", + "react-apexcharts": "^1.4.0", "react-dom": "^18.3.1", "simple-peer": "^9.11.1", "simplebar-react": "^3.2.1", diff --git a/frontend/src/sections/devices/cwmp/site-survey.js b/frontend/src/sections/devices/cwmp/site-survey.js index 1a97cfe..a3d5fa7 100644 --- a/frontend/src/sections/devices/cwmp/site-survey.js +++ b/frontend/src/sections/devices/cwmp/site-survey.js @@ -1,6 +1,3 @@ -import PropTypes from 'prop-types'; -import ArrowPathIcon from '@heroicons/react/24/solid/ArrowPathIcon'; -import ArrowRightIcon from '@heroicons/react/24/solid/ArrowRightIcon'; import { Button, Card, @@ -22,12 +19,11 @@ import { TableHead, TableRow, TableContainer, - Paper, - Container, - CircularProgress + CircularProgress, + ToggleButton, + ToggleButtonGroup } from '@mui/material'; -import { Scrollbar } from 'src/components/scrollbar'; -import { alpha, useTheme } from '@mui/material/styles'; +import { useTheme } from '@mui/material/styles'; import { Chart } from 'src/components/chart'; import ChartBarSquareIcon from '@heroicons/react/24/outline/ChartBarSquareIcon'; import ListBulletIcon from '@heroicons/react/24/outline/ListBulletIcon'; @@ -35,109 +31,255 @@ import { useRouter } from 'next/router'; import { Stack } from '@mui/system'; import { useEffect, useState } from 'react'; -const useChartOptions = () => { - const theme = useTheme(); - - return { - chart: { - background: 'transparent', - stacked: false, - toolbar: { - show: true - } - }, - colors: [ - theme.palette.graphics.dark, - theme.palette.graphics.darkest, - theme.palette.graphics.light, - theme.palette.graphics.main, - theme.palette.graphics.lightest, - ], - dataLabels: { - enabled: false - }, - fill: { - opacity: 1, - type: 'solid' - }, - grid: { - borderColor: theme.palette.divider, - strokeDashArray: 2, - xaxis: { - lines: { - show: false - } - }, - yaxis: { - lines: { - show: true - } - } - }, - legend: { - show: true - }, - plotOptions: { - bar: { - columnWidth: '40px' - } - }, - stroke: { - colors: ['transparent'], - show: true, - width: 2 - }, - theme: { - mode: theme.palette.mode - }, - xaxis: { - axisBorder: { - color: theme.palette.divider, - show: true - }, - axisTicks: { - color: theme.palette.divider, - show: true - }, - categories: [ - 'Jan', - 'Feb', - 'Mar', - 'Apr', - 'May', - 'Jun', - 'Jul', - 'Aug', - 'Sep', - 'Oct', - 'Nov', - 'Dec' - ], - labels: { - offsetY: 5, - style: { - colors: theme.palette.text.secondary - } - } - }, - yaxis: { - labels: { - formatter: (value) => (value > 0 ? `${value}K` : `${value}`), - offsetX: -10, - style: { - colors: theme.palette.text.secondary - } - } - } - }; -}; - export const SiteSurvey = (props) => { - const chartSeries = [{ name: 'This year', data: [18, 16, 5, 8, 3, 14, 14, 16, 17, 19, 18, 20] }, { name: 'Last year', data: [12, 11, 4, 6, 2, 9, 9, 10, 11, 12, 13, 13] }] + + // const getMaxChannel = () => { + // if (frequency == "2.4GHz") { + // return 13; + // } else { + // return 128; + // } + // } + + // const geMinChannel = () => { + // if (frequency == "2.4GHz") { + // return 0; + // } else { + // return 36; + // } + // } + + // const getChannelSpacing = () => { + // if (frequency == "2.4GHz") { + // return 1; + // } else { + // return 4; + // } + // } + + // const getChannelAmount = () => { + // if (frequency == "2.4GHz") { + // return 13; + // } else { + // return 20; + // } + // } + + const getCategories = () => { + + } const router = useRouter(); - const [content, setContent] = useState(null); + const [frequency, setFrequency] = useState("2.4GHz"); + const [view, setView] = useState("chart"); + const [content, setContent] = useState(null); + + const getSeries = () => { + let series = [] + content[frequency].map((network) => { + + let data = [] + + if (frequency == "2.4GHz") { + if (Number(network.bandwidth) == 20) { + data.push({"x": Number(network.channel) -2, "y": -100}) + data.push({"x": Number(network.channel), "y": Number(network.signal_level)}) + data.push({"x": Number(network.channel) +2, "y": -100}) + } + if (Number(network.bandwidth) == 40) { + data.push({"x": Number(network.channel) -4, "y": -100}) + data.push({"x": Number(network.channel), "y": Number(network.signal_level)}) + data.push({"x": Number(network.channel) +4, "y": -100}) + } + }else { + if (Number(network.bandwidth) == 20) { + data.push({"x": Number(network.channel) -4, "y": -100}) + data.push({"x": Number(network.channel), "y": Number(network.signal_level)}) + data.push({"x": Number(network.channel) +4, "y": -100}) + } + if (Number(network.bandwidth) == 40) { + data.push({"x": Number(network.channel) -8, "y": -100}) + data.push({"x": Number(network.channel), "y": Number(network.signal_level)}) + data.push({"x": Number(network.channel) +8, "y": -100}) + } + if (Number(network.bandwidth) == 80) { + data.push({"x": Number(network.channel) -16, "y": -100}) + data.push({"x": Number(network.channel), "y": Number(network.signal_level)}) + data.push({"x": Number(network.channel) +16, "y": -100}) + } + if (Number(network.bandwidth) == 160) { + data.push({"x": Number(network.channel) -32, "y": -100}) + data.push({"x": Number(network.channel), "y": Number(network.signal_level)}) + data.push({"x": Number(network.channel) +32, "y": -100}) + } + } + + let ssid = network.ssid + if ( ssid == "") { + ssid = " " + } + return series.push({ + name: ssid, + data: data + }) + }) + return series; + } + + const useChartOptions = () => { + const theme = useTheme(); + + return { + chart: { + background: 'transparent', + stacked: false, + toolbar: { + show: true + }, + zoom: { + enabled: false + }, + }, + title: { + text: 'Site Survey Results', + }, + // markers: { + // size: 5, + // hover: { + // size: 9 + // } + // }, + colors: [ + theme.palette.graphics.dark, + theme.palette.warning.main, + theme.palette.graphics.darkest, + theme.palette.graphics.main, + theme.palette.info.light, + theme.palette.graphics.lightest, + theme.palette.primary.main, + theme.palette.graphics.light, + theme.palette.error.light, + ], + dataLabels: { + enabled: false + }, + grid: { + //borderColor: theme.palette.divider, + strokeDashArray: 2, + xaxis: { + lines: { + show: true + } + }, + yaxis: { + lines: { + show: true + }, + }, + }, + legend: { + show: true, + showForSingleSeries: true, + }, + plotOptions: { + area: { + fillTo: 'end', + } + }, + stroke: { + show: true, + curve: 'smooth', + lineCap: 'round', + }, + theme: { + mode: theme.palette.mode + }, + yaxis: { + min: -100, + max: 0, + labels: { + formatter: function (value) { + return value + ' dBm'; + }, + //offsetY: -10, + style: { + //colors: theme.palette.text.secondary + } + }, + }, + // annotations: { + // xaxis: [ + // { + // x: 9, + // x2: 10, + // borderColor: '#0b54ff', + // label: { + // style: { + // color: 'black', + // }, + // text: 'Channel 10', + // offsetX: 45, + // borderColor: 'transparent', + // style: { + // background: 'transparent', + // color: theme.palette.text.secondary, + // fontSize: '17px', + // }, + // } + // } + // ] + // }, + // annotations: { + // points: [{ + // x: 9, + // y: -5, + // label: { + // borderColor: '#775DD0', + // offsetY: 0, + // style: { + // color: '#fff', + // background: '#775DD0', + // }, + // rotate: -45, + // text: 'Bananas are good', + // } + // }] + // }, + xaxis: { + // tickPlacement: 'on', + // tickAmount: getChannelAmount(), + tickPlacement: 'on', + labels: { + show: true, + style: { + //colors: theme.palette.text.secondary + }, + trim: true, + }, + // max: getMaxChannel(), + // min: geMinChannel(), + // stepSize: getChannelSpacing(), + //type: 'category', + //categories: [getCategories()], + type: 'numeric', + decimalsInFloat: 0, + }, + tooltip: { + x: { + show: true, + formatter: (seriesName) => "Channel "+ seriesName, + }, + followCursor: false, + intersect: false, + shared: true, + enabled: true, + onDatasetHover: { + highlightDataSeries: true, + } + } + }; + }; const chartOptions = useChartOptions(); @@ -181,23 +323,30 @@ export const SiteSurvey = (props) => { <Grid spacing={1}> <Grid container> <Card> - <Button - color="inherit" - size="small" - disabled="true" + <ToggleButtonGroup + value={view} + exclusive + onChange={(e, value) => { + setView(value) + }} > - <SvgIcon> - <ChartBarSquareIcon /> - </SvgIcon> - </Button> - <Button - color="inherit" - size="small" - > - <SvgIcon> - <ListBulletIcon /> - </SvgIcon> - </Button> + <ToggleButton + size="small" + value="chart" + > + <SvgIcon> + <ChartBarSquareIcon /> + </SvgIcon> + </ToggleButton> + <ToggleButton + size="small" + value="list" + > + <SvgIcon> + <ListBulletIcon /> + </SvgIcon> + </ToggleButton> + </ToggleButtonGroup> </Card> </Grid> <Box display="flex" @@ -221,7 +370,7 @@ export const SiteSurvey = (props) => { </RadioGroup> </FormControl> </Box> - <Card sx={{ height: '100%' }}> + {view == "list" && <Card sx={{ height: '100%' }}> <Box sx={{ minWidth: 800, }}> <TableContainer sx={{ maxHeight: 600 }}> <Table exportButton={true}> @@ -273,31 +422,18 @@ export const SiteSurvey = (props) => { </Table> </TableContainer> </Box> - {/* <CardContent> - <Chart - height={500} - options={chartOptions} - series={chartSeries} - type="bar" - width="100%" - /> - </CardContent> - <Divider /> - <CardActions sx={{ justifyContent: 'center' }}> - <FormControl xs={2}> - <RadioGroup - aria-labelledby="demo-controlled-radio-buttons-group" - name="controlled-radio-buttons-group" - value={"2.4GHz"} - > - <Grid container> - <FormControlLabel value="2.4GHz" control={<Radio />} label="2.4GHz" /> - <FormControlLabel value="5GHz" control={<Radio />} label="5GHz" /> - </Grid> - </RadioGroup> - </FormControl> - </CardActions> */} - </Card> + </Card>} + {view == "chart" && <Card> + <CardContent> + <Chart + height={500} + options={chartOptions} + series={getSeries()} + type="area" + width="100%" + /> + </CardContent> + </Card>} </Grid>: <CircularProgress></CircularProgress>} </Stack> ); From 9df2a044045b2c860f32dc42a36f20627c90bf96 Mon Sep 17 00:00:00 2001 From: leandrofars <leandrofars@gmail.com> Date: Wed, 10 Jul 2024 11:14:55 -0300 Subject: [PATCH 25/28] chore(frontend): site survey add one more color to chart --- frontend/src/sections/devices/cwmp/site-survey.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/sections/devices/cwmp/site-survey.js b/frontend/src/sections/devices/cwmp/site-survey.js index a3d5fa7..dcfb1bb 100644 --- a/frontend/src/sections/devices/cwmp/site-survey.js +++ b/frontend/src/sections/devices/cwmp/site-survey.js @@ -160,6 +160,7 @@ export const SiteSurvey = (props) => { theme.palette.primary.main, theme.palette.graphics.light, theme.palette.error.light, + theme.palette.error.dark ], dataLabels: { enabled: false From 35372dd9800f329f8270826f97bb99d255863255 Mon Sep 17 00:00:00 2001 From: leandrofars <leandrofars@gmail.com> Date: Wed, 10 Jul 2024 11:59:56 -0300 Subject: [PATCH 26/28] feat(frontend): device rssi indication of signal state --- .../sections/devices/cwmp/connecteddevices.js | 189 ++++++++++-------- 1 file changed, 109 insertions(+), 80 deletions(-) diff --git a/frontend/src/sections/devices/cwmp/connecteddevices.js b/frontend/src/sections/devices/cwmp/connecteddevices.js index 50e86a2..7628a60 100644 --- a/frontend/src/sections/devices/cwmp/connecteddevices.js +++ b/frontend/src/sections/devices/cwmp/connecteddevices.js @@ -16,102 +16,131 @@ export const ConnectedDevices = () => { const [interfaces, setInterfaces] = useState([]); const [interfaceValue, setInterfaceValue] = useState(null); + const getConnectionState = (rssi) => { + let connectionStatus = "Signal " + if (rssi > -30) { + return connectionStatus + "Excellent" + } else if (rssi > -60) { + return connectionStatus + "Good" + } else if (rssi > -70) { + return connectionStatus + "Bad" + } else { + return connectionStatus + "Awful" + } + } + const fetchConnectedDevicesData = async () => { var myHeaders = new Headers(); myHeaders.append("Content-Type", "application/json"); myHeaders.append("Authorization", localStorage.getItem("token")); - + var requestOptions = { - method: 'GET', - headers: myHeaders, - redirect: 'follow' + method: 'GET', + headers: myHeaders, + redirect: 'follow' }; fetch(`${process.env.NEXT_PUBLIC_REST_ENDPOINT || ""}/api/device/${router.query.id[0]}/connecteddevices`, requestOptions) - .then(response => { - if (response.status === 401) { - router.push("/auth/login") - } - return response.json() - }) - .then(result => { - console.log("connecteddevices content", result) - let interfaces = Object.keys(result) - setInterfaces(interfaces) - setInterfaceValue(interfaces[0]) - setContent(result) - }) - .catch(error => console.log('error', error)); + .then(response => { + if (response.status === 401) { + router.push("/auth/login") + } + return response.json() + }) + .then(result => { + console.log("connecteddevices content", result) + let interfaces = Object.keys(result) + setInterfaces(interfaces) + setInterfaceValue(interfaces[0]) + setContent(result) + }) + .catch(error => console.log('error', error)); }; useEffect(() => { fetchConnectedDevicesData(); - },[]) + }, []) - return ( - <Stack - justifyContent="center" - alignItems={(!content || interfaces.length == 0) &&"center"} + return ( + <Stack + justifyContent="center" + alignItems={(!content || interfaces.length == 0) && "center"} > - {content && interfaces.length > 0 ? - <Card> - <CardContent> - <Grid mb={3}> - <InputLabel> Interface </InputLabel> - <Select label="interface" variant="standard" value={interfaceValue} onChange={(e)=> setInterfaceValue(e.target.value)}> - {( - interfaces.map((item, index) => ( - <MenuItem key={index} value={item}> - {item} - </MenuItem> + {content && interfaces.length > 0 ? + <Card> + <CardContent> + <Grid mb={3}> + <InputLabel> Interface </InputLabel> + <Select label="interface" variant="standard" value={interfaceValue} onChange={(e) => setInterfaceValue(e.target.value)}> + {( + interfaces.map((item, index) => ( + <MenuItem key={index} value={item}> + {item} + </MenuItem> + )) + )} + </Select> + </Grid> + { + content[interfaceValue].map((property, index) => ( + <Card key={index}> + <CardContent> + <Grid container justifyContent={"center"}> + <Stack direction="row" spacing={5}> + <Stack justifyItems={"center"} direction={"row"} mt={2}> + <Tooltip title={property.active ? "Online" : "Offline"}> + <SvgIcon> + <CpuChipIcon color={property.active ? theme.palette.success.main : theme.palette.error.main}></CpuChipIcon> + </SvgIcon> + </Tooltip> + <Typography ml={"10px"}> + {property.hostname} + </Typography> + </Stack> + <Divider orientation="vertical" /> + <Stack spacing={2}> + <Typography> + IP address: {property.ip_adress} + </Typography> + <Typography> + MAC: {property.mac} + </Typography> + </Stack> + <Stack spacing={2}> + <Typography> + Source: {property.adress_source} + </Typography> + <Tooltip title={getConnectionState(property.rssi)}> + <Typography display={"flex"} color={() => { + let rssi = property.rssi + if (rssi > -30) { + return theme.palette.success.main + } else if (rssi > -60) { + return theme.palette.success.main + } else if (rssi > -70) { + return theme.palette.warning.main + } else { + return theme.palette.error.main + } + }}> + <Typography color={theme.palette.neutral[900]} sx={{pr:"5px"}}> + RSSI: + </Typography> + {property.rssi} dbm + </Typography> + </Tooltip> + </Stack> + </Stack> + </Grid> + </CardContent> + </Card> )) - )} - </Select> - </Grid> - { - content[interfaceValue].map((property,index) => ( - <Card key={index}> - <CardContent> - <Grid container justifyContent={"center"}> - <Stack direction="row" spacing={5}> - <Stack justifyItems={"center"} direction={"row"} mt={2}> - <Tooltip title={property.active ? "Online": "Offline"}> - <SvgIcon> - <CpuChipIcon color={property.active ? theme.palette.success.main : theme.palette.error.main}></CpuChipIcon> - </SvgIcon> - </Tooltip> - <Typography ml={"10px"}> - {property.hostname} - </Typography> - </Stack> - <Divider orientation="vertical"/> - <Stack spacing={2}> - <Typography> - IP address: {property.ip_adress} - </Typography> - <Typography> - MAC: {property.mac} - </Typography> - </Stack> - <Stack spacing={2}> - <Typography> - RSSI: {property.rssi} dbm - </Typography> - <Typography> - Source: {property.adress_source} - </Typography> - </Stack> - </Stack> - </Grid> - </CardContent> - </Card> - )) - } - </CardContent> - </Card>: ( - content ? <Typography> No connected devices found </Typography> : <CircularProgress/> - )} + } + </CardContent> + </Card> : ( + content ? <Typography> No connected devices found </Typography> : <CircularProgress /> + )} </Stack> ) } \ No newline at end of file From 8e1760fad1a77af406b157f6d219d863e9713b2d Mon Sep 17 00:00:00 2001 From: leandrofars <leandrofars@gmail.com> Date: Wed, 10 Jul 2024 13:54:20 -0300 Subject: [PATCH 27/28] fix(frontend): connected devices no rssi state when dBm is zero --- frontend/src/sections/devices/cwmp/connecteddevices.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/sections/devices/cwmp/connecteddevices.js b/frontend/src/sections/devices/cwmp/connecteddevices.js index 7628a60..6d5d859 100644 --- a/frontend/src/sections/devices/cwmp/connecteddevices.js +++ b/frontend/src/sections/devices/cwmp/connecteddevices.js @@ -114,7 +114,9 @@ export const ConnectedDevices = () => { <Tooltip title={getConnectionState(property.rssi)}> <Typography display={"flex"} color={() => { let rssi = property.rssi - if (rssi > -30) { + if(rssi == 0){ + return theme.palette.neutral[900] + } else if (rssi > -30) { return theme.palette.success.main } else if (rssi > -60) { return theme.palette.success.main From b057076ed42cce03d107d8829599917f43c70e45 Mon Sep 17 00:00:00 2001 From: leandrofars <leandrofars@gmail.com> Date: Wed, 10 Jul 2024 13:59:44 -0300 Subject: [PATCH 28/28] feat: new oktopus emails --- README.md | 4 ++-- backend/services/controller/internal/bridge/bridge.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dc6e229..2c8f3ae 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Oktopus is a multi-vendor management platform for CPEs and IoTs. <b>Any device t <a href="https://www.inango.com/" target="_blank"><img src="https://github.com/OktopUSP/oktopus/assets/83298718/3b3e65d9-33fa-46c4-8b24-f9e2a84a04a6" width="100px"/></a> -<p>If you'd like to become a sponsor, start a partnership or somehow to contribute to the project, email <a href="">leandro@oktopus.app.br</a>, every contribution is welcome, and the resources will help the project to move on. Also, if your company uses this project and you'd like your logo to appear up here, contact us. +<p>If you'd like to become a sponsor, start a partnership or somehow to contribute to the project, email <a href="">contact@oktopus.app.br</a>, every contribution is welcome, and the resources will help the project to move on. Also, if your company uses this project and you'd like your logo to appear up here, contact us. <ul> <li> @@ -23,7 +23,7 @@ Oktopus is a multi-vendor management platform for CPEs and IoTs. <b>Any device t <p> Our solution has an open-source software license, meaning you can modify/study the code and use it for free. You can perform all the configurations, allocate servers, and set it up on your network with the classic "do it yourself" approach, or save time and money: contact us for a quote and get commercial support. </p> - <p>Contact <a href="">leandro@oktopus.app.br</a> via email and get a quote.</p> + <p>Contact <a href="">sales@oktopus.app.br</a> via email and get a quote.</p> </li> </ul> diff --git a/backend/services/controller/internal/bridge/bridge.go b/backend/services/controller/internal/bridge/bridge.go index f9697ed..16f1b43 100644 --- a/backend/services/controller/internal/bridge/bridge.go +++ b/backend/services/controller/internal/bridge/bridge.go @@ -263,7 +263,7 @@ func NatsEnterpriseInteraction( if err != nil { if err == nats.ErrNoResponders { w.WriteHeader(http.StatusInternalServerError) - w.Write(utils.Marshall("You have no enterprise license, to get one contact: leandro@oktopus.app.br")) + w.Write(utils.Marshall("You have no enterprise license, to get one contact: sales@oktopus.app.br")) return err } w.WriteHeader(http.StatusInternalServerError)