Skip to content

Admin UI

Next.js web portal for managing the entire GE platform. Rebuilt from scratch Feb 9 2026 (v2). The old admin-ui is preserved at admin-ui-old/ for reference.

Source: admin-ui/

Tech Stack

Component Version
Next.js 16
React 19
Tailwind CSS 4
Drizzle ORM PostgreSQL
ioredis Redis Streams
WebAuthn YubiKey authentication

Routes

Route Purpose
/ Dashboard — system overview, agent activity
/agents Agent list, status, provider config
/agents/[id] Agent detail + chat
/tasks Task management
/queue Redis stream queue viewer
/billing Billing overview
/billing/consumption LLM consumption tracking
/billing/budgets Budget management
/billing/invoices Invoice generation
/clients Client list
/clients/[id] Client detail
/projects Project management
/discussions Multi-agent discussions
/discussions/[id] Discussion thread
/audit Audit log
/stream Live Redis stream viewer
/api-management API key management
/infrastructure K8s cluster health
/knowledge Knowledge patterns + learnings
/inbox Human inbox (clarifications, approvals)
/halt HALT flag management
/login WebAuthn login

Key Services

Service File Purpose
TaskService lib/services/task-service.ts Unified task mutations: DB insert + Redis XADD to triggers.{agent}
agent-bootstrap lib/services/agent-bootstrap.ts Upserts agent data from AGENT-REGISTRY.json to DB
claude-chat lib/ge-integration/claude-chat.ts Chat with agents: multi-provider (Claude/OpenAI), persisted to DB
agent-registry lib/ge-integration/agent-registry.ts Read/write registry + config publish
k8s client lib/k8s/client.ts K8s API for cluster health page

Authentication

  • Method: WebAuthn with YubiKey
  • Flow: /api/auth/challenge → store challenge in httpOnly cookie → /api/auth/verify
  • Critical: Challenge MUST be in httpOnly cookie, NOT in response body (old code had body.challenge which was undefined)

API Tiers

Tier Prefix Auth Purpose
Public /api/auth/* None Login flow
Internal /api/internal/* Bearer token Agent-to-admin communication
Authenticated /api/* WebAuthn session Human UI operations

K8s Deployment

  • Namespace: ge-system
  • HostPath volumes: Code directory, ge-ops directory, kubeconfig
  • Note: kubectl + kubeconfig are mounted into the pod for the infrastructure page
  • Access: https://office.growing-europe.com (via Traefik)

Database

PostgreSQL with Drizzle ORM. Key tables:

  • agent_task — task state + Redis trigger tracking
  • agents — agent config (provider, model, status)
  • session_learnings — structured learnings from agent sessions
  • knowledge_patterns — cross-session patterns with confidence scores
  • work_package_deps — DAG dependencies with swimming lanes
  • discussions, discussion_messages — multi-agent discussion threads

Migrations: admin-ui/drizzle/migrations/

Critical Rules

  • Never trigger agent execution directly from admin-ui — use TaskService which goes through Redis
  • Never call Claude API from sync/data pipeline code — only from explicit chat endpoints
  • Always use onConflictDoUpdate for upserts (not select-then-insert)
  • All XADD calls must include MAXLEN (~100 for per-agent, ~1000 for system)
  • Never XADD to both triggers.{agent} AND ge:work:incoming for the same task