homepage 集成 Spoolman Widget:3D 打印耗材余量监控配置与源码级解析
【免费下载链接】homepageA highly customizable homepage (or startpage / application dashboard) with Docker and service API integrations.项目地址: https://gitcode.com/GitHub_Trending/ho/homepage
导读
Spoolman 是一个用于 3D 打印的耗材(spool)库存管理服务,可以记录每卷耗材的剩余重量、初始重量与所属线材型号。homepage 通过内置的spoolman类型 Widget,把 Spoolman 实例中的耗材余量以百分比进度条的形式直接渲染到你的起始页仪表盘上,让你在准备打印任务时无需打开 Spoolman 后台即可一眼掌握耗材余量。本文将以 Spoolman Widget 官方文档 为主体,结合仓库源码与测试用例,完整讲解其配置方式、展示逻辑、API 代理链路与常见排障思路。
Spoolman Widget 能做什么
Spoolman(Spoolman 项目,仓库 README 未在本仓库内,此处仅作功能背景说明)提供 REST API 管理耗材库存。homepage 的spoolmanWidget 通过其/api/v1/spool接口拉取所有耗材记录,并为每个 spool 展示两块信息:
- 线材名称(
filament.name):例如 "PLA 白色"、"PETG 黑色"; - 剩余比例(
remaining_weight / initial_weight * 100):以百分比形式显示当前耗材剩余量。
这样,首页上每个耗材对应一个信息块(Block),形成一排放置、一眼可读的耗材余量面板,非常适合放在 3D 打印相关的服务分组中。
快速开始:最小配置
在services.yaml中为你的 Spoolman 实例添加一个服务条目,并在其widget段声明type: spoolman和url即可:
services: my-print-stack: displayName: 3D Print icon: sh-print widget: type: spoolman url: http://spoolman.host.or.ip配置完成后,首页会从http://spoolman.host.or.ip/api/v1/spool拉取耗材数据,并默认展示前 4 个 spool的余量。
配置参数详解
核心参数
| 参数 | 必填 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
type | 是 | string | — | 固定为spoolman,声明 Widget 类型 |
url | 是 | string | — | Spoolman 实例的根地址,如http://spoolman.host.or.ip,Widget 会在此基础上拼接/api/v1/spool |
spoolIds | 否 | number[] | 未设置时展示前 4 个 | 指定要展示的耗材 ID 列表,用于过滤显示 |
官方文档给出的带过滤示例:
widget: type: spoolman url: http://spoolman.host.or.ip spoolIds: [1, 2, 3, 4] # optional通用 Widget 参数(可选)
spoolman使用认证代理处理器(credentialedProxyHandler),因此还可以叠加 homepage 服务 Widget 的通用配置项:
widget: type: spoolman url: http://spoolman.host.or.ip spoolIds: [1, 2, 3, 4] username: admin # 可选,Basic Auth 用户名 password: secret # 可选,Basic Auth 密码 refreshInterval: 60 # 可选,覆盖全局刷新间隔(秒)username/password:当 Spoolman 开启了 Basic Auth 时使用,代理层会生成Authorization: Basic ...请求头;refreshInterval:自定义该 Widget 的数据刷新间隔,未设置时遵循全局设置。
数据链路:Widget 如何拿到耗材数据
整个请求链路由三部分组成,均可在仓库中直接找到对应实现。
1. Widget 定义:API 模板与端点映射
Widget 的元信息定义在 src/widgets/spoolman/widget.js:
import credentialedProxyHandler from "utils/proxy/handlers/credentialed"; const widget = { api: "{url}/api/v1/{endpoint}", proxyHandler: credentialedProxyHandler, mappings: { spools: { endpoint: "spool", }, }, }; export default widget;关键点:
api模板为{url}/api/v1/{endpoint},其中{url}来自配置中的url,{endpoint}由映射决定;mappings.spools.endpoint = "spool",即前端请求名为spools的数据时,实际请求的是GET {url}/api/v1/spool;- 该 Widget 注册在 src/widgets/widgets.js 与 src/widgets/components.js 中,供配置校验与组件渲染统一引用。
2. 代理层:服务端转发,避免浏览器直连
Widget 数据并非由浏览器直接请求 Spoolman,而是经过 homepage 的服务端代理。spoolman使用的 src/utils/proxy/handlers/credentialed.js 会:
- 根据请求中的
group/service/index定位到对应 Widget 配置(getServiceWidget); - 用
formatApiCall按api模板拼出真实地址http://spoolman.host.or.ip/api/v1/spool; - 合并请求头:若配置了
username/password,自动附加Basic base64(username:password)认证头(见basicAuthHeader,源码 src/utils/proxy/handlers/credentialed.js); - 将请求转发到 Spoolman,再把响应回传给前端组件。
这种"浏览器 → homepage 服务端 → Spoolman"的链路,天然规避了跨域(CORS)问题,也避免了在浏览器中暴露认证凭据。
3. 组件渲染:过滤、排序与展示
前端组件实现位于 src/widgets/spoolman/component.jsx,其完整渲染逻辑如下:
let { data: spoolData, error: spoolError } = useWidgetAPI(widget, "spools"); if (spoolError) return <Container service={service} error={spoolError} />; if (!spoolData) { const nBlocksGuess = widget.spoolIds?.length ?? 4; return ( <Container service={service}> {[...Array(nBlocksGuess)].map((_, i) => ( <Block key={i} label="spoolman.loading" /> ))} </Container> ); } if (spoolData.length === 0) { return <Container service={service}><Block label="spoolman.noSpools" /></Container>; } if (widget.spoolIds?.length) { spoolData = spoolData.filter((spool) => widget.spoolIds.includes(spool.id)); } if (spoolData.length > 4) { spoolData = spoolData.slice(0, 4); }具体行为可归纳为 4 条规则:
- 加载占位:数据尚未返回时,渲染 N 个 "Loading" 占位块,N 取
spoolIds的长度;未配置spoolIds时默认渲染 4 个占位; - 空状态:Spoolman 返回空列表时,显示一条 "No spools"(
spoolman.noSpools)提示; - 过滤:配置了
spoolIds时,只保留 ID 命中列表的 spool; - 数量上限:无论是否过滤,最终最多渲染4 个spool(
slice(0, 4))。
每个 spool 块的展示内容为:
<Block key={spool.id} label={spool.filament.name} value={t("common.percent", { value: (spool.remaining_weight / spool.initial_weight) * 100, })} />即标签(label)显示线材名称,数值(value)显示剩余重量占初始重量的百分比,并套用common.percent的本地化格式输出。
从源码看展示规则的细节
spoolIds 的解析与透传
在 src/utils/config/service-helpers.js 中,spoolIds作为该 Widget 的特有字段被解构出来,并在type === "spoolman"时写入最终 Widget 配置(src/utils/config/service-helpers.js):
if (type === "spoolman") { if (spoolIds !== undefined) widget.spoolIds = spoolIds; }这意味着spoolIds可以直接以 YAML 数组形式书写(如[1, 2, 3, 4]),服务端代理会根据该字段在数据返回后过滤展示。
数量上限的"双保险"
即便你不配置spoolIds,组件也会在过滤之后执行slice(0, 4),确保展示数量不超过 4。因此,默认只显示前 4 个 spool;若你的 Spoolman 实例中耗材超过 4 卷,有两种方式控制展示:
- 用
spoolIds精确指定要看的耗材(推荐,顺序可控); - 不指定时,展示 API 返回顺序中的前 4 个。
测试用例印证
仓库的组件测试 src/widgets/spoolman/component.test.jsx 覆盖了上述全部核心行为:
- 加载时按
spoolIds: [1, 2]渲染 2 个占位块(renders guessed loading blocks while loading); - API 返回空数组时展示
spoolman.noSpools(renders no-spools message when API returns an empty list); - 传入 5 个 spool 数据与
spoolIds: [2, 3, 4, 5, 1]时,先按 ID 过滤、再截断为 4 个块,并验证第一个块为 "A / 50%",第二个为 "B / 25%"(filters to selected spoolIds and caps at 4 entries)。
而 src/widgets/spoolman/widget.test.js 则验证了 Widget 定义(api模板、代理处理器、端点映射)符合 homepage 的统一配置形状,保证该 Widget 能被配置校验与代理调度正常识别。
实际使用建议
- 按打印任务挑选耗材:若你同时持有 10+ 卷耗材,建议用
spoolIds只列出常用的 3~4 卷,避免首页信息过载; - 结合服务组布局:把
spoolman服务与你的打印管理服务(如 OctoPrint、Mainsail 等,参见 docs/widgets/services 中对应文档)放在同一分组,形成完整的 3D 打印监控面板; - 认证场景:Spoolman 若启用了 Basic Auth,务必在 Widget 中配置
username/password,否则代理请求会返回 401,组件将进入错误态并显示错误信息(hideErrors全局设置可控制是否展示错误详情); - 错误排查:Widget 出现异常时,首先用浏览器直接访问
http://<spoolman地址>/api/v1/spool确认服务本身可用,再检查 homepage 配置中的url是否可达、认证是否匹配;代理层会将非 2xx 响应回传为error,组件会通过错误容器提示。
小结
spoolmanWidget 是 homepage 服务 Widget 体系中"API 映射 + 服务端代理 + 受控渲染"模式的典型范例:只需在 docs/widgets/services/spoolman.md 描述的type/url/spoolIds三个核心配置项上做少量声明,即可把 Spoolman 的耗材库存变成首页上一个实时、美观、信息密度适中的余量面板。配合 src/widgets/spoolman/widget.js 定义的端点映射、src/widgets/spoolman/component.jsx 的渲染规则以及 src/widgets/spoolman/component.test.jsx 的测试佐证,你可以清楚地理解它的数据链路与行为边界,并据此按需定制自己的打印监控面板。
【免费下载链接】homepageA highly customizable homepage (or startpage / application dashboard) with Docker and service API integrations.项目地址: https://gitcode.com/GitHub_Trending/ho/homepage
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考