diff --git a/client/app/globals.css b/client/app/globals.css index a30a014..800182b 100644 --- a/client/app/globals.css +++ b/client/app/globals.css @@ -98,3 +98,7 @@ background: #0F172A; /* Color of the scrollbar handle on hover */ border: 2px solid white; /* Color of the scrollbar handle on hover */ } + +nextjs-portal { + display: none; +} \ No newline at end of file diff --git a/client/components/dashboard-componenets/cards/EmptyFillContainer.tsx b/client/components/dashboard-componenets/cards/EmptyFillContainer.tsx index bab6f88..87137ee 100644 --- a/client/components/dashboard-componenets/cards/EmptyFillContainer.tsx +++ b/client/components/dashboard-componenets/cards/EmptyFillContainer.tsx @@ -1,6 +1,6 @@ -export default function EmptyFillContainer({children}: {children: React.ReactNode}) { +export default function EmptyFillContainer({children, className = ""}: {children: React.ReactNode, className?: string}) { return ( -
{children}
+
{children}
); } \ No newline at end of file diff --git a/client/components/dashboard-componenets/mainContent/systemAdminContents/Roles.tsx b/client/components/dashboard-componenets/mainContent/systemAdminContents/Roles.tsx index 8950880..7190efb 100644 --- a/client/components/dashboard-componenets/mainContent/systemAdminContents/Roles.tsx +++ b/client/components/dashboard-componenets/mainContent/systemAdminContents/Roles.tsx @@ -1,7 +1,9 @@ import { Button } from "@/components/ui/button"; import { Cog, UserRoundCog } from "lucide-react"; import EmptyFillContainer from "../../cards/EmptyFillContainer"; -import { RoleCreateModal } from "@/components/modals/userControls/RoleModal"; +import { RoleCreateModal } from "@/components/modals/userControls/RoleModal"; +import PermissonList from "@/components/lists/PermissonList"; +import { RoleDeleteModal } from "@/components/modals/userControls/RoleDeleteModal"; export default function AdminRolesManagementPanel() { return ( @@ -11,32 +13,30 @@ export default function AdminRolesManagementPanel() {
- + + + +
-
-
-
- Roles List -
-
- Permisson List -
-
- Role Distribution chart -
-
- Role Requests -
-
+
+ +

+ PERMISSON LIST BY ROLE +

+ +
); diff --git a/client/components/lists/PermissonList.tsx b/client/components/lists/PermissonList.tsx new file mode 100644 index 0000000..b318c89 --- /dev/null +++ b/client/components/lists/PermissonList.tsx @@ -0,0 +1,197 @@ +"usr client"; +import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; +import { Label } from "@/components/ui/label"; +import EmptyFillContainer from "../dashboard-componenets/cards/EmptyFillContainer"; +import { use, useEffect, useState } from "react"; +import useGetAllRole from "@/hooks/user_data/useGetAllRole"; +import { fetchAllPermissons } from "@/hooks/user_data/getAllPermissonList"; +import { set } from "react-hook-form"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "../ui/alert-dialog"; +import { Button } from "../ui/button"; +import { updatePermisson } from "@/hooks/user_data/updatePermisson"; +import { Pointer, SquareMousePointer } from "lucide-react"; + +type RolesWithPermisson = { + id: string; + name: string; + permissions: [ + { + name: string; + description: string; + } + ]; +}; + +type Permisson = { + name: string; + description: string; +}; + +type EditPermisson = { + role: string; + permission: string; + action: string; +}; + +function PermissonList() { + const { fetchAllRoles, rolesWithPermissions } = useGetAllRole(); + const [roles, setRoles] = useState([]); + const [permissions, setPermissions] = useState([]); + const [msg, setMsg] = useState(); + + async function getPermissons() { + setPermissions(await fetchAllPermissons()); + } + + useEffect(() => { + fetchAllRoles(); + getPermissons(); + }, []); + + useEffect(() => { + setRoles(rolesWithPermissions); + //setSelectedRole(rolesWithPermissions[0]?.name); + }, [rolesWithPermissions]); + + const [selectedRole, setSelectedRole] = useState(); + + return ( +
+ setSelectedRole(e)} + > + {roles.map((role) => ( +
+ + +
+ ))} +
+ + {selectedRole ? ( +
+
+ {roles + .filter((role) => role.name === selectedRole) + .map((role) => ( + + +
+ {permissions.map((permission) => ( +
{ + const str = role.permissions.find( + (perm) => perm.name === permission.name + ) + ? "REMOVE" + : "ADD"; + await setMsg({ + role: role.name, + permission: permission.name, + action: str, + }); + }} + className={ + "flex flex-col gap-1 my-2 px-6 py-4 bg-green-100 text-card-foreground shadow-md rounded-lg hover:cursor-pointer hover:scale-110 hover:text-white" + + (role.permissions.find( + (perm) => perm.name === permission.name + ) + ? " bg-green-500" + : " bg-red-500") + } + > +

+ {permission.name} +

+

{permission.description}

+
+ ))} +
+
+ + + +
+ CONFIRM THAT YOU WANT TO{" "} + {msg?.action === "ADD" ? ( +
+ + ADD "{msg?.permission}" + {" "} + PERMISSION TO{" "} +
+ ) : ( +
+ + REMOVE "{msg?.permission}" + {" "} + PERMISSION FROM{" "} +
+ )} + {msg?.role} ? +
+
+ +
+
+ ACTION : {msg?.action} PERMISSON +
+
+ ROLE : {msg?.role} +
+
+ PERMISSION : {msg?.permission} +
+
+
+
+ + Cancel + { + msg && alert(await updatePermisson(msg)); + await fetchAllRoles(); + setMsg(undefined); + }} + > + Confirm + + +
+
+ ))} +
+
+ ) : ( +

Select a role from above to view permissions

+ )} +
+ ); +} +export default PermissonList; diff --git a/client/components/modals/userControls/RoleDeleteModal.tsx b/client/components/modals/userControls/RoleDeleteModal.tsx new file mode 100644 index 0000000..3b17fc8 --- /dev/null +++ b/client/components/modals/userControls/RoleDeleteModal.tsx @@ -0,0 +1,72 @@ +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, + DialogClose +} from "@/components/ui/dialog"; + +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import addNewRole from "@/hooks/user_data/addNewRole"; +import deleteRole from "@/hooks/user_data/deleteRole"; +import React , {useState} from "react"; + +interface DialogWrapperProps { + children: React.ReactNode; +} + +export const RoleDeleteModal: React.FC = ({ + children, +}) => { + const [roleName, setRoleName] = useState(""); + + const handleSaveChanges = async () => { + await deleteRole(roleName); + window.location.reload(); + }; + + + return ( + + + {/* */} + {children} + + + + Delete Role + + Select a role to delete. Click delete when you're done. + + +
+
+ + setRoleName(e.target.value)} + /> +
+
+ + +
+ +
+
+
+ +
+
+ ); +}; diff --git a/client/components/modals/userControls/RoleModal.tsx b/client/components/modals/userControls/RoleModal.tsx index fc9f7d0..cad0dd3 100644 --- a/client/components/modals/userControls/RoleModal.tsx +++ b/client/components/modals/userControls/RoleModal.tsx @@ -12,6 +12,7 @@ import { import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; +import addNewRole from "@/hooks/user_data/addNewRole"; import React , {useState} from "react"; interface DialogWrapperProps { @@ -21,13 +22,11 @@ interface DialogWrapperProps { export const RoleCreateModal: React.FC = ({ children, }) => { - const [description, setDescription] = useState(""); const [roleName, setRoleName] = useState(""); - const handleSaveChanges = () => { - console.log("Vehicle Number:", description); - console.log("Vehicle Number:", roleName); - + const handleSaveChanges = async () => { + await addNewRole(roleName); + window.location.reload(); }; @@ -56,19 +55,7 @@ export const RoleCreateModal: React.FC = ({ value={roleName} onChange={(e) => setRoleName(e.target.value)} /> -
-
- - setDescription(e.target.value)} - /> -
+ diff --git a/client/components/ui/radio-group.tsx b/client/components/ui/radio-group.tsx new file mode 100644 index 0000000..cf01673 --- /dev/null +++ b/client/components/ui/radio-group.tsx @@ -0,0 +1,44 @@ +"use client" + +import * as React from "react" +import { CheckIcon } from "@radix-ui/react-icons" +import * as RadioGroupPrimitive from "@radix-ui/react-radio-group" + +import { cn } from "@/lib/utils" + +const RadioGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + return ( + + ) +}) +RadioGroup.displayName = RadioGroupPrimitive.Root.displayName + +const RadioGroupItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + return ( + + + + + + ) +}) +RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName + +export { RadioGroup, RadioGroupItem } diff --git a/client/data/apiRoutes.ts b/client/data/apiRoutes.ts index 6b80a02..9af7026 100644 --- a/client/data/apiRoutes.ts +++ b/client/data/apiRoutes.ts @@ -27,10 +27,12 @@ export const apiRoutes = { edit: `${baseUrl}/landfills/`, }, rbac: { - create: `${baseUrl}/rbac/create`, + create: `${baseUrl}/rbac/roles`, getByRole: `${baseUrl}/rbac/roles/get/`, - delete: `${baseUrl}/rbac/`, - edit: `${baseUrl}/rbac/`, + delete: `${baseUrl}/rbac/roles/delete/`, + edit: `${baseUrl}/rbac/roles/`, + getAllRolesWithPermisson: `${baseUrl}/rbac/all`, + fetchAllPermissons: `${baseUrl}/rbac/permissions`, }, vehicle: { create: `${baseUrl}/vehicles/create`, diff --git a/client/hooks/user_data/addNewRole.ts b/client/hooks/user_data/addNewRole.ts new file mode 100644 index 0000000..bcab66b --- /dev/null +++ b/client/hooks/user_data/addNewRole.ts @@ -0,0 +1,27 @@ +"use client"; +import { UserData } from "@/components/graphs/Data"; +import { apiRoutes } from "@/data/apiRoutes"; +import { jwtToken } from "@/data/cookieNames"; +import { admin, landfillManager, stsManager, unassigned } from "@/data/roles"; +import { getCookie } from "@/lib/cookieFunctions"; +import axios from "axios"; +import { useState, useEffect, use } from "react"; + + +export default async function addNewRole(roleName: string) { + + if(!roleName) return "Enter a valid role name."; + try { + const res = await axios.post(apiRoutes.rbac.create, { roleName },{ + headers: { + Authorization: `Bearer ${await getCookie(jwtToken)}`, + }, + }); + + if(res) return "Role added successfully"; + } catch (error: any) { + alert("Error creating role... Are you authorized?"); + console.log(error.message); + } + +} diff --git a/client/hooks/user_data/deleteRole.ts b/client/hooks/user_data/deleteRole.ts new file mode 100644 index 0000000..4619a8c --- /dev/null +++ b/client/hooks/user_data/deleteRole.ts @@ -0,0 +1,27 @@ +"use client"; +import { UserData } from "@/components/graphs/Data"; +import { apiRoutes } from "@/data/apiRoutes"; +import { jwtToken } from "@/data/cookieNames"; +import { admin, landfillManager, stsManager, unassigned } from "@/data/roles"; +import { getCookie } from "@/lib/cookieFunctions"; +import axios from "axios"; +import { useState, useEffect, use } from "react"; + + +export default async function deleteRole(roleName: string) { + + if(!roleName) return "Enter a valid role name."; + try { + const res = await axios.delete(apiRoutes.rbac.delete + roleName ,{ + headers: { + Authorization: `Bearer ${await getCookie(jwtToken)}`, + }, + }); + + if(res) return "Role added successfully"; + } catch (error: any) { + alert("Error creating role... Are you authorized?"); + console.log(error.message); + } + +} diff --git a/client/hooks/user_data/getAllPermissonList.ts b/client/hooks/user_data/getAllPermissonList.ts new file mode 100644 index 0000000..9bf5a50 --- /dev/null +++ b/client/hooks/user_data/getAllPermissonList.ts @@ -0,0 +1,26 @@ +import { apiRoutes } from "@/data/apiRoutes"; +import { jwtToken } from "@/data/cookieNames"; +import { getCookie } from "@/lib/cookieFunctions"; +import axios from "axios"; + +export async function fetchAllPermissons() { + try { + const token = getCookie(jwtToken); + const response = await axios.get(apiRoutes.rbac.fetchAllPermissons, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + //console.log(response.data); + return response.data.map((permisson: any) => { + return { + name: permisson.name, + description: permisson.description, + }; + }); + } catch (error: any) { + alert("Error fetching permissons... Are you authorized?"); + console.log(error.message); + } +} diff --git a/client/hooks/user_data/updatePermisson.tsx b/client/hooks/user_data/updatePermisson.tsx new file mode 100644 index 0000000..d155dcf --- /dev/null +++ b/client/hooks/user_data/updatePermisson.tsx @@ -0,0 +1,46 @@ +import { apiRoutes } from "@/data/apiRoutes"; +import { jwtToken } from "@/data/cookieNames"; +import { getCookie } from "@/lib/cookieFunctions"; +import axios from "axios"; + +type EditPermisson = { + role: string; + permission: string; + action: string; +}; + +export async function updatePermisson(payload: EditPermisson) { + try { + const token = getCookie(jwtToken); + let response; + payload.action === "ADD" + ? (response = await axios.post( + apiRoutes.rbac.edit + + payload.role + + "/permissions/" + + payload.permission, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + )) + : (response = await axios.delete( + apiRoutes.rbac.delete + + payload.role + + "/permissions/" + + payload.permission, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + )); + + //console.log(response.data); + return "Permisson updated successfully!"; + } catch (error: any) { + alert("Error updating permissons... Are you authorized?"); + console.log(error.message); + } +} diff --git a/client/hooks/user_data/useGetAllRole.tsx b/client/hooks/user_data/useGetAllRole.tsx index 933d946..c6431f7 100644 --- a/client/hooks/user_data/useGetAllRole.tsx +++ b/client/hooks/user_data/useGetAllRole.tsx @@ -5,20 +5,42 @@ import { admin, landfillManager, stsManager, unassigned } from "@/data/roles"; import { getCookie } from "@/lib/cookieFunctions"; import axios from "axios"; import { useState, useEffect, use } from "react"; +import { set } from "react-hook-form"; -type User = { +type RolesWithPermisson = { id: string; - username: string; - email: string; - role: string; + name: string; + permissions: [{ + name: string; + description: string; +}] }; export default function useGetAllRole() { const [roles, setRoles] = useState([]); + const [rolesWithPermissions, setRolesWithPermissions] = useState([]); async function fetchAllRoles() { try { - await setRoles([unassigned, admin, landfillManager, stsManager]); + const token = getCookie(jwtToken); + const response = await axios.get(apiRoutes.rbac.getAllRolesWithPermisson, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + setRolesWithPermissions(response.data.map((role: RolesWithPermisson) => { + return { + id: role.id, + name: role.name, + permissions: role.permissions.map((permission: any) => { + return { + name: permission.name, + description: permission.description, + }; + }, + )}; + })); + await setRoles([unassigned, admin, landfillManager, stsManager]); console.log(roles); } catch (error: any) { alert("Error fetching roles... Are you authorized?"); @@ -30,5 +52,5 @@ export default function useGetAllRole() { fetchAllRoles(); }, []); - return {fetchAllRoles, roles}; + return {fetchAllRoles, roles, rolesWithPermissions}; } diff --git a/client/package-lock.json b/client/package-lock.json index 16d7b9b..cedf5da 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -19,6 +19,7 @@ "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.0.2", + "@radix-ui/react-radio-group": "^1.1.3", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toast": "^1.1.5", @@ -2513,6 +2514,38 @@ } } }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.1.3.tgz", + "integrity": "sha512-x+yELayyefNeKeTx4fjK6j99Fs6c4qKm3aY38G3swQVTN6xMpsrbigC0uHs2L//g8q4qR7qOcww8430jJmi2ag==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-direction": "1.0.1", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-roving-focus": "1.0.4", + "@radix-ui/react-use-controllable-state": "1.0.1", + "@radix-ui/react-use-previous": "1.0.1", + "@radix-ui/react-use-size": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-roving-focus": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz", diff --git a/client/package.json b/client/package.json index 8725d7f..5a99c45 100644 --- a/client/package.json +++ b/client/package.json @@ -20,6 +20,7 @@ "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.0.2", + "@radix-ui/react-radio-group": "^1.1.3", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toast": "^1.1.5", diff --git a/server/src/controllers/rbac.ts b/server/src/controllers/rbac.ts index 53acc4e..a5d3612 100644 --- a/server/src/controllers/rbac.ts +++ b/server/src/controllers/rbac.ts @@ -7,11 +7,15 @@ const prisma = new PrismaClient(); const getAllRoles = errorWrapper( async (req: Request, res: Response) => { - const roles = await prisma.role.findMany({}); + const roles = await prisma.role.findMany({ + include: { + User: true, + }, + }); - const roleNames = roles.map((role) => role.name); + // const roleNames = roles.map((role) => role.name); - res.json(roleNames); + res.json(roles); }, { statusCode: 500, message: "Couldn't fetch roles" } ); @@ -59,7 +63,7 @@ const getPermissions = errorWrapper( async (req: Request, res: Response) => { const permissions = await prisma.permission.findMany({}); - const permissionNames = permissions.map((permission) => permission.name); + const permissionNames = permissions; res.json(permissionNames); },