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

@@ -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

@@ -0,0 +1,245 @@
import React, { useState, useEffect, Dispatch, SetStateAction } from 'react'
import axios from 'axios'
import {
Button,
Grid,
DialogContent,
DialogActions,
TextField,
CircularProgress,
Autocomplete
} from '@mui/material'
import { BootstrapDialog } from '../../../../components/modal'
import { BootstrapDialogTitle } from '../../../../components/dialogTitle'
import {
UserResponse,
GroupResponse,
RegisterPermissionPayload
} from '../../../../utils/types'
type AddPermissionModalProps = {
open: boolean
handleOpen: Dispatch<SetStateAction<boolean>>
addPermission: (
permissions: RegisterPermissionPayload[],
permissionType: string,
principalType: string,
principal: string,
permissionSetting: string
) => void
}
const AddPermissionModal = ({
open,
handleOpen,
addPermission
}: AddPermissionModalProps) => {
const [paths, setPaths] = useState<string[]>([])
const [loadingPaths, setLoadingPaths] = useState(false)
const [selectedPaths, setSelectedPaths] = useState<string[]>([])
const [permissionType, setPermissionType] = useState('Route')
const [principalType, setPrincipalType] = useState('Group')
const [userPrincipal, setUserPrincipal] = useState<UserResponse>()
const [groupPrincipal, setGroupPrincipal] = useState<GroupResponse>()
const [permissionSetting, setPermissionSetting] = useState('Grant')
const [loadingPrincipals, setLoadingPrincipals] = useState(false)
const [userPrincipals, setUserPrincipals] = useState<UserResponse[]>([])
const [groupPrincipals, setGroupPrincipals] = useState<GroupResponse[]>([])
useEffect(() => {
setLoadingPaths(true)
axios
.get('/SASjsApi/info/authorizedRoutes')
.then((res: any) => {
if (res.data) {
setPaths(res.data.paths)
}
})
.catch((err) => {
console.log(err)
})
.finally(() => {
setLoadingPaths(false)
})
}, [])
useEffect(() => {
setLoadingPrincipals(true)
axios
.get(`/SASjsApi/${principalType.toLowerCase()}`)
.then((res: any) => {
if (res.data) {
if (principalType.toLowerCase() === 'user') {
const users: UserResponse[] = res.data
const nonAdminUsers = users.filter((user) => !user.isAdmin)
setUserPrincipals(nonAdminUsers)
} else {
setGroupPrincipals(res.data)
}
}
})
.catch((err) => {
console.log(err)
})
.finally(() => {
setLoadingPrincipals(false)
})
}, [principalType])
const handleAddPermission = () => {
const permissions: RegisterPermissionPayload[] = []
selectedPaths.forEach((path) => {
const addPermissionPayload: any = {
path,
type: permissionType,
setting: permissionSetting,
principalType: principalType.toLowerCase(),
principalId:
principalType.toLowerCase() === 'user'
? userPrincipal?.id
: groupPrincipal?.groupId
}
permissions.push(addPermissionPayload)
})
const principal =
principalType.toLowerCase() === 'user'
? userPrincipal?.username
: groupPrincipal?.name
addPermission(
permissions,
permissionType,
principalType,
principal!,
permissionSetting
)
}
const addButtonDisabled =
!selectedPaths.length ||
(principalType.toLowerCase() === 'user' ? !userPrincipal : !groupPrincipal)
return (
<BootstrapDialog onClose={() => handleOpen(false)} open={open}>
<BootstrapDialogTitle
id="add-permission-dialog-title"
handleOpen={handleOpen}
>
Add Permission
</BootstrapDialogTitle>
<DialogContent dividers>
<Grid container spacing={2}>
<Grid item xs={12}>
<Autocomplete
multiple
disableClearable
options={paths}
filterSelectedOptions
value={selectedPaths}
onChange={(event: any, newValue: string[]) => {
setSelectedPaths(newValue)
}}
renderInput={(params) => <TextField {...params} label="Paths" />}
/>
</Grid>
<Grid item xs={12}>
<Autocomplete
options={['Route']}
disableClearable
value={permissionType}
onChange={(event: any, newValue: string) =>
setPermissionType(newValue)
}
renderInput={(params) =>
loadingPaths ? (
<CircularProgress />
) : (
<TextField {...params} label="Permission Type" />
)
}
/>
</Grid>
<Grid item xs={12}>
<Autocomplete
options={['Group', 'User']}
disableClearable
value={principalType}
onChange={(event: any, newValue: string) =>
setPrincipalType(newValue)
}
renderInput={(params) => (
<TextField {...params} label="Principal Type" />
)}
/>
</Grid>
<Grid item xs={12}>
{principalType.toLowerCase() === 'user' ? (
<Autocomplete
options={userPrincipals}
getOptionLabel={(option) => option.displayName}
disableClearable
value={userPrincipal}
onChange={(event: any, newValue: UserResponse) =>
setUserPrincipal(newValue)
}
renderInput={(params) =>
loadingPrincipals ? (
<CircularProgress />
) : (
<TextField {...params} label="Principal" />
)
}
/>
) : (
<Autocomplete
options={groupPrincipals}
getOptionLabel={(option) => option.name}
disableClearable
value={groupPrincipal}
onChange={(event: any, newValue: GroupResponse) =>
setGroupPrincipal(newValue)
}
renderInput={(params) =>
loadingPrincipals ? (
<CircularProgress />
) : (
<TextField {...params} label="Principal" />
)
}
/>
)}
</Grid>
<Grid item xs={12}>
<Autocomplete
options={['Grant', 'Deny']}
disableClearable
value={permissionSetting}
onChange={(event: any, newValue: string) =>
setPermissionSetting(newValue)
}
renderInput={(params) => (
<TextField {...params} label="Settings" />
)}
/>
</Grid>
</Grid>
</DialogContent>
<DialogActions>
<Button
variant="outlined"
onClick={handleAddPermission}
disabled={addButtonDisabled}
>
Add
</Button>
</DialogActions>
</BootstrapDialog>
)
}
export default AddPermissionModal

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

