Files
EMS-ESP32/interface/src/framework/security/ManageUsersForm.tsx
2022-06-25 14:25:51 +02:00

220 lines
6.5 KiB
TypeScript

import { FC, useContext, useState } from 'react';
import { Button, IconButton, Box } from '@mui/material';
import SaveIcon from '@mui/icons-material/Save';
import DeleteIcon from '@mui/icons-material/Delete';
import PersonAddIcon from '@mui/icons-material/PersonAdd';
import EditIcon from '@mui/icons-material/Edit';
import CheckIcon from '@mui/icons-material/Check';
import CloseIcon from '@mui/icons-material/Close';
import VpnKeyIcon from '@mui/icons-material/VpnKey';
import { Table } from '@table-library/react-table-library/table';
import { useTheme } from '@table-library/react-table-library/theme';
import { Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
import * as SecurityApi from '../../api/security';
import { SecuritySettings, User } from '../../types';
import { ButtonRow, FormLoader, MessageBox, SectionContent } from '../../components';
import { createUserValidator } from '../../validators';
import { useRest } from '../../utils';
import { AuthenticatedContext } from '../../contexts/authentication';
import GenerateToken from './GenerateToken';
import UserForm from './UserForm';
const ManageUsersForm: FC = () => {
const { loadData, saving, data, setData, saveData, errorMessage } = useRest<SecuritySettings>({
read: SecurityApi.readSecuritySettings,
update: SecurityApi.updateSecuritySettings
});
const [user, setUser] = useState<User>();
const [creating, setCreating] = useState<boolean>(false);
const [generatingToken, setGeneratingToken] = useState<string>();
const authenticatedContext = useContext(AuthenticatedContext);
const table_theme = useTheme({
Table: `
--data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) 90px 120px;
`,
BaseRow: `
font-size: 14px;
`,
HeaderRow: `
text-transform: uppercase;
background-color: black;
color: #90CAF9;
.th {
padding: 8px;
height: 42px;
font-weight: 500;
border-bottom: 1px solid #565656;
}
`,
Row: `
.td {
padding: 8px;
border-top: 1px solid #565656;
border-bottom: 1px solid #565656;
}
&:nth-of-type(odd) .td {
background-color: #303030;
}
&:nth-of-type(even) .td {
background-color: #1e1e1e;
}
`,
BaseCell: `
&:nth-of-type(2) {
text-align: center;
}
&:last-of-type {
text-align: right;
}
`
});
const content = () => {
if (!data) {
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
}
const noAdminConfigured = () => !data.users.find((u) => u.admin);
const removeUser = (toRemove: User) => {
const users = data.users.filter((u) => u.username !== toRemove.username);
setData({ ...data, users });
};
const createUser = () => {
setCreating(true);
setUser({
username: '',
password: '',
admin: true
});
};
const editUser = (toEdit: User) => {
setCreating(false);
setUser({ ...toEdit });
};
const cancelEditingUser = () => {
setUser(undefined);
};
const doneEditingUser = () => {
if (user) {
const users = [...data.users.filter((u) => u.username !== user.username), user];
setData({ ...data, users });
setUser(undefined);
}
};
const closeGenerateToken = () => {
setGeneratingToken(undefined);
};
const generateToken = (username: string) => {
setGeneratingToken(username);
};
const onSubmit = async () => {
await saveData();
authenticatedContext.refresh();
};
const user_table = data.users.map((u) => ({ ...u, id: u.username }));
return (
<>
<Table data={{ nodes: user_table }} theme={table_theme} layout={{ custom: true }}>
{(tableList: any) => (
<>
<Header>
<HeaderRow>
<HeaderCell resize>USERNAME</HeaderCell>
<HeaderCell stiff>IS ADMIN</HeaderCell>
<HeaderCell stiff />
</HeaderRow>
</Header>
<Body>
{tableList.map((u: any) => (
<Row key={u.id} item={u}>
<Cell>{u.username}</Cell>
<Cell stiff>{u.admin ? <CheckIcon /> : <CloseIcon />}</Cell>
<Cell stiff>
<IconButton
size="small"
disabled={!authenticatedContext.me.admin}
aria-label="Generate Token"
onClick={() => generateToken(u.username)}
>
<VpnKeyIcon />
</IconButton>
<IconButton size="small" aria-label="Delete" onClick={() => removeUser(u)}>
<DeleteIcon />
</IconButton>
<IconButton size="small" aria-label="Edit" onClick={() => editUser(u)}>
<EditIcon />
</IconButton>
</Cell>
</Row>
))}
</Body>
</>
)}
</Table>
{noAdminConfigured() && (
<MessageBox level="warning" message="You must have at least one admin user configured" my={2} />
)}
<Box display="flex" flexWrap="wrap">
<Box flexGrow={1} sx={{ '& button': { mt: 2 } }}>
<Button
startIcon={<SaveIcon />}
disabled={saving || noAdminConfigured()}
variant="outlined"
color="primary"
type="submit"
onClick={onSubmit}
>
Save
</Button>
</Box>
<Box flexWrap="nowrap" whiteSpace="nowrap">
<ButtonRow>
<Button startIcon={<PersonAddIcon />} variant="outlined" color="secondary" onClick={createUser}>
Add
</Button>
</ButtonRow>
</Box>
</Box>
<GenerateToken username={generatingToken} onClose={closeGenerateToken} />
<UserForm
user={user}
setUser={setUser}
creating={creating}
onDoneEditing={doneEditingUser}
onCancelEditing={cancelEditingUser}
validator={createUserValidator(data.users, creating)}
/>
</>
);
};
return (
<SectionContent title="Manage Users" titleGutter>
{content()}
</SectionContent>
);
};
export default ManageUsersForm;