From f37d89338a81d20e81339a8818f242fd491cea43 Mon Sep 17 00:00:00 2001 From: Amp Date: Sat, 5 Sep 2026 16:08:04 +0000 Subject: [PATCH] fix(frontend): polish dashboard and inspector UI Amp-Thread-ID: https://ampcode.com/threads/T-01a06dc8-52cb-7499-9951-7772265b1f86 --- .../apps/inspector/src/routes/_context.tsx | 11 ++- frontend/apps/inspector/vite.config.ts | 2 + frontend/packages/components/public/theme.css | 2 + frontend/public/images/brand/actors-mark.svg | 21 +++++ frontend/public/images/brand/agentos-mark.svg | 13 +++ frontend/src/app/actors-grid.tsx | 23 +++--- .../src/app/billing/billing-plan-badge.tsx | 10 ++- .../src/app/billing/billing-usage-gauge.tsx | 13 ++- frontend/src/app/billing/compute-card.tsx | 8 +- frontend/src/app/context-switcher.tsx | 34 ++++++-- frontend/src/app/help-button.tsx | 2 +- frontend/src/app/images-table.tsx | 4 +- frontend/src/app/new-org-page.tsx | 2 +- frontend/src/app/org-landing.tsx | 10 +-- .../app/settings-pages/whats-new-panel.tsx | 2 +- .../src/components/actors/actor-database.tsx | 5 +- .../actors/actor-details-iframe.tsx | 4 +- .../actors/actor-details-legacy.tsx | 4 +- .../actors/actor-details-skeleton.tsx | 4 +- .../actors/actor-status-label.stories.tsx | 20 +++++ .../components/actors/actor-status-label.tsx | 30 ++++--- .../database/database-table.stories.tsx | 79 +++++++++++++++++++ .../actors/database/database-table.tsx | 15 ++-- .../ns.$namespace/deployments.tsx | 2 +- 24 files changed, 262 insertions(+), 58 deletions(-) create mode 100644 frontend/public/images/brand/actors-mark.svg create mode 100644 frontend/public/images/brand/agentos-mark.svg create mode 100644 frontend/src/components/actors/actor-status-label.stories.tsx create mode 100644 frontend/src/components/actors/database/database-table.stories.tsx diff --git a/frontend/apps/inspector/src/routes/_context.tsx b/frontend/apps/inspector/src/routes/_context.tsx index 61e0668d75..d0d7250080 100644 --- a/frontend/apps/inspector/src/routes/_context.tsx +++ b/frontend/apps/inspector/src/routes/_context.tsx @@ -4,11 +4,13 @@ import { Outlet, redirect, useNavigate, + useRouteContext, } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import z from "zod"; import { getInspectorClientEndpoint } from "@/app/data-providers/inspector-data-provider"; import { useDialog } from "@/components"; +import { DataProviderContext } from "@/components/actors"; import { ModalRenderer } from "@/components/modal-renderer"; const searchSchema = z @@ -66,12 +68,17 @@ export const Route = createFileRoute("/_context")({ }); function RouteComponent() { + const dataProvider = useRouteContext({ + from: "/_context", + select: (context) => context.dataProvider, + }); + return ( - <> + - + ); } diff --git a/frontend/apps/inspector/vite.config.ts b/frontend/apps/inspector/vite.config.ts index 03dc5ca772..df0d0c7202 100644 --- a/frontend/apps/inspector/vite.config.ts +++ b/frontend/apps/inspector/vite.config.ts @@ -25,6 +25,8 @@ export default defineConfig({ __APP_BUILD_ID__: JSON.stringify( `${new Date().toISOString()}@${crypto.randomUUID()}`, ), + __MCP_APP__: JSON.stringify(false), + "import.meta.env.VITE_FEATURE_FLAGS": JSON.stringify(""), }, optimizeDeps: { include: ["@fortawesome/*", "@rivet-gg/icons", "@rivet-gg/cloud"], diff --git a/frontend/packages/components/public/theme.css b/frontend/packages/components/public/theme.css index 2056105ff4..e4a48cdd82 100644 --- a/frontend/packages/components/public/theme.css +++ b/frontend/packages/components/public/theme.css @@ -19,10 +19,12 @@ --accent-foreground: 240 10% 4%; --destructive: 0 84.2% 60.2%; --destructive-foreground: 0 0% 100%; + --warning: 47.9 95.8% 53.1%; --border: 240 6% 90%; --input: 240 6% 90%; --ring: 18.5 100% 50%; --radius: 0.5rem; + --background-main: 0 0% 100%; } :root[class~="dark"] { /* Cool zinc neutrals — mirrors Foundry's #09090b / #0c0c0e / #0f0f11. */ diff --git a/frontend/public/images/brand/actors-mark.svg b/frontend/public/images/brand/actors-mark.svg new file mode 100644 index 0000000000..31ac2ba5c6 --- /dev/null +++ b/frontend/public/images/brand/actors-mark.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/public/images/brand/agentos-mark.svg b/frontend/public/images/brand/agentos-mark.svg new file mode 100644 index 0000000000..d1621e661a --- /dev/null +++ b/frontend/public/images/brand/agentos-mark.svg @@ -0,0 +1,13 @@ + + + + + + + +OS + + + + + diff --git a/frontend/src/app/actors-grid.tsx b/frontend/src/app/actors-grid.tsx index 4f59f65964..8822cb7e38 100644 --- a/frontend/src/app/actors-grid.tsx +++ b/frontend/src/app/actors-grid.tsx @@ -22,15 +22,14 @@ import { useCloudNamespaceDataProvider, useDataProvider, } from "@/components/actors"; -import { Badge } from "@/components/ui/badge"; +import { NoProvidersAlert } from "@/components/actors/no-providers-alert"; +import { ActorIcon } from "@/components/lazy-icon"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; -import { NoProvidersAlert } from "@/components/actors/no-providers-alert"; -import { ActorIcon } from "@/components/lazy-icon"; import { VisibilitySensor } from "@/components/visibility-sensor"; import { features } from "@/lib/features"; import { getRivetRunUrl } from "../lib/env"; @@ -104,16 +103,20 @@ function CreateMenu({ openModal("create-actor")}> + Actor openModal("create-agent-os")}> + agentOS - - Beta - @@ -293,7 +296,7 @@ export function ActorsGrid({ namespaceLabel }: { namespaceLabel?: string }) { Deploy code that registers an actor to see it here. - + ) ) : ( diff --git a/frontend/src/app/billing/billing-plan-badge.tsx b/frontend/src/app/billing/billing-plan-badge.tsx index 1f5d5bd3f7..63d8b2591d 100644 --- a/frontend/src/app/billing/billing-plan-badge.tsx +++ b/frontend/src/app/billing/billing-plan-badge.tsx @@ -42,15 +42,17 @@ export function LazyBillingPlanBadge({ project, organization, className, + eager = false, }: { project: string; organization: string; className?: string; + eager?: boolean; }) { const [isVisible, setIsVisible] = useState(false); const dataProvider = useCloudDataProvider(); const { data, isLoading } = useQuery({ - enabled: isVisible, + enabled: eager || isVisible, ...dataProvider.billingDetailsQueryOptions({ project, organization }), }); @@ -58,7 +60,7 @@ export function LazyBillingPlanBadge({ return ( <> - {isLoading || !isVisible ? ( + {isLoading || (!eager && !isVisible) ? ( ) : ( )} - setIsVisible(true)} /> + {eager ? null : ( + setIsVisible(true)} /> + )} ); } diff --git a/frontend/src/app/billing/billing-usage-gauge.tsx b/frontend/src/app/billing/billing-usage-gauge.tsx index 97158da40d..75b7997dd5 100644 --- a/frontend/src/app/billing/billing-usage-gauge.tsx +++ b/frontend/src/app/billing/billing-usage-gauge.tsx @@ -11,6 +11,11 @@ const progressColors = { border: "border-emerald-900/30", }, medium: { + stroke: "stroke-warning", + background: "bg-warning/10", + border: "border-warning/60", + }, + warning: { stroke: "stroke-amber-500", background: "bg-amber-950/50", border: "border-amber-900/30", @@ -45,7 +50,13 @@ export function BillingUsageGauge() { const strokeDashoffset = circumference - (progress / 100) * circumference; const progressVariant = - progress < 50 ? "low" : progress < 80 ? "medium" : "high"; + progress < 50 + ? "low" + : progress < 80 + ? "medium" + : progress < 100 + ? "warning" + : "high"; return (
-
+
@@ -182,10 +182,10 @@ export function ComputeUsageProjectBillingPointer() {
-
+
diff --git a/frontend/src/app/context-switcher.tsx b/frontend/src/app/context-switcher.tsx index ed45c6108d..61ac53c214 100644 --- a/frontend/src/app/context-switcher.tsx +++ b/frontend/src/app/context-switcher.tsx @@ -428,7 +428,14 @@ function ProjectSegmentPopover({ project: currentProject, }), ); - const label = projectData?.displayName ?? currentProject; + const { data: projects } = useInfiniteQuery( + useCloudDataProvider().currentOrgProjectsQueryOptions(), + ); + const label = + projectData?.displayName ?? + projects?.find((project) => project.name === currentProject) + ?.displayName ?? + currentProject; return ( @@ -451,19 +458,20 @@ function ProjectSegmentPopover({ }), }); }} - className="flex h-auto items-center gap-2 px-2 py-1 text-sm font-medium text-foreground rounded-lg hover:bg-foreground/[0.06]" + className="flex h-auto items-center gap-2 px-1 py-1 text-sm font-medium text-foreground rounded-lg hover:bg-foreground/[0.06]" > {label} @@ -501,7 +509,16 @@ function NamespaceSegmentPopover({ namespace: currentNamespace, }), ); - const label = nsData?.displayName ?? currentNamespace; + const { data: namespaces } = useInfiniteQuery( + useCloudDataProvider().currentOrgProjectNamespacesQueryOptions({ + project: currentProject, + }), + ); + const label = + nsData?.displayName ?? + namespaces?.find((namespace) => namespace.name === currentNamespace) + ?.displayName ?? + currentNamespace; return ( @@ -568,7 +585,14 @@ function EngineNamespaceSegmentPopover({ const { data: nsData } = useQuery( useEngineCompatDataProvider().namespaceQueryOptions(currentNamespace), ); - const label = nsData?.displayName ?? currentNamespace; + const { data: namespaces } = useInfiniteQuery( + useEngineCompatDataProvider().namespacesQueryOptions(), + ); + const label = + nsData?.displayName ?? + namespaces?.find((namespace) => namespace.name === currentNamespace) + ?.displayName ?? + currentNamespace; return ( diff --git a/frontend/src/app/help-button.tsx b/frontend/src/app/help-button.tsx index bc1b0c5353..99cfc00d83 100644 --- a/frontend/src/app/help-button.tsx +++ b/frontend/src/app/help-button.tsx @@ -155,7 +155,7 @@ export function HelpButton({ source = "web" }: { source?: string }) { - + {(Object.keys(IMPACT_LABELS) as Impact[]).map( (k) => ( diff --git a/frontend/src/app/images-table.tsx b/frontend/src/app/images-table.tsx index 5e2eef4514..3051022935 100644 --- a/frontend/src/app/images-table.tsx +++ b/frontend/src/app/images-table.tsx @@ -62,7 +62,9 @@ export function ImagesTable({ Tag - Deployed To + + Deployed To + Date diff --git a/frontend/src/app/new-org-page.tsx b/frontend/src/app/new-org-page.tsx index cf6ced42cd..ff35357983 100644 --- a/frontend/src/app/new-org-page.tsx +++ b/frontend/src/app/new-org-page.tsx @@ -139,7 +139,7 @@ function GradientAvatar({ }) { return (
-
+
(
- + diff --git a/frontend/src/components/actors/actor-details-iframe.tsx b/frontend/src/components/actors/actor-details-iframe.tsx index 3f497f2b31..17991ae828 100644 --- a/frontend/src/components/actors/actor-details-iframe.tsx +++ b/frontend/src/components/actors/actor-details-iframe.tsx @@ -623,7 +623,7 @@ function ActorDetailsIframePath({ ref={tabListRef} className="flex-1 min-w-0 overflow-hidden h-full" > - + {displayedTabs.map((t) => ( - + {displayedTabs.map((t) => (
- + {PLACEHOLDER_TABS.map((t) => ( ( +
+
+

Future retry

+ +
+
+

Stale retry timestamp

+ +
+
+); diff --git a/frontend/src/components/actors/actor-status-label.tsx b/frontend/src/components/actors/actor-status-label.tsx index 5d08591928..f9f46e6140 100644 --- a/frontend/src/components/actors/actor-status-label.tsx +++ b/frontend/src/components/actors/actor-status-label.tsx @@ -1,8 +1,8 @@ import { useQuery } from "@tanstack/react-query"; import { formatISO } from "date-fns"; import { isObject } from "lodash"; -import { formatValue } from "@/lib/format-value"; import { match, P } from "ts-pattern"; +import { formatValue } from "@/lib/format-value"; import type { RivetActorError } from "@/queries/types"; import { CodePreview } from "../code-preview/code-preview"; import { cn } from "../lib/utils"; @@ -67,15 +67,7 @@ export function QueriedActorStatusAdditionalInfo({ ); if (rescheduleTs) { - return ( - - Will try to start again{" "} - - ( - {formatISO(rescheduleTs)}){" "} - - - ); + return ; } if (error) { @@ -85,6 +77,24 @@ export function QueriedActorStatusAdditionalInfo({ return null; } +export function ActorRescheduleStatus({ + rescheduleTs, +}: { + rescheduleTs: Date | number; +}) { + const rescheduleAt = new Date(rescheduleTs); + const isOverdue = rescheduleAt.getTime() <= Date.now(); + + return ( + + {isOverdue ? "Retry was scheduled " : "Will try to start again "} + + ({formatISO(rescheduleAt)}){" "} + + + ); +} + export function ActorError({ error }: { error: object | string }) { return match(error) .with(P.string, (errMsg) => diff --git a/frontend/src/components/actors/database/database-table.stories.tsx b/frontend/src/components/actors/database/database-table.stories.tsx new file mode 100644 index 0000000000..47b9ba2602 --- /dev/null +++ b/frontend/src/components/actors/database/database-table.stories.tsx @@ -0,0 +1,79 @@ +import type { Story } from "@ladle/react"; +import { faTable, faTableCells, Icon } from "@rivet-gg/icons"; +import "../../../../.ladle/ladle.css"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "../../ui/select"; +import type { DatabaseColumn } from "../actor-inspector-context"; +import { DatabaseTable } from "./database-table"; + +const columns: DatabaseColumn[] = [ + { + cid: 0, + name: "id", + type: "INTEGER", + notnull: true, + dflt_value: null, + pk: true, + }, + { + cid: 1, + name: "has_initialized", + type: "INTEGER", + notnull: true, + dflt_value: null, + pk: false, + }, + { + cid: 2, + name: "input", + type: "BLOB", + notnull: false, + dflt_value: null, + pk: false, + }, +]; + +export const SchemaTypesAndBlob: Story = () => ( +
+
+
+
+ +
+
+ + main._rivet_actor + + (3 columns, 1 row) + +
+
+ +
+
+); diff --git a/frontend/src/components/actors/database/database-table.tsx b/frontend/src/components/actors/database/database-table.tsx index b65d4f9e64..50dc224381 100644 --- a/frontend/src/components/actors/database/database-table.tsx +++ b/frontend/src/components/actors/database/database-table.tsx @@ -134,7 +134,7 @@ export function DatabaseTable({ width: table.getTotalSize(), }} > - + {table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => { @@ -168,6 +168,7 @@ export function DatabaseTable({ icon={ faArrowUpWideShort } + className="size-3 shrink-0 text-muted-foreground" /> ) : header.column.getIsSorted() === "desc" ? ( @@ -175,10 +176,12 @@ export function DatabaseTable({ icon={ faArrowDownWideShort } + className="size-3 shrink-0 text-muted-foreground" /> ) : ( ) ) : null} @@ -334,9 +337,9 @@ function createColumns( ...columns.map((col) => ch.accessor(col.name, { header: () => ( - - {col.name}{" "} - + + {col.name} + {col.type} @@ -356,14 +359,14 @@ export function renderDatabaseCellValue( ): ReactNode { if (isBlobColumn(column, value)) { return ( - + BINARY ); } if (value === null) { return ( - + NULL ); diff --git a/frontend/src/routes/_context/orgs.$organization/projects.$project/ns.$namespace/deployments.tsx b/frontend/src/routes/_context/orgs.$organization/projects.$project/ns.$namespace/deployments.tsx index 9af44dfcbb..d2f3b49483 100644 --- a/frontend/src/routes/_context/orgs.$organization/projects.$project/ns.$namespace/deployments.tsx +++ b/frontend/src/routes/_context/orgs.$organization/projects.$project/ns.$namespace/deployments.tsx @@ -143,7 +143,7 @@ function Deployments() { }); return ( -
+