首页
/ Homepage 自定义 Widget 开发指南:从组件到代理的完整实现路径

Homepage 自定义 Widget 开发指南:从组件到代理的完整实现路径

2026-09-09 19:20:03作者:尤峻淳Whitney

Widget(小组件)是 Homepage 的核心组成部分,负责展示关于你的系统、服务与环境的关键信息。无论是为某个自托管服务新增一块状态面板,还是把第三方 API 的数据呈现在首页上,本质上都是在编写一个 Widget。本篇指南以 Homepage 官方 Widget 开发文档(docs/widgets/authoring/index.md)为主线,结合仓库内的真实源码与测试,完整讲解一个自定义 Widget 从目录创建、元数据定义、组件渲染、API 拉取、代理处理器到国际化落地的全过程。读完本文,你将能够独立为 Homepage 编写并注册一个可用的服务型 Widget,并理解其背后的安全白名单机制与数据校验流程。

Homepage 组件区块布局

上图展示了 Homepage Widget 组件各区块(Block)的布局结构,出自官方组件指南 component.md

一、Widget 在 Homepage 中的定位

在进入编码之前,先明确 Widget 的类型与适用场景。Homepage 共有两类 Widget(详见 docs/widgets/index.md):

  • 服务型 Widget(Service Widget):用于展示某个服务(通常是 Web 服务或 API)的状态,在 services.yaml 中定义。例如为 Plex、Uptime Kuma 等配置的监控面板即属此类。
  • 信息型 Widget(Info Widget):用于在页面头部展示系统或环境信息,在 widgets.yaml 中定义,例如 openmeteo 天气、系统资源占用等。

本文聚焦于服务型 Widget 的开发,其官方教程位于 tutorial.md。整个作者指南(Authoring)系列还包括:

子文档 主题 仓库路径
完整教程 从零搭建一个 Widget tutorial.md
组件指南 如何构建 Widget UI component.md
元数据指南 端点、映射、校验规则 metadata.md
API 指南 如何发起 API 请求 api.md
代理指南 内置与自定义代理处理器 proxies.md
国际化指南 翻译与本地化 translations.md

二、开发环境准备

开发 Widget 前,需要先把 Homepage 跑在本地。项目使用 pnpm 管理依赖(仓库根目录存在 pnpm-lock.yamlpnpm-workspace.yaml),详见 getting-started.md

pnpm install   # 安装依赖
pnpm dev       # 启动开发服务器,打开 http://localhost:3000

Homepage 是一个 Next.js 应用,因此 Widget 组件遵循 React/Next.js 的组件模型。代码质量方面还提供了:

pnpm lint         # ESLint 检查
pnpm test         # Vitest 单元/组件测试
pnpm test:coverage

三、核心教程:创建你的第一个 Widget

官方教程使用 yourwidget 作为占位名称(只能包含小写字母、不含空格),并假设目标 API 的 v1/info 端点返回如下 JSON:

{ "key1": 123, "key2": 456, "key3": 789 }

3.1 定义 Widget 元数据(widget.js)

src/widgets 目录下新建 yourwidget 文件夹,并创建 widget.js

import genericProxyHandler from "utils/proxy/handlers/generic";

const widget = {
  api: "{url}/{endpoint}",
  proxyHandler: genericProxyHandler,

  mappings: {
    info: {
      endpoint: "v1/info",
    },
  },
};

export default widget;

各字段含义如下:

  1. api:API 端点模板。{url}{endpoint} 是运行时占位符,分别替换为服务配置中的 url 和 mapping 中定义的 endpoint。这里最终会拼出 http://127.0.0.1:1337/v1/info
  2. proxyHandler:负责实际发起请求的代理处理器函数。genericProxyHandler 用于免认证请求,也可以选择其他内置处理器或自定义处理器(详见第七节)。
  3. mappings:端点映射对象,每个 key 对应一个端点。组件中通过 useWidgetAPI(widget, "info") 调用名为 info 的映射。
  4. endpoint:映射实际请求的端点路径。除 endpoint 外,映射还可携带 methodheadersbody 等属性(见第五节)。

安全警告:所有需要从动态端点拉取数据的 Widget,都必须声明 mappingsallowedEndpoints 属性,这是 Homepage 的白名单安全机制,防止 Widget 任意访问外部地址(详见第五节)。

3.2 创建组件(component.jsx)

