mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-11 22:12:23 +00:00
storage(disks): restore v5 information density to the physical disks table
The v6 physical disks table regressed against its v5 ancestor: device path was demoted to a tooltip (identical models became indistinguishable), SSD wearout vanished from the surface entirely despite flowing all the way into the presentation layer, the Belongs column rendered empty for every disk without a ZFS pool mapping, and a Source badge spent a column saying PVE on every row. Flagged from the side-by-side screenshots in #1471. - Add a Device column (mono devPath, md+). - Add a Life column rendering wearout with v5-style thresholds (md+). - Fall back Belongs to Proxmox's usage string (ZFS / ext4 / BIOS boot) when no pool mapping exists; searchable via the existing haystack. - Demote the Source badge to the row expansion header next to the serial; drop the table column. - Render missing temperatures as muted dashes instead of green ones (getTemperatureTextClass(0) returned the healthy tone). - Tighten rows from 38px to 32px to match the platform table standard.
This commit is contained in:
@@ -38,6 +38,7 @@ import {
|
|||||||
STORAGE_DETAIL_MONO_CHIP_CLASS,
|
STORAGE_DETAIL_MONO_CHIP_CLASS,
|
||||||
STORAGE_DETAIL_SECTION_TITLE_CLASS,
|
STORAGE_DETAIL_SECTION_TITLE_CLASS,
|
||||||
} from '@/features/storageBackups/detailPresentation';
|
} from '@/features/storageBackups/detailPresentation';
|
||||||
|
import { getPhysicalDiskSourceBadgePresentation } from '@/features/storageBackups/diskPresentation';
|
||||||
import { StorageDetailMetricCard } from './StorageDetailMetricCard';
|
import { StorageDetailMetricCard } from './StorageDetailMetricCard';
|
||||||
import { useDiskDetailModel } from './useDiskDetailModel';
|
import { useDiskDetailModel } from './useDiskDetailModel';
|
||||||
|
|
||||||
@@ -82,6 +83,12 @@ export const DiskDetail: Component<DiskDetailProps> = (props) => {
|
|||||||
<span class={STORAGE_DISK_DETAIL_MODEL_CLASS}>{diskData().model || 'Unknown Disk'}</span>
|
<span class={STORAGE_DISK_DETAIL_MODEL_CLASS}>{diskData().model || 'Unknown Disk'}</span>
|
||||||
<span class={STORAGE_DETAIL_MONO_CHIP_CLASS}>{diskData().devPath}</span>
|
<span class={STORAGE_DETAIL_MONO_CHIP_CLASS}>{diskData().devPath}</span>
|
||||||
<span class={STORAGE_DISK_DETAIL_NODE_CLASS}>{diskData().node}</span>
|
<span class={STORAGE_DISK_DETAIL_NODE_CLASS}>{diskData().node}</span>
|
||||||
|
<span
|
||||||
|
class={getPhysicalDiskSourceBadgePresentation(props.disk).className}
|
||||||
|
title="Data source"
|
||||||
|
>
|
||||||
|
{getPhysicalDiskSourceBadgePresentation(props.disk).label}
|
||||||
|
</span>
|
||||||
<Show when={diskData().serial}>
|
<Show when={diskData().serial}>
|
||||||
<span class={STORAGE_DISK_DETAIL_SERIAL_CLASS}>S/N: {diskData().serial}</span>
|
<span class={STORAGE_DISK_DETAIL_SERIAL_CLASS}>S/N: {diskData().serial}</span>
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -16,21 +16,23 @@ import {
|
|||||||
import { formatBytes } from '@/utils/format';
|
import { formatBytes } from '@/utils/format';
|
||||||
import { formatTemperature, getTemperatureTextClass } from '@/utils/temperature';
|
import { formatTemperature, getTemperatureTextClass } from '@/utils/temperature';
|
||||||
import {
|
import {
|
||||||
|
PHYSICAL_DISK_CELL_DEVICE_CLASS,
|
||||||
PHYSICAL_DISK_CELL_DISK_CLASS,
|
PHYSICAL_DISK_CELL_DISK_CLASS,
|
||||||
PHYSICAL_DISK_CELL_HEALTH_CLASS,
|
PHYSICAL_DISK_CELL_HEALTH_CLASS,
|
||||||
PHYSICAL_DISK_CELL_HOST_CLASS,
|
PHYSICAL_DISK_CELL_HOST_CLASS,
|
||||||
|
PHYSICAL_DISK_CELL_LIFE_CLASS,
|
||||||
PHYSICAL_DISK_CELL_PARENT_CLASS,
|
PHYSICAL_DISK_CELL_PARENT_CLASS,
|
||||||
PHYSICAL_DISK_CELL_ROLE_CLASS,
|
PHYSICAL_DISK_CELL_ROLE_CLASS,
|
||||||
PHYSICAL_DISK_CELL_SIZE_CLASS,
|
PHYSICAL_DISK_CELL_SIZE_CLASS,
|
||||||
PHYSICAL_DISK_CELL_SOURCE_CLASS,
|
|
||||||
PHYSICAL_DISK_CELL_TEMP_CLASS,
|
PHYSICAL_DISK_CELL_TEMP_CLASS,
|
||||||
|
PHYSICAL_DISK_COL_DEVICE_CLASS,
|
||||||
PHYSICAL_DISK_COL_DISK_CLASS,
|
PHYSICAL_DISK_COL_DISK_CLASS,
|
||||||
PHYSICAL_DISK_COL_HEALTH_CLASS,
|
PHYSICAL_DISK_COL_HEALTH_CLASS,
|
||||||
PHYSICAL_DISK_COL_HOST_CLASS,
|
PHYSICAL_DISK_COL_HOST_CLASS,
|
||||||
|
PHYSICAL_DISK_COL_LIFE_CLASS,
|
||||||
PHYSICAL_DISK_COL_PARENT_CLASS,
|
PHYSICAL_DISK_COL_PARENT_CLASS,
|
||||||
PHYSICAL_DISK_COL_ROLE_CLASS,
|
PHYSICAL_DISK_COL_ROLE_CLASS,
|
||||||
PHYSICAL_DISK_COL_SIZE_CLASS,
|
PHYSICAL_DISK_COL_SIZE_CLASS,
|
||||||
PHYSICAL_DISK_COL_SOURCE_CLASS,
|
|
||||||
PHYSICAL_DISK_COL_TEMP_CLASS,
|
PHYSICAL_DISK_COL_TEMP_CLASS,
|
||||||
PHYSICAL_DISK_DETAIL_ROW_CELL_CLASS,
|
PHYSICAL_DISK_DETAIL_ROW_CELL_CLASS,
|
||||||
PHYSICAL_DISK_EMPTY_CARD_CLASS,
|
PHYSICAL_DISK_EMPTY_CARD_CLASS,
|
||||||
@@ -42,17 +44,20 @@ import {
|
|||||||
PHYSICAL_DISK_EMPTY_REQUIREMENTS_NOTE_CLASS,
|
PHYSICAL_DISK_EMPTY_REQUIREMENTS_NOTE_CLASS,
|
||||||
PHYSICAL_DISK_EMPTY_REQUIREMENTS_TITLE_CLASS,
|
PHYSICAL_DISK_EMPTY_REQUIREMENTS_TITLE_CLASS,
|
||||||
PHYSICAL_DISK_EMPTY_TITLE_CLASS,
|
PHYSICAL_DISK_EMPTY_TITLE_CLASS,
|
||||||
|
PHYSICAL_DISK_HEADER_DEVICE_CLASS,
|
||||||
PHYSICAL_DISK_HEADER_DISK_CLASS,
|
PHYSICAL_DISK_HEADER_DISK_CLASS,
|
||||||
PHYSICAL_DISK_HEADER_HEALTH_CLASS,
|
PHYSICAL_DISK_HEADER_HEALTH_CLASS,
|
||||||
PHYSICAL_DISK_HEADER_HOST_CLASS,
|
PHYSICAL_DISK_HEADER_HOST_CLASS,
|
||||||
|
PHYSICAL_DISK_HEADER_LIFE_CLASS,
|
||||||
PHYSICAL_DISK_HEADER_PARENT_CLASS,
|
PHYSICAL_DISK_HEADER_PARENT_CLASS,
|
||||||
PHYSICAL_DISK_HEADER_ROLE_CLASS,
|
PHYSICAL_DISK_HEADER_ROLE_CLASS,
|
||||||
PHYSICAL_DISK_HEADER_SIZE_CLASS,
|
PHYSICAL_DISK_HEADER_SIZE_CLASS,
|
||||||
PHYSICAL_DISK_HEADER_SOURCE_CLASS,
|
|
||||||
PHYSICAL_DISK_HEADER_TEMP_CLASS,
|
PHYSICAL_DISK_HEADER_TEMP_CLASS,
|
||||||
PHYSICAL_DISK_HEALTH_LABEL_CLASS,
|
PHYSICAL_DISK_HEALTH_LABEL_CLASS,
|
||||||
PHYSICAL_DISK_HEALTH_SUMMARY_CLASS,
|
PHYSICAL_DISK_HEALTH_SUMMARY_CLASS,
|
||||||
PHYSICAL_DISK_HEALTH_WRAP_CLASS,
|
PHYSICAL_DISK_HEALTH_WRAP_CLASS,
|
||||||
|
PHYSICAL_DISK_DEVICE_TEXT_CLASS,
|
||||||
|
PHYSICAL_DISK_LIFE_CLASS,
|
||||||
PHYSICAL_DISK_MUTED_PLACEHOLDER_CLASS,
|
PHYSICAL_DISK_MUTED_PLACEHOLDER_CLASS,
|
||||||
PHYSICAL_DISK_NAME_TEXT_CLASS,
|
PHYSICAL_DISK_NAME_TEXT_CLASS,
|
||||||
PHYSICAL_DISK_NAME_WRAP_CLASS,
|
PHYSICAL_DISK_NAME_WRAP_CLASS,
|
||||||
@@ -70,9 +75,10 @@ import {
|
|||||||
getPhysicalDiskHealthStatus,
|
getPhysicalDiskHealthStatus,
|
||||||
getPhysicalDiskHealthSummary,
|
getPhysicalDiskHealthSummary,
|
||||||
getPhysicalDiskHostLabel,
|
getPhysicalDiskHostLabel,
|
||||||
|
getPhysicalDiskLifeLabel,
|
||||||
|
getPhysicalDiskLifeTextClass,
|
||||||
getPhysicalDiskParentLabel,
|
getPhysicalDiskParentLabel,
|
||||||
getPhysicalDiskRoleLabel,
|
getPhysicalDiskRoleLabel,
|
||||||
getPhysicalDiskSourceBadgePresentation,
|
|
||||||
} from '@/features/storageBackups/diskPresentation';
|
} from '@/features/storageBackups/diskPresentation';
|
||||||
import type { Resource } from '@/types/resource';
|
import type { Resource } from '@/types/resource';
|
||||||
import type { StorageHealthFilter } from '@/features/storageBackups/models';
|
import type { StorageHealthFilter } from '@/features/storageBackups/models';
|
||||||
@@ -178,29 +184,19 @@ export const DiskList: Component<DiskListProps> = (props) => {
|
|||||||
<Table class={PHYSICAL_DISK_TABLE_CLASS}>
|
<Table class={PHYSICAL_DISK_TABLE_CLASS}>
|
||||||
<colgroup>
|
<colgroup>
|
||||||
<col class={PHYSICAL_DISK_COL_DISK_CLASS} />
|
<col class={PHYSICAL_DISK_COL_DISK_CLASS} />
|
||||||
<col class={PHYSICAL_DISK_COL_SOURCE_CLASS} />
|
<col class={PHYSICAL_DISK_COL_DEVICE_CLASS} />
|
||||||
<col class={PHYSICAL_DISK_COL_HOST_CLASS} />
|
<col class={PHYSICAL_DISK_COL_HOST_CLASS} />
|
||||||
<col class={PHYSICAL_DISK_COL_ROLE_CLASS} />
|
<col class={PHYSICAL_DISK_COL_ROLE_CLASS} />
|
||||||
<col class={PHYSICAL_DISK_COL_PARENT_CLASS} />
|
<col class={PHYSICAL_DISK_COL_PARENT_CLASS} />
|
||||||
<col class={PHYSICAL_DISK_COL_HEALTH_CLASS} />
|
<col class={PHYSICAL_DISK_COL_HEALTH_CLASS} />
|
||||||
|
<col class={PHYSICAL_DISK_COL_LIFE_CLASS} />
|
||||||
<col class={PHYSICAL_DISK_COL_TEMP_CLASS} />
|
<col class={PHYSICAL_DISK_COL_TEMP_CLASS} />
|
||||||
<col class={PHYSICAL_DISK_COL_SIZE_CLASS} />
|
<col class={PHYSICAL_DISK_COL_SIZE_CLASS} />
|
||||||
</colgroup>
|
</colgroup>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow class={PHYSICAL_DISK_TABLE_HEADER_ROW_CLASS}>
|
<TableRow class={PHYSICAL_DISK_TABLE_HEADER_ROW_CLASS}>
|
||||||
<TableHead class={PHYSICAL_DISK_HEADER_DISK_CLASS}>Disk</TableHead>
|
<TableHead class={PHYSICAL_DISK_HEADER_DISK_CLASS}>Disk</TableHead>
|
||||||
<TableHead
|
<TableHead class={PHYSICAL_DISK_HEADER_DEVICE_CLASS}>Device</TableHead>
|
||||||
class={PHYSICAL_DISK_HEADER_SOURCE_CLASS}
|
|
||||||
aria-label="Source"
|
|
||||||
title="Source"
|
|
||||||
>
|
|
||||||
<span aria-hidden="true" class="hidden sm:inline">
|
|
||||||
Source
|
|
||||||
</span>
|
|
||||||
<span aria-hidden="true" class="sm:hidden">
|
|
||||||
Src
|
|
||||||
</span>
|
|
||||||
</TableHead>
|
|
||||||
<TableHead class={PHYSICAL_DISK_HEADER_HOST_CLASS}>Host</TableHead>
|
<TableHead class={PHYSICAL_DISK_HEADER_HOST_CLASS}>Host</TableHead>
|
||||||
<TableHead class={PHYSICAL_DISK_HEADER_ROLE_CLASS}>Role</TableHead>
|
<TableHead class={PHYSICAL_DISK_HEADER_ROLE_CLASS}>Role</TableHead>
|
||||||
<TableHead
|
<TableHead
|
||||||
@@ -211,6 +207,13 @@ export const DiskList: Component<DiskListProps> = (props) => {
|
|||||||
Belongs
|
Belongs
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead class={PHYSICAL_DISK_HEADER_HEALTH_CLASS}>Health</TableHead>
|
<TableHead class={PHYSICAL_DISK_HEADER_HEALTH_CLASS}>Health</TableHead>
|
||||||
|
<TableHead
|
||||||
|
class={PHYSICAL_DISK_HEADER_LIFE_CLASS}
|
||||||
|
aria-label="SSD life remaining"
|
||||||
|
title="SSD life remaining"
|
||||||
|
>
|
||||||
|
Life
|
||||||
|
</TableHead>
|
||||||
<TableHead class={PHYSICAL_DISK_HEADER_TEMP_CLASS}>Temp</TableHead>
|
<TableHead class={PHYSICAL_DISK_HEADER_TEMP_CLASS}>Temp</TableHead>
|
||||||
<TableHead class={PHYSICAL_DISK_HEADER_SIZE_CLASS}>Size</TableHead>
|
<TableHead class={PHYSICAL_DISK_HEADER_SIZE_CLASS}>Size</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@@ -222,7 +225,6 @@ export const DiskList: Component<DiskListProps> = (props) => {
|
|||||||
const status = getPhysicalDiskHealthStatus(data);
|
const status = getPhysicalDiskHealthStatus(data);
|
||||||
const hostLabel = getPhysicalDiskHostLabel(data, disk);
|
const hostLabel = getPhysicalDiskHostLabel(data, disk);
|
||||||
const healthSummary = getPhysicalDiskHealthSummary(status);
|
const healthSummary = getPhysicalDiskHealthSummary(status);
|
||||||
const sourceBadge = getPhysicalDiskSourceBadgePresentation(disk);
|
|
||||||
const isSelected = () => model.selectedDisk()?.id === disk.id;
|
const isSelected = () => model.selectedDisk()?.id === disk.id;
|
||||||
const summarySeriesId = resolvePhysicalDiskMetricResourceId(disk);
|
const summarySeriesId = resolvePhysicalDiskMetricResourceId(disk);
|
||||||
const isSummaryHighlighted = () =>
|
const isSummaryHighlighted = () =>
|
||||||
@@ -269,8 +271,15 @@ export const DiskList: Component<DiskListProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell class={PHYSICAL_DISK_CELL_SOURCE_CLASS}>
|
<TableCell class={PHYSICAL_DISK_CELL_DEVICE_CLASS}>
|
||||||
<span class={sourceBadge.className}>{sourceBadge.label}</span>
|
<Show
|
||||||
|
when={data.devPath}
|
||||||
|
fallback={<span class={PHYSICAL_DISK_MUTED_PLACEHOLDER_CLASS}>—</span>}
|
||||||
|
>
|
||||||
|
<span class={PHYSICAL_DISK_DEVICE_TEXT_CLASS} title={data.devPath}>
|
||||||
|
{data.devPath}
|
||||||
|
</span>
|
||||||
|
</Show>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell class={PHYSICAL_DISK_CELL_HOST_CLASS}>
|
<TableCell class={PHYSICAL_DISK_CELL_HOST_CLASS}>
|
||||||
@@ -325,12 +334,30 @@ export const DiskList: Component<DiskListProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell class={PHYSICAL_DISK_CELL_TEMP_CLASS}>
|
<TableCell class={PHYSICAL_DISK_CELL_LIFE_CLASS}>
|
||||||
<span
|
<Show
|
||||||
class={`${PHYSICAL_DISK_TEMPERATURE_CLASS} ${getTemperatureTextClass(data.temperature)}`}
|
when={getPhysicalDiskLifeLabel(data)}
|
||||||
|
fallback={<span class={PHYSICAL_DISK_MUTED_PLACEHOLDER_CLASS}>—</span>}
|
||||||
>
|
>
|
||||||
{data.temperature > 0 ? formatTemperature(data.temperature) : '—'}
|
<span
|
||||||
</span>
|
class={`${PHYSICAL_DISK_LIFE_CLASS} ${getPhysicalDiskLifeTextClass(data)}`}
|
||||||
|
>
|
||||||
|
{getPhysicalDiskLifeLabel(data)}
|
||||||
|
</span>
|
||||||
|
</Show>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell class={PHYSICAL_DISK_CELL_TEMP_CLASS}>
|
||||||
|
<Show
|
||||||
|
when={data.temperature > 0}
|
||||||
|
fallback={<span class={PHYSICAL_DISK_MUTED_PLACEHOLDER_CLASS}>—</span>}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class={`${PHYSICAL_DISK_TEMPERATURE_CLASS} ${getTemperatureTextClass(data.temperature)}`}
|
||||||
|
>
|
||||||
|
{formatTemperature(data.temperature)}
|
||||||
|
</span>
|
||||||
|
</Show>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell class={PHYSICAL_DISK_CELL_SIZE_CLASS}>
|
<TableCell class={PHYSICAL_DISK_CELL_SIZE_CLASS}>
|
||||||
@@ -355,7 +382,7 @@ export const DiskList: Component<DiskListProps> = (props) => {
|
|||||||
<TableRow data-inline-detail-for={summarySeriesId}>
|
<TableRow data-inline-detail-for={summarySeriesId}>
|
||||||
<TableCell
|
<TableCell
|
||||||
id={detailControlsId}
|
id={detailControlsId}
|
||||||
colSpan={8}
|
colSpan={9}
|
||||||
class={PHYSICAL_DISK_DETAIL_ROW_CELL_CLASS}
|
class={PHYSICAL_DISK_DETAIL_ROW_CELL_CLASS}
|
||||||
>
|
>
|
||||||
<DiskDetail disk={disk} nodes={props.nodes} />
|
<DiskDetail disk={disk} nodes={props.nodes} />
|
||||||
|
|||||||
@@ -105,17 +105,39 @@ describe('DiskList', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(screen.getByRole('columnheader', { name: 'Disk' })).toBeInTheDocument();
|
expect(screen.getByRole('columnheader', { name: 'Disk' })).toBeInTheDocument();
|
||||||
expect(screen.getByRole('columnheader', { name: 'Source' })).toBeInTheDocument();
|
expect(screen.getByRole('columnheader', { name: 'Device' })).toBeInTheDocument();
|
||||||
expect(screen.getByRole('columnheader', { name: 'Host' })).toBeInTheDocument();
|
expect(screen.getByRole('columnheader', { name: 'Host' })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('columnheader', { name: 'SSD life remaining' })).toBeInTheDocument();
|
||||||
|
expect(screen.queryByRole('columnheader', { name: 'Source' })).not.toBeInTheDocument();
|
||||||
expect(screen.getByText('Disk sda')).toBeInTheDocument();
|
expect(screen.getByText('Disk sda')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('/dev/sda')).toBeInTheDocument();
|
||||||
expect(screen.getByText('tower')).toBeInTheDocument();
|
expect(screen.getByText('tower')).toBeInTheDocument();
|
||||||
expect(screen.getByText('PVE')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('Parity')).toBeInTheDocument();
|
expect(screen.getByText('Parity')).toBeInTheDocument();
|
||||||
expect(screen.getByText('Tower Array')).toBeInTheDocument();
|
expect(screen.getByText('Tower Array')).toBeInTheDocument();
|
||||||
expect(screen.getByText('Needs Attention')).toBeInTheDocument();
|
expect(screen.getByText('Needs Attention')).toBeInTheDocument();
|
||||||
expect(screen.getByText('Pending sectors detected.')).toBeInTheDocument();
|
expect(screen.getByText('Pending sectors detected.')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders SSD life and falls back to the Proxmox usage string for Belongs', () => {
|
||||||
|
renderDiskList({
|
||||||
|
disks: [
|
||||||
|
buildDisk('sda', 'tower', {
|
||||||
|
diskType: 'ssd',
|
||||||
|
wearout: 96,
|
||||||
|
storageGroup: '',
|
||||||
|
used: 'ZFS',
|
||||||
|
storageRole: '',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
nodes: [buildNode('node-tower', 'tower')],
|
||||||
|
selectedNode: null,
|
||||||
|
searchTerm: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByText('96%')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('ZFS')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
it('filters disks by search term and supports row expansion', () => {
|
it('filters disks by search term and supports row expansion', () => {
|
||||||
renderDiskList({
|
renderDiskList({
|
||||||
disks: [buildDisk('sda', 'tower'), buildDisk('sdb', 'tower', { model: 'Cache SSD' })],
|
disks: [buildDisk('sda', 'tower'), buildDisk('sdb', 'tower', { model: 'Cache SSD' })],
|
||||||
@@ -196,7 +218,7 @@ describe('DiskList', () => {
|
|||||||
searchTerm: '',
|
searchTerm: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(screen.getByText('TrueNAS')).toBeInTheDocument();
|
expect(screen.getByText('truenas-main')).toBeInTheDocument();
|
||||||
fireEvent.click(screen.getByText('Seagate IronWolf'));
|
fireEvent.click(screen.getByText('Seagate IronWolf'));
|
||||||
expect(screen.getByTestId('disk-detail')).toHaveTextContent('sda');
|
expect(screen.getByTestId('disk-detail')).toHaveTextContent('sda');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ import {
|
|||||||
PHYSICAL_DISK_EMPTY_CARD_CLASS,
|
PHYSICAL_DISK_EMPTY_CARD_CLASS,
|
||||||
PHYSICAL_DISK_ALL_GROUPS_FILTER_LABEL,
|
PHYSICAL_DISK_ALL_GROUPS_FILTER_LABEL,
|
||||||
PHYSICAL_DISK_ALL_ROLES_FILTER_LABEL,
|
PHYSICAL_DISK_ALL_ROLES_FILTER_LABEL,
|
||||||
|
PHYSICAL_DISK_HEADER_DEVICE_CLASS,
|
||||||
PHYSICAL_DISK_HEADER_DISK_CLASS,
|
PHYSICAL_DISK_HEADER_DISK_CLASS,
|
||||||
PHYSICAL_DISK_HEADER_SOURCE_CLASS,
|
PHYSICAL_DISK_HEADER_LIFE_CLASS,
|
||||||
PHYSICAL_DISK_NAME_TEXT_CLASS,
|
PHYSICAL_DISK_NAME_TEXT_CLASS,
|
||||||
PHYSICAL_DISK_SOURCE_BADGE_CLASS,
|
PHYSICAL_DISK_SOURCE_BADGE_CLASS,
|
||||||
PHYSICAL_DISK_TABLE_CLASS,
|
PHYSICAL_DISK_TABLE_CLASS,
|
||||||
@@ -20,6 +21,8 @@ import {
|
|||||||
getPhysicalDiskHealthStatus,
|
getPhysicalDiskHealthStatus,
|
||||||
getPhysicalDiskHealthSummary,
|
getPhysicalDiskHealthSummary,
|
||||||
getPhysicalDiskHostLabel,
|
getPhysicalDiskHostLabel,
|
||||||
|
getPhysicalDiskLifeLabel,
|
||||||
|
getPhysicalDiskLifeTextClass,
|
||||||
getPhysicalDiskNormalizedHealth,
|
getPhysicalDiskNormalizedHealth,
|
||||||
getPhysicalDiskParentLabel,
|
getPhysicalDiskParentLabel,
|
||||||
getPhysicalDiskPlatformLabel,
|
getPhysicalDiskPlatformLabel,
|
||||||
@@ -63,7 +66,8 @@ describe('diskPresentation', () => {
|
|||||||
expect(PHYSICAL_DISK_TABLE_CLASS).toBe('w-full table-fixed text-xs');
|
expect(PHYSICAL_DISK_TABLE_CLASS).toBe('w-full table-fixed text-xs');
|
||||||
expect(PHYSICAL_DISK_TABLE_ROW_HOVER_CLASS).toContain('hover:bg-surface-hover');
|
expect(PHYSICAL_DISK_TABLE_ROW_HOVER_CLASS).toContain('hover:bg-surface-hover');
|
||||||
expect(PHYSICAL_DISK_HEADER_DISK_CLASS).toContain('uppercase');
|
expect(PHYSICAL_DISK_HEADER_DISK_CLASS).toContain('uppercase');
|
||||||
expect(PHYSICAL_DISK_HEADER_SOURCE_CLASS).toContain('sm:w-[11%]');
|
expect(PHYSICAL_DISK_HEADER_DEVICE_CLASS).toContain('md:table-cell');
|
||||||
|
expect(PHYSICAL_DISK_HEADER_LIFE_CLASS).toContain('md:table-cell');
|
||||||
expect(PHYSICAL_DISK_NAME_TEXT_CLASS).toContain('font-semibold');
|
expect(PHYSICAL_DISK_NAME_TEXT_CLASS).toContain('font-semibold');
|
||||||
expect(PHYSICAL_DISK_SOURCE_BADGE_CLASS).toContain('justify-center');
|
expect(PHYSICAL_DISK_SOURCE_BADGE_CLASS).toContain('justify-center');
|
||||||
|
|
||||||
@@ -119,6 +123,19 @@ describe('diskPresentation', () => {
|
|||||||
storageGroup: 'tank',
|
storageGroup: 'tank',
|
||||||
}),
|
}),
|
||||||
).toBe('tank');
|
).toBe('tank');
|
||||||
|
expect(getPhysicalDiskParentLabel(makeDiskData({ storageGroup: '', used: 'ZFS' }))).toBe('ZFS');
|
||||||
|
expect(getPhysicalDiskParentLabel(makeDiskData({ storageGroup: 'tank', used: 'ZFS' }))).toBe(
|
||||||
|
'tank',
|
||||||
|
);
|
||||||
|
expect(getPhysicalDiskParentLabel(makeDiskData({ storageGroup: '', used: 'unknown' }))).toBe(
|
||||||
|
'',
|
||||||
|
);
|
||||||
|
expect(getPhysicalDiskLifeLabel(makeDiskData({ wearout: 96 }))).toBe('96%');
|
||||||
|
expect(getPhysicalDiskLifeLabel(makeDiskData({ wearout: 0 }))).toBe('');
|
||||||
|
expect(getPhysicalDiskLifeLabel(makeDiskData({ wearout: -1 }))).toBe('');
|
||||||
|
expect(getPhysicalDiskLifeTextClass(makeDiskData({ wearout: 96 }))).toContain('text-green');
|
||||||
|
expect(getPhysicalDiskLifeTextClass(makeDiskData({ wearout: 35 }))).toContain('text-amber');
|
||||||
|
expect(getPhysicalDiskLifeTextClass(makeDiskData({ wearout: 9 }))).toContain('text-red');
|
||||||
expect(getPhysicalDiskPlatformLabel({} as Resource, 'PVE')).toBe('PVE');
|
expect(getPhysicalDiskPlatformLabel({} as Resource, 'PVE')).toBe('PVE');
|
||||||
expect(
|
expect(
|
||||||
getPhysicalDiskSourceBadgePresentation({
|
getPhysicalDiskSourceBadgePresentation({
|
||||||
|
|||||||
@@ -96,25 +96,28 @@ export const PHYSICAL_DISK_TABLE_BODY_CLASS = 'divide-y divide-border';
|
|||||||
export const PHYSICAL_DISK_TABLE_ROW_CLASS = 'cursor-pointer transition-colors';
|
export const PHYSICAL_DISK_TABLE_ROW_CLASS = 'cursor-pointer transition-colors';
|
||||||
export const PHYSICAL_DISK_TABLE_ROW_SELECTED_CLASS = 'bg-blue-50 dark:bg-blue-900';
|
export const PHYSICAL_DISK_TABLE_ROW_SELECTED_CLASS = 'bg-blue-50 dark:bg-blue-900';
|
||||||
export const PHYSICAL_DISK_TABLE_ROW_HOVER_CLASS = 'hover:bg-surface-hover';
|
export const PHYSICAL_DISK_TABLE_ROW_HOVER_CLASS = 'hover:bg-surface-hover';
|
||||||
export const PHYSICAL_DISK_TABLE_ROW_STYLE = { height: '38px' } as const;
|
export const PHYSICAL_DISK_TABLE_ROW_STYLE = { height: '32px' } as const;
|
||||||
export const PHYSICAL_DISK_DETAIL_ROW_CELL_CLASS =
|
export const PHYSICAL_DISK_DETAIL_ROW_CELL_CLASS =
|
||||||
'border-b border-border-subtle bg-surface-alt px-4 py-4 shadow-inner';
|
'border-b border-border-subtle bg-surface-alt px-4 py-4 shadow-inner';
|
||||||
export const PHYSICAL_DISK_COL_DISK_CLASS = 'w-[42%] sm:w-[28%] md:w-[28%] xl:w-[22%]';
|
export const PHYSICAL_DISK_COL_DISK_CLASS = 'w-[47%] sm:w-[31%] md:w-[24%] xl:w-[19%]';
|
||||||
export const PHYSICAL_DISK_COL_SOURCE_CLASS = 'w-[15%] sm:w-[11%] md:w-[9%] xl:w-[8%]';
|
export const PHYSICAL_DISK_COL_DEVICE_CLASS = 'hidden md:table-column md:w-[11%] xl:w-[9%]';
|
||||||
export const PHYSICAL_DISK_COL_HOST_CLASS =
|
export const PHYSICAL_DISK_COL_HOST_CLASS =
|
||||||
'hidden sm:table-column sm:w-[18%] md:w-[14%] xl:w-[12%]';
|
'hidden sm:table-column sm:w-[20%] md:w-[12%] xl:w-[10%]';
|
||||||
export const PHYSICAL_DISK_COL_ROLE_CLASS = 'hidden xl:table-column xl:w-[10%]';
|
export const PHYSICAL_DISK_COL_ROLE_CLASS = 'hidden xl:table-column xl:w-[8%]';
|
||||||
export const PHYSICAL_DISK_COL_PARENT_CLASS = 'hidden xl:table-column xl:w-[13%]';
|
export const PHYSICAL_DISK_COL_PARENT_CLASS = 'hidden xl:table-column xl:w-[12%]';
|
||||||
export const PHYSICAL_DISK_COL_HEALTH_CLASS = 'w-[25%] sm:w-[24%] md:w-[20%] xl:w-[16%]';
|
export const PHYSICAL_DISK_COL_HEALTH_CLASS = 'w-[29%] sm:w-[27%] md:w-[19%] xl:w-[17%]';
|
||||||
export const PHYSICAL_DISK_COL_TEMP_CLASS = 'hidden md:table-column md:w-[9%] xl:w-[7%]';
|
export const PHYSICAL_DISK_COL_LIFE_CLASS = 'hidden md:table-column md:w-[7%] xl:w-[6%]';
|
||||||
export const PHYSICAL_DISK_COL_SIZE_CLASS = 'w-[18%] sm:w-[19%] md:w-[20%] xl:w-[12%]';
|
export const PHYSICAL_DISK_COL_TEMP_CLASS = 'hidden md:table-column md:w-[8%] xl:w-[7%]';
|
||||||
|
export const PHYSICAL_DISK_COL_SIZE_CLASS = 'w-[24%] sm:w-[22%] md:w-[19%] xl:w-[12%]';
|
||||||
|
const PHYSICAL_DISK_CELL_DEVICE_RESPONSIVE_CLASS = 'hidden md:table-cell md:w-[11%] xl:w-[9%]';
|
||||||
const PHYSICAL_DISK_CELL_HOST_RESPONSIVE_CLASS =
|
const PHYSICAL_DISK_CELL_HOST_RESPONSIVE_CLASS =
|
||||||
'hidden sm:table-cell sm:w-[18%] md:w-[14%] xl:w-[12%]';
|
'hidden sm:table-cell sm:w-[20%] md:w-[12%] xl:w-[10%]';
|
||||||
const PHYSICAL_DISK_CELL_ROLE_RESPONSIVE_CLASS = 'hidden xl:table-cell xl:w-[10%]';
|
const PHYSICAL_DISK_CELL_ROLE_RESPONSIVE_CLASS = 'hidden xl:table-cell xl:w-[8%]';
|
||||||
const PHYSICAL_DISK_CELL_PARENT_RESPONSIVE_CLASS = 'hidden xl:table-cell xl:w-[13%]';
|
const PHYSICAL_DISK_CELL_PARENT_RESPONSIVE_CLASS = 'hidden xl:table-cell xl:w-[12%]';
|
||||||
const PHYSICAL_DISK_CELL_TEMP_RESPONSIVE_CLASS = 'hidden md:table-cell md:w-[9%] xl:w-[7%]';
|
const PHYSICAL_DISK_CELL_LIFE_RESPONSIVE_CLASS = 'hidden md:table-cell md:w-[7%] xl:w-[6%]';
|
||||||
|
const PHYSICAL_DISK_CELL_TEMP_RESPONSIVE_CLASS = 'hidden md:table-cell md:w-[8%] xl:w-[7%]';
|
||||||
export const PHYSICAL_DISK_HEADER_DISK_CLASS = `${PHYSICAL_DISK_TABLE_HEADER_CLASS} ${PHYSICAL_DISK_COL_DISK_CLASS}`;
|
export const PHYSICAL_DISK_HEADER_DISK_CLASS = `${PHYSICAL_DISK_TABLE_HEADER_CLASS} ${PHYSICAL_DISK_COL_DISK_CLASS}`;
|
||||||
export const PHYSICAL_DISK_HEADER_SOURCE_CLASS = `${PHYSICAL_DISK_TABLE_HEADER_CLASS} ${PHYSICAL_DISK_COL_SOURCE_CLASS}`;
|
export const PHYSICAL_DISK_HEADER_DEVICE_CLASS = `${PHYSICAL_DISK_TABLE_HEADER_CLASS} ${PHYSICAL_DISK_CELL_DEVICE_RESPONSIVE_CLASS}`;
|
||||||
// Headers for responsively-hidden columns must use `table-cell` (not
|
// Headers for responsively-hidden columns must use `table-cell` (not
|
||||||
// `table-column`, which is for `<col>` and does not render `<th>`
|
// `table-column`, which is for `<col>` and does not render `<th>`
|
||||||
// content), otherwise the cells render but the column header is blank.
|
// content), otherwise the cells render but the column header is blank.
|
||||||
@@ -122,14 +125,16 @@ export const PHYSICAL_DISK_HEADER_HOST_CLASS = `${PHYSICAL_DISK_TABLE_HEADER_CLA
|
|||||||
export const PHYSICAL_DISK_HEADER_ROLE_CLASS = `${PHYSICAL_DISK_TABLE_HEADER_CLASS} ${PHYSICAL_DISK_CELL_ROLE_RESPONSIVE_CLASS}`;
|
export const PHYSICAL_DISK_HEADER_ROLE_CLASS = `${PHYSICAL_DISK_TABLE_HEADER_CLASS} ${PHYSICAL_DISK_CELL_ROLE_RESPONSIVE_CLASS}`;
|
||||||
export const PHYSICAL_DISK_HEADER_PARENT_CLASS = `${PHYSICAL_DISK_TABLE_HEADER_CLASS} ${PHYSICAL_DISK_CELL_PARENT_RESPONSIVE_CLASS}`;
|
export const PHYSICAL_DISK_HEADER_PARENT_CLASS = `${PHYSICAL_DISK_TABLE_HEADER_CLASS} ${PHYSICAL_DISK_CELL_PARENT_RESPONSIVE_CLASS}`;
|
||||||
export const PHYSICAL_DISK_HEADER_HEALTH_CLASS = `${PHYSICAL_DISK_TABLE_HEADER_CLASS} ${PHYSICAL_DISK_COL_HEALTH_CLASS}`;
|
export const PHYSICAL_DISK_HEADER_HEALTH_CLASS = `${PHYSICAL_DISK_TABLE_HEADER_CLASS} ${PHYSICAL_DISK_COL_HEALTH_CLASS}`;
|
||||||
|
export const PHYSICAL_DISK_HEADER_LIFE_CLASS = `${PHYSICAL_DISK_TABLE_HEADER_CLASS} ${PHYSICAL_DISK_CELL_LIFE_RESPONSIVE_CLASS}`;
|
||||||
export const PHYSICAL_DISK_HEADER_TEMP_CLASS = `${PHYSICAL_DISK_TABLE_HEADER_CLASS} ${PHYSICAL_DISK_CELL_TEMP_RESPONSIVE_CLASS}`;
|
export const PHYSICAL_DISK_HEADER_TEMP_CLASS = `${PHYSICAL_DISK_TABLE_HEADER_CLASS} ${PHYSICAL_DISK_CELL_TEMP_RESPONSIVE_CLASS}`;
|
||||||
export const PHYSICAL_DISK_HEADER_SIZE_CLASS = `${PHYSICAL_DISK_TABLE_HEADER_CLASS} ${PHYSICAL_DISK_COL_SIZE_CLASS}`;
|
export const PHYSICAL_DISK_HEADER_SIZE_CLASS = `${PHYSICAL_DISK_TABLE_HEADER_CLASS} ${PHYSICAL_DISK_COL_SIZE_CLASS}`;
|
||||||
export const PHYSICAL_DISK_CELL_DISK_CLASS = `${PHYSICAL_DISK_COL_DISK_CLASS} overflow-hidden px-1 sm:px-1.5 lg:px-2 py-1 align-middle text-xs`;
|
export const PHYSICAL_DISK_CELL_DISK_CLASS = `${PHYSICAL_DISK_COL_DISK_CLASS} overflow-hidden px-1 sm:px-1.5 lg:px-2 py-1 align-middle text-xs`;
|
||||||
export const PHYSICAL_DISK_CELL_SOURCE_CLASS = `${PHYSICAL_DISK_COL_SOURCE_CLASS} overflow-hidden px-1 sm:px-1.5 lg:px-2 py-1 align-middle text-xs`;
|
export const PHYSICAL_DISK_CELL_DEVICE_CLASS = `${PHYSICAL_DISK_CELL_DEVICE_RESPONSIVE_CLASS} overflow-hidden px-1 sm:px-1.5 lg:px-2 py-1 align-middle text-xs`;
|
||||||
export const PHYSICAL_DISK_CELL_HOST_CLASS = `${PHYSICAL_DISK_CELL_HOST_RESPONSIVE_CLASS} overflow-hidden px-1 sm:px-1.5 lg:px-2 py-1 align-middle text-xs`;
|
export const PHYSICAL_DISK_CELL_HOST_CLASS = `${PHYSICAL_DISK_CELL_HOST_RESPONSIVE_CLASS} overflow-hidden px-1 sm:px-1.5 lg:px-2 py-1 align-middle text-xs`;
|
||||||
export const PHYSICAL_DISK_CELL_ROLE_CLASS = `${PHYSICAL_DISK_CELL_ROLE_RESPONSIVE_CLASS} overflow-hidden px-1 sm:px-1.5 lg:px-2 py-1 align-middle text-xs`;
|
export const PHYSICAL_DISK_CELL_ROLE_CLASS = `${PHYSICAL_DISK_CELL_ROLE_RESPONSIVE_CLASS} overflow-hidden px-1 sm:px-1.5 lg:px-2 py-1 align-middle text-xs`;
|
||||||
export const PHYSICAL_DISK_CELL_PARENT_CLASS = `${PHYSICAL_DISK_CELL_PARENT_RESPONSIVE_CLASS} overflow-hidden px-1 sm:px-1.5 lg:px-2 py-1 align-middle text-xs`;
|
export const PHYSICAL_DISK_CELL_PARENT_CLASS = `${PHYSICAL_DISK_CELL_PARENT_RESPONSIVE_CLASS} overflow-hidden px-1 sm:px-1.5 lg:px-2 py-1 align-middle text-xs`;
|
||||||
export const PHYSICAL_DISK_CELL_HEALTH_CLASS = `${PHYSICAL_DISK_COL_HEALTH_CLASS} overflow-hidden px-1 sm:px-1.5 lg:px-2 py-1 align-middle text-xs`;
|
export const PHYSICAL_DISK_CELL_HEALTH_CLASS = `${PHYSICAL_DISK_COL_HEALTH_CLASS} overflow-hidden px-1 sm:px-1.5 lg:px-2 py-1 align-middle text-xs`;
|
||||||
|
export const PHYSICAL_DISK_CELL_LIFE_CLASS = `${PHYSICAL_DISK_CELL_LIFE_RESPONSIVE_CLASS} overflow-hidden px-1 sm:px-1.5 lg:px-2 py-1 align-middle text-xs whitespace-nowrap`;
|
||||||
export const PHYSICAL_DISK_CELL_TEMP_CLASS = `${PHYSICAL_DISK_CELL_TEMP_RESPONSIVE_CLASS} overflow-hidden px-1 sm:px-1.5 lg:px-2 py-1 align-middle text-xs whitespace-nowrap`;
|
export const PHYSICAL_DISK_CELL_TEMP_CLASS = `${PHYSICAL_DISK_CELL_TEMP_RESPONSIVE_CLASS} overflow-hidden px-1 sm:px-1.5 lg:px-2 py-1 align-middle text-xs whitespace-nowrap`;
|
||||||
export const PHYSICAL_DISK_CELL_SIZE_CLASS = `${PHYSICAL_DISK_COL_SIZE_CLASS} overflow-hidden px-1 sm:px-1.5 lg:px-2 py-1 align-middle text-xs whitespace-nowrap`;
|
export const PHYSICAL_DISK_CELL_SIZE_CLASS = `${PHYSICAL_DISK_COL_SIZE_CLASS} overflow-hidden px-1 sm:px-1.5 lg:px-2 py-1 align-middle text-xs whitespace-nowrap`;
|
||||||
export const PHYSICAL_DISK_NAME_WRAP_CLASS = 'flex min-w-0 items-center gap-1.5 whitespace-nowrap';
|
export const PHYSICAL_DISK_NAME_WRAP_CLASS = 'flex min-w-0 items-center gap-1.5 whitespace-nowrap';
|
||||||
@@ -138,7 +143,10 @@ export const PHYSICAL_DISK_NAME_TEXT_CLASS =
|
|||||||
export const PHYSICAL_DISK_SOURCE_BADGE_CLASS =
|
export const PHYSICAL_DISK_SOURCE_BADGE_CLASS =
|
||||||
'inline-flex max-w-full min-w-0 justify-center overflow-hidden text-ellipsis px-1 sm:px-1.5 py-px text-[9px] font-medium';
|
'inline-flex max-w-full min-w-0 justify-center overflow-hidden text-ellipsis px-1 sm:px-1.5 py-px text-[9px] font-medium';
|
||||||
export const PHYSICAL_DISK_VALUE_TEXT_CLASS = 'block truncate text-[11px] text-base-content';
|
export const PHYSICAL_DISK_VALUE_TEXT_CLASS = 'block truncate text-[11px] text-base-content';
|
||||||
|
export const PHYSICAL_DISK_DEVICE_TEXT_CLASS =
|
||||||
|
'block truncate font-mono text-[11px] text-base-content';
|
||||||
export const PHYSICAL_DISK_MUTED_PLACEHOLDER_CLASS = 'text-[11px] text-muted';
|
export const PHYSICAL_DISK_MUTED_PLACEHOLDER_CLASS = 'text-[11px] text-muted';
|
||||||
|
export const PHYSICAL_DISK_LIFE_CLASS = 'text-[11px] font-medium';
|
||||||
export const PHYSICAL_DISK_HEALTH_WRAP_CLASS =
|
export const PHYSICAL_DISK_HEALTH_WRAP_CLASS =
|
||||||
'flex min-w-0 items-center gap-1.5 whitespace-nowrap';
|
'flex min-w-0 items-center gap-1.5 whitespace-nowrap';
|
||||||
export const PHYSICAL_DISK_HEALTH_LABEL_CLASS = 'min-w-0 truncate text-[11px] font-semibold';
|
export const PHYSICAL_DISK_HEALTH_LABEL_CLASS = 'min-w-0 truncate text-[11px] font-semibold';
|
||||||
@@ -568,9 +576,28 @@ export function getPhysicalDiskRoleLabel(disk: PhysicalDiskPresentationData): st
|
|||||||
|
|
||||||
export function getPhysicalDiskParentLabel(disk: PhysicalDiskPresentationData): string {
|
export function getPhysicalDiskParentLabel(disk: PhysicalDiskPresentationData): string {
|
||||||
if (disk.storageGroup?.trim()) return disk.storageGroup.trim();
|
if (disk.storageGroup?.trim()) return disk.storageGroup.trim();
|
||||||
|
// Proxmox reports a usage string ("ZFS", "ext4", "BIOS boot", "LVM", ...)
|
||||||
|
// for disks it cannot map to a named pool; better than an empty cell.
|
||||||
|
const used = disk.used?.trim() || '';
|
||||||
|
if (used && used.toLowerCase() !== 'unknown') return used;
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getPhysicalDiskLifeLabel(disk: PhysicalDiskPresentationData): string {
|
||||||
|
// Wearout is "% life remaining" (100 = new); 0 and -1 both mean unreported.
|
||||||
|
if (typeof disk.wearout !== 'number' || disk.wearout <= 0) return '';
|
||||||
|
return `${Math.min(disk.wearout, 100)}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPhysicalDiskLifeTextClass(disk: PhysicalDiskPresentationData): string {
|
||||||
|
if (typeof disk.wearout !== 'number' || disk.wearout <= 0) {
|
||||||
|
return PHYSICAL_DISK_MUTED_PLACEHOLDER_CLASS;
|
||||||
|
}
|
||||||
|
if (disk.wearout < 20) return 'text-red-600 dark:text-red-400';
|
||||||
|
if (disk.wearout < 50) return 'text-amber-600 dark:text-amber-400';
|
||||||
|
return 'text-green-600 dark:text-green-400';
|
||||||
|
}
|
||||||
|
|
||||||
export function getPhysicalDiskRoleFilterValue(disk: PhysicalDiskPresentationData): string {
|
export function getPhysicalDiskRoleFilterValue(disk: PhysicalDiskPresentationData): string {
|
||||||
return normalizePhysicalDiskFacetFilter(getPhysicalDiskRoleLabel(disk));
|
return normalizePhysicalDiskFacetFilter(getPhysicalDiskRoleLabel(disk));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1238,7 +1238,10 @@ describe('frontend resource type boundaries', () => {
|
|||||||
expect(diskListSource).toContain('getPhysicalDiskEmptyStatePresentation');
|
expect(diskListSource).toContain('getPhysicalDiskEmptyStatePresentation');
|
||||||
expect(diskListSource).toContain('getPhysicalDiskRoleLabel');
|
expect(diskListSource).toContain('getPhysicalDiskRoleLabel');
|
||||||
expect(diskListSource).toContain('getPhysicalDiskParentLabel');
|
expect(diskListSource).toContain('getPhysicalDiskParentLabel');
|
||||||
expect(diskListSource).toContain('getPhysicalDiskSourceBadgePresentation');
|
expect(diskListSource).toContain('getPhysicalDiskLifeLabel');
|
||||||
|
// The source badge renders in the expansion (DiskDetail), not the table.
|
||||||
|
expect(diskListSource).not.toContain('getPhysicalDiskSourceBadgePresentation');
|
||||||
|
expect(diskDetailSource).toContain('getPhysicalDiskSourceBadgePresentation');
|
||||||
expect(diskListSource).not.toContain('const titleize =');
|
expect(diskListSource).not.toContain('const titleize =');
|
||||||
expect(diskListSource).not.toContain('const platformLabel =');
|
expect(diskListSource).not.toContain('const platformLabel =');
|
||||||
expect(diskListSource).not.toContain('function hasSmartWarning(');
|
expect(diskListSource).not.toContain('function hasSmartWarning(');
|
||||||
|
|||||||
Reference in New Issue
Block a user