|
|
@@ -0,0 +1,192 @@
|
|
|
+import React, { Component } from "react";
|
|
|
+import * as ReactDom from "react-dom/client";
|
|
|
+import { DAL } from "../DAL/systemInfo";
|
|
|
+import {SystemInfo, CpuInfo, NetworkInfo, DriveInfo} from "../../src/models/systemInfo";
|
|
|
+import menu from "./menu";
|
|
|
+
|
|
|
+interface SystemInfoComponentState {
|
|
|
+ loading: boolean;
|
|
|
+ hidden: boolean;
|
|
|
+}
|
|
|
+
|
|
|
+interface SystemInfoProps {
|
|
|
+ hostname: string;
|
|
|
+
|
|
|
+ setHidden: any;//((val:boolean)=>void|null);
|
|
|
+}
|
|
|
+
|
|
|
+export class SystemInfoComponent extends Component<SystemInfoProps, SystemInfoComponentState> {
|
|
|
+ private data: SystemInfo|undefined = undefined;
|
|
|
+
|
|
|
+ public constructor(props: SystemInfoProps) {
|
|
|
+ super(props);
|
|
|
+
|
|
|
+ this.state = {
|
|
|
+ loading: true,
|
|
|
+ hidden: this.isFiltered()
|
|
|
+ };
|
|
|
+ DAL.SystemInfo.getInfo(this.props.hostname).then(data => {
|
|
|
+ this.data = data; // FIXME error handling
|
|
|
+ this.setState({...this.state, loading: false});
|
|
|
+ });
|
|
|
+ menu.addFilterEventListener(() => {
|
|
|
+ this.refreshFilter();
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private isFiltered(): boolean {
|
|
|
+ const filterShown = menu.getFilteredHostnames();
|
|
|
+ return (filterShown.indexOf(this.props.hostname) < 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ private refreshFilter() {
|
|
|
+ const hidden = this.isFiltered();
|
|
|
+ if (hidden === this.state.hidden)
|
|
|
+ return;
|
|
|
+ this.setState({...this.state, hidden: hidden});
|
|
|
+ }
|
|
|
+
|
|
|
+ private layout(content: React.JSX.Element) {
|
|
|
+ return <section key={this.props.hostname} className={(this.state.hidden ? "hidden" : "")}><div><h1>{this.props.hostname}</h1>{content}</div></section>;
|
|
|
+ }
|
|
|
+ private statItem(key: string, value: string|React.JSX.Element, listKey?: number): React.JSX.Element {
|
|
|
+ if (!value)
|
|
|
+ return <></>;
|
|
|
+ key = key.charAt(0).toUpperCase()+String(key).slice(1);
|
|
|
+ if (listKey === undefined)
|
|
|
+ return <li>{key}: {value}</li>;
|
|
|
+ return <li key={listKey}>{key}: {value}</li>;
|
|
|
+ }
|
|
|
+ private statItemNumber(key: string, value: number): React.JSX.Element {
|
|
|
+ if (!value || isNaN(value))
|
|
|
+ return <></>;
|
|
|
+ return this.statItem(key, `${value}`);
|
|
|
+ }
|
|
|
+ private statItemMhz(key: string, value: number): React.JSX.Element {
|
|
|
+ if (!value || isNaN(value))
|
|
|
+ return <></>;
|
|
|
+ return this.statItem(key, `${value}`);
|
|
|
+ }
|
|
|
+ private stringifyByte(value: number): string {
|
|
|
+ if (!value || isNaN(value))
|
|
|
+ return "";
|
|
|
+ let units = ["Byte", "kB", "MB", "GB", "TB", "PB"];
|
|
|
+ let unitIndex = 0;
|
|
|
+ while (value > 1024 && unitIndex < units.length) {
|
|
|
+ value /= 1024;
|
|
|
+ unitIndex++;
|
|
|
+ }
|
|
|
+ value = Math.round(value*100)/100;
|
|
|
+ return `${value}${units[unitIndex]}`;
|
|
|
+ }
|
|
|
+ private statItemBytes(key: string, value: number): React.JSX.Element {
|
|
|
+ if (!value || isNaN(value))
|
|
|
+ return <></>;
|
|
|
+ return this.statItem(key, this.stringifyByte(value));
|
|
|
+ }
|
|
|
+ private statItemReadOnly(key: string, value: boolean|undefined): React.JSX.Element {
|
|
|
+ if (value === undefined)
|
|
|
+ return <></>;
|
|
|
+ return this.statItem(key, value ? "Read-Write" : "Read Only");
|
|
|
+ }
|
|
|
+ private statItemBoolean(key: string, value: boolean|undefined): React.JSX.Element {
|
|
|
+ if (value === undefined)
|
|
|
+ return <></>;
|
|
|
+ return this.statItem(key, value ? "true" : "false");
|
|
|
+ }
|
|
|
+ private statItemArray(key: string, values: string[]): React.JSX.Element {
|
|
|
+ return this.statItem(key, <ul>{values.map((x, idx) => <li key={idx}>{x}</li>)}</ul>);
|
|
|
+ }
|
|
|
+ private statItemFragArray(key: string, values: React.JSX.Element[]): React.JSX.Element {
|
|
|
+ return this.statItem(key, <ul>{values}</ul>);
|
|
|
+ }
|
|
|
+ private uptime(data: SystemInfo): string {
|
|
|
+ let totalTime = data.uptime;
|
|
|
+ let timeInSec = Math.floor(totalTime % 60);
|
|
|
+ totalTime /= 60;
|
|
|
+ let timeInMin = Math.floor(totalTime % 60);
|
|
|
+ totalTime /= 60;
|
|
|
+ let timeInHours = Math.floor(totalTime % 24);
|
|
|
+ totalTime /= 24;
|
|
|
+ let timeInDays = Math.floor(totalTime);
|
|
|
+ let result = "";
|
|
|
+ if (timeInDays)
|
|
|
+ result += `${timeInDays}d, `;
|
|
|
+ if (result.length || timeInHours)
|
|
|
+ result += (`${timeInHours}:`).padStart(3, "0");
|
|
|
+ result += (`${timeInMin}`).padStart(2, "0")+':'+(`${timeInSec}`).padStart(2, "0");
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ private cpuInfos(cpu: CpuInfo, cpuIndex: number): React.JSX.Element {
|
|
|
+ return this.statItem("model", cpu.model, cpuIndex);
|
|
|
+ }
|
|
|
+ private networkInfo(network: NetworkInfo, netIndex: number): React.JSX.Element {
|
|
|
+ return <li key={netIndex}><ul>
|
|
|
+ {this.statItem("Interface", network.iface)}
|
|
|
+ {this.statItem("Address", network.address)}
|
|
|
+ {this.statItem("Netmask", network.netmask)}
|
|
|
+ {this.statItem("Family", network.family)}
|
|
|
+ {this.statItem("Mac Address", network.mac)}
|
|
|
+ </ul></li>;
|
|
|
+ }
|
|
|
+ private driveInfo(drive: DriveInfo, driveIdx: number): React.JSX.Element {
|
|
|
+ return <li key={driveIdx}><ul>
|
|
|
+ {this.statItem("name", drive.name)}
|
|
|
+ {this.statItem("type", drive.type)}
|
|
|
+ {this.statItem("Mount point", drive.mount)}
|
|
|
+ {this.statItem("label", drive.label)}
|
|
|
+ {this.statItemBytes("Size", drive.size)}
|
|
|
+ {this.statItemBytes("Used Size", drive.usedSize)}
|
|
|
+ {this.statItemBytes("Remaining Size", drive.size - drive.usedSize)}
|
|
|
+ {this.statItemNumber("Used (%)", Math.round(10000 * drive.usedSize / drive.size) / 100)}
|
|
|
+ {this.statItem("Physical", drive.physical)}
|
|
|
+ {this.statItem("UUID", drive.uuid)}
|
|
|
+ {this.statItem("model", drive.model)}
|
|
|
+ {this.statItem("serial", drive.serial)}
|
|
|
+ {this.statItemBoolean("removable", drive.removable)}
|
|
|
+ {this.statItem("protocol", drive.protocol)}
|
|
|
+ {this.statItem("device", drive.device)}
|
|
|
+ {this.statItemReadOnly("Access", drive.rw)}
|
|
|
+ </ul></li>;
|
|
|
+ }
|
|
|
+ public render(): React.JSX.Element {
|
|
|
+ if (this.state.loading)
|
|
|
+ return this.layout(<>Loading..</>);
|
|
|
+ else if (this.data === undefined)
|
|
|
+ return this.layout(<>Error</>);
|
|
|
+ const data = this.data!;
|
|
|
+ return this.layout(<>
|
|
|
+ <ul>
|
|
|
+ {this.statItem("Platform", data.platform)}
|
|
|
+ {this.statItem("Distribution", data.distribution)}
|
|
|
+ {this.statItem("Architecture", data.arch)}
|
|
|
+ {this.statItem("OS Version", data.osVersion)}
|
|
|
+ {this.statItem("uptime", this.uptime(data))}
|
|
|
+ {this.statItem("Architecture", data.arch)}
|
|
|
+ {this.statItem("Node Version", data.nodeVersion)}
|
|
|
+ </ul>
|
|
|
+ <ul>
|
|
|
+ {this.statItem("Manufacturer", data.manufacturer)}
|
|
|
+ {this.statItem("Model", data.model)}
|
|
|
+ {this.statItemMhz("CPU Max Speed", data.cpuMaxSpeed)}
|
|
|
+ {this.statItemFragArray("Cpus", data.cpus.map((x, idx) => this.cpuInfos(x, idx)))}
|
|
|
+ {this.statItemBytes("Memory", data.memory)}
|
|
|
+ {this.statItem("Memory Layout", <ul>{data.memoryLayout.map((x, idx) => <li key={idx}>{this.stringifyByte(x)}</li>)}</ul>)}
|
|
|
+ </ul>
|
|
|
+ <ul>
|
|
|
+ {this.statItemArray("DNS Servers", data.dnsServers)}
|
|
|
+ {this.statItemFragArray("Network Interfaces", data.network.map((x, idx) => this.networkInfo(x, idx)))}
|
|
|
+ </ul>
|
|
|
+ <ul>
|
|
|
+ {this.statItemFragArray("Drives", data.drives.map((x, idx) => this.driveInfo(x, idx)))}
|
|
|
+ </ul>
|
|
|
+ </>);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static async renderMultiple(container: HTMLElement): Promise<React.JSX.Element[]> {
|
|
|
+ let domNodes = (await DAL.SystemInfo.listHosts()).map(hostname => <SystemInfoComponent key={hostname} setHidden={null} hostname={hostname}/>);
|
|
|
+ ReactDom.createRoot(container).render(<>{domNodes}</>);
|
|
|
+ return domNodes;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|