Add API tokens support #9
58
virtweb_frontend/src/dialogs/CreatedTokenDialog.tsx
Normal file
58
virtweb_frontend/src/dialogs/CreatedTokenDialog.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { APITokenURL, CreatedAPIToken } from "../api/TokensApi";
|
||||
import { CopyToClipboard } from "../widgets/CopyToClipboard";
|
||||
import { InlineCode } from "../widgets/InlineCode";
|
||||
|
||||
export function CreatedTokenDialog(p: {
|
||||
createdToken: CreatedAPIToken;
|
||||
}): React.ReactElement {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const close = () => {
|
||||
navigate(APITokenURL(p.createdToken.token));
|
||||
};
|
||||
return (
|
||||
<Dialog open>
|
||||
<DialogTitle>Token successfully created</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography>
|
||||
Your token was successfully created. You need now to copy the private
|
||||
key, as it will be technically impossible to recover it after closing
|
||||
this dialog.
|
||||
</Typography>
|
||||
|
||||
<InfoBlock label="Key ID" value={p.createdToken.token.id} />
|
||||
<InfoBlock label="Key algorithm" value={p.createdToken.priv_key.alg} />
|
||||
<InfoBlock label="Private key" value={p.createdToken.priv_key.priv} />
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={close} color="error">
|
||||
I copied the key, close this dialog
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
function InfoBlock(
|
||||
p: React.PropsWithChildren<{ label: string; value: string }>
|
||||
): React.ReactElement {
|
||||
return (
|
||||
<div
|
||||
style={{ display: "flex", flexDirection: "column", margin: "20px 10px" }}
|
||||
>
|
||||
<Typography variant="overline">{p.label}</Typography>
|
||||
<CopyToClipboard content={p.value}>
|
||||
<InlineCode>{p.value}</InlineCode>
|
||||
</CopyToClipboard>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,7 +1,13 @@
|
||||
import { Button } from "@mui/material";
|
||||
import React from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { APIToken, APITokenURL, TokensApi } from "../api/TokensApi";
|
||||
import {
|
||||
APIToken,
|
||||
APITokenURL,
|
||||
CreatedAPIToken,
|
||||
TokensApi,
|
||||
} from "../api/TokensApi";
|
||||
import { CreatedTokenDialog } from "../dialogs/CreatedTokenDialog";
|
||||
import { useAlert } from "../hooks/providers/AlertDialogProvider";
|
||||
import { useLoadingMessage } from "../hooks/providers/LoadingMessageProvider";
|
||||
import { useSnackbar } from "../hooks/providers/SnackbarProvider";
|
||||
@ -18,6 +24,10 @@ export function CreateApiTokenRoute(): React.ReactElement {
|
||||
const snackbar = useSnackbar();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [createdToken, setCreatedToken] = React.useState<
|
||||
CreatedAPIToken | undefined
|
||||
>();
|
||||
|
||||
const [token] = React.useState<APIToken>({
|
||||
id: "",
|
||||
name: "",
|
||||
@ -32,8 +42,7 @@ export function CreateApiTokenRoute(): React.ReactElement {
|
||||
try {
|
||||
const res = await TokensApi.Create(n);
|
||||
snackbar("The api token was successfully created!");
|
||||
// TODO : show a modal to give token information
|
||||
navigate(APITokenURL(res.token));
|
||||
setCreatedToken(res);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alert(`Failed to create API token!\n${e}`);
|
||||
@ -41,12 +50,16 @@ export function CreateApiTokenRoute(): React.ReactElement {
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{createdToken && <CreatedTokenDialog createdToken={createdToken} />}
|
||||
|
||||
<EditApiTokenRouteInner
|
||||
token={token}
|
||||
creating={true}
|
||||
onCancel={() => navigate("/tokens")}
|
||||
onSave={createApiToken}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
18
virtweb_frontend/src/widgets/InlineCode.tsx
Normal file
18
virtweb_frontend/src/widgets/InlineCode.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
export function InlineCode(p: React.PropsWithChildren): React.ReactElement {
|
||||
return (
|
||||
<code
|
||||
style={{
|
||||
display: "inline-block",
|
||||
backgroundColor: "black",
|
||||
color: "white",
|
||||
wordBreak: "break-all",
|
||||
wordWrap: "break-word",
|
||||
whiteSpace: "pre-wrap",
|
||||
padding: "0px 7px",
|
||||
borderRadius: "5px",
|
||||
}}
|
||||
>
|
||||
{p.children}
|
||||
</code>
|
||||
);
|
||||
}
|
@ -3,6 +3,7 @@ import React, { PropsWithChildren } from "react";
|
||||
import { NetworkHookStatus, ServerApi } from "../../api/ServerApi";
|
||||
import { AsyncWidget } from "../AsyncWidget";
|
||||
import { CopyToClipboard } from "../CopyToClipboard";
|
||||
import { InlineCode } from "../InlineCode";
|
||||
|
||||
export function NetworkHookStatusWidget(p: {
|
||||
hiddenIfInstalled: boolean;
|
||||
@ -72,25 +73,6 @@ function NetworkHookStatusWidgetInner(p: {
|
||||
);
|
||||
}
|
||||
|
||||
function InlineCode(p: PropsWithChildren): React.ReactElement {
|
||||
return (
|
||||
<code
|
||||
style={{
|
||||
display: "inline-block",
|
||||
backgroundColor: "black",
|
||||
color: "white",
|
||||
wordBreak: "break-all",
|
||||
wordWrap: "break-word",
|
||||
whiteSpace: "pre-wrap",
|
||||
padding: "0px 7px",
|
||||
borderRadius: "5px",
|
||||
}}
|
||||
>
|
||||
{p.children}
|
||||
</code>
|
||||
);
|
||||
}
|
||||
|
||||
function CodeBlock(p: PropsWithChildren): React.ReactElement {
|
||||
return (
|
||||
<pre
|
||||
|
Loading…
Reference in New Issue
Block a user