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 + + + + + + + Under development + + + 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} + /> : + + } +
+
+
+ { + setAddDeviceDialogOpen(false) + setIsEmailEmpty(false) + setIsEmailExistent(false) + setIsPasswordEmpty(false) + setNewUserData({}) + }} + > + Create User + + + { + setNewUserData({...newUserData, email: event.target.value}) + } + } + variant="standard"> + + { + setNewUserData({...newUserData, password: event.target.value}) + } + } + variant="standard"> + + + + { + setNewUserData({...newUserData, name: event.target.value}) + } + } + > + + { + setNewUserData({...newUserData, phone: event.target.value}) + } + } + > + + + + + + + + { + theme.zIndex.drawer + 1 }} + open={creatingNewUser} + > + + + } + + + ); +}; + +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]} + /> */} + setShowDeleteDialog(false)} + aria-labelledby="alert-dialog-title" + aria-describedby="alert-dialog-description" + > + {"Delete User"} + + + Are you sure you want to delete this user? + + + + + + +
); }; @@ -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) ? : : } );