NexusLinkNexusLink Docs

dashboard — UI for agent-networks

This module documents code that lives in the dashboard repo (under src/modules/agent-network/ and src/app/(dashboard)/agent-network/), not in this repo. It is co-located here so backend readers see the full picture.

Risk level: Medium. The new surface is isolated under src/modules/agent-network/ and src/app/(dashboard)/agent-network/, but it also reshapes the sidebar, splits /peers, renames reverse-proxy/clustersself-hosted-proxies, and overlays the Control Center graph. Regressions here would be cross-cutting. Backward-compat impact: Additive on the API side. Breaking on URL/navigation: /peers redirects to /peers/devices (src/app/(dashboard)/peers/page.tsx:7-15), /reverse-proxy/clusters was renamed to /reverse-proxy/self-hosted-proxies, the sidebar lost Access Control / Networks / Reverse Proxy / DNS / standalone Guardrails / Consumption / Activity (Navigation.tsx:165-171 — routes still resolve via URL), and the standalone /agent-network/{access-log,consumption,global-controls} routes are gone in favor of /agent-network/observability.

Module boundary

The dashboard is the only place an operator interacts with agent-networks: provider catalog, configured providers, policies, guardrails, account-level budget rules, account settings (collection / redaction toggles), per-request access log, and consumption rollups all render, paginate, and edit here. Data flows in via SWR (useFetchApi) keyed by REST URL. One big context provider (src/modules/agent-network/AIProvidersProvider.tsx) aggregates five resources (providers, policies, guardrails, budget rules, settings) plus the proxy access-log stream filtered to agent_network=true, and exposes add* / update* / toggle* / delete* mutators that call through useApiCall and re-mutate() SWR. Pages mount the provider once at the top and compose presentational tables and modals beneath. The control-center page additionally fetches /agent-network/{providers,policies} directly (control-center/page.tsx:123-130) to overlay graph nodes.

What the UI delivers

Surface added

New pages

Route Purpose Backing module(s)
/agent-network Redirect to /agent-network/providers page.tsx:7-15
/agent-network/providers List + connect providers; header surfaces per-account base URL providers/page.tsx + AgentProvidersTable + AIProviderModal
/agent-network/policies Group → Provider authorization with per-policy Limits + Guardrail attach policies/page.tsx + AgentPoliciesTable + AgentPolicyModal
/agent-network/guardrails Reusable guardrail sets (model allowlist + prompt capture) guardrails/page.tsx + AgentGuardrailsTable + AgentGuardrailModal
/agent-network/observability Tabs: Access Logs / Budget Dashboard / Budget Settings / Log Settings observability/page.tsx
/peers/devices, /peers/agents Split of /peers, shared via PeersListView keyed by kind peers/{devices,agents}/page.tsx
/reverse-proxy/self-hosted-proxies Renamed from clusters self-hosted-proxies/page.tsx

Removed in favor of /agent-network/observability: /agent-network/access-log, /agent-network/consumption, /agent-network/global-controls.

New modules under src/modules/agent-network

File Role
AIProvidersProvider.tsx (~1158 LOC) Aggregates every agent-network resource via SWR; normalises snake↔camel; exposes mutators; holds wizard-open state
AIProviderModal.tsx (~1268 LOC) Connect / edit provider wizard with per-vendor copy (Bifrost, Portkey, LiteLLM, Cloudflare, Vercel, OpenRouter, custom)
AIProviderLogo + useProviderCatalog Catalog-driven brand swatch + SWR hook over /agent-network/catalog/providers
AgentPoliciesTable + AgentPolicyModal + AgentPolicyGuardrailsTab + AgentPolicyLimitsTab Policies; modal has 3 tabs (Rule, Limits, Guardrails)
AgentGuardrailsTable + AgentGuardrailModal + AgentGuardrailBrowseModal + AgentGuardrailChecksCell Guardrails CRUD + attach-from-policy
AgentBudgetRulesTable + AgentBudgetRuleModal Account-level budget rules; modal reuses AgentPolicyLimitsTab verbatim
AgentAccountControlsCard Three account-wide toggles (Log Collection / Prompt Collection / Redact PII)
AgentAccessLogTable + AgentAccessLogExpandedRow Access log on /events/proxy?agent_network=true
AgentConsumptionPanel + AgentConsumptionTable Token + cost panel: charts + counter table
table/AgentProvidersTable + AgentProviderActionCell Providers table + per-row actions
data/mockData.ts Domain types and a few residual MOCK_* constants (see scrutinize)

Touched non-agent-network areas

Architecture & flow

Page → Provider → Table/Modal hierarchy

