344 lines
9.2 KiB
TypeScript
344 lines
9.2 KiB
TypeScript
import {
|
|
mdiHarddisk,
|
|
mdiInformation,
|
|
mdiMemory,
|
|
mdiNetwork,
|
|
mdiPackageVariantClosed,
|
|
} from "@mdi/js";
|
|
import Icon from "@mdi/react";
|
|
import {
|
|
Box,
|
|
LinearProgress,
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableRow,
|
|
Typography,
|
|
} from "@mui/material";
|
|
import Grid from "@mui/material/Grid";
|
|
import { PieChart } from "@mui/x-charts";
|
|
import { filesize } from "filesize";
|
|
import humanizeDuration from "humanize-duration";
|
|
import React from "react";
|
|
import {
|
|
DiskInfo,
|
|
NetworkInfo,
|
|
ServerApi,
|
|
ServerSystemInfo,
|
|
} from "../api/ServerApi";
|
|
import { AsyncWidget } from "../widgets/AsyncWidget";
|
|
import { VirtWebPaper } from "../widgets/VirtWebPaper";
|
|
import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer";
|
|
|
|
export function SysInfoRoute(): React.ReactElement {
|
|
const [info, setInfo] = React.useState<ServerSystemInfo>();
|
|
|
|
const load = async () => {
|
|
setInfo(await ServerApi.SystemInfo());
|
|
};
|
|
|
|
return (
|
|
<AsyncWidget
|
|
load={load}
|
|
loadKey={1}
|
|
build={() => <SysInfoRouteInner info={info!} />}
|
|
errMsg="Failed to load system info"
|
|
/>
|
|
);
|
|
}
|
|
|
|
export function SysInfoRouteInner(p: {
|
|
info: ServerSystemInfo;
|
|
}): React.ReactElement {
|
|
const sumDiskUsage = p.info.disks.reduce(
|
|
(prev, disk) => {
|
|
return {
|
|
used: prev.used + disk.total_space - disk.available_space,
|
|
free: prev.free + disk.available_space,
|
|
};
|
|
},
|
|
{ used: 0, free: 0 }
|
|
);
|
|
|
|
return (
|
|
<VirtWebRouteContainer label="Sysinfo">
|
|
<Grid container spacing={2}>
|
|
{/* Memory */}
|
|
<Grid size={{ xs: 4 }}>
|
|
<Box flexGrow={1}>
|
|
<Typography style={{ textAlign: "center" }}>Memory</Typography>
|
|
<PieChart
|
|
series={[
|
|
{
|
|
data: [
|
|
{
|
|
id: 3,
|
|
value: p.info.system.free_memory,
|
|
label: "Free",
|
|
},
|
|
{
|
|
id: 2,
|
|
value: p.info.system.available_memory,
|
|
label: "Available",
|
|
},
|
|
{
|
|
id: 1,
|
|
value: p.info.system.used_memory,
|
|
label: "Used",
|
|
},
|
|
],
|
|
},
|
|
]}
|
|
width={400}
|
|
height={200}
|
|
/>
|
|
</Box>
|
|
</Grid>
|
|
|
|
{/* Disk usage */}
|
|
<Grid size={{ xs: 4 }}>
|
|
<Box flexGrow={1}>
|
|
<Typography style={{ textAlign: "center" }}>Disk usage</Typography>
|
|
<PieChart
|
|
series={[
|
|
{
|
|
data: [
|
|
{
|
|
id: 1,
|
|
value: sumDiskUsage.free,
|
|
label: "Free",
|
|
},
|
|
|
|
{
|
|
id: 2,
|
|
value: sumDiskUsage.used,
|
|
label: "Used",
|
|
},
|
|
],
|
|
},
|
|
]}
|
|
width={400}
|
|
height={200}
|
|
/>
|
|
</Box>
|
|
</Grid>
|
|
|
|
{/* CPU usage */}
|
|
<Grid size={{ xs: 4 }}>
|
|
<Box flexGrow={1}>
|
|
<Typography style={{ textAlign: "center" }}>CPU usage</Typography>
|
|
<PieChart
|
|
series={[
|
|
{
|
|
data: [
|
|
{
|
|
id: 1,
|
|
value: 100 - p.info.system.global_cpu_usage,
|
|
label: "Free",
|
|
},
|
|
|
|
{
|
|
id: 2,
|
|
value: p.info.system.global_cpu_usage,
|
|
label: "Used",
|
|
},
|
|
],
|
|
},
|
|
]}
|
|
width={400}
|
|
height={200}
|
|
/>
|
|
</Box>
|
|
</Grid>
|
|
</Grid>
|
|
|
|
<SysInfoDetailsTable
|
|
label="General"
|
|
icon={<Icon size={"1rem"} path={mdiInformation} />}
|
|
entries={[
|
|
{
|
|
label: "Load",
|
|
value: `${p.info.system.load_average.one} ${p.info.system.load_average.five} ${p.info.system.load_average.fifteen}`,
|
|
},
|
|
{
|
|
label: "Uptime",
|
|
value: humanizeDuration(p.info.system.uptime * 1000),
|
|
},
|
|
{
|
|
label: "Bootime",
|
|
value: new Date(p.info.system.boot_time * 1000).toString(),
|
|
},
|
|
{
|
|
label: "Hypvervisor type",
|
|
value: p.info.hypervisor.type,
|
|
},
|
|
]}
|
|
/>
|
|
|
|
<SysInfoDetailsTable
|
|
label="CPU info"
|
|
icon={<Icon size={"1rem"} path={mdiMemory} />}
|
|
entries={[
|
|
{ label: "Brand", value: p.info.system.cpus[0].brand },
|
|
{
|
|
label: "Vendor ID",
|
|
value: p.info.system.cpus[0].vendor_id,
|
|
},
|
|
{
|
|
label: "CPU usage",
|
|
value: p.info.system.cpus[0].cpu_usage,
|
|
},
|
|
{
|
|
label: "Name",
|
|
value: p.info.system.cpus[0].name,
|
|
},
|
|
{
|
|
label: "CPU model",
|
|
value: p.info.hypervisor.node.cpu_model,
|
|
},
|
|
{
|
|
label: "CPU frequency (MHz)",
|
|
value: p.info.hypervisor.node.cpu_frequency_mhz,
|
|
},
|
|
{
|
|
label: "Number of socket",
|
|
value: p.info.hypervisor.node.number_of_cpu_socket_per_node,
|
|
},
|
|
{
|
|
label: "Number of cores per socket",
|
|
value: p.info.hypervisor.node.number_of_core_per_sockets,
|
|
},
|
|
{
|
|
label: "Number of threads per core",
|
|
value: p.info.hypervisor.node.number_of_threads_per_core,
|
|
},
|
|
]}
|
|
/>
|
|
|
|
<SysInfoDetailsTable
|
|
label="OS info"
|
|
icon={<Icon size={"1rem"} path={mdiPackageVariantClosed} />}
|
|
entries={[
|
|
{ label: "Name", value: p.info.system.name },
|
|
{ label: "Host name", value: p.info.system.host_name },
|
|
{ label: "Long OS version", value: p.info.system.long_os_version },
|
|
{ label: "Kernel version", value: p.info.system.kernel_version },
|
|
]}
|
|
/>
|
|
|
|
<DiskDetailsTable disks={p.info.disks} />
|
|
<NetworksDetailsTable networks={p.info.networks} />
|
|
</VirtWebRouteContainer>
|
|
);
|
|
}
|
|
|
|
function SysInfoDetailsTable(p: {
|
|
label: string;
|
|
icon: React.ReactElement;
|
|
entries: Array<{ label: string; value: string | number }>;
|
|
}): React.ReactElement {
|
|
return (
|
|
<VirtWebPaper
|
|
label={
|
|
<>
|
|
{p.icon} {p.label}
|
|
</>
|
|
}
|
|
>
|
|
<Table>
|
|
<TableBody>
|
|
{p.entries.map((e, c) => (
|
|
<TableRow hover key={c}>
|
|
<TableCell style={{ padding: 5, fontWeight: "bold" }}>
|
|
{e.label}
|
|
</TableCell>
|
|
<TableCell style={{ padding: 5 }}>{e.value}</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</VirtWebPaper>
|
|
);
|
|
}
|
|
|
|
function DiskDetailsTable(p: { disks: DiskInfo[] }): React.ReactElement {
|
|
return (
|
|
<VirtWebPaper
|
|
label={
|
|
<>
|
|
<Icon size={1} path={mdiHarddisk} /> Storage
|
|
</>
|
|
}
|
|
>
|
|
<Table>
|
|
<TableHead>
|
|
<TableRow>
|
|
<TableCell>Name</TableCell>
|
|
<TableCell>Kind</TableCell>
|
|
<TableCell>Mount point</TableCell>
|
|
<TableCell>Total space</TableCell>
|
|
<TableCell>Free space</TableCell>
|
|
<TableCell></TableCell>
|
|
<TableCell>Removable</TableCell>
|
|
</TableRow>
|
|
</TableHead>
|
|
<TableBody>
|
|
{p.disks.map((e, c) => (
|
|
<TableRow hover key={c}>
|
|
<TableCell>{e.name}</TableCell>
|
|
<TableCell>{e.DiskKind}</TableCell>
|
|
<TableCell>{e.mount_point}</TableCell>
|
|
<TableCell>{filesize(e.total_space)}</TableCell>
|
|
<TableCell>{filesize(e.available_space)}</TableCell>
|
|
<TableCell>
|
|
<LinearProgress
|
|
variant="determinate"
|
|
style={{ minWidth: "100px", width: "100%" }}
|
|
value={
|
|
100 * ((e.total_space - e.available_space) / e.total_space)
|
|
}
|
|
/>
|
|
</TableCell>
|
|
<TableCell>{e.is_removable ? "Yes" : "No"}</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</VirtWebPaper>
|
|
);
|
|
}
|
|
|
|
function NetworksDetailsTable(p: {
|
|
networks: NetworkInfo[];
|
|
}): React.ReactElement {
|
|
return (
|
|
<VirtWebPaper
|
|
label={
|
|
<>
|
|
<Icon size={1} path={mdiNetwork} /> Networks
|
|
</>
|
|
}
|
|
>
|
|
<Table>
|
|
<TableHead>
|
|
<TableRow>
|
|
<TableCell>Name</TableCell>
|
|
<TableCell>Total received</TableCell>
|
|
<TableCell>Total transmitted</TableCell>
|
|
</TableRow>
|
|
</TableHead>
|
|
<TableBody>
|
|
{p.networks.map((e, c) => (
|
|
<TableRow hover key={c}>
|
|
<TableCell>{e[0]}</TableCell>
|
|
<TableCell>{filesize(e[1].total_received)}</TableCell>
|
|
<TableCell>{filesize(e[1].total_transmitted)}</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</VirtWebPaper>
|
|
);
|
|
}
|