参考
主题契约
写一个第三方主题需要知道的全部接口。
主题是纯静态 SPA。hub 把它当文件伺服,不编译、不注入、不提供构建期变量。
主题包
一个可安装主题是一个目录,名字必须和 theme.json 里的 short 相同:
<themes-dir>/<short>/
├── theme.json
├── preview.png # 可选,面板上的预览图。文件名是约定,不是字段
└── dist/
└── index.htmltheme.json 的字段都是字符串:
| 字段 | 含义 |
|---|---|
name | 显示名称 |
short | 唯一短名,限字母、数字、-、_。default 为内置主题保留 |
description | 简介 |
version | 主题版本 |
author | 作者 |
url | 源码地址。指向 GitHub 仓库时,面板的一键更新从它的 release 取 theme.tar.gz |
每个 tag 的 release 里的 theme.tar.gz 解开就是这个目录。
可以用的接口
只有这四个,全部同源:
| 接口 | 用途 |
|---|---|
GET /api/me | 站点名、登录状态、公开页开关 |
GET /api/nodes | 节点列表、实时指标、累计流量 |
GET /api/nodes/{id}/metrics | 历史指标和延迟记录 |
GET /api/ws | 每 2 秒推送一次节点快照的 WebSocket |
metrics 的三个查询参数都可以省:
hours=N窗口宽度。匿名上限 168,登录后 2160,超出静默收窄points=W你画得下几个点。只会让 hub 抽得更稀,不会更密series=metrics|ping只取要画的那一半,省掉的那半原本占响应的三分之一到三分之二
探测曲线的名字在响应的 probes 里随样本一起下发,匿名可读——画延迟图不需要第二个请求,
也不需要管理员身份。
匿名拿不到什么
匿名访问 GET /api/nodes 只返回公开的节点,而且响应里没有 ip、hostname、remark
这三个 key——不是空字符串,是根本不存在。
指标部分走白名单,所以 boot_id、net_rx_total、net_tx_total 也只给登录后的面板。
字段定义以 hub 的源码为准。写主题时按「这个 key 可能不存在」处理。
三种要处理的状态
| 状态 | 长什么样 |
|---|---|
| 离线节点 | metrics: null |
| 刚连上还没上报 | online: true + metrics: null |
| 坏数据 | 字段缺失或类型不对 |
第二种最容易漏。默认主题在渲染入口再检查一次完整指标,缺失或畸形时显示「不可用」—— 不让一个节点的坏数据把整个页面白屏。
路由
未知路径回落到主题自己的 dist/index.html,所以客户端路由可用。
/admin/* 由 hub 内置后台接管,主题覆盖不了它。
默认主题用 /node/{id} 做详情页。hub 的回落对它够用,但前面若有按路径做正向白名单的
反代或 WAF,得把这个前缀放行:从列表点进去只是 pushState,边缘看不见,
刷新详情页才会真的请求这个路径。症状是「点进去正常,一刷新就被拦」。
本地开发
起一个 hub 实例:
monitor-hub --listen 127.0.0.1:9911 --db /tmp/monitor.db --site http://127.0.0.1:9911bash主题的开发服务器把 /api 和 WebSocket 代理到它。默认主题的 vite.config.ts 里就是这么配的,
照抄即可。
参考实现
monitor-theme-default,
React + Vite + shadcn/ui,黑白配色。它同时是内置默认主题和第三方主题的范本——
hub 嵌进去的那个包,和你解到 themes/ 的那个是同一个文件。