Skip to content
3 min read · 606 words

Pages

Four user-facing routes plus one API route. Reads are server components that fetch fresh data per request; the only interactive page is the create-agent form.

/ — redirect

tsx
import { redirect } from "next/navigation";
export default function HomePage() {
  redirect("/dashboard");
}

/dashboard — the operations dashboard

app/dashboard/page.tsx · server component · export const dynamic = "force-dynamic"

Fetch: AGENTOPS_DASHBOARD_API_URL (default http://localhost:8001/agents/1/dashboard) with cache: "no-store". The typed AgentDashboardResponse mirrors the backend dashboard schema field-for-field.

Render tree:

Sidebar (activeItem="Dashboard") + Topbar (agentName)
└── DashboardContent
    ├── StatCard "Total Tasks" · StatCard "Workflow Runs"
    ├── StatCard "Task Overview" (4-column breakdown, spans 2)
    ├── StatCard "Tool Calls" (success/failure split + ratio bar)
    ├── PerformanceCard (latency · cost · tokens · quality, spans 3)
    ├── MetricCharts (bars, latency curve, cost banner)
    └── RecentActivity (4 items derived from the metrics)

Notable logic:

  • getToolSuccessPercent guards division by zero; the success/failure ratio bar widths come from it.
  • buildActivityItems converts metrics into narrative ActivityItems with tone/icon chosen by state (e.g. failed workflow runs > 0 → error tone + "Review" badge).
  • MetricCharts renders hardcoded sample bars ([30, 45, 60, 40, 80, 90]) and a static SVG latency curve; only the cost banner (estimated_total_cost, estimated_total_tokens) is live data. Documented in Known Issues.

Failure behavior: getDashboardData throws distinct errors for unreachable backend vs. non-OK status; the page catches and renders DashboardErrorPanel (message + Retry link) instead of crashing. A thrown render error would hit dashboard/error.tsx; dashboard/loading.tsx covers the fetch window.

/agents — agent inventory

app/agents/page.tsx · server component · force-dynamic

Fetch: AGENTOPS_AGENTS_API_URL (default http://localhost:8001/agents/), no-store, typed as AgentResponse[].

Render: three-way branch —

  1. ErrorAgentsErrorPanel (same pattern as the dashboard's).
  2. EmptyEmptyAgentsState with a "New Agent" CTA to /create-agent.
  3. Data → responsive grid (1 col → 2 cols at xl) of AgentCards.

/create-agent — the only interactive page

app/create-agent/page.tsx renders the page chrome and mounts CreateAgentForm — a "use client" component holding the entire form state. Submission flow:

Rendering diagram…

Details of the payload and error normalization: API Proxy.

Layout & chrome

app/layout.tsx is the root layout: sets metadata (title: "AgentOps AI"), loads Inter / JetBrains Mono / Material Symbols from Google Fonts, applies bg-background text-on-background, and renders children. Every page composes its own Sidebar + Topbar and offsets content with md:ml-[260px] … pt-20 to clear the fixed chrome.

Edge cases worth knowing

  • Both list pages fetch directly from the backend, so the browser never needs CORS — but it also means SSR fails (gracefully) when the backend is down, rather than rendering stale data.
  • force-dynamic + no-store opt out of all Next.js caching — correct for an ops dashboard, costly if traffic ever grows (each page view = backend round trip).
  • The dashboard's agent is fixed by URL, not by user selection — multi-agent dashboard selection is a roadmap item.