Add API tokens support #9

Merged
pierre merged 40 commits from api into master 2024-04-23 17:04:45 +00:00
4 changed files with 99 additions and 28 deletions
Showing only changes of commit 5e134ffba6 - Show all commits

View 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>
);
}

View File

@ -1,7 +1,13 @@
import { Button } from "@mui/material"; import { Button } from "@mui/material";
import React from "react"; import React from "react";
import { useNavigate, useParams } from "react-router-dom"; 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 { useAlert } from "../hooks/providers/AlertDialogProvider";
import { useLoadingMessage } from "../hooks/providers/LoadingMessageProvider"; import { useLoadingMessage } from "../hooks/providers/LoadingMessageProvider";
import { useSnackbar } from "../hooks/providers/SnackbarProvider"; import { useSnackbar } from "../hooks/providers/SnackbarProvider";
@ -18,6 +24,10 @@ export function CreateApiTokenRoute(): React.ReactElement {
const snackbar = useSnackbar(); const snackbar = useSnackbar();
const navigate = useNavigate(); const navigate = useNavigate();
const [createdToken, setCreatedToken] = React.useState<
CreatedAPIToken | undefined
>();
const [token] = React.useState<APIToken>({ const [token] = React.useState<APIToken>({
id: "", id: "",
name: "", name: "",
@ -32,8 +42,7 @@ export function CreateApiTokenRoute(): React.ReactElement {
try { try {
const res = await TokensApi.Create(n); const res = await TokensApi.Create(n);
snackbar("The api token was successfully created!"); snackbar("The api token was successfully created!");
// TODO : show a modal to give token information setCreatedToken(res);
navigate(APITokenURL(res.token));
} catch (e) { } catch (e) {
console.error(e); console.error(e);
alert(`Failed to create API token!\n${e}`); alert(`Failed to create API token!\n${e}`);
@ -41,12 +50,16 @@ export function CreateApiTokenRoute(): React.ReactElement {
}; };
return ( return (
<>
{createdToken && <CreatedTokenDialog createdToken={createdToken} />}
<EditApiTokenRouteInner <EditApiTokenRouteInner
token={token} token={token}
creating={true} creating={true}
onCancel={() => navigate("/tokens")} onCancel={() => navigate("/tokens")}
onSave={createApiToken} onSave={createApiToken}
/> />
</>
); );
} }

View 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>
);
}

View File

@ -3,6 +3,7 @@ import React, { PropsWithChildren } from "react";
import { NetworkHookStatus, ServerApi } from "../../api/ServerApi"; import { NetworkHookStatus, ServerApi } from "../../api/ServerApi";
import { AsyncWidget } from "../AsyncWidget"; import { AsyncWidget } from "../AsyncWidget";
import { CopyToClipboard } from "../CopyToClipboard"; import { CopyToClipboard } from "../CopyToClipboard";
import { InlineCode } from "../InlineCode";
export function NetworkHookStatusWidget(p: { export function NetworkHookStatusWidget(p: {
hiddenIfInstalled: boolean; 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 { function CodeBlock(p: PropsWithChildren): React.ReactElement {
return ( return (
<pre <pre