diff --git a/ui/consumer/package.json b/ui/consumer/package.json index b5a53967..98a6881c 100644 --- a/ui/consumer/package.json +++ b/ui/consumer/package.json @@ -14,7 +14,7 @@ }, "dependencies": { "@datum-cloud/activity-ui": "0.7.1", - "@datum-cloud/datum-ui": "3.6.0", + "@datum-cloud/datum-ui": "3.8.0", "@tanstack/react-query": "5.102.8", "@tanstack/react-table": "^9.3.1", "@tanstack/react-virtual": "^3.14.26", diff --git a/ui/consumer/pnpm-lock.yaml b/ui/consumer/pnpm-lock.yaml index d600b664..583670e8 100644 --- a/ui/consumer/pnpm-lock.yaml +++ b/ui/consumer/pnpm-lock.yaml @@ -13,10 +13,10 @@ importers: dependencies: '@datum-cloud/activity-ui': specifier: 0.7.1 - version: 0.7.1(23f2bc69542bccbc3ee5c81689a30302) + version: 0.7.1(4d79df9ee4747b64609303fe30ef220b) '@datum-cloud/datum-ui': - specifier: 3.6.0 - version: 3.6.0(@monaco-editor/react@4.7.0(monaco-editor@0.52.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(@tanstack/react-table@9.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(@tanstack/react-virtual@3.14.26(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(ai@7.0.161(zod@4.6.7))(date-fns-tz@3.2.0(date-fns@4.4.0))(date-fns@4.4.0)(lucide-react@1.67.0(@types/react@19.3.0)(react@19.3.0))(monaco-editor@0.52.2)(motion@13.5.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(nuqs@2.10.2(react-router@8.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(react@19.3.0))(react-day-picker@10.0.2(@types/react@19.3.0)(react@19.3.0))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(recharts@3.10.1(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react-is@19.3.0)(react@19.3.0)(redux@5.0.1))(sonner@2.0.8(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(zod@4.6.7) + specifier: 3.8.0 + version: 3.8.0(@monaco-editor/react@4.7.0(monaco-editor@0.52.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(@tanstack/react-table@9.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(@tanstack/react-virtual@3.14.26(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(ai@7.0.161(zod@4.6.7))(date-fns-tz@3.2.0(date-fns@4.4.0))(date-fns@4.4.0)(lucide-react@1.67.0(@types/react@19.3.0)(react@19.3.0))(monaco-editor@0.52.2)(motion@13.5.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(nuqs@2.10.2(react-router@8.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(react@19.3.0))(react-day-picker@10.0.2(@types/react@19.3.0)(react@19.3.0))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(recharts@3.10.1(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react-is@19.3.0)(react@19.3.0)(redux@5.0.1))(sonner@2.0.8(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(zod@4.6.7) '@tanstack/react-query': specifier: 5.102.8 version: 5.102.8(react@19.3.0) @@ -261,8 +261,8 @@ packages: peerDependencies: ai: '>=7 <8' - '@datum-cloud/datum-ui@3.6.0': - resolution: {integrity: sha512-SWA6m7k1UXsu8JZEjFGo0K32zfaoNFozYtLlrx7emNSP1TRBIOLkFVtRSewIhWKVPPMIdgI2o5zE2EuF+F0MOg==} + '@datum-cloud/datum-ui@3.8.0': + resolution: {integrity: sha512-T0IJxfBllKP7/TWO1Pbi7XCKKUYa0icaK176Okoyy1k4gR6ibOVl9QZSDrqR7LiHKGOVar7htzbetDID6K9vsQ==} peerDependencies: '@conform-to/react': '>=1.19.4 <2' '@conform-to/zod': '>=1.19.4 <2' @@ -304,7 +304,7 @@ packages: recharts: '>=3 <4' sonner: '>=2.0.7 <3' streamdown: '>=2 <3' - zod: '>=4 <5' + zod: '>=4.6.6 <5' peerDependenciesMeta: '@conform-to/react': optional: true @@ -356,8 +356,6 @@ packages: optional: true monaco-editor: optional: true - motion: - optional: true nprogress: optional: true nuqs: @@ -1825,8 +1823,8 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - isomorphic-dompurify@3.22.0: - resolution: {integrity: sha512-cz/BkSODnQir4IV2quqbuEHhAGz4MAYGvhoemsxuwMpY/oZbRHlNjPLPyIQ0fdULiwDPUE+OHPwWb6XTEiNk1A==} + isomorphic-dompurify@3.23.0: + resolution: {integrity: sha512-RuYFgdNApoTlR3QxVUyWdS/1ATpLEWEVg3H2tMImbfr6HkYPnuW1sdEtW4PRGlFhp04VbQQjcjXnucHYpXT44A==} engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} deprecated: Raised the minimum Node.js version (breaking) without a major bump. Use 4.x for the same code with correct semver, or pin 3.19.0 for Node < 22.22.2. @@ -2152,6 +2150,9 @@ packages: tailwind-merge@3.6.0: resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==} + tailwind-merge@3.7.0: + resolution: {integrity: sha512-XPPUyAc+cvspz3lHTcR/QgPfW2A0lv/xQNIjX3HGhLR+Nq2lHaLq5MtTesHn8GUr3W3DguT2KT5x3NVgRtYwmA==} + tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} @@ -2566,9 +2567,9 @@ snapshots: '@date-fns/tz@1.5.0': {} - '@datum-cloud/activity-ui@0.7.1(23f2bc69542bccbc3ee5c81689a30302)': + '@datum-cloud/activity-ui@0.7.1(4d79df9ee4747b64609303fe30ef220b)': dependencies: - '@datum-cloud/datum-ui': 3.6.0(@monaco-editor/react@4.7.0(monaco-editor@0.52.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(@tanstack/react-table@9.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(@tanstack/react-virtual@3.14.26(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(ai@7.0.161(zod@4.6.7))(date-fns-tz@3.2.0(date-fns@4.4.0))(date-fns@4.4.0)(lucide-react@1.67.0(@types/react@19.3.0)(react@19.3.0))(monaco-editor@0.52.2)(motion@13.5.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(nuqs@2.10.2(react-router@8.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(react@19.3.0))(react-day-picker@10.0.2(@types/react@19.3.0)(react@19.3.0))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(recharts@3.10.1(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react-is@19.3.0)(react@19.3.0)(redux@5.0.1))(sonner@2.0.8(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(zod@4.6.7) + '@datum-cloud/datum-ui': 3.8.0(@monaco-editor/react@4.7.0(monaco-editor@0.52.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(@tanstack/react-table@9.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(@tanstack/react-virtual@3.14.26(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(ai@7.0.161(zod@4.6.7))(date-fns-tz@3.2.0(date-fns@4.4.0))(date-fns@4.4.0)(lucide-react@1.67.0(@types/react@19.3.0)(react@19.3.0))(monaco-editor@0.52.2)(motion@13.5.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(nuqs@2.10.2(react-router@8.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(react@19.3.0))(react-day-picker@10.0.2(@types/react@19.3.0)(react@19.3.0))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(recharts@3.10.1(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react-is@19.3.0)(react@19.3.0)(redux@5.0.1))(sonner@2.0.8(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(zod@4.6.7) '@monaco-editor/react': 4.7.0(monaco-editor@0.52.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@radix-ui/react-tooltip': 1.2.16(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) class-variance-authority: 0.7.1 @@ -2584,7 +2585,7 @@ snapshots: dependencies: ai: 7.0.161(zod@4.6.7) - '@datum-cloud/datum-ui@3.6.0(@monaco-editor/react@4.7.0(monaco-editor@0.52.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(@tanstack/react-table@9.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(@tanstack/react-virtual@3.14.26(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(ai@7.0.161(zod@4.6.7))(date-fns-tz@3.2.0(date-fns@4.4.0))(date-fns@4.4.0)(lucide-react@1.67.0(@types/react@19.3.0)(react@19.3.0))(monaco-editor@0.52.2)(motion@13.5.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(nuqs@2.10.2(react-router@8.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(react@19.3.0))(react-day-picker@10.0.2(@types/react@19.3.0)(react@19.3.0))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(recharts@3.10.1(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react-is@19.3.0)(react@19.3.0)(redux@5.0.1))(sonner@2.0.8(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(zod@4.6.7)': + '@datum-cloud/datum-ui@3.8.0(@monaco-editor/react@4.7.0(monaco-editor@0.52.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(@tanstack/react-table@9.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(@tanstack/react-virtual@3.14.26(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(ai@7.0.161(zod@4.6.7))(date-fns-tz@3.2.0(date-fns@4.4.0))(date-fns@4.4.0)(lucide-react@1.67.0(@types/react@19.3.0)(react@19.3.0))(monaco-editor@0.52.2)(motion@13.5.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(nuqs@2.10.2(react-router@8.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(react@19.3.0))(react-day-picker@10.0.2(@types/react@19.3.0)(react@19.3.0))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(recharts@3.10.1(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react-is@19.3.0)(react@19.3.0)(redux@5.0.1))(sonner@2.0.8(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(zod@4.6.7)': dependencies: '@datum-cloud/datum-ui-assistant-core': 0.1.0(ai@7.0.161(zod@4.6.7)) '@radix-ui/react-avatar': 1.2.6(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) @@ -2606,11 +2607,12 @@ snapshots: class-variance-authority: 0.7.1 clsx: 2.1.1 cmdk: 1.1.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - isomorphic-dompurify: 3.22.0 + isomorphic-dompurify: 3.23.0 lucide-react: 1.67.0(@types/react@19.3.0)(react@19.3.0) + motion: 13.5.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0) react: 19.3.0 react-dom: 19.3.0(react@19.3.0) - tailwind-merge: 3.6.0 + tailwind-merge: 3.7.0 tw-animate-css: 1.4.0 optionalDependencies: '@monaco-editor/react': 4.7.0(monaco-editor@0.52.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) @@ -2620,7 +2622,6 @@ snapshots: date-fns: 4.4.0 date-fns-tz: 3.2.0(date-fns@4.4.0) monaco-editor: 0.52.2 - motion: 13.5.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0) nuqs: 2.10.2(react-router@8.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(react@19.3.0) react-day-picker: 10.0.2(@types/react@19.3.0)(react@19.3.0) recharts: 3.10.1(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react-is@19.3.0)(react@19.3.0)(redux@5.0.1) @@ -3896,7 +3897,7 @@ snapshots: isexe@2.0.0: {} - isomorphic-dompurify@3.22.0: + isomorphic-dompurify@3.23.0: dependencies: dompurify: 3.4.17 jsdom: 30.0.1 @@ -4196,6 +4197,8 @@ snapshots: tailwind-merge@3.6.0: {} + tailwind-merge@3.7.0: {} + tiny-invariant@1.3.3: {} tinybench@6.2.2: {} diff --git a/ui/consumer/src/adapter.ts b/ui/consumer/src/adapter.ts index a3198639..48153b0e 100644 --- a/ui/consumer/src/adapter.ts +++ b/ui/consumer/src/adapter.ts @@ -192,6 +192,15 @@ function deriveReplicasPerRegion(placements: RawWorkloadPlacement[]): number | u return mins.every((m) => m === first) ? first : undefined; } +function deriveNamedPorts(runtime?: RawRuntime): Workload['namedPorts'] { + const containers = runtime?.sandbox?.containers ?? []; + return containers.flatMap((c) => + (c.ports ?? []).flatMap((p) => + p.name && p.port !== undefined ? [{ name: p.name, port: p.port, protocol: p.protocol ?? 'TCP' }] : [] + ) + ); +} + function derivePorts(runtime?: RawRuntime): string[] { const containers = runtime?.sandbox?.containers ?? []; return containers.flatMap((c) => @@ -349,6 +358,7 @@ export function toWorkload(raw: RawWorkload): Workload { runtimeType: runtime ? (runtime.sandbox ? 'Container sandbox' : 'Virtual machine') : undefined, tags: deriveTags(runtime), ports, + namedPorts: deriveNamedPorts(runtime), locations: workloadLocations(placements, raw.status?.placements ?? []), resources: deriveResources(runtime), replicasPerRegion: deriveReplicasPerRegion(placements), 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({ )}