src/widgets/yourwidget 目录下创建 component.jsx。先从导入依赖开始:

import { useTranslation } from "next-i18next"; // 访问翻译字符串

import Container from "components/services/widget/container"; // 组件容器
import Block from "components/services/widget/block"; // 键值对区块
import useWidgetAPI from "utils/proxy/use-widget-api"; // 数据获取 hook

随后逐步构建组件主体:

export default function Component({ service }) {
  const { t } = useTranslation();          // 翻译函数
  const { widget } = service;              // 从 service prop 中解构 widget 元数据
  const { data, error } = useWidgetAPI(widget, "info"); // 拉取数据

  // 出错时交给 Container 渲染错误信息
  if (error) {
    return <Container service={service} error={error} />;
  }

  // 无数据时渲染骨架屏(placeholder)
  if (!data) {
    return (
      <Container service={service}>
        <Block label="yourwidget.key1" />
        <Block label="yourwidget.key2" />
        <Block label="yourwidget.key3" />
      </Container>
    );
  }

  // 有数据时渲染真实值,并用 common.number 做数字本地化
  return (
    <Container service={service}>
      <Block label="yourwidget.key1" value={t("common.number", { value: data.key1 })} />
      <Block label="yourwidget.key2" value={t("common.number", { value: data.key2 })} />
      <Block label="yourwidget.key3" value={t("common.number", { value: data.key3 })} />
    </Container>
  );
}

关键点解析:

  • useWidgetAPI(widget, "info") 返回 { data, error }。调用名 info 必须与 widget.jsmappings 的 key 一致——mapping 名与真实端点常常相同,但无论如何都必须显式定义
  • Blocklabel 属性对应翻译 key;无 value 时显示骨架占位(Block 内部通过 value === undefined 触发 animate-pulse 动画,见 block.jsx)。
  • t("common.number", { value: ... }) 使用 Homepage 内置的数字格式化翻译,可以自动适配逗号/小数点的本地化习惯。

3.3 添加翻译字符串

Widget 中所有文本与数值内容都应国际化。编辑 public/locales/en/common.json,在列表末尾为你的 Widget 新增对象:

"yourwidget": {
  "key1": "Value 1",
  "key2": "Value 2",
  "key3": "Value 3"
}

即使你母语不是英语,也只需添加英文翻译;合并后再通过 Crowdin 补充其他语言(详见第八节)。

3.4 注册 Widget

要让 Homepage 认识新 Widget,需要修改两个注册文件(均需保持字母序):

src/widgets/widgets.js——注册元数据:

import yourwidget from "./yourwidget/widget";

const widgets = {
  // ...
  yourwidget: yourwidget,
  // ...
};

src/widgets/components.js——注册懒加载组件(使用 Next.js dynamic 导入):

const components = {
  // ...
  yourwidget: dynamic(() => import("./yourwidget/component")),
  // ...
};

这两处注册是 Widget 生效的必要步骤:前者让 services.yaml 中的 type: yourwidget 能解析到元数据,后者让组件能够被按需渲染。

3.5 在 services.yaml 中使用

编辑你的 services.yaml

- Services:
    - Your Widget:
        icon: yourwidget.svg
        href: https://example.com/
        widget:
          type: yourwidget
          url: http://127.0.0.1:1337

这里的 url 会替换 api 模板中的 {url},最终请求地址为 http://127.0.0.1:1337/v1/info。至此,一个自定义 Widget 已经可以运行在首页上。

四、组件构建原理(Component 深入)

Homepage 的 Widget 组件基于 React,官方提供了一系列 hook 与 UI 组件来保证布局一致。完整的组件示例与逐行拆解见 component.md

4.1 两个核心 Hook

  • useTranslation:来自 next-i18next,用于翻译文本与数值内容。所有文本和数值都应通过它本地化。
  • useWidgetAPI:负责经代理拉取数据。该 hook 是安全关键路径——所有数据都必须经由服务端代理,避免在浏览器端直接跨域请求(详见第六节)。

4.2 两个核心 UI 组件

<Container>:Widget 的外层包裹组件,提供统一布局。可接收 error prop 渲染错误态:

<Container service={service} error={error}></Container>

从源码 container.jsx 可以看到,Container 还承担了两项额外职责:

  • 支持通过 service.widget.fields 过滤可见区块(fields 数组可以是 "resources.cpu" 这类带 . 的通用翻译 key,也可以是 "widget_type.field" 形式);
  • 支持基于 blockHighlights 设置构建高亮配置,通过 BlockHighlightContext 传递给子 Block

