dsh-subpages:在 DSH 同一端口挂载独立看板与统一门户

前言

在 DSH 上跑 Harness 时,常会额外做几个小网页:待办汇总、用量统计、内部工具面板。常见做法是再起一个端口、单独配域名,或再写一套登录。DSH 客户端本身也没有可收藏、可深链的前端路由,刷新后状态容易丢。

dsh-subpages(维护者 SJTUMalPan)是 DSH 的客户端插件:复用 DSH webServer 上的前缀路由(默认 /subpages),提供网关与统一门户,把独立仓库里开发的下级页面挂进来。请求先过 DSH 既有鉴权,不必为新页面单独开端口、单独暴露面。

这是什么

一句话:DSH 看板宿主——把独立开发的小网页应用变成 DSH 里的「看板」,共用 DSH 端口与登录态,用一个门户统一进入。

核心能力

网关与门户

浏览器访问 DSH 端口(例如 :3080)下的 /subpages/ 进入门户壳:导航栏、iframe、hash 路由。公共前端在 /subpages/_assets/*(含 subpage.css);已挂载清单由 /subpages/_api/pages 提供。每个 /subpages/<id>/* 请求经 DSH 鉴权(connection.requestRejection)后再转发到看板后端或静态目录。

浏览器 ──> DSH :3080 /subpages/            门户壳(导航栏 + iframe + hash 路由)
                   /subpages/_assets/*      公共前端库(subpage.css)
                   /subpages/_api/pages     已挂载清单(壳用)
                   /subpages/<id>/*   ──►   看板自己的 http 服务(127.0.0.1:<port>)

门户 URL 如 /subpages/notify-hub/todos 可收藏、可深链、刷新不丢——这是相对 DSH 客户端自身路由的补足。

两种看板形态

  1. 代理形态:subpage.json 里 target 为 http://…,宿主反向代理到本机服务;可选 health 探活(默认 /healthz)。
  2. 静态形态:target 为目录路径,宿主直接投递文件,并对 ../、符号链接做 realpath 边界校验。

样式策略:style 为 inherit(默认,宿主在导航类 HTML 里注入公共样式)或 standalone(不注入)。静态页可自引:

<link rel="stylesheet" href="/subpages/_assets/subpage.css">

公共样式基于 Pico 2 classless + 深色 token + sp-* 组件类(.sp-page、.sp-card 等)。

挂载方式

目录扫描:在插件包内 pages/<id>/ 下放 subpage.json;也可通过 submodule 指向独立看板仓库(git submodule add <看板仓库> pages/<id>,清单放在看板仓库根目录,详见仓库内 pages/README.md)。重启 dsh web 即可,不必改宿主代码。

运行时注册(供其它 DSH 插件):

await ctx.subPages.register({ id: 'x', title: 'X', target: 'http://127.0.0.1:9000' })
ctx.subPages.list()
ctx.subPages.unregister('x')
ctx.subPages.mountPath     // '/subpages'

下游鉴权(令牌不下发)

可选 auth:从 tokenEnv 或 tokenFile + tokenPath 读令牌,以 header/scheme 或 queryParam 注入转发;浏览器侧的 ?token= 会在转发前剥掉。宿主还可代持下游会话 cookie,浏览器既看不到令牌,也拿不到下游 cookie。令牌不会出现在清单 JSON、HTML 或可见 URL 中(仓库含对应测试)。

自带示例看板

pages/ 下两个 submodule 分别演示代理 + standalone 与静态 + 独立样式:

看板 仓库 形态
待办 notify-hub 代理
用量 dsh-usage-stats 静态单文件 HTML

可选:启动通告

配置 startupNotice 后,DSH 每次启动可把带 token 的访问链接投递到 notify-hub(再转发飞书等)。须显式 enabled: true 并配置 publicHost;token 从启动日志新增横幅读取,未配 publicHost 则不投递。

安装与启用

安装命令以 README 为准:

# 本机开发(符号链接,改完即生效)
dsh plugin --profile web add "$PWD"     # 在仓库根目录执行

# 从 GitHub 装(插件市场也可直接装)
dsh plugin --profile web add github:SJTUMalPan/dsh-subpages

# 锁 commit(可复现)
dsh plugin --profile web add "git+https://github.com/SJTUMalPan/dsh-subpages#<sha>"

装完重启 dsh web。侧边栏底部会出现「看板」按钮。

卸载:

dsh plugin --profile web remove dsh-subpages

典型用法:挂载一个代理看板

在 pages/ 下建目录并放置 subpage.json(字段以 README 表格为准):

{
  "id": "notify-hub",
  "title": "待办",
  "icon": "inbox",
  "order": 10,
  "target": "http://127.0.0.1:8000",
  "health": "/healthz",
  "style": "standalone",
  "auth": {
    "tokenFile": "~/notify-hub-run/config.yaml",
    "tokenPath": "server.auth_token",
    "queryParam": "token"
  }
}

target 必填;id 缺省用目录名,须 kebab-case。下游服务需在本机监听对应端口并已启动,否则门户会显示「看板离线」。

profile 中可改插件 config(或 cordis.patch.yml):

- id: subpages
  config:
    mountPath: /subpages
    pagesDirs: [/abs/extra]
    requestTimeoutMs: 15000
    probeTimeoutMs: 2000

适用场景与注意

适合:已有或计划用独立仓库维护小面板,希望与 DSH Web 同端口、同登录;需要真 URL 深链;希望静态页与门户风格统一且零额外设计系统。

信任模型(挂载前必读):挂一个看板等于让它的前端在 DSH 同源下运行。门户 iframe 使用 allow-same-origin allow-scripts,看板脚本可访问 DSH 的 Cookie、存储与 HTTP 接口,并可操作父窗口 DOM。只挂自己信任、自己审计过的看板;第三方代码应部署在独立源再以跨源方式接入,本插件不做跨源隔离。

进程与权限:插件以当前 DSH 进程权限运行;安装前请阅读源码与 MIT 许可证,并理解 auth、转发头白名单(浏览器 cookie/authorization 不会透传给下游)及「同一看板共用进程级下游会话」的语义。

排障速查:无「看板」按钮 → 确认 profile 已装插件且重启过 dsh web;「还没有挂载任何看板」→ 检查 pages/ 与 subpage.json;链接跳出网关 404 → 下游勿用绝对 URL 重定向;401 → 刷新 DSH 页面重新登录。

结尾

dsh-subpages 把「独立小应用」收成 DSH 内的看板:不新开监听端口、清单与 submodule 解耦宿主代码、网关补上前端深链,并可选把启动链接推到 notify-hub。DeepSeek Harness 生态强调「一切皆插件」;社区目录 SkillHub 与 GitHub 仓库并列,便于检索与安装,与 DeepSeek / 幻方无官方从属关系。

  • 目录页:https://www.skillhub.cn/plugins/SJTUMalPan/dsh-subpages
  • GitHub:https://github.com/SJTUMalPan/dsh-subpages
羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