From 25d4eff2be4aef330c2f61c5e886288762098492 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Fri, 29 May 2026 10:03:01 +0100 Subject: [PATCH] refactor(proxmox): extract PBS artifacts table into ProxmoxPbsTable Fifth decomposition step. Moves the "Source details > PBS artifacts" view out of ProxmoxBackupsTable into a presentational ProxmoxPbsTable component that owns its error / loading / empty / table states and takes the filtered backups, sort accessors/handler, size scale, and resource-derived error/loading/refresh as props. The parent keeps the filtered+sorted memo and the shared search/status filters. Bakes in the table-fixed + colgroup layout fix so the Files column no longer balloons on wide viewports. Behavior-preserving; verified live at 1800px (balanced columns, sorting and verification/protection badges intact, zero console errors). The orchestrator drops from 2571 to 2366 lines. --- .../features/proxmox/ProxmoxBackupsTable.tsx | 235 +--------------- .../src/features/proxmox/ProxmoxPbsTable.tsx | 263 ++++++++++++++++++ 2 files changed, 278 insertions(+), 220 deletions(-) create mode 100644 frontend-modern/src/features/proxmox/ProxmoxPbsTable.tsx diff --git a/frontend-modern/src/features/proxmox/ProxmoxBackupsTable.tsx b/frontend-modern/src/features/proxmox/ProxmoxBackupsTable.tsx index df8aa3977..6caed7366 100644 --- a/frontend-modern/src/features/proxmox/ProxmoxBackupsTable.tsx +++ b/frontend-modern/src/features/proxmox/ProxmoxBackupsTable.tsx @@ -123,6 +123,7 @@ import { artifactStateLabel, } from './proxmoxBackupsTableShared'; import { ProxmoxBackupsCoverageStrip } from './ProxmoxBackupsCoverageStrip'; +import { ProxmoxPbsTable } from './ProxmoxPbsTable'; import { ProxmoxRecoverableTable } from './ProxmoxRecoverableTable'; import { ProxmoxTasksTable } from './ProxmoxTasksTable'; @@ -1854,226 +1855,20 @@ export const ProxmoxBackupsTable: Component<{ - - void refetchPBS()} - class="inline-flex min-h-10 items-center rounded-md border border-border px-3 py-2 text-sm font-medium hover:bg-surface-hover" - > - Refresh - - } - /> - - } - > - - - - } - > - 0} - fallback={ - - - - } - > - - - - - - - - - - - - Files - - - - - - {(backup) => ( - - -
-
{pbsWorkloadLabel(backup)}
-
- {backup.backupType || 'backup'} -
-
-
- -
-
- {pbsRepositoryLabel(backup)} -
-
- {backup.instance || '—'} -
-
-
- - —} - > - {formatRelativeTime(backup.backupTime, { compact: true })} - - - - 0 - ? (backup.size / pbsSizeMaxBytes()) * 100 - : 0 - } - fillClass="bg-blue-500/40 dark:bg-blue-500/40" - label={formatBytes(backup.size)} - tooltip={`${formatBytes(backup.size)} (relative to largest PBS artifact in view)`} - /> - - - - Unverified - - } - > - - Verified - - - - - Unprotected} - > - - Protected - - - - - - {(backup.files ?? []).length > 0 - ? `${backup.files.length} files` - : '—'} - - -
- )} -
-
-
-
-
-
-
+ 0} + errorMessage={(pbsBackups.error as Error | undefined)?.message} + isLoading={pbsBackups() === undefined} + onRefresh={() => void refetchPBS()} + emptyIcon={props.emptyIcon} + emptyTitle={sourceDetailSpecFor('pbs').emptyTitle} + emptyDescription={sourceDetailSpecFor('pbs').emptyDescription} + sortKey={pbsSortKey} + sortDirection={pbsSortDirection} + onSort={handlePBSSort} + sizeMaxBytes={pbsSizeMaxBytes()} + />
diff --git a/frontend-modern/src/features/proxmox/ProxmoxPbsTable.tsx b/frontend-modern/src/features/proxmox/ProxmoxPbsTable.tsx new file mode 100644 index 000000000..ba9312243 --- /dev/null +++ b/frontend-modern/src/features/proxmox/ProxmoxPbsTable.tsx @@ -0,0 +1,263 @@ +import { For, Show, type Accessor, type JSX } from 'solid-js'; + +import { Card } from '@/components/shared/Card'; +import { EmptyState } from '@/components/shared/EmptyState'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/shared/Table'; +import { TableCard } from '@/components/shared/TableCard'; +import { formatBytes, formatRelativeTime } from '@/utils/format'; +import { + PLATFORM_TABLE_BODY_CLASS, + PLATFORM_TABLE_CARD_CLASS, + PLATFORM_TABLE_HEADER_ROW_CLASS, + getPlatformTableCellClassForKind, + getPlatformTableHeadClassForKind, +} from '@/features/platformPage/sharedPlatformPage'; +import type { PBSBackup } from '@/types/api'; + +import { pbsRepositoryLabel, pbsWorkloadLabel, type PBSSortKey } from './proxmoxBackupsTableModel'; +import { RowMetricBar, SortableHead } from './proxmoxBackupsTableShared'; + +const hasValidBackupTime = (backup: PBSBackup): boolean => + !!backup.backupTime && Number.isFinite(Date.parse(backup.backupTime)); + +// "Source details > PBS artifacts" table: every Proxmox Backup Server snapshot +// with repository, age, size, verification, and protection. Presentational — +// the parent owns the filtered + sorted memo, shared filters, and the PBS +// resource; this component just renders its error / loading / empty / table +// states. table-fixed + colgroup keeps columns from ballooning on wide views. +export function ProxmoxPbsTable(props: { + backups: PBSBackup[]; + hasAnyArtifacts: boolean; + errorMessage?: string; + isLoading: boolean; + onRefresh: () => void; + emptyIcon: JSX.Element; + emptyTitle: string; + emptyDescription: string; + sortKey: Accessor; + sortDirection: Accessor<'asc' | 'desc'>; + onSort: (key: PBSSortKey) => void; + sizeMaxBytes: number; +}) { + return ( + + props.onRefresh()} + class="inline-flex min-h-10 items-center rounded-md border border-border px-3 py-2 text-sm font-medium hover:bg-surface-hover" + > + Refresh + + } + /> + + } + > + + + + } + > + 0} + fallback={ + + + + } + > + + + + + + + + + + + + + + + + + + + + Files + + + + + {(backup) => ( + + +
+
{pbsWorkloadLabel(backup)}
+
+ {backup.backupType || 'backup'} +
+
+
+ +
+
{pbsRepositoryLabel(backup)}
+
+ {backup.instance || '—'} +
+
+
+ + —} + > + {formatRelativeTime(backup.backupTime, { compact: true })} + + + + 0 ? (backup.size / props.sizeMaxBytes) * 100 : 0 + } + fillClass="bg-blue-500/40 dark:bg-blue-500/40" + label={formatBytes(backup.size)} + tooltip={`${formatBytes(backup.size)} (relative to largest PBS artifact in view)`} + /> + + + Unverified + } + > + + Verified + + + + + Unprotected} + > + + Protected + + + + + + {(backup.files ?? []).length > 0 ? `${backup.files.length} files` : '—'} + + +
+ )} +
+
+
+
+
+
+
+ ); +}