<Block>:渲染一个键值对区块,接收 labelvalue props:

<Block label="yourwidget.key1" value={t("common.number", { value: data.key1 })} />

value 时渲染骨架占位布局:

<Container service={service}>
  <Block label="yourwidget.key1" />
  <Block label="yourwidget.key2" />
  <Block label="yourwidget.key3" />
</Container>

label 用于在翻译文件中查找翻译 key。从 block.jsx 源码可见,Block 还会读取高亮配置,对命中条件的值附加高亮样式类,实现阈值告警之类的视觉效果。

五、Widget 元数据(Metadata)全面参考

元数据是一个 JS 对象,包含 API 端点、代理处理器与端点映射等全部信息,是 Widget 的数据蓝图。以下属性参考 metadata.md

5.1 api

字符串,API 端点模板。运行时占位符会被实际值替换:{url} 替换为 Widget 配置中的 url{endpoint} 替换为 mapping 中的 endpoint 值:

const widgetExample = {
  api: "{url}/api/{endpoint}",
};

api-helpers.jsformatApiCall 实现可以看到,所有 {...} 占位符都会被替换,且 url 会先去除末尾斜杠。模板中的任意 key(如 {key}{username})都可以从 Widget 配置中取值。

5.2 proxyHandler

负责发起 API 请求的函数。Homepage 内置了多个开箱即用的处理器(位于 src/utils/proxy/handlers),详细对比见第七节。基本用法:

const widgetExample = {
  api: "{url}/api/{endpoint}",
  proxyHandler: genericProxyHandler,
};

5.3 mappings 与各子属性

mappings 是端点映射对象,可包含多个端点,各自独立配置。

const widgetExample = {
  api: "{url}/api/{endpoint}",
  mappings: {
    // 最终请求:/api/stats?start=...&end=...
    stats: {
      endpoint: "stats",
      validate: ["total", "average"],
      params: ["start", "end"],
    },
    // 最终请求:/api/notices
    notices: {
      endpoint: "notices",
      map: (data) => {
        total: asJson(data).length;
      },
    },
  },
};

各子属性详解:

endpoint

字符串,实际请求的端点名,用于替换 api 中的 {endpoint} 占位符。

validate

字符串数组,声明 API 响应中必须存在的 key。若响应缺失任一 key,Widget 不会渲染。其底层实现在 validate-widget-data.js:找到与 endpoint 匹配的 mapping 后,遍历 validate 数组检查 dataParsed[key] === undefined;校验失败会记录错误日志并返回 { error: { message: "Invalid data", ... } }。此外该文件还处理了 Buffer 数据的 JSON 解析(含去除空白字符后的二次尝试)。

mappings: {
  stats: {
    endpoint: "stats",
    validate: ["total", "average"],
  },
}

params

字符串数组,声明允许作为查询参数传给 API 的 key。这是白名单机制——未在 params 中声明的参数不会传给上游,防止任意参数注入。组件侧配合 useWidgetAPI 传入值:

const { data: statsData, error: statsError } = useWidgetAPI(widget, "stats", {
  start: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
  end: new Date(),
});

服务端在 proxy.js 中解析 req.query.query,仅拼接 mappingParams(以及 optionalParams 中确实传入了值的部分)到端点 URL。

map

函数,在 API 原始响应传给组件之前进行数据变换。配合 api-helpers.js 中的 asJsonjsonArrayFilter 等工具使用:

import { asJson } from "utils/proxy/api-helpers";

mappings: {
  notices: {
    endpoint: "notices",
    map: (data) => {
      total: asJson(data).length;
    },
  },
  warnings: {
    endpoint: "notices",
    map: (data) => {
      total: asJson(data).filter((alert) => alert.type === "warning").length;
    },
  },
}

注意 map 函数以 (data) => { total: ...; } 的对象字面量简写形式编写(即 (data) => ({ total: ... }))。map 在服务端代理处理中于校验通过后执行,见 generic.js

method

请求使用的 HTTP 方法,默认 GET注意POST 请求不允许通过 Widget API 直接发起,需要自定义代理处理器(见第七节)。mappings 中声明了 method 时,服务端 proxy.js 会校验请求方法一致性,并据此改写 req.methodreq.body

