diff --git a/package.json b/package.json index b131bde6..62ddf41c 100644 --- a/package.json +++ b/package.json @@ -22,10 +22,12 @@ "@mantine/prism": "^6.0.15", "@monaco-editor/react": "^4.5.1", "@tabler/icons-react": "^2.23.0", + "@types/js-cookie": "^3.0.3", "axios": "^1.4.0", "dayjs": "^1.11.8", "http-status-codes": "^2.2.0", "immer": "^10.0.2", + "js-cookie": "^3.0.5", "just-compare": "^2.3.0", "ms": "^2.1.3", "react": "^18.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7c4cce6f..ac389350 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ dependencies: '@tabler/icons-react': specifier: ^2.23.0 version: 2.23.0(react@18.2.0) + '@types/js-cookie': + specifier: ^3.0.3 + version: 3.0.3 axios: specifier: ^1.4.0 version: 1.4.0 @@ -44,6 +47,9 @@ dependencies: immer: specifier: ^10.0.2 version: 10.0.2 + js-cookie: + specifier: ^3.0.5 + version: 3.0.5 just-compare: specifier: ^2.3.0 version: 2.3.0 @@ -964,6 +970,10 @@ packages: hoist-non-react-statics: 3.3.2 dev: false + /@types/js-cookie@3.0.3: + resolution: {integrity: sha512-Xe7IImK09HP1sv2M/aI+48a20VX+TdRJucfq4vfRVy6nWN8PYPOEnlMRSgxJAgYQIXJVL8dZ4/ilAM7dWNaOww==} + dev: false + /@types/json-schema@7.0.11: resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} dev: true @@ -2359,6 +2369,11 @@ packages: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true + /js-cookie@3.0.5: + resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} + engines: {node: '>=14'} + dev: false + /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} diff --git a/src/api/auth.ts b/src/api/auth.ts index 5ea63030..367c7d4c 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -1,10 +1,11 @@ import { Axios } from './axios'; -import { LOG_STREAM_LIST_URL } from './constants'; +import { LOGIN_URL } from './constants'; + export const loginIn = (username: string, password: string) => { const credentials = btoa(`${username}:${password}`); - return Axios().get(LOG_STREAM_LIST_URL, { + return Axios().get(LOGIN_URL, { headers: { Authorization: `Basic ${credentials}`, }, diff --git a/src/api/axios.ts b/src/api/axios.ts index ddbff558..1d7ab96b 100644 --- a/src/api/axios.ts +++ b/src/api/axios.ts @@ -2,17 +2,11 @@ import axios from 'axios'; const baseURL = import.meta.env.VITE_PARSEABLE_URL ?? '/'; -const instance = axios.create({ baseURL, validateStatus: () => true }); +const instance = axios.create({ baseURL, validateStatus: () => true, withCredentials: true }); instance.interceptors.request.use( (request) => { - const credentials = localStorage.getItem('credentials'); - if (credentials) { - const Authorization = credentials ? `Basic ${credentials}` : null; - - request.headers.Authorization = Authorization; - } return request; }, diff --git a/src/api/constants.ts b/src/api/constants.ts index a05a26b9..137cd631 100644 --- a/src/api/constants.ts +++ b/src/api/constants.ts @@ -1,6 +1,5 @@ const API_V1 = 'api/v1'; - - +// const baseURL = process.env.REACT_APP_API_URL || '/'; // Streams Management export const LOG_STREAM_LIST_URL = `${API_V1}/logstream`; export const LOG_STREAMS_SCHEMA_URL = (streamName: string) => `${LOG_STREAM_LIST_URL}/${streamName}/schema`; @@ -17,4 +16,14 @@ export const ABOUT_URL = `${API_V1}/about`; export const USERS_LIST_URL = `${API_V1}/user`; export const USER_URL = (username: string) => `${USERS_LIST_URL}/${username}`; export const USER_ROLES_URL = (username: string) => `${USER_URL(username)}/role`; -export const USER_PASSWORD_URL = (username: string) => `${USER_URL(username)}/generate-new-password`; \ No newline at end of file +export const USER_PASSWORD_URL = (username: string) => `${USER_URL(username)}/generate-new-password`; + + +// Roles Management +export const ROLES_LIST_URL = `${API_V1}/role`; +export const ROLE_URL = (roleName: string) => `${ROLES_LIST_URL}/${roleName}`; + + +//USERS LOGIN + +export const LOGIN_URL = `${API_V1}/o/login?redirect=${window.location.origin}`; \ No newline at end of file diff --git a/src/api/roles.ts b/src/api/roles.ts new file mode 100644 index 00000000..ae3367a2 --- /dev/null +++ b/src/api/roles.ts @@ -0,0 +1,18 @@ +import { Axios } from "./axios"; +import { ROLE_URL, ROLES_LIST_URL } from "./constants"; + +export const getRoles = () => { + return Axios().get(ROLES_LIST_URL); +} + +export const deleteRole = (roleName: string) => { + return Axios().delete(ROLE_URL(roleName)); +} + +export const putRole = (roleName: string, privilege: object[]) => { + return Axios().put(ROLE_URL(roleName), privilege); +} + +export const getRole = (roleName: string) => { + return Axios().get(ROLE_URL(roleName)); +} \ No newline at end of file diff --git a/src/components/Header/SubHeader.tsx b/src/components/Header/SubHeader.tsx index 68df437e..d799bddc 100644 --- a/src/components/Header/SubHeader.tsx +++ b/src/components/Header/SubHeader.tsx @@ -8,8 +8,7 @@ import TimeRange from './TimeRange'; import { useLogQueryStyles } from './styles'; import ReloadUser from './ReloadUser'; import DocsUser from './UserDocs'; -import CreateUser from './CreateUser'; -// import LimitLog from './LimitLogs'; + export const StatsHeader: FC = () => { const { classes } = useLogQueryStyles(); @@ -110,7 +109,7 @@ export const UsersManagementHeader: FC = () => { - + {/* */} diff --git a/src/components/Mantine/theme.tsx b/src/components/Mantine/theme.tsx index cb51b6eb..f5e54f26 100644 --- a/src/components/Mantine/theme.tsx +++ b/src/components/Mantine/theme.tsx @@ -1,4 +1,4 @@ -import type { CSSObject, MantineThemeOverride } from '@mantine/core'; +import { type CSSObject, type MantineThemeOverride } from '@mantine/core'; import { heights, widths, sizing } from './sizing'; const globalStyles = (): CSSObject => { @@ -235,5 +235,17 @@ export const theme: MantineThemeOverride = { }; } }, + + PasswordInput: { + styles: ({ colors }) => { + return { + input: { + border: `${sizing.px} ${colors.gray[2]} solid`, + borderRadius: 'md', + + }, + }; + } + } }, }; diff --git a/src/components/Navbar/index.tsx b/src/components/Navbar/index.tsx index fd733a3c..4b1f9701 100644 --- a/src/components/Navbar/index.tsx +++ b/src/components/Navbar/index.tsx @@ -16,20 +16,22 @@ import { } from '@tabler/icons-react'; import { FC, useEffect } from 'react'; import { useNavbarStyles } from './styles'; -import { useLocation, useParams } from 'react-router-dom'; +import { useLocation, useParams } from 'react-router-dom'; import { useGetLogStreamList } from '@/hooks/useGetLogStreamList'; import { notifications } from '@mantine/notifications'; import { useNavigate } from 'react-router-dom'; import { DEFAULT_FIXED_DURATIONS, useHeaderContext } from '@/layouts/MainLayout/Context'; import useMountedState from '@/hooks/useMountedState'; import dayjs from 'dayjs'; -import { useDisclosure, useLocalStorage } from '@mantine/hooks'; -import { LOGIN_ROUTE, USERS_MANAGEMENT_ROUTE } from '@/constants/routes'; +import { useDisclosure } from '@mantine/hooks'; +import { USERS_MANAGEMENT_ROUTE } from '@/constants/routes'; import { useDeleteLogStream } from '@/hooks/useDeleteLogStream'; import InfoModal from './infoModal'; import { useGetUserRole } from '@/hooks/useGetUserRoles'; import { getStreamsSepcificAccess, getUserSepcificStreams } from './rolesHandler'; import { LogStreamData } from '@/@types/parseable/api/stream'; +import Cookies from 'js-cookie'; +const baseURL = import.meta.env.VITE_PARSEABLE_URL ?? '/'; const links = [ { icon: IconZoomCode, label: 'Query', pathname: '/query', requiredAccess: ['Query', 'GetSchema'] }, @@ -45,9 +47,7 @@ const Navbar: FC = (props) => { const { streamName } = useParams(); const location = useLocation(); - const [username] = useLocalStorage({ key: 'username', getInitialValueInEffect: false }); - const [, , removeCredentials] = useLocalStorage({ key: 'credentials' }); - const [, , removeUsername] = useLocalStorage({ key: 'username' }); + const username = Cookies.get('username') const { state: { subNavbarTogle }, @@ -75,14 +75,10 @@ const Navbar: FC = (props) => { }, [subNavbarTogle.get()]); const onSignOut = () => { - removeCredentials(); - removeUsername(); - navigate( - { - pathname: LOGIN_ROUTE, - }, - { replace: true }, - ); + Cookies.remove('session'); + Cookies.remove('username'); + + window.location.href=`${baseURL}api/v1/o/logout?redirect=${window.location.origin}/login`; }; const { @@ -172,7 +168,6 @@ const Navbar: FC = (props) => { } }, [deleteData]); - //isAdmin const { data: roles, getRoles, resetData } = useGetUserRole(); useEffect(() => { if (username) { @@ -184,8 +179,8 @@ const Navbar: FC = (props) => { }, [username]); useEffect(() => { - if (streams && streams.length > 0 && roles && roles.length > 0) { - const userStreams = getUserSepcificStreams(roles, streams as any); + if(streams && streams.length > 0 && roles){ + const userStreams = getUserSepcificStreams(roles,streams as any); setUserSepecficStreams(userStreams as any); } }, [roles, streams]); diff --git a/src/components/Navbar/rolesHandler.ts b/src/components/Navbar/rolesHandler.ts index 634c98d8..163299e4 100644 --- a/src/components/Navbar/rolesHandler.ts +++ b/src/components/Navbar/rolesHandler.ts @@ -1,67 +1,101 @@ - const adminAccess =[ "Ingest", "Query", "CreateStream", "ListStream", "GetSchema", "GetStats", "DeleteStream", "GetRetention", "PutRetention", "PutAlert", "GetAlert", "PutUser", "ListUser", "DeleteUser", "PutRoles", "GetRole"] const editorAccess = ["Ingest", "Query", "CreateStream", "ListStream", "GetSchema", "GetStats", "GetRetention", "PutRetention", "PutAlert", "GetAlert"] const writerAccess = ["Ingest", "Query", "ListStream", "GetSchema", "GetStats", "GetRetention", "PutAlert", "GetAlert"] const readerAccess = ["Query", "ListStream", "GetSchema", "GetStats", "GetRetention", "GetAlert"] -const getStreamsSepcificAccess = (roles: object[], stream: string ) => { - let access : string[] = []; - roles.forEach((role: any) => { - if(role.privilege === "admin"){ - adminAccess.forEach((adminAction: string) => { - if(!access.includes(adminAction)){ - access.push(adminAction); - } - } - ) - } - if(role.privilege === "editor"){ - editorAccess.forEach((editorAction: string) => { - if(!access.includes(editorAction)){ - access.push(editorAction); - } - } - ) - } - if(role.privilege === "writer" && role.resource.stream === stream){ - writerAccess.forEach((writerAction: string) => { - if(!access.includes(writerAction)){ - access.push(writerAction); - } - } - ) +const adminAccess = [ + 'Ingest', + 'Query', + 'CreateStream', + 'ListStream', + 'GetSchema', + 'GetStats', + 'DeleteStream', + 'GetRetention', + 'PutRetention', + 'PutAlert', + 'GetAlert', + 'PutUser', + 'ListUser', + 'DeleteUser', + 'PutRoles', + 'GetRole', +]; +const editorAccess = [ + 'Ingest', + 'Query', + 'CreateStream', + 'ListStream', + 'GetSchema', + 'GetStats', + 'GetRetention', + 'PutRetention', + 'PutAlert', + 'GetAlert', +]; +const writerAccess = ['Ingest', 'Query', 'ListStream', 'GetSchema', 'GetStats', 'GetRetention', 'PutAlert', 'GetAlert']; +const readerAccess = ['Query', 'ListStream', 'GetSchema', 'GetStats', 'GetRetention', 'GetAlert']; - } - if(role.privilege === "reader" && role.resource.stream === stream){ - readerAccess.forEach((readerAction: string) => { - if(!access.includes(readerAction)){ - access.push(readerAction); - } - } - ) - - } - }); +const getStreamsSepcificAccess = (rolesWithRoleName: object[], stream: string) => { + let access: string[] = []; + let roles: any[] = []; + for (var prop in rolesWithRoleName) { + roles = [...roles, ...rolesWithRoleName[prop] as any]; + } + roles.forEach((role: any) => { + if (role.privilege === 'admin') { + adminAccess.forEach((adminAction: string) => { + if (!access.includes(adminAction)) { + access.push(adminAction); + } + }); + } + if (role.privilege === 'editor') { + editorAccess.forEach((editorAction: string) => { + if (!access.includes(editorAction)) { + access.push(editorAction); + } + }); + } + if (role.privilege === 'writer' && role.resource.stream === stream) { + writerAccess.forEach((writerAction: string) => { + if (!access.includes(writerAction)) { + access.push(writerAction); + } + }); + } + if (role.privilege === 'reader' && role.resource.stream === stream) { + readerAccess.forEach((readerAction: string) => { + if (!access.includes(readerAction)) { + access.push(readerAction); + } + }); + } + }); - return access; -} + return access; +}; -const getUserSepcificStreams = (roles: object[], streams: any[]) => { - let userStreams: any[] = []; - roles.forEach((role: any) => { - if(role.privilege === "admin" || role.privilege === "editor" || role.resource.stream === "*"){ - userStreams = streams; - return userStreams; - } - if(streams.find((stream: any) => stream.name === role.resource.stream)){ - if(!userStreams.find((stream: any) => stream.name === role.resource.stream)){ - userStreams.push({name: role.resource.stream}) - } - } - }); +const getUserSepcificStreams = (rolesWithRoleName: object[], streams: any[]) => { + let userStreams: any[] = []; + let roles: any[] = []; + for (var prop in rolesWithRoleName) { + roles = [...roles, ...rolesWithRoleName[prop] as any]; + } + roles.forEach((role: any) => { + if (role.privilege === 'admin' || role.privilege === 'editor' || role.resource.stream === '*') { + userStreams = streams; + return userStreams; + } + if (streams.find((stream: any) => stream.name === role.resource.stream)) { + if (!userStreams.find((stream: any) => stream.name === role.resource.stream)) { + userStreams.push({ name: role.resource.stream }); + } + } + }); - return userStreams; -} + return userStreams; +}; -export {getStreamsSepcificAccess, getUserSepcificStreams}; +export { getStreamsSepcificAccess, getUserSepcificStreams }; diff --git a/src/hooks/useDeleteRole.tsx b/src/hooks/useDeleteRole.tsx new file mode 100644 index 00000000..5c398955 --- /dev/null +++ b/src/hooks/useDeleteRole.tsx @@ -0,0 +1,75 @@ + +import { StatusCodes } from 'http-status-codes'; +import useMountedState from './useMountedState'; +import { deleteRole } from '@/api/roles'; +import { notifications } from '@mantine/notifications'; +import { IconCheck, IconFileAlert } from '@tabler/icons-react'; + +export const useDeleteRole = () => { + const [data, setData] = useMountedState(null); + const [error, setError] = useMountedState(null); + const [loading, setLoading] = useMountedState(false); + + const deleteRoleFun = async (roleName:string) => { + try { + setLoading(true); + notifications.show({ + id: 'load-data', + loading: true, + color: '#545BEB', + title: 'Deleting Role', + message: 'Role will be deleted.', + autoClose: false, + withCloseButton: false, + }); + setError(null); + const res = await deleteRole(roleName); + + switch (res.status) { + case StatusCodes.OK: { + setData("Role was deleted"); + notifications.update({ + id: 'load-data', + color: 'green', + title: 'Role was deleted', + message: 'Successfully deleted', + icon: , + autoClose: 3000, + }); + break; + } + default: { + setError('Failed to Delete Roles'); + notifications.update({ + id: 'load-data', + color: 'red', + title: 'Error occurred', + message: 'Error occurred while deleting Role', + icon: , + autoClose: 2000, + }); + console.error(res); + } + } + } catch(error) { + notifications.update({ + id: 'load-data', + color: 'red', + title: 'Error occurred', + message: 'Error occurred while deleting role', + icon: , + autoClose: 2000, + }); + setError('Failed to Delete Roles'); + console.error(error); + } finally { + setLoading(false); + } + }; + + const resetData = () => { + setData(null); + }; + + return { data, error, loading, deleteRoleFun, resetData }; +} \ No newline at end of file diff --git a/src/hooks/useGetLogStreamList.tsx b/src/hooks/useGetLogStreamList.tsx index 1bf096b9..dbcd4e79 100644 --- a/src/hooks/useGetLogStreamList.tsx +++ b/src/hooks/useGetLogStreamList.tsx @@ -6,16 +6,16 @@ import { useEffect } from 'react'; import useMountedState from './useMountedState'; import { notifications } from '@mantine/notifications'; import { IconFileAlert, IconCheck } from '@tabler/icons-react'; -import { useLocalStorage } from '@mantine/hooks'; + import { useNavigate } from 'react-router-dom'; import { LOGIN_ROUTE } from '@/constants/routes'; +import Cookies from 'js-cookie'; export const useGetLogStreamList = () => { const [data, setData] = useMountedState(null); const [error, setError] = useMountedState(null); const [loading, setLoading] = useMountedState(false); - const [, , removeCredentials] = useLocalStorage({ key: 'credentials' }); - const [, , removeUsername] = useLocalStorage({ key: 'username' }); + const navigate = useNavigate(); const getData = async () => { @@ -63,6 +63,9 @@ export const useGetLogStreamList = () => { } case StatusCodes.UNAUTHORIZED: { setError('Unauthorized'); + Cookies.remove('session'); + Cookies.remove('username'); + console.log(Cookies.get()); notifications.update({ id: 'load-data', color: 'red', @@ -71,9 +74,6 @@ export const useGetLogStreamList = () => { icon: , autoClose: 2000, }); - - removeCredentials(); - removeUsername(); navigate( { pathname: LOGIN_ROUTE, diff --git a/src/hooks/useGetRole.tsx b/src/hooks/useGetRole.tsx new file mode 100644 index 00000000..27dcc357 --- /dev/null +++ b/src/hooks/useGetRole.tsx @@ -0,0 +1,39 @@ +import { StatusCodes } from 'http-status-codes'; +import useMountedState from './useMountedState'; +import { getRole } from '@/api/roles'; + +export const useGetRole = () => { + const [data, setData] = useMountedState(null); + const [error, setError] = useMountedState(null); + const [loading, setLoading] = useMountedState(false); + + const getRolePrivilege = async (roleName:string) => { + try { + setLoading(true); + setError(null); + const res = await getRole(roleName); + + switch (res.status) { + case StatusCodes.OK: { + setData(res.data); + break; + } + default: { + setError('Failed to get Role'); + console.error(res); + } + } + } catch(error) { + setError('Failed to get Role'); + console.error(error); + } finally { + setLoading(false); + } + }; + + const resetData = () => { + setData(null); + }; + + return { data, error, loading, getRolePrivilege, resetData }; +}; diff --git a/src/hooks/useGetRoles.tsx b/src/hooks/useGetRoles.tsx new file mode 100644 index 00000000..a0363879 --- /dev/null +++ b/src/hooks/useGetRoles.tsx @@ -0,0 +1,39 @@ +import { StatusCodes } from 'http-status-codes'; +import useMountedState from './useMountedState'; +import { getRoles } from '@/api/roles'; + +export const useGetRoles = () => { + const [data, setData] = useMountedState(null); + const [error, setError] = useMountedState(null); + const [loading, setLoading] = useMountedState(false); + + const getRolesList = async () => { + try { + setLoading(true); + setError(null); + const res = await getRoles(); + + switch (res.status) { + case StatusCodes.OK: { + setData(res.data); + break; + } + default: { + setError('Failed to get Roles'); + console.error(res); + } + } + } catch(error) { + setError('Failed to get Roles'); + console.error(error); + } finally { + setLoading(false); + } + }; + + const resetData = () => { + setData(null); + }; + + return { data, error, loading, getRolesList, resetData }; +}; diff --git a/src/hooks/useGetUserRoles.ts b/src/hooks/useGetUserRoles.ts index b6c35a73..53e27a0c 100644 --- a/src/hooks/useGetUserRoles.ts +++ b/src/hooks/useGetUserRoles.ts @@ -1,7 +1,6 @@ import { StatusCodes } from 'http-status-codes'; import useMountedState from './useMountedState'; import { getUserRoles } from '@/api/users'; -import { useLocalStorage } from '@mantine/hooks'; import { useNavigate } from 'react-router-dom'; import { LOGIN_ROUTE } from '@/constants/routes'; @@ -9,8 +8,6 @@ export const useGetUserRole = () => { const [data, setData] = useMountedState(null); const [error, setError] = useMountedState(null); const [loading, setLoading] = useMountedState(false); - const [, , removeCredentials] = useLocalStorage({ key: 'credentials' }); - const [, , removeUsername] = useLocalStorage({ key: 'username' }); const navigate = useNavigate(); const getRoles = async (userName:string) => { @@ -18,16 +15,17 @@ export const useGetUserRole = () => { setLoading(true); setError(null); const res = await getUserRoles(userName); - switch (res.status) { case StatusCodes.OK: { + // let result:any[]=[] + // for (var prop in res.data ) { + // result=[...result,...res.data[prop]] + // } setData(res.data); break; } case StatusCodes.UNAUTHORIZED: { setError('Unauthorized'); - removeCredentials(); - removeUsername(); navigate( { pathname: LOGIN_ROUTE, diff --git a/src/hooks/useLoginForm.ts b/src/hooks/useLoginForm.ts index 8ebb17e0..e1fa892b 100644 --- a/src/hooks/useLoginForm.ts +++ b/src/hooks/useLoginForm.ts @@ -6,32 +6,21 @@ import { StatusCodes } from 'http-status-codes'; import useMountedState from './useMountedState'; import { useLocation, useNavigate } from 'react-router-dom'; import { HOME_ROUTE } from '@/constants/routes'; -import { useId, useLocalStorage } from '@mantine/hooks'; +import { useId } from '@mantine/hooks'; import { useEffect } from 'react'; +import Cookies from 'js-cookie'; export const useLoginForm = () => { const notificationId = useId(); const [loading, setLoading] = useMountedState(false); const [error, setError] = useMountedState(null); - const [credentials, setCredentials] = useLocalStorage({ - key: 'credentials', - getInitialValueInEffect: false, - serialize: (value) => { - return value; - }, - }); - const [, setUsername] = useLocalStorage({ - key: 'username', - serialize: (value) => { - return value; - }, - }); + const auth = Cookies.get('session') const nav = useNavigate(); const location = useLocation(); useEffect(() => { - if (credentials) { + if (auth) { nav( { pathname: HOME_ROUTE, @@ -69,15 +58,12 @@ export const useLoginForm = () => { switch (res.status) { case StatusCodes.OK: { - const credentials = btoa(`${data.username}:${data.password}`); - setCredentials(credentials); - setUsername(data.username); - const pathname = location.state?.from?.pathname || HOME_ROUTE; - + console.log(pathname,"pathname"); nav({ - pathname, - }); + pathname:"/" + }, + { replace: true }); break; } diff --git a/src/hooks/usePutRole.tsx b/src/hooks/usePutRole.tsx new file mode 100644 index 00000000..5d77e030 --- /dev/null +++ b/src/hooks/usePutRole.tsx @@ -0,0 +1,74 @@ +import { StatusCodes } from 'http-status-codes'; +import useMountedState from './useMountedState'; +import { putRole } from '@/api/roles'; +import { notifications } from '@mantine/notifications'; +import { IconCheck, IconFileAlert } from '@tabler/icons-react'; + +export const usePutRole = () => { + const [data, setData] = useMountedState(null); + const [error, setError] = useMountedState(null); + const [loading, setLoading] = useMountedState(false); + + const putRolePrivilege = async (roleName: string, privilege: object[]) => { + try { + setLoading(true); + setError(null); + notifications.show({ + id: 'load-data', + loading: true, + color: '#545BEB', + title: 'updating/creating Role', + message: 'Role will be updated/created.', + autoClose: false, + withCloseButton: false, + }); + const res = await putRole(roleName, privilege); + + switch (res.status) { + case StatusCodes.OK: { + setData("Role was updated/created"); + notifications.update({ + id: 'load-data', + color: 'green', + title: 'Role was updated/created', + message: 'Successfully updated/created', + icon: , + autoClose: 3000, + }); + break; + } + default: { + setError('Failed to put Role'); + notifications.update({ + id: 'load-data', + color: 'red', + title: 'Error occurred', + message: 'Error occurred while updated/created Role', + icon: , + autoClose: 2000, + }); + console.error(res); + } + } + } catch (error) { + setError('Failed to get Role'); + notifications.update({ + id: 'load-data', + color: 'red', + title: 'Error occurred', + message: 'Error occurred while updated/created Role', + icon: , + autoClose: 2000, + }); + console.error(error); + } finally { + setLoading(false); + } + }; + + const resetData = () => { + setData(null); + }; + + return { data, error, loading, putRolePrivilege, resetData }; +}; diff --git a/src/hooks/usePutUserRole.tsx b/src/hooks/usePutUserRole.tsx index fa0f4209..74f7132b 100644 --- a/src/hooks/usePutUserRole.tsx +++ b/src/hooks/usePutUserRole.tsx @@ -1,15 +1,16 @@ import { StatusCodes } from 'http-status-codes'; import useMountedState from './useMountedState'; -import { putUserRoles } from '@/api/users'; +import { putUserRoles } from '@/api/users'; import { notifications } from '@mantine/notifications'; import { IconCheck, IconFileAlert } from '@tabler/icons-react'; +import Cookies from 'js-cookie'; export const usePutUserRole = () => { const [data, setData] = useMountedState(null); const [error, setError] = useMountedState(null); const [loading, setLoading] = useMountedState(false); - const putRole = async (userName:string, roles :any) => { + const putRole = async (userName: string, roles: any) => { try { setLoading(true); notifications.show({ @@ -32,15 +33,16 @@ export const usePutUserRole = () => { color: 'green', title: 'Updated user', message: 'Successfully updated', - icon: , + icon: , autoClose: 3000, }); break; - } default: { setError(res.data); console.error(res); + Cookies.remove('session'); + Cookies.remove('username'); notifications.update({ id: 'load-data', color: 'red', @@ -51,17 +53,17 @@ export const usePutUserRole = () => { }); } } - } catch(error) { + } catch (error) { setError('Failed to get create user'); - notifications.update({ - id: 'load-data', - color: 'red', - title: 'Error occurred', - message: 'Error occurred while updating user role(s)', - icon: , - autoClose: 2000, - }); - console.error(error); + notifications.update({ + id: 'load-data', + color: 'red', + title: 'Error occurred', + message: 'Error occurred while updating user role(s)', + icon: , + autoClose: 2000, + }); + console.error(error); } finally { setLoading(false); } @@ -73,4 +75,3 @@ export const usePutUserRole = () => { return { data, error, loading, putRole, resetData }; }; - diff --git a/src/pages/AccessManagement/PrivilegeTR.tsx b/src/pages/AccessManagement/PrivilegeTR.tsx new file mode 100644 index 00000000..4b5904a8 --- /dev/null +++ b/src/pages/AccessManagement/PrivilegeTR.tsx @@ -0,0 +1,447 @@ +import { + ActionIcon, + Badge, + Box, + Button, + Group, + Modal, + Select, + Stack, + Text, + TextInput, + Tooltip, + px, + rem, +} from '@mantine/core'; +import { useDisclosure } from '@mantine/hooks'; +import { IconPlus, IconTrash, IconX } from '@tabler/icons-react'; +import { FC, useEffect, useState } from 'react'; +import { useUsersStyles } from './styles'; +import { useGetRole } from '@/hooks/useGetRole'; +import { useDeleteRole } from '@/hooks/useDeleteRole'; +import { usePutRole } from '@/hooks/usePutRole'; +import { useGetLogStreamList } from '@/hooks/useGetLogStreamList'; + +interface PrivilegeTRProps { + roleName: string; + getRolesList: () => void; +} + +const PrivilegeTR: FC = (props) => { + const { roleName, getRolesList } = props; + + const [UserInput, setUserInput] = useState(''); + + // Delete Privilege Modal Constants : Starts + const [deletePrivilegeIndex, setDeletePrivilegeIndex] = useState(0); + const [isDeletedPrivilegeOpen, { open: openDeletePrivilege, close: closeDeletePrivilege }] = useDisclosure(); + // Delete Privilege Modal Constants : Ends + + // Delete Role Modal Constants : Starts + const [isDeletedRoleOpen, { open: openDeleteRole, close: closeDeleteRole }] = useDisclosure(); + // Delete Role Modal Constants : Ends + + // Update Role Modal Constants : Starts + const [isUpdatedRoleOpen, { open: openUpdateRole, close: closeUpdateRole }] = useDisclosure(); + const [selectedPrivilege, setSelectedPrivilege] = useState(''); + const [SelectedStream, setSelectedStream] = useState(''); + const [streamSearchValue, setStreamSearchValue] = useState(''); + const [tagInput, setTagInput] = useState(''); + const { data: streams } = useGetLogStreamList(); + // Update Role Modal Constants : Ends + + const { + data: privileges, + error: getRolePrivilegeError, + loading: getRolePrivilegeLoading, + getRolePrivilege, + resetData: resetGetRolePrivilegeData, + } = useGetRole(); + + const { data: DeleteRoleResponse, deleteRoleFun, resetData: resetDeleteRoleData } = useDeleteRole(); + + const { data: UpdatedRoleResponse, putRolePrivilege, resetData: resetUpdatedRoleData } = usePutRole(); + + useEffect(() => { + getRolePrivilege(roleName); + + return () => { + resetGetRolePrivilegeData(); + }; + }, []); + + useEffect(() => { + getRolePrivilege(roleName); + + return () => { + resetUpdatedRoleData(); + }; + }, [UpdatedRoleResponse]); + + const getBadge = (privilege: any, i: number, withAction: boolean) => { + if (privilege.privilege === 'admin' || privilege.privilege === 'editor') { + return ( + + {privilege.privilege} + + ); + } + + if (privilege.privilege === 'reader') { + if (privilege.resource?.tag) { + return ( + + {privilege.privilege} of {privilege.resource?.stream === '*' ? 'All' : privilege.resource?.stream} with{' '} + {privilege.resource?.tag} + + ); + } + return ( + + {privilege.privilege} of {privilege.resource?.stream === '*' ? 'All' : privilege.resource?.stream} + + ); + } + return ( + + {privilege.privilege} of {privilege.resource?.stream === '*' ? 'All' : privilege.resource?.stream} + + ); + }; + + const removeButton = (i: number) => ( + { + openDeletePrivilege(); + setDeletePrivilegeIndex(i); + }}> + + + ); + + const handleClosePrivilegeDelete = () => { + closeDeletePrivilege(); + setUserInput(''); + }; + + const handlePrivilegeDelete = () => { + const newPrivileges = privileges?.filter((privilege: any, i: number) => { + if (i !== deletePrivilegeIndex){ + return privilege; + } + }); + + putRolePrivilege(roleName, newPrivileges); + + handleClosePrivilegeDelete(); + }; + + const getBadges = (userRole: any) => { + if (userRole.length > 0) { + const Badges = userRole.map((privilege: any, i: number) => { + return {getBadge(privilege, i, true)}; + }); + return Badges; + } else { + return ( + + No Role + + ); + } + }; + + const handleDelete = () => { + deleteRoleFun(roleName); + handleCloseDelete(); + }; + + const handleCloseDelete = () => { + closeDeleteRole(); + setUserInput(''); + }; + useEffect(() => { + if (DeleteRoleResponse) { + getRolesList(); + resetDeleteRoleData(); + } + }, [DeleteRoleResponse]); + + const handleCloseUpdateRole = () => { + closeUpdateRole(); + setSelectedPrivilege(''); + setSelectedStream(''); + setStreamSearchValue(''); + setTagInput(''); + }; + + const handleUpadterole = () => { + if (selectedPrivilege === 'admin' || selectedPrivilege === 'editor') { + privileges?.push({ + privilege: selectedPrivilege, + }); + } + if (selectedPrivilege === 'reader' || selectedPrivilege === 'writer') { + if (streams?.find((stream) => stream.name === SelectedStream)) { + if (tagInput !== '' && tagInput !== undefined && selectedPrivilege !== 'writer') { + privileges?.push({ + privilege: selectedPrivilege, + resource: { + stream: SelectedStream, + tag: tagInput, + }, + }); + } else { + privileges?.push({ + privilege: selectedPrivilege, + resource: { + stream: SelectedStream, + }, + }); + } + } + } + putRolePrivilege(roleName, privileges); + handleCloseUpdateRole(); + }; + + const updateRoleVaildtion = () => { + if (selectedPrivilege === 'admin' || selectedPrivilege === 'editor') { + if (privileges?.find((role: any) => role.privilege === selectedPrivilege)) { + return true; + } + return false; + } + if (selectedPrivilege === 'reader') { + if ( + privileges?.find( + (role: any) => + role.privilege === selectedPrivilege && + role.resource?.stream === SelectedStream && + (tagInput + ? role.resource?.tag === tagInput + : role.resource?.tag === null || role.resource?.tag === undefined), + ) + ) { + return true; + } + if ( + streams?.find((stream) => stream.name === SelectedStream) && + SelectedStream !== '' && + SelectedStream !== undefined + ) { + return false; + } + return true; + } + if (selectedPrivilege === 'writer') { + if ( + privileges?.find( + (role: any) => role.privilege === selectedPrivilege && role.resource?.stream === SelectedStream, + ) + ) { + return true; + } + if ( + streams?.find((stream) => stream.name === SelectedStream) && + SelectedStream !== '' && + SelectedStream !== undefined + ) { + return false; + } + return true; + } + return true; + }; + + const { classes } = useUsersStyles(); + return ( + <> + + {roleName} + + {getRolePrivilegeError ? ( + 'Error' + ) : getRolePrivilegeLoading ? ( + 'loading..' + ) : privileges ? ( + <> + {getBadges(privileges)} + + + + + + + ) : ( + Error + )} + + + + + + + + + + + { + setUserInput(e.target.value); + }} + placeholder={`Please enter the Role to confirm, i.e. ${roleName}`} + required + /> + + + + + + + {privileges && privileges[deletePrivilegeIndex] ? ( + + + {getBadge(privileges[deletePrivilegeIndex], deletePrivilegeIndex, false)} + { + setUserInput(e.target.value); + }} + placeholder={`Please enter the role to confirm, i.e. ${roleName}`} + required + /> + + + + + + + + ) : ( + '' + )} + + + { + setSelectedStream(value ?? ''); + }} + nothingFound="No options" + value={SelectedStream} + searchValue={streamSearchValue} + onSearchChange={(value) => setStreamSearchValue(value)} + onDropdownClose={() => setStreamSearchValue(SelectedStream)} + onDropdownOpen={() => setStreamSearchValue('')} + data={streams?.map((stream) => ({ value: stream.name, label: stream.name })) ?? []} + searchable + label="Select a stream to assign" + required + /> + {selectedPrivilege === 'reader' ? ( + { + setTagInput(e.target.value); + }} + /> + ) : ( + '' + )} + + ) : ( + '' + )} + + + + + + + + + ); +}; + +export default PrivilegeTR; diff --git a/src/pages/Users/row.tsx b/src/pages/AccessManagement/RoleTR.tsx similarity index 54% rename from src/pages/Users/row.tsx rename to src/pages/AccessManagement/RoleTR.tsx index f7b42f33..bd96f85b 100644 --- a/src/pages/Users/row.tsx +++ b/src/pages/AccessManagement/RoleTR.tsx @@ -21,28 +21,29 @@ import { FC, useEffect, useState } from 'react'; import { useUsersStyles } from './styles'; import { Prism } from '@mantine/prism'; import { useDeleteUser } from '@/hooks/useDeleteUser'; -import { useGetLogStreamList } from '@/hooks/useGetLogStreamList'; import { usePostUserResetPassword } from '@/hooks/usePostResetPassword'; +import { useGetRoles } from '@/hooks/useGetRoles'; -interface RoleTdProps { - Username: string; +interface RoleTRProps { + user: { + id: string; + method: string; + }; getUsersList: () => void; } -const RoleTd: FC = (props) => { - const { Username, getUsersList } = props; +const RoleTR: FC = (props) => { + const { user, getUsersList } = props; const [openedDelete, { close: closeDelete, open: openDelete }] = useDisclosure(); const [openedDeleteRole, { close: closeDeleteRole, open: openDeleteRole }] = useDisclosure(); const [openedEditModal, { close: closeEditModal, open: openEditModal }] = useDisclosure(); - const [deleteRoleIndex, setDeleteRoleIndex] = useState(0); + const [deleteRole, setDeleteRole] = useState(null); const [opened, { open, close }] = useDisclosure(false); const [UserInput, setUserInput] = useState(''); + const [SelectedRole, setSelectedRole] = useState(''); + const [roleSearchValue, setRoleSearchValue] = useState(''); - const [tagInput, setTagInput] = useState(''); - const [selectedPrivilege, setSelectedPrivilege] = useState(''); - const [SelectedStream, setSelectedStream] = useState(''); - const [streamSearchValue, setStreamSearchValue] = useState(''); const { putRole, data: putRoleData, resetData: resetPutRoleData } = usePutUserRole(); const { @@ -60,7 +61,8 @@ const RoleTd: FC = (props) => { resetData: userRoleReset, } = useGetUserRole(); const { data: deletedUser, deleteUserFun: deleteUserAction, resetData: deletedUserReset } = useDeleteUser(); - const { data: streams } = useGetLogStreamList(); + const { data: roles, getRolesList, resetData: rolesReset } = useGetRoles(); + useEffect(() => { if (deletedUser) { getUsersList(); @@ -69,23 +71,25 @@ const RoleTd: FC = (props) => { }, [deletedUser]); useEffect(() => { - getRoles(Username); + getRoles(user.id); + getRolesList(); return () => { + rolesReset(); userRoleReset(); }; - }, [Username]); + }, [user]); useEffect(() => { if (putRoleData) { - getRoles(Username); + getRoles(user.id); } return () => { resetPutRoleData(); }; }, [putRoleData]); - const removeButton = (i: number) => ( + const removeButton = (role: string) => ( = (props) => { variant="transparent" onClick={() => { openDeleteRole(); - setDeleteRoleIndex(i); + setDeleteRole(role); }}> ); - const getBadge = (role: any, i: number, withAction: boolean) => { - if (role.privilege === 'admin' || role.privilege === 'editor') { - return ( - - {role.privilege} - - ); - } - - if (role.privilege === 'reader') { - if (role.resource?.tag) { - return ( - - {role.privilege} of {role.resource?.stream === '*' ? 'All' : role.resource?.stream} with{' '} - {role.resource?.tag} - - ); - } - return ( - - {role.privilege} of {role.resource?.stream === '*' ? 'All' : role.resource?.stream} - - ); - } + const getBadge = (role: any, withAction: boolean) => { return ( - - {role.privilege} of {role.resource?.stream === '*' ? 'All' : role.resource?.stream} + + {role} ); }; const getBadges = (userRole: any) => { - if (userRole.length > 0) { - const Badges = userRole.map((role: any, i: number) => { - return {getBadge(role, i, true)}; + if (Object.keys(userRole).length > 0) { + const Badges = Object.keys(userRole).map((role: any) => { + return {getBadge(role, user.method === 'native' ? true : false)}; }); return Badges; } else { @@ -150,21 +131,17 @@ const RoleTd: FC = (props) => { setUserInput(''); }; const handleDelete = () => { - deleteUserAction(Username); + deleteUserAction(user.id); closeDelete(); setUserInput(''); }; // For Delete Role const handleRoleDelete = () => { - const updatedRole = userRole?.filter((role: any, i: number) => { - if (i !== deleteRoleIndex) { - return role; - } - }); - putRole(Username, updatedRole); + let filtered = Object.keys(userRole).filter((role) => role !== deleteRole); + putRole(user.id, filtered); closeDeleteRole(); - setDeleteRoleIndex(0); + setDeleteRole(null); }; const handleCloseRoleDelete = () => { closeDeleteRole(); @@ -172,100 +149,27 @@ const RoleTd: FC = (props) => { }; // For Edit Role - const handleRoleEdit = () => { - if (selectedPrivilege === 'admin' || selectedPrivilege === 'editor') { - userRole?.push({ - privilege: selectedPrivilege, - }); - } - if (selectedPrivilege === 'reader' || selectedPrivilege === 'writer') { - if ( - streams?.find((stream) => stream.name === SelectedStream) && - SelectedStream !== '' && - SelectedStream !== undefined - ) { - if (tagInput !== '' && tagInput !== undefined && selectedPrivilege !== 'writer' && tagInput !== null) { - userRole?.push({ - privilege: selectedPrivilege, - resource: { - stream: SelectedStream, - tag: tagInput, - }, - }); - } else { - userRole?.push({ - privilege: selectedPrivilege, - resource: { - stream: SelectedStream, - }, - }); - } - } - } - putRole(Username, userRole); - closeEditModal(); - setUserInput(''); - setSelectedPrivilege(''); - setSelectedStream(''); - setStreamSearchValue(''); - setTagInput(''); - getRoles(Username); - }; const handleCloseRoleEdit = () => { closeEditModal(); - setUserInput(''); - setSelectedPrivilege(''); - setSelectedStream(''); - setStreamSearchValue(''); + setSelectedRole(''); + setRoleSearchValue(''); }; - const updateRoleVaildtion = () => { - if (selectedPrivilege === 'admin' || selectedPrivilege === 'editor') { - if (userRole?.find((role: any) => role.privilege === selectedPrivilege)) { - return true; - } - return false; - } - if (selectedPrivilege === 'reader') { - if ( - userRole?.find( - (role: any) => - role.privilege === selectedPrivilege && - role.resource?.stream === SelectedStream && - (tagInput - ? role.resource?.tag === tagInput - : role.resource?.tag === null || role.resource?.tag === undefined), - ) - ) { - return true; - } - if ( - streams?.find((stream) => stream.name === SelectedStream) && - SelectedStream !== '' && - SelectedStream !== undefined - ) { - return false; - } - return true; - } - if (selectedPrivilege === 'writer') { - if ( - userRole?.find((role: any) => role.privilege === selectedPrivilege && role.resource?.stream === SelectedStream) - ) { - return true; - } - if ( - streams?.find((stream) => stream.name === SelectedStream) && - SelectedStream !== '' && - SelectedStream !== undefined - ) { - return false; - } - return true; + + const handleEditUserRole = () => { + + let userRoleArray: any = Object.keys(userRole); + if (userRoleArray.includes(SelectedRole) || SelectedRole === '') { + return; } - return true; + userRoleArray.push(SelectedRole); + + putRole(user.id, userRoleArray); + handleCloseRoleEdit(); }; + + //for reset password const handleCloseResetPassword = () => { @@ -281,8 +185,8 @@ const RoleTd: FC = (props) => { const { classes } = useUsersStyles(); return ( - - {Username} + + {user.id} {userRoleError ? ( 'Error' @@ -319,13 +223,19 @@ const RoleTd: FC = (props) => { - + @@ -343,7 +253,7 @@ const RoleTd: FC = (props) => { onChange={(e) => { setUserInput(e.target.value); }} - placeholder={`Please enter the Username to confirm, i.e. ${Username}`} + placeholder={`Please enter the user to confirm, i.e. ${user.id}`} required /> @@ -352,7 +262,7 @@ const RoleTd: FC = (props) => { variant="filled" color="gray" className={classes.modalActionBtn} - disabled={UserInput === Username ? false : true} + disabled={UserInput === user.id ? false : true} onClick={handleDelete}> Delete @@ -361,7 +271,7 @@ const RoleTd: FC = (props) => { - {userRole && userRole[deleteRoleIndex] ? ( + {userRole && deleteRole && userRole[deleteRole] ? ( = (props) => { centered className={classes.modalStyle}> - {getBadge(userRole[deleteRoleIndex], deleteRoleIndex, false)} + {getBadge(deleteRole, false)} { setUserInput(e.target.value); }} - placeholder={`Please enter the Username to confirm, i.e. ${Username}`} + placeholder={`Please enter the user to confirm, i.e. ${user.id}`} required /> @@ -388,7 +298,7 @@ const RoleTd: FC = (props) => { variant="filled" color="gray" className={classes.modalActionBtn} - disabled={UserInput === Username ? false : true} + disabled={UserInput === user.id ? false : true} onClick={handleRoleDelete}> Delete @@ -411,7 +321,7 @@ const RoleTd: FC = (props) => { { setUserInput(e.target.value); }} @@ -443,14 +353,16 @@ const RoleTd: FC = (props) => { )} - + ):( + Cannot reset password for this user + )} @@ -464,62 +376,34 @@ const RoleTd: FC = (props) => { className={classes.modalStyle}> { - setSelectedStream(value || ''); - }} - nothingFound="No options" - value={SelectedStream} - searchValue={streamSearchValue} - onSearchChange={(value) => setStreamSearchValue(value)} - onDropdownClose={() => setStreamSearchValue(SelectedStream)} - onDropdownOpen={() => setStreamSearchValue('')} - data={streams?.map((stream) => ({ value: stream.name, label: stream.name })) ?? []} - searchable - required - /> - {selectedPrivilege === 'reader' ? ( - { - setTagInput(e.target.value); - }} - /> - ) : ( - '' - )} - - ) : ( - '' - )} - + + + - + - {tableRows} @@ -166,12 +174,12 @@ const Users: FC = () => { { - setCreateUserInput(e.target.value); + setCreateRoleInput(e.target.value); }} - value={createUserInput} + value={createRoleInput} required /> + + , + ); + } + if (usersLoading ) { + setTableRows( + + + , + ); + } + }, [users, usersError, usersLoading]); + + useEffect(() => { + if (CreatedUserResponse) { + getUsersList(); + } + }, [CreatedUserResponse]); + + const handleClose = () => { + setCreateUserInput(''); + setModalOpen(false); + resetCreateUser(); + setSelectedRole(''); + setRoleSearchValue(''); + }; + + const handleCreateUser = () => { + let userRole: any = []; + if (SelectedRole !== '') { + userRole.push(SelectedRole); + } + createUser(createUserInput, userRole); + }; + + const createVaildtion = () => { + if (users?.includes(createUserInput) || createUserInput.length < 3) { + return true; + } + + if (SelectedRole !== '') { + if (roles?.includes(SelectedRole)) { + return false; + } + return true; + } + return false; + }; + + const { classes } = useUsersStyles(); + return ( + + + + Users + + + + +
Username RoleAccess DeleteReset Password
error
loading
+ + + + + + + + + {tableRows} +
UsernameRoleDeleteReset Password
+
+ + + { + setCreateUserInput(e.target.value); + }} + value={createUserInput} + required + /> + +