Refactor unit preferences and update chart components

* Refactor user settings to use enum for unit preferences (temperature,
network, disk).
* Update chart components to utilize new unit formatting functions
* Remove deprecated conversion functions and streamline unit handling
across charts.
* Enhance settings page to allow user selection of unit preferences with
updated labels.
This commit is contained in:
henrygd
2025-07-15 18:57:37 -04:00
parent 6576141f54
commit 5c047e4afd
15 changed files with 269 additions and 305 deletions

View File

@@ -2,22 +2,11 @@ import { t } from "@lingui/core/macro"
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
import {
useYAxisWidth,
cn,
formatShortDate,
toFixedWithoutTrailingZeros,
decimalString,
chartMargin,
convertNetworkSpeed,
convertDiskSpeed,
} from "@/lib/utils"
import { useYAxisWidth, cn, formatShortDate, chartMargin } from "@/lib/utils"
// import Spinner from '../spinner'
import { ChartData } from "@/types"
import { memo, useMemo } from "react"
import { useLingui } from "@lingui/react/macro"
import { useStore } from "@nanostores/react"
import { $userSettings } from "@/lib/stores"
/** [label, key, color, opacity] */
type DataKeys = [string, string, number, number]
@@ -34,7 +23,6 @@ const getNestedValue = (path: string, max = false, data: any): number | null =>
export default memo(function AreaChartDefault({
maxToggled = false,
unit = " MB/s",
chartName,
chartData,
max,
@@ -42,35 +30,19 @@ export default memo(function AreaChartDefault({
contentFormatter,
}: {
maxToggled?: boolean
unit?: string
chartName: string
chartData: ChartData
max?: number
tickFormatter?: (value: number) => string
contentFormatter?: (value: number) => string
tickFormatter: (value: number) => string
contentFormatter: ({ value }: { value: number }) => string
}) {
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
const { i18n } = useLingui()
const userSettings = useStore($userSettings)
const { chartTime } = chartData
const showMax = chartTime !== "1h" && maxToggled
// Determine if this is a network chart or disk chart and adjust unit accordingly
const isNetworkChart = chartName === "bw"
const isDiskChart = chartName === "dio" || chartName.startsWith("efs")
const displayUnit = useMemo(() => {
if (isNetworkChart) {
const { symbol } = convertNetworkSpeed(1, userSettings.networkUnit)
return symbol
} else if (isDiskChart) {
const { symbol } = convertDiskSpeed(1, userSettings.diskUnit)
return symbol
}
return unit
}, [isNetworkChart, isDiskChart, userSettings.networkUnit, userSettings.diskUnit, unit])
const dataKeys: DataKeys[] = useMemo(() => {
// [label, key, color, opacity]
if (chartName === "CPU Usage") {
@@ -117,21 +89,7 @@ export default memo(function AreaChartDefault({
className="tracking-tighter"
width={yAxisWidth}
domain={[0, max ?? "auto"]}
tickFormatter={(value) => {
let val: string
if (tickFormatter) {
val = tickFormatter(value)
} else if (isNetworkChart) {
const { value: convertedValue, symbol } = convertNetworkSpeed(value, userSettings.networkUnit)
val = toFixedWithoutTrailingZeros(convertedValue, 2) + symbol
} else if (isDiskChart) {
const { value: convertedValue, symbol } = convertDiskSpeed(value, userSettings.diskUnit)
val = toFixedWithoutTrailingZeros(convertedValue, 2) + symbol
} else {
val = toFixedWithoutTrailingZeros(value, 2) + displayUnit
}
return updateYAxisWidth(val)
}}
tickFormatter={(value) => updateYAxisWidth(tickFormatter(value))}
tickLine={false}
axisLine={false}
/>
@@ -142,18 +100,7 @@ export default memo(function AreaChartDefault({
content={
<ChartTooltipContent
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
contentFormatter={({ value }) => {
if (contentFormatter) {
return contentFormatter(value)
} else if (isNetworkChart) {
const { display } = convertNetworkSpeed(value, userSettings.networkUnit)
return display
} else if (isDiskChart) {
const { display } = convertDiskSpeed(value, userSettings.diskUnit)
return display
}
return decimalString(value) + displayUnit
}}
contentFormatter={contentFormatter}
// indicator="line"
/>
}

View File

@@ -5,19 +5,17 @@ import {
useYAxisWidth,
cn,
formatShortDate,
decimalString,
chartMargin,
toFixedFloat,
getSizeAndUnit,
toFixedWithoutTrailingZeros,
convertNetworkSpeed,
formatBytes,
decimalString,
} from "@/lib/utils"
// import Spinner from '../spinner'
import { useStore } from "@nanostores/react"
import { $containerFilter, $userSettings } from "@/lib/stores"
import { ChartData } from "@/types"
import { Separator } from "../ui/separator"
import { ChartType } from "@/lib/enums"
import { ChartType, Unit } from "@/lib/enums"
export default memo(function ContainerChart({
dataKey,
@@ -31,7 +29,7 @@ export default memo(function ContainerChart({
unit?: string
}) {
const filter = useStore($containerFilter)
const userSettings = useStore($userSettings)
const userSettings = $userSettings.get()
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
const { containerData } = chartData
@@ -89,15 +87,11 @@ export default memo(function ContainerChart({
const val = toFixedWithoutTrailingZeros(value, 2) + unit
return updateYAxisWidth(val)
}
} else if (isNetChart) {
obj.tickFormatter = (value) => {
const { value: convertedValue, symbol } = convertNetworkSpeed(value, userSettings.networkUnit)
return updateYAxisWidth(`${toFixedFloat(convertedValue, 2)}${symbol}`)
}
} else {
obj.tickFormatter = (value) => {
const { v, u } = getSizeAndUnit(value, false)
return updateYAxisWidth(`${toFixedFloat(v, 2)}${u}`)
const chartUnit = isNetChart ? userSettings.unitNet : Unit.Bytes
obj.tickFormatter = (val) => {
const { value, unit } = formatBytes(val, isNetChart, chartUnit, true)
return updateYAxisWidth(decimalString(value, value >= 10 ? 0 : 1) + " " + unit)
}
}
// tooltip formatter
@@ -106,14 +100,14 @@ export default memo(function ContainerChart({
try {
const sent = item?.payload?.[key]?.ns ?? 0
const received = item?.payload?.[key]?.nr ?? 0
const { display: receivedDisplay } = convertNetworkSpeed(received, userSettings.networkUnit)
const { display: sentDisplay } = convertNetworkSpeed(sent, userSettings.networkUnit)
const { value: receivedValue, unit: receivedUnit } = formatBytes(received, true, userSettings.unitNet, true)
const { value: sentValue, unit: sentUnit } = formatBytes(sent, true, userSettings.unitNet, true)
return (
<span className="flex">
{receivedDisplay}
{decimalString(receivedValue)} {receivedUnit}
<span className="opacity-70 ms-0.5"> rx </span>
<Separator orientation="vertical" className="h-3 mx-1.5 bg-primary/40" />
{sentDisplay}
{decimalString(sentValue)} {sentUnit}
<span className="opacity-70 ms-0.5"> tx</span>
</span>
)
@@ -123,8 +117,8 @@ export default memo(function ContainerChart({
}
} else if (chartType === ChartType.Memory) {
obj.toolTipFormatter = (item: any) => {
const { v, u } = getSizeAndUnit(item.value, false)
return decimalString(v, 2) + u
const { value, unit } = formatBytes(item.value, false, Unit.Bytes, true)
return decimalString(value) + " " + unit
}
} else {
obj.toolTipFormatter = (item: any) => decimalString(item.value) + unit

View File

@@ -1,17 +1,10 @@
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
import {
useYAxisWidth,
cn,
formatShortDate,
decimalString,
toFixedFloat,
chartMargin,
getSizeAndUnit,
} from "@/lib/utils"
import { useYAxisWidth, cn, formatShortDate, decimalString, chartMargin, formatBytes } from "@/lib/utils"
import { ChartData } from "@/types"
import { memo } from "react"
import { useLingui } from "@lingui/react/macro"
import { Unit } from "@/lib/enums"
export default memo(function DiskChart({
dataKey,
@@ -53,9 +46,9 @@ export default memo(function DiskChart({
minTickGap={6}
tickLine={false}
axisLine={false}
tickFormatter={(value) => {
const { v, u } = getSizeAndUnit(value)
return updateYAxisWidth(toFixedFloat(v, 2) + u)
tickFormatter={(val) => {
const { value, unit } = formatBytes(val * 1024, false, Unit.Bytes, true)
return updateYAxisWidth(decimalString(value, value >= 10 ? 0 : 1) + " " + unit)
}}
/>
{xAxis(chartData)}
@@ -66,8 +59,8 @@ export default memo(function DiskChart({
<ChartTooltipContent
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
contentFormatter={({ value }) => {
const { v, u } = getSizeAndUnit(value)
return decimalString(v) + u
const { value: convertedValue, unit } = formatBytes(value * 1024, false, Unit.Bytes, true)
return decimalString(convertedValue) + " " + unit
}}
/>
}

View File

@@ -1,9 +1,10 @@
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
import { useYAxisWidth, cn, toFixedFloat, decimalString, formatShortDate, chartMargin } from "@/lib/utils"
import { useYAxisWidth, cn, toFixedFloat, decimalString, formatShortDate, chartMargin, formatBytes } from "@/lib/utils"
import { memo } from "react"
import { ChartData } from "@/types"
import { useLingui } from "@lingui/react/macro"
import { Unit } from "@/lib/enums"
export default memo(function MemChart({ chartData }: { chartData: ChartData }) {
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
@@ -39,8 +40,8 @@ export default memo(function MemChart({ chartData }: { chartData: ChartData }) {
tickLine={false}
axisLine={false}
tickFormatter={(value) => {
const val = toFixedFloat(value, 1)
return updateYAxisWidth(val + " GB")
const { value: convertedValue, unit } = formatBytes(value * 1024, false, Unit.Bytes, true)
return updateYAxisWidth(decimalString(convertedValue, value >= 10 ? 0 : 1) + " " + unit)
}}
/>
)}
@@ -54,8 +55,11 @@ export default memo(function MemChart({ chartData }: { chartData: ChartData }) {
// @ts-ignore
itemSorter={(a, b) => a.order - b.order}
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
contentFormatter={(item) => decimalString(item.value) + " GB"}
// indicator="line"
contentFormatter={({ value }) => {
// mem values are supplied as GB
const { value: convertedValue, unit } = formatBytes(value * 1024, false, Unit.Bytes, true)
return decimalString(convertedValue, convertedValue >= 100 ? 1 : 2) + " " + unit
}}
/>
}
/>

View File

@@ -1,4 +1,4 @@
import { t } from "@lingui/core/macro";
import { t } from "@lingui/core/macro"
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
@@ -9,12 +9,15 @@ import {
toFixedWithoutTrailingZeros,
decimalString,
chartMargin,
formatBytes,
} from "@/lib/utils"
import { ChartData } from "@/types"
import { memo } from "react"
import { $userSettings } from "@/lib/stores"
export default memo(function SwapChart({ chartData }: { chartData: ChartData }) {
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
const userSettings = $userSettings.get()
if (chartData.systemStats.length === 0) {
return null
@@ -37,7 +40,10 @@ export default memo(function SwapChart({ chartData }: { chartData: ChartData })
width={yAxisWidth}
tickLine={false}
axisLine={false}
tickFormatter={(value) => updateYAxisWidth(value + " GB")}
tickFormatter={(value) => {
const { value: convertedValue, unit } = formatBytes(value * 1024, false, userSettings.unitDisk, true)
return updateYAxisWidth(decimalString(convertedValue, value >= 10 ? 0 : 1) + " " + unit)
}}
/>
{xAxis(chartData)}
<ChartTooltip
@@ -46,7 +52,11 @@ export default memo(function SwapChart({ chartData }: { chartData: ChartData })
content={
<ChartTooltipContent
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
contentFormatter={(item) => decimalString(item.value) + " GB"}
contentFormatter={({ value }) => {
// mem values are supplied as GB
const { value: convertedValue, unit } = formatBytes(value * 1024, false, userSettings.unitDisk, true)
return decimalString(convertedValue, convertedValue >= 100 ? 1 : 2) + " " + unit
}}
// indicator="line"
/>
}

View File

@@ -13,9 +13,9 @@ import {
cn,
formatShortDate,
toFixedWithoutTrailingZeros,
decimalString,
chartMargin,
convertTemperature,
formatTemperature,
decimalString,
} from "@/lib/utils"
import { ChartData } from "@/types"
import { memo, useMemo } from "react"
@@ -38,17 +38,13 @@ export default memo(function TemperatureChart({ chartData }: { chartData: ChartD
colors: Record<string, string>
}
const tempSums = {} as Record<string, number>
const unit = userSettings.temperatureUnit || "celsius"
for (let data of chartData.systemStats) {
let newData = { created: data.created } as Record<string, number | string>
let keys = Object.keys(data.stats?.t ?? {})
for (let i = 0; i < keys.length; i++) {
let key = keys[i]
const celsiusTemp = data.stats.t![key]
const { value } = convertTemperature(celsiusTemp, unit)
newData[key] = value
tempSums[key] = (tempSums[key] ?? 0) + value
newData[key] = data.stats.t![key]
tempSums[key] = (tempSums[key] ?? 0) + newData[key]
}
newChartData.data.push(newData)
}
@@ -57,7 +53,7 @@ export default memo(function TemperatureChart({ chartData }: { chartData: ChartD
newChartData.colors[key] = `hsl(${((keys.indexOf(key) * 360) / keys.length) % 360}, 60%, 55%)`
}
return newChartData
}, [chartData, userSettings.temperatureUnit])
}, [chartData])
const colors = Object.keys(newChartData.colors)
@@ -78,10 +74,9 @@ export default memo(function TemperatureChart({ chartData }: { chartData: ChartD
className="tracking-tighter"
domain={[0, "auto"]}
width={yAxisWidth}
tickFormatter={(value) => {
const val = toFixedWithoutTrailingZeros(value, 2)
const { symbol } = convertTemperature(0, userSettings.temperatureUnit || "celsius")
return updateYAxisWidth(val + " " + symbol)
tickFormatter={(val) => {
const { value, unit } = formatTemperature(val, userSettings.unitTemp)
return updateYAxisWidth(toFixedWithoutTrailingZeros(value, 2) + " " + unit)
}}
tickLine={false}
axisLine={false}
@@ -96,8 +91,8 @@ export default memo(function TemperatureChart({ chartData }: { chartData: ChartD
<ChartTooltipContent
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
contentFormatter={(item) => {
const { symbol } = convertTemperature(0, userSettings.temperatureUnit || "celsius")
return decimalString(item.value) + " " + symbol
const { value, unit } = formatTemperature(item.value, userSettings.unitTemp)
return decimalString(value) + " " + unit
}}
filter={filter}
/>