Use tabs to organize UI
This commit is contained in:
parent
a4a3b36c4e
commit
e174bd4ae1
@ -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</>;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user