1
0
mirror of https://github.com/sasjs/server.git synced 2026-01-09 23:40:06 +00:00

chore(web): refactor react code

This commit is contained in:
2022-09-08 21:49:35 +05:00
parent d6651bbdbe
commit 4560ef942f
26 changed files with 1534 additions and 1127 deletions

View File

@@ -9,6 +9,7 @@ import Permission from './permission'
import Profile from './profile'
import { AppContext, ModeType } from '../../context/appContext'
import PermissionsContextProvider from '../../context/permissionsContext'
const StyledTab = styled(Tab)({
background: 'black',
@@ -64,7 +65,9 @@ const Settings = () => {
<Profile />
</StyledTabpanel>
<StyledTabpanel value="permission">
<Permission />
<PermissionsContextProvider>
<Permission />
</PermissionsContextProvider>
</StyledTabpanel>
</TabContext>
</Box>

View File

@@ -0,0 +1,40 @@
import React from 'react'
import { IconButton, Tooltip } from '@mui/material'
import { Add } from '@mui/icons-material'
import { RegisterPermissionPayload } from '../../../../utils/types'
import AddPermissionModal from './addPermissionModal'
type Props = {
openModal: boolean
setOpenModal: React.Dispatch<React.SetStateAction<boolean>>
addPermission: (
permissionsToAdd: RegisterPermissionPayload[],
permissionType: string,
principalType: string,
principal: string,
permissionSetting: string
) => Promise<void>
}
const AddPermission = ({ openModal, setOpenModal, addPermission }: Props) => {
return (
<>
<Tooltip
sx={{ marginLeft: 'auto' }}
title="Add Permission"
placement="bottom-end"
>
<IconButton onClick={() => setOpenModal(true)}>
<Add />
</IconButton>
</Tooltip>
<AddPermissionModal
open={openModal}
handleOpen={setOpenModal}
addPermission={addPermission}
/>
</>
)
}
export default AddPermission

View File

@@ -3,31 +3,21 @@ import axios from 'axios'
import {
Button,
Grid,
Dialog,
DialogContent,
DialogActions,
TextField,
CircularProgress,
Autocomplete
} from '@mui/material'
import { styled } from '@mui/material/styles'
import { BootstrapDialogTitle } from '../../components/dialogTitle'
import { BootstrapDialog } from '../../../../components/modal'
import { BootstrapDialogTitle } from '../../../../components/dialogTitle'
import {
UserResponse,
GroupResponse,
RegisterPermissionPayload
} from '../../utils/types'
const BootstrapDialog = styled(Dialog)(({ theme }) => ({
'& .MuiDialogContent-root': {
padding: theme.spacing(2)
},
'& .MuiDialogActions-root': {
padding: theme.spacing(1)
}
}))
} from '../../../../utils/types'
type AddPermissionModalProps = {
open: boolean

View File

@@ -0,0 +1,63 @@
import { useState } from 'react'
import { Typography, Popover } from '@mui/material'
import { GroupDetailsResponse } from '../../../../utils/types'
type DisplayGroupProps = {
group: GroupDetailsResponse
}
const DisplayGroup = ({ group }: DisplayGroupProps) => {
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null)
const handlePopoverOpen = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget)
}
const handlePopoverClose = () => {
setAnchorEl(null)
}
const open = Boolean(anchorEl)
return (
<div>
<Typography
aria-owns={open ? 'mouse-over-popover' : undefined}
aria-haspopup="true"
onMouseEnter={handlePopoverOpen}
onMouseLeave={handlePopoverClose}
>
{group.name}
</Typography>
<Popover
id="mouse-over-popover"
sx={{
pointerEvents: 'none'
}}
open={open}
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left'
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left'
}}
onClose={handlePopoverClose}
disableRestoreFocus
>
<Typography sx={{ p: 1 }} variant="h6" component="div">
Group Members
</Typography>
{group.users.map((user, index) => (
<Typography key={index} sx={{ p: 1 }} component="li">
{user.username}
</Typography>
))}
</Popover>
</div>
)
}
export default DisplayGroup

View File

@@ -0,0 +1,72 @@
import React, { Dispatch, SetStateAction, useState } from 'react'
import { IconButton, Tooltip } from '@mui/material'
import { FilterList } from '@mui/icons-material'
import { PermissionResponse } from '../../../../utils/types'
import PermissionFilterModal from './permissionFilterModal'
import { PrincipalType } from '../hooks/usePermission'
type Props = {
open: boolean
handleOpen: Dispatch<SetStateAction<boolean>>
permissions: PermissionResponse[]
applyFilter: (
pathFilter: string[],
principalFilter: string[],
principalTypeFilter: PrincipalType[],
settingFilter: string[]
) => void
resetFilter: () => void
}
const FilterPermissions = ({
open,
handleOpen,
permissions,
applyFilter,
resetFilter
}: Props) => {
const [pathFilter, setPathFilter] = useState<string[]>([])
const [principalFilter, setPrincipalFilter] = useState<string[]>([])
const [principalTypeFilter, setPrincipalTypeFilter] = useState<
PrincipalType[]
>([])
const [settingFilter, setSettingFilter] = useState<string[]>([])
const handleApplyFilter = () => {
applyFilter(pathFilter, principalFilter, principalTypeFilter, settingFilter)
}
const handleResetFilter = () => {
setPathFilter([])
setPrincipalFilter([])
setPrincipalFilter([])
setSettingFilter([])
resetFilter()
}
return (
<>
<Tooltip title="Filter Permissions">
<IconButton onClick={() => handleOpen(true)}>
<FilterList />
</IconButton>
</Tooltip>
<PermissionFilterModal
open={open}
handleOpen={handleOpen}
permissions={permissions}
pathFilter={pathFilter}
setPathFilter={setPathFilter}
principalFilter={principalFilter}
setPrincipalFilter={setPrincipalFilter}
principalTypeFilter={principalTypeFilter}
setPrincipalTypeFilter={setPrincipalTypeFilter}
settingFilter={settingFilter}
setSettingFilter={setSettingFilter}
applyFilter={handleApplyFilter}
resetFilter={handleResetFilter}
/>
</>
)
}
export default FilterPermissions

View File

