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 173 additions and 5 deletions
Showing only changes of commit e68569b9b4 - Show all commits

View File

@ -1,7 +1,9 @@
import { APIClient } from "./ApiClient"; import { APIClient } from "./ApiClient";
export type RightVerb = "POST" | "GET" | "PUT" | "DELETE" | "PATCH";
export interface TokenRight { export interface TokenRight {
verb: "POST" | "GET" | "PUT" | "DELETE" | "PATCH"; verb: RightVerb;
path: string; path: string;
} }

View File

@ -15,7 +15,8 @@ import { EditSection } from "../forms/EditSection";
import { IPInputWithMask } from "../forms/IPInput"; import { IPInputWithMask } from "../forms/IPInput";
import { RadioGroupInput } from "../forms/RadioGroupInput"; import { RadioGroupInput } from "../forms/RadioGroupInput";
import { TextInput } from "../forms/TextInput"; import { TextInput } from "../forms/TextInput";
import { RawRightsEditor } from "./RawRightsEditor"; import { TokenRawRightsEditor } from "./TokenRawRightsEditor";
import { TokenRightsEditor } from "./TokenRightsEditor";
const SECS_PER_DAY = 3600 * 24; const SECS_PER_DAY = 3600 * 24;
@ -106,7 +107,7 @@ function APITokenDetailsInner(p: DetailsInnerProps): React.ReactElement {
]} ]}
/> />
{currTab === TokenTab.General && <APITokenTabGeneral {...p} />} {currTab === TokenTab.General && <APITokenTabGeneral {...p} />}
{/* todo: rights */} {currTab === TokenTab.Rights && <APITokenRights {...p} />}
{currTab === TokenTab.RawRights && <APITokenRawRights {...p} />} {currTab === TokenTab.RawRights && <APITokenRawRights {...p} />}
{currTab === TokenTab.Danger && <APITokenTabDanger {...p} />} {currTab === TokenTab.Danger && <APITokenTabDanger {...p} />}
</> </>
@ -198,10 +199,24 @@ function APITokenTabGeneral(p: DetailsInnerProps): React.ReactElement {
); );
} }
function APITokenRights(p: DetailsInnerProps): React.ReactElement {
return (
<div style={{ padding: "30px" }}>
<TokenRightsEditor
{...p}
editable={p.status !== TokenWidgetStatus.Read}
/>
</div>
);
}
function APITokenRawRights(p: DetailsInnerProps): React.ReactElement { function APITokenRawRights(p: DetailsInnerProps): React.ReactElement {
return ( return (
<div style={{ padding: "30px" }}> <div style={{ padding: "30px" }}>
<RawRightsEditor {...p} editable={p.status !== TokenWidgetStatus.Read} /> <TokenRawRightsEditor
{...p}
editable={p.status !== TokenWidgetStatus.Read}
/>
</div> </div>
); );
} }

View File

@ -17,7 +17,7 @@ import { APIToken } from "../../api/TokensApi";
import { SelectInput } from "../forms/SelectInput"; import { SelectInput } from "../forms/SelectInput";
import { TextInput } from "../forms/TextInput"; import { TextInput } from "../forms/TextInput";
export function RawRightsEditor(p: { export function TokenRawRightsEditor(p: {
token: APIToken; token: APIToken;
editable: boolean; editable: boolean;
onChange?: () => void; onChange?: () => void;

View File

@ -0,0 +1,151 @@
import {
Checkbox,
FormControlLabel,
Paper,
Tooltip,
Typography,
} from "@mui/material";
import React from "react";
import { NWFilter } from "../../api/NWFilterApi";
import { NetworkInfo } from "../../api/NetworksApi";
import { APIToken, TokenRight } from "../../api/TokensApi";
import { VMInfo } from "../../api/VMApi";
export function TokenRightsEditor(p: {
token: APIToken;
editable: boolean;
onChange?: () => void;
vms: VMInfo[];
networks: NetworkInfo[];
nwFilters: NWFilter[];
tokens: APIToken[];
}): React.ReactElement {
return (
<>
{/* ISO files */}
<RightsSection label="ISO files">
<RouteRight
{...p}
right={{ verb: "POST", path: "/api/iso/upload" }}
label="Upload a new ISO file"
/>
<RouteRight
{...p}
right={{ verb: "POST", path: "/api/iso/upload_from_url" }}
label="Upload a new ISO file from a given URL"
/>
<RouteRight
{...p}
right={{ verb: "GET", path: "/api/iso/list" }}
label="Get the list of ISO files"
/>
<RouteRight
{...p}
right={{ verb: "GET", path: "/api/iso/*" }}
label="Download ISO files"
/>
<RouteRight
{...p}
right={{ verb: "DELETE", path: "/api/iso/*" }}
label="Delete ISO files"
/>
</RightsSection>
{/* Server general information */}
<RightsSection label="Server">
<RouteRight
{...p}
right={{ verb: "GET", path: "/api/server/static_config" }}
label="Get static server configuration"
/>
<RouteRight
{...p}
right={{ verb: "GET", path: "/api/server/info" }}
label="Get server information"
/>
<RouteRight
{...p}
right={{ verb: "GET", path: "/api/server/network_hook_status" }}
label="Get network hook status"
/>
<RouteRight
{...p}
right={{ verb: "GET", path: "/api/server/number_vcpus" }}
label="Get number of vCPU"
/>
<RouteRight
{...p}
right={{ verb: "GET", path: "/api/server/networks" }}
label="Get list of network cards"
/>
</RightsSection>
</>
);
}
function RightsSection(
p: React.PropsWithChildren<{ label: string }>
): React.ReactElement {
return (
<Paper style={{ padding: "20px" }}>
<Typography variant="h5">{p.label}</Typography>
{p.children}
</Paper>
);
}
function RouteRight(p: {
right: TokenRight;
label?: string;
editable: boolean;
token: APIToken;
onChange?: () => void;
parent?: TokenRight;
}): React.ReactElement {
const activated =
p.token.rights.find(
(r) => r.verb === p.right.verb && r.path === p.right.path
) !== undefined;
const toggle = (a: boolean) => {
if (a) {
p.token.rights.push(p.right);
} else {
p.token.rights.splice(p.token.rights.indexOf(p.right), 1);
}
p.onChange?.();
};
return (
<div>
<Tooltip
title={`${p.right.verb} ${p.right.path}`}
arrow
placement="left"
slotProps={{
popper: {
modifiers: [
{
name: "offset",
options: {
offset: [0, -14],
},
},
],
},
}}
>
<FormControlLabel
control={
<Checkbox
checked={activated}
disabled={!p.editable}
onChange={(_e, a) => toggle(a)}
/>
}
label={p.label}
/>
</Tooltip>
</div>
);
}