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 { AuthRouteWidget } from "./routes/AuthRouteWidget"; | ||||
| import { AsyncWidget } from "./widgets/AsyncWidget"; | ||||
| import { MainMenu } from "./widgets/MainMenu"; | ||||
| import { SystemInfoWidget } from "./widgets/SystemInfoWidget"; | ||||
| import { VirtualMachinesWidget } from "./widgets/VirtualMachinesWidget"; | ||||
| import { | ||||
|   DesktopFilled, | ||||
|   DesktopRegular, | ||||
|   Info12Filled, | ||||
|   Info12Regular, | ||||
|   InfoFilled, | ||||
|   InfoRegular, | ||||
|   bundleIcon, | ||||
| } from "@fluentui/react-icons"; | ||||
| import React from "react"; | ||||
|  | ||||
| const useStyles = makeStyles({ | ||||
|   title: typographyStyles.title2, | ||||
| }); | ||||
|  | ||||
| const InfoIcon = bundleIcon(InfoFilled, InfoRegular); | ||||
|  | ||||
| const DesktopIcon = bundleIcon(DesktopFilled, DesktopRegular); | ||||
|  | ||||
| export function App() { | ||||
|   return ( | ||||
|     <AsyncWidget | ||||
| @@ -24,6 +43,7 @@ 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 />; | ||||
| @@ -36,12 +56,31 @@ function AppInner(): React.ReactElement { | ||||
|         margin: "50px auto", | ||||
|       }} | ||||
|     > | ||||
|       <div style={{ display: "flex", justifyContent: "space-between" }}> | ||||
|       <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> | ||||
|       <VirtualMachinesWidget /> | ||||
|       <SystemInfoWidget /> | ||||
|       </div> | ||||
|       {tab === "vm" && <VirtualMachinesWidget />} | ||||
|       {tab === "info" && <SystemInfoWidget />} | ||||
|     </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"; | ||||
|  | ||||
| const useStyles = makeStyles({ | ||||
|   title: typographyStyles.title3, | ||||
| }); | ||||
|  | ||||
| export function SectionContainer( | ||||
|   p: React.PropsWithChildren<{ title: string }> | ||||
|   p: React.PropsWithChildren | ||||
| ): React.ReactElement { | ||||
|   const styles = useStyles(); | ||||
|   return ( | ||||
|     <div style={{ margin: "100px 0px" }}> | ||||
|       <span className={styles.title}>{p.title}</span> | ||||
|     <div style={{ margin: "50px 15px" }}> | ||||
|       <div style={{ height: "20px" }}></div> | ||||
|       {p.children} | ||||
|     </div> | ||||
|   | ||||
| @@ -15,7 +15,7 @@ export function SystemInfoWidget(): React.ReactElement { | ||||
|   }; | ||||
|  | ||||
|   return ( | ||||
|     <SectionContainer title="System info"> | ||||
|     <SectionContainer> | ||||
|       <AsyncWidget | ||||
|         loadKey={1} | ||||
|         load={load} | ||||
| @@ -84,7 +84,7 @@ function SystemInfoWidgetInner(): React.ReactElement { | ||||
|               status!.system.available_memory + status!.system.used_memory | ||||
|             )}`} | ||||
|             validationState="none" | ||||
|             style={{ flex: 1 }} | ||||
|             style={{ flex: 2 }} | ||||
|           > | ||||
|             <ProgressBar | ||||
|               value={ | ||||
| @@ -93,6 +93,7 @@ function SystemInfoWidgetInner(): React.ReactElement { | ||||
|               } | ||||
|             /> | ||||
|           </Field> | ||||
|           <div style={{ width: "20px" }}></div> | ||||
|           <div style={{ flex: 1 }}> | ||||
|             <p> | ||||
|               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 { AsyncWidget } from "./AsyncWidget"; | ||||
|  | ||||
| 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