Create and delete tokens from web ui
This commit is contained in:
262
moneymgr_web/src/routes/TokensRoute.tsx
Normal file
262
moneymgr_web/src/routes/TokensRoute.tsx
Normal file
@ -0,0 +1,262 @@
|
||||
import DeleteIcon from "@mui/icons-material/DeleteOutlined";
|
||||
import { Alert, AlertTitle, Button } from "@mui/material";
|
||||
import {
|
||||
DataGrid,
|
||||
GridActionsCellItem,
|
||||
GridColDef,
|
||||
GridRowId,
|
||||
} from "@mui/x-data-grid";
|
||||
import React from "react";
|
||||
import { Token, TokenWithSecret, TokensApi } from "../api/TokensApi";
|
||||
import { CreateTokenDialog } from "../dialogs/CreateTokenDialog";
|
||||
import { useAlert } from "../hooks/context_providers/AlertDialogProvider";
|
||||
import { useConfirm } from "../hooks/context_providers/ConfirmDialogProvider";
|
||||
import { useSnackbar } from "../hooks/context_providers/SnackbarProvider";
|
||||
import { AsyncWidget } from "../widgets/AsyncWidget";
|
||||
import { CopyTextChip } from "../widgets/CopyTextChip";
|
||||
import { MoneyMgrWebRouteContainer } from "../widgets/MoneyMgrWebRouteContainer";
|
||||
import { TimeWidget } from "../widgets/TimeWidget";
|
||||
import { QRCodeCanvas } from "qrcode.react";
|
||||
import { APIClient } from "../api/ApiClient";
|
||||
|
||||
export function TokensRoute(): React.ReactElement {
|
||||
const count = React.useRef(0);
|
||||
const [list, setList] = React.useState<Token[] | undefined>();
|
||||
|
||||
const [createdToken, setCreatedToken] = React.useState<
|
||||
TokenWithSecret | undefined
|
||||
>();
|
||||
|
||||
const [openCreateTokenDialog, setOpenCreateTokenDialog] =
|
||||
React.useState(false);
|
||||
|
||||
const load = async () => {
|
||||
setList(await TokensApi.GetList());
|
||||
};
|
||||
|
||||
const reload = () => {
|
||||
count.current += 1;
|
||||
setList(undefined);
|
||||
};
|
||||
|
||||
const onRequestCreateToken = () => {
|
||||
setOpenCreateTokenDialog(true);
|
||||
};
|
||||
|
||||
const closeCreateTokenDialog = () => {
|
||||
setOpenCreateTokenDialog(false);
|
||||
};
|
||||
|
||||
const onCreatedToken = (t: TokenWithSecret) => {
|
||||
setOpenCreateTokenDialog(false);
|
||||
setCreatedToken(t);
|
||||
reload();
|
||||
};
|
||||
|
||||
return (
|
||||
<AsyncWidget
|
||||
loadKey={count.current}
|
||||
ready={list !== undefined}
|
||||
load={load}
|
||||
errMsg="Failed to load API token list!"
|
||||
build={() => (
|
||||
<>
|
||||
<CreateTokenDialog
|
||||
open={openCreateTokenDialog}
|
||||
onClose={closeCreateTokenDialog}
|
||||
onCreated={onCreatedToken}
|
||||
/>
|
||||
|
||||
<TokensRouteInner
|
||||
list={list!}
|
||||
onReload={reload}
|
||||
onRequestCreateToken={onRequestCreateToken}
|
||||
createdToken={createdToken}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function TokensRouteInner(p: {
|
||||
list: Token[];
|
||||
onReload: () => void;
|
||||
onRequestCreateToken: () => void;
|
||||
createdToken?: TokenWithSecret;
|
||||
}): React.ReactElement {
|
||||
const confirm = useConfirm();
|
||||
const alert = useAlert();
|
||||
const snackbar = useSnackbar();
|
||||
|
||||
// Delete a token
|
||||
const handleDeleteClick = (id: GridRowId) => async () => {
|
||||
try {
|
||||
const token = p.list.find((t) => t.id === id)!;
|
||||
if (
|
||||
!(await confirm(
|
||||
`Do you really want to delete the token named '${token.name}' ?`
|
||||
))
|
||||
)
|
||||
return;
|
||||
|
||||
await TokensApi.Delete(token);
|
||||
p.onReload();
|
||||
|
||||
snackbar("The token was successfully deleted!");
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alert("Failed to delete API token!");
|
||||
}
|
||||
};
|
||||
|
||||
const columns: GridColDef[] = [
|
||||
{ field: "id", headerName: "ID", flex: 1 },
|
||||
{
|
||||
field: "name",
|
||||
headerName: "Name",
|
||||
flex: 3,
|
||||
},
|
||||
{
|
||||
field: "ip_net",
|
||||
headerName: "IP restriction",
|
||||
flex: 3,
|
||||
renderCell(params) {
|
||||
return (
|
||||
params.row.ip_net ?? (
|
||||
<span style={{ fontStyle: "italic" }}>Unrestricted</span>
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: "time_create",
|
||||
headerName: "Creation",
|
||||
flex: 3,
|
||||
renderCell(params) {
|
||||
return <TimeWidget time={params.row.time_create} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: "last_used",
|
||||
headerName: "Last usage",
|
||||
flex: 3,
|
||||
renderCell(params) {
|
||||
return <TimeWidget time={params.row.last_used} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: "max_inactivity",
|
||||
headerName: "Max inactivity",
|
||||
flex: 3,
|
||||
renderCell(params) {
|
||||
return <TimeWidget time={params.row.max_inactivity} isDuration />;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: "read_only",
|
||||
headerName: "Read only",
|
||||
flex: 2,
|
||||
type: "boolean",
|
||||
},
|
||||
{
|
||||
field: "right_account",
|
||||
headerName: "Account",
|
||||
flex: 2,
|
||||
type: "boolean",
|
||||
},
|
||||
{
|
||||
field: "right_movement",
|
||||
headerName: "Movement",
|
||||
flex: 2,
|
||||
type: "boolean",
|
||||
},
|
||||
{
|
||||
field: "right_inbox",
|
||||
headerName: "Inbox",
|
||||
flex: 2,
|
||||
type: "boolean",
|
||||
},
|
||||
{
|
||||
field: "right_attachment",
|
||||
headerName: "Attachment",
|
||||
flex: 2,
|
||||
type: "boolean",
|
||||
},
|
||||
{
|
||||
field: "right_auth",
|
||||
headerName: "Auth",
|
||||
flex: 2,
|
||||
type: "boolean",
|
||||
},
|
||||
{
|
||||
field: "actions",
|
||||
type: "actions",
|
||||
headerName: "Actions",
|
||||
flex: 2,
|
||||
cellClassName: "actions",
|
||||
getActions: ({ id }) => {
|
||||
return [
|
||||
<GridActionsCellItem
|
||||
icon={<DeleteIcon />}
|
||||
label="Delete"
|
||||
onClick={handleDeleteClick(id)}
|
||||
color="inherit"
|
||||
/>,
|
||||
];
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<MoneyMgrWebRouteContainer
|
||||
label="API Tokens"
|
||||
actions={<Button onClick={p.onRequestCreateToken}>New</Button>}
|
||||
>
|
||||
{p.createdToken && <CreatedToken token={p.createdToken} />}
|
||||
<DataGrid
|
||||
style={{ flex: "1" }}
|
||||
rows={p.list}
|
||||
columns={columns}
|
||||
autoPageSize
|
||||
getRowId={(c) => c.id}
|
||||
isCellEditable={() => false}
|
||||
isRowSelectable={() => false}
|
||||
/>
|
||||
</MoneyMgrWebRouteContainer>
|
||||
);
|
||||
}
|
||||
|
||||
function CreatedToken(p: { token: TokenWithSecret }): React.ReactElement {
|
||||
return (
|
||||
<Alert severity="success" style={{ margin: "10px" }}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
}}
|
||||
>
|
||||
<div style={{ textAlign: "center", marginRight: "10px" }}>
|
||||
<div style={{ padding: "15px", backgroundColor: "white" }}>
|
||||
<QRCodeCanvas
|
||||
value={`moneymgr://api=${encodeURIComponent(
|
||||
APIClient.backendURL()
|
||||
)}&id=${p.token.id}&secret=${p.token.token}`}
|
||||
/>
|
||||
</div>
|
||||
<br />
|
||||
<em>Mobile App Qr Code</em>
|
||||
</div>
|
||||
<div>
|
||||
<AlertTitle>Token successfully created</AlertTitle>
|
||||
The API token was successfully created. Please note the following
|
||||
information as they won't be available next.
|
||||
<br />
|
||||
Token ID: <CopyTextChip text={p.token.id.toString()} />
|
||||
<br />
|
||||
Token value: <CopyTextChip text={p.token.token} />
|
||||
</div>
|
||||
</div>
|
||||
</Alert>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user