Use tabs to organize UI
This commit is contained in:
		@@ -1,15 +1,34 @@
 | 
				
			|||||||
import { makeStyles, typographyStyles } from "@fluentui/react-components";
 | 
					import {
 | 
				
			||||||
 | 
					  Tab,
 | 
				
			||||||
 | 
					  TabList,
 | 
				
			||||||
 | 
					  makeStyles,
 | 
				
			||||||
 | 
					  typographyStyles,
 | 
				
			||||||
 | 
					} from "@fluentui/react-components";
 | 
				
			||||||
import { ServerApi } from "./api/ServerApi";
 | 
					import { ServerApi } from "./api/ServerApi";
 | 
				
			||||||
import { AuthRouteWidget } from "./routes/AuthRouteWidget";
 | 
					import { AuthRouteWidget } from "./routes/AuthRouteWidget";
 | 
				
			||||||
import { AsyncWidget } from "./widgets/AsyncWidget";
 | 
					import { AsyncWidget } from "./widgets/AsyncWidget";
 | 
				
			||||||
import { MainMenu } from "./widgets/MainMenu";
 | 
					import { MainMenu } from "./widgets/MainMenu";
 | 
				
			||||||
import { SystemInfoWidget } from "./widgets/SystemInfoWidget";
 | 
					import { SystemInfoWidget } from "./widgets/SystemInfoWidget";
 | 
				
			||||||
