Operator dashboard

AI Ops Dashboard

Where it all comes together

โš™ Tabs

Show or hide any dashboard tab.

How: Toggle tabs on/off and pick the default tab. Hidden tabs stay installed and can be turned back on anytime โ€” nothing is deleted.

Change the dashboard's color theme.

How: Pick any of the 8 themes โ€” your choice is saved in this browser only (it doesn't change the shared default).

Data updated 10/19/2018, 6:46 PM PDT
Rendered 6/19/2026, 11:51 PM PDT ยท auto-refresh 120s
โ† AI Operations Dashboard docsยทhelp/deployment.md

Deployment

AI Operations Dashboard runs in three modes. Pick one with the AI_OPS_DASHBOARD_MODE env var. The dashboard config and panel code are identical across all three โ€” only runtime characteristics change.

| Mode | Set with | What it does | Host on | |---|---|---|---| | Vercel / SSR | AI_OPS_DASHBOARD_MODE=vercel (default) | Server-renders every request. Loaders run live. | Vercel, any Next.js host | | Static | AI_OPS_DASHBOARD_MODE=static | Exports plain HTML/CSS/JS. Loaders run once at build. | S3, GitHub Pages, Netlify, file://, nginx | | Node | AI_OPS_DASHBOARD_MODE=node | Long-running Node server. Optional SSE refresh daemon. | Docker, EC2, Fly, Render |

The mode flag is read by next.config.mjs:

output: process.env.AI_OPS_DASHBOARD_MODE === "static" ? "export" : undefined,
trailingSlash: process.env.AI_OPS_DASHBOARD_MODE === "static",

Deploy to Vercel

The fastest path. SSR is the default.

  1. Push your repo to GitHub.
  2. Go to vercel.com/new and import the repo.
  3. Framework preset: Next.js (auto-detected).
  4. Click Deploy.

That's it. The build command (npm run build) runs npm run data:build first, which freezes git history and aggregates AI usage. See the shallow-clone gotcha below โ€” it matters.

Optional env vars:

AI_OPS_DASHBOARD_MODE=vercel
AI_OPS_DASHBOARD_FREEZE_SINCE="180 days ago"
AI_OPS_DASHBOARD_FREEZE_LIMIT=500

Static export

Works anywhere you can serve static files.

AI_OPS_DASHBOARD_MODE=static npm run build

The output lands in out/. Loaders run at build time, so the dashboard reflects a snapshot of your data files when the build ran. Schedule a daily build to keep it fresh.

S3 + CloudFront

AI_OPS_DASHBOARD_MODE=static npm run build
aws s3 sync out/ s3://my-dashboard --delete
aws cloudfront create-invalidation --distribution-id <ID> --paths "/*"

Set the bucket policy to allow s3:GetObject from CloudFront and configure CloudFront with out/index.html as the default root object.

GitHub Pages

# .github/workflows/deploy.yml
on:
  push: { branches: [main] }
  schedule: [{ cron: "0 12 * * *" }]   # daily refresh

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }       # full history for freeze-commits
      - uses: actions/setup-node@v4
        with: { node-version: 20 }
      - run: npm ci
      - run: AI_OPS_DASHBOARD_MODE=static npm run build
      - uses: actions/upload-pages-artifact@v3
        with: { path: out }
  deploy:
    needs: build
    permissions: { pages: write, id-token: write }
    runs-on: ubuntu-latest
    steps:
      - uses: actions/deploy-pages@v4

Netlify

Build settings:

  • Build command: AI_OPS_DASHBOARD_MODE=static npm run build
  • Publish directory: out

Local file://

AI_OPS_DASHBOARD_MODE=static npm run build
open out/index.html

trailingSlash: true is auto-set in static mode so paths resolve correctly when opened directly from the filesystem.

Internal nginx

server {
  listen 80;
  server_name dashboard.internal;
  root /var/www/ai-ops-dashboard/out;
  index index.html;
  location / { try_files $uri $uri/ $uri.html =404; }
}

