From 2cf5b56441b31f204efbc57edded17cdf2fbd96c Mon Sep 17 00:00:00 2001 From: admin Date: Fri, 1 May 2026 10:25:50 +0200 Subject: [PATCH] OCI Warning status --- src/app/[locale]/dashboard/page.tsx | 6 +- src/app/[locale]/tenants/[name]/page.tsx | 2 + src/components/ui/warning-badge.tsx | 118 +++++++++++++++++++++++ src/messages/de.json | 4 + src/messages/en.json | 4 + src/messages/fr.json | 4 + src/messages/it.json | 4 + src/types/index.ts | 15 +++ 8 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 src/components/ui/warning-badge.tsx diff --git a/src/app/[locale]/dashboard/page.tsx b/src/app/[locale]/dashboard/page.tsx index 06637e9..57e0f03 100644 --- a/src/app/[locale]/dashboard/page.tsx +++ b/src/app/[locale]/dashboard/page.tsx @@ -11,6 +11,7 @@ import { import { personalAccountAtCapacity } from "@/lib/personal-org"; import { Card, CardHeader } from "@/components/ui/card"; import { StatusBadge } from "@/components/ui/status-badge"; +import { WarningBadge } from "@/components/ui/warning-badge"; import { OnboardingFlow } from "@/components/onboarding/onboarding-flow"; import { ProvisioningStatus } from "@/components/onboarding/provisioning-status"; import { formatDateTime } from "@/lib/format"; @@ -348,7 +349,10 @@ export default async function DashboardPage() { {tenant.metadata.name} - +
+ + +
{tenant.spec.agentName && ( diff --git a/src/app/[locale]/tenants/[name]/page.tsx b/src/app/[locale]/tenants/[name]/page.tsx index 1378fc5..b02692e 100644 --- a/src/app/[locale]/tenants/[name]/page.tsx +++ b/src/app/[locale]/tenants/[name]/page.tsx @@ -4,6 +4,7 @@ import { redirect, notFound } from "next/navigation"; import { getTenant } from "@/lib/k8s"; import { canUserSeeTenant } from "@/lib/visibility"; import { StatusBadge } from "@/components/ui/status-badge"; +import { WarningBadge } from "@/components/ui/warning-badge"; import { UsageDisplay } from "@/components/dashboard/usage-display"; import { PackageList } from "@/components/packages/package-list"; import { WorkspaceEditor } from "@/components/packages/workspace-editor"; @@ -88,6 +89,7 @@ export default async function TenantDetailPage({ {tenant.spec.displayName || name} + {tenant.spec.agentName && (

diff --git a/src/components/ui/warning-badge.tsx b/src/components/ui/warning-badge.tsx new file mode 100644 index 0000000..fb288ec --- /dev/null +++ b/src/components/ui/warning-badge.tsx @@ -0,0 +1,118 @@ +"use client"; + +import { useTranslations } from "next-intl"; + +/** + * Tenant warning shape received from the operator's status.warnings. + * Mirror of the operator's `TenantWarning` type. See + * pieced-operator/api/v1alpha1/piecedtenant_types.go. + */ +export interface TenantWarning { + source: string; + reason?: string; + message?: string; + since?: string; +} + +interface Props { + warnings: TenantWarning[]; +} + +/** + * Renders a small amber warning badge if there are any non-fatal + * warnings on the tenant. The badge sits visually next to the phase + * StatusBadge — they're separate concepts (phase = lifecycle, warnings + * = observed sub-issues) and may both be present at once (e.g. tenant + * is `Ready` but has a SkillPacksReady=False warning). + * + * Hover/focus reveals the warning detail. We don't truncate the message + * inside the tooltip; OCI/CRD condition messages tend to be short and + * include the actionable detail (which skill, which secret, which + * resolver). If a future warning source has a 5-line stacktrace as a + * message we'll need a different treatment; cross that bridge then. + * + * Returns null when there are no warnings — keep render-call sites + * simple, they don't have to gate on length themselves. + */ +export function WarningBadge({ warnings }: Props) { + const t = useTranslations("warnings"); + if (!warnings || warnings.length === 0) return null; + + const tooltipLabel = (() => { + try { + return warnings.length === 1 + ? t("oneTooltip") + : t("manyTooltip", { count: warnings.length }); + } catch { + return warnings.length === 1 + ? "1 warning" + : `${warnings.length} warnings`; + } + })(); + + return ( + + + + {/* + Tooltip. Hidden by default; shown on hover OR focus of the + sibling button. Positioned below-right so it doesn't collide with + the StatusBadge that typically sits left of this. Constrained + width so long messages wrap. + z-50 keeps it above table rows / cards. + */} +

+
+ {tooltipLabel} +
+
    + {warnings.map((w, i) => ( +
  • +
    + {w.source} +
    + {w.reason && ( +
    {w.reason}
    + )} + {w.message && ( +
    + {w.message} +
    + )} +
  • + ))} +
+
+ + ); +} diff --git a/src/messages/de.json b/src/messages/de.json index 8f87508..2f7f2f2 100644 --- a/src/messages/de.json +++ b/src/messages/de.json @@ -361,5 +361,9 @@ "Error": "Fehler", "Deleting": "Wird gelöscht", "Reconfiguring": "Wird neu konfiguriert" + }, + "warnings": { + "oneTooltip": "1 Warnung", + "manyTooltip": "{count} Warnungen" } } diff --git a/src/messages/en.json b/src/messages/en.json index 6cc7e37..7d67f76 100644 --- a/src/messages/en.json +++ b/src/messages/en.json @@ -361,5 +361,9 @@ "Error": "Error", "Deleting": "Deleting", "Reconfiguring": "Reconfiguring" + }, + "warnings": { + "oneTooltip": "1 warning", + "manyTooltip": "{count} warnings" } } diff --git a/src/messages/fr.json b/src/messages/fr.json index 08f3418..817db6f 100644 --- a/src/messages/fr.json +++ b/src/messages/fr.json @@ -361,5 +361,9 @@ "Error": "Erreur", "Deleting": "Suppression", "Reconfiguring": "Reconfiguration" + }, + "warnings": { + "oneTooltip": "1 avertissement", + "manyTooltip": "{count} avertissements" } } diff --git a/src/messages/it.json b/src/messages/it.json index 315fe85..fa6a167 100644 --- a/src/messages/it.json +++ b/src/messages/it.json @@ -361,5 +361,9 @@ "Error": "Errore", "Deleting": "Eliminazione", "Reconfiguring": "Riconfigurazione" + }, + "warnings": { + "oneTooltip": "1 avviso", + "manyTooltip": "{count} avvisi" } } diff --git a/src/types/index.ts b/src/types/index.ts index 9cd7aeb..da8d135 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -103,6 +103,21 @@ export interface PiecedTenantStatus { litellmKeyAlias?: string; tenantNamespace?: string; enabledPackages?: string[]; + /** + * Non-fatal issues from downstream resources surfaced by the operator + * (e.g. an OpenClawInstance sub-condition reporting failure). The + * tenant is still usable — these are informational, rendered as a + * warning badge alongside the phase. + * + * `source` is "/" e.g. "OpenClawInstance/SkillPacksReady". + * `message` is shown in the tooltip when the user hovers the badge. + */ + warnings?: Array<{ + source: string; + reason?: string; + message?: string; + since?: string; + }>; conditions?: Array<{ type: string; status: string;