Start to build routes to create, view and edit API token
This commit is contained in:
		@@ -9,30 +9,35 @@ import "./App.css";
 | 
			
		||||
import { AuthApi } from "./api/AuthApi";
 | 
			
		||||
import { ServerApi } from "./api/ServerApi";
 | 
			
		||||
import {
 | 
			
		||||
  CreateNetworkRoute,
 | 
			
		||||
  EditNetworkRoute,
 | 
			
		||||
} from "./routes/EditNetworkRoute";
 | 
			
		||||
import { CreateVMRoute, EditVMRoute } from "./routes/EditVMRoute";
 | 
			
		||||
import { IsoFilesRoute } from "./routes/IsoFilesRoute";
 | 
			
		||||
import { NetworksListRoute } from "./routes/NetworksListRoute";
 | 
			
		||||
import { NotFoundRoute } from "./routes/NotFound";
 | 
			
		||||
import { SysInfoRoute } from "./routes/SysInfoRoute";
 | 
			
		||||
import { VMListRoute } from "./routes/VMListRoute";
 | 
			
		||||
import { VMRoute } from "./routes/VMRoute";
 | 
			
		||||
import { VNCRoute } from "./routes/VNCRoute";
 | 
			
		||||
import { LoginRoute } from "./routes/auth/LoginRoute";
 | 
			
		||||
import { OIDCCbRoute } from "./routes/auth/OIDCCbRoute";
 | 
			
		||||
import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage";
 | 
			
		||||
import { BaseLoginPage } from "./widgets/BaseLoginPage";
 | 
			
		||||
import { ViewNetworkRoute } from "./routes/ViewNetworkRoute";
 | 
			
		||||
import { HomeRoute } from "./routes/HomeRoute";
 | 
			
		||||
import { NetworkFiltersListRoute } from "./routes/NetworkFiltersListRoute";
 | 
			
		||||
import { ViewNWFilterRoute } from "./routes/ViewNWFilterRoute";
 | 
			
		||||
  CreateApiTokenRoute,
 | 
			
		||||
  EditApiTokenRoute,
 | 
			
		||||
} from "./routes/EditAPITokenRoute";
 | 
			
		||||
import {
 | 
			
		||||
  CreateNWFilterRoute,
 | 
			
		||||
  EditNWFilterRoute,
 | 
			
		||||
} from "./routes/EditNWFilterRoute";
 | 
			
		||||
import {
 | 
			
		||||
  CreateNetworkRoute,
 | 
			
		||||
  EditNetworkRoute,
 | 
			
		||||
} from "./routes/EditNetworkRoute";
 | 
			
		||||
import { CreateVMRoute, EditVMRoute } from "./routes/EditVMRoute";
 | 
			
		||||
import { HomeRoute } from "./routes/HomeRoute";
 | 
			
		||||
import { IsoFilesRoute } from "./routes/IsoFilesRoute";
 | 
			
		||||
import { NetworkFiltersListRoute } from "./routes/NetworkFiltersListRoute";
 | 
			
		||||
import { NetworksListRoute } from "./routes/NetworksListRoute";
 | 
			
		||||
import { NotFoundRoute } from "./routes/NotFound";
 | 
			
		||||
import { SysInfoRoute } from "./routes/SysInfoRoute";
 | 
			
		||||
import { TokensListRoute } from "./routes/TokensListRoute";
 | 
			
		||||
import { VMListRoute } from "./routes/VMListRoute";
 | 
			
		||||
import { VMRoute } from "./routes/VMRoute";
 | 
			
		||||
import { VNCRoute } from "./routes/VNCRoute";
 | 
			
		||||
import { ViewApiTokenRoute } from "./routes/ViewApiTokenRoute";
 | 
			
		||||
import { ViewNWFilterRoute } from "./routes/ViewNWFilterRoute";
 | 
			
		||||
import { ViewNetworkRoute } from "./routes/ViewNetworkRoute";
 | 
			
		||||
import { LoginRoute } from "./routes/auth/LoginRoute";
 | 
			
		||||
import { OIDCCbRoute } from "./routes/auth/OIDCCbRoute";
 | 
			
		||||
import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage";
 | 
			
		||||
import { BaseLoginPage } from "./widgets/BaseLoginPage";
 | 
			
		||||
 | 
			
		||||
interface AuthContext {
 | 
			
		||||
  signedIn: boolean;
 | 
			