headers

对象,附加到请求的额外请求头:

mappings: {
  stats: {
    endpoint: "stats",
    headers: {
      "Content-Type": "application/json",
    },
  },
}

body

对象,请求体内容,仅在 methodPOST/PUT 时使用。典型场景是 GraphQL 查询:

mappings: {
  stats: {
    endpoint: "graphql",
    body: {
      query: `
        query {
          stats {
            total
            average
          }
        }
      `,
    },
    headers: {
      "Content-Type": "application/json",
    },
  },
}

5.4 allowedEndpoints

当端点校验规则简单到可以用正则表达时,可用 allowedEndpoints 替代 mappings

const widgetExample = {
  api: "{url}/api/{endpoint}",
  allowedEndpoints: /^stats|notices$/,
};

服务端 proxy.jsRegExp.test() 校验请求端点,不匹配则返回 403 "Unmapped proxy request."。仓库中已有多个实际案例,例如 glances/widget.js 使用 allowedEndpoints: /^\d+\/(quicklook|diskio|...)$/ 限制访问路径,gamedig/widget.jsfritzbox/widget.js 使用 /status/

安全提示(再次强调)mappingsallowedEndpoints 是动态端点 Widget 的必填项。Homepage 采用白名单策略,确保 Widget 只能访问被明确允许的端点。

六、通过 useWidgetAPI 发起 API 请求

useWidgetAPI 是组件与数据之间的桥梁,实现位于 use-widget-api.js。它接收三个参数:

参数 说明
widget Widget 元数据对象,通常从 service prop 传入
endpoint 要请求的端点名(即 metadata 中 mapping 的 key);省略时直接请求 api 定义的端点
params(可选) 传给 API 的查询参数对象,必须已在 metadata 的 params 中白名单化
import useWidgetAPI from "utils/proxy/use-widget-api";

export default function Component({ service }) {
  const { data, error } = useWidgetAPI(widget, "stats", { start: "2021-01-01", end: "2021-12-31" });
}

底层流程:

  1. formatProxyUrl(见 api-helpers.js)把调用构造成 /api/services/proxy?group=...&service=...&index=...&endpoint=...&query=...
  2. 该请求由 src/pages/api/services/proxy.js 的服务端 handler 接管,它查找 Widget 元数据、执行 mapping 分发(endpoint 重写、params 白名单过滤、headers 附加、map 变换);
  3. 最终由 proxyHandler 发起真实的上游请求并返回结果。

因为所有上游请求都发生在服务端代理内,Widget 组件本身从不直接触碰外部 API,这就是官方强调的"useWidgetAPI 必须走代理、对安全至关重要"的原因。该 hook 基于 swr 实现(支持 refreshInterval 配置),并统一把数据层的 error 提升为顶层返回,便于组件判断。

七、代理处理器(Proxy Handler)指南

Homepage 内置的代理处理器位于 src/utils/proxy/handlers 目录,每个处理器都附带对应测试文件(*.test.js)。

7.1 genericProxyHandler

发起免认证请求的通用处理器,是最常用的选择:

import genericProxyHandler from "utils/proxy/handlers/generic";

const widgetExample = {
  api: "{url}/api/{endpoint}",
  proxyHandler: genericProxyHandler,
};

它同样支持从 Widget 配置中传递 API key 等参数(通过 api 模板占位符注入):

// widget.js
const widgetExample = {
  api: "{url}/api/{endpoint}?key={key}",
  proxyHandler: genericProxyHandler,
};
# services.yaml
- Your Widget:
    icon: yourwidget.svg
    href: https://example.com/
    widget:
      type: yourwidget
      url: http://example.com
      key: your-api-key

generic.js 源码可见,该处理器还支持从配置读取 username/password 自动生成 Authorization: Basic ... 头,并支持 requestBody 配置;请求后若响应为非 2xx 状态,会返回带脱敏 URL(sanitizeErrorURL)的错误对象。

7.2 credentialedProxyHandler

通过请求头携带凭据的认证处理器。凭据从 Widget 配置中读取,默认将 key 作为 X-API-Key 头传递;若目标 API 需要其他头名,需要为 credentialedProxyHandler 增加分支或新建处理器:

import credentialedProxyHandler from "utils/proxy/handlers/credentialed";

const widgetExample = {
  api: "{url}/api/{endpoint}?key={key}",
  proxyHandler: credentialedProxyHandler,
};
- Your Widget:
    widget:
      type: yourwidget
      url: http://127.0.0.1:1337
      key: your-api-key

实际实现 credentialed.js 内部针对不同 Widget 类型分发不同的认证头:例如 coinmarketcap 使用 X-CMC_PRO_API_KEYgotify 使用 X-gotify-Key、大量 Widget(argocdauthentiklinkwardentailscalevikunja 等)使用 Bearer Token、proxmox 使用 PVEAPIToken=gitlab 使用 PRIVATE-TOKEN,未匹配类型则回落到 X-API-Key。这也是文档中"如需不同头名,请为该处理器增加 case 或新建处理器"的由来。

7.3 jsonrpcProxyHandler

用于发起带认证的 JSON-RPC 请求,支持用户名+密码或 API Token 两种认证方式。endpoint 即要调用的方法名,queryParams 即参数

// widget.js
import jsonrpcProxyHandler from "utils/proxy/handlers/jsonrpc";

const widgetExample = {
  api: "{url}/api/jsonrpc",
  proxyHandler: jsonrpcProxyHandler,

  mappings: {
    total: { endpoint: "total" },
    average: { endpoint: "average" },
    trigger: { endpoint: "trigger.get" },
  },
};

组件中调用:

const { data, error } = useWidgetAPI(widget, 'trigger', { "triggerids": "14062", "output": "extend", "selectFunctions": "extend" });

对应 services.yaml 配置(二选一):

- Your Widget:
    widget:
      type: yourwidget
      url: http://127.0.0.1:1337
      username: your-username
      password: your-password
- Your Widget:
    widget:
      type: yourwidget
      url: http://127.0.0.1:1337
      key: your-api-token

实现位于 jsonrpc.js,基于 json-rpc-2.0 客户端构建请求,有凭据时自动附加 BasicBearer 头,并把 JSON-RPC 错误对象规范化返回。

7.4 synologyProxyHandler

专用于 Synology DSM 服务的认证处理器,请求格式遵循 Synology WebAPI 约定:

import synologyProxyHandler from "utils/proxy/handlers/synology";

const widgetExample = {
  api: "{url}/webapi/{cgiPath}?api={apiName}&version={maxVersion}&method={apiMethod}",
  proxyHandler: synologyProxyHandler,

  mappings: {
    system_storage: {
      apiName: "SYNO.Core.System",
      apiMethod: 'info&type="storage"',
      endpoint: "system_storage",
    }
  },
};

7.5 创建自定义代理处理器

当内置处理器无法满足需求时(例如需要 POST、特殊签名或非标准认证),可以编写自定义处理器。代理处理器是一个函数,接收三个参数:

  • req:请求对象;
  • res:响应对象;
  • map:将 API 响应映射为 Widget 数据的函数。

函数应返回一个 resolve 到 API 响应的 Promise:

import createLogger from "utils/logger";
import { httpProxy } from "utils/proxy/http";

const logger = createLogger("customProxyHandler");

export default async function customProxyHandler(req, res, map) {
  const { url } = req.query;

  const [status, contentType, data] = await httpProxy(url);

  return res.status(status).send(data);
}

Widget 专属代理放在 src/widgets/<widget>/proxy.js,并在 metadata 中通过 proxyHandler 或 mapping 级 proxyHandler 引用。代理处理器是较复杂的主题,需要良好的 JS 功底和对 Homepage 代码库的理解;新手建议优先使用内置处理器。

7.6 测试代理处理器

代理处理器是回归问题的常见来源(涉及认证、请求格式化、上游 API 的怪癖行为),新增或修改时必须配套测试,重点覆盖:

  • 请求构造:正确的 URL/路径、查询参数、请求头与认证信息,且秘密信息不会被意外记入日志;
  • 响应映射:Widget/组件期望的载荷结构(包括可选/缺失字段);
  • 错误处理:上游非 200、非法 JSON、超时、意外载荷都应产生可预期结果。

测试位置约定:

  • 共享处理器位于 src/utils/proxy/handlers,测试文件同名(如 generic.test.js);
  • Widget 专属代理位于 src/widgets/<widget>/proxy.js,测试文件为 src/widgets/<widget>/proxy.test.js

八、国际化与本地化(Translations)

