import { Button } from "@mui/material"; import Grid from "@mui/material/Grid"; import React from "react"; import { useNavigate } from "react-router-dom"; import { GroupApi } from "../../api/GroupApi"; import { NWFilter, NWFilterApi } from "../../api/NWFilterApi"; import { NetworkApi, NetworkInfo } from "../../api/NetworksApi"; import { ServerApi } from "../../api/ServerApi"; import { APIToken, TokensApi } from "../../api/TokensApi"; import { VMApi, VMInfo } from "../../api/VMApi"; import { useAlert } from "../../hooks/providers/AlertDialogProvider"; import { useConfirm } from "../../hooks/providers/ConfirmDialogProvider"; import { useSnackbar } from "../../hooks/providers/SnackbarProvider"; import { AsyncWidget } from "../AsyncWidget"; import { TabsWidget } from "../TabsWidget"; import { EditSection } from "../forms/EditSection"; import { IPInputWithMask } from "../forms/IPInput"; import { RadioGroupInput } from "../forms/RadioGroupInput"; import { TextInput } from "../forms/TextInput"; import { TokenRawRightsEditor } from "./TokenRawRightsEditor"; import { TokenRightsEditor } from "./TokenRightsEditor"; const SECS_PER_DAY = 3600 * 24; export enum TokenWidgetStatus { Create, Read, Update, } interface DetailsProps { token: APIToken; status: TokenWidgetStatus; onChange?: () => void; } export function APITokenDetails(p: DetailsProps): React.ReactElement { const [vms, setVMs] = React.useState<VMInfo[]>(); const [groups, setGroups] = React.useState<string[]>(); const [networks, setNetworks] = React.useState<NetworkInfo[]>(); const [nwFilters, setNetworkFilters] = React.useState<NWFilter[]>(); const [tokens, setTokens] = React.useState<APIToken[]>(); const load = async () => { setVMs(await VMApi.GetList()); setGroups(await GroupApi.GetList()); setNetworks(await NetworkApi.GetList()); setNetworkFilters(await NWFilterApi.GetList()); setTokens(await TokensApi.GetList()); }; return ( <AsyncWidget loadKey={"1"} load={load} errMsg="Failed to load some system entities!" build={() => ( <APITokenDetailsInner vms={vms!} groups={groups!} networks={networks!} nwFilters={nwFilters!} tokens={tokens!} {...p} /> )} /> ); } enum TokenTab { General = 0, Rights, RawRights, Danger, } type DetailsInnerProps = DetailsProps & { vms: VMInfo[]; groups: string[]; networks: NetworkInfo[]; nwFilters: NWFilter[]; tokens: APIToken[]; }; function APITokenDetailsInner(p: DetailsInnerProps): React.ReactElement { const [currTab, setCurrTab] = React.useState(TokenTab.General); return ( <> <TabsWidget currTab={currTab} onTabChange={setCurrTab} options={[ { label: "General", value: TokenTab.General, visible: true }, { label: "Rights", value: TokenTab.Rights, visible: true, }, { label: "Raw rights", value: TokenTab.RawRights, visible: true, }, { label: "Danger zone", value: TokenTab.Danger, color: "red", visible: p.status === TokenWidgetStatus.Read, }, ]} /> {currTab === TokenTab.General && <APITokenTabGeneral {...p} />} {currTab === TokenTab.Rights && <APITokenRights {...p} />} {currTab === TokenTab.RawRights && <APITokenRawRights {...p} />} {currTab === TokenTab.Danger && <APITokenTabDanger {...p} />} </> ); } function APITokenTabGeneral(p: DetailsInnerProps): React.ReactElement { const [ipVersion, setIpVersion] = React.useState<4 | 6>( (p.token.ip_restriction ?? "").includes(":") ? 6 : 4 ); return ( <Grid container spacing={2}> {/* Metadata section */} <EditSection title="Metadata"> {p.status !== TokenWidgetStatus.Create && ( <TextInput label="UUID" editable={false} value={p.token.id} /> )} <TextInput label="Name" editable={p.status === TokenWidgetStatus.Create} value={p.token.name} onValueChange={(v) => { p.token.name = v ?? ""; p.onChange?.(); }} size={ServerApi.Config.constraints.api_token_name_size} /> <TextInput label="Description" editable={p.status === TokenWidgetStatus.Create} value={p.token.description} onValueChange={(v) => { p.token.description = v ?? ""; p.onChange?.(); }} multiline={true} size={ServerApi.Config.constraints.api_token_description_size} /> </EditSection> <EditSection title="General settings"> {p.status === TokenWidgetStatus.Create && ( <RadioGroupInput {...p} editable={p.status === TokenWidgetStatus.Create} options={[ { label: "IPv4", value: "4" }, { label: "IPv6", value: "6" }, ]} value={ipVersion.toString()} onValueChange={(v) => { setIpVersion(Number(v) as any); }} label="Token IP restriction version" /> )} <IPInputWithMask {...p} label="Token IP network restriction" ipAndMask={p.token.ip_restriction} editable={p.status === TokenWidgetStatus.Create} version={ipVersion} onValueChange={(_ip, _mask, ipAndMask) => { p.token.ip_restriction = ipAndMask; p.onChange?.(); }} /> <TextInput editable={p.status === TokenWidgetStatus.Create} label="Max inactivity of tokens (days)" type="number" value={ p.token.max_inactivity ? Math.floor(p.token.max_inactivity / SECS_PER_DAY).toString() : "" } onValueChange={(v) => { const secs = Number(v ?? "0") * SECS_PER_DAY; p.token.max_inactivity = secs === 0 ? undefined : secs; p.onChange?.(); }} /> </EditSection> </Grid> ); } 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 { return ( <div style={{ padding: "30px" }}> <TokenRawRightsEditor {...p} editable={p.status !== TokenWidgetStatus.Read} /> </div> ); } function APITokenTabDanger(p: DetailsInnerProps): React.ReactElement { const confirm = useConfirm(); const snackbar = useSnackbar(); const alert = useAlert(); const navigate = useNavigate(); const requestDelete = async () => { try { if ( !(await confirm( "Do you really want to delete this API token?", `Delete API token ${p.token.name}`, "Delete" )) ) return; await TokensApi.Delete(p.token); navigate("/tokens"); snackbar("The API token was successfully deleted!"); } catch (e) { console.error(e); alert(`Failed to delete the API token!\n${e}`); } }; return ( <Button color="error" onClick={requestDelete}> Delete this API token </Button> ); }