diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md
index ff7043ed8..d46e51f11 100644
--- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md
+++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md
@@ -3537,7 +3537,11 @@ the app shell must not add a separate top-level Availability destination.
The Machines page must not pretend its machine list is a generic overview:
the default tab is `Machines`, the Machines table is only for Pulse Agent-backed
resources with host telemetry, and the full availability-check row list belongs
-to the `Availability checks` tab. Servers, laptops, desktops, and comparable
+to the `Availability checks` tab. Its default disk column is an operator risk
+summary: it should surface the highest-usage operational filesystem for the
+machine, keep platform plumbing out of the visible disk set, and leave the full
+per-filesystem breakdown to hover/detail affordances rather than rendering the
+row as a raw mount browser. Servers, laptops, desktops, and comparable
computers monitored only by agentless reachability checks may use
`targetKind=machine` in the availability form, but they stay in Availability
checks until a Pulse Agent registers and supplies CPU, memory, disk, and network
diff --git a/docs/release-control/v6/internal/subsystems/performance-and-scalability.md b/docs/release-control/v6/internal/subsystems/performance-and-scalability.md
index 416e6d4f3..92bfa0889 100644
--- a/docs/release-control/v6/internal/subsystems/performance-and-scalability.md
+++ b/docs/release-control/v6/internal/subsystems/performance-and-scalability.md
@@ -353,7 +353,7 @@ regression protection.
Proxmox, and VMware pages expose filters for the objects visible on that
page rather than generic mixed-workload labels.
19. Extend threshold-slider value-position math, title/label derivation, and drag scroll-lock runtime through `frontend-modern/src/components/Workloads/thresholdSliderModel.ts` and `frontend-modern/src/components/Workloads/useThresholdSliderState.ts` rather than rebuilding slider-local state and pointer lifecycle inside `frontend-modern/src/components/Workloads/ThresholdSlider.tsx`
-20. Extend stacked disk-bar capacity math, segment/tooltip derivation, and resize-observer runtime through `frontend-modern/src/components/Workloads/stackedDiskBarModel.ts` and `frontend-modern/src/components/Workloads/useStackedDiskBarState.ts` rather than rebuilding disk-bar-local state, mode branching, and tooltip shaping inside `frontend-modern/src/components/Workloads/StackedDiskBar.tsx`. Compact multi-disk rows default to same-height per-disk lanes so each filesystem has its own visible usage bar without implying the whole host disk state is one max or aggregate percentage; explicit `mode="stacked"` remains the capacity-contribution stack for callers that intentionally need that presentation, while tooltips continue to carry the full per-disk breakdown.
+20. Extend stacked disk-bar capacity math, segment/tooltip derivation, summary-strategy selection, and resize-observer runtime through `frontend-modern/src/components/Workloads/stackedDiskBarModel.ts` and `frontend-modern/src/components/Workloads/useStackedDiskBarState.ts` rather than rebuilding disk-bar-local state, mode branching, and tooltip shaping inside `frontend-modern/src/components/Workloads/StackedDiskBar.tsx`. Compact multi-disk rows default to same-height per-disk lanes so each filesystem has its own visible usage bar without implying the whole host disk state is one max or aggregate percentage; explicit `mode="stacked"` remains the capacity-contribution stack for callers that intentionally need that presentation, and explicit aggregate callers may choose total-capacity or worst-disk summaries when the owning table's operator job needs one compact risk signal, while tooltips continue to carry the full per-disk breakdown.
21. Extend stacked memory-bar capacity math, balloon/swap derivation, and resize-observer runtime through `frontend-modern/src/components/Workloads/stackedMemoryBarModel.ts` and `frontend-modern/src/components/Workloads/useStackedMemoryBarState.ts` rather than rebuilding memory-bar-local state, tooltip shaping, and label-fit logic inside `frontend-modern/src/components/Workloads/StackedMemoryBar.tsx`
22. Extend metric-bar width, label-fit logic, and resize-observer runtime through `frontend-modern/src/components/Workloads/metricBarModel.ts` and `frontend-modern/src/components/Workloads/useMetricBarState.ts` rather than rebuilding metric-local state and threshold mapping inside `frontend-modern/src/components/Workloads/MetricBar.tsx`
23. Extend enhanced CPU bar usage/anomaly presentation and tooltip runtime through `frontend-modern/src/components/Workloads/enhancedCpuBarModel.ts` and `frontend-modern/src/components/Workloads/useEnhancedCPUBarState.ts` rather than rebuilding tooltip-local state and CPU-threshold formatting inside `frontend-modern/src/components/Workloads/EnhancedCPUBar.tsx`
diff --git a/frontend-modern/src/components/Workloads/StackedDiskBar.tsx b/frontend-modern/src/components/Workloads/StackedDiskBar.tsx
index d3ed90298..6c1f78d5f 100644
--- a/frontend-modern/src/components/Workloads/StackedDiskBar.tsx
+++ b/frontend-modern/src/components/Workloads/StackedDiskBar.tsx
@@ -128,10 +128,10 @@ export function StackedDiskBar(props: StackedDiskBarProps) {
{' '}
- [{props.disks?.length}]
+ {presentation().diskCountLabel}
{/* Anomaly indicator */}
diff --git a/frontend-modern/src/components/Workloads/__tests__/StackedDiskBar.test.tsx b/frontend-modern/src/components/Workloads/__tests__/StackedDiskBar.test.tsx
index 596730d67..4cb135caa 100644
--- a/frontend-modern/src/components/Workloads/__tests__/StackedDiskBar.test.tsx
+++ b/frontend-modern/src/components/Workloads/__tests__/StackedDiskBar.test.tsx
@@ -250,6 +250,46 @@ describe('StackedDiskBar', () => {
const disk2 = makeDisk();
render(() => );
expect(screen.queryByText('[2]')).not.toBeInTheDocument();
+ expect(screen.queryByText('(2)')).not.toBeInTheDocument();
+ });
+
+ it('uses total usage as the default aggregate summary', () => {
+ const disk1 = makeDisk({
+ used: 21474836480, // 20 GiB
+ total: 53687091200, // 50 GiB
+ });
+ const disk2 = makeDisk({
+ used: 53687091200, // 50 GiB
+ total: 107374182400, // 100 GiB
+ });
+ render(() => );
+ expect(screen.getByText('47%')).toBeInTheDocument();
+ });
+
+ it('can summarize aggregate mode by the highest individual disk usage', () => {
+ const disk1 = makeDisk({
+ used: Math.round(107374182400 * 0.2),
+ total: 107374182400,
+ mountpoint: '/data',
+ });
+ const disk2 = makeDisk({
+ used: Math.round(53687091200 * 0.92),
+ total: 53687091200,
+ mountpoint: '/var',
+ });
+ const { container } = render(() => (
+
+ ));
+ const bar = getSingleBarFill(container);
+ expect(screen.getByText('92%')).toBeInTheDocument();
+ expect(screen.getByText('(2)')).toHaveAttribute('title', '2 disks');
+ expect(bar).toHaveAttribute('width', '92');
+ expect(bar?.getAttribute('fill')).toContain('239, 68, 68');
});
});
diff --git a/frontend-modern/src/components/Workloads/stackedDiskBarModel.ts b/frontend-modern/src/components/Workloads/stackedDiskBarModel.ts
index fa3a282d8..6bc8e1c9c 100644
--- a/frontend-modern/src/components/Workloads/stackedDiskBarModel.ts
+++ b/frontend-modern/src/components/Workloads/stackedDiskBarModel.ts
@@ -14,6 +14,8 @@ export interface StackedDiskBarProps {
disks?: Disk[];
aggregateDisk?: Disk;
mode?: 'stacked' | 'aggregate' | 'mini' | 'vertical-bars';
+ summaryStrategy?: 'total' | 'max';
+ showDiskCount?: boolean;
anomaly?: AnomalyReport | null;
thresholds?: MetricDisplayThresholds | null;
}
@@ -53,6 +55,8 @@ export interface StackedDiskVerticalBar {
export interface StackedDiskMaxInfo {
label: string;
percent: number;
+ total: number;
+ used: number;
}
export interface StackedDiskBarPresentation {
@@ -63,6 +67,8 @@ export interface StackedDiskBarPresentation {
barColor: string;
barPercent: number;
containerClass: string;
+ diskCountLabel: string;
+ diskCountTitle: string;
displayLabel: string;
displayPercentValue: number;
displaySublabel: string;
@@ -220,6 +226,8 @@ function getMaxDiskInfo(disks: Disk[]): StackedDiskMaxInfo | null {
maxInfo = {
label: getDiskLabel(disk, index),
percent,
+ total: disk.total,
+ used: disk.used,
};
}
}
@@ -234,6 +242,7 @@ export function buildStackedDiskBarPresentation(
const hasDisks = disks.length > 0;
const hasMultipleDisks = disks.length > 1;
const aggregateMode = props.mode === 'aggregate';
+ const summaryStrategy = props.summaryStrategy ?? 'total';
const miniMode = props.mode === 'mini';
const explicitStackedMode = props.mode === 'stacked';
const verticalBarsMode = props.mode === 'vertical-bars' && hasDisks;
@@ -258,21 +267,31 @@ export function buildStackedDiskBarPresentation(
: 0;
const anomalyRatio = formatAnomalyRatio(props.anomaly) ?? '';
const maxInfo = getMaxDiskInfo(disks);
- const displayPercentValue = overallPercent;
+ const useMaxSummary =
+ aggregateMode && hasMultipleDisks && summaryStrategy === 'max' && maxInfo !== null;
+ const displayPercentValue = useMaxSummary && maxInfo ? maxInfo.percent : overallPercent;
const barPercent = Math.min(displayPercentValue, 100);
const maxLabelShort = maxInfo ? `max ${formatPercent(maxInfo.percent)}` : '';
const maxLabelFull = maxInfo ? `Max ${formatPercent(maxInfo.percent)} (${maxInfo.label})` : '';
const displayLabel = formatPercent(displayPercentValue);
- const displaySublabel = `${formatBytes(totalUsed)}/${formatBytes(totalCapacity)}`;
+ const displaySublabel = useMaxSummary && maxInfo
+ ? maxInfo.label
+ : `${formatBytes(totalUsed)}/${formatBytes(totalCapacity)}`;
+ const diskCountLabel = hasMultipleDisks ? `(${disks.length})` : '';
+ const diskCountTitle = hasMultipleDisks ? `${disks.length} disks` : '';
+ const showDiskCount = Boolean(props.showDiskCount && hasMultipleDisks);
const showMaxLabel =
aggregateMode &&
hasMultipleDisks &&
+ !useMaxSummary &&
maxLabelShort.length > 0 &&
containerWidth >= estimateTextWidth(`${displayLabel} ${maxLabelShort}`);
const showSublabel =
containerWidth >=
estimateTextWidth(
- `${displayLabel}${showMaxLabel ? ` ${maxLabelShort}` : ''} (${displaySublabel})`,
+ `${displayLabel}${showMaxLabel ? ` ${maxLabelShort}` : ''} (${displaySublabel})${
+ showDiskCount ? ` ${diskCountLabel}` : ''
+ }`,
);
const barColor =
aggregateMode && hasMultipleDisks && maxInfo
@@ -340,6 +359,8 @@ export function buildStackedDiskBarPresentation(
: inlineDiskMode && hasDisks
? 'metric-text w-full h-4 min-w-0'
: 'metric-text w-full h-4 flex items-center justify-center',
+ diskCountLabel,
+ diskCountTitle,
displayLabel,
displayPercentValue,
displaySublabel,
@@ -351,7 +372,7 @@ export function buildStackedDiskBarPresentation(
miniDisks,
miniMode,
segments,
- showDiskCount: useStackedSegments,
+ showDiskCount: useStackedSegments || showDiskCount,
showMaxLabel,
showSublabel,
tooltipContent,
diff --git a/frontend-modern/src/features/standalone/AgentsMachinesTable.tsx b/frontend-modern/src/features/standalone/AgentsMachinesTable.tsx
index 74062b709..fa3a0cb47 100644
--- a/frontend-modern/src/features/standalone/AgentsMachinesTable.tsx
+++ b/frontend-modern/src/features/standalone/AgentsMachinesTable.tsx
@@ -1612,7 +1612,9 @@ export const AgentsMachinesTable: Component<{
fallback={metricFallback()}
>
1 ? 'vertical-bars' : undefined}
+ mode={(disks()?.length ?? 0) > 1 ? 'aggregate' : undefined}
+ summaryStrategy="max"
+ showDiskCount
disks={disks()}
aggregateDisk={aggregateDisk()}
/>
diff --git a/frontend-modern/src/features/standalone/__tests__/agentMachineTableModel.test.ts b/frontend-modern/src/features/standalone/__tests__/agentMachineTableModel.test.ts
index 3d8566e5e..33630dc2c 100644
--- a/frontend-modern/src/features/standalone/__tests__/agentMachineTableModel.test.ts
+++ b/frontend-modern/src/features/standalone/__tests__/agentMachineTableModel.test.ts
@@ -1,6 +1,7 @@
import { describe, expect, it } from 'vitest';
import type { Resource } from '@/types/resource';
import {
+ getAgentMachineDiskPercent,
getAgentMachineDiskIODetails,
getAgentMachineNetworkInterfaceDetails,
getAgentMachineRaidArrayDetails,
@@ -273,6 +274,118 @@ describe('agentMachineTableModel', () => {
expect(sorted.map((machine) => machine.id)).toEqual(['warm', 'cool']);
});
+ it('uses the worst operational filesystem as the machine disk percent', () => {
+ const machine = resource({
+ disk: {
+ total: 1000,
+ used: 300,
+ free: 700,
+ current: 30,
+ },
+ agent: {
+ disks: [
+ {
+ device: '/dev/sda2',
+ mountpoint: '/',
+ type: 'ext4',
+ total: 1000,
+ used: 420,
+ free: 580,
+ },
+ {
+ device: '/dev/sdb1',
+ mountpoint: '/var/lib/postgresql',
+ type: 'xfs',
+ total: 1000,
+ used: 920,
+ free: 80,
+ },
+ {
+ device: 'pmxcfs',
+ mountpoint: '/etc/pve',
+ type: 'fuse',
+ total: 1000,
+ used: 990,
+ free: 10,
+ },
+ {
+ device: 'D:',
+ mountpoint: 'D:\\',
+ type: 'NTFS',
+ total: 1000,
+ used: 510,
+ free: 490,
+ },
+ {
+ device: 'system-reserved',
+ mountpoint: 'System Reserved',
+ type: 'NTFS',
+ total: 1000,
+ used: 980,
+ free: 20,
+ },
+ ],
+ },
+ });
+
+ expect(getAgentMachineDiskPercent(machine)).toBe(92);
+ });
+
+ it('sorts machines by worst operational disk pressure', () => {
+ const sorted = sortAgentMachines(
+ [
+ resource({
+ id: 'aggregate-heavy',
+ name: 'Aggregate Heavy',
+ disk: { total: 10_000, used: 6_000, free: 4_000, current: 60 },
+ agent: {
+ disks: [
+ {
+ device: '/dev/sda1',
+ mountpoint: '/',
+ type: 'ext4',
+ total: 10_000,
+ used: 6_000,
+ free: 4_000,
+ },
+ ],
+ },
+ }),
+ resource({
+ id: 'small-hot-volume',
+ name: 'Small Hot Volume',
+ disk: { total: 10_000, used: 2_000, free: 8_000, current: 20 },
+ agent: {
+ disks: [
+ {
+ device: '/dev/sda1',
+ mountpoint: '/',
+ type: 'ext4',
+ total: 9_000,
+ used: 1_000,
+ free: 8_000,
+ },
+ {
+ device: '/dev/sdb1',
+ mountpoint: '/srv/app',
+ type: 'xfs',
+ total: 1_000,
+ used: 910,
+ free: 90,
+ },
+ ],
+ },
+ }),
+ ],
+ 'disk',
+ 'desc',
+ () => '',
+ () => '',
+ );
+
+ expect(sorted.map((machine) => machine.id)).toEqual(['small-hot-volume', 'aggregate-heavy']);
+ });
+
it('matches machine-native search fields beyond the generic resource identity', () => {
const machine = resource({
id: 'richard-mac-mini',
diff --git a/frontend-modern/src/features/standalone/agentMachineTableModel.ts b/frontend-modern/src/features/standalone/agentMachineTableModel.ts
index 3b16e1afb..427fef3e6 100644
--- a/frontend-modern/src/features/standalone/agentMachineTableModel.ts
+++ b/frontend-modern/src/features/standalone/agentMachineTableModel.ts
@@ -1,6 +1,7 @@
import type { ColumnDef } from '@/hooks/useColumnVisibility';
import type { HostDiskIO, HostRAIDArray, HostRAIDDevice } from '@/types/api';
import type { Resource } from '@/types/resource';
+import { normalizeDiskArray } from '@/utils/format';
import { asTrimmedString } from '@/utils/stringUtils';
export type AgentMachineColumnId =
@@ -164,6 +165,31 @@ const positiveTemperature = (value: number | undefined): number | undefined => {
const maxTemperatureReading = (readings: readonly TemperatureReading[]): number | undefined =>
readings.length > 0 ? Math.max(...readings.map((reading) => reading.value)) : undefined;
+const getDiskUsagePercent = (disk: {
+ total?: number;
+ used?: number;
+ usage?: number;
+}): number | undefined => {
+ const total = finiteMetric(disk.total);
+ const used = finiteMetric(disk.used);
+ if (total && total > 0 && typeof used === 'number') {
+ return (used / total) * 100;
+ }
+
+ const usage = finiteMetric(disk.usage);
+ if (usage === undefined) return undefined;
+ return usage <= 1 ? usage * 100 : usage;
+};
+
+const getMaxOperationalDiskPercent = (machine: Resource): number | undefined => {
+ const disks = normalizeDiskArray(machine.agent?.disks) ?? [];
+ return disks.reduce((maxPercent, disk) => {
+ const percent = getDiskUsagePercent(disk);
+ if (percent === undefined) return maxPercent;
+ return maxPercent === undefined ? percent : Math.max(maxPercent, percent);
+ }, undefined);
+};
+
const getPositiveRecordReadings = (
values: Record | undefined,
): TemperatureReading[] =>
@@ -241,6 +267,9 @@ export const getAgentMachineMemoryPercent = (machine: Resource): number | undefi
};
export const getAgentMachineDiskPercent = (machine: Resource): number | undefined => {
+ const maxDiskPercent = getMaxOperationalDiskPercent(machine);
+ if (maxDiskPercent !== undefined) return maxDiskPercent;
+
const total = finiteMetric(machine.disk?.total);
const used = finiteMetric(machine.disk?.used);
if (total && total > 0 && typeof used === 'number') {
diff --git a/frontend-modern/src/utils/__tests__/formatExtra.test.ts b/frontend-modern/src/utils/__tests__/formatExtra.test.ts
index b51461b11..7f4a0e13f 100644
--- a/frontend-modern/src/utils/__tests__/formatExtra.test.ts
+++ b/frontend-modern/src/utils/__tests__/formatExtra.test.ts
@@ -201,6 +201,55 @@ describe('normalizeDiskArray', () => {
expect(result?.map((disk) => disk.mountpoint)).toEqual(['/', '/Volumes/Development']);
});
+ it('filters platform plumbing while preserving operational Windows and Linux disks', () => {
+ const result = normalizeDiskArray([
+ {
+ device: '/dev/sda1',
+ mountpoint: '/boot/firmware',
+ filesystem: 'vfat',
+ total: 536870912,
+ used: 67108864,
+ },
+ {
+ device: '/dev/sda2',
+ mountpoint: '/boot',
+ filesystem: 'ext4',
+ total: 2147483648,
+ used: 268435456,
+ },
+ {
+ device: 'C:',
+ mountpoint: 'C:\\',
+ filesystem: 'NTFS',
+ total: 536870912000,
+ used: 268435456000,
+ },
+ {
+ device: 'D:',
+ mountpoint: 'D:\\',
+ filesystem: 'NTFS',
+ total: 1099511627776,
+ used: 549755813888,
+ },
+ {
+ device: '\\\\?\\Volume{system-reserved}\\',
+ mountpoint: 'System Reserved',
+ filesystem: 'NTFS',
+ total: 524288000,
+ used: 104857600,
+ },
+ {
+ device: 'pmxcfs',
+ mountpoint: '/etc/pve',
+ filesystem: 'fuse',
+ total: 134217728,
+ used: 262144,
+ },
+ ]);
+
+ expect(result?.map((disk) => disk.mountpoint)).toEqual(['/boot', 'C:\\', 'D:\\']);
+ });
+
it('returns undefined when all disk rows are non-operational macOS mounts', () => {
expect(
normalizeDiskArray([
diff --git a/frontend-modern/src/utils/format.ts b/frontend-modern/src/utils/format.ts
index 642e65f25..230f95033 100644
--- a/frontend-modern/src/utils/format.ts
+++ b/frontend-modern/src/utils/format.ts
@@ -16,9 +16,18 @@ const NON_OPERATIONAL_DISK_MOUNT_PREFIXES = [
'/Library/Developer/CoreSimulator/Volumes/',
];
+const NON_OPERATIONAL_DISK_MOUNTPOINTS = new Set([
+ '/boot/efi',
+ '/boot/firmware',
+ '/etc/pve',
+ 'System Reserved',
+]);
+
function shouldDisplayDisk(disk: DiskInput): boolean {
const mountpoint = disk.mountpoint?.trim() ?? '';
if (!mountpoint) return true;
+ if (NON_OPERATIONAL_DISK_MOUNTPOINTS.has(mountpoint)) return false;
+ if (mountpoint.includes('System Reserved')) return false;
return !NON_OPERATIONAL_DISK_MOUNT_PREFIXES.some((prefix) => mountpoint.startsWith(prefix));
}
diff --git a/pkg/fsfilters/filters.go b/pkg/fsfilters/filters.go
index 06c7cd0a6..b619e183d 100644
--- a/pkg/fsfilters/filters.go
+++ b/pkg/fsfilters/filters.go
@@ -103,6 +103,15 @@ var specialMountPrefixes = []string{
"/Library/Developer/CoreSimulator/Volumes/", // Xcode simulator runtime disk images
}
+// specialMountpoints are exact mountpoints that represent platform plumbing
+// rather than operator-managed capacity.
+var specialMountpoints = map[string]bool{
+ "/boot/efi": true,
+ "/boot/firmware": true,
+ "/etc/pve": true,
+ "/var/run": true,
+}
+
// containerOverlayPatterns detect container overlay filesystem paths from various
// container runtimes (Docker, Podman, LXC, EnhanceCP, etc.) that may not be in
// standard locations. These paths should be excluded from disk usage as they
@@ -155,7 +164,7 @@ func ShouldSkipFilesystem(fsType, mountpoint string, totalBytes, usedBytes uint6
}
// Check specific special mountpoints
- if mountpoint == "/boot/efi" || mountpoint == "/var/run" {
+ if specialMountpoints[mountpoint] {
reasons = append(reasons, "special-mountpoint")
}
diff --git a/pkg/fsfilters/filters_test.go b/pkg/fsfilters/filters_test.go
index d63278d80..ab497548e 100644
--- a/pkg/fsfilters/filters_test.go
+++ b/pkg/fsfilters/filters_test.go
@@ -175,6 +175,9 @@ func TestShouldSkipFilesystem(t *testing.T) {
{"/var/lib/docker", "ext4", "/var/lib/docker/overlay2", 1000000, 500000, true},
{"/snap prefix", "ext4", "/snap/core/12345", 1000000, 500000, true},
{"/boot/efi exact", "vfat", "/boot/efi", 512 * 1024 * 1024, 50 * 1024 * 1024, true},
+ {"/boot/firmware exact", "vfat", "/boot/firmware", 512 * 1024 * 1024, 50 * 1024 * 1024, true},
+ {"/boot regular filesystem - should NOT skip", "ext4", "/boot", 2 * 1024 * 1024 * 1024, 256 * 1024 * 1024, false},
+ {"Proxmox cluster config filesystem", "fuse", "/etc/pve", 128 * 1024 * 1024, 256 * 1024, true},
{"/var/lib/containers podman", "ext4", "/var/lib/containers/storage/overlay/abc123/merged", 1000000, 500000, true},
// Container overlay paths in non-standard locations (issue #790)