* feat: add GPU monitoring support via Glances integration - Add GPU fields to SystemHealthMonitoring interface (gpuId, name, memoryUtilization, processorUtilization, temperature, fanSpeed) - Extend Glances integration to parse GPU data from /api/4/all endpoint (supports NVIDIA, AMD, and Intel GPUs via Glances) - Add GPU utilization ring to Health Monitoring widget with tooltip showing VRAM usage, temperature, and fan speed - Add GPU time-series chart to System Resources widget (grape color) - Add GPU toggle option to both widgets - Add English translations for GPU labels - Add gpu: [] to all other integrations (dashdot, truenas, unraid, openmediavault) for backward compatibility - Add GPU mock data for development/testing Closes community request from PR #5102 for GPU monitoring support. * fix: implement GPU monitoring for dashdot integration and clarify OMV comment - Add GPU load fetching via dashdot /load/gpu endpoint - Add GPU info parsing from dashdot /info endpoint - Add gpuLoadApi and gpu field to internalServerInfoApi schemas - Add getCurrentGpuLoadAsync method to DashDotIntegration - Map GPU load data to SystemHealthMonitoring gpu array - Add explanatory comment for OMV's empty gpu array Addresses review comments from manuel-rw on PR #5255 * Delete manifest.json
130 lines
4.5 KiB
TypeScript
130 lines
4.5 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { Box, Group, Stack } from "@mantine/core";
|
|
import { useElementSize } from "@mantine/hooks";
|
|
|
|
import { clientApi } from "@homarr/api/client";
|
|
|
|
import type { WidgetComponentProps } from "../definition";
|
|
import { CombinedNetworkTrafficChart } from "./chart/combined-network-traffic";
|
|
import { SystemResourceCPUChart } from "./chart/cpu-chart";
|
|
import { SystemResourceGPUChart } from "./chart/gpu-chart";
|
|
import { SystemResourceMemoryChart } from "./chart/memory-chart";
|
|
import { NetworkTrafficChart } from "./chart/network-traffic";
|
|
|
|
const MAX_QUEUE_SIZE = 15;
|
|
|
|
export default function SystemResources({ integrationIds, options }: WidgetComponentProps<"systemResources">) {
|
|
const { ref, width } = useElementSize();
|
|
|
|
const [data] = clientApi.widget.healthMonitoring.getSystemHealthStatus.useSuspenseQuery({
|
|
integrationIds,
|
|
});
|
|
const memoryCapacityInBytes =
|
|
(data[0]?.healthInfo.memAvailableInBytes ?? 0) + (data[0]?.healthInfo.memUsedInBytes ?? 0);
|
|
|
|
const [items, setItems] = useState<
|
|
{ cpu: number; memory: number; gpu: number; network: { up: number; down: number } | null }[]
|
|
>(
|
|
data.map((item) => ({
|
|
cpu: item.healthInfo.cpuUtilization,
|
|
memory: item.healthInfo.memUsedInBytes,
|
|
gpu:
|
|
item.healthInfo.gpu.length > 0
|
|
? item.healthInfo.gpu.reduce((acc, g) => acc + g.processorUtilization, 0) / item.healthInfo.gpu.length
|
|
: 0,
|
|
network: item.healthInfo.network,
|
|
})),
|
|
);
|
|
|
|
clientApi.widget.healthMonitoring.subscribeSystemHealthStatus.useSubscription(
|
|
{
|
|
integrationIds,
|
|
},
|
|
{
|
|
onData(data) {
|
|
setItems((previousItems) => {
|
|
const next = {
|
|
cpu: data.healthInfo.cpuUtilization,
|
|
memory: data.healthInfo.memUsedInBytes,
|
|
gpu:
|
|
data.healthInfo.gpu.length > 0
|
|
? data.healthInfo.gpu.reduce((acc, g) => acc + g.processorUtilization, 0) / data.healthInfo.gpu.length
|
|
: 0,
|
|
network: data.healthInfo.network,
|
|
};
|
|
|
|
return [...previousItems, next].slice(-MAX_QUEUE_SIZE);
|
|
});
|
|
},
|
|
},
|
|
);
|
|
|
|
const showNetwork =
|
|
items.length === 0 || (items.every((item) => item.network !== null) && options.visibleCharts.includes("network"));
|
|
const rowHeight = `calc((100% - ${(options.visibleCharts.length - 1) * 8}px) / ${options.visibleCharts.length})`;
|
|
|
|
return (
|
|
<Stack gap="xs" p="xs" ref={ref} h="100%">
|
|
{options.visibleCharts.includes("cpu") && (
|
|
<Box h={rowHeight}>
|
|
<SystemResourceCPUChart
|
|
cpuUsageOverTime={items.map((item) => item.cpu)}
|
|
hasShadow={options.hasShadow}
|
|
labelDisplayMode={options.labelDisplayMode}
|
|
/>
|
|
</Box>
|
|
)}
|
|
{options.visibleCharts.includes("memory") && (
|
|
<Box h={rowHeight}>
|
|
<SystemResourceMemoryChart
|
|
memoryUsageOverTime={items.map((item) => item.memory)}
|
|
totalCapacityInBytes={memoryCapacityInBytes}
|
|
hasShadow={options.hasShadow}
|
|
labelDisplayMode={options.labelDisplayMode}
|
|
/>
|
|
</Box>
|
|
)}
|
|
{options.visibleCharts.includes("gpu") && (
|
|
<Box h={rowHeight}>
|
|
<SystemResourceGPUChart
|
|
gpuUsageOverTime={items.map((item) => item.gpu)}
|
|
hasShadow={options.hasShadow}
|
|
labelDisplayMode={options.labelDisplayMode}
|
|
/>
|
|
</Box>
|
|
)}
|
|
{showNetwork &&
|
|
(width > 256 ? (
|
|
<Group h={rowHeight} gap="xs" grow>
|
|
<NetworkTrafficChart
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
usageOverTime={items.map((item) => item.network!.down)}
|
|
isUp={false}
|
|
hasShadow={options.hasShadow}
|
|
labelDisplayMode={options.labelDisplayMode}
|
|
/>
|
|
|
|
<NetworkTrafficChart
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
usageOverTime={items.map((item) => item.network!.up)}
|
|
isUp
|
|
hasShadow={options.hasShadow}
|
|
labelDisplayMode={options.labelDisplayMode}
|
|
/>
|
|
</Group>
|
|
) : (
|
|
<Box h={rowHeight}>
|
|
<CombinedNetworkTrafficChart
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
usageOverTime={items.map((item) => item.network!)}
|
|
hasShadow={options.hasShadow}
|
|
labelDisplayMode={options.labelDisplayMode}
|
|
/>
|
|
</Box>
|
|
))}
|
|
</Stack>
|
|
);
|
|
}
|