Widget 内所有文本与数值内容都应翻译并本地化。英文为默认语言,其他语言通过 Crowdin 协作平台维护。Homepage 基于 next-i18next 实现翻译,并扩展了数字、日期等格式化能力,详见 translations.md

在组件中使用:

import { useTranslation } from "next-i18next";

export default function Component() {
  const { t } = useTranslation();

  return (
    <Container service={service}>
      <Block label="yourwidget.key1" />
      <Block label="yourwidget.key2" />
      <Block label="yourwidget.key3" />
    </Container>
  );
}

翻译字符串添加在 public/locales/en/common.json 中。

8.1 常用数字格式化翻译

Homepage 提供一组开箱即用的通用翻译 key(实际定义于 public/locales/en/common.json,例如 "bytes": "{{value, bytes}}""number": "{{value, number}}"):

Key 示例输出 说明
common.bytes 1,000 B 字节数格式化
common.bits 1,000 bit 位数格式化
common.bbytes 1 KiB 二进制字节
common.bbits 1 Kibit 二进制位
common.byterate 1,000 B/s 字节速率
common.bibyterate 1 KiB/s 二进制字节速率
common.bitrate 1,000 bit/s 位速率
common.bibitrate 1 Kibit/s 二进制位速率
common.percent 50% 百分比
common.number 1,000 数字格式化
common.ms 1,000 ms 毫秒
common.date 2024-01-01 日期
common.relativeDate 1 day ago 相对日期
common.duration 1 day, 1 hour 时长

用法:t("common.bytes", { value: 1024 })。这些 key 配合 i18next 的格式化机制,可自动适配不同地区的数字分隔符。

8.2 常用文本翻译

Key 翻译 说明
resources.cpu CPU CPU 使用率
resources.mem MEM 内存使用率
resources.total Total 总量
resources.free Free 空闲量
resources.used Used 已用量
resources.load Load 负载值
resources.temp TEMP 温度值
resources.max Max 最大值
resources.uptime UP 运行时间

九、测试规范与质量保障

Homepage 使用 Vitest 做单元与组件测试,Widget 开发的测试约定(来自 getting-started.md):

  • 组件测试:新增/修改 Widget 应在组件旁提供 component.test.jsx,覆盖加载/占位状态、错误状态和代表性"快乐路径"渲染;
  • 元数据测试:改动 src/widgets/<widget>/widget.js 时,同步更新 widget.test.js 覆盖配置与映射行为;
  • 代理测试:有自定义代理时提供 proxy.test.js,验证请求构造、响应映射与错误路径;
  • 目录注意:测试文件不要放在 src/pages/**(Next.js 会把其中的文件当作路由),页面测试应放在 src/__tests__/pages/**。仓库现有测试即遵循此约定,如 src/tests/pages/api/services/proxy.test.js 中对 allowedEndpoints 正则匹配的验证。

十、开发新 Widget 的社区准则

为了保证 Widget 生态的协调性与可维护性,官方在 getting-started.md 中对新 Widget 提出了明确的开发规范:

  • 以自托管开源软件为优先:Homepage 定位是自托管服务仪表盘,Widget 应主要服务于自托管工具;
  • 提交门槛:服务型 Widget 通常需要对应 feature request 讨论获得至少 20 个 up-vote,且拒绝为过年轻(如 <1 年)或影响力过小(如低 Star 数)的项目维护 Widget;
  • 布局约束:Widget 只能是一行 Block,宽度不超过 4 个 Block,并遵循其他 Widget 的样式与设计选择;
  • 性能约束:尽量减少 API 调用次数;除非绝对必要,避免使用自定义代理;Widget 应保持只读(不得通过 API 进行写入变更);不允许用户手动覆盖"刷新间隔"设置,以防误配置导致性能问题。

遵循这些约束,既能保证 Widget 与 Homepage 整体风格一致,也能避免给上游服务与页面带来不必要的负担。

结语

widget.js 元数据、component.jsx 组件、两处注册入口,到 useWidgetAPI 数据拉取、内置代理处理器与国际化落地,本文完整覆盖了 Homepage 自定义 Widget 的全部开发环节。官方完整流程可对照 tutorial.md,属性细节可查阅 metadata.md,代理进阶可阅读 proxies.md。掌握了这套框架,你就能为任意自托管服务打造专属的状态面板,让 Homepage 真正成为"你的"首页。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
docsdocs
暂无描述
Markdown
899
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
925
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
601
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.03 K
525
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
395