@@ -10,9 +10,9 @@ import {
import { styled } from '@mui/material/styles'
import Autocomplete from '@mui/material/Autocomplete'
import { PermissionResponse } from '../../utils/types'
import { BootstrapDialogTitle } from '../../components/dialogTitle'
import { PrincipalType } from './permission'
import { PermissionResponse } from '../../../../utils/types'
import { BootstrapDialogTitle } from '../../../../components/dialogTitle'
import { PrincipalType } from '../hooks/usePermission'
const BootstrapDialog = styled(Dialog)(({ theme }) => ({
'& .MuiDialogContent-root': {

View File

@@ -2,9 +2,9 @@ import React from 'react'
import { Typography, DialogContent } from '@mui/material'
import { BootstrapDialog } from '../../components/modal'
import { BootstrapDialogTitle } from '../../components/dialogTitle'
import { PermissionResponse } from '../../utils/types'
import { BootstrapDialog } from '../../../../components/modal'
import { BootstrapDialogTitle } from '../../../../components/dialogTitle'
import { PermissionResponse } from '../../../../utils/types'
export interface PermissionResponsePayload {
permissionType: string

View File

@@ -0,0 +1,101 @@
import { useContext } from 'react'
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
IconButton,
Tooltip
} from '@mui/material'
import EditIcon from '@mui/icons-material/Edit'
import DeleteForeverIcon from '@mui/icons-material/DeleteForever'
import { styled } from '@mui/material/styles'
import { PermissionResponse } from '../../../../utils/types'
import { AppContext } from '../../../../context/appContext'
import { displayPrincipal, displayPrincipalType } from '../helper'
const BootstrapTableCell = styled(TableCell)({
textAlign: 'left'
})
export enum PrincipalType {
User = 'User',
Group = 'Group'
}
type PermissionTableProps = {
permissions: PermissionResponse[]
handleUpdatePermissionClick: (permission: PermissionResponse) => void
handleDeletePermissionClick: (permission: PermissionResponse) => void
}
const PermissionTable = ({
permissions,
handleUpdatePermissionClick,
handleDeletePermissionClick
}: PermissionTableProps) => {
const appContext = useContext(AppContext)
return (
<TableContainer component={Paper}>
<Table sx={{ minWidth: 650 }}>
<TableHead sx={{ background: 'rgb(0,0,0, 0.3)' }}>
<TableRow>
<BootstrapTableCell>Path</BootstrapTableCell>
<BootstrapTableCell>Permission Type</BootstrapTableCell>
<BootstrapTableCell>Principal</BootstrapTableCell>
<BootstrapTableCell>Principal Type</BootstrapTableCell>
<BootstrapTableCell>Setting</BootstrapTableCell>
{appContext.isAdmin && (
<BootstrapTableCell>Action</BootstrapTableCell>
)}
</TableRow>
</TableHead>
<TableBody>
{permissions.map((permission) => (
<TableRow key={permission.permissionId}>
<BootstrapTableCell>{permission.path}</BootstrapTableCell>
<BootstrapTableCell>{permission.type}</BootstrapTableCell>
<BootstrapTableCell>
{displayPrincipal(permission)}
</BootstrapTableCell>
<BootstrapTableCell>
{displayPrincipalType(permission)}
</BootstrapTableCell>
<BootstrapTableCell>{permission.setting}</BootstrapTableCell>
{appContext.isAdmin && (
<BootstrapTableCell>
<Tooltip title="Edit Permission">
<IconButton
onClick={() => handleUpdatePermissionClick(permission)}
>
<EditIcon />
</IconButton>
</Tooltip>
<Tooltip title="Delete Permission">
<IconButton
color="error"
onClick={() => handleDeletePermissionClick(permission)}
>
<DeleteForeverIcon />
</IconButton>
</Tooltip>
</BootstrapTableCell>
)}
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)
}
export default PermissionTable

View File

@@ -2,26 +2,17 @@ import React, { useState, Dispatch, SetStateAction, useEffect } from 'react'
import {
Button,
Grid,
Dialog,
DialogContent,
DialogActions,
TextField
} from '@mui/material'
import { styled } from '@mui/material/styles'
import Autocomplete from '@mui/material/Autocomplete'
import { BootstrapDialogTitle } from '../../components/dialogTitle'
import { BootstrapDialog } from '../../../../components/modal'
import { BootstrapDialogTitle } from '../../../../components/dialogTitle'
import { PermissionResponse } from '../../utils/types'
const BootstrapDialog = styled(Dialog)(({ theme }) => ({
'& .MuiDialogContent-root': {
padding: theme.spacing(2)
},
'& .MuiDialogActions-root': {
padding: theme.spacing(1)
}
}))
import { PermissionResponse } from '../../../../utils/types'
type UpdatePermissionModalProps = {
open: boolean

View File

@@ -0,0 +1,13 @@
import { PermissionResponse } from '../../../utils/types'
import { PrincipalType } from './hooks/usePermission'
import DisplayGroup from './components/displayGroup'
export const displayPrincipal = (permission: PermissionResponse) => {
if (permission.user) return permission.user.username
if (permission.group) return <DisplayGroup group={permission.group} />
}
export const displayPrincipalType = (permission: PermissionResponse) => {
if (permission.user) return PrincipalType.User
if (permission.group) return PrincipalType.Group
}

View File

@@ -0,0 +1,109 @@
import axios from 'axios'
import { useState, useContext } from 'react'
import {
PermissionResponse,
RegisterPermissionPayload
} from '../../../../utils/types'
import AddPermission from '../components/addPermission'
import { PermissionsContext } from '../../../../context/permissionsContext'
import {
findExistingPermission,
findUpdatingPermission
} from '../../../../utils/helper'
const useAddPermission = () => {
const {
permissions,
fetchPermissions,
setIsLoading,
setPermissionResponsePayload,
setOpenPermissionResponseModal
} = useContext(PermissionsContext)
const [addPermissionModalOpen, setAddPermissionModalOpen] = useState(false)
const addPermission = async (
permissionsToAdd: RegisterPermissionPayload[],
permissionType: string,
principalType: string,
principal: string,
permissionSetting: string
) => {
setAddPermissionModalOpen(false)
setIsLoading(true)
const newAddedPermissions: PermissionResponse[] = []
const updatedPermissions: PermissionResponse[] = []
const errorPaths: string[] = []
const existingPermissions: PermissionResponse[] = []
const updatingPermissions: PermissionResponse[] = []
const newPermissions: RegisterPermissionPayload[] = []
permissionsToAdd.forEach((permission) => {
const existingPermission = findExistingPermission(permissions, permission)
if (existingPermission) {
existingPermissions.push(existingPermission)
return
}
const updatingPermission = findUpdatingPermission(permissions, permission)
if (updatingPermission) {
updatingPermissions.push(updatingPermission)
return
}
newPermissions.push(permission)
})
for (const permission of newPermissions) {
await axios
.post('/SASjsApi/permission', permission)
.then((res) => {
newAddedPermissions.push(res.data)
})
.catch((error) => {
errorPaths.push(permission.path)
})
}
for (const permission of updatingPermissions) {
await axios
.patch(`/SASjsApi/permission/${permission.permissionId}`, {
setting: permission.setting === 'Grant' ? 'Deny' : 'Grant'
})
.then((res) => {
updatedPermissions.push(res.data)
})
.catch((error) => {
errorPaths.push(permission.path)
})
}
fetchPermissions()
setIsLoading(false)
setPermissionResponsePayload({
permissionType,
principalType,
principal,
permissionSetting,
existingPermissions,
updatedPermissions,
newAddedPermissions,
errorPaths
})
setOpenPermissionResponseModal(true)
}
const AddPermissionButton = () => (
<AddPermission
openModal={addPermissionModalOpen}
setOpenModal={setAddPermissionModalOpen}
addPermission={addPermission}
/>
)
return { AddPermissionButton, setAddPermissionModalOpen }
}
export default useAddPermission

View File

@@ -0,0 +1,61 @@
import axios from 'axios'
import { useState, useContext } from 'react'
import { PermissionsContext } from '../../../../context/permissionsContext'
import { AlertSeverityType } from '../../../../components/snackbar'
import DeleteConfirmationModal from '../../../../components/deleteConfirmationModal'
const useDeletePermissionModal = () => {
const {
selectedPermission,
setSelectedPermission,
fetchPermissions,
setIsLoading,
setSnackbarMessage,
setSnackbarSeverity,
setOpenSnackbar,
setModalTitle,
setModalPayload,
setOpenModal
} = useContext(PermissionsContext)
const [deleteConfirmationModalOpen, setDeleteConfirmationModalOpen] =
useState(false)
const deletePermission = () => {
setDeleteConfirmationModalOpen(false)
setIsLoading(true)
axios
.delete(`/SASjsApi/permission/${selectedPermission?.permissionId}`)
.then((res: any) => {
fetchPermissions()
setSnackbarMessage('Permission deleted!')
setSnackbarSeverity(AlertSeverityType.Success)
setOpenSnackbar(true)
})
.catch((err) => {
setModalTitle('Abort')
setModalPayload(
typeof err.response.data === 'object'
? JSON.stringify(err.response.data)
: err.response.data
)
setOpenModal(true)
})
.finally(() => {
setIsLoading(false)
setSelectedPermission(undefined)
})
}
const DeletePermissionDialog = () => (
<DeleteConfirmationModal
open={deleteConfirmationModalOpen}
setOpen={setDeleteConfirmationModalOpen}
message="Are you sure you want to delete this permission?"
_delete={deletePermission}
/>
)
return { DeletePermissionDialog, setDeleteConfirmationModalOpen }
}
export default useDeletePermissionModal

View File

@@ -0,0 +1,105 @@
import { useState, useContext } from 'react'
import { PermissionsContext } from '../../../../context/permissionsContext'
import { PrincipalType } from './usePermission'
import FilterPermissions from '../components/filterPermissions'
const useFilterPermissions = () => {
const { permissions, setFilteredPermissions, setFilterApplied } =
useContext(PermissionsContext)
const [filterModalOpen, setFilterModalOpen] = useState(false)
/**
* first find the permissions w.r.t each filter type
* take intersection of resultant arrays
*/
const applyFilter = (
pathFilter: string[],
principalFilter: string[],
principalTypeFilter: PrincipalType[],
settingFilter: string[]
) => {
setFilterModalOpen(false)
const uriFilteredPermissions =
pathFilter.length > 0
? permissions.filter((permission) =>
pathFilter.includes(permission.path)
)
: permissions
const principalFilteredPermissions =
principalFilter.length > 0
? permissions.filter((permission) => {
if (permission.user) {
return principalFilter.includes(permission.user.username)
}
if (permission.group) {
return principalFilter.includes(permission.group.name)
}
return false
})
: permissions
const principalTypeFilteredPermissions =
principalTypeFilter.length > 0
? permissions.filter((permission) => {
if (permission.user) {
return principalTypeFilter.includes(PrincipalType.User)
}
if (permission.group) {
return principalTypeFilter.includes(PrincipalType.Group)
}
return false
})
: permissions
const settingFilteredPermissions =
settingFilter.length > 0
? permissions.filter((permission) =>
settingFilter.includes(permission.setting)
)
: permissions
let filteredArray = uriFilteredPermissions.filter((permission) =>
principalFilteredPermissions.some(
(item) => item.permissionId === permission.permissionId
)
)
filteredArray = filteredArray.filter((permission) =>
principalTypeFilteredPermissions.some(
(item) => item.permissionId === permission.permissionId
)
)
filteredArray = filteredArray.filter((permission) =>
settingFilteredPermissions.some(
(item) => item.permissionId === permission.permissionId
)
)
setFilteredPermissions(filteredArray)
setFilterApplied(true)
}
const resetFilter = () => {
setFilterModalOpen(false)
setFilterApplied(false)
setFilteredPermissions([])
}
const FilterPermissionsButton = () => (
<FilterPermissions
open={filterModalOpen}
handleOpen={setFilterModalOpen}
permissions={permissions}
applyFilter={applyFilter}
resetFilter={resetFilter}
/>
)
return { FilterPermissionsButton }
}
export default useFilterPermissions

View File

@@ -0,0 +1,71 @@
import { useContext, useEffect } from 'react'
import { AppContext } from '../../../../context/appContext'
import { PermissionsContext } from '../../../../context/permissionsContext'
import { PermissionResponse } from '../../../../utils/types'
import useAddPermission from './useAddPermission'
import useUpdatePermissionModal from './useUpdatePermissionModal'
import useDeletePermissionModal from './useDeletePermissionModal'
import useFilterPermissions from './useFilterPermissions'
export enum PrincipalType {
User = 'User',
Group = 'Group'
}
const usePermission = () => {
const { isAdmin } = useContext(AppContext)
const {
filterApplied,
filteredPermissions,
isLoading,
permissions,
Dialog,
Snackbar,
PermissionResponseDialog,
fetchPermissions,
setSelectedPermission
} = useContext(PermissionsContext)
const { AddPermissionButton } = useAddPermission()
const { UpdatePermissionDialog, setUpdatePermissionModalOpen } =
useUpdatePermissionModal()
const { DeletePermissionDialog, setDeleteConfirmationModalOpen } =
useDeletePermissionModal()
const { FilterPermissionsButton } = useFilterPermissions()
useEffect(() => {
if (fetchPermissions) fetchPermissions()
}, [fetchPermissions])
const handleUpdatePermissionClick = (permission: PermissionResponse) => {
setSelectedPermission(permission)
setUpdatePermissionModalOpen(true)
}
const handleDeletePermissionClick = (permission: PermissionResponse) => {
setSelectedPermission(permission)
setDeleteConfirmationModalOpen(true)
}
return {
filterApplied,
filteredPermissions,
isAdmin,
isLoading,
permissions,
AddPermissionButton,
UpdatePermissionDialog,
DeletePermissionDialog,
FilterPermissionsButton,
handleDeletePermissionClick,
handleUpdatePermissionClick,
PermissionResponseDialog,
Dialog,
Snackbar
}
}
export default usePermission

View File

@@ -0,0 +1,36 @@
import { useState } from 'react'
import PermissionResponseModal, {
PermissionResponsePayload
} from '../components/permissionResponseModal'
const usePermissionResponseModal = () => {
const [openPermissionResponseModal, setOpenPermissionResponseModal] =
useState(false)
const [permissionResponsePayload, setPermissionResponsePayload] =
useState<PermissionResponsePayload>({
permissionType: '',
principalType: '',
principal: '',
permissionSetting: '',
existingPermissions: [],
newAddedPermissions: [],
updatedPermissions: [],
errorPaths: []
})
const PermissionResponseDialog = () => (
<PermissionResponseModal
open={openPermissionResponseModal}
setOpen={setOpenPermissionResponseModal}
payload={permissionResponsePayload}
/>
)
return {
PermissionResponseDialog,
setOpenPermissionResponseModal,
setPermissionResponsePayload
}
}
export default usePermissionResponseModal

View File

@@ -0,0 +1,63 @@
import axios from 'axios'
import { useState, useContext } from 'react'
import UpdatePermissionModal from '../components/updatePermissionModal'
import { PermissionsContext } from '../../../../context/permissionsContext'
import { AlertSeverityType } from '../../../../components/snackbar'
const useUpdatePermissionModal = () => {
const {
selectedPermission,
setSelectedPermission,
fetchPermissions,
setIsLoading,
setSnackbarMessage,
setSnackbarSeverity,
setOpenSnackbar,
setModalTitle,
setModalPayload,
setOpenModal
} = useContext(PermissionsContext)
const [updatePermissionModalOpen, setUpdatePermissionModalOpen] =
useState(false)
const updatePermission = (setting: string) => {
setUpdatePermissionModalOpen(false)
setIsLoading(true)
axios
.patch(`/SASjsApi/permission/${selectedPermission?.permissionId}`, {
setting
})
.then((res: any) => {
fetchPermissions()
setSnackbarMessage('Permission updated!')
setSnackbarSeverity(AlertSeverityType.Success)
setOpenSnackbar(true)
})
.catch((err) => {
setModalTitle('Abort')
setModalPayload(
typeof err.response.data === 'object'
? JSON.stringify(err.response.data)
: err.response.data
)
setOpenModal(true)
})
.finally(() => {
setIsLoading(false)
setSelectedPermission(undefined)
})
}
const UpdatePermissionDialog = () => (
<UpdatePermissionModal
open={updatePermissionModalOpen}
handleOpen={setUpdatePermissionModalOpen}
permission={selectedPermission}
updatePermission={updatePermission}
/>
)
return { UpdatePermissionDialog, setUpdatePermissionModalOpen }
}
export default useUpdatePermissionModal

View File

@@ -1,54 +1,7 @@
import React, { useState, useEffect, useContext, useCallback } from 'react'
import axios from 'axios'
import {
Box,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
Grid,
CircularProgress,
IconButton,
Tooltip,
Typography,
Popover
} from '@mui/material'
import FilterListIcon from '@mui/icons-material/FilterList'
import AddIcon from '@mui/icons-material/Add'
import EditIcon from '@mui/icons-material/Edit'
import DeleteForeverIcon from '@mui/icons-material/DeleteForever'
import { Box, Paper, Grid, CircularProgress } from '@mui/material'
import { styled } from '@mui/material/styles'
import Modal from '../../components/modal'
import PermissionFilterModal from './permissionFilterModal'
import AddPermissionModal from './addPermissionModal'
import PermissionResponseModal, {
PermissionResponsePayload
} from './addPermissionResponseModal'
import UpdatePermissionModal from './updatePermissionModal'
import DeleteConfirmationModal from '../../components/deleteConfirmationModal'
import BootstrapSnackbar, { AlertSeverityType } from '../../components/snackbar'
import {
GroupDetailsResponse,
PermissionResponse,
RegisterPermissionPayload
} from '../../utils/types'
import {
findExistingPermission,
findUpdatingPermission
} from '../../utils/helper'
import { AppContext } from '../../context/appContext'
const BootstrapTableCell = styled(TableCell)({
textAlign: 'left'
})
import PermissionTable from './internal/components/permissionTable'
import usePermission from './internal/hooks/usePermission'
const BootstrapGridItem = styled(Grid)({
'&.MuiGrid-item': {
@@ -56,298 +9,23 @@ const BootstrapGridItem = styled(Grid)({
}
})
export enum PrincipalType {
User = 'User',
Group = 'Group'
}
const Permission = () => {
const appContext = useContext(AppContext)
const [isLoading, setIsLoading] = useState(false)
const [openModal, setOpenModal] = useState(false)
const [modalTitle, setModalTitle] = useState('')
const [modalPayload, setModalPayload] = useState('')
const [openSnackbar, setOpenSnackbar] = useState(false)
const [snackbarMessage, setSnackbarMessage] = useState('')
const [snackbarSeverity, setSnackbarSeverity] = useState<AlertSeverityType>(
AlertSeverityType.Success
)
const [addPermissionModalOpen, setAddPermissionModalOpen] = useState(false)
const [openPermissionResponseModal, setOpenPermissionResponseModal] =
useState(false)
const [permissionResponsePayload, setPermissionResponsePayload] =
useState<PermissionResponsePayload>({
permissionType: '',
principalType: '',
principal: '',
permissionSetting: '',
existingPermissions: [],
newAddedPermissions: [],
updatedPermissions: [],
errorPaths: []
})
const [updatePermissionModalOpen, setUpdatePermissionModalOpen] =
useState(false)
const [deleteConfirmationModalOpen, setDeleteConfirmationModalOpen] =
useState(false)
const [deleteConfirmationModalMessage, setDeleteConfirmationModalMessage] =
useState('')
const [selectedPermission, setSelectedPermission] =
useState<PermissionResponse>()
const [filterModalOpen, setFilterModalOpen] = useState(false)
const [pathFilter, setPathFilter] = useState<string[]>([])
const [principalFilter, setPrincipalFilter] = useState<string[]>([])
const [principalTypeFilter, setPrincipalTypeFilter] = useState<
PrincipalType[]
>([])
const [settingFilter, setSettingFilter] = useState<string[]>([])
const [permissions, setPermissions] = useState<PermissionResponse[]>([])
const [filteredPermissions, setFilteredPermissions] = useState<
PermissionResponse[]
>([])
const [filterApplied, setFilterApplied] = useState(false)
const fetchPermissions = useCallback(() => {
axios
.get(`/SASjsApi/permission`)
.then((res: any) => {
if (res.data?.length > 0) {
setPermissions(res.data)
}
})
.catch((err) => {
setModalTitle('Abort')
setModalPayload(
typeof err.response.data === 'object'
? JSON.stringify(err.response.data)
: err.response.data
)
setOpenModal(true)
})
}, [])
useEffect(() => {
fetchPermissions()
}, [fetchPermissions])
/**
* first find the permissions w.r.t each filter type
* take intersection of resultant arrays
*/
const applyFilter = () => {
setFilterModalOpen(false)
const uriFilteredPermissions =
pathFilter.length > 0
? permissions.filter((permission) =>
pathFilter.includes(permission.path)
)
: permissions
const principalFilteredPermissions =
principalFilter.length > 0
? permissions.filter((permission) => {
if (permission.user) {
return principalFilter.includes(permission.user.username)
}
if (permission.group) {
return principalFilter.includes(permission.group.name)
}
return false
})
: permissions
const principalTypeFilteredPermissions =
principalTypeFilter.length > 0
? permissions.filter((permission) => {
if (permission.user) {
return principalTypeFilter.includes(PrincipalType.User)
}
if (permission.group) {
return principalTypeFilter.includes(PrincipalType.Group)
}
return false
})
: permissions
const settingFilteredPermissions =
settingFilter.length > 0
? permissions.filter((permission) =>
settingFilter.includes(permission.setting)
)
: permissions
let filteredArray = uriFilteredPermissions.filter((permission) =>
principalFilteredPermissions.some(
(item) => item.permissionId === permission.permissionId
)
)
filteredArray = filteredArray.filter((permission) =>
principalTypeFilteredPermissions.some(
(item) => item.permissionId === permission.permissionId
)
)
filteredArray = filteredArray.filter((permission) =>
settingFilteredPermissions.some(
(item) => item.permissionId === permission.permissionId
)
)
setFilteredPermissions(filteredArray)
setFilterApplied(true)
}
const resetFilter = () => {
setFilterModalOpen(false)
setPathFilter([])
setPrincipalFilter([])
setSettingFilter([])
setFilteredPermissions([])
setFilterApplied(false)
}
const addPermission = async (
permissionsToAdd: RegisterPermissionPayload[],
permissionType: string,
principalType: string,
principal: string,
permissionSetting: string
) => {
setAddPermissionModalOpen(false)
setIsLoading(true)
const newAddedPermissions: PermissionResponse[] = []
const updatedPermissions: PermissionResponse[] = []
const errorPaths: string[] = []
const existingPermissions: PermissionResponse[] = []
const updatingPermissions: PermissionResponse[] = []
const newPermissions: RegisterPermissionPayload[] = []
permissionsToAdd.forEach((permission) => {
const existingPermission = findExistingPermission(permissions, permission)
if (existingPermission) {
existingPermissions.push(existingPermission)
return
}
const updatingPermission = findUpdatingPermission(permissions, permission)
if (updatingPermission) {
updatingPermissions.push(updatingPermission)
return
}
newPermissions.push(permission)
})
for (const permission of newPermissions) {
await axios
.post('/SASjsApi/permission', permission)
.then((res) => {
newAddedPermissions.push(res.data)
})
.catch((error) => {
errorPaths.push(permission.path)
})
}
for (const permission of updatingPermissions) {
await axios
.patch(`/SASjsApi/permission/${permission.permissionId}`, {
setting: permission.setting === 'Grant' ? 'Deny' : 'Grant'
})
.then((res) => {
updatedPermissions.push(res.data)
})
.catch((error) => {
errorPaths.push(permission.path)
})
}
fetchPermissions()
setIsLoading(false)
setPermissionResponsePayload({
permissionType,
principalType,
principal,
permissionSetting,
existingPermissions,
updatedPermissions,
newAddedPermissions,
errorPaths
})
setOpenPermissionResponseModal(true)
}
const handleUpdatePermissionClick = (permission: PermissionResponse) => {
setSelectedPermission(permission)
setUpdatePermissionModalOpen(true)
}
const updatePermission = (setting: string) => {
setUpdatePermissionModalOpen(false)
setIsLoading(true)
axios
.patch(`/SASjsApi/permission/${selectedPermission?.permissionId}`, {
setting
})
.then((res: any) => {
fetchPermissions()
setSnackbarMessage('Permission updated!')
setSnackbarSeverity(AlertSeverityType.Success)
setOpenSnackbar(true)
})
.catch((err) => {
setModalTitle('Abort')
setModalPayload(
typeof err.response.data === 'object'
? JSON.stringify(err.response.data)
: err.response.data
)
setOpenModal(true)
})
.finally(() => {
setIsLoading(false)
setSelectedPermission(undefined)
})
}
const handleDeletePermissionClick = (permission: PermissionResponse) => {
setSelectedPermission(permission)
setDeleteConfirmationModalOpen(true)
setDeleteConfirmationModalMessage(
'Are you sure you want to delete this permission?'
)
}
const deletePermission = () => {
setDeleteConfirmationModalOpen(false)
setIsLoading(true)
axios
.delete(`/SASjsApi/permission/${selectedPermission?.permissionId}`)
.then((res: any) => {
fetchPermissions()
setSnackbarMessage('Permission deleted!')
setSnackbarSeverity(AlertSeverityType.Success)
setOpenSnackbar(true)
})
.catch((err) => {
setModalTitle('Abort')
setModalPayload(
typeof err.response.data === 'object'
? JSON.stringify(err.response.data)
: err.response.data
)
setOpenModal(true)
})
.finally(() => {
setIsLoading(false)
setSelectedPermission(undefined)
})
}
const {
filterApplied,
filteredPermissions,
isAdmin,
isLoading,
permissions,
AddPermissionButton,
UpdatePermissionDialog,
DeletePermissionDialog,
FilterPermissionsButton,
handleDeletePermissionClick,
handleUpdatePermissionClick,
PermissionResponseDialog,
Dialog,
Snackbar
} = usePermission()
return isLoading ? (
<CircularProgress
@@ -358,22 +36,8 @@ const Permission = () => {
<Grid container direction="column" spacing={1}>
<BootstrapGridItem item xs={12}>
<Paper elevation={3} sx={{ display: 'flex' }}>
<Tooltip title="Filter Permissions">
<IconButton onClick={() => setFilterModalOpen(true)}>
<FilterListIcon />
</IconButton>
</Tooltip>
{appContext.isAdmin && (
<Tooltip
sx={{ marginLeft: 'auto' }}
title="Add Permission"
placement="bottom-end"
>
<IconButton onClick={() => setAddPermissionModalOpen(true)}>
<AddIcon />
</IconButton>
</Tooltip>
)}
<FilterPermissionsButton />
{isAdmin && <AddPermissionButton />}
</Paper>
</BootstrapGridItem>
<BootstrapGridItem item xs={12}>
@@ -384,192 +48,13 @@ const Permission = () => {
/>
</BootstrapGridItem>
</Grid>
<BootstrapSnackbar
open={openSnackbar}
setOpen={setOpenSnackbar}
message={snackbarMessage}
severity={snackbarSeverity}
/>
<Modal
open={openModal}
setOpen={setOpenModal}
title={modalTitle}
payload={modalPayload}
/>
<PermissionFilterModal
open={filterModalOpen}
handleOpen={setFilterModalOpen}
permissions={permissions}
pathFilter={pathFilter}
setPathFilter={setPathFilter}
principalFilter={principalFilter}
setPrincipalFilter={setPrincipalFilter}
principalTypeFilter={principalTypeFilter}
setPrincipalTypeFilter={setPrincipalTypeFilter}
settingFilter={settingFilter}
setSettingFilter={setSettingFilter}
applyFilter={applyFilter}
resetFilter={resetFilter}
/>
<AddPermissionModal
open={addPermissionModalOpen}
handleOpen={setAddPermissionModalOpen}
addPermission={addPermission}
/>
<PermissionResponseModal
open={openPermissionResponseModal}
setOpen={setOpenPermissionResponseModal}
payload={permissionResponsePayload}
/>
<UpdatePermissionModal
open={updatePermissionModalOpen}
handleOpen={setUpdatePermissionModalOpen}
permission={selectedPermission}
updatePermission={updatePermission}
/>
<DeleteConfirmationModal
open={deleteConfirmationModalOpen}
setOpen={setDeleteConfirmationModalOpen}
message={deleteConfirmationModalMessage}
_delete={deletePermission}
/>
<PermissionResponseDialog />
<UpdatePermissionDialog />
<DeletePermissionDialog />
<Dialog />
<Snackbar />
</Box>
)
}
export default Permission
type PermissionTableProps = {
permissions: PermissionResponse[]
handleUpdatePermissionClick: (permission: PermissionResponse) => void
handleDeletePermissionClick: (permission: PermissionResponse) => void
}
const PermissionTable = ({
permissions,
handleUpdatePermissionClick,
handleDeletePermissionClick
}: PermissionTableProps) => {
const appContext = useContext(AppContext)
return (
<TableContainer component={Paper}>
<Table sx={{ minWidth: 650 }}>
<TableHead sx={{ background: 'rgb(0,0,0, 0.3)' }}>
<TableRow>
<BootstrapTableCell>Path</BootstrapTableCell>
<BootstrapTableCell>Permission Type</BootstrapTableCell>
<BootstrapTableCell>Principal</BootstrapTableCell>
<BootstrapTableCell>Principal Type</BootstrapTableCell>
<BootstrapTableCell>Setting</BootstrapTableCell>
{appContext.isAdmin && (
<BootstrapTableCell>Action</BootstrapTableCell>
)}
</TableRow>
</TableHead>
<TableBody>
{permissions.map((permission) => (
<TableRow key={permission.permissionId}>
<BootstrapTableCell>{permission.path}</BootstrapTableCell>
<BootstrapTableCell>{permission.type}</BootstrapTableCell>
<BootstrapTableCell>
{displayPrincipal(permission)}
</BootstrapTableCell>
<BootstrapTableCell>
{displayPrincipalType(permission)}
</BootstrapTableCell>
<BootstrapTableCell>{permission.setting}</BootstrapTableCell>
{appContext.isAdmin && (
<BootstrapTableCell>
<Tooltip title="Edit Permission">
<IconButton
onClick={() => handleUpdatePermissionClick(permission)}
>
<EditIcon />
</IconButton>
</Tooltip>
<Tooltip title="Delete Permission">
<IconButton
color="error"
onClick={() => handleDeletePermissionClick(permission)}
>
<DeleteForeverIcon />
</IconButton>
</Tooltip>
</BootstrapTableCell>
)}
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)
}
const displayPrincipal = (permission: PermissionResponse) => {
if (permission.user) return permission.user.username
if (permission.group) return <DisplayGroup group={permission.group} />
}
type DisplayGroupProps = {
group: GroupDetailsResponse
}
const DisplayGroup = ({ group }: DisplayGroupProps) => {
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null)
const handlePopoverOpen = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget)
}
const handlePopoverClose = () => {
setAnchorEl(null)
}
const open = Boolean(anchorEl)
return (
<div>
<Typography
aria-owns={open ? 'mouse-over-popover' : undefined}
aria-haspopup="true"
onMouseEnter={handlePopoverOpen}
onMouseLeave={handlePopoverClose}
>
{group.name}
</Typography>
<Popover
id="mouse-over-popover"
sx={{
pointerEvents: 'none'
}}
open={open}
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left'
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left'
}}
onClose={handlePopoverClose}
disableRestoreFocus
>
<Typography sx={{ p: 1 }} variant="h6" component="div">
Group Members
</Typography>
{group.users.map((user, index) => (
<Typography key={index} sx={{ p: 1 }} component="li">
{user.username}
</Typography>
))}
</Popover>
</div>
)
}
const displayPrincipalType = (permission: PermissionResponse) => {
if (permission.user) return PrincipalType.User
if (permission.group) return PrincipalType.Group
}