import * as React from "react"; import { useContext, useEffect, useState } from "react"; import { Alert, CardActions, CardContent, Chip, FormControl, Select, Table, TableBody, TableCell, TableHead, TableRow, Tooltip, Typography, Container, Card, Button, Dialog, DialogTitle, DialogContent, TextField, IconButton, MenuItem, DialogContentText, useMediaQuery, useTheme, Stack, CircularProgress, Box, } from "@mui/material"; import EditIcon from "@mui/icons-material/Edit"; import { useTranslation } from "react-i18next"; import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; import AddIcon from "@mui/icons-material/Add"; import CloseIcon from "@mui/icons-material/Close"; import LockIcon from "@mui/icons-material/Lock"; import routes from "./routes"; import { AccountContext } from "./App"; import DialogFooter from "./DialogFooter"; import { Paragraph } from "./styles"; import { UnauthorizedError } from "../app/errors"; import session from "../app/Session"; import adminApi from "../app/AdminApi"; import { Role } from "../app/AccountApi"; const Admin = () => { const { account } = useContext(AccountContext); // Redirect non-admins away if (!session.exists() || (account && account.role !== Role.ADMIN)) { window.location.href = routes.app; return null; } // Wait for account to load if (!account) { return ( ); } return ( ); }; const Users = () => { const { t } = useTranslation(); const [users, setUsers] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); const [addDialogKey, setAddDialogKey] = useState(0); const [addDialogOpen, setAddDialogOpen] = useState(false); const loadUsers = async () => { try { setLoading(true); const data = await adminApi.getUsers(); setUsers(data); setError(""); } catch (e) { console.log(`[Admin] Error loading users`, e); if (e instanceof UnauthorizedError) { await session.resetAndRedirect(routes.login); } else { setError(e.message); } } finally { setLoading(false); } }; useEffect(() => { loadUsers(); }, []); const handleAddClick = () => { setAddDialogKey((prev) => prev + 1); setAddDialogOpen(true); }; const handleDialogClose = () => { setAddDialogOpen(false); loadUsers(); }; return ( {t("admin_users_title")} {t("admin_users_description")} {error && ( {error} )} {loading && ( )} {!loading && users && (
)}
); }; const UsersTable = (props) => { const { t } = useTranslation(); const [editDialogKey, setEditDialogKey] = useState(0); const [editDialogOpen, setEditDialogOpen] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [accessDialogKey, setAccessDialogKey] = useState(0); const [accessDialogOpen, setAccessDialogOpen] = useState(false); const [deleteAccessDialogOpen, setDeleteAccessDialogOpen] = useState(false); const [selectedUser, setSelectedUser] = useState(null); const [selectedGrant, setSelectedGrant] = useState(null); const { users } = props; const handleEditClick = (user) => { setEditDialogKey((prev) => prev + 1); setSelectedUser(user); setEditDialogOpen(true); }; const handleDeleteClick = (user) => { setSelectedUser(user); setDeleteDialogOpen(true); }; const handleAddAccessClick = (user) => { setAccessDialogKey((prev) => prev + 1); setSelectedUser(user); setAccessDialogOpen(true); }; const handleDeleteAccessClick = (user, grant) => { setSelectedUser(user); setSelectedGrant(grant); setDeleteAccessDialogOpen(true); }; const handleDialogClose = () => { setEditDialogOpen(false); setDeleteDialogOpen(false); setAccessDialogOpen(false); setDeleteAccessDialogOpen(false); setSelectedUser(null); setSelectedGrant(null); props.onUserChanged(); }; return ( <> {t("admin_users_table_username_header")} {t("admin_users_table_role_header")} {t("admin_users_table_tier_header")} {t("admin_users_table_grants_header")} {t("admin_users_table_actions_header")} {users.map((user) => ( {user.username} {user.provisioned && ( )} {user.tier || "-"} {user.grants && user.grants.length > 0 ? ( {user.grants.map((grant, idx) => { const canDelete = user.role !== "admin" && !grant.provisioned; const tooltipText = grant.provisioned ? t("admin_users_table_grant_provisioned_tooltip", { permission: grant.permission }) : t("admin_users_table_grant_tooltip", { permission: grant.permission }); return ( : undefined} onDelete={canDelete ? () => handleDeleteAccessClick(user, grant) : undefined} /> ); })} ) : ( "-" )} {user.role !== "admin" && !user.provisioned ? ( <> handleAddAccessClick(user)} size="small"> handleEditClick(user)} size="small"> handleDeleteClick(user)} size="small"> ) : user.role !== "admin" && user.provisioned ? ( <> handleAddAccessClick(user)} size="small"> ) : ( )} ))}
); }; const RoleChip = ({ role }) => { const { t } = useTranslation(); if (role === "admin") { return ; } return ; }; const AddUserDialog = (props) => { const theme = useTheme(); const { t } = useTranslation(); const [error, setError] = useState(""); const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [tier, setTier] = useState(""); const fullScreen = useMediaQuery(theme.breakpoints.down("sm")); const handleSubmit = async () => { try { await adminApi.addUser(username, password, tier || undefined); props.onClose(); } catch (e) { console.log(`[Admin] Error adding user`, e); if (e instanceof UnauthorizedError) { await session.resetAndRedirect(routes.login); } else { setError(e.message); } } }; return ( {t("admin_users_add_dialog_title")} setUsername(ev.target.value)} fullWidth variant="standard" autoFocus /> setPassword(ev.target.value)} fullWidth variant="standard" /> setTier(ev.target.value)} fullWidth variant="standard" helperText={t("admin_users_add_dialog_tier_helper")} /> ); }; const EditUserDialog = (props) => { const theme = useTheme(); const { t } = useTranslation(); const [error, setError] = useState(""); const [password, setPassword] = useState(""); const [tier, setTier] = useState(props.user?.tier || ""); const fullScreen = useMediaQuery(theme.breakpoints.down("sm")); const handleSubmit = async () => { try { await adminApi.updateUser(props.user.username, password || undefined, tier || undefined); props.onClose(); } catch (e) { console.log(`[Admin] Error updating user`, e); if (e instanceof UnauthorizedError) { await session.resetAndRedirect(routes.login); } else { setError(e.message); } } }; if (!props.user) { return null; } return ( {t("admin_users_edit_dialog_title", { username: props.user.username })} setPassword(ev.target.value)} fullWidth variant="standard" helperText={t("admin_users_edit_dialog_password_helper")} /> setTier(ev.target.value)} fullWidth variant="standard" helperText={t("admin_users_edit_dialog_tier_helper")} /> ); }; const DeleteUserDialog = (props) => { const { t } = useTranslation(); const [error, setError] = useState(""); const handleSubmit = async () => { try { await adminApi.deleteUser(props.user.username); props.onClose(); } catch (e) { console.log(`[Admin] Error deleting user`, e); if (e instanceof UnauthorizedError) { await session.resetAndRedirect(routes.login); } else { setError(e.message); } } }; if (!props.user) { return null; } return ( {t("admin_users_delete_dialog_title")} {t("admin_users_delete_dialog_description", { username: props.user.username })} ); }; const AddAccessDialog = (props) => { const theme = useTheme(); const { t } = useTranslation(); const [error, setError] = useState(""); const [topic, setTopic] = useState(""); const [permission, setPermission] = useState("read-write"); const fullScreen = useMediaQuery(theme.breakpoints.down("sm")); const handleSubmit = async () => { try { await adminApi.allowAccess(props.user.username, topic, permission); props.onClose(); } catch (e) { console.log(`[Admin] Error adding access`, e); if (e instanceof UnauthorizedError) { await session.resetAndRedirect(routes.login); } else { setError(e.message); } } }; if (!props.user) { return null; } return ( {t("admin_access_add_dialog_title", { username: props.user.username })} setTopic(ev.target.value)} fullWidth variant="standard" autoFocus helperText={t("admin_access_add_dialog_topic_helper")} /> ); }; const DeleteAccessDialog = (props) => { const { t } = useTranslation(); const [error, setError] = useState(""); const handleSubmit = async () => { try { await adminApi.resetAccess(props.user.username, props.grant.topic); props.onClose(); } catch (e) { console.log(`[Admin] Error removing access`, e); if (e instanceof UnauthorizedError) { await session.resetAndRedirect(routes.login); } else { setError(e.message); } } }; if (!props.user || !props.grant) { return null; } return ( {t("admin_access_delete_dialog_title")} {t("admin_access_delete_dialog_description", { username: props.user.username, topic: props.grant.topic })} ); }; export default Admin;