项目十 应用仪表盘(Heimdall 式)
用 Worker 渲染磁贴页、KV 存应用清单、并行子请求做健康检查,实现一个可控的最小应用仪表盘。
编辑与核验:橙宝书编辑团队 ·
完成标准
打开 / 能看到应用磁贴网格;GET /api/health 并行探测所有应用并聚合出 up/down;健康结果被 KV 缓存 60 秒,重复打开页面不会打爆后端;只有持 ADMIN_KEY 的请求能增删应用。
前置条件与运行随书示例
需要 Node.js 22、pnpm 和已安装的仓库依赖。示例位于 examples/app-dashboard,使用本地 KV;先复制本地 Secret,再启动 Worker:
cp examples/app-dashboard/.dev.vars.example examples/app-dashboard/.dev.vars
pnpm wrangler dev --config examples/app-dashboard/wrangler.jsonc另一个终端运行 node --test examples/app-dashboard/test/index.test.mjs,应看到 6 个测试通过。按示例 README 的 curl 添加两个应用,响应状态应为 201;打开 http://127.0.0.1:8787/ 应看到磁贴网格;请求 /api/health 返回每个应用的 up 或 down。不带 Authorization 头的管理请求应得到 401;url 不是 http/https 时应得到 400 invalid_url。
示例认证边界
ADMIN_KEY 只用于演示服务端鉴权,且管理 API 面向服务端脚本调用。公开部署时不要把 Key 放进浏览器代码;面向团队的仪表盘应换成 Cloudflare Access 或真实会话。
先澄清:Heimdall 跑不到 Cloudflare 上
用户社区常说的 Heimdall 是 linuxserver/Heimdall(Star 9000+),它是 PHP/Laravel 应用,官方只提供 Docker/VPS 部署形态,无法运行在 Cloudflare Workers 或 Pages 上;Cloudflare 生态目前也没有成熟的 Heimdall 平替。如果你想要零代码方案,可以把 gethomepage/homepage(Star 32000+)构建成纯静态站点后托管到 Cloudflare Pages。本项目的随书示例演示的是第三条路:自己用 Workers + KV 写一个最小可控的仪表盘,换来的是完全可控的认证、健康检查逻辑和边界。
| 需求 | 选择 |
|---|---|
| 零代码、可静态构建的导航页 | gethomepage/homepage 构建后托管到 Pages |
| 完整 Heimdall 功能(应用感知、增强磁贴) | 留在 Docker/VPS 上,不要强行迁移 |
| 可控认证、自定义健康检查、边缘部署 | 本项目:Worker 渲染 + KV 清单 |
技术方案:Worker 渲染磁贴 + KV 清单
仪表盘不需要框架。一个 Worker 在 GET / 时从 KV 读取 apps 键(JSON 数组,每项含 id、name、description、url、healthUrl),服务端渲染磁贴网格:每个磁贴显示名称、描述和名称首字母作为图标,整块链接到应用地址。所有用户可控字段在渲染前做 HTML 转义,url 在写入前限定为 http/https,避免 javascript: 之类的伪协议进入 href。
const apps = await env.DASHBOARD_KV.get('apps', 'json');
const tiles = (apps ?? []).map((app) => `
<a class='tile' href='${escapeHtml(app.url)}' rel='noopener noreferrer'>
<span class="icon">${escapeHtml(app.name[0].toUpperCase())}</span>
<span class="name">${escapeHtml(app.name)}</span>
</a>`);KV 适合这份清单:读多写少、值小、可以接受秒级最终一致。应用数量设上限(示例为 50),防止清单无限增长拖慢渲染。
健康检查:并行子请求与 60 秒缓存
GET /api/health 对每个应用的 healthUrl 发起 Worker 子请求,用 Promise.allSettled 并行执行,任何一个失败都不拖垮整体;AbortSignal.timeout(3000) 把单个探测限制在 3 秒内,超时与连接拒绝统一映射为 down。结果按健康端点 URL 写入 KV,expirationTtl: 60,缓存命中时直接返回,不发任何子请求:
const settled = await Promise.allSettled(
misses.map((app) => fetchImpl(app.healthUrl, { signal: AbortSignal.timeout(3000) })),
);
// 命中缓存的应用完全跳过;misses 的探测结果写回 KV,TTL 60 秒子请求来自网络边界
Worker 子请求的来源 IP 是 Cloudflare 边缘,不是办公室或家庭网络。被监控的公网服务需要在防火墙或 WAF 中放行这些来源;只在内网可达的应用要用 Tunnel 暴露,参考 用 Tunnel 保护私有服务访问。
管理 API 与输入校验
POST /api/apps 与 DELETE /api/apps/<id> 都要求 Authorization: Bearer <ADMIN_KEY>,比较时对两侧做 SHA-256 后再逐字节异或,避免时序差异泄露。新增应用校验:name 为 1 到 60 字符,description 不超过 160 字符,url 必须是合法 http/https URL,healthUrl 可选、缺省回退到 url。请求体超过 4KB 返回 413,非 JSON 返回 415,清单达到上限返回 409 app_limit_reached。
验证与排障
| 现象 | 先检查 | 恢复动作 |
|---|---|---|
401 unauthorized | .dev.vars 与 Authorization 头是否一致 | 重新复制本地模板,不把值提交到仓库 |
400 invalid_url | url 是否以 http:// 或 https:// 开头 | 修正为完整 URL 后重试 |
健康检查全部 down | 目标服务是否放行 Cloudflare 边缘来源 | 放行来源或为内网应用配置 Tunnel |
| 页面打开后状态不更新 | 是否还在 60 秒缓存窗口内 | 等待 TTL 过期,或删除 health: 前缀的 KV 键 |
远程边界与回滚
创建远程 KV namespace、写入生产 Secret、部署 Worker 都会修改 Cloudflare 状态,本教程不自动执行。回滚时需要注意:代码回滚不会删除已写入 KV 的应用清单和健康缓存,但 health: 键有 60 秒 TTL 会自行过期,apps 清单需要用管理 API 或 wrangler kv key delete 显式清理。若新版健康检查逻辑误判导致告警风暴,先回滚到稳定代码,再核对 KV 中残留的缓存键。
下一步:按数据形态选择存储服务。
官方来源
这篇内容帮你完成目标了吗?
内测反馈只在当前浏览器生成,不会自动上传。