runcycles/cycles-dashboard

GitHub: runcycles/cycles-dashboard

为 Cycles AI agent 治理平台打造的多租户运营管理面板,提供预算管控、操作审计、事件响应和可观测性功能。

Stars: 0 | Forks: 2

[![CI](https://static.pigsec.cn/wp-content/uploads/repos/cas/ad/ad5834178f7599af9fdda11629d49cae07f2997beec49821b2920eff5bfd50e7.svg)](https://github.com/runcycles/cycles-dashboard/actions) [![License](https://img.shields.io/badge/license-Apache%202.0-blue)](LICENSE) [![Spec](https://img.shields.io/badge/spec-v0.1.25.34-blue)](https://github.com/runcycles/cycles-protocol/blob/main/cycles-governance-admin-v0.1.25.yaml) [![Vue](https://img.shields.io/badge/vue-3-brightgreen)](https://vuejs.org) [![TypeScript](https://img.shields.io/badge/typescript-strict-blue)](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)(工程开发纪事)。

Dashboard walkthrough — tenants, budgets, reservations, webhooks
End-to-end walkthrough of the main operator flows

## 概览 用于监控和管理 Cycles 预算执行平台的运营优先面板。围绕运维人员的工作流设计,而非 CRUD 实体列表。 | 页面 | 用途 | |------|---------| | **Overview** | 运营健康度一览 — 单请求聚合面板 | | **Tenants** | 租户列表 + 详情页,包含预算、API keys 和策略标签页 | | **Budgets** | 租户范围内的预算列表,带有使用率/欠费进度条 + 精确的范围详情 | | **Events** | 以关联性为首的调查工具,带有可展开的详情行 | | **API Keys** | 跨租户密钥列表,包含掩码处理的 ID、权限和状态过滤器 | | **Webhooks** | 订阅健康度(绿/黄/红)+ 投递历史 | | **Reservations** | 在事件响应期间强制释放挂起的预约(runtime-plane 的 admin-on-behalf-of 模式);包含已提交/已结算列、元数据详情、时间范围 + Subject 过滤器 | | **Audit** | 合规性查询工具,支持导出 CSV/JSON(仅限手动,不自动刷新) | | **Evidence** | 按 ID 检索并检查已签名的 evidence envelope;根据发布的 JWK Set 进行签名者密钥解析。需经过运维人员认证查询 — 底层的 envelope 检索是一个公开的、基于内容寻址的 runtime API | ### 运维操作 可直接在面板中执行的一级事件响应操作(受功能权限控制,需确认): | 操作 | 位置 | 效果 | |--------|-------|--------| | **Freeze budget** | 预算详情 | 阻止所有预约、提交和资金操作 | | **Unfreeze budget** | 预算详情 | 恢复正常操作 | | **Create budget** | 预算列表,租户详情 | Admin-on-behalf-of(双重认证) — 包含 ScopeBuilder + 租户选择器的模态框 | | **Adjust budget allocation** | 预算详情 | 内联表单 — 使用带 RESET 操作的 fund endpoint | | **Rollover billing period (RESET_SPENT)** | 预算详情 → Fund → RESET_SPENT | 重置 `spent` 计数但不改动 `allocated`;可选的精确支出覆盖(留空 = 零)。需要 cycles-server-admin v0.1.25.18+ | | **Bulk budget action (CREDIT / DEBIT / RESET / RESET_SPENT / REPAY_DEBT)** | 预算列表 | 过滤并应用 — 要求指定单一租户(规范约束);预览步骤 + expected_count 门槛 + 针对失败/跳过行的逐行结果对话框。需要 cycles-server-admin v0.1.25.29+ | | **Emergency Freeze (tenant-wide)** | 租户详情 | 跨所有 ACTIVE 预算执行顺序冻结 — 一键锁定,包含确认 + 影响范围摘要 | | **Create policy** | 策略标签页(租户详情) | Admin-on-behalf-of — 模态表单,租户范围内 | | **Edit policy** | 策略标签页 | Admin-on-behalf-of — 提交 policy_id,由服务器解析所属租户 | | **Suspend tenant** | 租户详情 | 阻止该租户的所有 API 访问 | | **Reactivate tenant** | 租户详情 | 恢复 API 访问 | | **Bulk suspend / reactivate tenants** | 租户列表 | 多选 + 批量操作栏,按租户顺序发起调用,显示实时进度,支持在请求间取消 | | **Create tenant** | 租户列表 | 模态表单,成功后跳转至新租户 | | **Edit tenant** | 租户详情 | 编辑显示名称 | | **Revoke API key** | API Keys 列表,租户详情 | 立即使密钥失效(不可逆) | | **Create API key** | API Keys 列表,租户详情 | 带有权限设置的模态表单,仅显示一次密钥 | | **Edit API key** | API Keys 列表 | 编辑名称、权限、范围过滤器 | | **Pause webhook** | Webhook 详情 | 停止事件投递;事件将被静默丢弃 | | **Enable webhook** | Webhook 详情 | 恢复投递(重置失败计数器) | | **Reset & re-enable webhook** | Webhook 详情 | 重新启用已禁用/失败状态的 webhook,清除失败记录 | | **Bulk pause / enable webhooks** | Webhooks 列表 | 多选 + 租户过滤器;逐个处理订阅并支持取消。自动禁用的 webhook 不包含在批量启用中(需逐行验证) | | **Create webhook** | Webhooks 列表 | 模态表单,仅显示一次签名密钥 | | **Delete webhook** | Webhook 详情 | 确认后永久删除 | | **Test webhook** | Webhook 详情 | 发送合成测试事件,内联显示结果 | | **Replay events** | Webhook 详情 | 重新投递指定时间范围内的事件 | | **Force release reservation** | 预约 | Runtime-plane 的 admin-on-behalf-of — 预填 `[INCIDENT_FORCE_RELEASE]` 原因以便于审计检索;当服务器发出 `cycles_evidence` 引用时,会显示“View evidence”链接 | ## 架构 ``` src/ ├── api/ # API client (X-Admin-API-Key only) ├── components/ # Reusable UI: Sidebar, PageHeader, StatusBadge, SortHeader, EmptyState, etc. ├── composables/ # usePolling, useSort, useDarkMode, useTerminalAwareList, useChartTheme ├── stores/ # Pinia: auth (introspect + capabilities) ├── views/ # route views (login, overview, budgets, events, api-keys, webhooks, audit, tenants, reservations, evidence + detail views) └── types.ts # TypeScript types matching governance spec schemas ``` - **框架:** Vue 3 + TypeScript + Vite - **状态:** Pinia - **样式:** Tailwind CSS v4,支持暗色模式 - **测试:** Vitest + @vue/test-utils(单元测试);Playwright(针对活动 compose stack 的端到端测试) - **路由:** Vue Router 4,带有 auth guard - **安全:** SRI hashes (`vite-plugin-sri-gen`),CSP + HSTS headers,登录限流 ## 快速开始 ### 开发环境(使用 Vite proxy) 要求本地**同时**运行两个后端: - **cycles-server-admin** 位于 `localhost:7979` — 治理平面(租户、预算、策略、webhooks、审计、introspect)。 - **cycles-server** 位于 `localhost:7878` — 运行时平面(预约;强制释放使用 admin-on-behalf-of 双重认证)。 ``` npm install npm run dev ``` Dashboard 启动于 `http://localhost:5173`。Vite 开发服务器在运行时平面和治理平面之间拆分代理: - `/v1/reservations*`, `/v1/evidence*`, `/v1/.well-known/cycles-jwks.json` → `localhost:7878` (cycles-server, 运行时平面) - `/v1/*` (其他所有请求) → `localhost:7979` (cycles-server-admin, 治理平面) 在生产容器中,`default.conf.template` 镜像了相同的路由拆分规则, 这两个上游服务可以通过 `ADMIN_UPSTREAM` / `RUNTIME_UPSTREAM` 环境变量进行配置。 ### 开发环境(通过 Docker 运行完整 stack) ``` # 启动 admin server + Redis cd ../cycles-server-admin ADMIN_API_KEY=your-key docker compose up -d # 启动 dashboard cd ../cycles-dashboard npm install npm run dev ``` ### 生产环境 请参阅下方的 [生产环境部署](#production-deployment)。推荐的配置使用 Caddy 实现自动 HTTPS: ``` cp Caddyfile.example Caddyfile # edit domain # 创建包含 ADMIN_API_KEY、REDIS_PASSWORD 等的 .env docker compose -f docker-compose.prod.yml up -d ``` `Caddyfile` 是本地部署配置,被 git 有意忽略;请将 `Caddyfile.example` 作为提交的 模板。 仅暴露 443 和 80 端口。所有内部服务(dashboard、admin server、Redis)均通过 Docker 网络通信。 ## 身份验证 Dashboard 仅使用 `AdminKeyAuth`(`X-Admin-API-Key` 请求头)。不使用任何租户的 API keys。 1. 用户在登录页输入 admin API key 2. Dashboard 调用 `GET /v1/auth/introspect` 验证并获取功能权限 3. 侧边栏导航受布尔类型的功能权限控制(`view_overview`、`view_budgets` 等) 4. 任何 API 调用如果返回 401/403,将清除会话并将用户重定向至登录页 5. API key 存储在 `sessionStorage` 中 — 刷新页面后依然有效,关闭标签页/浏览器时清除 6. 会话空闲超时(30 分钟)和绝对超时(8 小时)在客户端强制执行(每 15 秒检查一次) 7. 登录限流 — 3 次失败尝试后触发指数退避(5秒 → 上限 60秒) ## 使用的 API 端点 | 端点 | 页面 | 备注 | |----------|------|-------| | `GET /v1/auth/introspect` | 登录 | 认证校验 + 权限发现 | | `GET /v1/admin/overview` | 概览 | 单请求聚合的 dashboard payload | | `GET /v1/admin/tenants` | 租户 | 租户列表 | | `GET /v1/admin/tenants/{id}` | 租户详情 | 单个租户 | | `GET /v1/admin/budgets` | 预算 | 限定租户范围的列表(需要 `tenant_id` 参数) | | `GET /v1/admin/budgets/lookup` | 预算详情 | 精确查找 (scope, unit) | | `GET /v1/admin/events` | 事件 | 过滤后的事件流 | | `GET /v1/admin/webhooks` | Webhooks | 订阅列表 | | `GET /v1/admin/webhooks/{id}` | Webhook 详情 | 单个订阅 | | `GET /v1/admin/webhooks/{id}/deliveries` | Webhook 详情 | 投递历史 | | `GET /v1/admin/audit/logs` | 审计 | 带导出功能的手动查询 | | `GET /v1/admin/api-keys` | 租户详情 | 每个租户的 API keys | | `GET /v1/admin/policies` | 租户详情 | 每个租户的策略(需要 `tenant_id`) | | `POST /v1/admin/budgets/freeze` | 预算详情 | 冻结预算 (ACTIVE → FROZEN) | | `POST /v1/admin/budgets/unfreeze` | 预算详情 | 解冻预算 (FROZEN → ACTIVE) | | `PATCH /v1/admin/tenants/{id}` | 租户详情 | 挂起 / 激活租户 | | `DELETE /v1/admin/api-keys/{key_id}` | API Keys, 租户详情 | 吊销 API key | | `PATCH /v1/admin/webhooks/{subscription_id}` | Webhook 详情 | 暂停/启用,重置失败计数 | | `DELETE /v1/admin/webhooks/{subscription_id}` | Webhook 详情 | 删除 webhook 订阅 | | `POST /v1/admin/webhooks/{subscription_id}/test` | Webhook 详情 | 发送测试事件 | | `POST /v1/admin/webhooks/{subscription_id}/replay` | Webhook 详情 | 重放历史事件 | | `POST /v1/admin/budgets/fund` | 预算详情 | 调整配额 (RESET 操作) | | `GET /v1/reservations` | 预约 | 租户范围列表;支持 `include=`、created/expires/finalized 范围以及 Subject 过滤器 | | `GET /v1/reservations/{id}` | 预约 | 包含 `committed_metadata` / reserve metadata + `evidence` projections 的详情(支持一键点击“View evidence”链接;需要 cycles-server v0.1.25.37+)| `POST /v1/reservations/{id}/release` | 预约 | 强制释放;响应中的 `cycles_evidence` 会提供“View evidence”链接 | | `GET /v1/evidence/{evidence_id}` | Evidence | 公开的签名 envelope 检索(运行时平面) | | `GET /v1/.well-known/cycles-jwks.json` | Evidence | 用于解析签名者密钥的 Signer JWK Set(运行时平面) | ## 列表约定 每个顶级列表视图(Tenants、Budgets、Webhooks、API Keys)以及 TenantDetail 的子列表都共享一个 **hide-terminal-by-default**(默认隐藏终态)模式 (v0.1.25.46+)。处于终态的行在挂载时会被隐藏,并通过筛选行中的 **"Show <verb>"** 开关进行展示: | 实体 | 终态 | 开关标签 | URL 参数 | |---|---|---|---| | Tenant | `CLOSED` | 显示已关闭 (N) | `?include_terminal=1` | | Budget | `CLOSED` | 显示已关闭 (N) | `?include_terminal=1` | | Webhook | `DISABLED` | 显示已禁用 (N) | `?include_terminal=1` | | API Key | `REVOKED`, `EXPIRED` | 显示已吊销 (N) | `?include_terminal=1` | - **原因:** 在默认的 `created_at desc` 排序下,刚刚进入终态的数据行会固定在顶部,在视觉上与仍需运维人员处理的数据行混淆。这与 Gmail / GitHub / Linear 中“隐藏已完成 / 已归档”的惯例一致。 - **自动触发:** 从状态下拉菜单中选择终态值(例如 `status=CLOSED`)会自动显示这些行,这样运维人员就不会看到空列表(与 GitHub 的 `state:closed` 模式相同)。 - **置底顺序:** 开启时,终态行会通过稳定分区显示在可见列表的底部(每组内保留列排序顺序)。 - **导出 / 全选 / 计数器:** 全部基于应用终态过滤后的可见列表读取。CSV/JSON 导出永远不会包含隐藏的终态;批量操作永远不会静默处理隐藏行。 共享实现:`src/composables/useTerminalAwareList.ts`。 ## 数据可视化 Dashboard 通过 Apache ECharts (`vue-echarts`) 在数据表旁渲染内联图表。 图表层作为试验性切片在 v0.1.25.47(单个环形图)中引入, 并在 v0.1.25.48 – v0.1.25.50 中扩展为三个 Overview 环形图: **Budget status distribution**(生命周期分布)、 **Budget fleet utilization**(真实利用率桶 — Healthy < 90% / Near cap 90–99% / Over cap ≥ 100%,根据 `spent/allocated` 计算得出,而不是基于债务的 `is_over_limit` 服务器 信号),以及 **Events by category**(最近时间窗口的活动分类)。 v0.1.25.51 添加了 **webhook fleet-health donut** (Healthy / Failing / Paused / Disabled) 以及在 `WebhookDetailView` 上的四格 **per-subscription stat row** (last-success band, delivery-outcome donut, attempts histogram, response-time p50/p95/max) — 所有的数据均来自已经处于请求状态的数据轮询。 v0.1.25.52 **重新调整了** webhook fleet-health donut 的 位置,从 `WebhooksView` 移至 Overview 图表行 (现在 `lg` 屏幕下为四格布局: budget utilization → webhook fleet health → events by category → top-10 by debt),因此 `WebhooksView` 可以将表格保留在首屏 以便进行行级别的筛查;`WebhookDetailView` 的统计行保留在 详情视图中(订阅详情理应与该订阅放在一起)。随后的切片将此模式扩展到了 API Keys / Events 视图。 共享构建块: | 文件 | 作用 | |---|---| | `src/components/BaseChart.vue` | 共享包装器。Props:`option`、`label` (可访问性)、`height`。支持 Tree-shaking 的 ECharts 注册 — 仅打包使用中的图表类型。 | | `src/composables/useChartTheme.ts` | 将 Tailwind 状态 token(success / warning / danger / info / neutral)以及 axis / grid / tooltip 颜色映射到 ECharts 值的响应式调色板。在暗色模式切换时重新推导。 | ECharts 通过 `defineAsyncComponent` 在每个视图中延迟加载, 因此只有当图表实际渲染时才会下载图表包。没有任何视图的初始 chunk 会承担图表库的加载成本。v0.1.25.51 重新注册了 BarChart + GridComponent(在 v0.1.25.50 中当所有三个 Overview 图表变为 环形图时被移除),因为 `WebhookDetailView` 引入了一个基于投递次数的 柱状图。当前激活的注册包含:PieChart, BarChart, TooltipComponent, LegendComponent, GridComponent。 每个图表读取的是视图已经获取的数据 — 没有任何图表会在 上方注意力卡片已驱动的网络请求之外增加新的 网络请求。图表同时也是**可点击的**:点击切片会触发 `slice-click`, 父级视图将其映射为 `router.push`,并预先应用相应的列表视图 过滤器。当前的下钻约定: - Budget status donut → Budgets 视图,按 `status=ACTIVE|FROZEN|CLOSED` 或 `filter=over_limit` 过滤。 - Budget fleet utilization donut → Budgets 视图,按 `utilization_min` / `utilization_max` 过滤(整数百分比,0–100)。`BudgetsView` 在挂载时从 URL 中提取这两个参数。 - Events by category donut → Events 视图,按 `category=` 过滤。 - 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 <
标签:AI治理, TypeScript, Vue3, Webhook, 力导向图, 安全插件, 特征检测, 管理面板, 自动化攻击, 请求拦截, 预算管理