Centralize rights management
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is passing
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			This commit is contained in:
		@@ -12,7 +12,7 @@ import {
 | 
			
		||||
  bundleIcon,
 | 
			
		||||
} from "@fluentui/react-icons";
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { ServerApi } from "./api/ServerApi";
 | 
			
		||||
import { Rights, ServerApi } from "./api/ServerApi";
 | 
			
		||||
import { AuthRouteWidget } from "./routes/AuthRouteWidget";
 | 
			
		||||
import { AsyncWidget } from "./widgets/AsyncWidget";
 | 
			
		||||
import { MainMenu } from "./widgets/MainMenu";
 | 
			
		||||
@@ -40,45 +40,72 @@ export function App() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function AppInner(): React.ReactElement {
 | 
			
		||||
  const styles = useStyles();
 | 
			
		||||
  const [tab, setTab] = React.useState<"vm" | "info">("vm");
 | 
			
		||||
 | 
			
		||||
  if (!ServerApi.Config.authenticated && !ServerApi.Config.disable_auth)
 | 
			
		||||
    return <AuthRouteWidget />;
 | 
			
		||||
 | 
			
		||||
  return <AuthenticatedApp />;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function AuthenticatedApp(): React.ReactElement {
 | 
			
		||||
  const styles = useStyles();
 | 
			
		||||
  const [tab, setTab] = React.useState<"vm" | "info">("vm");
 | 
			
		||||
 | 
			
		||||
  const [rights, setRights] = React.useState<Rights | undefined>();
 | 
			
		||||
 | 
			
		||||
  const load = async () => {
 | 
			
		||||
    setRights(await ServerApi.GetRights());
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div
 | 
			
		||||
      style={{
 | 
			
		||||
        width: "95%",
 | 
			
		||||
        maxWidth: "1000px",
 | 
			
		||||
        margin: "50px auto",
 | 
			
		||||
    <AsyncWidget
 | 
			
		||||
      loadKey={1}
 | 
			
		||||
      load={load}
 | 
			
		||||
      errMsg="Failed to retrieve application rights!"
 | 
			
		||||
      build={() => {
 | 
			
		||||
        return (
 | 
			
		||||
          <div
 | 
			
		||||
            style={{
 | 
			
		||||
              width: "95%",
 | 
			
		||||
              maxWidth: "1000px",
 | 
			
		||||
              margin: "50px auto",
 | 
			
		||||
            }}
 | 
			
		||||
          >
 | 
			
		||||
            <span className={styles.title}>VirtWebRemote</span>
 | 
			
		||||
            <div
 | 
			
		||||
              style={{
 | 
			
		||||
                display: "flex",
 | 
			
		||||
                justifyContent: "space-between",
 | 
			
		||||
                marginTop: "30px",
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              <TabList
 | 
			
		||||
                selectedValue={tab}
 | 
			
		||||
                onTabSelect={(_, d) => setTab(d.value as any)}
 | 
			
		||||
              >
 | 
			
		||||
                <Tab
 | 
			
		||||
                  value="vm"
 | 
			
		||||
                  icon={<DesktopIcon />}
 | 
			
		||||
                  disabled={rights!.vms.length === 0}
 | 
			
		||||
                >
 | 
			
		||||
                  Virtual machines
 | 
			
		||||
                </Tab>
 | 
			
		||||
                <Tab
 | 
			
		||||
                  value="info"
 | 
			
		||||
                  icon={<InfoIcon />}
 | 
			
		||||
                  disabled={!rights!.sys_info}
 | 
			
		||||
                >
 | 
			
		||||
                  System info
 | 
			
		||||
                </Tab>
 | 
			
		||||
              </TabList>
 | 
			
		||||
              <div>
 | 
			
		||||
                <MainMenu />
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            {tab === "vm" && <VirtualMachinesWidget rights={rights!} />}
 | 
			
		||||
            {tab === "info" && <SystemInfoWidget />}
 | 
			
		||||
          </div>
 | 
			
		||||
        );
 | 
			
		||||
      }}
 | 
			
		||||
    >
 | 
			
		||||
      <span className={styles.title}>VirtWebRemote</span>
 | 
			
		||||
      <div
 | 
			
		||||
        style={{
 | 
			
		||||
          display: "flex",
 | 
			
		||||
          justifyContent: "space-between",
 | 
			
		||||
          marginTop: "30px",
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        <TabList
 | 
			
		||||
          selectedValue={tab}
 | 
			
		||||
          onTabSelect={(_, d) => setTab(d.value as any)}
 | 
			
		||||
        >
 | 
			
		||||
          <Tab value="vm" icon={<DesktopIcon />}>
 | 
			
		||||
            Virtual machines
 | 
			
		||||
          </Tab>
 | 
			
		||||
          <Tab value="info" icon={<InfoIcon />}>
 | 
			
		||||
            System info
 | 
			
		||||
          </Tab>
 | 
			
		||||
        </TabList>
 | 
			
		||||
        <div>
 | 
			
		||||
          <MainMenu />
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      {tab === "vm" && <VirtualMachinesWidget />}
 | 
			
		||||
      {tab === "info" && <SystemInfoWidget />}
 | 
			
		||||
    </div>
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,16 @@
 | 
			
		||||
import { APIClient } from "./ApiClient";
 | 
			
		||||
import { VMInfo } from "./VMApi";
 | 
			
		||||
 | 
			
		||||
export interface ServerConfig {
 | 
			
		||||
  authenticated: boolean;
 | 
			
		||||
  disable_auth: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface Rights {
 | 
			
		||||
  vms: VMInfo[];
 | 
			
		||||
  sys_info: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
let config: ServerConfig | null = null;
 | 
			
		||||
 | 
			
		||||
export class ServerApi {
 | 
			
		||||
@@ -27,4 +33,16 @@ export class ServerApi {
 | 
			
		||||
    if (config === null) throw new Error("Missing configuration!");
 | 
			
		||||
    return config;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get application rights
 | 
			
		||||
   */
 | 
			
		||||
  static async GetRights(): Promise<Rights> {
 | 
			
		||||
    return (
 | 
			
		||||
      await APIClient.exec({
 | 
			
		||||
        uri: "/server/rights",
 | 
			
		||||
        method: "GET",
 | 
			
		||||
      })
 | 
			
		||||
    ).data;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,5 @@
 | 
			
		||||
import { APIClient } from "./ApiClient";
 | 
			
		||||
 | 
			
		||||
export interface SysInfoConfig {
 | 
			
		||||
  allowed: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface LoadAverage {
 | 
			
		||||
  one: number;
 | 
			
		||||
  five: number;
 | 
			
		||||
@@ -24,14 +20,6 @@ export interface SysInfoStatus {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class SysInfoApi {
 | 
			
		||||
  /**
 | 
			
		||||
   * Get system info configuration (ie. check if it allowed)
 | 
			
		||||
   */
 | 
			
		||||
  static async GetConfig(): Promise<SysInfoConfig> {
 | 
			
		||||
    return (await APIClient.exec({ method: "GET", uri: "/sysinfo/config" }))
 | 
			
		||||
      .data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get system status
 | 
			
		||||
   */
 | 
			
		||||
 
 | 
			
		||||
@@ -29,13 +29,6 @@ export type VMState =
 | 
			
		||||
  | "Other";
 | 
			
		||||
 | 
			
		||||
export class VMApi {
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the list of VM that can be managed by this console
 | 
			
		||||
   */
 | 
			
		||||
  static async GetList(): Promise<VMInfo[]> {
 | 
			
		||||
    return (await APIClient.exec({ method: "GET", uri: "/vm/list" })).data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the state of a VM
 | 
			
		||||
   */
 | 
			
		||||
 
 | 
			
		||||
@@ -1,47 +1,13 @@
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { SysInfoApi, SysInfoConfig, SysInfoStatus } from "../api/SysInfoApi";
 | 
			
		||||
import { AsyncWidget } from "./AsyncWidget";
 | 
			
		||||
import { SectionContainer } from "./SectionContainer";
 | 
			
		||||
import { Field, ProgressBar } from "@fluentui/react-components";
 | 
			
		||||
import { filesize } from "filesize";
 | 
			
		||||
import { format_duration } from "../utils/time_utils";
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { SysInfoApi, SysInfoStatus } from "../api/SysInfoApi";
 | 
			
		||||
import { useToast } from "../hooks/providers/ToastProvider";
 | 
			
		||||
import { format_duration } from "../utils/time_utils";
 | 
			
		||||
import { AsyncWidget } from "./AsyncWidget";
 | 
			
		||||
import { SectionContainer } from "./SectionContainer";
 | 
			
		||||
 | 
			
		||||
export function SystemInfoWidget(): React.ReactElement {
 | 
			
		||||
  const [config, setConfig] = React.useState<SysInfoConfig | undefined>();
 | 
			
		||||
 | 
			
		||||
  const load = async () => {
 | 
			
		||||
    setConfig(await SysInfoApi.GetConfig());
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <SectionContainer>
 | 
			
		||||
      <AsyncWidget
 | 
			
		||||
        loadKey={1}
 | 
			
		||||
        load={load}
 | 
			
		||||
        errMsg="Failed to check system configuration!"
 | 
			
		||||
        loadingMessage="Checking server configuration..."
 | 
			
		||||
        build={() =>
 | 
			
		||||
          config?.allowed ? (
 | 
			
		||||
            <SystemInfoWidgetInner />
 | 
			
		||||
          ) : (
 | 
			
		||||
            <SystemInfoWidgetUnavailable />
 | 
			
		||||
          )
 | 
			
		||||
        }
 | 
			
		||||
      />
 | 
			
		||||
    </SectionContainer>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function SystemInfoWidgetUnavailable(): React.ReactElement {
 | 
			
		||||
  return (
 | 
			
		||||
    <p style={{ textAlign: "center" }}>
 | 
			
		||||
      Unfortunatley, system information is available. (not enough privileges)
 | 
			
		||||
    </p>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function SystemInfoWidgetInner(): React.ReactElement {
 | 
			
		||||
  const toast = useToast();
 | 
			
		||||
 | 
			
		||||
  const [status, setStatus] = React.useState<SysInfoStatus | undefined>();
 | 
			
		||||
@@ -63,49 +29,51 @@ function SystemInfoWidgetInner(): React.ReactElement {
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <AsyncWidget
 | 
			
		||||
      loadKey={1}
 | 
			
		||||
      load={load}
 | 
			
		||||
      loadingMessage="Loading system status..."
 | 
			
		||||
      errMsg="Failed to load system status!"
 | 
			
		||||
      build={() => (
 | 
			
		||||
        <div
 | 
			
		||||
          style={{
 | 
			
		||||
            display: "flex",
 | 
			
		||||
            flexDirection: "row",
 | 
			
		||||
            alignItems: "center",
 | 
			
		||||
            justifyContent: "center",
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          <Field
 | 
			
		||||
            validationMessage={`${filesize(
 | 
			
		||||
              status!.system.used_memory
 | 
			
		||||
            )} of memory used out of ${filesize(
 | 
			
		||||
              status!.system.available_memory + status!.system.used_memory
 | 
			
		||||
            )}`}
 | 
			
		||||
            validationState="none"
 | 
			
		||||
            style={{ flex: 2 }}
 | 
			
		||||
    <SectionContainer>
 | 
			
		||||
      <AsyncWidget
 | 
			
		||||
        loadKey={1}
 | 
			
		||||
        load={load}
 | 
			
		||||
        loadingMessage="Loading system status..."
 | 
			
		||||
        errMsg="Failed to load system status!"
 | 
			
		||||
        build={() => (
 | 
			
		||||
          <div
 | 
			
		||||
            style={{
 | 
			
		||||
              display: "flex",
 | 
			
		||||
              flexDirection: "row",
 | 
			
		||||
              alignItems: "center",
 | 
			
		||||
              justifyContent: "center",
 | 
			
		||||
            }}
 | 
			
		||||
          >
 | 
			
		||||
            <ProgressBar
 | 
			
		||||
              value={
 | 
			
		||||
                status!.system.used_memory /
 | 
			
		||||
                (status!.system.available_memory + status!.system.used_memory)
 | 
			
		||||
              }
 | 
			
		||||
            />
 | 
			
		||||
          </Field>
 | 
			
		||||
          <div style={{ width: "20px" }}></div>
 | 
			
		||||
          <div style={{ flex: 1 }}>
 | 
			
		||||
            <p>
 | 
			
		||||
              Load average: {status!.system.load_average.one}{" "}
 | 
			
		||||
              {status!.system.load_average.five}{" "}
 | 
			
		||||
              {status!.system.load_average.fifteen}
 | 
			
		||||
            </p>
 | 
			
		||||
            <UptimeWidget uptime={status!.system.uptime} />
 | 
			
		||||
            Number physical cores: {status!.system.physical_core_count}
 | 
			
		||||
            <Field
 | 
			
		||||
              validationMessage={`${filesize(
 | 
			
		||||
                status!.system.used_memory
 | 
			
		||||
              )} of memory used out of ${filesize(
 | 
			
		||||
                status!.system.available_memory + status!.system.used_memory
 | 
			
		||||
              )}`}
 | 
			
		||||
              validationState="none"
 | 
			
		||||
              style={{ flex: 2 }}
 | 
			
		||||
            >
 | 
			
		||||
              <ProgressBar
 | 
			
		||||
                value={
 | 
			
		||||
                  status!.system.used_memory /
 | 
			
		||||
                  (status!.system.available_memory + status!.system.used_memory)
 | 
			
		||||
                }
 | 
			
		||||
              />
 | 
			
		||||
            </Field>
 | 
			
		||||
            <div style={{ width: "20px" }}></div>
 | 
			
		||||
            <div style={{ flex: 1 }}>
 | 
			
		||||
              <p>
 | 
			
		||||
                Load average: {status!.system.load_average.one}{" "}
 | 
			
		||||
                {status!.system.load_average.five}{" "}
 | 
			
		||||
                {status!.system.load_average.fifteen}
 | 
			
		||||
              </p>
 | 
			
		||||
              <UptimeWidget uptime={status!.system.uptime} />
 | 
			
		||||
              Number physical cores: {status!.system.physical_core_count}
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      )}
 | 
			
		||||
    />
 | 
			
		||||
        )}
 | 
			
		||||
      />
 | 
			
		||||
    </SectionContainer>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -21,54 +21,39 @@ import {
 | 
			
		||||
} from "@fluentui/react-icons";
 | 
			
		||||
import { filesize } from "filesize";
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { Rights } from "../api/ServerApi";
 | 
			
		||||
import { VMApi, VMInfo, VMState } from "../api/VMApi";
 | 
			
		||||
import { useConfirm } from "../hooks/providers/ConfirmDialogProvider";
 | 
			
		||||
import { useToast } from "../hooks/providers/ToastProvider";
 | 
			
		||||
import { AsyncWidget } from "./AsyncWidget";
 | 
			
		||||
import { SectionContainer } from "./SectionContainer";
 | 
			
		||||
import { VMLiveScreenshot } from "./VMLiveScreenshot";
 | 
			
		||||
import { SectionContainer } from "./SectionContainer";
 | 
			
		||||
 | 
			
		||||
const useStyles = makeStyles({
 | 
			
		||||
  body1Stronger: typographyStyles.body1Stronger,
 | 
			
		||||
  caption1: typographyStyles.caption1,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export function VirtualMachinesWidget(): React.ReactElement {
 | 
			
		||||
  const [list, setList] = React.useState<VMInfo[] | undefined>();
 | 
			
		||||
  const load = async () => {
 | 
			
		||||
    setList(await VMApi.GetList());
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
export function VirtualMachinesWidget(p: {
 | 
			
		||||
  rights: Rights;
 | 
			
		||||
}): React.ReactElement {
 | 
			
		||||
  return (
 | 
			
		||||
    <SectionContainer>
 | 
			
		||||
      <AsyncWidget
 | 
			
		||||
        loadKey={1}
 | 
			
		||||
        load={load}
 | 
			
		||||
        loadingMessage="Loading the list virtual machines..."
 | 
			
		||||
        errMsg="Failed to load the list of virtual machines!"
 | 
			
		||||
        build={() => <VirtualMachinesWidgetInner list={list!} />}
 | 
			
		||||
      />
 | 
			
		||||
      <div
 | 
			
		||||
        style={{
 | 
			
		||||
          display: "flex",
 | 
			
		||||
          flexDirection: "row",
 | 
			
		||||
          flexWrap: "wrap",
 | 
			
		||||
          justifyContent: "center",
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        {p.rights.vms.map((v, n) => (
 | 
			
		||||
          <VMWidget key={n} vm={v} />
 | 
			
		||||
        ))}
 | 
			
		||||
      </div>
 | 
			
		||||
    </SectionContainer>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function VirtualMachinesWidgetInner(p: { list: VMInfo[] }): React.ReactElement {
 | 
			
		||||
  return (
 | 
			
		||||
    <div
 | 
			
		||||
      style={{
 | 
			
		||||
        display: "flex",
 | 
			
		||||
        flexDirection: "row",
 | 
			
		||||
        flexWrap: "wrap",
 | 
			
		||||
        justifyContent: "center",
 | 
			
		||||
      }}
 | 
			
		||||
    >
 | 
			
		||||
      {p.list.map((v, n) => (
 | 
			
		||||
        <VMWidget key={n} vm={v} />
 | 
			
		||||
      ))}{" "}
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function VMWidget(p: { vm: VMInfo }): React.ReactElement {
 | 
			
		||||
  const toast = useToast();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user