runcycles/cycles-dashboard
GitHub: runcycles/cycles-dashboard
为 Cycles AI agent 治理平台打造的多租户运营管理面板,提供预算管控、操作审计、事件响应和可观测性功能。
Stars: 0 | Forks: 2
[](https://github.com/runcycles/cycles-dashboard/actions)
[](LICENSE)
[](https://github.com/runcycles/cycles-protocol/blob/main/cycles-governance-admin-v0.1.25.yaml)
[](https://vuejs.org)
[](https://www.typescriptlang.org)
# Cycles Dashboard — AI agent 预算与操作执行可观测性平台
**为 Cycles AI agent 治理平台打造的运营优先管理面板 — 实时可视化租户预算、操作权限执行、预约状态及 webhook 投递情况。** 默认支持多租户,专为事件响应而非 CRUD 实体列表的运维工作流而设计。
与 [Cycles Admin API](https://github.com/runcycles/cycles-server-admin) 和 [Cycles Server](https://github.com/runcycles/cycles-server) 配合使用,提供对 agent 支出、风险及工具操作执行的端到端可观测性。与 [治理规范 v0.1.25.34](https://github.com/runcycles/cycles-protocol/blob/main/cycles-governance-admin-v0.1.25.yaml) 保持一致。
**文档:** [CHANGELOG](CHANGELOG.md)(下游发行说明) · [OPERATIONS](OPERATIONS.md)(生产环境操作手册) · [AUDIT](AUDIT.md)(工程开发纪事)。
` 过滤。
- Webhook fleet-health donut → Webhooks 视图,按 `status=ACTIVE|PAUSED|DISABLED` 或 `failing=1` 过滤(Failing 切片与状态正交 — 一个状态为 `PAUSED` 但 `consecutive_failures ≥ 1` 的 webhook 依然算作 Failing,从而使图表与 `failing=1` 过滤器相匹配)。从 v0.1.25.53 起,`status=…` 会被推送到服务器(`listWebhookSubscriptions` 的 `status` 参数),因此下钻计数与 Overview 的 counter-strip 图块能够保持一致。
- Delivery-outcome donut (WebhookDetailView) → 历史记录表上的本地状态过滤器,不触发路由跳转。
有关完整的六切片路线图以及各视图预期可视化的内容,请参阅 `AUDIT.md` → *v0.1.25.47 charting layer*。
## 轮询策略
每个页面通过 `usePolling` 组合式函数管理自身的轮询生命周期:
| 页面 | 间隔 | 行为 |
|------|----------|----------|
| Overview | 30秒 | 标签页隐藏时暂停,出错时 2 倍退避(最大 5 分钟) |
| Budgets | 60秒 | 同上 |
| Events | 15秒 | 同上 |
| Webhooks | 60秒 | 同上 |
| Tenants | 60秒 | 同上 |
| Audit | 仅手动 | 显式的“Run Query”按钮 |
## 构建
```
npm run build # Type-check + production build → dist/
npm run test # Run Vitest unit tests
npm run dev # Development server with HMR
npm run preview # Preview production build locally
```
## 端到端测试
针对线上 docker-compose stack 运行两层测试:
1. **HTTP probes** (`scripts/e2e-probes.sh`) — 通过 dashboard 的 nginx 进行 curl 请求,验证路由和响应结构。
2. **Playwright** (`tests/e2e/`) — 通过真实的 Chromium 驱动执行关键用户流程(登录、预约强制释放、排序访问器等)。
本地运行:
```
# 一次性操作:安装 Playwright 的 Chromium + OS deps
npm run test:e2e:install
# 启动完整 stack(admin + runtime + redis + dashboard 在 :8080)
ADMIN_API_KEY=admin-bootstrap-key docker compose -f docker-compose.yml up -d --wait
# 运行两个 layer:
bash scripts/e2e-probes.sh
npm run test:e2e
# 交互式 UI(选择测试,内联查看 trace):
npm run test:e2e:ui
# 销毁
docker compose -f docker-compose.yml down -v
```
这两层测试已集成到 `.github/workflows/e2e.yml` 中 — 每晚运行,并在 PR 修改了 nginx、Dockerfile、compose、API client、`tests/e2e/` 或 workflow/probe 文件时触发。
## Docker
多阶段构建:使用 Node 20 执行 `npm run build`,然后使用 nginx:alpine 进行服务托管。
```
# 构建
FROM node:20-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
# 服务
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY default.conf.template /etc/nginx/templates/default.conf.template
ENV ADMIN_UPSTREAM=http://cycles-admin:7979 \
RUNTIME_UPSTREAM=http://cycles-server:7878
```
nginx 配置负责处理 SPA 路由 (`try_files $uri /index.html`),并将 `/v1/*` 反向代理到 admin server,但 runtime-plane 的路由(`/v1/reservations/*`、`/v1/evidence/*`、`/v1/.well-known/cycles-jwks.json`)除外,这些会指向 runtime server。它以 `envsubst` 模板形式提供,因此 `ADMIN_UPSTREAM` / `RUNTIME_UPSTREAM` 上游服务可以在部署时重新定向,无需重新构建镜像 — 请参阅 [OPERATIONS.md](OPERATIONS.md#reverse-proxy-wiring)。
## 生产环境部署
### 架构
```
┌─────────────┐
Browser ──HTTPS──▶ │ TLS Proxy │──HTTP──▶ Dashboard (nginx:80)
│ (Caddy/ALB) │ │
└─────────────┘ /v1/ split-proxy
│ │
/v1/reservations, /v1/evidence, │ │ /v1/*
/v1/.well-known/cycles-jwks.json ▼ ▼ (everything else)
Runtime Server (:7878) Admin Server (:7979)
│ │
└────────► Redis (:6379) ◄┘
```
Dashboard 是由 nginx 提供服务的静态 SPA。API 调用通过同一个 nginx 反向代理至 **两个后端平面**:**治理/管理服务器** (`:7979`,默认 — 租户、预算、策略、webhooks、审计、introspect) 和 **runtime server** (`:7878` — 预约、evidence 以及签名者 JWKS)。两者必须可达;这种拆分通过 `ADMIN_UPSTREAM` / `RUNTIME_UPSTREAM` 进行配置。在生产环境中,前端通常会部署一个负责 TLS 终结的代理。
### docker-compose(生产环境)
这镜像了标准的 [`docker-compose.prod.yml`](docker-compose.prod.yml) —
请将该文件视为事实来源。Dashboard 镜像内置了一个 nginx
代理,它将 `/v1/*` 在 **governance plane** (cycles-admin) 和
**runtime plane** (cycles-server — 预约、evidence、JWKS) 之间进行拆分,
因此**两个**后端都必须存在且可达。
```
services:
caddy:
image: caddy:2-alpine
restart: unless-stopped
ports:
- "443:443"
- "80:80"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy-data:/data
depends_on:
dashboard:
condition: service_healthy
networks:
- cycles
dashboard:
image: ghcr.io/runcycles/cycles-dashboard:0.1.25.64
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1/"]
interval: 30s
timeout: 3s
retries: 3
start_period: 5s
# No exposed ports — only accessible through Caddy. nginx proxies
# /v1/* to both planes; override the upstreams only for split hosts.
environment:
ADMIN_UPSTREAM: ${ADMIN_UPSTREAM:-http://cycles-admin:7979}
RUNTIME_UPSTREAM: ${RUNTIME_UPSTREAM:-http://cycles-server:7878}
depends_on:
cycles-admin:
condition: service_healthy
cycles-server:
condition: service_healthy
networks:
- cycles
cycles-admin:
image: ghcr.io/runcycles/cycles-server-admin:0.1.25.47
restart: unless-stopped
environment:
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_PASSWORD: ${REDIS_PASSWORD:?REDIS_PASSWORD must be set}
ADMIN_API_KEY: ${ADMIN_API_KEY:?ADMIN_API_KEY must be set}
WEBHOOK_SECRET_ENCRYPTION_KEY: ${WEBHOOK_SECRET_ENCRYPTION_KEY:?WEBHOOK_SECRET_ENCRYPTION_KEY must be set}
WEBHOOK_SECRET_ENCRYPTION_REQUIRED: "true"
JAVA_OPTS: "-XX:MaxRAMPercentage=75 -XX:+UseG1GC -XX:+UseStringDeduplication"
DASHBOARD_CORS_ORIGIN: ${DASHBOARD_ORIGIN:-https://admin.example.com}
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:7979/actuator/health/readiness"]
interval: 10s
timeout: 5s
retries: 3
start_period: 30s
depends_on:
redis:
condition: service_healthy
networks:
- cycles
# Runtime plane — serves /v1/reservations, /v1/evidence, and the signer
# JWKS the dashboard's Reservations + Evidence views consume. Pinned to
# .37: .36+ surfaces reservation committed/finalized/metadata, .37+ adds
# the include=evidence projection (reservation->evidence links). Older
# versions degrade gracefully (fields omitted, no links). Evidence signing
# is not enabled by the default compose file; use docker-compose.override.yml
# for the local demo identity, or set the CyclesEvidence env vars on both
# cycles-server and cycles-events in your deployment.
cycles-server:
image: ghcr.io/runcycles/cycles-server:0.1.25.44
restart: unless-stopped
environment:
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_PASSWORD: ${REDIS_PASSWORD:?REDIS_PASSWORD must be set}
ADMIN_API_KEY: ${ADMIN_API_KEY:?ADMIN_API_KEY must be set}
JAVA_OPTS: "-XX:MaxRAMPercentage=75 -XX:+UseG1GC -XX:+UseStringDeduplication"
CYCLES_METRICS_TENANT_TAG_ENABLED: "false"
SPRINGDOC_API_DOCS_ENABLED: "false"
SPRINGDOC_SWAGGER_UI_ENABLED: "false"
DASHBOARD_CORS_ORIGIN: ${DASHBOARD_ORIGIN:-https://admin.example.com}
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:7878/actuator/health/readiness"]
interval: 10s
timeout: 5s
retries: 3
start_period: 30s
depends_on:
redis:
condition: service_healthy
networks:
- cycles
# Webhook-delivery worker (consumes events from Redis, fans out to
# subscriber endpoints).
cycles-events:
image: ghcr.io/runcycles/cycles-server-events:0.1.25.20
restart: unless-stopped
environment:
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_PASSWORD: ${REDIS_PASSWORD:?REDIS_PASSWORD must be set}
WEBHOOK_SECRET_ENCRYPTION_KEY: ${WEBHOOK_SECRET_ENCRYPTION_KEY:?WEBHOOK_SECRET_ENCRYPTION_KEY must be set}
CYCLES_METRICS_TENANT_TAG_ENABLED: "false"
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:9980/actuator/health/readiness"]
interval: 10s
timeout: 5s
retries: 3
start_period: 30s
depends_on:
redis:
condition: service_healthy
networks:
- cycles
redis:
image: redis:7-alpine
restart: unless-stopped
environment:
REDIS_PASSWORD: ${REDIS_PASSWORD:?REDIS_PASSWORD must be set}
command: ["sh", "-c", "redis-server --appendonly yes --requirepass \"$${REDIS_PASSWORD}\""]
volumes:
- redis-data:/data
healthcheck:
test: ["CMD-SHELL", "redis-cli -a \"$${REDIS_PASSWORD}\" ping"]
interval: 5s
timeout: 3s
retries: 5
networks:
- cycles
volumes:
redis-data:
caddy-data:
networks:
cycles:
```
**Caddyfile**(通过 Let's Encrypt 实现自动 HTTPS):
```
admin.example.com {
reverse_proxy dashboard:80
}
```
**部署:**
```
# 生成密钥并创建 .env(切勿提交此文件)
ADMIN_API_KEY="$(openssl rand -base64 32)"
REDIS_PASSWORD="$(openssl rand -base64 32)"
WEBHOOK_SECRET_ENCRYPTION_KEY="$(openssl rand -base64 32)"
DASHBOARD_ORIGIN="https://admin.example.com"
cat > .env <

End-to-end walkthrough of the main operator flows
标签:AI治理, TypeScript, Vue3, Webhook, 力导向图, 安全插件, 特征检测, 管理面板, 自动化攻击, 请求拦截, 预算管理