From 6638aea5b26522f49f6d3305c5a036234bc5d5d2 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Wed, 8 May 2024 20:29:55 -0300 Subject: [PATCH] feat(frontend): devices credentials crud | close #242 --- .../services/controller/internal/api/api.go | 2 +- .../controller/internal/api/device.go | 18 +- frontend/src/layouts/dashboard/config.js | 18 +- frontend/src/pages/credentials.js | 385 ++++++++++++++++++ .../sections/credentials/credentials-table.js | 192 +++++++++ 5 files changed, 603 insertions(+), 12 deletions(-) create mode 100644 frontend/src/pages/credentials.js create mode 100644 frontend/src/sections/credentials/credentials-table.js diff --git a/backend/services/controller/internal/api/api.go b/backend/services/controller/internal/api/api.go index bd20320..5d8114b 100644 --- a/backend/services/controller/internal/api/api.go +++ b/backend/services/controller/internal/api/api.go @@ -56,7 +56,7 @@ func (a *Api) StartApi() { authentication.HandleFunc("/admin/register", a.registerAdminUser).Methods("POST") authentication.HandleFunc("/admin/exists", a.adminUserExists).Methods("GET") iot := r.PathPrefix("/api/device").Subrouter() - iot.HandleFunc("/auth", a.deviceAuth).Methods("GET", "PUT", "DELETE") + iot.HandleFunc("/auth", a.deviceAuth).Methods("GET", "POST", "DELETE") iot.HandleFunc("/cwmp/{sn}/getParameterNames", a.cwmpGetParameterNamesMsg).Methods("GET", "PUT", "DELETE") iot.HandleFunc("", a.retrieveDevices).Methods("GET") iot.HandleFunc("/{id}", a.retrieveDevices).Methods("GET") diff --git a/backend/services/controller/internal/api/device.go b/backend/services/controller/internal/api/device.go index 8a96644..cd8a515 100644 --- a/backend/services/controller/internal/api/device.go +++ b/backend/services/controller/internal/api/device.go @@ -188,7 +188,7 @@ func (a *Api) deviceAuth(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusBadRequest) utils.MarshallEncoder("No id provided", w) - } else if r.Method == http.MethodPut { + } else if r.Method == http.MethodPost { var deviceAuth DeviceAuth @@ -200,12 +200,26 @@ func (a *Api) deviceAuth(w http.ResponseWriter, r *http.Request) { } if deviceAuth.User != "" { - _, err := a.kv.PutString(r.Context(), deviceAuth.User, deviceAuth.Password) + _, err := a.kv.Get(r.Context(), deviceAuth.User) + if err != nil { + + if err == jetstream.ErrKeyNotFound { + _, err = a.kv.PutString(r.Context(), deviceAuth.User, deviceAuth.Password) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + utils.MarshallEncoder(err, w) + } + return + } + w.WriteHeader(http.StatusInternalServerError) utils.MarshallEncoder(err, w) return } + + w.WriteHeader(http.StatusConflict) + utils.MarshallEncoder("Username already exists", w) return } diff --git a/frontend/src/layouts/dashboard/config.js b/frontend/src/layouts/dashboard/config.js index ea00338..1bd4cb2 100644 --- a/frontend/src/layouts/dashboard/config.js +++ b/frontend/src/layouts/dashboard/config.js @@ -44,15 +44,15 @@ export const items = [ // // ) // }, - // { - // title: 'Credentials', - // path: '/credentials', - // icon: ( - // - // - // - // ) - // }, + { + title: 'Credentials', + path: '/credentials', + icon: ( + + + + ) + }, { title: 'Users', path: '/users', diff --git a/frontend/src/pages/credentials.js b/frontend/src/pages/credentials.js new file mode 100644 index 0000000..31448b5 --- /dev/null +++ b/frontend/src/pages/credentials.js @@ -0,0 +1,385 @@ +import { useCallback, useState, useEffect } from 'react'; +import Head from 'next/head'; +import MagnifyingGlassIcon from '@heroicons/react/24/solid/MagnifyingGlassIcon'; +import PlusIcon from '@heroicons/react/24/solid/PlusIcon'; +import { Box, Button, Container, Stack, SvgIcon, Tooltip, Typography, IconButton, +DialogActions, +Dialog, +DialogTitle, +DialogContent, +TextField, +InputAdornment, +Input, Backdrop, CircularProgress, +InputLabel, FormControl, +OutlinedInput, +Card } from '@mui/material'; +import EyeIcon from '@heroicons/react/24/outline/EyeIcon'; +import EyeSlashIcon from '@heroicons/react/24/outline/EyeSlashIcon'; +import { Layout as DashboardLayout } from 'src/layouts/dashboard/layout'; +import { CredentialsTable } from 'src/sections/credentials/credentials-table'; +import InformactionCircleIcon from '@heroicons/react/24/outline/InformationCircleIcon'; +import { useAuth } from 'src/hooks/use-auth'; +import { useRouter } from 'next/router'; + +const Page = () => { + const auth = useAuth(); + const router = useRouter(); + + const [page, setPage] = useState(0); + const [devices, setDevices] = useState({}); + const [addDeviceDialogOpen, setAddDeviceDialogOpen] = useState(false); + const [newDeviceData, setNewDeviceData] = useState({}); + const [showPassword, setShowPassword] = useState(false); + const [isUsernameEmpty, setIsUsernameEmpty] = useState(false); + const [isUsernameExistent, setIsUsernameExistent] = useState(false); + const [creatingNewCredential, setCreatingNewCredential] = useState(false); + const [loading, setLoading] = useState(true); + const [credentialNotFound, setCredentialNotFound] = useState(false); + + const deleteCredential = (id) => { + console.log("request to delete credentials: ", id) + + var myHeaders = new Headers(); + myHeaders.append("Content-Type", "application/json"); + myHeaders.append("Authorization", auth.user.token); + + var requestOptions = { + method: 'DELETE', + headers: myHeaders, + redirect: 'follow' + } + + return fetch(process.env.NEXT_PUBLIC_REST_ENDPOINT + '/device/auth?id='+id, requestOptions) + .then(response => { + if (response.status === 401) { + router.push("/auth/login") + } else if (response.status === 403) { + return router.push("/403") + } + let copiedDevices = {...devices} + delete copiedDevices[id]; + setDevices(device => ({ + ...copiedDevices + })) + }) + .catch(error => { + return console.error('Error:', error) + }); + } + + const createCredential = async (data) => { + var myHeaders = new Headers(); + myHeaders.append("Content-Type", "application/json"); + myHeaders.append("Authorization", auth.user.token); + + var raw = JSON.stringify(data); + + var requestOptions = { + method: 'POST', + headers: myHeaders, + body: raw, + redirect: 'follow' + }; + + let result = await fetch(process.env.NEXT_PUBLIC_REST_ENDPOINT+"/device/auth", requestOptions) + + if (result.status == 200) { + console.log("user created: deu boa raça !!") + }else if (result.status == 403) { + console.log("num tenx permissão, seu boca de sandália") + setCreatingNewCredential(false) + return router.push("/403") + }else if (result.status == 401){ + console.log("taix nem autenticado, sai fora oh") + setCreatingNewCredential(false) + return router.push("/auth/login") + }else if (result.status == 409){ + console.log("usuário já existe, seu boca de bagre") + setIsUsernameExistent(true) + setCreatingNewCredential(false) + return + }else if (result.status == 400){ + console.log("faltou mandar dados jow") + setAddDeviceDialogOpen(false) + setNewDeviceData({}) + setIsUsernameEmpty(false) + setIsUsernameExistent(false) + setCreatingNewCredential(false) + return + }else { + console.log("agora quebrasse ux córno mô quiridu") + const content = await result.json() + setCreatingNewCredential(false) + throw new Error(content); + } + setAddDeviceDialogOpen(false) + let newData = {} + newData[data.id] = data.password + setDevices(prevState =>({...prevState, ...newData})) + setNewDeviceData({}) + setIsUsernameEmpty(false) + setIsUsernameExistent(false) + setCreatingNewCredential(false) + } + + const fetchCredentials = async (id) => { + console.log("fetching credentials data...") + var myHeaders = new Headers(); + myHeaders.append("Content-Type", "application/json"); + myHeaders.append("Authorization", auth.user.token); + + var requestOptions = { + method: 'GET', + headers: myHeaders, + redirect: 'follow' + } + + let url = process.env.NEXT_PUBLIC_REST_ENDPOINT + '/device/auth' + if (id !== undefined && id !== "") { + url += "?id="+id + } + + return fetch(url, requestOptions) + .then(response => { + if (response.status === 401) { + router.push("/auth/login") + } else if (response.status === 403) { + return router.push("/403") + }else if (response.status === 404) { + setLoading(false) + setCredentialNotFound(true) + console.log("credential not found: ", credentialNotFound) + return + } + return response.json() + }) + .then(json => { + if (json === undefined) { + return + } + console.log("devices credentials: ", json) + setDevices(json) + setLoading(false) + setCredentialNotFound(false) + }) + .catch(error => { + setLoading(false) + setCredentialNotFound(false) + return console.error('Error:', error) + }); + } + + useEffect(() => { + fetchCredentials() + }, []); + + return ( + <> + + + Devices Credentials | Oktopus + + + + + + + + + Devices Credentials + + + + + + + + + +
+ +
+
+ + { + if (e.key === 'Enter') { + console.log("Fetch credentials per username: ", e.target.value) + fetchCredentials(e.target.value) + } + }} + startAdornment={( + + + + + + )} + sx={{ maxWidth: 500 }} + /> + + {!loading ? (credentialNotFound ? + +

Credential Not Found

+
: + ): + + } +
+
+
+ { + setAddDeviceDialogOpen(false) + setIsUsernameEmpty(false) + setIsUsernameExistent(false) + setNewDeviceData({}) + }} + > + Create New Credentials + + + + { + setNewDeviceData({...newDeviceData, id: event.target.value}) + } + } + variant="standard"> + + + + Password + + { + setShowPassword(!showPassword) + }} + > + + {showPassword ? : } + + + + } + onChange={ + (event) => { + setNewDeviceData({...newDeviceData, password: event.target.value}) + } + } + /> + + + + + + + + + + { + theme.zIndex.drawer + 1 }} + open={creatingNewCredential} + > + + + } + + + ); +}; + +Page.getLayout = (page) => ( + + {page} + +); + +export default Page; \ No newline at end of file diff --git a/frontend/src/sections/credentials/credentials-table.js b/frontend/src/sections/credentials/credentials-table.js new file mode 100644 index 0000000..687d6a5 --- /dev/null +++ b/frontend/src/sections/credentials/credentials-table.js @@ -0,0 +1,192 @@ +import PropTypes from 'prop-types'; +import { + Avatar, + Box, + Card, + Checkbox, + Icon, + Stack, + Tab, + Table, + TableBody, + TableCell, + TableHead, + //TablePagination, + TableRow, + Typography, + SvgIcon, + Dialog, + DialogActions, + DialogTitle, + DialogContent, + DialogContentText, + Button, + TablePagination, + TextField, + InputAdornment, + IconButton, + Input +} from '@mui/material'; +import EyeIcon from '@heroicons/react/24/outline/EyeIcon'; +import EyeSlashIcon from '@heroicons/react/24/outline/EyeSlashIcon'; +import { Scrollbar } from 'src/components/scrollbar'; +import PencilIcon from '@heroicons/react/24/outline/PencilIcon'; +import TrashIcon from '@heroicons/react/24/outline/TrashIcon'; +import { useEffect, useState } from 'react'; + +export const CredentialsTable = (props) => { + const { + count = 0, + items = {}, + onDeselectAll, + onDeselectOne, + onPageChange = () => {}, + onRowsPerPageChange, + onSelectAll, + onSelectOne, + deleteCredential, + page = 0, + rowsPerPage = 0, + // selected = [] + } = props; + + const [showPassword, setShowPassword] = useState({}) + const [showDeleteDialog, setShowDeleteDialog] = useState(false); + const [credentialToDelete, setCredentialToDelete] = useState("") + + useEffect(()=>{ + Object.keys(items).map((key) => { + let newData = {}; + newData[key] = false + setShowPassword(prevState => ({ + ...prevState, + ...newData + })) + }) + console.log("showPassword: "+ showPassword) + },[]) + + return ( + + + + + + + + Username + + + Password + + + Actions + + + + + {Object.keys(items).map((key) => { + let value = items[key]; + + return ( + + + {key} + + + + { + let newData = {}; + newData[key] = !showPassword[key] + setShowPassword(previous => ({...previous, ...newData})) + }} + //onMouseDown={handleMouseDownPassword} + > + + {showPassword[key] ? : } + + + + } + value={value} + /> + + + + + + ); + }) + } + +
+
+
+ setShowDeleteDialog(false)} + aria-labelledby="alert-dialog-title" + aria-describedby="alert-dialog-description" + > + {"Delete User"} + + + Are you sure you want to delete this credential? + + + + + + + +
+ ); +}; + +CredentialsTable.propTypes = { + count: PropTypes.number, + items: PropTypes.object, + //onDeselectAll: PropTypes.func, + //onDeselectOne: PropTypes.func, + onPageChange: PropTypes.func, + //onRowsPerPageChange: PropTypes.func, + //onSelectAll: PropTypes.func, + //onSelectOne: PropTypes.func, + deleteCredential: PropTypes.func, + //page: PropTypes.number, + //rowsPerPage: PropTypes.number, + //selected: PropTypes.array +};