@@ -0,0 +1,154 @@
import React, { Dispatch, SetStateAction } 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 { PermissionResponse } from '../../../../utils/types'
import { BootstrapDialogTitle } from '../../../../components/dialogTitle'
import { PrincipalType } from '../hooks/usePermission'
const BootstrapDialog = styled(Dialog)(({ theme }) => ({
'& .MuiDialogContent-root': {
padding: theme.spacing(2)
},
'& .MuiDialogActions-root': {
padding: theme.spacing(1)
}
}))
type FilterModalProps = {
open: boolean
handleOpen: Dispatch<SetStateAction<boolean>>
permissions: PermissionResponse[]
pathFilter: string[]
setPathFilter: Dispatch<SetStateAction<string[]>>
principalFilter: string[]
setPrincipalFilter: Dispatch<SetStateAction<string[]>>
principalTypeFilter: PrincipalType[]
setPrincipalTypeFilter: Dispatch<SetStateAction<PrincipalType[]>>
settingFilter: string[]
setSettingFilter: Dispatch<SetStateAction<string[]>>
applyFilter: () => void
resetFilter: () => void
}
const PermissionFilterModal = ({
open,
handleOpen,
permissions,
pathFilter,
setPathFilter,
principalFilter,
setPrincipalFilter,
principalTypeFilter,
setPrincipalTypeFilter,
settingFilter,
setSettingFilter,
applyFilter,
resetFilter
}: FilterModalProps) => {
const paths = permissions
.map((permission) => permission.path)
.filter((uri, index, array) => array.indexOf(uri) === index)
// fetch all the principals from permissions array
let principals = permissions.map((permission) => {
if (permission.user) return permission.user.username
if (permission.group) return permission.group.name
return ''
})
// removes empty strings
principals = principals.filter((principal) => principal !== '')
// removes the duplicates
principals = principals.filter(
(principal, index, array) => array.indexOf(principal) === index
)
return (
<BootstrapDialog onClose={() => handleOpen(false)} open={open}>
<BootstrapDialogTitle
id="permission-filter-dialog-title"
handleOpen={handleOpen}
>
Permission Filter
</BootstrapDialogTitle>
<DialogContent dividers>
<Grid container spacing={1}>
<Grid item xs={12}>
<Autocomplete
multiple
options={paths}
filterSelectedOptions
value={pathFilter}
onChange={(event: any, newValue: string[]) => {
setPathFilter(newValue)
}}
renderInput={(params) => <TextField {...params} label="Paths" />}
/>
</Grid>
<Grid item xs={12}>
<Autocomplete
multiple
options={principals}
filterSelectedOptions
value={principalFilter}
onChange={(event: any, newValue: string[]) => {
setPrincipalFilter(newValue)
}}
renderInput={(params) => (
<TextField {...params} label="Principals" />
)}
/>
</Grid>
<Grid item xs={12}>
<Autocomplete
multiple
options={Object.values(PrincipalType)}
filterSelectedOptions
value={principalTypeFilter}
onChange={(event: any, newValue: PrincipalType[]) => {
setPrincipalTypeFilter(newValue)
}}
renderInput={(params) => (
<TextField {...params} label="Principal Type" />
)}
/>
</Grid>
<Grid item xs={12}>
<Autocomplete
multiple
options={['Grant', 'Deny']}
filterSelectedOptions
value={settingFilter}
onChange={(event: any, newValue: string[]) => {
setSettingFilter(newValue)
}}
renderInput={(params) => (
<TextField {...params} label="Settings" />
)}
/>
</Grid>
</Grid>
</DialogContent>
<DialogActions>
<Button variant="outlined" color="error" onClick={resetFilter}>
Reset
</Button>
<Button variant="outlined" onClick={applyFilter}>
Apply
</Button>
</DialogActions>
</BootstrapDialog>
)
}
export default PermissionFilterModal

