From 613501657da115d10ba12ef61cdf3152bd476ced Mon Sep 17 00:00:00 2001 From: leandrofars Date: Wed, 30 Oct 2024 08:18:19 -0300 Subject: [PATCH 01/18] feat(frontend): add new enterprise modules --- frontend/src/layouts/dashboard/config.js | 67 ++++++++++++++----- .../src/layouts/dashboard/side-nav-item.js | 13 +++- 2 files changed, 62 insertions(+), 18 deletions(-) diff --git a/frontend/src/layouts/dashboard/config.js b/frontend/src/layouts/dashboard/config.js index e77f4d2..6e85951 100644 --- a/frontend/src/layouts/dashboard/config.js +++ b/frontend/src/layouts/dashboard/config.js @@ -1,14 +1,16 @@ import ChartBarIcon from '@heroicons/react/24/solid/ChartBarIcon'; import CogIcon from '@heroicons/react/24/solid/CogIcon'; import ChatBubbleLeftRightIcon from '@heroicons/react/24/solid/ChatBubbleLeftRightIcon' -import MapIcon from '@heroicons/react/24/solid/MapIcon' import RectangleGroupIcon from '@heroicons/react/24/solid/RectangleGroupIcon' import ArrowDownOnSquareStackIcon from '@heroicons/react/24/solid/ArrowDownOnSquareStackIcon' import UserGroupIcon from '@heroicons/react/24/solid/UserGroupIcon' import KeyIcon from '@heroicons/react/24/solid/KeyIcon' import CpuChip from '@heroicons/react/24/solid/CpuChipIcon'; -import BriefCaseIcon from '@heroicons/react/24/outline/BriefcaseIcon'; import { SvgIcon } from '@mui/material'; +import FolderIcon from '@heroicons/react/24/solid/FolderIcon'; +import ShieldCheckIcon from '@heroicons/react/24/solid/ShieldCheckIcon'; +import EnvelopeIcon from '@heroicons/react/24/solid/EnvelopeIcon'; +import UserIcon from '@heroicons/react/24/solid/UserIcon'; export const items = [ { @@ -48,19 +50,19 @@ export const items = [ ), disabled: true - } + }, + { + title: 'Message', + tooltip: 'Upgrade to Business Plan', + disabled: true, + icon: ( + + + + ) + }, ] }, - { - title: 'Map', - tooltip: 'Upgrade to Business Plan', - icon: ( - - - - ), - disabled: true - }, { title: 'Credentials', path: '/credentials', @@ -71,11 +73,44 @@ export const items = [ ) }, { - title: 'Users', - path: '/users', + title: 'Access Control', + disabled: true, + tooltip: 'Upgrade to Business Plan', icon: ( - + + + ), + children: [ + { + title: 'Roles', + disabled: true, + tooltip: 'Upgrade to Business Plan', + icon: ( + + + + ) + }, + { + title: 'Users', + disabled: true, + tooltip: 'Upgrade to Business Plan', + icon: ( + + + + ) + }, + ] + }, + { + title: 'File Server', + tooltip: 'Upgrade to Business Plan', + disabled: true, + icon: ( + + ) }, diff --git a/frontend/src/layouts/dashboard/side-nav-item.js b/frontend/src/layouts/dashboard/side-nav-item.js index dee2d2d..e46634c 100644 --- a/frontend/src/layouts/dashboard/side-nav-item.js +++ b/frontend/src/layouts/dashboard/side-nav-item.js @@ -9,7 +9,7 @@ import { usePathname } from 'next/navigation'; export const SideNavItem = (props) => { const { active = false, disabled, external, icon, path, title, children, padleft, tooltip } = props; - const [open, setOpen] = useState(false); + const [open, setOpen] = useState(true); const pathname = usePathname(); const isItemActive = (currentPath, itemPath) => { @@ -64,10 +64,14 @@ export const SideNavItem = (props) => { } }} > - {icon && ( { + if (!path){ + setOpen(!open) + } + }} sx={{ alignItems: 'center', color: 'neutral.400', @@ -85,6 +89,11 @@ export const SideNavItem = (props) => { )} { + if (!path){ + setOpen(!open) + } + }} sx={{ color: 'neutral.400', flexGrow: 1, From e356716eeeb1619be81d51958a40555cedbe5965 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Wed, 30 Oct 2024 08:29:13 -0300 Subject: [PATCH 02/18] fix(frontend): sign out errors --- frontend/src/contexts/auth-context.js | 2 ++ frontend/src/pages/devices.js | 23 +++++++++-------------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/frontend/src/contexts/auth-context.js b/frontend/src/contexts/auth-context.js index a35e9d4..8400f9e 100644 --- a/frontend/src/contexts/auth-context.js +++ b/frontend/src/contexts/auth-context.js @@ -211,6 +211,8 @@ export const AuthProvider = (props) => { }; const signOut = () => { + router.push("/auth/login") + localStorage.removeItem("token") dispatch({ type: HANDLERS.SIGN_OUT }); diff --git a/frontend/src/pages/devices.js b/frontend/src/pages/devices.js index 1f91311..48183ee 100644 --- a/frontend/src/pages/devices.js +++ b/frontend/src/pages/devices.js @@ -172,8 +172,7 @@ const Page = () => { } const getDeviceProtocol = (order) => { - console.log("order:", order) - if (order.Cwmp == 2) { + if (order.Cwmp != null) { return "cwmp" } else { return "usp" @@ -204,15 +203,10 @@ const Page = () => { useEffect(() => { getColumns() setLoading(true) - if (auth.user.token) { - console.log("auth.user.token =", auth.user.token) - } else { - auth.user.token = localStorage.getItem("token") - } var myHeaders = new Headers(); myHeaders.append("Content-Type", "application/json"); - myHeaders.append("Authorization", auth.user.token); + myHeaders.append("Authorization", localStorage.getItem("token")); var requestOptions = { method: 'GET', @@ -293,8 +287,9 @@ const Page = () => { console.log("set alias result:", content) setShowSetDeviceAlias(false) setDeviceAlias(null) - orders[deviceToBeChanged].Alias = alias + devices[deviceToBeChanged].Alias = alias setDeviceToBeChanged(null) + setDevices([...devices]) } // .then(response => { // if (response.status === 401) { @@ -415,7 +410,7 @@ const Page = () => { <> - Oktopus | TR-369 + Oktopus | Controller @@ -651,7 +646,7 @@ const Page = () => { } - {/* + - + {/* } From e9d6b808d10595aac1e58207184e391098a45962 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Wed, 30 Oct 2024 08:35:55 -0300 Subject: [PATCH 03/18] feat(frontend): new users page --- frontend/src/layouts/dashboard/config.js | 5 ++--- frontend/src/pages/{ => access-control}/users.js | 0 2 files changed, 2 insertions(+), 3 deletions(-) rename frontend/src/pages/{ => access-control}/users.js (100%) diff --git a/frontend/src/layouts/dashboard/config.js b/frontend/src/layouts/dashboard/config.js index 6e85951..4a836b8 100644 --- a/frontend/src/layouts/dashboard/config.js +++ b/frontend/src/layouts/dashboard/config.js @@ -94,11 +94,10 @@ export const items = [ }, { title: 'Users', - disabled: true, - tooltip: 'Upgrade to Business Plan', + path: '/access-control/users', icon: ( - + ) }, diff --git a/frontend/src/pages/users.js b/frontend/src/pages/access-control/users.js similarity index 100% rename from frontend/src/pages/users.js rename to frontend/src/pages/access-control/users.js From c65d0a53503cacc340884941c88209371c0cde08 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Wed, 30 Oct 2024 08:39:20 -0300 Subject: [PATCH 04/18] chore(frontend): remove maps page --- frontend/src/pages/map.js | 259 -------------------------------------- 1 file changed, 259 deletions(-) delete mode 100644 frontend/src/pages/map.js diff --git a/frontend/src/pages/map.js b/frontend/src/pages/map.js deleted file mode 100644 index 6ba92ea..0000000 --- a/frontend/src/pages/map.js +++ /dev/null @@ -1,259 +0,0 @@ -import Head from 'next/head'; -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'; -import { useRouter } from 'next/router'; - -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 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 () => { - - 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 { - setMarkers([]) - console.log("error to get map markers") - } - } - - 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(()=> { - 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 - 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.log("Error getting user location:", error); - } - ); - } else { - // Geolocation is not supported by the browser - console.log("Geolocation is not supported by this browser, or the user denied access"); - } - },[]) - - const mapOptions = useMemo( - () => ({ - disableDefaultUI: false, - clickableIcons: true, - zoomControl: true, - controlSize: 23, - styles: mapStyles, - mapTypeControlOptions: { - mapTypeIds: ['roadmap', 'satellite'], - } - }), - [] - ); - - const { isLoaded } = useLoadScript({ - googleMapsApiKey: process.env.NEXT_PUBLIC_GOOGLE_MAPS_KEY, - libraries: libraries, - }); - - if (!isLoaded) { - return

Loading...

; - } - - return ( markers && zoom && - <> - - - Maps | Oktopus - - - - { - markers.map((marker, index) => ( - { - 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 } - }); - }} - > - - )) - } - {activeMarker && - -
SN: {activeMarker.sn}
-
-
Model: {activeMarkerdata.Model?activeMarkerdata.Model:activeMarkerdata.ProductClass}
-
Alias: {activeMarkerdata.Alias}
-
Status: {activeMarkerdata.Status == 2 ? online : offline}
-
- - :

no device info found

} - />} -
- - )}; - - Page.getLayout = (page) => ( - - {page} - - ); - - export default Page; \ No newline at end of file From 8efa09397b49e3709067b69a90420658ab450878 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Wed, 30 Oct 2024 08:44:54 -0300 Subject: [PATCH 05/18] feat(frontend): add backend and error contexts --- frontend/src/contexts/backend-context.js | 91 ++++++++++++++++++++++++ frontend/src/contexts/error-context.js | 32 +++++++++ frontend/src/pages/_app.js | 31 ++++---- 3 files changed, 142 insertions(+), 12 deletions(-) create mode 100644 frontend/src/contexts/backend-context.js create mode 100644 frontend/src/contexts/error-context.js diff --git a/frontend/src/contexts/backend-context.js b/frontend/src/contexts/backend-context.js new file mode 100644 index 0000000..4a26070 --- /dev/null +++ b/frontend/src/contexts/backend-context.js @@ -0,0 +1,91 @@ +import { createContext, useContext, } from 'react'; +import { useRouter } from 'next/router'; +import { useAlertContext } from './error-context'; + +export const BackendContext = createContext({ undefined }); + +export const BackendProvider = (props) => { + const { children } = props; + + const { setAlert } = useAlertContext(); + + const router = useRouter(); + + var myHeaders = new Headers(); + myHeaders.append("Content-Type", "application/json"); + myHeaders.append("Authorization", localStorage.getItem("token")); + + const httpRequest = async (path, method, body, headers, encoding) => { + + var requestOptions = { + method: method, + redirect: 'follow', + }; + + if (body) { + requestOptions.body = body + } + + console.log("headers:", headers) + if (headers) { + requestOptions.headers = headers + }else { + requestOptions.headers = myHeaders + } + + const response = await fetch(`${process.env.NEXT_PUBLIC_REST_ENDPOINT || ""}${path}`, requestOptions) + console.log("status:", response.status) + if (response.status != 200) { + if (response.status == 401) { + router.push("/auth/login") + } + if (response.status == 204 || response.status == 201) { + return {status : response.status, result: null} + } + const data = await response.text() + // setAlert({ + // severity: "error", + // title: "Error", + // message: `Status: ${response.status}, Content: ${data}`, + // }) + setAlert({ + severity: "error", + // title: "Error", + message: `${data}`, + }) + return {status : response.status, result: null} + } + if (encoding) { + console.log("encoding:", encoding) + if (encoding == "text") { + const data = await response.text() + return {status: response.status, result: data} + }else if (encoding == "blob") { + const data = await response.blob() + return {status: response.status, result: data} + }else if (encoding == "json") { + const data = await response.json() + return {status: response.status, result: data} + }else{ + return {status: response.status, result: response} + } + } + const data = await response.json() + return {status: response.status, result: data} + } + + return ( + + {children} + + ); +}; + +export const BackendConsumer = BackendContext.Consumer; + +export const useBackendContext = () => useContext(BackendContext); \ No newline at end of file diff --git a/frontend/src/contexts/error-context.js b/frontend/src/contexts/error-context.js new file mode 100644 index 0000000..75651d9 --- /dev/null +++ b/frontend/src/contexts/error-context.js @@ -0,0 +1,32 @@ +import { createContext, useContext, useState } from 'react'; + + +export const AlertContext = createContext({ undefined }); + +export const AlertProvider = (props) => { + const { children } = props; + /* + { + severity: '', // options => error, warning, info, success + message: '', + title: '', + } + */ + // const [alert, setAlert] = useState(null); + const [alert, setAlert] = useState(); + + return ( + + {children} + + ); +}; + +export const AlertConsumer = AlertContext.Consumer; + +export const useAlertContext = () => useContext(AlertContext); \ No newline at end of file diff --git a/frontend/src/pages/_app.js b/frontend/src/pages/_app.js index a59cb06..d745110 100644 --- a/frontend/src/pages/_app.js +++ b/frontend/src/pages/_app.js @@ -9,9 +9,10 @@ import { useNProgress } from 'src/hooks/use-nprogress'; 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'; import { useEffect, useState } from 'react'; +import { BackendProvider } from 'src/contexts/backend-context'; +import { AlertProvider } from 'src/contexts/error-context'; const clientSideEmotionCache = createEmotionCache(); @@ -33,7 +34,7 @@ const App = (props) => { - Oktopus | TR-369 Controller + Oktopus | Controller { - - - - { - (auth) => auth.isLoading - ? - : getLayout() - } - - + {/* */} + + + + + + { + (auth) => auth.isLoading + ? + : getLayout() + } + + + + + {/* */} From 0d1add60c2a4e0ee8b51e83c81f1e039db194aec Mon Sep 17 00:00:00 2001 From: leandrofars Date: Wed, 30 Oct 2024 09:22:22 -0300 Subject: [PATCH 06/18] fix(frontend): alert component handler --- frontend/src/layouts/dashboard/layout.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/frontend/src/layouts/dashboard/layout.js b/frontend/src/layouts/dashboard/layout.js index 104db95..3dddca3 100644 --- a/frontend/src/layouts/dashboard/layout.js +++ b/frontend/src/layouts/dashboard/layout.js @@ -4,6 +4,8 @@ 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 { useAlertContext } from 'src/contexts/error-context'; +import { Alert, AlertTitle, Snackbar } from '@mui/material'; const SIDE_NAV_WIDTH = 280; @@ -28,6 +30,8 @@ export const Layout = withAuthGuard((props) => { const pathname = usePathname(); const [openNav, setOpenNav] = useState(false); + const {alert, setAlert} = useAlertContext(); + const handlePathnameChange = useCallback( () => { if (openNav) { @@ -57,6 +61,21 @@ export const Layout = withAuthGuard((props) => { {children} + {alert && setAlert(null)} + > + + {alert?.title && {alert.title}} + {alert?.message} + + } ); }); \ No newline at end of file From 5b299b993aaf37191fe332376a9d7f294e238340 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Wed, 30 Oct 2024 09:22:54 -0300 Subject: [PATCH 07/18] fix(frontend): update password --- .../sections/settings/settings-password.js | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/frontend/src/sections/settings/settings-password.js b/frontend/src/sections/settings/settings-password.js index 8899d98..e022fde 100644 --- a/frontend/src/sections/settings/settings-password.js +++ b/frontend/src/sections/settings/settings-password.js @@ -9,8 +9,14 @@ import { Stack, TextField } from '@mui/material'; +import { useBackendContext } from 'src/contexts/backend-context'; +import { useAlertContext } from 'src/contexts/error-context'; export const SettingsPassword = () => { + + let {httpRequest} = useBackendContext(); + let {setAlert} = useAlertContext(); + const [values, setValues] = useState({ password: '', confirm: '' @@ -26,15 +32,8 @@ export const SettingsPassword = () => { [] ); - const handleSubmit = useCallback( - (event) => { - event.preventDefault(); - }, - [] - ); - return ( -
+ { - From 51bc89dfb7fb5aa7557cd5b41dca5a76f70df5cd Mon Sep 17 00:00:00 2001 From: leandrofars Date: Wed, 30 Oct 2024 09:25:39 -0300 Subject: [PATCH 08/18] feat(controller): validate new user password + only allow to change own user password --- .../services/controller/internal/api/user.go | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/backend/services/controller/internal/api/user.go b/backend/services/controller/internal/api/user.go index 3e31726..9103e93 100644 --- a/backend/services/controller/internal/api/user.go +++ b/backend/services/controller/internal/api/user.go @@ -135,16 +135,6 @@ func (a *Api) changePassword(w http.ResponseWriter, r *http.Request) { return } - userToChangePasswd := mux.Vars(r)["user"] - if userToChangePasswd != "" && userToChangePasswd != email { - rUser, _ := a.db.FindUser(email) - if rUser.Level != db.AdminUser { - w.WriteHeader(http.StatusForbidden) - return - } - email = userToChangePasswd - } - var user db.User err = json.NewDecoder(r.Body).Decode(&user) if err != nil { @@ -154,6 +144,12 @@ func (a *Api) changePassword(w http.ResponseWriter, r *http.Request) { } user.Email = email + if len(user.Password) < 8 { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("Password must be at least 8 characters long")) + return + } + if err := user.HashPassword(user.Password); err != nil { w.WriteHeader(http.StatusInternalServerError) return @@ -164,6 +160,7 @@ func (a *Api) changePassword(w http.ResponseWriter, r *http.Request) { return } + w.WriteHeader(http.StatusNoContent) } func (a *Api) registerAdminUser(w http.ResponseWriter, r *http.Request) { From a43e62c96bdcc5daed832970699bbd70d7d112b0 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Wed, 30 Oct 2024 10:01:31 -0300 Subject: [PATCH 09/18] fix(frontend): get device management protocol --- frontend/src/pages/devices.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/devices.js b/frontend/src/pages/devices.js index 48183ee..dec5f6e 100644 --- a/frontend/src/pages/devices.js +++ b/frontend/src/pages/devices.js @@ -172,7 +172,8 @@ const Page = () => { } const getDeviceProtocol = (order) => { - if (order.Cwmp != null) { + console.log("order:", order) + if (order.Cwmp == 2) { return "cwmp" } else { return "usp" From 97ff229f0ea00c099b26f2f98aca998496401e26 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Wed, 30 Oct 2024 10:02:28 -0300 Subject: [PATCH 10/18] feat(frontend): usp device add enterprise tabs --- frontend/src/pages/devices/usp/[...id].js | 43 ++++++++++++++++++----- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/frontend/src/pages/devices/usp/[...id].js b/frontend/src/pages/devices/usp/[...id].js index 986c10b..22a7f35 100644 --- a/frontend/src/pages/devices/usp/[...id].js +++ b/frontend/src/pages/devices/usp/[...id].js @@ -20,6 +20,9 @@ import SignalIcon from '@heroicons/react/24/solid/SignalIcon'; import DevicePhoneMobile from '@heroicons/react/24/solid/DevicePhoneMobileIcon'; import WrenchScrewDriverIcon from '@heroicons/react/24/outline/WrenchScrewdriverIcon'; import CommandLineIcon from '@heroicons/react/24/outline/CommandLineIcon'; +import CubeTransparentIcon from '@heroicons/react/24/outline/CubeTransparentIcon'; +import MapPin from '@heroicons/react/24/outline/MapPinIcon'; +import ArrowTrendingUp from '@heroicons/react/24/outline/ArrowTrendingUpIcon'; const Page = () => { const router = useRouter() @@ -54,9 +57,9 @@ const Page = () => { py: 0, }} > - - - + + + {[ Devices , @@ -72,9 +75,8 @@ const Page = () => { - + }}> + } @@ -117,6 +119,27 @@ const Page = () => { value={"ports"} /> } + iconPosition={"end"} + label="Historic" + style={{cursor:"default", opacity: 0.5}} + value={"historic"} /> + + } + iconPosition={"end"} + label="LCM" + style={{cursor:"default", opacity: 0.5}} + value={"lcm"} /> + + } + iconPosition={"end"} + label="Location" + style={{cursor:"default", opacity: 0.5}} + value={"location"} /> + + } iconPosition={"end"} label="Actions" @@ -127,15 +150,19 @@ const Page = () => { onClick={()=>{router.push(`/devices/usp/${deviceID}/discovery`)}} icon={} iconPosition={"end"} - label="Discover Parameters" /> + label="Parameters" /> {router.push(`/devices/usp/${deviceID}/msg`)}} icon={} iconPosition={"end"} - label="Remote Messages" /> + label="Messages" /> + + + + { sectionHandler() } From 8017e3cb26b90aea297003f6486b01224833dfab Mon Sep 17 00:00:00 2001 From: leandrofars Date: Wed, 30 Oct 2024 10:08:00 -0300 Subject: [PATCH 11/18] fix(adapter): do not overwrite device alias --- backend/services/mtp/adapter/internal/db/device.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/backend/services/mtp/adapter/internal/db/device.go b/backend/services/mtp/adapter/internal/db/device.go index 50ef7e8..bb6be92 100644 --- a/backend/services/mtp/adapter/internal/db/device.go +++ b/backend/services/mtp/adapter/internal/db/device.go @@ -82,6 +82,13 @@ func (d *Database) CreateDevice(device Device) error { return err } } + + /* ------------------------- Do not overwrite alias ------------------------- */ + if deviceExistent.Alias != "" { + device.Alias = deviceExistent.Alias + } + /* -------------------------------------------------------------------------- */ + /* -------------------------------------------------------------------------- */ callback := func(sessCtx mongo.SessionContext) (interface{}, error) { From df885f63e0b6bffbdceeba5da9aca16f343fb79c Mon Sep 17 00:00:00 2001 From: leandrofars Date: Wed, 30 Oct 2024 13:24:04 -0300 Subject: [PATCH 12/18] fix(frontend): devices pages title + router redirect + usp page styling --- frontend/src/pages/devices/cwmp/[...id].js | 25 ++++++++++++++-------- frontend/src/pages/devices/usp/[...id].js | 4 ++-- frontend/src/pages/index.js | 2 +- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/frontend/src/pages/devices/cwmp/[...id].js b/frontend/src/pages/devices/cwmp/[...id].js index b30ac99..2047b32 100644 --- a/frontend/src/pages/devices/cwmp/[...id].js +++ b/frontend/src/pages/devices/cwmp/[...id].js @@ -31,10 +31,10 @@ const Page = () => { switch(section){ case "msg": return - case "wifi": - return + /* case "wifi": + return */ default: - router.push(`/devices/cwmp/${deviceID}/wifi`) + router.replace(`/devices/cwmp/${deviceID}/msg`) } } @@ -42,7 +42,7 @@ const Page = () => { <> - Oktopus | TR-369 + Oktopus | Controller { py: 0, }} > - - - + + + {[ Devices , @@ -71,13 +71,16 @@ const Page = () => { display:'flex', justifyContent:'center', }}> - + + } iconPosition={"end"} label="Wi-Fi" onClick={()=>{router.push(`/devices/cwmp/${deviceID}/wifi`)}} - value={"wifi"}/> + value={"wifi"} + disabled + /> } @@ -127,6 +130,10 @@ const Page = () => { label="Remote Messages" /> + + + + { sectionHandler() } diff --git a/frontend/src/pages/devices/usp/[...id].js b/frontend/src/pages/devices/usp/[...id].js index 22a7f35..a42bd41 100644 --- a/frontend/src/pages/devices/usp/[...id].js +++ b/frontend/src/pages/devices/usp/[...id].js @@ -39,7 +39,7 @@ const Page = () => { case "discovery": return default: - router.push(`/devices/usp/${deviceID}/discovery`) + router.replace(`/devices/usp/${deviceID}/discovery`) } } @@ -47,7 +47,7 @@ const Page = () => { <> - Oktopus | TR-369 + Oktopus | Controller { <> - Oktopus | TR-369 + Oktopus | Controller Date: Wed, 30 Oct 2024 13:38:32 -0300 Subject: [PATCH 13/18] fix(acs): connection request update when device changes ip and similars --- backend/services/acs/internal/server/handler/cwmp.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/services/acs/internal/server/handler/cwmp.go b/backend/services/acs/internal/server/handler/cwmp.go index 3f3bc65..aff1024 100644 --- a/backend/services/acs/internal/server/handler/cwmp.go +++ b/backend/services/acs/internal/server/handler/cwmp.go @@ -80,6 +80,8 @@ func (h *Handler) CwmpHandler(w http.ResponseWriter, r *http.Request) { h.pub(NATS_CWMP_SUBJECT_PREFIX+sn+".info", tmp) } + cpe.ConnectionRequestURL = Inform.GetConnectionRequest() // Update connection request URL, in case the CPE changed IP + log.Printf("Received an Inform from device %s withEventCodes %s", addr, Inform.GetEvents()) expiration := time.Now().AddDate(0, 0, 1) From a895e3ce310adc476cb174119f1d3524eba40637 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Wed, 30 Oct 2024 13:55:35 -0300 Subject: [PATCH 14/18] feat(frontend): add cwmp tabs available at enterprise version --- frontend/src/pages/devices/cwmp/[...id].js | 38 ++++++++++++++++++---- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/frontend/src/pages/devices/cwmp/[...id].js b/frontend/src/pages/devices/cwmp/[...id].js index 2047b32..8576e49 100644 --- a/frontend/src/pages/devices/cwmp/[...id].js +++ b/frontend/src/pages/devices/cwmp/[...id].js @@ -19,6 +19,9 @@ import DevicePhoneMobile from '@heroicons/react/24/solid/DevicePhoneMobileIcon'; import WrenchScrewDriverIcon from '@heroicons/react/24/outline/WrenchScrewdriverIcon'; import CommandLineIcon from '@heroicons/react/24/outline/CommandLineIcon'; import { DevicesWiFi } from 'src/sections/devices/cwmp/devices-wifi'; +import ArrowTrendingUpIcon from '@heroicons/react/24/outline/ArrowTrendingUpIcon'; +import DocumentTextIcon from '@heroicons/react/24/outline/DocumentTextIcon'; +import MapPinIcon from '@heroicons/react/24/outline/MapPinIcon'; const Page = () => { @@ -52,7 +55,7 @@ const Page = () => { py: 0, }} > - + {[ @@ -79,8 +82,7 @@ const Page = () => { label="Wi-Fi" onClick={()=>{router.push(`/devices/cwmp/${deviceID}/wifi`)}} value={"wifi"} - disabled - /> + style={{opacity:"0.5", cursor:"default"}}/> } @@ -111,23 +113,47 @@ const Page = () => { icon={} iconPosition={"end"} label="Ports" - onClick={()=>{router.push(`/devices/usp/${deviceID}/ports`)}} + onClick={()=>{router.push(`/devices/cwmp/${deviceID}/ports`)}} style={{opacity:"0.5", cursor:"default"}} value={"ports"} /> } + iconPosition={"end"} + label="Historic" + onClick={()=>{router.push(`/devices/cwmp/${deviceID}/historic`)}} + value={"historic"} + style={{opacity:"0.5", cursor:"default"}}/> + + } iconPosition={"end"} label="Actions" - onClick={()=>{router.push(`/devices/usp/${deviceID}/actions`)}} + onClick={()=>{router.push(`/devices/cwmp/${deviceID}/actions`)}} style={{opacity:"0.5", cursor:"default"}} value={"actions"} /> + + } + iconPosition={"end"} + label="Logs" + onClick={()=>{router.push(`/devices/cwmp/${deviceID}/logs`)}} + style={{opacity:"0.5", cursor:"default"}} + value={"logs"} /> + + } + iconPosition={"end"} + label="Location" + onClick={()=>{router.push(`/devices/cwmp/${deviceID}/location`)}} + style={{opacity:"0.5", cursor:"default"}} + value={"location"} /> {router.push(`/devices/cwmp/${deviceID}/msg`)}} icon={} iconPosition={"end"} - label="Remote Messages" /> + label="Messages" /> From c4ac20c1cc570dfa888a3efcff87774cddf5faba Mon Sep 17 00:00:00 2001 From: leandrofars Date: Wed, 30 Oct 2024 14:14:08 -0300 Subject: [PATCH 15/18] feat(controller): custom cwmp/usp messages templates --- .../services/controller/internal/api/api.go | 6 + .../services/controller/internal/api/cwmp.go | 27 +++- .../controller/internal/api/device.go | 139 ++++++++++++++++++ .../services/controller/internal/api/usp.go | 40 +++++ backend/services/controller/internal/db/db.go | 17 ++- .../controller/internal/db/template.go | 72 +++++++++ 6 files changed, 297 insertions(+), 4 deletions(-) create mode 100644 backend/services/controller/internal/db/template.go diff --git a/backend/services/controller/internal/api/api.go b/backend/services/controller/internal/api/api.go index d0b6898..29e5227 100644 --- a/backend/services/controller/internal/api/api.go +++ b/backend/services/controller/internal/api/api.go @@ -53,6 +53,11 @@ func (a *Api) StartApi() { iot := r.PathPrefix("/api/device").Subrouter() iot.HandleFunc("/alias", a.setDeviceAlias).Methods("PUT") iot.HandleFunc("/auth", a.deviceAuth).Methods("GET", "POST", "DELETE") + iot.HandleFunc("/message/{type}", a.addTemplate).Methods("POST") + iot.HandleFunc("/message", a.updateTemplate).Methods("PUT") + iot.HandleFunc("/message", a.getTemplate).Methods("GET") + iot.HandleFunc("/message", a.deleteTemplate).Methods("DELETE") + iot.HandleFunc("/cwmp/{sn}/generic", a.cwmpGenericMsg).Methods("PUT") iot.HandleFunc("/cwmp/{sn}/getParameterNames", a.cwmpGetParameterNamesMsg).Methods("PUT") iot.HandleFunc("/cwmp/{sn}/getParameterValues", a.cwmpGetParameterValuesMsg).Methods("PUT") iot.HandleFunc("/cwmp/{sn}/getParameterAttributes", a.cwmpGetParameterAttributesMsg).Methods("PUT") @@ -61,6 +66,7 @@ func (a *Api) StartApi() { iot.HandleFunc("/cwmp/{sn}/deleteObject", a.cwmpDeleteObjectMsg).Methods("PUT") iot.HandleFunc("", a.retrieveDevices).Methods("GET") iot.HandleFunc("/filterOptions", a.filterOptions).Methods("GET") + iot.HandleFunc("/{sn}/{mtp}/generic", a.deviceGenericMessage).Methods("PUT") iot.HandleFunc("/{sn}/{mtp}/get", a.deviceGetMsg).Methods("PUT") iot.HandleFunc("/{sn}/{mtp}/add", a.deviceCreateMsg).Methods("PUT") iot.HandleFunc("/{sn}/{mtp}/del", a.deviceDeleteMsg).Methods("PUT") diff --git a/backend/services/controller/internal/api/cwmp.go b/backend/services/controller/internal/api/cwmp.go index bb9d34c..3eb6e6f 100644 --- a/backend/services/controller/internal/api/cwmp.go +++ b/backend/services/controller/internal/api/cwmp.go @@ -17,6 +17,31 @@ import ( var errDeviceModelNotFound = errors.New("device model not found") +func (a *Api) cwmpGenericMsg(w http.ResponseWriter, r *http.Request) { + + sn := getSerialNumberFromRequest(r) + + payload, err := io.ReadAll(r.Body) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write(utils.Marshall(err.Error())) + return + } + + if len(payload) == 0 { + w.WriteHeader(http.StatusBadRequest) + w.Write(utils.Marshall("Empty payload")) + return + } + + data, _, err := cwmpInteraction[cwmp.SoapEnvelope](sn, payload, w, a.nc) + if err != nil { + return + } + + w.Write(data) +} + func (a *Api) cwmpGetParameterNamesMsg(w http.ResponseWriter, r *http.Request) { sn := getSerialNumberFromRequest(r) @@ -125,7 +150,7 @@ func (a *Api) cwmpDeleteObjectMsg(w http.ResponseWriter, r *http.Request) { w.Write(data) } -func cwmpInteraction[T cwmp.SetParameterValuesResponse | cwmp.DeleteObjectResponse | cwmp.GetParameterAttributesResponse | cwmp.GetParameterNamesResponse | cwmp.GetParameterValuesResponse | cwmp.AddObjectResponse]( +func cwmpInteraction[T cwmp.SetParameterValuesResponse | cwmp.SoapEnvelope | cwmp.DeleteObjectResponse | cwmp.GetParameterAttributesResponse | cwmp.GetParameterNamesResponse | cwmp.GetParameterValuesResponse | cwmp.AddObjectResponse]( sn string, payload []byte, w http.ResponseWriter, nc *nats.Conn, ) ([]byte, T, error) { diff --git a/backend/services/controller/internal/api/device.go b/backend/services/controller/internal/api/device.go index a24485f..4270186 100644 --- a/backend/services/controller/internal/api/device.go +++ b/backend/services/controller/internal/api/device.go @@ -8,6 +8,7 @@ import ( "strconv" "strings" + "github.com/gorilla/mux" "github.com/leandrofars/oktopus/internal/bridge" "github.com/leandrofars/oktopus/internal/db" "github.com/leandrofars/oktopus/internal/entity" @@ -367,3 +368,141 @@ func (a *Api) filterOptions(w http.ResponseWriter, r *http.Request) { w.WriteHeader(resp.Code) w.Write(utils.Marshall(resp.Msg)) } + +func (a *Api) updateTemplate(w http.ResponseWriter, r *http.Request) { + + name := r.URL.Query().Get("name") + if name == "" { + w.WriteHeader(http.StatusBadRequest) + utils.MarshallEncoder("No name provided", w) + return + } + + payload, err := io.ReadAll(r.Body) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + utils.MarshallEncoder("Error to decode payload: "+err.Error(), w) + return + } + + payloadLen := len(payload) + if payloadLen == 0 { + w.WriteHeader(http.StatusBadRequest) + utils.MarshallEncoder("No payload provided", w) + return + } + + err = a.db.UpdateTemplate(name, string(payload)) + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + json.NewEncoder(w).Encode(err.Error()) + return + } + + w.WriteHeader(http.StatusNoContent) +} + +func (a *Api) addTemplate(w http.ResponseWriter, r *http.Request) { + + name := r.URL.Query().Get("name") + if name == "" { + w.WriteHeader(http.StatusBadRequest) + utils.MarshallEncoder("No name provided", w) + return + } + + payload, err := io.ReadAll(r.Body) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + utils.MarshallEncoder("Error to decode payload: "+err.Error(), w) + return + } + + payloadLen := len(payload) + if payloadLen == 0 { + w.WriteHeader(http.StatusBadRequest) + utils.MarshallEncoder("No payload provided", w) + return + } + + vars := mux.Vars(r) + switch vars["type"] { + case "cwmp": + err = a.db.AddTemplate(name, "cwmp", string(payload)) + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + json.NewEncoder(w).Encode(err.Error()) + return + } + w.WriteHeader(http.StatusNoContent) + return + case "usp": + err = a.db.AddTemplate(name, "usp", string(payload)) + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + json.NewEncoder(w).Encode(err.Error()) + return + } + w.WriteHeader(http.StatusNoContent) + return + default: + w.WriteHeader(http.StatusBadRequest) + utils.MarshallEncoder("Invalid template type", w) + return + } +} + +func (a *Api) getTemplate(w http.ResponseWriter, r *http.Request) { + + name := r.URL.Query().Get("name") + msgType := r.URL.Query().Get("type") + + if name == "" { + + var filter bson.D + if msgType == "" { + filter = bson.D{} + } else { + filter = bson.D{{"type", msgType}} + } + + result, err := a.db.AllTemplates(filter) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode("Error to get all templates: " + err.Error()) + return + } + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(result) + return + } else { + t, err := a.db.FindTemplate(bson.D{{"name", name}}) + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + json.NewEncoder(w).Encode("error to find message: " + err.Error()) + return + } + w.WriteHeader(http.StatusOK) + w.Write([]byte(t.Value)) + return + } + +} + +func (a *Api) deleteTemplate(w http.ResponseWriter, r *http.Request) { + + name := r.URL.Query().Get("name") + if name == "" { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode("needs template name!") + return + } else { + err := a.db.DeleteTemplate(name) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode("error to delete template: " + err.Error()) + return + } + w.WriteHeader(http.StatusNoContent) + } +} diff --git a/backend/services/controller/internal/api/usp.go b/backend/services/controller/internal/api/usp.go index 7a663ff..d6c961a 100644 --- a/backend/services/controller/internal/api/usp.go +++ b/backend/services/controller/internal/api/usp.go @@ -1,6 +1,7 @@ package api import ( + "io" "log" "net/http" @@ -12,6 +13,7 @@ import ( "github.com/leandrofars/oktopus/internal/usp/usp_utils" "github.com/leandrofars/oktopus/internal/utils" "github.com/nats-io/nats.go" + "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" ) @@ -86,6 +88,44 @@ func sendUspMsg(msg usp_msg.Msg, sn string, w http.ResponseWriter, nc *nats.Conn return nil } +func (a *Api) deviceGenericMessage(w http.ResponseWriter, r *http.Request) { + + sn := getSerialNumberFromRequest(r) + mtp, err := getMtpFromRequest(r, w) + if err != nil { + return + } + + if mtp == "" { + var ok bool + mtp, ok = deviceStateOK(w, a.nc, sn) + if !ok { + return + } + } + + payload, err := io.ReadAll(r.Body) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write(utils.Marshall(err.Error())) + return + } + + var msg usp_msg.Msg + + err = protojson.Unmarshal(payload, &msg) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write(utils.Marshall(err.Error())) + return + } + + err = sendUspMsg(msg, sn, w, a.nc, mtp) + if err != nil { + return + } +} + func (a *Api) deviceGetMsg(w http.ResponseWriter, r *http.Request) { sn := getSerialNumberFromRequest(r) diff --git a/backend/services/controller/internal/db/db.go b/backend/services/controller/internal/db/db.go index c3365b2..c5289ab 100644 --- a/backend/services/controller/internal/db/db.go +++ b/backend/services/controller/internal/db/db.go @@ -10,9 +10,10 @@ import ( ) type Database struct { - client *mongo.Client - users *mongo.Collection - ctx context.Context + client *mongo.Client + users *mongo.Collection + template *mongo.Collection + ctx context.Context } func NewDatabase(ctx context.Context, mongoUri string) Database { @@ -43,6 +44,16 @@ func NewDatabase(ctx context.Context, mongoUri string) Database { log.Fatalln(err) } + db.template = client.Database("general").Collection("templates") + indexField = bson.M{"name": 1} + _, err = db.template.Indexes().CreateOne(ctx, mongo.IndexModel{ + Keys: indexField, + Options: options.Index().SetUnique(true), + }) + if err != nil { + log.Fatalln(err) + } + db.ctx = ctx return db diff --git a/backend/services/controller/internal/db/template.go b/backend/services/controller/internal/db/template.go new file mode 100644 index 0000000..efdcfd4 --- /dev/null +++ b/backend/services/controller/internal/db/template.go @@ -0,0 +1,72 @@ +package db + +import ( + "errors" + "log" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +type Template struct { + Name string `json:"name" bson:"name"` + Type string `json:"type" bson:"type"` + Value string `json:"value" bson:"value"` +} + +var ErrorTemplateExists = errors.New("message already exists") +var ErrorTemplateNotExists = errors.New("message don't exist") + +func (d *Database) FindTemplate(filter interface{}) (Template, error) { + var result Template + err := d.template.FindOne(d.ctx, filter).Decode(&result) + return result, err +} + +func (d *Database) AllTemplates(filter interface{}) ([]Template, error) { + var results []Template + + cursor, err := d.template.Find(d.ctx, filter) + if err != nil { + return results, err + } + if err = cursor.All(d.ctx, &results); err != nil { + log.Println(err) + } + return results, err +} + +func (d *Database) AddTemplate(name, tr string, t string) error { + opts := options.FindOneAndReplace().SetUpsert(true) + err := d.template.FindOneAndReplace(d.ctx, bson.D{{"name", name}}, Template{Name: name, Type: tr, Value: t}, opts).Err() + if err != nil { + if err == mongo.ErrNoDocuments { + log.Printf("New message %s added to database", name) + return nil + } + return err + } + log.Printf("Message %s already existed, and got replaced for new payload", name) + return err +} + +func (d *Database) UpdateTemplate(name, t string) error { + result, err := d.template.UpdateOne(d.ctx, bson.D{{"name", name}}, bson.D{{"$set", bson.D{{"value", t}}}}) + if err == nil { + if result.MatchedCount == 0 { + return ErrorTemplateNotExists + } + } + return err +} + +func (d *Database) DeleteTemplate(name string) error { + result, err := d.template.DeleteOne(d.ctx, bson.D{{"name", name}}) + if err == nil { + if result.DeletedCount == 0 { + return ErrorTemplateNotExists + } + } + return err +} From 55cf6f431049d98a2559bb506320c1442df9a7f0 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Wed, 30 Oct 2024 14:17:10 -0300 Subject: [PATCH 16/18] fix(controller): check if usp message is of error type or null to avoid panic --- backend/services/controller/internal/api/usp.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/backend/services/controller/internal/api/usp.go b/backend/services/controller/internal/api/usp.go index d6c961a..e552b27 100644 --- a/backend/services/controller/internal/api/usp.go +++ b/backend/services/controller/internal/api/usp.go @@ -61,6 +61,16 @@ func sendUspMsg(msg usp_msg.Msg, sn string, w http.ResponseWriter, nc *nats.Conn } body := receivedMsg.Body.GetResponse() + if body == nil { + errorMsg := receivedMsg.Body.GetError() + if errorMsg == nil { + w.WriteHeader(http.StatusInternalServerError) + log.Println("No response body or error") + return nil + } + w.Write(utils.Marshall(errorMsg)) + return nil + } switch body.RespType.(type) { case *usp_msg.Response_GetResp: From 0d134e10824d9d44989221d9d1f65f052f428064 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Wed, 30 Oct 2024 14:17:35 -0300 Subject: [PATCH 17/18] feat(frontend): custom cwmp/usp messages templates --- .../src/sections/devices/cwmp/devices-rpc.js | 517 +++++++++++----- .../src/sections/devices/usp/devices-rpc.js | 551 +++++++++++++----- 2 files changed, 760 insertions(+), 308 deletions(-) diff --git a/frontend/src/sections/devices/cwmp/devices-rpc.js b/frontend/src/sections/devices/cwmp/devices-rpc.js index 62c97b0..13d99bf 100644 --- a/frontend/src/sections/devices/cwmp/devices-rpc.js +++ b/frontend/src/sections/devices/cwmp/devices-rpc.js @@ -19,37 +19,26 @@ import { DialogContentText, DialogActions, Box, - IconButton + IconButton, + Grid } from '@mui/material'; import XMarkIcon from '@heroicons/react/24/outline/XMarkIcon'; import PaperAirplane from '@heroicons/react/24/solid/PaperAirplaneIcon'; import CircularProgress from '@mui/material/CircularProgress'; import Backdrop from '@mui/material/Backdrop'; import { useRouter } from 'next/router'; +import { useBackendContext } from 'src/contexts/backend-context'; +import DocumentArrowDown from '@heroicons/react/24/outline/DocumentArrowDownIcon'; +import TrashIcon from '@heroicons/react/24/outline/TrashIcon'; +import PlusCircleIcon from '@heroicons/react/24/outline/PlusCircleIcon'; +import EnvelopeIcon from '@heroicons/react/24/outline/EnvelopeIcon'; +import CheckIcon from '@heroicons/react/24/outline/CheckIcon'; export const DevicesRPC = () => { const router = useRouter() - -const [open, setOpen] = useState(false); -const [scroll, setScroll] = useState('paper'); -const [answer, setAnswer] = useState(false) -const [content, setContent] = useState('') -const [age, setAge] = useState(2); - -const [value, setValue] = useState(` - - - - - - InternetGatewayDevice.LANDevice.1.WLANConfiguration.1. - InternetGatewayDevice.LANDevice.1.WLANConfiguration.2. - - - -`) +let { httpRequest } = useBackendContext() var prettifyXml = function(sourceXml) { @@ -75,129 +64,219 @@ var prettifyXml = function(sourceXml) return resultXml; }; +const [open, setOpen] = useState(false); +const [scroll, setScroll] = useState('paper'); +const [answer, setAnswer] = useState(false) +const [content, setContent] = useState('') +const [age, setAge] = useState(6); +const [newMessage, setNewMessage] = useState(false) +const [message, setMessage] = useState(null) +const [currentMsg, setCurrentMsg] = useState(0) +const [newMsgName, setNewMsgName] = useState("") +const [value, setValue] = useState() +const [saveChanges, setSaveChanges] = useState(false) +const [loadingSaveMsg, setLoadingSaveMsg] = useState(false) +const possibleMsgs = [ + ` + + + + + + + InternetGatewayDevice.TraceRouteDiagnostics.Host + 192.168.60.4 + + + + + +`, +` + + + + + InternetGatewayDevice.LANDevice.1.WLANConfiguration.2. + + + +`, +` + + + + + InternetGatewayDevice.LANDevice.1.WLANConfiguration. + + + +`, +` + + + + + + 007 + + + + `, +` + + + + + + InternetGatewayDevice.LANDevice.1.WLANConfiguration.1. + + + +`,` + + + + + InternetGatewayDevice. + 1 + + +`, +` + + + + + + InternetGatewayDevice.LANDevice.1.WLANConfiguration. + + + +`] +const [newMsgValue, setNewMsgValue] = useState(possibleMsgs[age-1]) +const [loading, setLoading] = useState(false); + +const handleNewMessageValue = (event) => { + setNewMsgValue(event.target.value) +} + const handleClose = () => { setOpen(false); }; -const handleOpen = () => { - setOpen(true); - var myHeaders = new Headers(); - myHeaders.append("Authorization", localStorage.getItem("token")); - var raw = value +const handleCancelNewMsgTemplate = () => { + setNewMessage(false) + setNewMsgName("") + setNewMsgValue(possibleMsgs[age-1]) + // setValue(possibleMsgs[age-1]) +} - var requestOptions = { - method: 'PUT', - headers: myHeaders, - body: raw, - redirect: 'follow' - }; - - var method; - - switch(age) { - case 1: - method="addObject" - break; - case 2: - method="getParameterValues" - break; - case 3: - method="setParameterValues" - break; - case 4: - method="deleteObject" - break; - } - - - fetch(`${process.env.NEXT_PUBLIC_REST_ENDPOINT || ""}/api/device/cwmp/${router.query.id[0]}/${method}`, requestOptions) - .then(response => response.text()) - .then(result => { - if (result.status === 401){ - router.push("/auth/login") +const saveMsg = async () => { + let {status} = await httpRequest( + `/api/device/message?name=`+message[currentMsg].name, + "PUT", + value, + null, + ) + if ( status === 204){ + setSaveChanges(false) + setMessage(message.map((msg, index) => { + if (index === currentMsg) { + return {...msg, value: value} + }else{ + return msg } - setOpen(false) - setAnswer(true) - let teste = prettifyXml(result) - console.log(teste) - setContent(teste) - }) - .catch(error => console.log('error', error)); - }; + })) + } +} + +const createNewMsg = async () => { + setLoading(true) + let {status} = await httpRequest( + `/api/device/message/cwmp?name=`+newMsgName, + "POST", + newMsgValue, + null, + ) + if ( status === 204){ + setNewMessage(false) + setNewMsgName("") + let result = await fetchMessages() + if (result) { + setCurrentMsg(result.length-1) + } + setValue(newMsgValue) + setNewMsgValue(possibleMsgs[age-1]) + } + setLoading(false) +} + +const handleChangeMessage = (event) => { + setSaveChanges(false) + setCurrentMsg(event.target.value) + setValue(message[event.target.value].value) +} + +const handleDeleteMessage = async () => { + let {status} = await httpRequest( + `/api/device/message?name=`+message[currentMsg].name.replace(" ", '+'), + "DELETE", + ) + if ( status === 204){ + fetchMessages() + setCurrentMsg(0) + setValue("") + } +} + +const handleOpen = async () => { + setOpen(true); + + let {result, status} = await httpRequest( + `/api/device/cwmp/${router.query.id[0]}/generic`, + "PUT", + value, + null, + "text", + ) + if (status === 200){ + setAnswer(true) + console.log("result:",result) + let answer = prettifyXml(result) + if (answer == "null"){ + answer = result + } + console.log(answer) + setContent(answer) + } + setOpen(false) + +} + +const fetchMessages = async () => { + let {result, status} = await httpRequest( + `/api/device/message?type=cwmp`, + "GET", + null, + null, + ) + if ( status === 200){ + setMessage(result) + setValue(result ? result[0].value : "") + return result + } +} const handleChangeRPC = (event) => { setAge(event.target.value); - switch(event.target.value) { - case 1: - setValue(` - - - - - InternetGatewayDevice.LANDevice. - - - - `) - break; - case 2: - setValue(` - - - - - - InternetGatewayDevice.LANDevice.1.WLANConfiguration.1. - InternetGatewayDevice.LANDevice.1.WLANConfiguration.2. - InternetGatewayDevice.LANDevice.2.WLANConfiguration.2. - InternetGatewayDevice.LANDevice.2.WLANConfiguration.1. - - - - `) - break; - case 3: - setValue(` - - - - - - - - InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.Enable - 0 - - - InternetGatewayDevice.LANDevice.1.WLANConfiguration.2.SSID - HUAWEI_TEST-2 - - - LC1309123 - - - `) - break; - case 4: - setValue(` - - - - - InternetGatewayDevice.LANDevice.3. - - - - `) - break; - default: - // code block - } + setNewMsgValue(possibleMsgs[event.target.value-1]) }; - const handleChange = (event) => { - setValue(event.target.value); - }; + const handleEditMessage = (event) => { + setSaveChanges(true) + setValue(event.target.value) + } const handleSubmit = useCallback( (event) => { @@ -206,54 +285,91 @@ const handleOpen = () => { [] ); + useEffect(() => { + fetchMessages(); + },[]); + return ( - - - - - + < EnvelopeIcon/>} + title="Custom Message" + action={ + + + } + > + + + + Message + + + - + />:} - - - + {/* */} + + + + {!loadingSaveMsg ? : } + + + + theme.zIndex.drawer + 1 }} @@ -307,6 +423,83 @@ const handleOpen = () => { }}>Ok + + + + + + + {setNewMsgName(event.target.value)}} + label="Name" + sx={{maxWidth: "30%", justifyContent:"center"}} + /> + + Template + + + + + + + + {/* */} + + + {!loading ? + :} + + ); diff --git a/frontend/src/sections/devices/usp/devices-rpc.js b/frontend/src/sections/devices/usp/devices-rpc.js index 521bd9e..4f54a41 100644 --- a/frontend/src/sections/devices/usp/devices-rpc.js +++ b/frontend/src/sections/devices/usp/devices-rpc.js @@ -19,151 +19,296 @@ import { DialogContentText, DialogActions, Box, - IconButton + IconButton, + Grid } from '@mui/material'; import XMarkIcon from '@heroicons/react/24/outline/XMarkIcon'; import PaperAirplane from '@heroicons/react/24/solid/PaperAirplaneIcon'; import CircularProgress from '@mui/material/CircularProgress'; import Backdrop from '@mui/material/Backdrop'; import { useRouter } from 'next/router'; +import { useBackendContext } from 'src/contexts/backend-context'; +import DocumentArrowDown from '@heroicons/react/24/outline/DocumentArrowDownIcon'; +import TrashIcon from '@heroicons/react/24/outline/TrashIcon'; +import PlusCircleIcon from '@heroicons/react/24/outline/PlusCircleIcon'; +import EnvelopeIcon from '@heroicons/react/24/outline/EnvelopeIcon'; +import CheckIcon from '@heroicons/react/24/outline/CheckIcon'; export const DevicesRPC = () => { const router = useRouter() +let { httpRequest } = useBackendContext() const [open, setOpen] = useState(false); const [scroll, setScroll] = useState('paper'); const [answer, setAnswer] = useState(false) const [content, setContent] = useState('') -const [age, setAge] = useState(2); +const [age, setAge] = useState(6); +const [newMessage, setNewMessage] = useState(false) +const [message, setMessage] = useState(null) +const [currentMsg, setCurrentMsg] = useState(0) +const [newMsgName, setNewMsgName] = useState("") +const [value, setValue] = useState() +const [saveChanges, setSaveChanges] = useState(false) +const [loadingSaveMsg, setLoadingSaveMsg] = useState(false) +const possibleMsgs = [ + `{ + "header": { + "msg_id": "b7dc38ea-aefb-4761-aa55-edaa97adb2f0", + "msg_type": 4 + }, + "body": { + "request": { + "set": { + "allow_partial":true, + "update_objs":[ + { + "obj_path":"Device.IP.Interface.1.", + "param_settings":[ + { + "param":"Alias", + "value":"test", + "required":true + } + ] + } + ] + } + } + } +}`, +`{ + "header": { + "msg_id": "b7dc38ea-aefb-4761-aa55-edaa97adb2f0", + "msg_type": 10 + }, + "body": { + "request": { + "delete": { + "allow_partial": true, + "obj_paths": [ + "Device.IP.Interface.[Alias==test]." + ] + } + } + } +}`, +` +{ + "header": { + "msg_id": "b7dc38ea-aefb-4761-aa55-edaa97adb2f0", + "msg_type": 8 + }, + "body": { + "request": { + "add": { + "allow_partial": true, + "create_objs": [ + { + "obj_path": "Device.IP.Interface.", + "param_settings": [ + { + "param": "Alias", + "value": "test", + "required": true + } + ] + } + ] + } + } + } +}`, +`{ + "header": { + "msg_id": "b7dc38ea-aefb-4761-aa55-edaa97adb2f0", + "msg_type": 6 + }, + "body": { + "request": { + "operate": { + "command": "Device.Reboot()", + "send_resp": true + } + } + } +}`, +`{ + "header": { + "msg_id": "b7dc38ea-aefb-4761-aa55-edaa97adb2f0", + "msg_type": 1 + }, + "body": { + "request": { + "get": { + "paramPaths": [ + "Device.WiFi.SSID.[Name==wlan0].", + "Device.IP.Interface.*.Alias", + "Device.DeviceInfo.FirmwareImage.*.Alias", + "Device.IP.Interface.1.IPv4Address.1.IPAddress" + ], + "maxDepth": 2 + } + } + } +}`,`{ + "header": { + "msg_id": "b7dc38ea-aefb-4761-aa55-edaa97adb2f0", + "msg_type": 12 + }, + "body": { + "request": { + "get_supported_dm": { + "obj_paths" : [ + "Device." + ], + "first_level_only" : false, + "return_commands" : false, + "return_events" : false, + "return_params" : true + } + } + } +}`, +`{ + "header": { + "msg_id": "b7dc38ea-aefb-4761-aa55-edaa97adb2f0", + "msg_type": 14 + }, + "body": { + "request": { + "get_instances": { + "obj_paths" : ["Device.DeviceInfo."], + "first_level_only" : false + } + } + } +}`] +const [newMsgValue, setNewMsgValue] = useState(possibleMsgs[age-1]) +const [loading, setLoading] = useState(false); -const [value, setValue] = useState(`{ - "param_paths": [ - "Device.WiFi.SSID.[Name==wlan0].", - "Device.IP.Interface.*.Alias", - "Device.DeviceInfo.FirmwareImage.*.Alias", - "Device.IP.Interface.1.IPv4Address.1.IPAddress" - ], - "max_depth": 2 -}`) +const handleNewMessageValue = (event) => { + setNewMsgValue(event.target.value) +} const handleClose = () => { setOpen(false); }; -const handleOpen = () => { - setOpen(true); - var myHeaders = new Headers(); - myHeaders.append("Content-Type", "application/json"); - myHeaders.append("Authorization", localStorage.getItem("token")); - var raw = value +const handleCancelNewMsgTemplate = () => { + setNewMessage(false) + setNewMsgName("") + setNewMsgValue(possibleMsgs[age-1]) + // setValue(possibleMsgs[age-1]) +} - var requestOptions = { - method: 'PUT', - headers: myHeaders, - body: raw, - redirect: 'follow' - }; - - var method; - - switch(age) { - case 1: - method="add" - break; - case 2: - method="get" - break; - case 3: - method="set" - break; - case 4: - method="del" - break; - } - - - fetch(`${process.env.NEXT_PUBLIC_REST_ENDPOINT || ""}/api/device/${router.query.id[0]}/any/${method}`, requestOptions) - .then(response => response.text()) - .then(result => { - if (result.status === 401){ - router.push("/auth/login") +const saveMsg = async () => { + let {status} = await httpRequest( + `/api/device/message?name=`+message[currentMsg].name, + "PUT", + value, + null, + ) + if ( status === 204){ + setSaveChanges(false) + setMessage(message.map((msg, index) => { + if (index === currentMsg) { + return {...msg, value: value} + }else{ + return msg } - setOpen(false) - setAnswer(true) - let teste = JSON.stringify(JSON.parse(result), null, 2) - console.log(teste) - setContent(teste) - }) - .catch(error => console.log('error', error)); - }; + })) + } +} + +const createNewMsg = async () => { + setLoading(true) + let {status} = await httpRequest( + `/api/device/message/usp?name=`+newMsgName, + "POST", + newMsgValue, + null, + ) + if ( status === 204){ + setNewMessage(false) + setNewMsgName("") + let result = await fetchMessages() + if (result) { + setCurrentMsg(result.length-1) + } + setValue(newMsgValue) + setNewMsgValue(possibleMsgs[age-1]) + } + setLoading(false) +} + +const handleChangeMessage = (event) => { + setSaveChanges(false) + setCurrentMsg(event.target.value) + setValue(message[event.target.value].value) +} + +const handleDeleteMessage = async () => { + let {status} = await httpRequest( + `/api/device/message?name=`+message[currentMsg].name.replace(" ", '+'), + "DELETE", + ) + if ( status === 204){ + fetchMessages() + setCurrentMsg(0) + setValue("") + } +} + +const handleOpen = async () => { + setOpen(true); + + let {result, status} = await httpRequest( + `/api/device/${router.query.id[0]}/any/generic`, + "PUT", + value, + null, + ) + if (status === 200){ + setAnswer(true) + console.log("result:",result) + let answer = JSON.stringify(result, null, 2) + if (answer == "null"){ + answer = result + } + console.log(answer) + setContent(answer) + } + setOpen(false) + +} + +const fetchMessages = async () => { + let {result, status} = await httpRequest( + `/api/device/message?type=usp`, + "GET", + null, + null, + ) + if ( status === 200){ + setMessage(result) + setValue(result ? result[0].value : "") + return result + } +} const handleChangeRPC = (event) => { setAge(event.target.value); - switch(event.target.value) { - case 1: - setValue(`{ - "allow_partial": true, - "create_objs": [ - { - "obj_path": "Device.IP.Interface.", - "param_settings": [ - { - "param": "Alias", - "value": "test", - "required": true - } - ] - } - ] - }`) - break; - case 2: - setValue(`{ - "param_paths": [ - "Device.WiFi.SSID.[Name==wlan0].", - "Device.IP.Interface.*.Alias", - "Device.DeviceInfo.FirmwareImage.*.Alias", - "Device.IP.Interface.1.IPv4Address.1.IPAddress" - ], - "max_depth": 2 - }`) - break; - case 3: - setValue(` - { - "allow_partial":true, - "update_objs":[ - { - "obj_path":"Device.IP.Interface.[Alias==pamonha].", - "param_settings":[ - { - "param":"Alias", - "value":"goiaba", - "required":true - } - ] - } - ] - }`) - break; - case 4: - setValue(`{ - "allow_partial": true, - "obj_paths": [ - "Device.IP.Interface.3." - ] - }`) - break; - default: - // code block - } + setNewMsgValue(possibleMsgs[event.target.value-1]) }; - const handleChange = (event) => { - setValue(event.target.value); - }; + const handleEditMessage = (event) => { + if (message) { + setSaveChanges(true) + } + setValue(event.target.value) + } const handleSubmit = useCallback( (event) => { @@ -172,54 +317,91 @@ const handleOpen = () => { [] ); + useEffect(() => { + fetchMessages(); + },[]); + return (
- - - - - + < EnvelopeIcon/>} + title="Custom Message" + action={ + + + } + > + + + + Message + + + - + />:} - - - + {/* */} + + + + {!loadingSaveMsg ? : } + + + + theme.zIndex.drawer + 1 }} @@ -273,6 +455,83 @@ const handleOpen = () => { }}>Ok + + + + + + + {setNewMsgName(event.target.value)}} + label="Name" + sx={{maxWidth: "30%", justifyContent:"center"}} + /> + + Template + + + + + + + + {/* */} + + + {!loading ? + :} + +
); From 509b90cd4fc3e0e3459a6f60d72e4db2979020b2 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Wed, 30 Oct 2024 14:23:19 -0300 Subject: [PATCH 18/18] fix(frontend): cwmp enterprise tabs remove routers --- frontend/src/pages/devices/cwmp/[...id].js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/frontend/src/pages/devices/cwmp/[...id].js b/frontend/src/pages/devices/cwmp/[...id].js index 8576e49..6168ac4 100644 --- a/frontend/src/pages/devices/cwmp/[...id].js +++ b/frontend/src/pages/devices/cwmp/[...id].js @@ -80,7 +80,6 @@ const Page = () => { icon={} iconPosition={"end"} label="Wi-Fi" - onClick={()=>{router.push(`/devices/cwmp/${deviceID}/wifi`)}} value={"wifi"} style={{opacity:"0.5", cursor:"default"}}/>
@@ -88,7 +87,6 @@ const Page = () => { icon={} iconPosition={"end"} label="Site Survey" - onClick={()=>{router.push(`/devices/cwmp/${deviceID}/site-survey`)}} value={"site-survey"} style={{opacity:"0.5", cursor:"default"}}/> @@ -97,7 +95,6 @@ const Page = () => { iconPosition={"end"} label="Connected Devices" style={{opacity:"0.5", cursor:"default"}} - onClick={()=>{router.push(`/devices/cwmp/${deviceID}/connected-devices`)}} value={"connected-devices"} /> @@ -105,7 +102,6 @@ const Page = () => { icon={} iconPosition={"end"} label="Diagnostic" - onClick={()=>{router.push(`/devices/cwmp/${deviceID}/diagnostic`)}} value={"diagnostic"} style={{opacity:"0.5", cursor:"default"}}/> @@ -113,7 +109,6 @@ const Page = () => { icon={} iconPosition={"end"} label="Ports" - onClick={()=>{router.push(`/devices/cwmp/${deviceID}/ports`)}} style={{opacity:"0.5", cursor:"default"}} value={"ports"} /> @@ -121,7 +116,6 @@ const Page = () => { icon={} iconPosition={"end"} label="Historic" - onClick={()=>{router.push(`/devices/cwmp/${deviceID}/historic`)}} value={"historic"} style={{opacity:"0.5", cursor:"default"}}/> @@ -129,7 +123,6 @@ const Page = () => { icon={} iconPosition={"end"} label="Actions" - onClick={()=>{router.push(`/devices/cwmp/${deviceID}/actions`)}} style={{opacity:"0.5", cursor:"default"}} value={"actions"} /> @@ -137,7 +130,6 @@ const Page = () => { icon={} iconPosition={"end"} label="Logs" - onClick={()=>{router.push(`/devices/cwmp/${deviceID}/logs`)}} style={{opacity:"0.5", cursor:"default"}} value={"logs"} /> @@ -145,7 +137,6 @@ const Page = () => { icon={} iconPosition={"end"} label="Location" - onClick={()=>{router.push(`/devices/cwmp/${deviceID}/location`)}} style={{opacity:"0.5", cursor:"default"}} value={"location"} />