import { VirtualMachinesWidget } from "./widgets/VirtualMachinesWidget";
 | 
					import { VirtualMachinesWidget } from "./widgets/VirtualMachinesWidget";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  DesktopFilled,
 | 
				
			||||||
 | 
					  DesktopRegular,
 | 
				
			||||||
 | 
					  Info12Filled,
 | 
				
			||||||
 | 
					  Info12Regular,
 | 
				
			||||||
 | 
					  InfoFilled,
 | 
				
			||||||
 | 
					  InfoRegular,
 | 
				
			||||||
 | 
					  bundleIcon,
 | 
				
			||||||
 | 
					} from "@fluentui/react-icons";
 | 
				
			||||||
 | 
					import React from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const useStyles = makeStyles({
 | 
					const useStyles = makeStyles({
 | 
				
			||||||
  title: typographyStyles.title2,
 | 
					  title: typographyStyles.title2,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const InfoIcon = bundleIcon(InfoFilled, InfoRegular);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DesktopIcon = bundleIcon(DesktopFilled, DesktopRegular);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function App() {
 | 
					export function App() {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <AsyncWidget
 | 
					    <AsyncWidget
 | 
				
			||||||
@@ -24,6 +43,7 @@ export function App() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
function AppInner(): React.ReactElement {
 | 
					function AppInner(): React.ReactElement {
 | 
				
			||||||
  const styles = useStyles();
 | 
					  const styles = useStyles();
 | 
				
			||||||
 | 
					  const [tab, setTab] = React.useState<"vm" | "info">("vm");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (!ServerApi.Config.authenticated && !ServerApi.Config.disable_auth)
 | 
					  if (!ServerApi.Config.authenticated && !ServerApi.Config.disable_auth)
 | 
				
			||||||
    return <AuthRouteWidget />;
 | 
					    return <AuthRouteWidget />;
 | 
				
			||||||
@@ -36,12 +56,31 @@ function AppInner(): React.ReactElement {
 | 
				
			|||||||
        margin: "50px auto",
 | 
					        margin: "50px auto",
 | 
				
			||||||
      }}
 | 
					      }}
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      <div style={{ display: "flex", justifyContent: "space-between" }}>
 | 
					      <span className={styles.title}>VirtWebRemote</span>
 | 
				
			||||||
        <span className={styles.title}>VirtWebRemote</span>
 | 
					      <div
 | 
				
			||||||
        <MainMenu />
 | 
					        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>
 | 
					      </div>
 | 
				
			||||||
      <VirtualMachinesWidget />
 | 
					      {tab === "vm" && <VirtualMachinesWidget />}
 | 
				
			||||||
      <SystemInfoWidget />
 | 
					      {tab === "info" && <SystemInfoWidget />}
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										27
									
								
								remote_frontend/src/api/VMApi.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								remote_frontend/src/api/VMApi.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					import { APIClient } from "./ApiClient";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface VMInfo {
 | 
				
			||||||
 | 
					  uiid: string;
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					  description?: string;
 | 
				
			||||||
 | 
					  architecture: string;
 | 
				
			||||||
 | 
					  memory: number;
 | 
				
			||||||
 | 
					  number_vcpu: number;
 | 
				
			||||||
 | 
					  can_get_state: boolean;
 | 
				
			||||||
 | 
					  can_start: boolean;
 | 
				
			||||||
 | 
					  can_shutdown: boolean;
 | 
				
			||||||
 | 
					  can_kill: boolean;
 | 
				
			||||||
 | 
					  can_reset: boolean;
 | 
				
			||||||
 | 
					  can_suspend: boolean;
 | 
				
			||||||
 | 
					  can_resume: boolean;
 | 
				
			||||||
 | 
					  can_screenshot: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,17 +1,10 @@
 | 
				
			|||||||
import { makeStyles, typographyStyles } from "@fluentui/react-components";
 | 
					 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const useStyles = makeStyles({
 | 
					 | 
				
			||||||
  title: typographyStyles.title3,
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function SectionContainer(
 | 
					export function SectionContainer(
 | 
				
			||||||
  p: React.PropsWithChildren<{ title: string }>
 | 
					  p: React.PropsWithChildren
 | 
				
			||||||
): React.ReactElement {
 | 
					): React.ReactElement {
 | 
				
			||||||
  const styles = useStyles();
 | 
					 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div style={{ margin: "100px 0px" }}>
 | 
					    <div style={{ margin: "50px 15px" }}>
 | 
				
			||||||
      <span className={styles.title}>{p.title}</span>
 | 
					 | 
				
			||||||
      <div style={{ height: "20px" }}></div>
 | 
					      <div style={{ height: "20px" }}></div>
 | 
				
			||||||
      {p.children}
 | 
					      {p.children}
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,7 +15,7 @@ export function SystemInfoWidget(): React.ReactElement {
 | 
				
			|||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <SectionContainer title="System info">
 | 
					    <SectionContainer>
 | 
				
			||||||
      <AsyncWidget
 | 
					      <AsyncWidget
 | 
				
			||||||
        loadKey={1}
 | 
					        loadKey={1}
 | 
				
			||||||
        load={load}
 | 
					        load={load}
 | 
				
			||||||
@@ -84,7 +84,7 @@ function SystemInfoWidgetInner(): React.ReactElement {
 | 
				
			|||||||
              status!.system.available_memory + status!.system.used_memory
 | 
					              status!.system.available_memory + status!.system.used_memory
 | 
				
			||||||
            )}`}
 | 
					            )}`}
 | 
				
			||||||
            validationState="none"
 | 
					            validationState="none"
 | 
				
			||||||
            style={{ flex: 1 }}
 | 
					            style={{ flex: 2 }}
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
            <ProgressBar
 | 
					            <ProgressBar
 | 
				
			||||||
              value={
 | 
					              value={
 | 
				
			||||||
@@ -93,6 +93,7 @@ function SystemInfoWidgetInner(): React.ReactElement {
 | 
				
			|||||||
              }
 | 
					              }
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
          </Field>
 | 
					          </Field>
 | 
				
			||||||
 | 
					          <div style={{ width: "20px" }}></div>
 | 
				
			||||||
          <div style={{ flex: 1 }}>
 | 
					          <div style={{ flex: 1 }}>
 | 
				
			||||||
            <p>
 | 
					            <p>
 | 
				
			||||||
              Load average: {status!.system.load_average.one}{" "}
 | 
					              Load average: {status!.system.load_average.one}{" "}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,27 @@
 | 
				
			|||||||
 | 
					import React from "react";
 | 
				
			||||||
 | 
					import { VMApi, VMInfo } from "../api/VMApi";
 | 
				
			||||||
import { SectionContainer } from "./SectionContainer";
 | 
					import { SectionContainer } from "./SectionContainer";
 | 
				
			||||||
 | 
					import { AsyncWidget } from "./AsyncWidget";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function VirtualMachinesWidget(): React.ReactElement {
 | 
					export function VirtualMachinesWidget(): React.ReactElement {
 | 
				
			||||||
  return <SectionContainer title="Virtual machines">TODO</SectionContainer>;
 | 
					  const [list, setList] = React.useState<VMInfo[] | undefined>();
 | 
				
			||||||
 | 
					  const load = async () => {
 | 
				
			||||||
 | 
					    setList(await VMApi.GetList());
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  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!} />}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    </SectionContainer>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function VirtualMachinesWidgetInner(p: { list: VMInfo[] }): React.ReactElement {
 | 
				
			||||||
 | 
					  return <>build list of vms</>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user