mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-09 16:00:59 +00:00
Fix Mac machine disk filtering
This commit is contained in:
parent
c179afa89d
commit
f868487a68
6 changed files with 110 additions and 20 deletions
|
|
@ -232,8 +232,13 @@ export function StackedDiskBar(props: StackedDiskBarProps) {
|
|||
</Show>
|
||||
|
||||
{/* Tooltip for disk breakdown */}
|
||||
<TooltipPortal when={state.tooltipVisible()} x={state.tip.pos().x} y={state.tip.pos().y}>
|
||||
<div class="min-w-[140px]">
|
||||
<TooltipPortal
|
||||
when={state.tooltipVisible()}
|
||||
x={state.tip.pos().x}
|
||||
y={state.tip.pos().y}
|
||||
maxWidth={420}
|
||||
>
|
||||
<div class="w-[360px] max-w-full">
|
||||
<div class="font-medium mb-1 text-slate-300 border-b border-border pb-1">
|
||||
{presentation().tooltipTitle}
|
||||
</div>
|
||||
|
|
@ -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 }}
|
||||
>
|
||||
<div class="flex justify-between gap-3">
|
||||
<span class="flex max-w-[100px] items-center gap-1 truncate text-slate-300">
|
||||
<svg aria-hidden="true" class="h-2 w-2 shrink-0" viewBox="0 0 8 8">
|
||||
<div class="grid grid-cols-[minmax(0,1fr)_auto] gap-x-3 gap-y-0.5">
|
||||
<span class="flex min-w-0 items-start gap-1.5 text-slate-300">
|
||||
<svg aria-hidden="true" class="mt-0.5 h-2 w-2 shrink-0" viewBox="0 0 8 8">
|
||||
<circle cx="4" cy="4" r="4" fill={item.color} />
|
||||
</svg>
|
||||
<span class="truncate">{item.label}</span>
|
||||
<span class="min-w-0 break-all leading-snug">{item.label}</span>
|
||||
</span>
|
||||
<span class="whitespace-nowrap text-slate-300">
|
||||
{item.percent} ({item.used}/{item.total})
|
||||
<span class="whitespace-nowrap text-right font-medium text-base-content">
|
||||
{item.percent}
|
||||
</span>
|
||||
<span class="col-span-2 text-right text-[9px] leading-none text-muted">
|
||||
{item.used}/{item.total}
|
||||
</span>
|
||||
</div>
|
||||
<div class="relative h-1.5 w-full overflow-hidden rounded bg-surface-hover">
|
||||
|
|
|
|||
|
|
@ -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(() => <StackedDiskBar disks={[disk1, disk2]} />);
|
||||
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(() => <StackedDiskBar disks={[disk]} />);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue