homepage 集成 SABnzbd Widget 完整配置指南:速率、队列与剩余时间监控
导读
本文讲解如何在 homepage 项目中为 SABnzbd(Usenet 下载工具)添加服务 Widget,实现下载速率(Rate)、队列条目数(Queue)与预计剩余时间(Time Left)的实时展示。你将掌握从获取 API Key、编写 services.yaml 配置,到理解请求代理链路与前端单位换算原理的完整流程,可直接复制配置接入自己的仪表盘。
前置准备:确认 SABnzbd 实例与 API Key
在配置 Widget 之前,需要满足两个条件:
- 一个可访问的 SABnzbd 实例:homepage 通过 HTTP 请求 SABnzbd 的 API,因此你的 SABnzbd 地址必须能从运行 homepage 的机器(容器或宿主机)访问到。常见形式为
http://sabnzbd.host.or.ip:8080。 - API Key:在 SABnzbd 的 Config > General 页面中可以找到你的 API Key(通常位于 SABnzbd 配置页面底部,
API Key字段)。该 Key 是调用 SABnzbd API 的身份凭证,homepage 会把它拼接到请求 URL 中。
原文档提示:API Key 在
Config > General下查找。它是一串较长的字符串,复制时注意不要包含多余空格或换行。
编写 Widget 配置
SABnzbd Widget 在 homepage 的 services.yaml 中配置。将其放入某个服务分组(Group)下,完整的最小配置如下:
widget:
type: sabnzbd
url: http://sabnzbd.host.or.ip
key: apikeyapikeyapikeyapikeyapikey
配置字段说明
| 字段 | 必填 | 说明 |
|---|---|---|
type |
是 | 固定为 sabnzbd,用于在 homepage 的 widgets 注册表 中匹配对应实现 |
url |
是 | SABnzbd 实例的基础地址,无需带末尾斜杠(代理层会自动去除尾部 /) |
key |
是 | 来自 SABnzbd Config > General 的 API Key,用于请求鉴权 |
从源码看,key 会被直接填充进 API 模板 {url}/api/?apikey={key}&output=json&mode={endpoint}(见 widget.js),因此 API Key 必须准确无误,否则请求将返回鉴权错误,组件会渲染错误提示 UI。
Allowed fields:可展示的统计字段
该 Widget 支持且仅支持以下三个字段:
["rate", "queue", "timeleft"]
rate(Rate):当前下载速率,例如1.0 M,组件会将字符串换算为字节数后按common.byterate翻译键格式化展示;queue(Queue):当前队列中的下载条目数量(noofslots字段);timeleft(Time Left):队列预计完成所需时间,例如00:01:00。
这三个字段是文档明确允许的展示项,不包含上传速率、磁盘剩余等 SABnzbd 提供的其他 API 字段,请勿在配置中声明不支持的字段。
深入源码:请求如何被构造与代理
配置中只需要三个字段,但背后是 homepage 一套完整的代理机制在运作。
API 模板与 endpoint 映射
widget.js 定义了 SABnzbd Widget 的全部后端逻辑:
const widget = {
api: "{url}/api/?apikey={key}&output=json&mode={endpoint}",
proxyHandler: genericProxyHandler,
mappings: {
queue: {
endpoint: "queue",
validate: ["queue"],
},
},
};
api模板:最终请求地址由{url}、{key}、{endpoint}三个占位符组成。output=json要求 SABnzbd 返回 JSON 格式,mode={endpoint}会被替换为具体的 API 模式。mappings.queue:唯一的 endpoint 映射,客户端请求queue端点时,代理层将其转换为 SABnzbd API 的mode=queue调用。validate: ["queue"]:响应数据必须包含顶层queue字段才算合法,否则代理会返回 "Invalid data" 错误。
占位符替换与代理转发
占位符替换由 api-helpers.js 中的 formatApiCall 完成:它扫描模板中的 {...} 并依次用对应参数替换,其中 url 会先去除尾部斜杠(replace(/\/+$/, "")),避免出现 http://host//api/... 之类的双斜杠问题。
请求转发由 generic.js 中的 genericProxyHandler 负责:
- 根据
group与service从用户配置中读取该服务的 Widget 定义; - 校验 Widget 类型是否注册(
widgets?.[widget.type]?.api),未注册则返回 403; - 用
formatApiCall拼接完整 API URL; - 支持为 Widget 附加自定义
headers、username/password(Basic Auth)与requestBody; - 通过
httpProxy发起请求,status === 200时调用validateWidgetData校验数据形状; - 校验失败返回 "Invalid data" 错误,
>= 400状态码返回 "HTTP Error" 并附上脱敏后的 URL(仅保留主机名,见sanitizeErrorURL)。
前端侧的 use-widget-api.js 通过 SWR 请求 /api/services/proxy?group=...&service=...&endpoint=queue,并自动处理错误透传与可选的刷新间隔。
前端渲染:单位换算与三块统计信息
组件实现在 component.jsx(对应 src/widgets/sabnzbd/component.jsx)。加载成功后渲染三块统计:
<Block label="sabnzbd.rate" value={t("common.byterate", { value: fromUnits(queueData.queue.speed) })} />
<Block label="sabnzbd.queue" value={t("common.number", { value: queueData.queue.noofslots })} />
<Block label="sabnzbd.timeleft" value={queueData.queue.timeleft} />
关键点在于 fromUnits 函数(component.jsx):
function fromUnits(value) {
const units = ["B", "K", "M", "G", "T", "P"];
const [number, unit] = value.split(" ");
const index = units.indexOf(unit);
if (index === -1) {
return 0;
}
return parseFloat(number) * 1024 ** index;
}
SABnzbd API 返回的 speed 是类似 "1.0 M" 的字符串(数值 + 空格 + 单位),fromUnits 将其拆分为数字与单位后缀,按 1024 进制(K=1024、M=1024²、G=1024³……)换算为字节数,再由 common.byterate 翻译键格式化为人类可读速率。若单位无法识别则返回 0,避免渲染异常。
三块统计的文案标签(Rate / Queue / Time Left)来自 public/locales/en/common.json 的 sabnzbd 命名空间,其余语言文件同理,因此 Widget 文案会随 homepage 界面语言自动切换。
测试验证:组件行为有据可查
仓库为 SABnzbd Widget 提供了完整测试,可作为行为契约:
- widget.test.js:校验导出的 Widget 配置结构合法;
- component.test.jsx:
- 加载态:数据未返回时渲染 3 个占位 Block(
sabnzbd.rate/sabnzbd.queue/sabnzbd.timeleft); - 错误态:接口报错时渲染
widget.api_error错误 UI 并透传错误消息; - 数据态:
{ queue: { speed: "1.0 M", noofslots: 2, timeleft: "00:01:00" } }时,速率块期望值为1024 ** 2(即 1 MiB),队列块为2,剩余时间块为"00:01:00",与fromUnits换算逻辑一一对应。
- 加载态:数据未返回时渲染 3 个占位 Block(
这组测试同时验证了前端渲染与换算逻辑,你在排查展示异常时可以直接参照其断言。
常见问题排查
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| Widget 显示 "HTTP Error" | url 不可达或 SABnzbd 未运行 |
在运行 homepage 的环境中 curl 测试 {url}/api/?apikey=...&output=json&mode=queue |
| 显示鉴权失败 | API Key 错误 | 回到 SABnzbd Config > General 重新复制 Key,检查是否有空格 |
| 显示 "Invalid data" | 响应缺少顶层 queue 字段 |
确认 SABnzbd 版本 API 返回格式,或直接在浏览器访问上述 API 地址核对 JSON 结构 |
| 速率显示为 0 | SABnzbd 返回的单位不在 B/K/M/G/T/P 中,或当前无下载任务 |
空闲队列时 speed 可能为空值,属正常现象 |
小结
SABnzbd Widget 是 homepage 服务集成体系中的一个典型示例:配置侧只需 type、url、key 三个字段,后端通过 widget.js 的 API 模板与 generic.js 的通用代理完成请求转发与数据校验,前端再由 component.jsx 完成单位换算与三块统计渲染。如果你需要展示更多 SABnzbd 指标,可以结合 widgets.js 注册表 与 API 文档 了解扩展方式;其他下载类服务(如 qBittorrent、Transmission)的配置思路与此一致,可对照 services 文档目录 逐一配置。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00