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
+ :
+ ):
+
+ }
+
+
+
+
+ >
+ );
+};
+
+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 (
+
+
+
+
+
+
+
+
+ );
+};
+
+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
+};
diff --git a/frontend/src/sections/overview/overview-latest-orders.js b/frontend/src/sections/overview/overview-latest-orders.js
index b84a1f2..4df6fb9 100644
--- a/frontend/src/sections/overview/overview-latest-orders.js
+++ b/frontend/src/sections/overview/overview-latest-orders.js
@@ -97,14 +97,14 @@ export const OverviewLatestOrders = (props) => {
- { order.Mqtt == 0 && order.Websockets == 0 && order.Stomp == 0 ? :