diff --git a/backend/services/controller/internal/api/user.go b/backend/services/controller/internal/api/user.go
index 6800d40..b3893ad 100644
--- a/backend/services/controller/internal/api/user.go
+++ b/backend/services/controller/internal/api/user.go
@@ -4,11 +4,13 @@ import (
"encoding/json"
"log"
"net/http"
+ "net/mail"
"github.com/gorilla/mux"
"github.com/leandrofars/oktopus/internal/api/auth"
"github.com/leandrofars/oktopus/internal/db"
"github.com/leandrofars/oktopus/internal/utils"
+ "go.mongodb.org/mongo-driver/bson/primitive"
)
func (a *Api) retrieveUsers(w http.ResponseWriter, r *http.Request) {
@@ -20,6 +22,11 @@ func (a *Api) retrieveUsers(w http.ResponseWriter, r *http.Request) {
}
for _, x := range users {
+ objectID, ok := x["_id"].(primitive.ObjectID)
+ if ok {
+ creationTime := objectID.Timestamp()
+ x["createdAt"] = creationTime.Format("02/01/2006")
+ }
delete(x, "password")
}
@@ -27,7 +34,6 @@ func (a *Api) retrieveUsers(w http.ResponseWriter, r *http.Request) {
if err != nil {
log.Println(err)
}
- return
}
func (a *Api) registerUser(w http.ResponseWriter, r *http.Request) {
@@ -64,12 +70,27 @@ func (a *Api) registerUser(w http.ResponseWriter, r *http.Request) {
return
}
+ if user.Email == "" || user.Password == "" || !valid(user.Email) {
+ w.WriteHeader(http.StatusBadRequest)
+ return
+ }
+
if err := a.db.RegisterUser(user); err != nil {
+ if err == db.ErrorUserExists {
+ w.WriteHeader(http.StatusConflict)
+ w.Write([]byte("User with this email already exists"))
+ return
+ }
w.WriteHeader(http.StatusInternalServerError)
return
}
}
+func valid(email string) bool {
+ _, err := mail.ParseAddress(email)
+ return err == nil
+}
+
func (a *Api) deleteUser(w http.ResponseWriter, r *http.Request) {
tokenString := r.Header.Get("Authorization")
if tokenString == "" {
@@ -84,21 +105,21 @@ func (a *Api) deleteUser(w http.ResponseWriter, r *http.Request) {
//Check if user which is requesting deletion has the necessary privileges
rUser, err := a.db.FindUser(email)
- if rUser.Level != AdminUser {
- w.WriteHeader(http.StatusForbidden)
+ if err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
return
}
userEmail := mux.Vars(r)["user"]
- if userEmail == email {
- w.WriteHeader(http.StatusBadRequest)
- return
- }
- if err := a.db.DeleteUser(userEmail); err != nil {
- w.WriteHeader(http.StatusInternalServerError)
- json.NewEncoder(w).Encode(err)
- return
+ if rUser.Email == userEmail || (rUser.Level == AdminUser && rUser.Email != userEmail) { //Admin can delete any account, but admin account can never be deleted
+ if err := a.db.DeleteUser(userEmail); err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ json.NewEncoder(w).Encode(err)
+ return
+ }
+ } else {
+ w.WriteHeader(http.StatusForbidden)
}
}
diff --git a/backend/services/controller/internal/db/db.go b/backend/services/controller/internal/db/db.go
index 820a925..c3365b2 100644
--- a/backend/services/controller/internal/db/db.go
+++ b/backend/services/controller/internal/db/db.go
@@ -4,6 +4,7 @@ import (
"context"
"log"
+ "go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
@@ -33,6 +34,15 @@ func NewDatabase(ctx context.Context, mongoUri string) Database {
log.Println("Connected to MongoDB-->", mongoUri)
db.users = client.Database("account-mngr").Collection("users")
+ indexField := bson.M{"email": 1}
+ _, err = db.users.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/user.go b/backend/services/controller/internal/db/user.go
index af0950f..e7e2cfc 100644
--- a/backend/services/controller/internal/db/user.go
+++ b/backend/services/controller/internal/db/user.go
@@ -1,6 +1,7 @@
package db
import (
+ "errors"
"log"
"go.mongodb.org/mongo-driver/bson"
@@ -13,8 +14,11 @@ type User struct {
Name string `json:"name"`
Password string `json:"password,omitempty"`
Level int `json:"level"`
+ Phone string `json:"phone"`
}
+var ErrorUserExists = errors.New("User already exists")
+
func (d *Database) RegisterUser(user User) error {
err := d.users.FindOne(d.ctx, bson.D{{"email", user.Email}}).Err()
if err != nil {
@@ -23,8 +27,10 @@ func (d *Database) RegisterUser(user User) error {
return err
}
log.Println(err)
+ return err
+ } else {
+ return ErrorUserExists
}
- return err
}
func (d *Database) UpdatePassword(user User) error {
diff --git a/deploy/kubernetes/README.md b/deploy/kubernetes/README.md
index 9ef30ea..8975b3e 100644
--- a/deploy/kubernetes/README.md
+++ b/deploy/kubernetes/README.md
@@ -19,6 +19,15 @@ git clone https://github.com/OktopUSP/oktopus
export DEPLOYMENT_PATH=oktopus/deploy/kubernetes
```
+## HAProxy Ingress Controller
+
+```shell
+helm install haproxy-kubernetes-ingress haproxytech/kubernetes-ingress \
+ --create-namespace \
+ --namespace haproxy-controller \
+ --set controller.kind=DaemonSet \
+ --set controller.daemonset.useHostPort=true
+```
## MongoBD
@@ -49,27 +58,6 @@ helm install nats nats/nats --set config.jetstream.enabled=true
## Oktopus
-
-Node Ports
-
-For this deployment, we are not using a load balancer and kubernetes is deployed on-premises so we are using Nodeports to insource the client traffic into cluster. below the ports set on deployment files:
-
-1. MQTT broker service (mqtt-svc): 30000
-2. Frontend (frontend-svc): 30001
-3. SocketIO: (socketio-svc): 30002
-4. Controller (controller-svc): 30003
-5. WebSocket (ws-svc): 30005
-
-Before deploying the files, edit the frontend.yaml file to set the correct enviroment variables:
-
-```yaml
-env:
- - name: NEXT_PUBLIC_REST_ENDPOINT
- value: ":30003"
- - name: NEXT_PUBLIC_WS_ENDPOINT
- value: ":30005"
-```
-
```shell
kubectl apply -f $DEPLOYMENT_PATH/mqtt.yaml
kubectl apply -f $DEPLOYMENT_PATH/mqtt-adapter.yaml
@@ -88,4 +76,4 @@ kubectl apply -f $DEPLOYMENT_PATH/ws-adapter.yaml
kubectl get pods
kubectl get svc
-```
\ No newline at end of file
+```
diff --git a/deploy/kubernetes/adapter.yaml b/deploy/kubernetes/adapter.yaml
index d2d2d23..3160766 100644
--- a/deploy/kubernetes/adapter.yaml
+++ b/deploy/kubernetes/adapter.yaml
@@ -33,7 +33,7 @@ spec:
- name: NATS_VERIFY_CERTIFICATES
value: "false"
- name: MONGO_URI
- value: "mongodb://oktopusp:oktopusp@mongodb-0.mongodb-svc.mongodb.svc.cluster.local:27017,mongodb-1.mongodb-svc.mongodb.svc.cluster.local:27017,mongodb-2.mongodb-svc.mongodb.svc.cluster.local:27017/adapter?replicaSet=mongodb&ssl=false"
+ value: "mongodb://oktopusp:oktopusp@mongodb-0.mongodb-svc.mongodb.svc.cluster.local:27017,mongodb-1.mongodb-svc.mongodb.svc.cluster.local:27017,mongodb-2.mongodb-svc.mongodb.svc.cluster.local:27017/?replicaSet=mongodb&ssl=false"
- name: CONTROLLER_ID
value: "oktopusController"
diff --git a/deploy/kubernetes/controller.yaml b/deploy/kubernetes/controller.yaml
index 450317d..17b13cf 100644
--- a/deploy/kubernetes/controller.yaml
+++ b/deploy/kubernetes/controller.yaml
@@ -48,6 +48,5 @@ spec:
- protocol: TCP
port: 8000
targetPort: 8000
- nodePort: 30003
- type: NodePort
+ type: ClusterIP
diff --git a/deploy/kubernetes/frontend.yaml b/deploy/kubernetes/frontend.yaml
index 6731899..86945db 100644
--- a/deploy/kubernetes/frontend.yaml
+++ b/deploy/kubernetes/frontend.yaml
@@ -28,10 +28,8 @@ spec:
- containerPort: 3000
imagePullPolicy: IfNotPresent
env:
- - name: NEXT_PUBLIC_REST_ENDPOINT
- value: "192.168.1.130:30003"
- - name: NEXT_PUBLIC_WS_ENDPOINT
- value: "192.168.1.130:30005"
+ - name: NEXT_PUBLIC_REST_ENDPOINT
+ value: "/api"
---
apiVersion: v1
kind: Service
@@ -44,5 +42,5 @@ spec:
- protocol: TCP
port: 3000
targetPort: 3000
- nodePort: 30001
- type: NodePort
+ #externalTrafficPolicy: Local
+ type: ClusterIP
diff --git a/deploy/kubernetes/haproxy-kubernetes-ingress-cm.yaml b/deploy/kubernetes/haproxy-kubernetes-ingress-cm.yaml
deleted file mode 100644
index c4aabd9..0000000
--- a/deploy/kubernetes/haproxy-kubernetes-ingress-cm.yaml
+++ /dev/null
@@ -1,7 +0,0 @@
-apiVersion: v1
-kind: ConfigMap
-metadata:
- name: haproxy-kubernetes-ingress
- namespace: haproxy-controller
-data:
- syslog-server: "address:stdout, format: raw, facility:daemon"
diff --git a/deploy/kubernetes/haproxy-tcp-services-cm.yaml b/deploy/kubernetes/haproxy-tcp-services-cm.yaml
deleted file mode 100644
index a4b8f7f..0000000
--- a/deploy/kubernetes/haproxy-tcp-services-cm.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
-apiVersion: v1
-kind: ConfigMap
-metadata:
- name: haproxy-tcp
- namespace: default
-data:
- 1883: # Port where the frontend is going to listen to.
- default/mqtt-svc:1883 # Kubernetes service in the format NS/ServiceName:ServicePort
diff --git a/deploy/kubernetes/haproxy-tcp-services.yaml b/deploy/kubernetes/haproxy-tcp-services.yaml
deleted file mode 100644
index 77db1d6..0000000
--- a/deploy/kubernetes/haproxy-tcp-services.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
-controller:
- service:
- tcpPorts:
- - name: mqtt
- port: 1883
- targetPort: 1883
- extraArgs:
- - --configmap-tcp-services=default/haproxy-tcp
diff --git a/deploy/kubernetes/ingress.yaml b/deploy/kubernetes/ingress.yaml
new file mode 100644
index 0000000..b429543
--- /dev/null
+++ b/deploy/kubernetes/ingress.yaml
@@ -0,0 +1,34 @@
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: web-ingress
+ namespace: default
+ annotations:
+spec:
+ ingressClassName: "haproxy"
+ rules:
+ - host: oktopus.rdss.cloud
+ http:
+ paths:
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: frontend-svc
+ port:
+ number: 3000
+ - path: /api
+ pathType: Prefix
+ backend:
+ service:
+ name: controller-svc
+ port:
+ number: 8000
+ - path: /socket.io
+ pathType: Prefix
+ backend:
+ service:
+ name: socketio-svc
+ port:
+ number: 5000
+
diff --git a/deploy/kubernetes/mqtt-adapter.yaml b/deploy/kubernetes/mqtt-adapter.yaml
index 3d7b7b5..8bd522d 100644
--- a/deploy/kubernetes/mqtt-adapter.yaml
+++ b/deploy/kubernetes/mqtt-adapter.yaml
@@ -31,7 +31,7 @@ spec:
- name: NATS_VERIFY_CERTIFICATES
value: "false"
- name: MQTT_URL
- value: "tcp://mqtt:1883"
+ value: "tcp://mqtt-svc:1883"
- name: MQTT_CLIENT_ID
value: "mqtt-adapter"
- name: MQTT_USERNAME
diff --git a/deploy/kubernetes/mqtt.yaml b/deploy/kubernetes/mqtt.yaml
index 0d705ee..55fca1b 100644
--- a/deploy/kubernetes/mqtt.yaml
+++ b/deploy/kubernetes/mqtt.yaml
@@ -9,8 +9,8 @@ spec:
- protocol: TCP
port: 1883
targetPort: 1883
- nodePort: 30000
- type: NodePort
+ externalTrafficPolicy: Local
+ type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
@@ -46,3 +46,6 @@ spec:
value: "false"
- name: LOG_LEVEL
value: "0" # 0 - DEBUG
+ - name: REDIS_ENABLE
+ value: "false"
+
diff --git a/deploy/kubernetes/socketio.yaml b/deploy/kubernetes/socketio.yaml
index a51c5fd..e8695a1 100644
--- a/deploy/kubernetes/socketio.yaml
+++ b/deploy/kubernetes/socketio.yaml
@@ -19,6 +19,8 @@ spec:
env:
- name: NATS_URL
value: "nats:4222"
+ - name: CORS_ALLOWED_ORIGINS
+ value: ""
---
apiVersion: v1
kind: Service
@@ -31,5 +33,3 @@ spec:
- protocol: TCP
port: 5000
targetPort: 5000
- nodePort: 30002
- type: NodePort
diff --git a/frontend/src/contexts/auth-context.js b/frontend/src/contexts/auth-context.js
index 44d790a..0b72543 100644
--- a/frontend/src/contexts/auth-context.js
+++ b/frontend/src/contexts/auth-context.js
@@ -82,18 +82,22 @@ export const AuthProvider = (props) => {
console.error(err);
}
+ console.log("isAuthenticated: ", isAuthenticated)
if (isAuthenticated) {
const user = {
id: '5e86809283e28b96d2d38537',
avatar: '/assets/avatars/default-avatar.png',
name: 'Oktopus',
email: 'anika.visser@devias.io',
+ token: localStorage.getItem("token")
};
dispatch({
type: HANDLERS.INITIALIZE,
payload: user
});
+
+ console.log("AUTH CONTEXT --> auth.user.token:", user.token)
} else {
dispatch({
type: HANDLERS.INITIALIZE
diff --git a/frontend/src/layouts/dashboard/config.js b/frontend/src/layouts/dashboard/config.js
index 612a933..ea00338 100644
--- a/frontend/src/layouts/dashboard/config.js
+++ b/frontend/src/layouts/dashboard/config.js
@@ -2,6 +2,8 @@ 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 UserGroupIcon from '@heroicons/react/24/solid/UserGroupIcon'
+import KeyIcon from '@heroicons/react/24/solid/KeyIcon'
import CpuChip from '@heroicons/react/24/solid/CpuChipIcon';
import { SvgIcon } from '@mui/material';
@@ -42,6 +44,24 @@ export const items = [
//
// )
// },
+ // {
+ // title: 'Credentials',
+ // path: '/credentials',
+ // icon: (
+ //
+ //
+ //
+ // )
+ // },
+ {
+ title: 'Users',
+ path: '/users',
+ icon: (
+
+
+
+ )
+ },
{
title: 'Settings',
path: '/settings',
diff --git a/frontend/src/pages/403.js b/frontend/src/pages/403.js
new file mode 100644
index 0000000..a65ce5e
--- /dev/null
+++ b/frontend/src/pages/403.js
@@ -0,0 +1,79 @@
+import Head from 'next/head';
+import NextLink from 'next/link';
+import ArrowLeftIcon from '@heroicons/react/24/solid/ArrowLeftIcon';
+import { Box, Button, Container, SvgIcon, Typography } from '@mui/material';
+
+const Page = () => (
+ <>
+
+
+ 403 | Oktopus TR-369
+
+
+
+
+
+
+
+
+
+ 403: You're not allowed to perform this action
+
+
+ You either tried to perform an action you're not authorized to do or you came here by mistake.
+
+
+
+
+
+ >
+);
+
+export default Page;
diff --git a/frontend/src/pages/customers.js b/frontend/src/pages/customers.js
deleted file mode 100644
index d31a34a..0000000
--- a/frontend/src/pages/customers.js
+++ /dev/null
@@ -1,290 +0,0 @@
-import { useCallback, useMemo, useState } from 'react';
-import Head from 'next/head';
-import { subDays, subHours } from 'date-fns';
-import ArrowDownOnSquareIcon from '@heroicons/react/24/solid/ArrowDownOnSquareIcon';
-import ArrowUpOnSquareIcon from '@heroicons/react/24/solid/ArrowUpOnSquareIcon';
-import PlusIcon from '@heroicons/react/24/solid/PlusIcon';
-import { Box, Button, Container, Stack, SvgIcon, Typography } from '@mui/material';
-import { useSelection } from 'src/hooks/use-selection';
-import { Layout as DashboardLayout } from 'src/layouts/dashboard/layout';
-import { CustomersTable } from 'src/sections/customer/customers-table';
-import { CustomersSearch } from 'src/sections/customer/customers-search';
-import { applyPagination } from 'src/utils/apply-pagination';
-
-const now = new Date();
-
-const data = [
- {
- id: '5e887ac47eed253091be10cb',
- address: {
- city: 'Cleveland',
- country: 'USA',
- state: 'Ohio',
- street: '2849 Fulton Street'
- },
- avatar: '/assets/avatars/avatar-carson-darrin.png',
- createdAt: subDays(subHours(now, 7), 1).getTime(),
- email: 'carson.darrin@devias.io',
- name: 'Carson Darrin',
- phone: '304-428-3097'
- },
- {
- id: '5e887b209c28ac3dd97f6db5',
- address: {
- city: 'Atlanta',
- country: 'USA',
- state: 'Georgia',
- street: '1865 Pleasant Hill Road'
- },
- avatar: '/assets/avatars/avatar-fran-perez.png',
- createdAt: subDays(subHours(now, 1), 2).getTime(),
- email: 'fran.perez@devias.io',
- name: 'Fran Perez',
- phone: '712-351-5711'
- },
- {
- id: '5e887b7602bdbc4dbb234b27',
- address: {
- city: 'North Canton',
- country: 'USA',
- state: 'Ohio',
- street: '4894 Lakeland Park Drive'
- },
- avatar: '/assets/avatars/avatar-jie-yan-song.png',
- createdAt: subDays(subHours(now, 4), 2).getTime(),
- email: 'jie.yan.song@devias.io',
- name: 'Jie Yan Song',
- phone: '770-635-2682'
- },
- {
- id: '5e86809283e28b96d2d38537',
- address: {
- city: 'Madrid',
- country: 'Spain',
- name: 'Anika Visser',
- street: '4158 Hedge Street'
- },
- avatar: '/assets/avatars/avatar-anika-visser.png',
- createdAt: subDays(subHours(now, 11), 2).getTime(),
- email: 'anika.visser@devias.io',
- name: 'Anika Visser',
- phone: '908-691-3242'
- },
- {
- id: '5e86805e2bafd54f66cc95c3',
- address: {
- city: 'San Diego',
- country: 'USA',
- state: 'California',
- street: '75247'
- },
- avatar: '/assets/avatars/avatar-miron-vitold.png',
- createdAt: subDays(subHours(now, 7), 3).getTime(),
- email: 'miron.vitold@devias.io',
- name: 'Miron Vitold',
- phone: '972-333-4106'
- },
- {
- id: '5e887a1fbefd7938eea9c981',
- address: {
- city: 'Berkeley',
- country: 'USA',
- state: 'California',
- street: '317 Angus Road'
- },
- avatar: '/assets/avatars/avatar-penjani-inyene.png',
- createdAt: subDays(subHours(now, 5), 4).getTime(),
- email: 'penjani.inyene@devias.io',
- name: 'Penjani Inyene',
- phone: '858-602-3409'
- },
- {
- id: '5e887d0b3d090c1b8f162003',
- address: {
- city: 'Carson City',
- country: 'USA',
- state: 'Nevada',
- street: '2188 Armbrester Drive'
- },
- avatar: '/assets/avatars/avatar-omar-darboe.png',
- createdAt: subDays(subHours(now, 15), 4).getTime(),
- email: 'omar.darobe@devias.io',
- name: 'Omar Darobe',
- phone: '415-907-2647'
- },
- {
- id: '5e88792be2d4cfb4bf0971d9',
- address: {
- city: 'Los Angeles',
- country: 'USA',
- state: 'California',
- street: '1798 Hickory Ridge Drive'
- },
- avatar: '/assets/avatars/avatar-siegbert-gottfried.png',
- createdAt: subDays(subHours(now, 2), 5).getTime(),
- email: 'siegbert.gottfried@devias.io',
- name: 'Siegbert Gottfried',
- phone: '702-661-1654'
- },
- {
- id: '5e8877da9a65442b11551975',
- address: {
- city: 'Murray',
- country: 'USA',
- state: 'Utah',
- street: '3934 Wildrose Lane'
- },
- avatar: '/assets/avatars/avatar-iulia-albu.png',
- createdAt: subDays(subHours(now, 8), 6).getTime(),
- email: 'iulia.albu@devias.io',
- name: 'Iulia Albu',
- phone: '313-812-8947'
- },
- {
- id: '5e8680e60cba5019c5ca6fda',
- address: {
- city: 'Salt Lake City',
- country: 'USA',
- state: 'Utah',
- street: '368 Lamberts Branch Road'
- },
- avatar: '/assets/avatars/avatar-nasimiyu-danai.png',
- createdAt: subDays(subHours(now, 1), 9).getTime(),
- email: 'nasimiyu.danai@devias.io',
- name: 'Nasimiyu Danai',
- phone: '801-301-7894'
- }
-];
-
-const useCustomers = (page, rowsPerPage) => {
- return useMemo(
- () => {
- return applyPagination(data, page, rowsPerPage);
- },
- [page, rowsPerPage]
- );
-};
-
-const useCustomerIds = (customers) => {
- return useMemo(
- () => {
- return customers.map((customer) => customer.id);
- },
- [customers]
- );
-};
-
-const Page = () => {
- const [page, setPage] = useState(0);
- const [rowsPerPage, setRowsPerPage] = useState(5);
- const customers = useCustomers(page, rowsPerPage);
- const customersIds = useCustomerIds(customers);
- const customersSelection = useSelection(customersIds);
-
- const handlePageChange = useCallback(
- (event, value) => {
- setPage(value);
- },
- []
- );
-
- const handleRowsPerPageChange = useCallback(
- (event) => {
- setRowsPerPage(event.target.value);
- },
- []
- );
-
- return (
- <>
-
-
- Customers | Devias Kit
-
-
-
-
-
-
-
-
- Customers
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- );
-};
-
-Page.getLayout = (page) => (
-
- {page}
-
-);
-
-export default Page;
diff --git a/frontend/src/pages/devices.js b/frontend/src/pages/devices.js
index 158bf21..a55f33e 100644
--- a/frontend/src/pages/devices.js
+++ b/frontend/src/pages/devices.js
@@ -22,7 +22,7 @@ const Page = () => {
const router = useRouter()
const auth = useAuth();
const [devices, setDevices] = useState([]);
- const [deviceFound, setDeviceFound] = useState(false)
+ const [deviceFound, setDeviceFound] = useState(true)
const [pages, setPages] = useState(0);
const [page, setPage] = useState(null);
const [Loading, setLoading] = useState(true);
@@ -104,6 +104,7 @@ const Page = () => {
const fetchDevicePerId = async (id) => {
setLoading(true)
+ setDeviceFound(true)
var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
myHeaders.append("Authorization", auth.user.token);
diff --git a/frontend/src/pages/users.js b/frontend/src/pages/users.js
new file mode 100644
index 0000000..3549a49
--- /dev/null
+++ b/frontend/src/pages/users.js
@@ -0,0 +1,449 @@
+import { useCallback, useMemo, useState, useEffect } from 'react';
+import Head from 'next/head';
+import { subDays, subHours } from 'date-fns';
+import ArrowDownOnSquareIcon from '@heroicons/react/24/solid/ArrowDownOnSquareIcon';
+import ArrowUpOnSquareIcon from '@heroicons/react/24/solid/ArrowUpOnSquareIcon';
+import PlusIcon from '@heroicons/react/24/solid/PlusIcon';
+import { Box, Button, CircularProgress, Container, Dialog, DialogContent, DialogTitle, Stack, SvgIcon, Typography,
+ DialogActions,
+ TextField,
+ Backdrop,
+} from '@mui/material';
+import { useSelection } from 'src/hooks/use-selection';
+import { Layout as DashboardLayout } from 'src/layouts/dashboard/layout';
+import { CustomersTable } from 'src/sections/customer/customers-table';
+import { CustomersSearch } from 'src/sections/customer/customers-search';
+import { applyPagination } from 'src/utils/apply-pagination';
+import { useAuth } from 'src/hooks/use-auth';
+import { useRouter } from 'next/router';
+import { is } from 'date-fns/locale';
+import { set } from 'nprogress';
+
+const Page = () => {
+
+ const auth = useAuth();
+ const router = useRouter();
+
+ const validateEmail = (email) => {
+ return email.match(
+ /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
+ );
+ };
+
+ //const [page, setPage] = useState(0);
+ //const [rowsPerPage, setRowsPerPage] = useState(5);
+ const [loading, setLoading] = useState(true);
+ const [creatingNewUser, setCreatingNewUser] = useState(false);
+ const [users, setUsers] = useState([]);
+ const [selected, setSelected] = useState([]);
+ const [addDeviceDialogOpen, setAddDeviceDialogOpen] = useState(false);
+ const [newUserData, setNewUserData] = useState({});
+ const [isPasswordEmpty, setIsPasswordEmpty] = useState(false);
+ const [isEmailEmpty, setIsEmailEmpty] = useState(false);
+ const [isEmailExistent, setIsEmailExistent] = useState(false);
+
+ const deleteUser = (id) => {
+ console.log("request to delete user: ", 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 + '/auth/delete/' + id, requestOptions)
+ .then(response => {
+ if (response.status === 401) {
+ router.push("/auth/login")
+ } else if (response.status === 403) {
+ return router.push("/403")
+ }
+ setUsers(users.filter(user => user.email !== id))
+ })
+ .catch(error => {
+ return console.error('Error:', error)
+ });
+ }
+
+
+ const fetchUsers = async () => {
+ console.log("fetching users 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'
+ }
+
+ return fetch(process.env.NEXT_PUBLIC_REST_ENDPOINT + '/users', requestOptions)
+ .then(response => {
+ if (response.status === 401) {
+ router.push("/auth/login")
+ } else if (response.status === 403) {
+ return router.push("/403")
+ }
+ return response.json()
+ })
+ .then(json => {
+ console.log("users: ", json)
+ setUsers(json)
+ // setPages(json.pages + 1)
+ // setPage(json.page +1)
+ // setDevices(json.devices)
+ setLoading(false)
+ })
+ .catch(error => {
+ return console.error('Error:', error)
+ });
+ }
+
+ useEffect(() => {
+ // if (auth.user.token) {
+ // console.log("auth.user.token =", auth.user.token)
+ // }else{
+ // auth.user.token = localStorage.getItem("token")
+ // }
+ //console.log("auth.user.token =", auth.user.token)
+ fetchUsers()
+ }, []);
+
+ // const handlePageChange = useCallback(
+ // (event, value) => {
+ // setPage(value);
+ // },
+ // []
+ // );
+
+ // const handleRowsPerPageChange = useCallback(
+ // (event) => {
+ // setRowsPerPage(event.target.value);
+ // },
+ // []
+ // );
+
+ const createUser = 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+"/auth/register", 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")
+ setCreatingNewUser(false)
+ return router.push("/403")
+ }else if (result.status == 401){
+ console.log("taix nem autenticado, sai fora oh")
+ setCreatingNewUser(false)
+ return router.push("/auth/login")
+ }else if (result.status == 409){
+ console.log("usuário já existe, seu boca de bagre")
+ setIsEmailExistent(true)
+ setCreatingNewUser(false)
+ return
+ }else if (result.status == 400){
+ console.log("faltou mandar dados jow")
+ setAddDeviceDialogOpen(false)
+ setNewUserData({})
+ setIsPasswordEmpty(false)
+ setIsEmailEmpty(false)
+ setIsEmailExistent(false)
+ setCreatingNewUser(false)
+ return
+ }else {
+ console.log("agora quebrasse ux córno mô quiridu")
+ const content = await result.json()
+ setCreatingNewUser(false)
+ throw new Error(content);
+ }
+ setAddDeviceDialogOpen(false)
+ data["_id"] = data.email
+ data["createdAt"] = new Date().toLocaleDateString('es-pa')
+ data["level"] = 0
+
+ setUsers([...users, data])
+ setNewUserData({})
+ setIsPasswordEmpty(false)
+ setIsEmailEmpty(false)
+ setIsEmailExistent(false)
+ setCreatingNewUser(false)
+ }
+
+
+
+ return (
+ <>
+
+
+ Oktopus | Users
+
+
+
+
+
+
+
+
+ Users
+
+
+ {/* */}
+ {/* */}
+
+
+
+
+
+
+ {/* */}
+ {users && !loading ?
+ {
+ setSelected(selected.filter((item) => item !== id))
+ }}
+ //onPageChange={handlePageChange}
+ //onRowsPerPageChange={handleRowsPerPageChange}
+ //onSelectAll={customersSelection.handleSelectAll}
+ onSelectOne={(id) => {
+ setSelected([...selected, id])
+ console.log("added user " + id + " to selected array")
+ }}
+ //page={page}
+ //rowsPerPage={rowsPerPage}
+ deleteUser={deleteUser}
+ selected={selected}
+ /> :
+
+ }
+
+
+
+
+ >
+ );
+};
+
+Page.getLayout = (page) => (
+
+ {page}
+
+);
+
+export default Page;
diff --git a/frontend/src/sections/customer/customers-table.js b/frontend/src/sections/customer/customers-table.js
index 2d1fe88..f790415 100644
--- a/frontend/src/sections/customer/customers-table.js
+++ b/frontend/src/sections/customer/customers-table.js
@@ -1,21 +1,31 @@
import PropTypes from 'prop-types';
-import { format } from 'date-fns';
import {
Avatar,
Box,
Card,
Checkbox,
+ Icon,
Stack,
+ Tab,
Table,
TableBody,
TableCell,
TableHead,
- TablePagination,
+ //TablePagination,
TableRow,
- Typography
+ Typography,
+ SvgIcon,
+ Dialog,
+ DialogActions,
+ DialogTitle,
+ DialogContent,
+ DialogContentText,
+ Button
} from '@mui/material';
import { Scrollbar } from 'src/components/scrollbar';
import { getInitials } from 'src/utils/get-initials';
+import TrashIcon from '@heroicons/react/24/outline/TrashIcon';
+import { useState } from 'react';
export const CustomersTable = (props) => {
const {
@@ -27,14 +37,18 @@ export const CustomersTable = (props) => {
onRowsPerPageChange,
onSelectAll,
onSelectOne,
+ deleteUser,
page = 0,
rowsPerPage = 0,
selected = []
} = props;
- const selectedSome = (selected.length > 0) && (selected.length < items.length);
- const selectedAll = (items.length > 0) && (selected.length === items.length);
+ // const selectedSome = (selected.length > 0) && (selected.length < items.length);
+ // const selectedAll = (items.length > 0) && (selected.length === items.length);
+ const [showDeleteDialog, setShowDeleteDialog] = useState(false);
+ const [userToDelete, setUserToDelete] = useState("")
+
return (
@@ -42,8 +56,8 @@ export const CustomersTable = (props) => {
-
- */}
+ {/* {
@@ -53,55 +67,60 @@ export const CustomersTable = (props) => {
onDeselectAll?.();
}
}}
- />
-
-
+ /> */}
+ {/* */}
+
Name
Email
-
+ {/*
Location
-
+ */}
Phone
- Signed Up
+ Created At
+
+
+ Level
+
+
+ Actions
{items.map((customer) => {
- const isSelected = selected.includes(customer.id);
- const createdAt = format(customer.createdAt, 'dd/MM/yyyy');
-
+ const isSelected = selected.includes(customer._id);
return (
-
- */}
+ {/* {
if (event.target.checked) {
- onSelectOne?.(customer.id);
+ console.log(customer._id+" is selected");
+ onSelectOne(customer._id);
} else {
- onDeselectOne?.(customer.id);
+ onDeselectOne(customer._id);
}
}}
- />
-
-
+ /> */}
+ {/* */}
+
-
+
{getInitials(customer.name)}
@@ -112,14 +131,33 @@ export const CustomersTable = (props) => {
{customer.email}
-
- {customer.address.city}, {customer.address.state}, {customer.address.country}
-
+ {/*
+ {customer.address}
+ */}
{customer.phone}
- {createdAt}
+ {customer.createdAt}
+
+
+ {customer.level == 1 ? "Admin" : "User"}
+
+
+ { customer.level == 0 ? : }
);
@@ -128,15 +166,43 @@ export const CustomersTable = (props) => {
-
+ //onPageChange={onPageChange}
+ //onRowsPerPageChange={onRowsPerPageChange}
+ //page={page}
+ //rowsPerPage={rowsPerPage}
+ //rowsPerPageOptions={[5, 10, 25]}
+ /> */}
+
);
};
@@ -147,10 +213,11 @@ CustomersTable.propTypes = {
onDeselectAll: PropTypes.func,
onDeselectOne: PropTypes.func,
onPageChange: PropTypes.func,
- onRowsPerPageChange: PropTypes.func,
+ //onRowsPerPageChange: PropTypes.func,
onSelectAll: PropTypes.func,
onSelectOne: PropTypes.func,
- page: PropTypes.number,
- rowsPerPage: PropTypes.number,
+ deleteUser: 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 6f85c03..b84a1f2 100644
--- a/frontend/src/sections/overview/overview-latest-orders.js
+++ b/frontend/src/sections/overview/overview-latest-orders.js
@@ -79,7 +79,7 @@ export const OverviewLatestOrders = (props) => {
hover
key={order.SN}
>
-
+
{order.SN}
@@ -97,18 +97,17 @@ export const OverviewLatestOrders = (props) => {
- { order.Status == 2 && (order.Mqtt == 0 && order.Websockets == 0 && order.Stomp == 0) ? : : }
);