View File

@@ -0,0 +1,120 @@
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'
export interface PermissionResponsePayload {
permissionType: string
principalType: string
principal: string
permissionSetting: string
existingPermissions: PermissionResponse[]
newAddedPermissions: PermissionResponse[]
updatedPermissions: PermissionResponse[]
errorPaths: string[]
}
type Props = {
open: boolean
setOpen: React.Dispatch<React.SetStateAction<boolean>>
payload: PermissionResponsePayload
}
const PermissionResponseModal = ({ open, setOpen, payload }: Props) => {
const newAddedPermissionsLength = payload.newAddedPermissions.length
const updatedPermissionsLength = payload.updatedPermissions.length
const existingPermissionsLength = payload.existingPermissions.length
const appliedPermissionsLength =
newAddedPermissionsLength + updatedPermissionsLength
return (
<div>
<BootstrapDialog onClose={() => setOpen(false)} open={open}>
<BootstrapDialogTitle
id="permission-response-modal"
handleOpen={setOpen}
>
Permission Response
</BootstrapDialogTitle>
<DialogContent dividers>
<Typography sx={{ fontWeight: 'bold', marginBottom: '15px' }}>
{`${appliedPermissionsLength} "${payload.permissionSetting}", "${
payload.permissionType
}", "${payload.principalType}", "${payload.principal}" ${
appliedPermissionsLength > 1 ? 'Rules' : 'Rule'
}`}{' '}
Applied:
</Typography>
{newAddedPermissionsLength > 0 && (
<>
<Typography>
{`${newAddedPermissionsLength} ${
newAddedPermissionsLength > 1 ? 'Rules' : 'Rule'
}`}{' '}
Added:
</Typography>
<ul>
{payload.newAddedPermissions.map((permission, index) => (
<li key={index}>{permission.path}</li>
))}
</ul>
</>
)}
{updatedPermissionsLength > 0 && (
<>
<Typography>
{` ${updatedPermissionsLength} ${
updatedPermissionsLength > 1 ? 'Rules' : 'Rule'
}`}{' '}
Updated:
</Typography>
<ul>
{payload.updatedPermissions.map((permission, index) => (
<li key={index}>{permission.path}</li>
))}
</ul>
</>
)}
{existingPermissionsLength > 0 && (
<>
<Typography>
{`${existingPermissionsLength} ${
existingPermissionsLength > 1 ? 'Rules' : 'Rule'
}`}{' '}
Unchanged:
</Typography>
<ul>
{payload.existingPermissions.map((permission, index) => (
<li key={index}>{permission.path}</li>
))}
</ul>
</>
)}
{payload.errorPaths.length > 0 && (
<>
<Typography style={{ color: 'red', marginTop: '10px' }}>
Errors occurred for following paths:
</Typography>
<ul>
{payload.errorPaths.map((path, index) => (
<li key={index}>
<Typography>{path}</Typography>
</li>
))}
</ul>
</>
)}
</DialogContent>
</BootstrapDialog>
</div>
)
}
export default PermissionResponseModal

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

@@ -0,0 +1,75 @@
import React, { useState, Dispatch, SetStateAction, useEffect } from 'react'
import {
Button,
Grid,
DialogContent,
DialogActions,
TextField
} from '@mui/material'
import Autocomplete from '@mui/material/Autocomplete'
import { BootstrapDialog } from '../../../../components/modal'
import { BootstrapDialogTitle } from '../../../../components/dialogTitle'
import { PermissionResponse } from '../../../../utils/types'
type UpdatePermissionModalProps = {
open: boolean
handleOpen: Dispatch<SetStateAction<boolean>>
permission: PermissionResponse | undefined
updatePermission: (setting: string) => void
}
const UpdatePermissionModal = ({
open,
handleOpen,
permission,
updatePermission
}: UpdatePermissionModalProps) => {
const [permissionSetting, setPermissionSetting] = useState('Grant')
useEffect(() => {
if (permission) setPermissionSetting(permission.setting)
}, [permission])
return (
<BootstrapDialog onClose={() => handleOpen(false)} open={open}>
<BootstrapDialogTitle
id="add-permission-dialog-title"
handleOpen={handleOpen}
>
Update Permission
</BootstrapDialogTitle>
<DialogContent dividers>
<Grid container spacing={2}>
<Grid item xs={12}>
<Autocomplete
sx={{ width: 300 }}
options={['Grant', 'Deny']}
disableClearable
value={permissionSetting}
onChange={(event: any, newValue: string) =>
setPermissionSetting(newValue)
}
renderInput={(params) => (
<TextField {...params} label="Settings" />
)}
/>
</Grid>
</Grid>
</DialogContent>
<DialogActions>
<Button
variant="outlined"
onClick={() => updatePermission(permissionSetting)}
disabled={permission?.setting === permissionSetting}
>
Update
</Button>
</DialogActions>
</BootstrapDialog>
)
}
export default UpdatePermissionModal