feat: track battery levels per device

This commit is contained in:
henrygd
2026-08-15 18:16:03 -04:00
parent b4e1f3fafe
commit 68f417575f
18 changed files with 431 additions and 373 deletions

View File

@@ -125,7 +125,7 @@ export default memo(function SystemDetail({ id }: { id: string }) {
<FanChart {...coreProps} />
<BatteryChart {...coreProps} />
<BatteryChart system={system} {...coreProps} />
{hasGpuPowerData && <GpuPowerChart chartData={chartData} grid={grid} dataEmpty={dataEmpty} />}
</div>
@@ -191,7 +191,7 @@ export default memo(function SystemDetail({ id }: { id: string }) {
<BandwidthChart {...coreProps} systemStats={systemStats} />
<TemperatureChart {...coreProps} setPageBottomExtraMargin={setPageBottomExtraMargin} />
<FanChart {...coreProps} />
<BatteryChart {...coreProps} />
<BatteryChart system={system} {...coreProps} />
<SwapChart chartData={chartData} grid={grid} dataEmpty={dataEmpty} systemStats={systemStats} />
{pageBottomExtraMargin > 0 && <div style={{ marginBottom: pageBottomExtraMargin }}></div>}
</div>

View File

@@ -3,7 +3,7 @@ import AreaChartDefault from "@/components/charts/area-chart"
import { batteryStateTranslations } from "@/lib/i18n"
import { $fanFilter, $temperatureFilter, $userSettings } from "@/lib/stores"
import { cn, decimalString, formatTemperature, toFixedFloat } from "@/lib/utils"
import type { ChartData, SystemStatsRecord } from "@/types"
import type { ChartData, SystemRecord, SystemStatsRecord } from "@/types"
import { ChartCard, FilterBar } from "../chart-card"
import LineChartDefault from "@/components/charts/line-chart"
import { useStore } from "@nanostores/react"
@@ -14,18 +14,57 @@ export function BatteryChart({
grid,
dataEmpty,
maxValues,
system,
}: {
chartData: ChartData
grid: boolean
dataEmpty: boolean
maxValues: boolean
system: SystemRecord
}) {
const showBatteryChart = chartData.systemStats.at(-1)?.stats.bat
const batteryNames = useMemo(() => {
const names = new Set<string>()
for (const record of chartData.systemStats) {
for (const name of Object.keys(record.stats?.bats ?? {})) names.add(name)
}
return [...names].sort()
}, [chartData.systemStats])
const hasNamedBatteries = batteryNames.length > 0
const showBatteryChart = hasNamedBatteries || chartData.systemStats.some((record) => record.stats?.bat)
if (!showBatteryChart) {
return null
}
if (hasNamedBatteries) {
const dataPoints = batteryNames.map((name, index) => ({
label: name,
dataKey: ({ stats }: SystemStatsRecord) => stats?.bats?.[name],
color: `hsl(${(index * 360) / batteryNames.length}, 60%, 55%)`,
}))
return (
<ChartCard
empty={dataEmpty}
grid={grid}
title={t`Battery`}
description={`${t({
message: "Current state",
comment: "Context: Battery state",
})}: ${batteryStateTranslations[system.info.bat?.[1] ?? 0]()}`}
>
<LineChartDefault
chartData={chartData}
maxToggled={maxValues}
dataPoints={dataPoints}
domain={[0, 100]}
legend={true}
tickFormatter={(val) => `${val}%`}
contentFormatter={({ value }) => `${value}%`}
/>
</ChartCard>
)
}
return (
<ChartCard
empty={dataEmpty}
@@ -34,7 +73,7 @@ export function BatteryChart({
description={`${t({
message: "Current state",
comment: "Context: Battery state",
})}: ${batteryStateTranslations[chartData.systemStats.at(-1)?.stats.bat?.[1] ?? 0]()}`}
})}: ${batteryStateTranslations[system.info.bat?.[1] ?? 0]()}`}
>
<AreaChartDefault
chartData={chartData}
@@ -210,15 +249,7 @@ export function TemperatureChart({
)
}
export function FanChart({
chartData,
grid,
dataEmpty,
}: {
chartData: ChartData
grid: boolean
dataEmpty: boolean
}) {
export function FanChart({ chartData, grid, dataEmpty }: { chartData: ChartData; grid: boolean; dataEmpty: boolean }) {
const showFanChart = chartData.systemStats.at(-1)?.stats.f
const filter = useStore($fanFilter)

View File

@@ -151,6 +151,8 @@ export interface SystemStats {
g?: Record<string, GPUData>
/** battery percent and state */
bat?: [number, BatteryState]
/** battery percentages by device name */
bats?: Record<string, number>
/** network interfaces [upload bytes, download bytes, total upload bytes, total download bytes] */
ni?: Record<string, [number, number, number, number]>
}