Node mode

For SSE / long-running connections / custom middleware.

AI_OPS_DASHBOARD_MODE=node npm run build
AI_OPS_DASHBOARD_MODE=node npm start

Use this when you want to wire a real-time push mechanism into a panel (Server-Sent Events, WebSockets) instead of the default <meta http-equiv="refresh"> polling.

Docker

Sample Dockerfile:

FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
ENV AI_OPS_DASHBOARD_MODE=node
RUN npm run build

FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY --from=builder /app/data ./data
COPY --from=builder /app/docs ./docs
COPY --from=builder /app/help ./help
COPY --from=builder /app/plans ./plans
COPY --from=builder /app/knowledge ./knowledge
RUN npm ci --omit=dev
EXPOSE 3000
CMD ["npm", "start"]

Compose:

services:
  dashboard:
    build: .
    ports: ["3000:3000"]
    environment:
      AI_OPS_DASHBOARD_MODE: node
    volumes:
      - ./data:/app/data:ro
      - ./docs:/app/docs:ro
      - ./help:/app/help:ro
      - ./plans:/app/plans:ro
      - ./knowledge:/app/knowledge:ro
    restart: unless-stopped

Mount data folders read-only so the running container reflects whatever lives on the host.

The shallow-clone gotcha

Vercel and most CI hosts clone with fetch-depth: 1 (just the latest commit). Running git log on a shallow clone returns one entry, which would silently truncate your Activity panel.

The fix: scripts/freeze-commits.mjs captures git log into data/recent-commits.json, which gets committed to the repo. The Activity panel reads the JSON snapshot first and only falls back to live git log in dev.

The freeze script is wired into npm run build via data:build. It also includes a safety check: it refuses to overwrite a richer existing snapshot with a smaller one, so a shallow clone can't silently truncate your committed history.

npm run data:freeze-commits   # manual refresh

Pre-push hook

To make sure pushed commits always include an up-to-date freeze, install a pre-push hook. Use simple-git-hooks (already in the recommended dev deps):

// package.json
{
  "simple-git-hooks": {
    "pre-push": "node scripts/freeze-commits.mjs && git add data/recent-commits.json && git diff --cached --quiet || git commit -m '[freeze] data/recent-commits.json'"
  }
}

Then run npx simple-git-hooks once on each checkout. Bypass for emergencies with SKIP_SIMPLE_GIT_HOOKS=1 git push.

Authentication

The dashboard is read-only and contains no auth layer. Put it behind something.

Vercel preview protection (paid plans)

Toggle on in the Vercel project settings. Free for Pro/Enterprise. One-click. Best option if you're already on Vercel.

Cloudflare Access

Put the dashboard behind Cloudflare Tunnel, then add a Cloudflare Access policy (email allowlist, Google SSO, GitHub SSO โ€” all free up to 50 users). Zero changes to AI Operations Dashboard.

Basic auth via Next.js middleware

For static or node mode hosted on your own infra:

// middleware.ts
import { NextResponse, type NextRequest } from "next/server"

export function middleware(req: NextRequest) {
  const auth = req.headers.get("authorization")
  if (!auth) return new NextResponse("Auth required", { status: 401, headers: { "WWW-Authenticate": "Basic" } })
  const [scheme, b64] = auth.split(" ")
  if (scheme !== "Basic") return new NextResponse("Forbidden", { status: 403 })
  const [user, pass] = atob(b64).split(":")
  if (user !== process.env.DASH_USER || pass !== process.env.DASH_PASS) {
    return new NextResponse("Forbidden", { status: 403 })
  }
  return NextResponse.next()
}

export const config = { matcher: ["/((?!_next|favicon).*)"] }

Set DASH_USER and DASH_PASS as env vars. Middleware doesn't run on static-mode exports โ€” for that path, use Cloudflare Access or your host's auth layer.

Next

  • upgrading.md โ€” Pull new versions without breaking your config.
  • faq.md โ€” Common deployment questions.

Deployment