diff --git a/agent/agent.go b/agent/agent.go index e1135b25..91ef0299 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -166,6 +166,7 @@ func (a *Agent) gatherStats(cacheTimeMs uint16) *system.CombinedData { } data.Stats.ExtraFs = make(map[string]*system.FsStats) + data.Info.ExtraFsPct = make(map[string]system.ExtraFsInfo) for name, stats := range a.fsStats { if !stats.Root && stats.DiskTotal > 0 { // Use custom name if available, otherwise use device name @@ -174,6 +175,11 @@ func (a *Agent) gatherStats(cacheTimeMs uint16) *system.CombinedData { key = stats.Name } data.Stats.ExtraFs[key] = stats + // Add percentage info to Info struct for dashboard + if stats.DiskTotal > 0 { + pct := (stats.DiskUsed / stats.DiskTotal) * 100 + data.Info.ExtraFsPct[key] = system.ExtraFsInfo{DiskPct: pct} + } } } slog.Debug("Extra FS", "data", data.Stats.ExtraFs) diff --git a/internal/entities/system/system.go b/internal/entities/system/system.go index b467d892..c51699c5 100644 --- a/internal/entities/system/system.go +++ b/internal/entities/system/system.go @@ -99,6 +99,11 @@ type FsStats struct { MaxDiskWriteBytes uint64 `json:"wbm,omitempty" cbor:"-"` } +// ExtraFsInfo contains summary info for extra filesystems in the system info +type ExtraFsInfo struct { + DiskPct float64 `json:"dp" cbor:"0,keyasint"` +} + type NetIoStats struct { BytesRecv uint64 BytesSent uint64 @@ -146,6 +151,7 @@ type Info struct { // TODO: remove load fields in future release in favor of load avg array LoadAvg [3]float64 `json:"la,omitempty" cbor:"19,keyasint"` ConnectionType ConnectionType `json:"ct,omitempty" cbor:"20,keyasint,omitempty,omitzero"` + ExtraFsPct map[string]ExtraFsInfo `json:"efsp,omitempty" cbor:"21,keyasint,omitempty"` } // Final data structure to return to the hub diff --git a/internal/site/src/components/systems-table/systems-table-columns.tsx b/internal/site/src/components/systems-table/systems-table-columns.tsx index 3f23153f..2fc59379 100644 --- a/internal/site/src/components/systems-table/systems-table-columns.tsx +++ b/internal/site/src/components/systems-table/systems-table-columns.tsx @@ -20,6 +20,7 @@ import { WifiIcon, } from "lucide-react" import { memo, useMemo, useRef, useState } from "react" +import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip" import { isReadOnlyUser, pb } from "@/lib/api" import { ConnectionType, connectionTypeLabels, MeterState, SystemStatus } from "@/lib/enums" import { $longestSystemNameLen, $userSettings } from "@/lib/stores" @@ -153,7 +154,7 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD accessorFn: ({ info }) => info.dp, id: "disk", name: () => t`Disk`, - cell: TableCellWithMeter, + cell: DiskCellWithMultiple, Icon: HardDriveIcon, header: sortableHeader, }, @@ -354,6 +355,92 @@ function TableCellWithMeter(info: CellContext) { ) } +function DiskCellWithMultiple(info: CellContext) { + const { info: sysInfo, status } = info.row.original + const rootDiskPct = sysInfo.dp + const extraFsData = sysInfo.efsp + const extraFsCount = extraFsData ? Object.keys(extraFsData).length : 0 + + const threshold = getMeterState(rootDiskPct) + const meterClass = cn( + "h-full", + (status !== SystemStatus.Up && STATUS_COLORS.paused) || + (threshold === MeterState.Good && STATUS_COLORS.up) || + (threshold === MeterState.Warn && STATUS_COLORS.pending) || + STATUS_COLORS.down + ) + + // No extra disks - show simple meter + if (extraFsCount === 0) { + return ( +
+ {decimalString(rootDiskPct, rootDiskPct >= 10 ? 1 : 2)}% + + + +
+ ) + } + + // Has extra disks - show with tooltip + return ( + + +
+
+ {decimalString(rootDiskPct, rootDiskPct >= 10 ? 1 : 2)}% + + + +
+
+{extraFsCount} more
+
+
+ +
+
+ + {t`All Disks`} +
+
+
+
{t`Root`}
+
+ {decimalString(rootDiskPct, rootDiskPct >= 10 ? 1 : 2)}% + + + +
+
+ {extraFsData && Object.entries(extraFsData).map(([name, fs]) => { + const pct = fs.dp + const fsThreshold = getMeterState(pct) + const fsMeterClass = cn( + "h-full", + (status !== SystemStatus.Up && STATUS_COLORS.paused) || + (fsThreshold === MeterState.Good && STATUS_COLORS.up) || + (fsThreshold === MeterState.Warn && STATUS_COLORS.pending) || + STATUS_COLORS.down + ) + return ( +
+
{name}
+
+ {decimalString(pct, pct >= 10 ? 1 : 2)}% + + + +
+
+ ) + })} +
+
+
+
+ ) +} + export function IndicatorDot({ system, className }: { system: SystemRecord; className?: ClassValue }) { className ||= STATUS_COLORS[system.status as keyof typeof STATUS_COLORS] || "" return ( diff --git a/internal/site/src/types.d.ts b/internal/site/src/types.d.ts index 75b2c51c..67488ca7 100644 --- a/internal/site/src/types.d.ts +++ b/internal/site/src/types.d.ts @@ -77,6 +77,13 @@ export interface SystemInfo { os?: Os /** connection type */ ct?: ConnectionType + /** extra filesystem percentages */ + efsp?: Record +} + +export interface ExtraFsInfo { + /** disk percent */ + dp: number } export interface SystemStats {