diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 921bb930090..69d3b4583c4 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -13,6 +13,7 @@ import { RobotInformation, Segment, SystemHostInfo, + SystemRuntimeInfo, Timer, TimerInformation, TimerProperties, @@ -278,6 +279,14 @@ export const fetchSystemHostInfo = async (): Promise => { }); }; +export const fetchSystemRuntimeInfo = async (): Promise => { + return valetudoAPI + .get("/system/runtime/info") + .then(({data}) => { + return data; + }); +}; + export const fetchLatestGitHubRelease = async (): Promise => { return axios .get( diff --git a/frontend/src/api/hooks.ts b/frontend/src/api/hooks.ts index b9bad94a91e..ebd30e88560 100644 --- a/frontend/src/api/hooks.ts +++ b/frontend/src/api/hooks.ts @@ -23,6 +23,7 @@ import { fetchSegments, fetchStateAttributes, fetchSystemHostInfo, + fetchSystemRuntimeInfo, fetchTimerInformation, fetchTimerProperties, fetchValetudoInformation, @@ -74,6 +75,7 @@ enum CacheKey { ValetudoVersion = "valetudo_version", GitHubRelease = "github_release", SystemHostInfo = "system_host_info", + SystemRuntimeInfo = "system_runtime_info", MQTTConfiguration = "mqtt_configuration", MQTTProperties = "mqtt_properties", Timers = "timers", @@ -398,6 +400,10 @@ export const useSystemHostInfoQuery = () => { return useQuery(CacheKey.SystemHostInfo, fetchSystemHostInfo); }; +export const useSystemRuntimeInfoQuery = () => { + return useQuery(CacheKey.SystemRuntimeInfo, fetchSystemRuntimeInfo); +}; + export const useMQTTConfigurationQuery = () => { return useQuery(CacheKey.MQTTConfiguration, fetchMQTTConfiguration); }; diff --git a/frontend/src/api/types.ts b/frontend/src/api/types.ts index 9f4db59fc9c..5cc61c48e74 100644 --- a/frontend/src/api/types.ts +++ b/frontend/src/api/types.ts @@ -122,6 +122,18 @@ export interface SystemHostInfo { }; } +export interface SystemRuntimeInfo { + uptime: number; + argv: Array; + execArgv: Array; + execPath: string; + uid: number; + gid: number; + pid: number; + versions: Record; + env: Record +} + export interface MapSegmentationActionRequestParameters { segment_ids: string[]; iterations?: number; diff --git a/frontend/src/settings/About.tsx b/frontend/src/settings/About.tsx index a638bc3a363..d795936d55c 100644 --- a/frontend/src/settings/About.tsx +++ b/frontend/src/settings/About.tsx @@ -1,28 +1,45 @@ import { Box, Button, + ButtonGroup, Card, CardContent, CircularProgress, Container, + Dialog, + DialogActions, + DialogContent, + DialogTitle, Divider, Fade, - Grid, LinearProgress, + Grid, + LinearProgress, Link, + Paper, + Skeleton, + Stack, styled, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, Typography, } from "@material-ui/core"; -import { withStyles } from "@material-ui/styles"; +import {withStyles} from "@material-ui/styles"; import {Refresh as RefreshIcon} from "@material-ui/icons"; import React from "react"; import { useLatestGitHubReleaseLazyQuery, useRobotInformationQuery, useSystemHostInfoQuery, + useSystemRuntimeInfoQuery, useValetudoVersionQuery, } from "../api"; import RatioBar from "../compontents/RatioBar"; import {convertSecondsToHumans} from "../utils"; +import {useIsMobileView} from "../hooks"; const TopRightIconButton = styled(Button)(({theme}) => { return { @@ -37,6 +54,200 @@ const ThickLinearProgressWithTopMargin = withStyles({ } })(LinearProgress); +const SystemRuntimeInfo = (): JSX.Element => { + const { + data: systemRuntimeInfo, + isLoading: systemRuntimeInfoLoading, + refetch: fetchSystemRuntimeInfo, + } = useSystemRuntimeInfoQuery(); + + const [nodeDialogOpen, setNodeDialogOpen] = React.useState(false); + const [envDialogOpen, setEnvDialogOpen] = React.useState(false); + + const mobileView = useIsMobileView(); + + const systemRuntimeInformation = React.useMemo(() => { + if (systemRuntimeInfoLoading) { + return ; + } + + if (!systemRuntimeInfo) { + return No runtime information; + } + + const topItems: Array<[header: string, body: string]> = [ + ["Valetudo uptime", convertSecondsToHumans(systemRuntimeInfo.uptime)], + ["UID", String(systemRuntimeInfo.uid)], + ["GID", String(systemRuntimeInfo.gid)], + ["PID", String(systemRuntimeInfo.pid)], + ["argv", systemRuntimeInfo.argv.join(" ")] + ]; + + const versionRows = Object.keys(systemRuntimeInfo.versions).map(lib => { + const version = systemRuntimeInfo.versions[lib]; + return ( + + {lib} + {version} + + ); + }); + + const environmentRows = Object.keys(systemRuntimeInfo.env).sort().map(key => { + const value = systemRuntimeInfo.env[key]; + return ( + + {key} + {value} + + ); + }); + + return ( + + + {topItems.map(([header, body]) => { + return ( + + + {header} + + {body} + + ); + })} + + + + + + + { + setNodeDialogOpen(false); + }} + scroll={"body"} + fullScreen={mobileView} + > + Node information + + + + + + execPath + + {systemRuntimeInfo.execPath} + + + + execArgv + + {systemRuntimeInfo.execArgv.join(" ")} + + + + + + + + Dependency + Version + + + + {versionRows} + +
+
+
+
+ + + +
+ + { + setEnvDialogOpen(false); + }} + fullScreen={mobileView} + maxWidth={"xl"} + scroll={"body"} + > + Environment + + + + + + Key + Value + + + + {environmentRows} + +
+
+
+ + + +
+
+ ); + }, [systemRuntimeInfoLoading, systemRuntimeInfo, nodeDialogOpen, envDialogOpen, mobileView]); + + return ( + + + + + + Runtime Information + + + + { + return fetchSystemRuntimeInfo(); + }} + > + + + + + + {systemRuntimeInformation} + + + ); +}; + const About = (): JSX.Element => { const { data: robotInformation, @@ -57,7 +268,6 @@ const About = (): JSX.Element => { refetch: fetchSystemHostInfo, } = useSystemHostInfoQuery(); - const systemLoading = robotInformationLoading || versionLoading || systemHostInfoLoading; const newerReleaseAvailable = (githubReleaseInformation?.tag_name ?? "0.0.0") > (version?.release ?? "a"); @@ -232,9 +442,12 @@ const About = (): JSX.Element => { System Load (1, 5, 15 Minutes) - - - + + + @@ -322,6 +535,9 @@ const About = (): JSX.Element => { + + + );