--- sidebar_position: 17 title: "扩展 Dashboard" description: "为 Hermes Web Dashboard 构建主题和插件——调色板、字体排版、布局、自定义标签页、shell 插槽、页面级插槽以及后端 API 路由" --- # 扩展 Dashboard Hermes Web Dashboard(`hermes dashboard`)在设计上支持换肤和扩展,无需 fork 代码库。对外暴露三个层次: 1. **主题(Themes)** — YAML 文件,用于重绘 dashboard 的调色板、字体排版、布局以及各组件的外观。将文件放入 `~/.hermes/dashboard-themes/`,即可在主题切换器中看到它。 2. **UI 插件(UI plugins)** — 一个包含 `manifest.json` 和 JavaScript bundle 的目录,可注册标签页、替换内置页面、通过页面级插槽增强内置页面,或向命名 shell 插槽注入组件。 3. **后端插件(Backend plugins)** — 插件目录内的 Python 文件,暴露一个 FastAPI `router`;路由挂载在 `/api/plugins//` 下,由插件的 UI 调用。 三者均为**运行时即插即用**:无需克隆仓库、无需 `npm run build`、无需修改 dashboard 源码。本页是三者的权威参考文档。 如果只是想使用 dashboard,请参阅 [Web Dashboard](./web-dashboard)。如果想为终端 CLI(而非 Web Dashboard)换肤,请参阅 [Skins & Themes](./skins) —— CLI 皮肤系统与 dashboard 主题无关。 :::note 各部分如何组合 主题和插件相互独立,但可协同工作。主题可以单独使用(仅一个 YAML 文件)。插件也可以单独使用(仅一个标签页)。两者结合可构建带有自定义 HUD 的完整视觉换肤方案——内置的 `strike-freedom-cockpit` 演示正是如此。参见[主题 + 插件组合演示](#combined-theme--plugin-demo)。 ::: --- ## 目录 - [主题](#themes) - [快速上手——你的第一个主题](#quick-start--your-first-theme) - [调色板、字体排版、布局](#palette-typography-layout) - [布局变体](#layout-variants) - [主题资源(图片作为 CSS 变量)](#theme-assets-images-as-css-vars) - [组件外观覆盖](#component-chrome-overrides) - [颜色覆盖](#color-overrides) - [原始 `customCSS`](#raw-customcss) - [内置主题](#built-in-themes) - [完整主题 YAML 参考](#full-theme-yaml-reference) - [插件](#plugins) - [快速上手——你的第一个插件](#quick-start--your-first-plugin) - [目录结构](#directory-layout) - [Manifest 参考](#manifest-reference) - [Plugin SDK](#the-plugin-sdk) - [Shell 插槽](#shell-slots) - [替换内置页面(`tab.override`)](#replacing-built-in-pages-taboverride) - [增强内置页面(页面级插槽)](#augmenting-built-in-pages-page-scoped-slots) - [仅插槽插件(`tab.hidden`)](#slot-only-plugins-tabhidden) - [后端 API 路由](#backend-api-routes) - [插件自定义 CSS](#custom-css-per-plugin) - [插件发现与重载](#plugin-discovery--reload) - [主题 + 插件组合演示](#combined-theme--plugin-demo) - [API 参考](#api-reference) - [故障排查](#troubleshooting) --- ## 主题 主题是存储在 `~/.hermes/dashboard-themes/` 中的 YAML 文件。文件名无关紧要(系统使用主题的 `name:` 字段),但惯例是 `.yaml`。所有字段均为可选——缺失的键会回退到内置的 `default` 主题,因此一个主题可以只包含一个颜色。 ### 快速上手——你的第一个主题 ```bash mkdir -p ~/.hermes/dashboard-themes ``` ```yaml # ~/.hermes/dashboard-themes/neon.yaml name: neon label: Neon description: Pure magenta on black palette: background: "#000000" midground: "#ff00ff" ``` 刷新 dashboard。点击顶栏的调色板图标,选择 **Neon**。背景变为黑色,文字和强调色变为洋红色,所有派生颜色(card、border、muted、ring 等)均通过 CSS 的 `color-mix()` 从这两个颜色自动计算得出。 这就是全部入门流程:一个文件,两个颜色。以下内容均为可选的进阶配置。 ### 调色板、字体排版、布局 这三个块是主题的核心。每个块相互独立——覆盖其中一个,其余保持不变。 #### 调色板(3 层) 调色板由三层颜色加一个暖光晕(warm-glow)颜色和一个噪点颗粒倍增器组成。Dashboard 的设计系统级联通过 CSS `color-mix()` 从这三层颜色派生出所有兼容 shadcn 的 token(card、popover、muted、border、primary、destructive、ring 等)。覆盖三个颜色即可级联影响整个 UI。 | 键 | 描述 | |-----|-------------| | `palette.background` | 最深的画布颜色——通常接近黑色。驱动页面背景和卡片填充。 | | `palette.midground` | 主要文字和强调色。大多数 UI 外观读取此值(前景文字、按钮轮廓、焦点环)。 | | `palette.foreground` | 顶层高亮色。默认主题将其设为 alpha 为 0 的白色(不可见);需要顶层亮色强调的主题可提高其 alpha 值。 | | `palette.warmGlow` | `rgba(...)` 字符串,用作 `` 的晕光颜色。 | | `palette.noiseOpacity` | 0–1.2 的颗粒叠加层倍增器。越低越柔和,越高越粗粝。 | 每层接受 `{hex: "#RRGGBB", alpha: 0.0–1.0}` 或裸十六进制字符串(alpha 默认为 1.0)。 ```yaml palette: background: hex: "#05091a" alpha: 1.0 midground: "#d8f0ff" # bare hex, alpha = 1.0 foreground: hex: "#ffffff" alpha: 0 # invisible top layer warmGlow: "rgba(255, 199, 55, 0.24)" noiseOpacity: 0.7 ``` #### 字体排版 | 键 | 类型 | 描述 | |-----|------|-------------| | `fontSans` | string | 正文的 CSS font-family 栈(应用于 `html`、`body`)。 | | `fontMono` | string | 代码块、``、`.font-mono` 工具类的 CSS font-family 栈。 | | `fontDisplay` | string | 可选的标题/展示字体栈。回退到 `fontSans`。 | | `fontUrl` | string | 可选的外部样式表 URL。在主题切换时以 `` 注入 ``。相同 URL 不会重复注入。支持 Google Fonts、Bunny Fonts、自托管 `@font-face` 样式表——任何可链接的资源均可。 | | `baseSize` | string | 根字体大小——控制 rem 比例。例如 `"14px"`、`"16px"`。 | | `lineHeight` | string | 默认行高。例如 `"1.5"`、`"1.65"`。 | | `letterSpacing` | string | 默认字间距。例如 `"0"`、`"0.01em"`、`"-0.01em"`。 | ```yaml typography: fontSans: '"Orbitron", "Eurostile", "Impact", sans-serif' fontMono: '"Share Tech Mono", ui-monospace, monospace' fontDisplay: '"Orbitron", "Eurostile", sans-serif' fontUrl: "https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;600;700&family=Share+Tech+Mono&display=swap" baseSize: "14px" lineHeight: "1.5" letterSpacing: "0.04em" ``` #### 布局 | 键 | 值 | 描述 | |-----|--------|-------------| | `radius` | 任意 CSS 长度(`"0"`、`"0.25rem"`、`"0.5rem"`、`"1rem"` 等) | 圆角 token。映射到 `--radius` 并级联到 `--radius-sm/md/lg/xl`——所有圆角元素同步变化。 | | `density` | `compact` \| `comfortable` \| `spacious` | 间距倍增器,以 `--spacing-mul` CSS 变量形式应用。`compact = 0.85×`,`comfortable = 1.0×`(默认),`spacious = 1.2×`。缩放 Tailwind 的基础间距,因此 padding、gap 和 space-between 工具类均按比例调整。 | ```yaml layout: radius: "0" density: compact ``` ### 布局变体 `layoutVariant` 选择整体 shell 布局。缺省时默认为 `"standard"`。 | 变体 | 行为 | |---------|-----------| | `standard` | 单列,最大宽度 1600px(默认)。 | | `cockpit` | 左侧边栏轨道(260px)+ 主内容区。由插件通过 `sidebar` 插槽填充——参见 [Shell 插槽](#shell-slots)。没有插件时轨道显示占位符。 | | `tiled` | 取消最大宽度限制,页面可使用完整视口宽度。 | ```yaml layoutVariant: cockpit ``` 当前变体通过 `document.documentElement.dataset.layoutVariant` 暴露,因此 `customCSS` 中的原始 CSS 可通过 `:root[data-layout-variant="cockpit"] ...` 定向匹配。 ### 主题资源(图片作为 CSS 变量) 随主题附带图片 URL。每个命名插槽会成为一个 CSS 变量(`--theme-asset-`),内置 shell 和任何插件均可读取。`bg` 插槽自动接入 backdrop;其他插槽面向插件开放。 ```yaml assets: bg: "https://example.com/hero-bg.jpg" # auto-wired into hero: "/my-images/strike-freedom.png" # for plugin sidebars crest: "/my-images/crest.svg" # for header-left plugins logo: "/my-images/logo.png" sidebar: "/my-images/rail.png" header: "/my-images/header-art.png" custom: scanLines: "/my-images/scanlines.png" # → --theme-asset-custom-scanLines ``` 值接受: - 裸 URL——自动包装为 `url(...)`。 - 已包装的 `url(...)`、`linear-gradient(...)`、`radial-gradient(...)` 表达式——直接使用。 - `"none"` ——明确禁用。 每个资源还会以 `--theme-asset--raw`(未包装的 URL)形式输出,以便插件需要将其传给 `` 而非 `background-image` 时使用。 插件通过普通 CSS 或 JS 读取这些变量: ```javascript // In a plugin slot const hero = getComputedStyle(document.documentElement) .getPropertyValue("--theme-asset-hero").trim(); ``` ### 组件外观覆盖 `componentStyles` 可在不编写 CSS 选择器的情况下重新设置各 shell 组件的样式。每个桶(bucket)的条目会成为 CSS 变量(`--component--`),shell 的共享组件会读取这些变量。因此 `card:` 的覆盖应用于所有 ``,`header:` 应用于应用栏,以此类推。 ```yaml componentStyles: card: clipPath: "polygon(12px 0, 100% 0, 100% calc(100% - 12px), calc(100% - 12px) 100%, 0 100%, 0 12px)" background: "linear-gradient(180deg, rgba(10, 22, 52, 0.85), rgba(5, 9, 26, 0.92))" boxShadow: "inset 0 0 0 1px rgba(64, 200, 255, 0.28)" header: background: "linear-gradient(180deg, rgba(16, 32, 72, 0.95), rgba(5, 9, 26, 0.9))" tab: clipPath: "polygon(6px 0, 100% 0, calc(100% - 6px) 100%, 0 100%)" sidebar: {} backdrop: {} footer: {} progress: {} badge: {} page: {} ``` 支持的桶:`card`、`header`、`footer`、`sidebar`、`tab`、`progress`、`badge`、`backdrop`、`page`。 属性名使用 camelCase(`clipPath`),输出为 kebab-case(`clip-path`)。值为纯 CSS 字符串——CSS 接受的任何内容均可(`clip-path`、`border-image`、`background`、`box-shadow`、`animation` 等)。 ### 颜色覆盖 大多数主题不需要此功能——3 层调色板已派生出所有 shadcn token。当你需要派生无法产生的特定强调色时(例如柔和主题的更柔和的破坏性红色,或品牌专属的成功绿色),才使用 `colorOverrides`。 ```yaml colorOverrides: primary: "#ffce3a" primaryForeground: "#05091a" accent: "#3fd3ff" ring: "#3fd3ff" destructive: "#ff3a5e" border: "rgba(64, 200, 255, 0.28)" ``` 支持的键:`card`、`cardForeground`、`popover`、`popoverForeground`、`primary`、`primaryForeground`、`secondary`、`secondaryForeground`、`muted`、`mutedForeground`、`accent`、`accentForeground`、`destructive`、`destructiveForeground`、`success`、`warning`、`border`、`input`、`ring`。 每个键与 `--color-` CSS 变量一一对应(例如 `primaryForeground` → `--color-primary-foreground`)。此处设置的任何键仅对当前激活主题生效,切换到其他主题时覆盖会被清除。 ### 原始 `customCSS` 对于 `componentStyles` 无法表达的选择器级外观——伪元素、动画、媒体查询、主题范围内的覆盖——可将原始 CSS 写入 `customCSS`: ```yaml customCSS: | /* Scanline overlay — only visible when cockpit variant is active. */ :root[data-layout-variant="cockpit"] body::before { content: ""; position: fixed; inset: 0; pointer-events: none; z-index: 100; background: repeating-linear-gradient(to bottom, transparent 0px, transparent 2px, rgba(64, 200, 255, 0.035) 3px, rgba(64, 200, 255, 0.035) 4px); mix-blend-mode: screen; } ``` CSS 在主题应用时以单个带作用域的 `