mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-24 04:33:17 +00:00
Keep Proxmox backup and node tables whole on phones
On a 390px phone the Proxmox tables clipped the values users came for.
The nodes table showed six "Anal…" rows for the Analytics A–F cluster:
a sixth column (temperature) left the node name four characters wide
with the toggle and status dot in front of it. The backup server row
showed "H…" for Healthy, and the coverage and By date rows clipped
"18h ago" to "18 …" and "Running" to "Runni…".
Nodes now carry the same five columns as the guest table beneath them
with a wider identity cell; temperature returns from 440px, where it
fits. Backup ages use a suffix-free compact form ("18h") with the full
timestamp on hover. The server row keeps its status word and used
percentage whole and lets the backup count return at the next width,
and its stale header width classes no longer fight the colgroup. The
By date row keeps its source and state badges whole while the
repository moves to the row's hover title. Just above the phone
projection the servers' used/total pair and the coverage posture word
now fit, and at desktop width "Automation Lab Standalone" no longer
truncates in the nodes table.
Measured with a scrollWidth-over-clientWidth sweep of every table cell
at 1440px, 560px and 390px; only identity strings still clip, each with
its full text on hover.
Contract-Neutral: phone and mid-width column projections and labels only; owning unified-resources clause updated; no storage-recovery authority or frontend-primitives change
This commit is contained in:
@@ -842,6 +842,18 @@ the daemon tally as width shrinks, keeps one five-value phone projection
|
||||
(cluster, health, OSDs, pools, capacity) whose values each fit whole, and
|
||||
leaves quorum membership and the FSID to the cluster disclosure, where the
|
||||
FSID stays searchable and is shown in full.
|
||||
The Proxmox host table drops temperature from its phone projection
|
||||
(container widths under 440px) so the node name, expansion toggle, and
|
||||
status dot share the same five-column shape as the guest table beneath it,
|
||||
and the identity cell takes a wider phone anchor than the shared default
|
||||
because of that leading toggle. `ProxmoxBackupServersTable`,
|
||||
`ProxmoxCoverageTable`, and `ProxmoxRecoverableTable` keep their phone
|
||||
projections to values that fit whole: the server row keeps its status word
|
||||
and used percentage and defers the backup count to the next width, backup
|
||||
ages use a suffix-free compact form with the timestamp on hover, and the
|
||||
recoverable row keeps its source and state badges while the repository
|
||||
location moves to the row title. Header width classes must not compete with
|
||||
the colgroup those tables size through the shared weighted helper.
|
||||
Optional numeric table cells follow the same split: unified-resource consumers
|
||||
own which count or replica field is meaningful, whether the domain should
|
||||
zero-default an absent scheduler/service/inventory count, whether a
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
{
|
||||
"version": 1,
|
||||
"base_sha": "b052d29b19909e8ed2581043426d76bb9d20c2a0",
|
||||
"verified_at": "2026-10-02T22:15:42Z",
|
||||
"base_sha": "3f2715e3f3f9b563ad3f119ba0d302b488b97e8a",
|
||||
"verified_at": "2026-10-02T22:20:55Z",
|
||||
"result": "passed",
|
||||
"changed_paths": [
|
||||
"frontend-modern/src/features/proxmox/ProxmoxBackupsTable.tsx",
|
||||
"frontend-modern/src/features/proxmox/ProxmoxBackupServersTable.tsx",
|
||||
"frontend-modern/src/features/proxmox/ProxmoxCoverageTable.tsx",
|
||||
"frontend-modern/src/features/proxmox/proxmoxBackupRecoveryModel.ts",
|
||||
"frontend-modern/src/features/proxmox/proxmoxBackupsTablePresentation.ts"
|
||||
"frontend-modern/src/features/proxmox/ProxmoxRecoverableTable.tsx",
|
||||
"frontend-modern/src/features/proxmox/proxmoxBackupsTablePresentation.ts",
|
||||
"frontend-modern/src/features/proxmox/proxmoxBackupsTableShared.tsx",
|
||||
"frontend-modern/src/features/proxmox/proxmoxHostTableModel.ts"
|
||||
],
|
||||
"content_sha256": {
|
||||
"frontend-modern/src/features/proxmox/ProxmoxBackupsTable.tsx": "786be66c40d33006edc1eda234df083ee68e0296103882ff108711026dd2ea29",
|
||||
"frontend-modern/src/features/proxmox/ProxmoxCoverageTable.tsx": "075994cbe8d47c47a95747ab4d5181c11666cf6272ec8fbe6026b5567de10dfb",
|
||||
"frontend-modern/src/features/proxmox/proxmoxBackupRecoveryModel.ts": "f001412aff663f53ed5ae4a64ebcaa381257dbb3c19497221cbebb77ee5fab08",
|
||||
"frontend-modern/src/features/proxmox/proxmoxBackupsTablePresentation.ts": "dc2f9dcfbd51eb311596ff12a2588a645394e9ab717595a2886b41c89fae5c95"
|
||||
"frontend-modern/src/features/proxmox/ProxmoxBackupServersTable.tsx": "adbae773cc485f5dc16d1d316bbd5e1b1ace6be24404861ce02bf7dd8890bb25",
|
||||
"frontend-modern/src/features/proxmox/ProxmoxCoverageTable.tsx": "ce0bf7e73d500c09a61df52b7c948428d83cebf6852ae737f7bcdb7ee0997ae0",
|
||||
"frontend-modern/src/features/proxmox/ProxmoxRecoverableTable.tsx": "7b7aea630dda12202b5734cf94d4c3409d6dc21b632efe918e7391d4b150e80c",
|
||||
"frontend-modern/src/features/proxmox/proxmoxBackupsTablePresentation.ts": "0037e6f97e1180ef0c0d585ec5e9385dcbe4c4e1b51a7bc29d8631b4b0e87745",
|
||||
"frontend-modern/src/features/proxmox/proxmoxBackupsTableShared.tsx": "2ab0f6c58e18337f05ea85082ed5c465a625c5388ff82987eec41bbbe3005915",
|
||||
"frontend-modern/src/features/proxmox/proxmoxHostTableModel.ts": "4aab84754245dda70c3b3be3e81b15875a8664d38c8d0325329b2074b6c26fd5"
|
||||
},
|
||||
"routes": [
|
||||
"/proxmox/overview",
|
||||
"/proxmox/backups/coverage",
|
||||
"/proxmox/backups/coverage?posture=unprotected",
|
||||
"/proxmox/backups/date"
|
||||
],
|
||||
"viewports": [
|
||||
@@ -35,15 +39,16 @@
|
||||
}
|
||||
],
|
||||
"states": [
|
||||
"Mock estate: coverage rows whose newest guest snapshot is fresher than their PBS/PVE backup now show the backup age under Last backup instead of the snapshot age",
|
||||
"Snapshot-only rows show None under Last backup with the posture engine's snapshot-only reason as the hover title",
|
||||
"Rows with no artifacts show None with the no-backup title; attention rows with failed tasks keep their backup age",
|
||||
"Phone layout keeps the Age header and the same values; the By date view and Backup Health strip are unchanged"
|
||||
"Nodes table at 390px shows five columns (node, CPU, memory, disk, age) with Analytics A-F names whole; temperature returns at 560px and 1440px",
|
||||
"Backup server rows at 390px show server, Healthy, datastore and used percentage whole; the backup count returns at 560px with its used/total pair whole",
|
||||
"Coverage rows at 390px show whole compact ages (2h, 1d), the whole compact posture word, and the job dot; at 560px the posture word and Backup header fit",
|
||||
"By date rows at 390px keep source and state badges whole with the repository in the row title; at 1440px Automation Lab Standalone no longer truncates in the nodes table",
|
||||
"scrollWidth-over-clientWidth sweep of every table cell at the three widths: only identity strings (datastore names, long node names) clip, each with its full text on hover"
|
||||
],
|
||||
"interactions": [
|
||||
"Sorted by Last backup at 1440px and by Age at 390px and read the first row",
|
||||
"Read the None cell hover titles for unprotected rows at both widths",
|
||||
"Expanded a coverage row to its restore evidence at both widths",
|
||||
"Read coverage row cells through the DOM at 1440px and compared them with the Posture explanation"
|
||||
"Expanded the first node row and the first backup server row at each width",
|
||||
"Sorted the coverage table by its age column at each width and read the first row",
|
||||
"Read the By date first-row hover title at each width",
|
||||
"Read the node table column widths and the node cell layout through the DOM at 390px, 360px and 430px"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -422,14 +422,10 @@ export function ProxmoxBackupServersTable(props: {
|
||||
}
|
||||
header={
|
||||
<>
|
||||
<TableHead
|
||||
class={`${getPlatformTableHeadClassForKind('name')} platform-table-mobile-w-30 md:w-[15%]`}
|
||||
>
|
||||
<TableHead class={`${getPlatformTableHeadClassForKind('name')}`}>
|
||||
<PlatformResponsiveTableLabel compact="Server" full="Backup server" />
|
||||
</TableHead>
|
||||
<TableHead
|
||||
class={`${getPlatformTableHeadClassForKind('text')} platform-table-mobile-w-15 md:w-[10%]`}
|
||||
>
|
||||
<TableHead class={`${getPlatformTableHeadClassForKind('text')}`}>
|
||||
<PlatformResponsiveTableLabel compact="State" full="Status" />
|
||||
</TableHead>
|
||||
<Show when={columnVisible('version')}>
|
||||
@@ -449,20 +445,16 @@ export function ProxmoxBackupServersTable(props: {
|
||||
</TableHead>
|
||||
</Show>
|
||||
<Show when={columnVisible('datastore')}>
|
||||
<TableHead
|
||||
class={`${getPlatformTableHeadClassForKind('text')} platform-table-mobile-w-20 md:w-[13%]`}
|
||||
>
|
||||
<TableHead class={`${getPlatformTableHeadClassForKind('text')}`}>
|
||||
<PlatformResponsiveTableLabel compact="Store" full="Datastore" />
|
||||
</TableHead>
|
||||
</Show>
|
||||
<TableHead
|
||||
class={`${getPlatformTableHeadClassForKind('numeric-value')} platform-table-mobile-w-20 md:w-[15%]`}
|
||||
>
|
||||
<TableHead class={`${getPlatformTableHeadClassForKind('numeric-value')}`}>
|
||||
Used
|
||||
</TableHead>
|
||||
<Show when={columnVisible('backups')}>
|
||||
<TableHead
|
||||
class={`${getPlatformTableHeadClassForKind('numeric-value')} platform-table-mobile-w-15 md:w-[8%]`}
|
||||
class={`${getPlatformTableHeadClassForKind('numeric-value')}`}
|
||||
aria-label="Backups"
|
||||
title="Backups"
|
||||
>
|
||||
@@ -597,12 +589,14 @@ export function ProxmoxBackupServersTable(props: {
|
||||
>
|
||||
{(datastore) => (
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<StatusDot
|
||||
size="sm"
|
||||
variant={usageVariant(pct())}
|
||||
title={`Datastore ${formatPlatformTablePercentValue(pct())} used`}
|
||||
ariaHidden
|
||||
/>
|
||||
<Show when={layoutMode() !== 'compact' && layoutMode() !== 'basic'}>
|
||||
<StatusDot
|
||||
size="sm"
|
||||
variant={usageVariant(pct())}
|
||||
title={`Datastore ${formatPlatformTablePercentValue(pct())} used`}
|
||||
ariaHidden
|
||||
/>
|
||||
</Show>
|
||||
<span class={`tabular-nums font-medium ${usageToneClass(pct())}`}>
|
||||
<PlatformTablePercentValue value={pct()} />
|
||||
</span>
|
||||
|
||||
@@ -244,7 +244,13 @@ export function ProxmoxCoverageTable(props: {
|
||||
headClass={getPlatformTableHeadClassForKind('text')}
|
||||
/>
|
||||
<SortableHead
|
||||
label={layoutMode() === 'compact' ? 'Age' : 'Last backup'}
|
||||
label={
|
||||
layoutMode() === 'compact'
|
||||
? 'Age'
|
||||
: layoutMode() === 'basic'
|
||||
? 'Backup'
|
||||
: 'Last backup'
|
||||
}
|
||||
sortKey="latest"
|
||||
currentSort={props.sortKey}
|
||||
direction={props.sortDirection}
|
||||
@@ -425,7 +431,12 @@ export function ProxmoxCoverageTable(props: {
|
||||
</span>
|
||||
}
|
||||
>
|
||||
{(artifact) => <ProxmoxBackupAgeText artifact={artifact()} />}
|
||||
{(artifact) => (
|
||||
<ProxmoxBackupAgeText
|
||||
artifact={artifact()}
|
||||
compact={layoutMode() === 'compact'}
|
||||
/>
|
||||
)}
|
||||
</Show>
|
||||
</TableCell>
|
||||
<Show when={columnVisible('pbs')}>
|
||||
@@ -440,7 +451,12 @@ export function ProxmoxCoverageTable(props: {
|
||||
</span>
|
||||
}
|
||||
>
|
||||
{(artifact) => <ProxmoxBackupAgeText artifact={artifact()} />}
|
||||
{(artifact) => (
|
||||
<ProxmoxBackupAgeText
|
||||
artifact={artifact()}
|
||||
compact={layoutMode() === 'compact'}
|
||||
/>
|
||||
)}
|
||||
</Show>
|
||||
</TableCell>
|
||||
</Show>
|
||||
|
||||
@@ -123,6 +123,9 @@ export function ProxmoxRecoverableTable(props: {
|
||||
artifact.workload.name || artifact.workload.label,
|
||||
artifact.workload.typeLabel,
|
||||
artifact.workload.vmid,
|
||||
// The phone projection has no location column, so the row's
|
||||
// hover title carries the repository instead.
|
||||
columnVisible('location') ? undefined : artifact.location,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' · ')}
|
||||
@@ -163,7 +166,7 @@ export function ProxmoxRecoverableTable(props: {
|
||||
</TableCell>
|
||||
</Show>
|
||||
<TableCell class={`${getPlatformTableCellClassForKind('numeric-value')} text-base-content`}>
|
||||
<ProxmoxBackupAgeText artifact={artifact} />
|
||||
<ProxmoxBackupAgeText artifact={artifact} compact={layoutMode() === 'compact'} />
|
||||
</TableCell>
|
||||
<Show when={columnVisible('size')}>
|
||||
<TableCell class={`${getPlatformTableCellClassForKind('metric-bar')} text-base-content`}>
|
||||
|
||||
@@ -55,12 +55,13 @@ describe('ProxmoxRecoverableTable responsive columns', () => {
|
||||
expect([...document.querySelectorAll('thead th')].map((th) => th.textContent?.trim())).toEqual([
|
||||
'Workload',
|
||||
'Via',
|
||||
'Loc',
|
||||
'Age▼',
|
||||
'State',
|
||||
]);
|
||||
expect(document.body.textContent).toContain('VM 100');
|
||||
expect(document.body.textContent).toContain('main / pve1');
|
||||
// The phone projection has no location column; the row title carries it.
|
||||
expect(document.body.textContent).not.toContain('main / pve1');
|
||||
expect(document.querySelector('td [title*="main / pve1"]')).not.toBeNull();
|
||||
expect(document.body.textContent).not.toContain('5 PBS files');
|
||||
});
|
||||
|
||||
|
||||
+3
-4
@@ -44,10 +44,10 @@ describe('Proxmox backups responsive table presentation', () => {
|
||||
'status',
|
||||
'datastore',
|
||||
'used',
|
||||
'backups',
|
||||
]);
|
||||
expect(getBackupServerColumnWidthStyle('server', 'compact')).toEqual({ width: '40%' });
|
||||
expect(getBackupServerColumnWidthStyle('used', 'compact')).toEqual({ width: '20%' });
|
||||
expect(getBackupServerColumnWidthStyle('status', 'compact')).toEqual({ width: '23%' });
|
||||
expect(getBackupServerColumnWidthStyle('used', 'compact')).toEqual({ width: '15%' });
|
||||
expect(ids(getBackupServerColumns('basic'))).toEqual([
|
||||
'server',
|
||||
'status',
|
||||
@@ -124,12 +124,11 @@ describe('Proxmox backups responsive table presentation', () => {
|
||||
expect(ids(getRecoverableColumns('compact'))).toEqual([
|
||||
'workload',
|
||||
'source',
|
||||
'location',
|
||||
'created',
|
||||
'state',
|
||||
]);
|
||||
expect(getRecoverableColumnWidthStyle('workload', 'compact')).toEqual({ width: '40%' });
|
||||
expect(getRecoverableColumnWidthStyle('state', 'compact')).toEqual({ width: '16%' });
|
||||
expect(getRecoverableColumnWidthStyle('state', 'compact')).toEqual({ width: '30%' });
|
||||
expect(ids(getRecoverableColumns('basic'))).toEqual([
|
||||
'workload',
|
||||
'source',
|
||||
|
||||
@@ -8,8 +8,10 @@ import proxmoxBackupsTableSharedSource from '../proxmoxBackupsTableShared.tsx?ra
|
||||
import {
|
||||
ArtifactSourceBadge,
|
||||
ArtifactStateBadge,
|
||||
ProxmoxBackupAgeText,
|
||||
ProxmoxBackupWorkloadTypeBadge,
|
||||
SortableHead,
|
||||
formatCompactBackupAge,
|
||||
} from '../proxmoxBackupsTableShared';
|
||||
import type { RecoverableArtifact } from '../proxmoxBackupRecoveryModel';
|
||||
|
||||
@@ -86,6 +88,28 @@ describe('proxmoxBackupsTableShared', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('drops the age suffix in the phone projection and keeps the timestamp on hover', () => {
|
||||
const createdAt = new Date(Date.now() - 18 * 60 * 60 * 1000).toISOString();
|
||||
expect(formatCompactBackupAge(createdAt)).toBe('18h');
|
||||
expect(formatCompactBackupAge(new Date().toISOString())).toBe('now');
|
||||
|
||||
const artifact = {
|
||||
id: 'pbs:1',
|
||||
nativeId: '1',
|
||||
sourceKind: 'pbs',
|
||||
sourceLabel: 'PBS',
|
||||
workload: { key: 'w', type: 'vm', typeLabel: 'VM', vmid: '100', label: 'VM 100' },
|
||||
createdAt,
|
||||
createdMs: Date.parse(createdAt),
|
||||
location: 'main',
|
||||
detail: '',
|
||||
protected: false,
|
||||
} as RecoverableArtifact;
|
||||
render(() => <ProxmoxBackupAgeText artifact={artifact} compact />);
|
||||
const age = screen.getByText('18h');
|
||||
expect(age.closest('[title]')?.getAttribute('title')).toContain(createdAt);
|
||||
});
|
||||
|
||||
it('keeps backup age text on the shared relative-time primitive', () => {
|
||||
expect(proxmoxBackupsTableSharedSource).toContain('PlatformTableRelativeTimeValue');
|
||||
expect(proxmoxBackupsTableSharedSource).not.toContain('formatRelativeTime(');
|
||||
|
||||
@@ -19,16 +19,16 @@ describe('proxmoxHostTableModel', () => {
|
||||
expect(getProxmoxHostColumnWidthStyle('uptime', 'narrow', ids)).toEqual({ width: '15%' });
|
||||
});
|
||||
|
||||
it('shows the full operational metric track at phone width', () => {
|
||||
it('matches the guest table columns and keeps node names whole at phone width', () => {
|
||||
const columns = getProxmoxHostVisibleColumnsForLayout('phone');
|
||||
const ids = columns.map((column) => column.id);
|
||||
|
||||
expect(ids).toEqual(['node', 'cpu', 'memory', 'disk', 'temp', 'uptime']);
|
||||
expect(ids).toEqual(['node', 'cpu', 'memory', 'disk', 'uptime']);
|
||||
expect(getProxmoxHostTableMinWidthClass('phone')).toBe('min-w-[0px]');
|
||||
expect(getProxmoxHostColumnWidthStyle('node', 'phone', ids)).toEqual({ width: '30%' });
|
||||
expect(getProxmoxHostColumnWidthStyle('disk', 'phone', ids)).toEqual({ width: '13.3824%' });
|
||||
expect(getProxmoxHostColumnWidthStyle('temp', 'phone', ids)).toEqual({ width: '15.4412%' });
|
||||
expect(getProxmoxHostColumnWidthStyle('uptime', 'phone', ids)).toEqual({ width: '14.4118%' });
|
||||
expect(getProxmoxHostColumnWidthStyle('node', 'phone', ids)).toEqual({ width: '38%' });
|
||||
expect(getProxmoxHostColumnWidthStyle('cpu', 'phone', ids)).toEqual({ width: '15.5%' });
|
||||
expect(getProxmoxHostColumnWidthStyle('disk', 'phone', ids)).toEqual({ width: '15.5%' });
|
||||
expect(getProxmoxHostColumnWidthStyle('uptime', 'phone', ids)).toEqual({ width: '15.5%' });
|
||||
});
|
||||
|
||||
it('prioritizes live utilization columns in the mobile host table', () => {
|
||||
|
||||
@@ -93,8 +93,10 @@ const BACKUP_SERVER_VISIBLE: Record<
|
||||
> = {
|
||||
// Reachability and datastore exhaustion are the two risks that can stop all
|
||||
// future backups. Host telemetry and space-efficiency context return only as
|
||||
// the table gains enough room to keep those headline answers readable.
|
||||
compact: ['server', 'status', 'datastore', 'used', 'backups'],
|
||||
// the table gains enough room to keep those headline answers readable. The
|
||||
// phone projection keeps those two answers whole ("Healthy", "33.6%") and
|
||||
// lets the backup count return with the first wider layout.
|
||||
compact: ['server', 'status', 'datastore', 'used'],
|
||||
basic: ['server', 'status', 'datastore', 'used', 'backups'],
|
||||
operational: ['server', 'status', 'cpu', 'memory', 'datastore', 'used', 'backups'],
|
||||
expanded: ['server', 'status', 'cpu', 'memory', 'uptime', 'datastore', 'used', 'backups'],
|
||||
@@ -105,8 +107,10 @@ const BACKUP_SERVER_WEIGHTS: Record<
|
||||
ProxmoxBackupsTableLayoutMode,
|
||||
Partial<Record<BackupServerColumnId, number>>
|
||||
> = {
|
||||
compact: { server: 40, status: 15, datastore: 15, used: 20, backups: 10 },
|
||||
basic: { server: 24, status: 14, datastore: 20, used: 28, backups: 14 },
|
||||
compact: { server: 40, status: 23, datastore: 22, used: 15 },
|
||||
// Just above the phone projection the used cell carries its used/total
|
||||
// pair again, the longest value in the row, so it takes the most room.
|
||||
basic: { server: 24, status: 13.6, datastore: 18.2, used: 28.4, backups: 9.8 },
|
||||
operational: {
|
||||
server: 20,
|
||||
status: 12,
|
||||
@@ -198,11 +202,14 @@ const COVERAGE_WEIGHTS: Record<
|
||||
ProxmoxBackupsTableLayoutMode,
|
||||
Partial<Record<CoverageColumnId, number>>
|
||||
> = {
|
||||
compact: { workload: 40, posture: 18, latest: 16, pbs: 16, task: 10 },
|
||||
// Posture keeps its whole compact word ("Unknown"), the two age cells hold
|
||||
// the suffix-free compact age, and the job dot takes what a "Job" header
|
||||
// needs.
|
||||
compact: { workload: 40, posture: 21, latest: 14.5, pbs: 14.5, task: 10 },
|
||||
// Above the phone projection the identity columns (workload, node) take the
|
||||
// slack that the short type badge, target id, and age cells cannot use, so
|
||||
// names stay whole instead of truncating beside empty space.
|
||||
basic: { workload: 31, node: 20, posture: 20, latest: 14, task: 15 },
|
||||
basic: { workload: 31, node: 18, posture: 22, latest: 15, task: 14 },
|
||||
operational: { workload: 27, type: 8, node: 17, posture: 17, latest: 15, task: 16 },
|
||||
expanded: {
|
||||
workload: 19,
|
||||
@@ -287,7 +294,10 @@ const RECOVERABLE_VISIBLE: Record<ProxmoxBackupsTableLayoutMode, readonly Recove
|
||||
// A recovery-point feed starts with identity, source, location, age, and
|
||||
// verification. Size and verbose details progressively return with usable
|
||||
// room; the full values remain available through their title affordance.
|
||||
compact: ['workload', 'source', 'location', 'created', 'state'],
|
||||
// The phone projection keeps the source and state badges and the age whole;
|
||||
// the repository location returns with the first wider layout and stays in
|
||||
// the row's hover title meanwhile.
|
||||
compact: ['workload', 'source', 'created', 'state'],
|
||||
basic: ['workload', 'source', 'location', 'created', 'state'],
|
||||
operational: ['workload', 'type', 'source', 'location', 'created', 'size', 'state'],
|
||||
expanded: ['workload', 'type', 'targetId', 'source', 'location', 'created', 'size', 'state'],
|
||||
@@ -301,7 +311,7 @@ const RECOVERABLE_WEIGHTS: Record<
|
||||
// Reserve enough phone-width space for complete state badges (including
|
||||
// Failed and Running) rather than clipping the recovery answer at the
|
||||
// scroll edge. Identity remains on the canonical 40% anchor.
|
||||
compact: { workload: 40, source: 12, location: 17, created: 15, state: 16 },
|
||||
compact: { workload: 40, source: 16, created: 14, state: 30 },
|
||||
basic: { workload: 28, source: 14, location: 23, created: 18, state: 17 },
|
||||
operational: {
|
||||
workload: 24,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Accessor } from 'solid-js';
|
||||
import { Show, type Accessor } from 'solid-js';
|
||||
import { filterChipStatusDot } from '@/components/shared/FilterBar';
|
||||
import { type FilterOption } from '@/components/shared/FilterButtonGroup';
|
||||
import { MetadataBadge } from '@/components/shared/MetadataBadge';
|
||||
@@ -6,7 +6,10 @@ import { ProgressBar } from '@/components/shared/ProgressBar';
|
||||
import { TableHead } from '@/components/shared/Table';
|
||||
import { getTableSortIndicator } from '@/components/shared/tableSortPresentation';
|
||||
import { WorkloadTypeBadge as SharedWorkloadTypeBadge } from '@/components/shared/WorkloadTypeBadge';
|
||||
import { PlatformTableRelativeTimeValue } from '@/features/platformPage/sharedPlatformPage';
|
||||
import {
|
||||
PlatformTableRelativeTimeValue,
|
||||
formatPlatformTableRelativeTimeValue,
|
||||
} from '@/features/platformPage/sharedPlatformPage';
|
||||
|
||||
import {
|
||||
getRecoveryAgeBand,
|
||||
@@ -219,7 +222,15 @@ export function RowMetricBar(props: {
|
||||
);
|
||||
}
|
||||
|
||||
export function ProxmoxBackupAgeText(props: { artifact: RecoverableArtifact }) {
|
||||
// Phone columns are too narrow for "18h ago", so the compact form drops the
|
||||
// suffix ("18h", "now") and keeps the full age band and timestamp on hover.
|
||||
export function formatCompactBackupAge(createdAt: string): string {
|
||||
const relative = formatPlatformTableRelativeTimeValue(createdAt);
|
||||
if (relative === 'just now') return 'now';
|
||||
return relative.replace(/ ago$/, '');
|
||||
}
|
||||
|
||||
export function ProxmoxBackupAgeText(props: { artifact: RecoverableArtifact; compact?: boolean }) {
|
||||
const band = () => getRecoveryAgeBand(props.artifact.createdMs);
|
||||
const title = () => {
|
||||
const parts = [recoveryAgeTitleByBand[band()], props.artifact.createdAt].filter(Boolean);
|
||||
@@ -228,7 +239,12 @@ export function ProxmoxBackupAgeText(props: { artifact: RecoverableArtifact }) {
|
||||
|
||||
return (
|
||||
<span class={`font-semibold tabular-nums ${recoveryAgeClassByBand[band()]}`} title={title()}>
|
||||
<PlatformTableRelativeTimeValue value={props.artifact.createdAt} />
|
||||
<Show
|
||||
when={props.compact}
|
||||
fallback={<PlatformTableRelativeTimeValue value={props.artifact.createdAt} />}
|
||||
>
|
||||
{formatCompactBackupAge(props.artifact.createdAt)}
|
||||
</Show>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ const HOST_COLUMN_MIN_LAYOUT: Record<ProxmoxHostTableColumnId, WorkloadTableLayo
|
||||
cpu: 'narrow',
|
||||
memory: 'narrow',
|
||||
disk: 'narrow',
|
||||
temp: 'phone',
|
||||
temp: 'mobile',
|
||||
uptime: 'narrow',
|
||||
cluster: 'tablet',
|
||||
vms: 'compact',
|
||||
@@ -46,8 +46,8 @@ const HOST_COLUMN_MIN_LAYOUT: Record<ProxmoxHostTableColumnId, WorkloadTableLayo
|
||||
// columns (version pill, uptime, temp gauge, vms/cts badges, cluster
|
||||
// pill) take only what they need.
|
||||
const HOST_COLUMN_DESKTOP_WIDTHS: Record<ProxmoxHostTableColumnId, number> = {
|
||||
node: 18,
|
||||
version: 7,
|
||||
node: 20,
|
||||
version: 6,
|
||||
uptime: 7,
|
||||
cpu: 13,
|
||||
memory: 13,
|
||||
@@ -55,7 +55,7 @@ const HOST_COLUMN_DESKTOP_WIDTHS: Record<ProxmoxHostTableColumnId, number> = {
|
||||
temp: 6,
|
||||
vms: 5,
|
||||
cts: 5,
|
||||
cluster: 13,
|
||||
cluster: 12,
|
||||
};
|
||||
|
||||
const HOST_COLUMN_RESPONSIVE_WEIGHTS: Record<
|
||||
@@ -69,13 +69,15 @@ const HOST_COLUMN_RESPONSIVE_WEIGHTS: Record<
|
||||
disk: 15,
|
||||
uptime: 15,
|
||||
},
|
||||
// Phones carry the same five columns as the guest table below them. A sixth
|
||||
// (temperature) left the node name with four characters, which made a
|
||||
// cluster of "Analytics A".."F" rows unreadable.
|
||||
phone: {
|
||||
node: 32,
|
||||
cpu: 13,
|
||||
memory: 13,
|
||||
disk: 13,
|
||||
temp: 15,
|
||||
uptime: 14,
|
||||
cpu: 15,
|
||||
memory: 15,
|
||||
disk: 15,
|
||||
uptime: 15,
|
||||
},
|
||||
mobile: {
|
||||
node: 32,
|
||||
@@ -146,6 +148,11 @@ export const getProxmoxHostVisibleColumnsForLayout = (
|
||||
);
|
||||
};
|
||||
|
||||
// The node cell carries the expansion toggle and status dot before the name,
|
||||
// so it needs more than the shared 30% phone identity anchor to show a
|
||||
// typical node name whole.
|
||||
const PROXMOX_HOST_PHONE_IDENTITY_WIDTH_PERCENT = 38;
|
||||
|
||||
export const getProxmoxHostColumnWidthStyle = (
|
||||
columnId: ProxmoxHostTableColumnId,
|
||||
layoutMode: WorkloadTableLayoutMode,
|
||||
@@ -159,9 +166,11 @@ export const getProxmoxHostColumnWidthStyle = (
|
||||
visibleColumnIds,
|
||||
layoutMode === 'narrow'
|
||||
? { columnId: 'node', widthPercent: PLATFORM_TABLE_NARROW_IDENTITY_WIDTH_PERCENT }
|
||||
: layoutMode === 'phone' || layoutMode === 'mobile'
|
||||
? { columnId: 'node', widthPercent: PLATFORM_TABLE_PHONE_IDENTITY_WIDTH_PERCENT }
|
||||
: undefined,
|
||||
: layoutMode === 'phone'
|
||||
? { columnId: 'node', widthPercent: PROXMOX_HOST_PHONE_IDENTITY_WIDTH_PERCENT }
|
||||
: layoutMode === 'mobile'
|
||||
? { columnId: 'node', widthPercent: PLATFORM_TABLE_PHONE_IDENTITY_WIDTH_PERCENT }
|
||||
: undefined,
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user