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
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:
getToolSuccessPercentguards division by zero; the success/failure ratio bar widths come from it.buildActivityItemsconverts metrics into narrativeActivityItems with tone/icon chosen by state (e.g. failed workflow runs > 0 →errortone + "Review" badge).MetricChartsrenders 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 —
- Error →
AgentsErrorPanel(same pattern as the dashboard's). - Empty →
EmptyAgentsStatewith a "New Agent" CTA to/create-agent. - Data → responsive grid (1 col → 2 cols at
xl) ofAgentCards.
/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:
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-storeopt 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.
Related pages
- Components — everything these pages compose
- Data Fetching — the caching decisions explained
- Design System — the classes used throughout