From 063a4a5532a3dd0a62afd122b42e9f8422fdf3e6 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Wed, 22 Apr 2026 23:03:02 +0200 Subject: [PATCH] ui(deploy): HistoryDisclosure with inline log expansion Collapsible deployment history table (sorted newest-first) with click-to-expand StartupLogPanel for any historical deployment row. Co-Authored-By: Claude Sonnet 4.6 --- .../DeploymentTab/HistoryDisclosure.tsx | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 ui/src/pages/AppsTab/AppDeploymentPage/DeploymentTab/HistoryDisclosure.tsx diff --git a/ui/src/pages/AppsTab/AppDeploymentPage/DeploymentTab/HistoryDisclosure.tsx b/ui/src/pages/AppsTab/AppDeploymentPage/DeploymentTab/HistoryDisclosure.tsx new file mode 100644 index 00000000..bb74b60c --- /dev/null +++ b/ui/src/pages/AppsTab/AppDeploymentPage/DeploymentTab/HistoryDisclosure.tsx @@ -0,0 +1,64 @@ +import { useState } from 'react'; +import { DataTable } from '@cameleer/design-system'; +import type { Column } from '@cameleer/design-system'; +import type { Deployment, AppVersion } from '../../../../api/queries/admin/apps'; +import { timeAgo } from '../../../../utils/format-utils'; +import { StartupLogPanel } from '../../../../components/StartupLogPanel'; +import styles from '../AppDeploymentPage.module.css'; + +interface Props { + deployments: Deployment[]; + versions: AppVersion[]; + appSlug: string; + envSlug: string; +} + +export function HistoryDisclosure({ deployments, versions, appSlug, envSlug }: Props) { + const [open, setOpen] = useState(false); + const [expanded, setExpanded] = useState(null); + const versionMap = new Map(versions.map((v) => [v.id, v])); + + const rows = deployments + .slice() + .sort((a, b) => (b.createdAt ?? '').localeCompare(a.createdAt ?? '')); + + const columns: Column[] = [ + { key: 'createdAt', header: 'Started', render: (_, d) => timeAgo(d.createdAt) }, + { + key: 'appVersionId', header: 'Version', + render: (_, d) => { + const v = versionMap.get(d.appVersionId); + return v ? `v${v.version}` : '?'; + }, + }, + { key: 'status', header: 'Status' }, + { + key: 'deployedAt', header: 'Duration', + render: (_, d) => d.deployedAt && d.createdAt + ? `${Math.round((Date.parse(d.deployedAt) - Date.parse(d.createdAt)) / 1000)}s` + : '—', + }, + ]; + + return ( +
+ + {open && ( + <> + setExpanded(expanded === row.id ? null : row.id)} + /> + {expanded && (() => { + const d = rows.find((r) => r.id === expanded); + if (!d) return null; + return ; + })()} + + )} +
+ ); +}