client-web/src/components/EditAcl.tsx
import React, { useEffect, useState } from "react";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import Paper from "@mui/material/Paper";
import { Button, Checkbox, Typography } from "@mui/material";
import {
getUserAcl,
IAclBody,
IUserAcl,
TPermission,
updateUserAcl,
} from "../http";
import { Box } from "@mui/system";
import { FullPageSpinner } from "./FullPageSpinner";
import { useStoreState } from "../store";
export interface IEditAcl {
userId: string;
updateData?(user: IUserAcl): void;
}
const label = { inputProps: { "aria-label": "Checkbox" } };
type TKeys =
| "appCreate"
| "appPush"
| "appSettings"
| "appStats"
| "appTokens"
| "appUsers";
const checkDisabled = (arr: Array<string> | undefined, property: string) => {
if (!arr) return false;
return !!arr.find((item) => item === property);
};
const checkAdminEnabled = (acl: TPermission) => {
if (!acl) return false;
return !!Object.entries(acl).find((item) => item[0] === "admin" && !!item[1]);
};
const Row = ({
name,
row,
onChange,
disableAllRow,
}: {
name: TKeys;
row: TPermission;
disableAllRow: boolean;
onChange: (
e: React.ChangeEvent<HTMLInputElement>,
keyToChange: TKeys
) => void;
}) => {
const isOwner = useStoreState((state) => state.user?.ACL.ownerAccess);
return (
<TableRow
sx={{
"&:last-child td, &:last-child th": { border: 0 },
}}
>
<TableCell component="th" scope="row">
{name}
</TableCell>
<TableCell
style={{
backgroundColor: !checkDisabled(row?.disabled, "create")
? "white"
: "lightgrey",
}}
align="left"
>
<Checkbox
checked={row?.create}
name={"create"}
disabled={
(checkDisabled(row?.disabled, "create") || disableAllRow) &&
!isOwner
}
onChange={(e) => onChange(e, name)}
{...label}
/>
</TableCell>
<TableCell
style={{
backgroundColor: !checkDisabled(row?.disabled, "read")
? "white"
: "lightgrey",
}}
align="left"
>
<Checkbox
name={"read"}
onChange={(e) => onChange(e, name)}
disabled={checkDisabled(row?.disabled, "read") || disableAllRow}
checked={row?.read}
{...label}
/>
</TableCell>
<TableCell
style={{
backgroundColor: !checkDisabled(row?.disabled, "update")
? "white"
: "lightgrey",
}}
align="left"
>
<Checkbox
name={"update"}
onChange={(e) => onChange(e, name)}
disabled={checkDisabled(row?.disabled, "update") || disableAllRow}
checked={row?.update}
{...label}
/>
</TableCell>
<TableCell
style={{
backgroundColor: !checkDisabled(row?.disabled, "delete")
? "white"
: "lightgrey",
}}
align="left"
>
<Checkbox
name={"delete"}
onChange={(e) => onChange(e, name)}
disabled={checkDisabled(row?.disabled, "delete") || disableAllRow}
checked={row?.delete}
{...label}
/>
</TableCell>
<TableCell
style={{
backgroundColor: !checkDisabled(row?.disabled, "admin")
? "white"
: "lightgrey",
}}
align="left"
>
<Checkbox
name={"admin"}
onChange={(e) => onChange(e, name)}
disabled={checkDisabled(row?.disabled, "admin") || disableAllRow}
checked={row?.admin}
{...label}
/>
</TableCell>
</TableRow>
);
};
export const EditAcl: React.FC<IEditAcl> = ({ userId, updateData }) => {
const [userAcl, setUserAcl] = useState<IUserAcl>();
const [userAclApplicationKeys, setUserAclApplicationKeys] = useState<
Array<TKeys>
>([]);
const [userAclNetworkKeys, setUserAclNetworkKeys] = useState<Array<TKeys>>(
[]
);
const myAcl = useStoreState((state) => state.ACL);
const [loading, setLoading] = useState(false);
useEffect(() => {
console.log("userAclApplicationKeys ", userAclApplicationKeys);
}, [userAclApplicationKeys]);
const getAcl = async () => {
setLoading(true);
try {
const { data } = await getUserAcl(userId);
console.log("getAcl ", data);
setUserAcl(data);
const appKeys = Object.keys(data.result.application) as TKeys[];
const networkKeys = Object.keys(data.result.network) as TKeys[];
setUserAclApplicationKeys(appKeys);
setUserAclNetworkKeys(networkKeys);
} catch (error) {
console.log(error);
}
setLoading(false);
};
const onApplicationAclChange = (
e: React.ChangeEvent<HTMLInputElement>,
keyToChange: TKeys
) => {
setUserAcl((prev) => ({
result: {
...prev!.result,
application: {
...prev!.result!.application,
[keyToChange]: {
...prev!.result!.application[keyToChange],
[e.target.name]: e.target.checked,
},
},
},
}));
};
const onNetworkAclChange = (
e: React.ChangeEvent<HTMLInputElement>,
keyToChange: TKeys
) => {
setUserAcl((prev) => ({
result: {
...prev!.result,
network: {
...prev!.result!.network,
netStats: {
...prev!.result!.network.netStats,
[e.target.name]: e.target.checked,
},
},
},
}));
};
const updateAcl = async () => {
setLoading(true);
try {
const filteredApplication = Object.fromEntries(
Object.entries(userAcl!.result!.application).map((item) => {
delete item[1].disabled;
return item;
})
);
const filteredNetwork = Object.fromEntries(
Object.entries(userAcl!.result!.network).map((item) => {
delete item[1].disabled;
return item;
})
);
const body = {
application: filteredApplication,
network: filteredNetwork,
} as IAclBody;
const aclRes = await updateUserAcl(userId, body);
const updatedUserAcl = aclRes.data as IUserAcl;
if (updateData) {
updateData(updatedUserAcl);
}
} catch (error) {
console.log(error);
}
setLoading(false);
};
useEffect(() => {
getAcl();
}, []);
if (loading) {
return <FullPageSpinner />;
}
return (
<>
<Typography marginBottom={"10px"} fontWeight={"bold"}>
Applications
</Typography>
<TableContainer component={Paper}>
<Table size={"small"} sx={{ minWidth: 650 }} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Name</TableCell>
<TableCell>Create</TableCell>
<TableCell align="left">Read</TableCell>
<TableCell align="left">Update</TableCell>
<TableCell align="left">Delete</TableCell>
<TableCell align="left">Admin</TableCell>
</TableRow>
</TableHead>
<TableBody>
{myAcl.result &&
userAclApplicationKeys.map((row) => {
const application = userAcl!.result.application[row];
const myApplicationAcl = myAcl!.result.application[row];
return (
<Row
disableAllRow={!checkAdminEnabled(myApplicationAcl)}
// disableAllRow={false}
onChange={onApplicationAclChange}
name={row}
row={application}
key={row}
/>
);
})}
</TableBody>
</Table>
</TableContainer>
<Typography marginBottom={"10px"} marginTop={"10px"} fontWeight={"bold"}>
Networks
</Typography>
<TableContainer component={Paper}>
<Table size={"small"} sx={{ minWidth: 650 }} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Name</TableCell>
<TableCell>Create</TableCell>
<TableCell align="left">Read</TableCell>
<TableCell align="left">Update</TableCell>
<TableCell align="left">Delete</TableCell>
<TableCell align="left">Admin</TableCell>
</TableRow>
</TableHead>
<TableBody>
{userAclNetworkKeys.map((row) => {
const network = userAcl!.result.network.netStats;
const myNetworkAcl = myAcl!.result.network.netStats;
return (
<Row
disableAllRow={!checkAdminEnabled(myNetworkAcl)}
// disableAllRow={false}
onChange={onNetworkAclChange}
name={row}
row={network}
key={row}
/>
);
})}
</TableBody>
</Table>
</TableContainer>
<Box
sx={{
display: "flex",
width: "100%",
justifyContent: "flex-end",
marginTop: "20px",
}}
>
<Button variant="contained" onClick={updateAcl}>
Update Acl
</Button>
</Box>
</>
);
};