		||||
@@ -74,6 +79,9 @@ export function App() {
 | 
			
		||||
          <Route path="nwfilter/:uuid/edit" element={<EditNWFilterRoute />} />
 | 
			
		||||
 | 
			
		||||
          <Route path="tokens" element={<TokensListRoute />} />
 | 
			
		||||
          <Route path="tokens/new" element={<CreateApiTokenRoute />} />
 | 
			
		||||
          <Route path="tokens/:id" element={<ViewApiTokenRoute />} />
 | 
			
		||||
          <Route path="tokens/:id/edit" element={<EditApiTokenRoute />} />
 | 
			
		||||
 | 
			
		||||
          <Route path="sysinfo" element={<SysInfoRoute />} />
 | 
			
		||||
          <Route path="*" element={<NotFoundRoute />} />
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,8 @@ export interface ServerConstraints {
 | 
			
		||||
  nwfilter_comment_size: LenConstraint;
 | 
			
		||||
  nwfilter_priority: LenConstraint;
 | 
			
		||||
  nwfilter_selectors_count: LenConstraint;
 | 
			
		||||
  api_token_name_size: LenConstraint;
 | 
			
		||||
  api_token_description_size: LenConstraint;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface LenConstraint {
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,30 @@ export function APITokenURL(t: APIToken, edit: boolean = false): string {
 | 
			
		||||
  return `/tokens/${t.id}${edit ? "/edit" : ""}`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface APITokenPrivateKey {
 | 
			
		||||
  alg: string;
 | 
			
		||||
  priv: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface CreatedAPIToken {
 | 
			
		||||
  token: APIToken;
 | 
			
		||||
  priv_key: APITokenPrivateKey;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class TokensApi {
 | 
			
		||||
  /**
 | 
			
		||||
   * Create a new API token
 | 
			
		||||
   */
 | 
			
		||||
  static async Create(n: APIToken): Promise<CreatedAPIToken> {
 | 
			
		||||
    return (
 | 
			
		||||
      await APIClient.exec({
 | 
			
		||||
        method: "POST",
 | 
			
		||||
        uri: "/tokens/create",
 | 
			
		||||
        jsonData: n,
 | 
			
		||||
      })
 | 
			
		||||
    ).data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the full list of tokens
 | 
			
		||||
   */
 | 
			
		||||
@@ -33,4 +56,39 @@ export class TokensApi {
 | 
			
		||||
      })
 | 
			
		||||
    ).data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the information about a single token
 | 
			
		||||
   */
 | 
			
		||||
  static async GetSingle(uuid: string): Promise<APIToken> {
 | 
			
		||||
    return (
 | 
			
		||||
      await APIClient.exec({
 | 
			
		||||
        method: "GET",
 | 
			
		||||
        uri: `/tokens/${uuid}`,
 | 
			
		||||
      })
 | 
			
		||||
    ).data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Update an existing API token information
 | 
			
		||||
   */
 | 
			
		||||
  static async Update(n: APIToken): Promise<void> {
 | 
			
		||||
    return (
 | 
			
		||||
      await APIClient.exec({
 | 
			
		||||
        method: "PATCH",
 | 
			
		||||
        uri: `/tokens/${n.id}`,
 | 
			
		||||
        jsonData: n,
 | 
			
		||||
      })
 | 
			
		||||
    ).data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Delete an API token
 | 
			
		||||
   */
 | 
			
		||||
  static async Delete(n: APIToken): Promise<void> {
 | 
			
		||||
    await APIClient.exec({
 | 
			
		||||
      method: "DELETE",
 | 
			
		||||
      uri: `/tokens/${n.id}`,
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										148
									
								
								virtweb_frontend/src/routes/EditAPITokenRoute.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								virtweb_frontend/src/routes/EditAPITokenRoute.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,148 @@
 | 
			
		||||
import { Button } from "@mui/material";
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { useNavigate, useParams } from "react-router-dom";
 | 
			
		||||
import { APIToken, APITokenURL, TokensApi } from "../api/TokensApi";
 | 
			
		||||
import { useAlert } from "../hooks/providers/AlertDialogProvider";
 | 
			
		||||
import { useLoadingMessage } from "../hooks/providers/LoadingMessageProvider";
 | 
			
		||||
import { useSnackbar } from "../hooks/providers/SnackbarProvider";
 | 
			
		||||
import { time } from "../utils/DateUtils";
 | 
			
		||||
import { AsyncWidget } from "../widgets/AsyncWidget";
 | 
			
		||||
import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer";
 | 
			
		||||
import {
 | 
			
		||||
  APITokenDetails,
 | 
			
		||||
  TokenWidgetStatus,
 | 
			
		||||
} from "../widgets/tokens/APITokenDetails";
 | 
			
		||||
 | 
			
		||||
export function CreateApiTokenRoute(): React.ReactElement {
 | 
			
		||||
  const alert = useAlert();
 | 
			
		||||
  const snackbar = useSnackbar();
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
 | 
			
		||||
  const [token] = React.useState<APIToken>({
 | 
			
		||||
    id: "",
 | 
			
		||||
    name: "",
 | 
			
		||||
    description: "",
 | 
			
		||||
    created: time(),
 | 
			
		||||
    updated: time(),
 | 
			
		||||
    last_used: time(),
 | 
			
		||||
    rights: [],
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const createApiToken = async (n: APIToken) => {
 | 
			
		||||
    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));
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.error(e);
 | 
			
		||||
      alert(`Failed to create API token!\n${e}`);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <EditApiTokenRouteInner
 | 
			
		||||
      token={token}
 | 
			
		||||
      creating={true}
 | 
			
		||||
      onCancel={() => navigate("/tokens")}
 | 
			
		||||
      onSave={createApiToken}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function EditApiTokenRoute(): React.ReactElement {
 | 
			
		||||
  const alert = useAlert();
 | 
			
		||||
  const snackbar = useSnackbar();
 | 
			
		||||
 | 
			
		||||
  const { id } = useParams();
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
 | 
			
		||||
  const [token, setToken] = React.useState<APIToken | undefined>();
 | 
			
		||||
 | 
			
		||||
  const load = async () => {
 | 
			
		||||
    setToken(await TokensApi.GetSingle(id!));
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const updateApiToken = async (n: APIToken) => {
 | 
			
		||||
    try {
 | 
			
		||||
      await TokensApi.Update(n);
 | 
			
		||||
      snackbar("The token was successfully updated!");
 | 
			
		||||
      navigate(APITokenURL(token!));
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.error(e);
 | 
			
		||||
      alert(`Failed to update token!\n${e}`);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <AsyncWidget
 | 
			
		||||
      loadKey={id}
 | 
			
		||||
      ready={token !== undefined}
 | 
			
		||||
      errMsg="Failed to fetch API token informations!"
 | 
			
		||||
      load={load}
 | 
			
		||||
      build={() => (
 | 
			
		||||
        <EditApiTokenRouteInner
 | 
			
		||||
          token={token!}
 | 
			
		||||
          creating={false}
 | 
			
		||||
          onCancel={() => navigate(`/token/${id}`)}
 | 
			
		||||
          onSave={updateApiToken}
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function EditApiTokenRouteInner(p: {
 | 
			
		||||
  token: APIToken;
 | 
			
		||||
  creating: boolean;
 | 
			
		||||
  onCancel: () => void;
 | 
			
		||||
  onSave: (token: APIToken) => Promise<void>;
 | 
			
		||||
}): React.ReactElement {
 | 
			
		||||
  const loadingMessage = useLoadingMessage();
 | 
			
		||||
 | 
			
		||||
  const [changed, setChanged] = React.useState(false);
 | 
			
		||||
 | 
			
		||||
  const [, updateState] = React.useState<any>();
 | 
			
		||||
  const forceUpdate = React.useCallback(() => updateState({}), []);
 | 
			
		||||
 | 
			
		||||
  const valueChanged = () => {
 | 
			
		||||
    setChanged(true);
 | 
			
		||||
    forceUpdate();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const save = async () => {
 | 
			
		||||
    loadingMessage.show("Saving API token configuration...");
 | 
			
		||||
    await p.onSave(p.token);
 | 
			
		||||
    loadingMessage.hide();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <VirtWebRouteContainer
 | 
			
		||||
      label={p.creating ? "Create an API Token" : "Edit API Token"}
 | 
			
		||||
      actions={
 | 
			
		||||
        <span>
 | 
			
		||||
          {changed && (
 | 
			
		||||
            <Button
 | 
			
		||||
              variant="contained"
 | 
			
		||||
              onClick={save}
 | 
			
		||||
              style={{ marginRight: "10px" }}
 | 
			
		||||
            >
 | 
			
		||||
              {p.creating ? "Create" : "Save"}
 | 
			
		||||
            </Button>
 | 
			
		||||
          )}
 | 
			
		||||
          <Button onClick={p.onCancel} variant="outlined">
 | 
			
		||||
            Cancel
 | 
			
		||||
          </Button>
 | 
			
		||||
        </span>
 | 
			
		||||
      }
 | 
			
		||||
    >
 | 
			
		||||
      <APITokenDetails
 | 
			
		||||
        token={p.token}
 | 
			
		||||
        status={
 | 
			
		||||
          p.creating ? TokenWidgetStatus.Create : TokenWidgetStatus.Update
 | 
			
		||||
        }
 | 
			
		||||
        onChange={valueChanged}
 | 
			
		||||
      />
 | 
			
		||||
    </VirtWebRouteContainer>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,3 @@
 | 
			
		||||
import DeleteIcon from "@mui/icons-material/Delete";
 | 
			
		||||
import VisibilityIcon from "@mui/icons-material/Visibility";
 | 
			
		||||
import {
 | 
			
		||||
  Button,
 | 
			
		||||
@@ -13,13 +12,13 @@ import {
 | 
			
		||||
  Typography,
 | 
			
		||||
} from "@mui/material";
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { useNavigate } from "react-router-dom";
 | 
			
		||||
import { NetworkApi, NetworkInfo, NetworkURL } from "../api/NetworksApi";
 | 
			
		||||
import { AsyncWidget } from "../widgets/AsyncWidget";
 | 
			
		||||
import { RouterLink } from "../widgets/RouterLink";
 | 
			
		||||
import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer";
 | 
			
		||||
import { NetworkStatusWidget } from "../widgets/net/NetworkStatusWidget";
 | 
			
		||||
import { useNavigate } from "react-router-dom";
 | 
			
		||||
import { NetworkHookStatusWidget } from "../widgets/net/NetworkHookStatusWidget";
 | 
			
		||||
import { NetworkStatusWidget } from "../widgets/net/NetworkStatusWidget";
 | 
			
		||||
 | 
			
		||||
export function NetworksListRoute(): React.ReactElement {
 | 
			
		||||
  const [list, setList] = React.useState<NetworkInfo[] | undefined>();
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										53
									
								
								virtweb_frontend/src/routes/ViewApiTokenRoute.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								virtweb_frontend/src/routes/ViewApiTokenRoute.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
			
		||||
import { Button } from "@mui/material";
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { useNavigate, useParams } from "react-router-dom";
 | 
			
		||||
import { APIToken, APITokenURL, TokensApi } from "../api/TokensApi";
 | 
			
		||||
import { AsyncWidget } from "../widgets/AsyncWidget";
 | 
			
		||||
import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer";
 | 
			
		||||
import {
 | 
			
		||||
  APITokenDetails,
 | 
			
		||||
  TokenWidgetStatus,
 | 
			
		||||
} from "../widgets/tokens/APITokenDetails";
 | 
			
		||||
 | 
			
		||||
export function ViewApiTokenRoute() {
 | 
			
		||||
  const { id } = useParams();
 | 
			
		||||
 | 
			
		||||
  const [token, setToken] = React.useState<APIToken | undefined>();
 | 
			
		||||
 | 
			
		||||
  const load = async () => {
 | 
			
		||||
    setToken(await TokensApi.GetSingle(id!));
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <AsyncWidget
 | 
			
		||||
      loadKey={id}
 | 
			
		||||
      ready={token !== undefined}
 | 
			
		||||
      errMsg="Failed to fetch API token information!"
 | 
			
		||||
      load={load}
 | 
			
		||||
      build={() => <ViewAPITokenRouteInner token={token!} />}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function ViewAPITokenRouteInner(p: { token: APIToken }): React.ReactElement {
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <VirtWebRouteContainer
 | 
			
		||||
      label={`API token ${p.token.name}`}
 | 
			
		||||
      actions={
 | 
			
		||||
        <span style={{ display: "flex", alignItems: "center" }}>
 | 
			
		||||
          <Button
 | 
			
		||||
            variant="contained"
 | 
			
		||||
            style={{ marginLeft: "15px" }}
 | 
			
		||||
            onClick={() => navigate(APITokenURL(p.token, true))}
 | 
			
		||||
          >
 | 
			
		||||
            Edit
 | 
			
		||||
          </Button>
 | 
			
		||||
        </span>
 | 
			
		||||
      }
 | 
			
		||||
    >
 | 
			
		||||
      <APITokenDetails token={p.token} status={TokenWidgetStatus.Read} />
 | 
			
		||||
    </VirtWebRouteContainer>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@@ -22,14 +22,16 @@ export function IPInput(p: {
 | 
			
		||||
export function IPInputWithMask(p: {
 | 
			
		||||
  label: string;
 | 
			
		||||
  editable: boolean;
 | 
			
		||||
  ipAndMask?: string;
 | 
			
		||||
  ip?: string;
 | 
			
		||||
  mask?: number;
 | 
			
		||||
  onValueChange?: (ip?: string, mask?: number) => void;
 | 
			
		||||
  onValueChange?: (ip?: string, mask?: number, ipAndMask?: string) => void;
 | 
			
		||||
  version: 4 | 6;
 | 
			
		||||
}): React.ReactElement {
 | 
			
		||||
  const showSlash = React.useRef(!!p.mask);
 | 
			
		||||
 | 
			
		||||
  const currValue =
 | 
			
		||||
    p.ipAndMask ??
 | 
			
		||||
    (p.ip ?? "") + (p.mask || showSlash.current ? "/" : "") + (p.mask ?? "");
 | 
			
		||||
 | 
			
		||||
  const { onValueChange, ...props } = p;
 | 
			
		||||
@@ -38,7 +40,7 @@ export function IPInputWithMask(p: {
 | 
			
		||||
      onValueChange={(v) => {
 | 
			
		||||
        showSlash.current = false;
 | 
			
		||||
        if (!v) {
 | 
			
		||||
          onValueChange?.(undefined, undefined);
 | 
			
		||||
          onValueChange?.(undefined, undefined, undefined);
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -52,7 +54,11 @@ export function IPInputWithMask(p: {
 | 
			
		||||
          mask = sanitizeMask(p.version, split[1]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        onValueChange?.(ip, mask);
 | 
			
		||||
        onValueChange?.(
 | 
			
		||||
          ip,
 | 
			
		||||
          mask,
 | 
			
		||||
          mask || showSlash.current ? `${ip}/${mask ?? ""}` : ip
 | 
			
		||||
        );
 | 
			
		||||
      }}
 | 
			
		||||
      value={currValue}
 | 
			
		||||
      {...props}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										216
									
								
								virtweb_frontend/src/widgets/tokens/APITokenDetails.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								virtweb_frontend/src/widgets/tokens/APITokenDetails.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,216 @@
 | 
			
		||||
import { Button, Grid } from "@mui/material";
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { useNavigate } from "react-router-dom";
 | 
			
		||||
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 { IPInput, IPInputWithMask } from "../forms/IPInput";
 | 
			
		||||
import { ResAutostartInput } from "../forms/ResAutostartInput";
 | 
			
		||||
import { SelectInput } from "../forms/SelectInput";
 | 
			
		||||
import { TextInput } from "../forms/TextInput";
 | 
			
		||||
import { RadioGroupInput } from "../forms/RadioGroupInput";
 | 
			
		||||
 | 
			
		||||
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 [networks, setNetworks] = React.useState<NetworkInfo[]>();
 | 
			
		||||
  const [nwFilters, setNetworkFilters] = React.useState<NWFilter[]>();
 | 
			
		||||
  const [tokens, setTokens] = React.useState<APIToken[]>();
 | 
			
		||||
 | 
			
		||||
  const load = async () => {
 | 
			
		||||
    setVMs(await VMApi.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!}
 | 
			
		||||
          networks={networks!}
 | 
			
		||||
          nwFilters={nwFilters!}
 | 
			
		||||
          tokens={tokens!}
 | 
			
		||||
          {...p}
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum TokenTab {
 | 
			
		||||
  General = 0,
 | 
			
		||||
  Rights,
 | 
			
		||||
  RawRights,
 | 
			
		||||
  Danger,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DetailsInnerProps = DetailsProps & {
 | 
			
		||||
  vms: VMInfo[];
 | 
			
		||||
  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 && <NetworkDetailsTabGeneral {...p} />}
 | 
			
		||||
      {/* todo: rights */}
 | 
			
		||||
      {currTab === TokenTab.Danger && <APITokenTabDanger {...p} />}
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function NetworkDetailsTabGeneral(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 || p.token.ip_restriction) && (
 | 
			
		||||
          <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);
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
        )}
 | 
			
		||||
        <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?.();
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
        {/* TODO : remaining */}
 | 
			
		||||
      </EditSection>
 | 
			
		||||
    </Grid>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user