From 2ce3d4278c84bf67d9c859da213a85ebf79633c3 Mon Sep 17 00:00:00 2001 From: Matt Jenkinson <75292329+mattdjenkinson@users.noreply.github.com> Date: Fri, 6 Nov 2026 13:25:23 +0000 Subject: [PATCH 1/4] feat(ui): Redesign the workload overview The overview now leads with a summary card in the ALB overview's style: status, endpoint, when it last changed, and the last hour's instances, CPU, memory, network and requests. Instances and configuration move into cards of their own, and recent activity replaces the recent-logs block that was usually empty. A deploy takes seconds, faster than the page polls, so the card no longer shows a step-by-step progress bar. It says what it can say truthfully: deploying, deployed (with a timeline rebuilt from condition timestamps), stalled with the cause the API reports (a missing Secret or ConfigMap, quota), or failed. Pages poll every 3s until instances are actually ready, not just until the workload reports Available. Instance pages explain a failure in plain words with what to try, and the instances table shows per-instance network traffic and status badges in the portal's style. The workload Metrics tab gains network in and out. --- ui/consumer/src/components/detail-list.tsx | 4 +- .../src/components/instance-problem.tsx | 63 ++ .../src/components/metric-sparkline.tsx | 4 + .../src/components/resource-activity.tsx | 39 +- ui/consumer/src/components/skeletons.tsx | 56 +- .../src/components/workload-instances.tsx | 286 ++++++--- .../src/components/workload-overview-card.tsx | 564 ++++++++++++++++++ ui/consumer/src/components/workload-table.tsx | 2 + ui/consumer/src/lib/api.ts | 57 +- ui/consumer/src/lib/deploy-presenters.test.ts | 160 +++++ ui/consumer/src/lib/deploy-presenters.ts | 241 ++++++++ ui/consumer/src/lib/metrics-queries.test.ts | 22 + ui/consumer/src/lib/metrics-queries.ts | 30 +- .../src/lib/network-interface-metrics.test.ts | 19 +- .../src/lib/network-interface-metrics.ts | 72 +++ .../src/lib/use-workload-overview-state.ts | 15 +- .../src/lib/workload-presenters.test.ts | 155 +++++ ui/consumer/src/lib/workload-presenters.ts | 157 +++++ ui/consumer/src/lib/workload-settling.ts | 20 + ui/consumer/src/pages/instance-overview.tsx | 19 +- ui/consumer/src/pages/workload-detail.tsx | 379 +++++++----- ui/consumer/src/pages/workload-metrics.tsx | 42 +- 22 files changed, 2081 insertions(+), 325 deletions(-) create mode 100644 ui/consumer/src/components/instance-problem.tsx create mode 100644 ui/consumer/src/components/workload-overview-card.tsx create mode 100644 ui/consumer/src/lib/deploy-presenters.test.ts create mode 100644 ui/consumer/src/lib/deploy-presenters.ts create mode 100644 ui/consumer/src/lib/workload-settling.ts diff --git a/ui/consumer/src/components/detail-list.tsx b/ui/consumer/src/components/detail-list.tsx index b4b34079..c88ada23 100644 --- a/ui/consumer/src/components/detail-list.tsx +++ b/ui/consumer/src/components/detail-list.tsx @@ -46,7 +46,7 @@ export function DetailList({ ); } -/** Compact status badge matching portal `BadgeStatus` sizing. */ +/** Compact status badge, styled like cloud-portal's `BadgeStatus` (whose classes the host compiles). */ export function StatusBadge({ type, children, @@ -59,7 +59,7 @@ export function StatusBadge({ + className="text-5xs flex cursor-default items-center gap-1.5 px-1 py-0.5 font-bold tracking-[0.03em] uppercase"> {children} diff --git a/ui/consumer/src/components/instance-problem.tsx b/ui/consumer/src/components/instance-problem.tsx new file mode 100644 index 00000000..cf379859 --- /dev/null +++ b/ui/consumer/src/components/instance-problem.tsx @@ -0,0 +1,63 @@ +/** + * The instance overview's problem callout: what is wrong in plain words, the + * platform's own message, and what to try. Shown only while the instance is + * failing or crashing; the General card's status badge stays as the summary. + */ +import { STATUS_TONE_COLOR, type InstanceProblem } from '../lib/workload-presenters'; +import { Icon } from '@datum-cloud/datum-ui/icons'; +import { CircleXIcon, LogsIcon, TriangleAlertIcon } from 'lucide-react'; +import { Link } from 'react-router'; + +function tint(color: string, percent: number): string { + return `color-mix(in oklab, ${color} ${percent}%, transparent)`; +} + +export function InstanceProblemCallout({ problem, logsHref }: { problem: InstanceProblem; logsHref?: string }) { + const color = STATUS_TONE_COLOR[problem.tone]; + return ( +
+ +
+
+

{problem.title}

+ {problem.showLogs && logsHref ? ( + + + View logs + + ) : null} +
+ {problem.message ? ( +

+ {problem.message} +

+ ) : null} + {problem.hints.length > 0 ? ( +
    + {problem.hints.map((hint) => ( +
  • + + • + + {hint} +
  • + ))} +
+ ) : null} +
+
+ ); +} diff --git a/ui/consumer/src/components/metric-sparkline.tsx b/ui/consumer/src/components/metric-sparkline.tsx index f165dc10..57dfb772 100644 --- a/ui/consumer/src/components/metric-sparkline.tsx +++ b/ui/consumer/src/components/metric-sparkline.tsx @@ -28,6 +28,7 @@ export function MetricSparkline({ denied = false, allocated, series, + hideValue = false, }: { /** Project whose metrics to read. */ projectId: string | undefined; @@ -52,6 +53,8 @@ export function MetricSparkline({ * in one request). When given, `query` is ignored and nothing is fetched. */ series?: readonly ChartDataPoint[]; + /** Draw the spark alone, for callers that show the value themselves. */ + hideValue?: boolean; }) { const gradientId = useId().replace(/[^a-zA-Z0-9_-]/g, ''); const provided = series !== undefined; @@ -154,6 +157,7 @@ export function MetricSparkline({ )}