Add API tokens support #9
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
151
virtweb_frontend/src/widgets/tokens/TokenRightsEditor.tsx
Normal file
151
virtweb_frontend/src/widgets/tokens/TokenRightsEditor.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user