diff --git a/frontend-modern/src/features/proxmox/ProxmoxCoverageTable.tsx b/frontend-modern/src/features/proxmox/ProxmoxCoverageTable.tsx
index 56a9f3d3d..f54bb9892 100644
--- a/frontend-modern/src/features/proxmox/ProxmoxCoverageTable.tsx
+++ b/frontend-modern/src/features/proxmox/ProxmoxCoverageTable.tsx
@@ -32,6 +32,7 @@ import { getProxmoxBackupSourcePresentation } from './proxmoxBackupSourcePresent
import {
ArtifactSourceBadge,
ArtifactStateBadge,
+ PROXMOX_BACKUP_COLUMN_LABELS,
ProxmoxBackupWorkloadTypeBadge,
SortableHead,
artifactStateLabel,
@@ -149,7 +150,9 @@ export function ProxmoxCoverageTable(props: {
headClass={getPlatformTableHeadClassForKind('name')}
/>
Type
- ID
+
+ {PROXMOX_BACKUP_COLUMN_LABELS.targetId}
+
Node
| Source |
Location |
- Created |
+
+ {PROXMOX_BACKUP_COLUMN_LABELS.created}
+ |
Size |
State |
- Details |
+
+ {PROXMOX_BACKUP_COLUMN_LABELS.details}
+ |
diff --git a/frontend-modern/src/features/proxmox/ProxmoxRecoverableTable.tsx b/frontend-modern/src/features/proxmox/ProxmoxRecoverableTable.tsx
index 497ac6c5a..8f7eb67bd 100644
--- a/frontend-modern/src/features/proxmox/ProxmoxRecoverableTable.tsx
+++ b/frontend-modern/src/features/proxmox/ProxmoxRecoverableTable.tsx
@@ -29,6 +29,7 @@ import type { RecoverableSortKey } from './proxmoxBackupsTableModel';
import {
ArtifactSourceBadge,
ArtifactStateBadge,
+ PROXMOX_BACKUP_COLUMN_LABELS,
ProxmoxBackupWorkloadTypeBadge,
RowMetricBar,
SortableHead,
@@ -187,7 +188,9 @@ export function ProxmoxRecoverableTable(props: {
headClass={getPlatformTableHeadClassForKind('name')}
/>
Type
- ID
+
+ {PROXMOX_BACKUP_COLUMN_LABELS.targetId}
+
- Details
+
+ {PROXMOX_BACKUP_COLUMN_LABELS.details}
+
@@ -250,8 +255,7 @@ export function ProxmoxRecoverableTable(props: {
colspan={COLUMN_COUNT}
class="border-t border-border bg-surface-alt px-3 py-1.5 text-[11px] font-semibold uppercase tracking-[0.14em] text-base-content"
>
- {group.label}
- {' '}
+ {group.label}{' '}
{group.items.length} {group.items.length === 1 ? 'backup' : 'backups'}
diff --git a/frontend-modern/src/features/proxmox/__tests__/ProxmoxBackupsTable.test.tsx b/frontend-modern/src/features/proxmox/__tests__/ProxmoxBackupsTable.test.tsx
index ba37784dc..81f0ee82c 100644
--- a/frontend-modern/src/features/proxmox/__tests__/ProxmoxBackupsTable.test.tsx
+++ b/frontend-modern/src/features/proxmox/__tests__/ProxmoxBackupsTable.test.tsx
@@ -143,8 +143,10 @@ describe('ProxmoxBackupsTable', () => {
expect(screen.getByRole('columnheader', { name: /location/i })).toBeInTheDocument();
expect(screen.getByRole('columnheader', { name: /source/i })).toBeInTheDocument();
expect(screen.getByRole('columnheader', { name: /type/i })).toBeInTheDocument();
+ expect(screen.getByRole('columnheader', { name: /target id/i })).toBeInTheDocument();
expect(screen.getAllByText('LXC').length).toBeGreaterThan(0);
expect(screen.getAllByText('PBS').length).toBeGreaterThan(0);
+ expect(screen.getByText('2 PBS files')).toBeInTheDocument();
expect(screen.getByRole('button', { name: /pbs snapshots/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /pve backup files/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /guest snapshots/i })).toBeInTheDocument();
diff --git a/frontend-modern/src/features/proxmox/__tests__/ProxmoxCoverageTable.test.tsx b/frontend-modern/src/features/proxmox/__tests__/ProxmoxCoverageTable.test.tsx
index b4e13c405..78d055a3d 100644
--- a/frontend-modern/src/features/proxmox/__tests__/ProxmoxCoverageTable.test.tsx
+++ b/frontend-modern/src/features/proxmox/__tests__/ProxmoxCoverageTable.test.tsx
@@ -55,12 +55,12 @@ describe('ProxmoxCoverageTable column visibility', () => {
// Always-on columns.
expect(headers).toContain('Workload');
expect(headers).toContain('Type');
- expect(headers).toContain('ID');
+ expect(headers).toContain('Target ID');
expect(headers).toContain('Node');
// Source columns gate on their flags.
- expect(headers).toContain('PBS');
- expect(headers).toContain('Snapshots');
- expect(headers).not.toContain('PVE files');
+ expect(headers).toContain('Latest PBS');
+ expect(headers).toContain('Latest snapshot');
+ expect(headers).not.toContain('Latest PVE file');
expect(headers).not.toContain('Latest task');
});
});
diff --git a/frontend-modern/src/features/proxmox/__tests__/proxmoxBackupRecoveryModel.test.ts b/frontend-modern/src/features/proxmox/__tests__/proxmoxBackupRecoveryModel.test.ts
index 72f493d77..8a5f73237 100644
--- a/frontend-modern/src/features/proxmox/__tests__/proxmoxBackupRecoveryModel.test.ts
+++ b/frontend-modern/src/features/proxmox/__tests__/proxmoxBackupRecoveryModel.test.ts
@@ -108,11 +108,28 @@ describe('proxmoxBackupRecoveryModel', () => {
'PVE file',
'Snapshot',
]);
+ expect(model.recoverableArtifacts[0].detail).toBe('2 PBS files');
expect(coverageRowMatchesSearch(row, 'pbs-docker')).toBe(true);
expect(coverageRowMatchesSearch(row, 'PVE backup file')).toBe(true);
expect(recoverableArtifactMatchesSearch(model.recoverableArtifacts[0], 'main')).toBe(true);
});
+ it('does not describe PBS backups with omitted file manifests as zero-file backups', () => {
+ const model = buildProxmoxBackupRecoveryModel({
+ workloads: [workload({})],
+ pbsBackups: [pbsBackup({ files: [] })],
+ archives: [],
+ snapshots: [],
+ tasks: [],
+ nowMs: Date.parse('2026-05-26T08:00:00Z'),
+ });
+
+ expect(model.recoverableArtifacts[0].detail).toBe('PBS files not listed');
+ expect(recoverableArtifactMatchesSearch(model.recoverableArtifacts[0], 'not listed')).toBe(
+ true,
+ );
+ });
+
it('surfaces a failed latest backup task as workload attention', () => {
const model = buildProxmoxBackupRecoveryModel({
workloads: [workload({})],
diff --git a/frontend-modern/src/features/proxmox/__tests__/proxmoxBackupSourcePresentation.test.ts b/frontend-modern/src/features/proxmox/__tests__/proxmoxBackupSourcePresentation.test.ts
index 06ce5e2e3..6f59f2188 100644
--- a/frontend-modern/src/features/proxmox/__tests__/proxmoxBackupSourcePresentation.test.ts
+++ b/frontend-modern/src/features/proxmox/__tests__/proxmoxBackupSourcePresentation.test.ts
@@ -12,16 +12,19 @@ describe('proxmoxBackupSourcePresentation', () => {
expect(getProxmoxBackupSourcePresentation('pbs')).toMatchObject({
badgeLabel: 'PBS',
+ coverageColumnLabel: 'Latest PBS',
filterLabel: 'PBS snapshots',
timelineLabel: 'PBS snapshots',
});
expect(getProxmoxBackupSourcePresentation('archive')).toMatchObject({
badgeLabel: 'PVE file',
+ coverageColumnLabel: 'Latest PVE file',
filterLabel: 'PVE backup files',
timelineLabel: 'PVE backup files',
});
expect(getProxmoxBackupSourcePresentation('snapshot')).toMatchObject({
badgeLabel: 'Snapshot',
+ coverageColumnLabel: 'Latest snapshot',
filterLabel: 'Guest snapshots',
timelineLabel: 'Guest snapshots',
});
diff --git a/frontend-modern/src/features/proxmox/proxmoxBackupRecoveryModel.ts b/frontend-modern/src/features/proxmox/proxmoxBackupRecoveryModel.ts
index 5cd8752fd..c01073bf7 100644
--- a/frontend-modern/src/features/proxmox/proxmoxBackupRecoveryModel.ts
+++ b/frontend-modern/src/features/proxmox/proxmoxBackupRecoveryModel.ts
@@ -116,6 +116,12 @@ function parseTimestampMs(value: string | undefined): number | undefined {
return Number.isFinite(ms) ? ms : undefined;
}
+function pbsBackupFileDetailLabel(fileCount: number): string {
+ if (fileCount <= 0) return 'PBS files not listed';
+ if (fileCount === 1) return '1 PBS file';
+ return `${fileCount} PBS files`;
+}
+
function normalizeKey(value: string | number | undefined | null): string {
return String(value ?? '')
.trim()
@@ -463,7 +469,7 @@ export function buildProxmoxBackupRecoveryModel(
createdMs,
size: backup.size,
location: `${backup.datastore || '—'} / ${backup.namespace?.trim() || '(root)'}`,
- detail: backup.files.length === 1 ? '1 file' : `${backup.files.length} files`,
+ detail: pbsBackupFileDetailLabel(backup.files.length),
protected: backup.protected,
verified: backup.verified,
fileCount: backup.files.length,
diff --git a/frontend-modern/src/features/proxmox/proxmoxBackupSourcePresentation.ts b/frontend-modern/src/features/proxmox/proxmoxBackupSourcePresentation.ts
index 01f1ce62d..2e60b6349 100644
--- a/frontend-modern/src/features/proxmox/proxmoxBackupSourcePresentation.ts
+++ b/frontend-modern/src/features/proxmox/proxmoxBackupSourcePresentation.ts
@@ -24,7 +24,7 @@ const SOURCE_PRESENTATION: Record ;
-export const ARCHIVE_STATUS_FILTERS: FilterOption<'all' | 'protected' | 'verified' | 'unverified'>[] =
- [
- { value: 'all', label: 'All' },
- { value: 'protected', label: 'Protected', tone: 'info', leading: statusDot('bg-blue-500') },
- { value: 'verified', label: 'Verified', tone: 'success', leading: statusDot('bg-emerald-500') },
- {
- value: 'unverified',
- label: 'Unverified',
- tone: 'warning',
- leading: statusDot('bg-amber-500'),
- },
- ];
+export const PROXMOX_BACKUP_COLUMN_LABELS = {
+ targetId: 'Target ID',
+ created: 'Created',
+ details: 'Details',
+} as const;
+
+export const ARCHIVE_STATUS_FILTERS: FilterOption<
+ 'all' | 'protected' | 'verified' | 'unverified'
+>[] = [
+ { value: 'all', label: 'All' },
+ { value: 'protected', label: 'Protected', tone: 'info', leading: statusDot('bg-blue-500') },
+ { value: 'verified', label: 'Verified', tone: 'success', leading: statusDot('bg-emerald-500') },
+ {
+ value: 'unverified',
+ label: 'Unverified',
+ tone: 'warning',
+ leading: statusDot('bg-amber-500'),
+ },
+];
export const PBS_STATUS_FILTERS: FilterOption<'all' | 'protected' | 'verified' | 'unverified'>[] = [
{ value: 'all', label: 'All' },