From f868487a6871801f7c8d838e49773c905583ab1c Mon Sep 17 00:00:00 2001 From: rcourtman Date: Wed, 3 Jun 2026 10:51:41 +0100 Subject: [PATCH] Fix Mac machine disk filtering --- .../components/Workloads/StackedDiskBar.tsx | 24 ++++++--- .../__tests__/StackedDiskBar.test.tsx | 10 ++++ .../src/utils/__tests__/formatExtra.test.ts | 49 +++++++++++++++++++ frontend-modern/src/utils/format.ts | 36 +++++++++----- pkg/fsfilters/filters.go | 3 ++ pkg/fsfilters/filters_test.go | 8 +++ 6 files changed, 110 insertions(+), 20 deletions(-) diff --git a/frontend-modern/src/components/Workloads/StackedDiskBar.tsx b/frontend-modern/src/components/Workloads/StackedDiskBar.tsx index b54597dc7..d3ed90298 100644 --- a/frontend-modern/src/components/Workloads/StackedDiskBar.tsx +++ b/frontend-modern/src/components/Workloads/StackedDiskBar.tsx @@ -232,8 +232,13 @@ export function StackedDiskBar(props: StackedDiskBarProps) { {/* Tooltip for disk breakdown */} - -
+ +
{presentation().tooltipTitle}
@@ -243,15 +248,18 @@ export function StackedDiskBar(props: StackedDiskBarProps) { class="flex flex-col gap-1 py-0.5" classList={{ 'border-t border-border': idx() > 0 }} > -
- -
+ + - {item.label} + {item.label} - - {item.percent} ({item.used}/{item.total}) + + {item.percent} + + + {item.used}/{item.total}
diff --git a/frontend-modern/src/components/Workloads/__tests__/StackedDiskBar.test.tsx b/frontend-modern/src/components/Workloads/__tests__/StackedDiskBar.test.tsx index f6cd8d068..596730d67 100644 --- a/frontend-modern/src/components/Workloads/__tests__/StackedDiskBar.test.tsx +++ b/frontend-modern/src/components/Workloads/__tests__/StackedDiskBar.test.tsx @@ -318,6 +318,16 @@ describe('StackedDiskBar', () => { expect(screen.getByText('Disk Breakdown')).toBeInTheDocument(); }); + it('wraps long mountpoint labels in the tooltip instead of truncating them', async () => { + const longMountpoint = '/Volumes/Development/projects/pulse/cache/runtime-images'; + const disk1 = makeDisk({ mountpoint: '/' }); + const disk2 = makeDisk({ mountpoint: longMountpoint }); + const { container } = render(() => ); + await fireEvent.mouseEnter(getBarTrigger(container)); + + expect(screen.getByText(longMountpoint)).toHaveClass('break-all'); + }); + it('hides tooltip on mouse leave', async () => { const disk = makeDisk({ mountpoint: '/home' }); const { container } = render(() => ); diff --git a/frontend-modern/src/utils/__tests__/formatExtra.test.ts b/frontend-modern/src/utils/__tests__/formatExtra.test.ts index 214a03e95..b51461b11 100644 --- a/frontend-modern/src/utils/__tests__/formatExtra.test.ts +++ b/frontend-modern/src/utils/__tests__/formatExtra.test.ts @@ -165,4 +165,53 @@ describe('normalizeDiskArray', () => { const result = normalizeDiskArray([{ device: '/dev/sda', mountpoint: '/home' }]); expect(result?.[0].mountpoint).toBe('/home'); }); + + it('filters macOS APFS companion volumes and simulator runtime images', () => { + const result = normalizeDiskArray([ + { + device: '/dev/disk3s1s1', + mountpoint: '/', + filesystem: 'apfs', + total: 245107195904, + used: 213573853184, + }, + { + device: '/dev/disk8s1', + mountpoint: '/Library/Developer/CoreSimulator/Volumes/iOS_23C54', + filesystem: 'apfs', + total: 17645436928, + used: 17188392960, + }, + { + device: '/dev/disk3s5', + mountpoint: '/System/Volumes/Data', + filesystem: 'apfs', + total: 245107195904, + used: 213573853184, + }, + { + device: '/dev/disk7s1', + mountpoint: '/Volumes/Development', + filesystem: 'apfs', + total: 999995129856, + used: 204885471232, + }, + ]); + + expect(result?.map((disk) => disk.mountpoint)).toEqual(['/', '/Volumes/Development']); + }); + + it('returns undefined when all disk rows are non-operational macOS mounts', () => { + expect( + normalizeDiskArray([ + { + device: '/dev/disk3s2', + mountpoint: '/System/Volumes/Preboot', + filesystem: 'apfs', + total: 245107195904, + used: 213573853184, + }, + ]), + ).toBeUndefined(); + }); }); diff --git a/frontend-modern/src/utils/format.ts b/frontend-modern/src/utils/format.ts index 1f1d9b587..642e65f25 100644 --- a/frontend-modern/src/utils/format.ts +++ b/frontend-modern/src/utils/format.ts @@ -1,6 +1,27 @@ // Type-safe formatting utilities import type { Disk } from '@/types/api'; +type DiskInput = { + device?: string; + mountpoint?: string; + filesystem?: string; + type?: string; + total?: number; + used?: number; + free?: number; +}; + +const NON_OPERATIONAL_DISK_MOUNT_PREFIXES = [ + '/System/Volumes/', + '/Library/Developer/CoreSimulator/Volumes/', +]; + +function shouldDisplayDisk(disk: DiskInput): boolean { + const mountpoint = disk.mountpoint?.trim() ?? ''; + if (!mountpoint) return true; + return !NON_OPERATIONAL_DISK_MOUNT_PREFIXES.some((prefix) => mountpoint.startsWith(prefix)); +} + /** * Format bytes to human-readable string with dynamic precision. * @param bytes - Number of bytes to format @@ -289,19 +310,9 @@ export function getShortImageName(fullImage: string | undefined): string { * Normalize raw disk objects (from API/agent) into proper Disk[]. * Calculates `usage` from used/total and defaults missing fields. */ -export function normalizeDiskArray( - disks?: Array<{ - device?: string; - mountpoint?: string; - filesystem?: string; - type?: string; - total?: number; - used?: number; - free?: number; - }>, -): Disk[] | undefined { +export function normalizeDiskArray(disks?: DiskInput[]): Disk[] | undefined { if (!disks || disks.length === 0) return undefined; - return disks.map((d) => { + const normalized = disks.filter(shouldDisplayDisk).map((d) => { const total = d.total ?? 0; const used = d.used ?? 0; const free = d.free ?? (total > 0 ? Math.max(0, total - used) : 0); @@ -316,4 +327,5 @@ export function normalizeDiskArray( device: d.device, }; }); + return normalized.length > 0 ? normalized : undefined; } diff --git a/pkg/fsfilters/filters.go b/pkg/fsfilters/filters.go index 906ab7fab..06c7cd0a6 100644 --- a/pkg/fsfilters/filters.go +++ b/pkg/fsfilters/filters.go @@ -99,6 +99,8 @@ var specialMountPrefixes = []string{ "/var/lib/docker", "/var/lib/containers", "/snap", + "/System/Volumes/", // macOS APFS system/data/preboot/update companion volumes + "/Library/Developer/CoreSimulator/Volumes/", // Xcode simulator runtime disk images } // containerOverlayPatterns detect container overlay filesystem paths from various @@ -124,6 +126,7 @@ var containerPathPrefixes = []string{ // should be excluded, along with a list of reason strings. func ShouldSkipFilesystem(fsType, mountpoint string, totalBytes, usedBytes uint64) (skip bool, reasons []string) { fsTypeLower := strings.ToLower(strings.TrimSpace(fsType)) + mountpoint = strings.TrimSpace(mountpoint) // Check read-only filesystems (existing logic) if reason, isReadOnly := ReadOnlyFilesystemReason(fsType, totalBytes, usedBytes); isReadOnly { diff --git a/pkg/fsfilters/filters_test.go b/pkg/fsfilters/filters_test.go index 3ee85182d..d63278d80 100644 --- a/pkg/fsfilters/filters_test.go +++ b/pkg/fsfilters/filters_test.go @@ -194,6 +194,14 @@ func TestShouldSkipFilesystem(t *testing.T) { {"Windows C drive - should NOT skip", "NTFS", "C:\\", 500 * 1024 * 1024 * 1024, 200 * 1024 * 1024 * 1024, false}, {"Windows D drive - should NOT skip", "NTFS", "D:\\", 1000 * 1024 * 1024 * 1024, 500 * 1024 * 1024 * 1024, false}, + // macOS APFS companion volumes and simulator runtime images should not + // look like separate machine disks. + {"macos root APFS volume - should NOT skip", "apfs", "/", 245107195904, 213573853184, false}, + {"macos external APFS volume - should NOT skip", "apfs", "/Volumes/Development", 999995129856, 204885471232, false}, + {"macos data companion volume", "apfs", "/System/Volumes/Data", 245107195904, 213573853184, true}, + {"macos preboot companion volume", "apfs", "/System/Volumes/Preboot", 245107195904, 213573853184, true}, + {"macos simulator runtime volume", "apfs", "/Library/Developer/CoreSimulator/Volumes/iOS_23E254a", 18058575872, 17593331712, true}, + // Regular filesystems that should NOT be skipped {"ext4 root", "ext4", "/", 100 * 1024 * 1024 * 1024, 50 * 1024 * 1024 * 1024, false}, {"xfs data", "xfs", "/data", 500 * 1024 * 1024 * 1024, 200 * 1024 * 1024 * 1024, false},