Homepage 集成 Gotify 服务组件:配置指南与源码级实现解析
Gotify 是一款自托管的轻量级消息推送服务,通常与各类自动化任务、监控告警协同使用。本文以 Homepage 官方文档中 Gotify 组件配置为核心,结合仓库内 widget 定义、组件实现 及 凭据代理处理器 等源码,完整讲解如何在服务仪表盘中接入 Gotify 并展示应用、客户端与消息统计信息,帮助读者在自建仪表盘中快速落地该组件。
Gotify 组件概述与数据来源
Gotify 组件通过 Gotify Server 的 REST API 读取三类数据,并在 Homepage 的服务卡片上以三个统计块展示:
- 应用(Applications):当前 Gotify 实例中注册的应用总数,对应
application接口; - 客户端(Clients):当前实例中的客户端数量,对应
client接口; - 消息(Messages):当前实例接收到的消息数量,对应
message接口。
从 widget.js 的映射定义可以看到,组件声明了统一的 API 模板与三个数据端点:
const widget = {
api: "{url}/{endpoint}",
proxyHandler: credentialedProxyHandler,
mappings: {
application: { endpoint: "application" },
client: { endpoint: "client" },
message: { endpoint: "message" },
},
};
其中 api 模板中的 {url} 与 {endpoint} 会在请求时被实际替换;{endpoint} 的取值正是 mappings 中声明的三个端点。该组件使用 credentialedProxyHandler 作为代理处理器,其身份认证细节将在下文展开。
获取 Gotify 客户端令牌
在配置组件前,需要先准备一个 Gotify 客户端令牌(client token):
- 若已有现成客户端,可直接复制其令牌;
- 若没有,请在 Gotify 的 Web 管理界面中创建新的客户端并获取令牌。
需要特别说明的是,这里的令牌是**客户端令牌(Client Token)**而非应用令牌(Application Token)。Gotify 的客户端令牌用于访问实例的管理类 API(如应用、客户端、消息列表),这正是组件读取上述三类数据所需的凭证。
基础配置:widget 配置块
在 Homepage 的 services.yaml 中,为某个服务添加如下 widget 配置块即可启用 Gotify 组件:
widget:
type: gotify
url: http://gotify.host.or.ip
key: clientoken
各字段含义如下:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
type |
字符串 | 是 | 固定为 gotify,用于在 widgets.js 中匹配组件定义 |
url |
字符串 | 是 | Gotify 服务的访问地址,如 http://gotify.host.or.ip,也可以是带端口或 HTTPS 的地址 |
key |
字符串 | 是 | Gotify 客户端令牌(client token),来自已有客户端或管理界面新建 |
允许的字段集合为 ["apps", "clients", "messages"](即组件可展示的三个统计项)。
配置校验入口
组件配置通过 widget.test.js 中的 expectWidgetConfigShape 进行结构校验,确保 widget.js 导出的配置符合项目统一的组件配置规范。若 type 或其他关键字段缺失,组件将无法被正确匹配,服务卡片不会渲染该组件。
认证与代理:凭据如何注入请求
Gotify 组件本身不直接向 Gotify Server 发起请求,而是经由 Homepage 的代理层转发。从 credentialed.js 可以看到,代理处理器会先根据 group 与 service 从配置中解析出 widget 定义,然后按组件类型注入对应的认证头:
} else if (widget.type === "gotify") {
headers["X-gotify-Key"] = `${widget.key}`;
}
即:Gotify 组件使用 X-gotify-Key 请求头传递客户端令牌。这一认证方式是 Gotify Server 所支持的客户端令牌认证形式,组件配置中的 key 字段最终就是通过该头字段发送给服务端。
代理层同时负责:
- 拼接目标地址:
new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget })),将{url}与{endpoint}替换为实际值; - 合并请求头:在默认
Content-Type: application/json基础上叠加组件自定义头(widget.headers)与额外头(req.extraHeaders); - 处理错误响应:对
400+状态码返回结构化错误信息,并通过sanitizeErrorURL去除 URL 中的敏感信息; - 校验响应数据:调用
validateWidgetData对返回内容做合法性检查。
响应数据校验
validate-widget-data.js 会尝试将响应解析为 JSON,并检查 mapping.validate 中声明的必填字段是否存在。Gotify 组件未声明额外的 validate 字段,因此校验主要依赖 JSON 解析是否成功;若响应无法解析为 JSON,代理将返回 Invalid data 错误并在服务卡片上呈现错误提示。
前端渲染:组件与统计块
component.jsx 通过 useWidgetAPI 钩子并行请求三个端点,加载完成后在服务卡片中渲染三个统计块:
const { data: appsData, error: appsError } = useWidgetAPI(widget, "application");
const { data: messagesData, error: messagesError } = useWidgetAPI(widget, "message");
const { data: clientsData, error: clientsError } = useWidgetAPI(widget, "client");
三个统计块对应的展示值:
| 统计块 | 展示值 | 数据来源 |
|---|---|---|
gotify.apps(应用) |
appsData?.length |
application 接口返回的数组长度 |
gotify.clients(客户端) |
clientsData?.length |
client 接口返回的数组长度 |
gotify.messages(信息) |
messagesData?.messages?.length |
message 接口返回对象中 messages 数组长度 |
由此可以看出消息接口的响应结构与其他两者不同:application 与 client 接口直接返回数组,而 message 接口返回的是包含 messages 字段的对象,这与 Gotify Server 的 REST API 设计一致。
组件渲染遵循如下流程:
- 若任一请求出错(
appsError || messagesError || clientsError),渲染带错误信息的Container,优先展示先返回的错误; - 若数据尚未就绪,渲染三个占位统计块(值为
-); - 数据就绪后,渲染带实际数值的三个统计块。
界面标签文本由国际化资源提供,例如英文语言包 common.json 中的 gotify.apps / gotify.clients / gotify.messages 对应 Applications / Clients / Messages,简体中文语言包 common.json 中则对应 应用 / Clients / 信息,展示语言随仪表盘当前语言自动切换。
测试用例佐证
component.test.jsx 覆盖了组件的三种典型状态,可作为理解组件行为与验证自建环境是否符合预期的参考:
- 加载中:三个端点均未返回数据时,渲染 3 个
.service-block占位块,值均为-; - 接口错误:任一端点报错时,展示
widget.api_error错误界面与错误信息; - 数据就绪:如
application返回[{ id: 1 }, { id: 2 }]、client返回 3 个元素、message返回{ messages: [{ id: 1 }] },则分别渲染apps=2、clients=3、messages=1。
完整接入示例
结合上述配置与源码说明,一个完整的接入步骤为:
- 在 Gotify Web 界面创建客户端并复制客户端令牌;
- 在 Homepage 的
services.yaml中为 Gotify 服务配置 widget(如本节开头所示),确保url可达、key为真实客户端令牌; - 保存配置并刷新仪表盘,服务卡片将依次展示应用数、客户端数与消息数三个统计块。
若卡片显示错误,可依次排查:url 是否可从 Homepage 所在网络访问、key 是否为有效的客户端令牌、Gotify 是否启用了外部 API 访问。相关组件定义与测试文件均可直接查阅 widget.js、component.jsx 与 component.test.jsx 进行对照验证。
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
video-shotcraftAI宣传片skill,使用 Remotion 制作电影级产品视频:提供106 张镜头配方卡和可复用的视频魔板。适用于 Claude Code 与 Codex以及所有其他智能体Markdown00
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