graph TD
  Nav[Navigation.tsx]
  Nav --> ProvidersPage[/agent-network/providers/]
  Nav --> PoliciesPage[/agent-network/policies/]
  Nav --> GuardrailsPage[/agent-network/guardrails/]
  Nav --> ObsPage[/agent-network/observability/]

  ProvidersPage --> AIPP1[AIProvidersProvider]
  PoliciesPage --> AIPP2[AIProvidersProvider]
  GuardrailsPage --> AIPP3[AIProvidersProvider]
  ObsPage --> AIPP4[AIProvidersProvider]
  ObsPage -.wraps.-> GroupsProvider
  ObsPage -.wraps.-> PeersProvider

  AIPP1 --> ProvTable[AgentProvidersTable]
  ProvTable --> ProvModal[AIProviderModal]
  AIPP2 --> PolTable[AgentPoliciesTable]
  PolTable --> PolModal[AgentPolicyModal]
  PolModal --> PolGuardTab[AgentPolicyGuardrailsTab]
  PolModal --> PolLimitsTab[AgentPolicyLimitsTab]
  PolGuardTab --> GuardBrowse[AgentGuardrailBrowseModal]
  PolGuardTab --> GuardModal[AgentGuardrailModal]
  AIPP3 --> GuardTable[AgentGuardrailsTable]
  GuardTable --> GuardModal
  AIPP4 --> Tabs[Tabs]
  Tabs --> AccessLog[AgentAccessLogTable]
  Tabs --> Consumption[AgentConsumptionPanel]
  Tabs --> BudgetRules[AgentBudgetRulesTable]
  Tabs --> AccountCtl[AgentAccountControlsCard]
  BudgetRules --> BudgetModal[AgentBudgetRuleModal]
  BudgetModal -.reuses.-> PolLimitsTab

AI Observability tab page

graph LR
  Page[AIObservabilityPage] --> RA[RestrictedAccess<br/>permission.services.read]
  RA --> GP[GroupsProvider]
  GP --> PP[PeersProvider]
  PP --> AIP[AIProvidersProvider]
  AIP --> Tabs[Tabs / TabsList]
  Tabs --> T1[Access Logs<br/>AgentAccessLogTable]
  Tabs --> T2[Budget Dashboard<br/>AgentConsumptionPanel]
  Tabs --> T3[Budget Settings<br/>AgentBudgetRulesTable]
  Tabs --> T4[Log Settings<br/>AgentAccountControlsCard]
  T1 -.GET.-> EP[/events/proxy?agent_network=true/]
  T2 -.GET poll 5s.-> CONS[/agent-network/consumption/]
  T3 -.GET/PUT.-> BR[/agent-network/budget-rules/]
  T4 -.GET/PUT.-> ST[/agent-network/settings/]

Data fetch path

graph TD
  Page[Page component] --> Prov[AIProvidersProvider]
  Prov -->|useFetchApi| SWR[(SWR cache<br/>key = URL)]
  SWR -.GET.-> P[/agent-network/providers/]
  SWR -.GET.-> POL[/agent-network/policies/]
  SWR -.GET.-> G[/agent-network/guardrails/]
  SWR -.GET.-> BR[/agent-network/budget-rules/]
  SWR -.GET ignoreError.-> ST[/agent-network/settings/]
  SWR -.GET.-> CAT[/agent-network/catalog/providers/]
  SWR -.GET pageSize=100.-> EVT[/events/proxy agent_network=true/]
  Prov --> Mut[useApiCall.post/put/del]
  Mut -.on success.-> MutateSWR[SWR mutate keys]
  Prov --> Children[Tables / Modals via useAIProviders]

Every list view reaches management through SWR over /api/agent-network/*. The provider context maps snake-case payloads to camelCase domain types (fromAPI, policyFromAPI, guardrailFromAPI, budgetRuleFromAPI, settingsFromAPI, accessLogFromAPI — AIProvidersProvider.tsx:138-562) and back via matching *ToRequest adaptors. The access log piggy-backs on /events/proxy with agent_network=true&page_size=100 (line 707-709) and decodes LLM-specific fields from per-event metadata. Group IDs on events are resolved to current names through the surrounding GroupsProvider catalog (lines 515-521, 717-731) — no extra round trip. Mutators run *ToRequest, await useApiCall.post/put/del, call SWR mutate(), then notify. Errors caught and surfaced via notify — no exceptions escape into render. The Connect Provider modal's open state lives in the provider itself (isWizardOpen at lines 732-735) so the providers-page empty-state CTA and the table's + button share one modal. Control-center re-fetches /agent-network/{providers,policies} directly on top of AIProvidersProvider — SWR de-dupes but the code path is harder to reason about.

Public contracts consumed

AIProviderId is a closed union in dashboard types (data/mockData.ts:8-21) but the converter tolerates anything the backend ships — unknown ids fall through to "custom" (AIProvidersProvider.tsx:497-506). Catalog values are pure read-through: anything declared in extra_headers renders in the modal automatically, copy keyed by header name (EXTRA_HEADER_UI in AIProviderModal.tsx:61-89), labeled-fallback for unknown ones.

Invariants

Things to scrutinize

Correctness

Security

Accessibility

Performance

Visual consistency

Test coverage

Known limitations / explicit non-goals

Cross-references