From 3c2e6a852fb8464e98fdb16a58e35b87d5925a1e Mon Sep 17 00:00:00 2001 From: Meier Lukas Date: Fri, 19 Sep 2025 16:58:46 +0200 Subject: [PATCH] feat(system-resources-widget): add has-shadow option (#4093) --- package.json | 1 + packages/translation/src/lang/en.json | 3 +++ .../chart/combined-network-traffic.tsx | 3 +++ .../src/system-resources/chart/common-chart.tsx | 12 ++++++++---- .../src/system-resources/chart/cpu-chart.tsx | 5 ++++- .../src/system-resources/chart/memory-chart.tsx | 3 +++ .../system-resources/chart/network-traffic.tsx | 3 +++ .../widgets/src/system-resources/component.tsx | 5 +++++ packages/widgets/src/system-resources/index.ts | 1 + pnpm-lock.yaml | 16 ++++++---------- 10 files changed, 37 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 4e949ebd7..9eda64091 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "nanoid@>=4.0.0 <5.0.9": ">=5.1.5", "prismjs@<1.30.0": ">=1.30.0", "proxmox-api>undici": "7.16.0", + "react-is": "^19.1.1", "rollup@>=4.0.0 <4.22.4": ">=4.50.1", "sha.js@<=2.4.11": ">=2.4.12", "tar-fs@>=3.0.0 <3.0.9": ">=3.1.0", diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json index edab3eef8..a8175679c 100644 --- a/packages/translation/src/lang/en.json +++ b/packages/translation/src/lang/en.json @@ -2503,6 +2503,9 @@ "name": "System resources", "description": "CPU, Memory, Disk and other hardware usage of your system", "option": { + "hasShadow": { + "label": "Enable chart shading" + }, "visibleCharts": { "label": "Visible charts", "description": "Select the charts you want to be visible.", diff --git a/packages/widgets/src/system-resources/chart/combined-network-traffic.tsx b/packages/widgets/src/system-resources/chart/combined-network-traffic.tsx index a9b254b79..38c72c72c 100644 --- a/packages/widgets/src/system-resources/chart/combined-network-traffic.tsx +++ b/packages/widgets/src/system-resources/chart/combined-network-traffic.tsx @@ -9,12 +9,14 @@ import { CommonChart } from "./common-chart"; export const CombinedNetworkTrafficChart = ({ usageOverTime, + hasShadow, labelDisplayMode, }: { usageOverTime: { up: number; down: number; }[]; + hasShadow: boolean; labelDisplayMode: LabelDisplayModeOption; }) => { const chartData = usageOverTime.map((usage, index) => ({ index, up: usage.up, down: usage.down })); @@ -31,6 +33,7 @@ export const CombinedNetworkTrafficChart = ({ title={t("network")} icon={IconNetwork} yAxisProps={{ domain: [0, "dataMax"] }} + chartType={hasShadow ? "area" : "line"} labelDisplayMode={labelDisplayMode} tooltipProps={{ content: ({ payload }) => { diff --git a/packages/widgets/src/system-resources/chart/common-chart.tsx b/packages/widgets/src/system-resources/chart/common-chart.tsx index 833d7007c..f7cc57c27 100644 --- a/packages/widgets/src/system-resources/chart/common-chart.tsx +++ b/packages/widgets/src/system-resources/chart/common-chart.tsx @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import type { ReactNode } from "react"; -import type { LineChartSeries } from "@mantine/charts"; -import { LineChart } from "@mantine/charts"; +import type { AreaChartSeries } from "@mantine/charts"; +import { AreaChart, LineChart } from "@mantine/charts"; import { Card, Center, Group, Loader, Stack, Text, useMantineColorScheme, useMantineTheme } from "@mantine/core"; import { useElementSize, useHover, useMergedRef } from "@mantine/hooks"; import type { TooltipProps, YAxisProps } from "recharts"; @@ -21,16 +21,18 @@ export const CommonChart = ({ tooltipProps, yAxisProps, lastValue, + chartType = "line", }: { data: Record[]; dataKey: string; - series: LineChartSeries[]; + series: AreaChartSeries[]; title: ReactNode; icon: TablerIcon; labelDisplayMode: LabelDisplayModeOption; tooltipProps?: TooltipProps; yAxisProps?: Omit; lastValue?: string; + chartType?: "line" | "area"; }) => { const { ref: elementSizeRef, height } = useElementSize(); const theme = useMantineTheme(); @@ -43,6 +45,7 @@ export const CommonChart = ({ const backgroundColor = scheme.colorScheme === "dark" ? `rgba(57, 57, 57, ${opacity})` : `rgba(246, 247, 248, ${opacity})`; + const ChartComponent = chartType === "line" ? LineChart : AreaChart; const showIcon = labelDisplayMode === "icon" || labelDisplayMode === "textWithIcon"; const showText = labelDisplayMode === "text" || labelDisplayMode === "textWithIcon"; @@ -88,7 +91,7 @@ export const CommonChart = ({ ) : ( - = 64} yAxisProps={yAxisProps} + fillOpacity={chartType === "area" ? 0.3 : undefined} /> )} diff --git a/packages/widgets/src/system-resources/chart/cpu-chart.tsx b/packages/widgets/src/system-resources/chart/cpu-chart.tsx index 3085f5b6d..ceb6ba547 100644 --- a/packages/widgets/src/system-resources/chart/cpu-chart.tsx +++ b/packages/widgets/src/system-resources/chart/cpu-chart.tsx @@ -3,14 +3,16 @@ import { IconCpu } from "@tabler/icons-react"; import { useScopedI18n } from "@homarr/translation/client"; -import { LabelDisplayModeOption } from ".."; +import type { LabelDisplayModeOption } from ".."; import { CommonChart } from "./common-chart"; export const SystemResourceCPUChart = ({ cpuUsageOverTime, + hasShadow, labelDisplayMode, }: { cpuUsageOverTime: number[]; + hasShadow: boolean; labelDisplayMode: LabelDisplayModeOption; }) => { const chartData = cpuUsageOverTime.map((usage, index) => ({ index, usage })); @@ -27,6 +29,7 @@ export const SystemResourceCPUChart = ({ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion cpuUsageOverTime.length > 0 ? `${Math.round(cpuUsageOverTime[cpuUsageOverTime.length - 1]!)}%` : undefined } + chartType={hasShadow ? "area" : "line"} yAxisProps={{ domain: [0, 100] }} labelDisplayMode={labelDisplayMode} tooltipProps={{ diff --git a/packages/widgets/src/system-resources/chart/memory-chart.tsx b/packages/widgets/src/system-resources/chart/memory-chart.tsx index 08a15a997..b0c6d14c1 100644 --- a/packages/widgets/src/system-resources/chart/memory-chart.tsx +++ b/packages/widgets/src/system-resources/chart/memory-chart.tsx @@ -10,10 +10,12 @@ import { CommonChart } from "./common-chart"; export const SystemResourceMemoryChart = ({ memoryUsageOverTime, totalCapacityInBytes, + hasShadow, labelDisplayMode, }: { memoryUsageOverTime: number[]; totalCapacityInBytes: number; + hasShadow: boolean; labelDisplayMode: LabelDisplayModeOption; }) => { const chartData = memoryUsageOverTime.map((usage, index) => ({ index, usage })); @@ -35,6 +37,7 @@ export const SystemResourceMemoryChart = ({ labelDisplayMode={labelDisplayMode} yAxisProps={{ domain: [0, totalCapacityInBytes] }} lastValue={percentageUsed !== undefined ? `${Math.round(percentageUsed * 100)}%` : undefined} + chartType={hasShadow ? "area" : "line"} tooltipProps={{ content: ({ payload }) => { if (!payload) { diff --git a/packages/widgets/src/system-resources/chart/network-traffic.tsx b/packages/widgets/src/system-resources/chart/network-traffic.tsx index 70452fadf..5edbabd61 100644 --- a/packages/widgets/src/system-resources/chart/network-traffic.tsx +++ b/packages/widgets/src/system-resources/chart/network-traffic.tsx @@ -10,10 +10,12 @@ import { CommonChart } from "./common-chart"; export const NetworkTrafficChart = ({ usageOverTime, isUp, + hasShadow, labelDisplayMode, }: { usageOverTime: number[]; isUp: boolean; + hasShadow: boolean; labelDisplayMode: LabelDisplayModeOption; }) => { const chartData = usageOverTime.map((usage, index) => ({ index, usage })); @@ -31,6 +33,7 @@ export const NetworkTrafficChart = ({ icon={isUp ? IconArrowUp : IconArrowDown} yAxisProps={{ domain: [0, upperBound] }} lastValue={`${humanFileSize(Math.round(max))}/s`} + chartType={hasShadow ? "area" : "line"} labelDisplayMode={labelDisplayMode} tooltipProps={{ content: ({ payload }) => { diff --git a/packages/widgets/src/system-resources/component.tsx b/packages/widgets/src/system-resources/component.tsx index 654436705..623ed8aa2 100644 --- a/packages/widgets/src/system-resources/component.tsx +++ b/packages/widgets/src/system-resources/component.tsx @@ -59,6 +59,7 @@ export default function SystemResources({ integrationIds, options }: WidgetCompo item.cpu)} + hasShadow={options.hasShadow} labelDisplayMode={options.labelDisplayMode} /> @@ -68,6 +69,7 @@ export default function SystemResources({ integrationIds, options }: WidgetCompo item.memory)} totalCapacityInBytes={memoryCapacityInBytes} + hasShadow={options.hasShadow} labelDisplayMode={options.labelDisplayMode} /> @@ -79,6 +81,7 @@ export default function SystemResources({ integrationIds, options }: WidgetCompo // 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} /> @@ -86,6 +89,7 @@ export default function SystemResources({ integrationIds, options }: WidgetCompo // eslint-disable-next-line @typescript-eslint/no-non-null-assertion usageOverTime={items.map((item) => item.network!.up)} isUp + hasShadow={options.hasShadow} labelDisplayMode={options.labelDisplayMode} /> @@ -94,6 +98,7 @@ export default function SystemResources({ integrationIds, options }: WidgetCompo item.network!)} + hasShadow={options.hasShadow} labelDisplayMode={options.labelDisplayMode} /> diff --git a/packages/widgets/src/system-resources/index.ts b/packages/widgets/src/system-resources/index.ts index 13b206020..4e0fe27fa 100644 --- a/packages/widgets/src/system-resources/index.ts +++ b/packages/widgets/src/system-resources/index.ts @@ -17,6 +17,7 @@ export const { definition, componentLoader } = createWidgetDefinition("systemRes supportedIntegrations: ["dashDot", "openmediavault", "truenas"], createOptions() { return optionsBuilder.from((factory) => ({ + hasShadow: factory.switch({ defaultValue: true }), visibleCharts: factory.multiSelect({ options: (["cpu", "memory", "network"] as const).map((key) => ({ value: key, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 02a3a2202..32e9789a2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,7 @@ overrides: nanoid@>=4.0.0 <5.0.9: '>=5.1.5' prismjs@<1.30.0: '>=1.30.0' proxmox-api>undici: 7.16.0 + react-is: ^19.1.1 rollup@>=4.0.0 <4.22.4: '>=4.50.1' sha.js@<=2.4.11: '>=2.4.12' tar-fs@>=3.0.0 <3.0.9: '>=3.1.0' @@ -8897,11 +8898,8 @@ packages: peerDependencies: react: ^16.8.4 || ^17.0.0 || ^18.0.0 - react-is@16.13.1: - resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - - react-is@18.3.1: - resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-is@19.1.1: + resolution: {integrity: sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==} react-markdown@10.1.0: resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} @@ -17874,7 +17872,7 @@ snapshots: dependencies: loose-envify: 1.4.0 object-assign: 4.1.1 - react-is: 16.13.1 + react-is: 19.1.1 proper-lockfile@4.1.2: dependencies: @@ -18133,9 +18131,7 @@ snapshots: dependencies: react: 19.1.1 - react-is@16.13.1: {} - - react-is@18.3.1: {} + react-is@19.1.1: {} react-markdown@10.1.0(@types/react@19.1.13)(react@19.1.1): dependencies: @@ -18314,7 +18310,7 @@ snapshots: lodash: 4.17.21 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - react-is: 18.3.1 + react-is: 19.1.1 react-smooth: 4.0.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) recharts-scale: 0.4.5 tiny-invariant: 1.3.3