@hyzyn/dsh-docker 0.9.5 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +157 -31
- package/README.md +146 -30
- package/client-src/docker.css +343 -160
- package/client-src/elevation.js +163 -0
- package/client-src/index.js +563 -119
- package/client.js +367 -183
- package/lib/audit.d.ts +106 -0
- package/lib/audit.js +140 -0
- package/lib/audit.js.map +1 -0
- package/lib/docker.d.ts +32 -3
- package/lib/docker.js +43 -7
- package/lib/docker.js.map +1 -1
- package/lib/index.d.ts +53 -0
- package/lib/index.js +473 -25
- package/lib/index.js.map +1 -1
- package/lib/ssh-exec.d.ts +9 -0
- package/lib/ssh-exec.js +149 -16
- package/lib/ssh-exec.js.map +1 -1
- package/package.json +2 -2
- package/scripts/client-smoke.mjs +394 -0
- package/scripts/log-perf.mjs +9 -2
- package/scripts/route-smoke.mjs +216 -5
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
- **常驻会话右侧栏**:面板作为会话右侧栏标签(`sidebar.right.pane.tab`)与对话**同屏**——发完错误交给 Agent 后日志留在右边,不影响看它干活;折叠即退出视野、不占屏。面板级状态(目标 / 视图 / 过滤词 / 选中容器及它的页签)**跨会话保留**,而折叠会主动断流、把 SSH 通道还回去。拿不到右侧栏服务的老宿主自动退回原有的 dock / 模态,行为与旧版一致。
|
|
10
10
|
- **日志右键交给 Agent**:日志页拖选报错行 → 右键「直接发送到当前会话 / 填入输入框,我先改改」,把「选中的行 + 目标 / 容器 / 时间窗 + 前后各 20 行上下文」交给会话(内容会进模型上下文,菜单里明写了留意凭证)。投递成功有两处回执:**视口级 toast**(挂 `body`,盖过面板与终端弹窗),以及——终端面板正开着时——**自动折起终端**(`ttyPanel` 契约 v2 的 `minimize()`;会话继续跑,恢复靠侧边栏「终端」入口的徽标),让会话直接露出来。老版本 tty 没有这个能力时退化成 toast 里「会话在面板后面」的指引。需要改稿就先「填入输入框」——**编辑面只有会话输入框一个**(它多行、能看到完整上下文,Agent 收到的就是它),不再另开一张更弱的卡片编辑器。目标会话的定位跨宿主版本:≤0.1.5 读 `sessions.list` 快照的 `current`,**0.1.6 起**该字段随「视图选中项」一起搬出了会话域,改看 `retainedBy.mainView > 0`(官方 `dsh-client-ui-session` 与本地 codegraph 的同一判据)——只认老字段会让菜单判成「当前没有打开的会话」而整组置灰;同一份取值也驱动「切会话把容器标签带过去」。
|
|
11
|
+
- **显式一键连接本机**:两个**非常驻**入口——一个目标都没有时是「还没有配置 Docker 目标」空态里的主按钮,已有别的目标时是**目标下拉的末一项** `+ 连接本机`(工具条上**不挂**:常驻时它既占位又招人点,纯噪音)——一次点击(或选中该项)就把宿主所在机器加成本机目标并选中(已有本机目标则直接复用,不重复创建;SSH / 自定义目标原样保留),agent 侧同一条操作是 `docker_connect_local`。它**不**改空 `targets` 的语义、**不**凭空造隐式默认目标、**不**自动装 Docker / 起 daemon / 切 docker context,也不需要任何能力授权(只加一条指向插件自身的本机目标)。
|
|
11
12
|
- **多目标聚合取数**:总览页对全部 `targets[]` 并行请求,单个目标不可达只污染自己那一格;agent 侧同一口径由 `docker_ps target:"*"` / `docker_attention target:"*"` 暴露,跨目标不互相阻塞。
|
|
12
13
|
- **「需关注」读权威字段**:不健康 / 反复重启 / OOM 被杀 / 非零退出 / 僵死;OOM 与真实退出码由一次 `docker inspect` 补齐——`docker ps` 摘要里的 137 分不出 OOM 与手动 kill,只按摘要筛必然误报。
|
|
13
14
|
- **四条 SSE 长流共用一套基建**:日志 FOLLOW、`docker stats`、`docker events`、`docker pull` 走同一个 `openSseStream`(心跳 / 活跃流登记 / 断开清理),差异只在收尾语义——日志与拉取自然结束,统计与事件由前端主动断。多选聚合日志按 `--timestamps` 前缀还原跨容器真实时序,「暂停」只冻结渲染(流继续接收,恢复时一次性补齐)。
|
|
@@ -172,6 +173,13 @@ add。装完重启 `dsh web`,侧边栏出现「容器」入口;设置 →
|
|
|
172
173
|
health_status / destroy / rename / update):`exec_*`、`archive-path`(`docker cp`)
|
|
173
174
|
这类噪音在服务端就丢掉了——实测一台跑批机器 24 小时 47 条事件全是 exec,白名单
|
|
174
175
|
命中 0,所以只被 exec 的机器上活动条是空的,这是刻意的。
|
|
176
|
+
断开后**要手动重连**:这条流进了 `closed` 就是终止态(EventSource 只在连接层抖动
|
|
177
|
+
时自愈;`docker events` 进程退出、服务端发 `end` 帧都会走 close),活动条上会出现
|
|
178
|
+
一个 **重连** 按钮(只在 `closed` 时出现;`unsupported` 环境不给——重连必然再失败),
|
|
179
|
+
工具条的 ⟳ 也一并重连。**AUTO REFRESH 轮询刻意不重连**:塞进轮询会让一条已终止的流
|
|
180
|
+
被不断重开,既掩盖「它断了」,又白建 SSE。重连成功后补一次全量列表刷新(断线期间
|
|
181
|
+
列表可能已经落后),daemon 全程没起、流从未连上过的情形同样补——否则活动条绿了而
|
|
182
|
+
列表还停在错误态,看着像好了其实没好。
|
|
175
183
|
- **聚合选择(临时多选聚合日志)**:工具条的 `聚合选择` 进入选择态——每张卡片
|
|
176
184
|
左侧出现勾选框,点卡片本体变成**勾选 / 取消**(不再进详情;动作条暂时收起,
|
|
177
185
|
免得一边多选一边误点启停删),工具条与列表之间出现操作条:「已选 N 个容器」+
|
|
@@ -347,6 +355,61 @@ add。装完重启 `dsh web`,侧边栏出现「容器」入口;设置 →
|
|
|
347
355
|
`permission denied while trying to connect to the Docker daemon socket`
|
|
348
356
|
之类的错误。
|
|
349
357
|
|
|
358
|
+
#### 连接本机(一键)
|
|
359
|
+
|
|
360
|
+
不想手填目标时,UI 里有两个入口,都**不做常驻**:
|
|
361
|
+
|
|
362
|
+
| 何时 | 入口在哪 |
|
|
363
|
+
| --- | --- |
|
|
364
|
+
| 一个目标都没有 | 面板正文的空态里一个 **连接本机** 主按钮(那时整块是空的,一键就是最短路径) |
|
|
365
|
+
| 已经有别的目标 | **目标下拉的末一项** `+ 连接本机`——就在你找机器的地方,点开才出现 |
|
|
366
|
+
|
|
367
|
+
两处互斥(不会同一个入口出现两次):没有目标时走空态那个大按钮;有目标时下拉里才有那一项。
|
|
368
|
+
**已经有本机目标时两处都不出现**(选择器里已经有那台本机了,再列一项等于重复)。
|
|
369
|
+
|
|
370
|
+
agent 侧同一件事是 `docker_connect_local` 工具,HTTP 侧是 `POST /connect-local`——
|
|
371
|
+
所有入口共用宿主里同一份实现。
|
|
372
|
+
|
|
373
|
+
为什么不做成常驻的工具条按钮:常驻一个「连接本机」既占掉目标选择器右边的位置,又要靠
|
|
374
|
+
强调色招人点,而**已经有可用目标**时它是纯噪音(第一版就是常驻的,用户当场指出多余)。
|
|
375
|
+
但只留空态那一个入口又会漏掉「手上有几台 SSH、想再加一台本机」这种常见情况——所以
|
|
376
|
+
第二个入口放进下拉,只有在真的去找机器时才出现。
|
|
377
|
+
|
|
378
|
+
| 情况 | 行为 |
|
|
379
|
+
| --- | --- |
|
|
380
|
+
| 已有一条 `kind=local` 目标(无论叫什么名字) | **复用它**(`created:false`),只切过去;不新建、不重写、不改名 |
|
|
381
|
+
| 没有本机目标 | 用稳定名 `local` 追加到列表末尾;`local` 已被别的目标占用时依次试 `local-2`、`local-3`…(确定性、不冲突、不覆盖) |
|
|
382
|
+
| 已有 SSH / 自定义目标 | **原样保留**(含顺序与凭据);只在末尾追加本机那条 |
|
|
383
|
+
| 重复点 / 并发点 | 幂等:最终只有一条本机目标 |
|
|
384
|
+
| 设置存储不可用 / 保存失败 | 明确报错(503 / 500 + 原因),**不改内存配置**——不会出现「界面上加上了、刷新就没了」 |
|
|
385
|
+
|
|
386
|
+
写盘成功之后**顺带做一次只读探测**(`docker version`),结果放进响应的 `message`:
|
|
387
|
+
daemon 可达时给出服务端版本;不可达时把成因说清楚——**docker CLI 缺失**(去装 Docker,
|
|
388
|
+
或把「docker CLI」改成正确路径)/ **探测超时**(daemon 卡死,或 `docker context` 指向了
|
|
389
|
+
连不上的远端;用 `docker context ls` 看当前指向)/ **daemon 未启动**(Linux
|
|
390
|
+
`systemctl start docker`,macOS / Windows 启动 Docker Desktop)/ **socket 无权访问**
|
|
391
|
+
(账号加入 `docker` 组),而不是只回一句「没有容器」。探测失败**不算这次操作失败**
|
|
392
|
+
(目标确实加上了,`reachable:false` 单独表示),下次 daemon 起来就能直接用。
|
|
393
|
+
|
|
394
|
+
探测用的二进制**就是设置卡片里的「docker CLI」**(`dockerBin`,默认 `docker`)。
|
|
395
|
+
CLI 缺失时会**只读地**在 `PATH` 里找一下别的容器 CLI(`docker` → `podman` → `nerdctl`),
|
|
396
|
+
找到就在那句话里点名(「这台机器上装了 podman:把『docker CLI』填成 podman 就能用」)——
|
|
397
|
+
**只提示,绝不替你改配置**:静默改写 `dockerBin` 会违背本插件「不替用户决定」的取向,
|
|
398
|
+
而且 podman 与本插件输出格式的兼容性并未逐项验证(见「已知限制」)。比对候选时按
|
|
399
|
+
**工具名**(basename)而不是配置里的原字符串,所以 `dockerBin` 写成
|
|
400
|
+
`/usr/local/bin/docker` 时不会出现「把 docker 改成 docker」这种废话。
|
|
401
|
+
|
|
402
|
+
超时是**独立一档**:`probe()` 不只报退出码(`code` 为 `null` 时那句「退出码 null」对用户
|
|
403
|
+
零信息量),而是回 `timedOut:true` + 一句可执行的话。超时还会**真的按时返回**——
|
|
404
|
+
SIGKILL 只作用于直接子进程,`dockerBin` 指向包装脚本时孙进程仍握着管道,会把手上的
|
|
405
|
+
15 秒上限拖成分钟级(真机实测 3 秒上限拖到 60 秒),所以到点先杀、再给 500ms 收敛期,
|
|
406
|
+
仍不 `close` 就自行放掉管道收尾。
|
|
407
|
+
|
|
408
|
+
刻意**不做**的事(保守边界):不自动安装 Docker、不启动 daemon、不切换 docker
|
|
409
|
+
context、不改任何环境变量;空 `targets`(= 用户没配)的语义不变,不产生隐式默认目标;
|
|
410
|
+
`clearTargets` 的防误清空逻辑一字未动。这条路径也**与能力开关无关**——它只写插件
|
|
411
|
+
自己的配置,不涉及任何需要授权的目标侧写操作。
|
|
412
|
+
|
|
350
413
|
### 聚合日志(多选 / Compose 项目)
|
|
351
414
|
|
|
352
415
|
多选容器或打开一个 Compose 项目,都能把多个容器的日志聚合成一条流(每个容器一条
|
|
@@ -403,6 +466,14 @@ add。装完重启 `dsh web`,侧边栏出现「容器」入口;设置 →
|
|
|
403
466
|
所以必须有个名字。**一个能力一行**:开关后面跟这个能力自己的授权状态与**授权时刻**(`已授权 · 2026-09-26 22:31:08`),
|
|
404
467
|
撤销按钮右对齐——两个能力的授权是分开的,并排放会看不出哪个撤销管哪个能力。
|
|
405
468
|
|
|
469
|
+
**两个能力的确认也能一次粘贴搞定**:面板开着时再点另一个开关不会顶掉前一块面板(两个能力的
|
|
470
|
+
授权本来就互不影响),两块面板**纵向堆叠**,顶部多出一个「复制全部(N 条)」——它把仍 pending 的
|
|
471
|
+
命令按**发起顺序**用**换行符**连接后一次复制,用户去宿主终端**粘贴一次**即可:`bash` / `zsh` /
|
|
472
|
+
PowerShell / cmd 粘贴多行都逐行执行,所以刻意不用 `&&`(PowerShell 5.1 不认)或 `;`(cmd 不认)
|
|
473
|
+
连接。某条过期后,合并按钮只复制仍 pending 的那些;全部解锁(或全部收起)后它自己消失。两个
|
|
474
|
+
challenge 的 TTL 与探测周期各自独立,所以文案只说「执行后全部开关自动解锁」、**不承诺同一时刻**
|
|
475
|
+
——倒计时仍是逐面板显示,每块面板的复制 / 重新生成 / 收起也照旧只管自己那一个能力。
|
|
476
|
+
|
|
406
477
|
为什么显示时刻:带外授权是**持久**的——宿主重启后它**直接生效、不再有任何一次确认**。也就是说
|
|
407
478
|
「上个月授权的能力今天一开机就开着」这件事不会有任何提示,除非界面与日志说出来。所以宿主启动时会
|
|
408
479
|
逐条打一行 `[dsh-docker] elevation: load capability=… via=file grantedAt=…`(不含 nonce 与路径),
|
|
@@ -436,9 +507,9 @@ add。装完重启 `dsh web`,侧边栏出现「容器」入口;设置 →
|
|
|
436
507
|
| --- | --- | --- |
|
|
437
508
|
| `enabled` | true | 关闭整个插件(**保存即热生效**:工具立即注销、公告撤下、除 `/config` 外的数据路由 403;`/config` 始终可读写——设置卡片就是重新启用的入口。与 tty 不同:tty 是重启生效) |
|
|
438
509
|
| `announceToAgent` | true | 是否向 agent 注入能力公告(systemPrompt section `plugin:dsh-docker`) |
|
|
439
|
-
| `dockerBin` | `docker` | docker CLI 可执行名或路径(podman 可填 `podman`);只允许字母、数字与 `_ . / \ : -` 及内部空格,且不能以 `-` 开头(**Windows 盘符与 `\`
|
|
510
|
+
| `dockerBin` | `docker` | docker CLI 可执行名或路径(podman 可填 `podman`);只允许字母、数字与 `_ . / \ : -` 及内部空格,且不能以 `-` 开头(**Windows 盘符与 `\` 必须放行**,否则任何绝对路径都填不进来)。**「连接本机」的探测用的就是它**;填错(CLI 不存在)时那句话会点名 `PATH` 里找到的别的容器 CLI,但**不会**替你改这个值 |
|
|
440
511
|
| `allowMutations` | false | 允许**变更操作**:容器 start / stop / restart / remove、镜像删除 / dangling 清理 / 拉取(面板按钮与 `docker_action`、`docker_image_remove`、`docker_image_prune`、`docker_image_pull` 工具;关闭时 `/action`、`/images/remove`、`/images/prune`、`/images/pull/stream` 返回 403,对应工具不注册) |
|
|
441
|
-
| —(能力授权) | 未授权 | `allowMutations` / `allowExec` 有**两条提权通道**:① **启动环境变量**(`DSH_DOCKER_ALLOW_MUTATIONS` / `DSH_DOCKER_ALLOW_EXEC`,值为 `1` / `true` / `yes` / `on`)——判定源是宿主的**启动环境快照**,只认继承来的 `process` 层:写项目 `.env` 或 `~/.dsh/env.yml` **不算**授权;② **就地提权**(免重启):在设置卡片点开关 → 面板给出一条「在宿主终端执行」的命令 →
|
|
512
|
+
| —(能力授权) | 未授权 | `allowMutations` / `allowExec` 有**两条提权通道**:① **启动环境变量**(`DSH_DOCKER_ALLOW_MUTATIONS` / `DSH_DOCKER_ALLOW_EXEC`,值为 `1` / `true` / `yes` / `on`)——判定源是宿主的**启动环境快照**,只认继承来的 `process` 层:写项目 `.env` 或 `~/.dsh/env.yml` **不算**授权;② **就地提权**(免重启):在设置卡片点开关 → 面板给出一条「在宿主终端执行」的命令 → 执行后十秒内生效;两个能力各点一次后,面板组顶部会给一个「复制全部(N 条)」(仍 pending 的命令按发起顺序换行连接),终端粘贴一次即可全部解锁(各自探测周期内先后生效,不承诺同一时刻)。HTTP 侧永远可以**关掉**它们(紧急刹车不能依赖重启),但给 `true` 而无授权会被 400 拒绝并说清两条路。配置里的 `true` **不算授权**(它与 HTTP 写进去的值存在同一个存储里,分不出来源)。**升级影响**:升级前靠界面打开的开关会变成关——设环境变量重启,或在卡片里就地确认。**为什么**:回环围栏与同源证明都拦不住跨站页面与页内脚本(它们能自己填 `Sec-Fetch-Site: same-origin`),而 docker socket 等价目标主机 root;细节(含拦不住谁)见 [architecture.md § 7](../../docs/architecture.md#7-一条请求经过什么) |
|
|
442
513
|
| `allowExec` | false | 允许一次性 `docker exec`(面板 exec 输入与 `docker_exec` 工具;关闭时 `/exec` 返回 403) |
|
|
443
514
|
| `execTimeoutSec` | 30 | exec 默认超时秒数(1~120) |
|
|
444
515
|
| `pollIntervalSec` | 5 | 面板统计刷新间隔秒数(1~60) |
|
|
@@ -495,6 +566,7 @@ add。装完重启 `dsh web`,侧边栏出现「容器」入口;设置 →
|
|
|
495
566
|
| 工具 | 注册条件 | 参数 | 作用 / 典型用法 |
|
|
496
567
|
| --- | --- | --- | --- |
|
|
497
568
|
| `docker_targets` | 恒注册 | `probe?: boolean` | 列出目标(name / kind / label);`probe:true` 逐个探测 docker 版本与 daemon 可达性(SSH 目标会建连接,较慢)。其他工具的 `target` 取自这里 |
|
|
569
|
+
| `docker_connect_local` | 恒注册 | —(无参数) | **连接本机**:确保存在一条 `kind=local` 目标并选中它(已存在则复用,不重复创建;SSH / 自定义目标原样保留),随后只读探测 daemon:返回 `{name, kind, created, saved, reachable, serverVersion?}`。没有任何目标时 `docker_ps` 等会报「尚未配置任何 Docker 目标」,用它一步补上。**不需要任何能力授权**(只写插件自己的配置 + 只读探测);不会安装 Docker / 启动 daemon / 切 context / 改环境变量。daemon 不可达时在结果文本里说清成因(CLI 缺失 / 探测超时 / daemon 未启动 / socket 无权;CLI 缺失时还会点名 PATH 里找到的别的容器 CLI,但不会替你改 dockerBin),不是只报空列表 |
|
|
498
570
|
| `docker_ps` | 恒注册 | `target?`(**传 `*` = 全部目标**)、`all?: boolean` | 列容器(名称 / 状态 / 健康 / 镜像 / 端口 / compose 项目与服务 / 短 ID);默认只列运行中,`all:true` 含已停止。`target:'*'` 时按目标分组返回,**单个目标不可达不影响其他目标**(该组带 `error`)。端口是 IPv4/IPv6 双栈归并后的映射(同一次 `-p` 不再出现两遍,D130);`ports` 为空**不等于**没暴露端口——host 网络容器的端口即宿主机端口,这种情况会给 `net` 字段(D131)。排障第一步 |
|
|
499
571
|
| `docker_attention` | 恒注册 | `target?`(支持 `*`)、`limit?: number` | **需关注汇总**:不健康 / 反复重启 / 被 OOM 杀 / 非零退出 / 僵死;每条带 `reasons`、`exitCode`、`oomKilled`、`restartCount`。OOM 与真实退出码来自一次 `docker inspect`(ps 摘要里 137 无法区分手动 kill)。排障入口:不确定从哪台/哪个容器看起时先调它 |
|
|
500
572
|
| `docker_inspect` | 恒注册 | `target?`、`id`(必填) | `docker inspect` 的权威详情:状态 / 健康检查 / 退出码 / 重启次数 / 端口 / 挂载 / 网络 / 启动命令 |
|
|
@@ -515,6 +587,15 @@ add。装完重启 `dsh web`,侧边栏出现「容器」入口;设置 →
|
|
|
515
587
|
列出可用目标名。
|
|
516
588
|
- 两个开关变化会**立即重注册**工具:关掉 `allowMutations` / `allowExec` 后,
|
|
517
589
|
对应工具(含三个镜像变更工具)从 agent 侧消失,无需重启。
|
|
590
|
+
- **已注册的调用还受会话权限档位闸(tier gate)约束**:按类分——`read` 类(上表「恒注册」
|
|
591
|
+
的全部)全档放行;`docker_action` / `docker_image_remove` / `docker_image_prune` /
|
|
592
|
+
`docker_image_pull` 归 `write` 类,`docker_exec` 归 `exec` 类,这两类在受限档(仅可查看 /
|
|
593
|
+
工作区内修改)下**每次调用弹授权询问**(人允许这一次才执行;无人值守即审批策略 `never`
|
|
594
|
+
时确定性拒绝),完全权限档零询问,Auto review 档不叠加(宿主的模型预审已在管)。宿主未组合
|
|
595
|
+
`sandboxPolicy` / `approval` 服务时不闸(行为与引入前一致),启动日志打一行
|
|
596
|
+
`tier-gate: services absent (…), per-call gate disabled`。它与上面两个静态开关是**双层**:
|
|
597
|
+
静态开关管注册(宿主级、持久授权),档位闸管已注册调用的放行(逐调用、随档位即时变)。
|
|
598
|
+
机制见 [docs/permission-tier-plan.md](../../docs/permission-tier-plan.md)。
|
|
518
599
|
- 推荐排障顺序:`docker_targets` → `docker_ps` → `docker_logs` →
|
|
519
600
|
`docker_inspect` → `docker_stats`;镜像排查 `docker_images` →
|
|
520
601
|
`docker_image_inspect`。
|
|
@@ -529,8 +610,8 @@ add。装完重启 `dsh web`,侧边栏出现「容器」入口;设置 →
|
|
|
529
610
|
`referrer-policy: no-referrer`。
|
|
530
611
|
|
|
531
612
|
**除 loopback 之外还有一道「同源证明」**:四条 SSE(`/logs/stream`、`/stats/stream`、`/events/stream`、
|
|
532
|
-
`/images/pull/stream
|
|
533
|
-
`/volumes/remove|prune`、`/exec`)要求请求带 `Origin: <同源>` 或 `Sec-Fetch-Site: same-origin`,
|
|
613
|
+
`/images/pull/stream`)与九条写子路由(`/action`、`/images/remove|prune`、`/networks/remove|prune`、
|
|
614
|
+
`/volumes/remove|prune`、`/exec`、`/connect-local`)要求请求带 `Origin: <同源>` 或 `Sec-Fetch-Site: same-origin`,
|
|
534
615
|
否则 403 `缺少同源证明`。这是为了挡住「恶意页面用 `<img src=.../images/pull/stream>` 触发一次真实拉取」
|
|
535
616
|
这类跨站副作用;curl / 老 Safari / 部分 WebView 不带这两个头时会撞上它(浏览器正常使用不受影响)。
|
|
536
617
|
只读路由不要求同源证明。
|
|
@@ -550,6 +631,7 @@ add。装完重启 `dsh web`,侧边栏出现「容器」入口;设置 →
|
|
|
550
631
|
| `/config` | GET | — | `{ok:true, config}`:配置快照(targets 只给 `passwordSet` / `passphraseSet`,另附只读的 `ttyBooks` / `ttyAvailable` / `toolsRegistered`) |
|
|
551
632
|
| `/config` | POST | 上表配置键的任意子集 | `{ok:true, config}`;未知键 400,非法 JSON 400 |
|
|
552
633
|
| `/targets` | GET / POST | — | `{ok:true, targets:[{name, kind, label?\|error?}]}` |
|
|
634
|
+
| `/connect-local` | POST | `{}`(请求体被忽略) | **一键连接本机**:复用或新建 `kind=local` 目标并返回 `{ok:true, result:{name, kind, created, saved, reachable, serverVersion?, message}, config}`;宿主没有 settings 服务时 503,保存失败时 500(都带原因且不改内存配置)。要求同源证明(它是写路径),但**不需要** `allowMutations` / `allowExec` |
|
|
553
635
|
| `/probe` | POST | `{target?}` | `{ok:true, probe:{ok, bin, serverVersion, error, target}}` |
|
|
554
636
|
| `/containers` | POST | `{target?, all?}` | `{ok:true, containers: ContainerSummary[]}`;`target:'*'` 时返回 `{ok:true, groups:[{target,label,ok,error?,data?}]}`(跨目标并发聚合) |
|
|
555
637
|
| `/attention` | POST | `{target?, limit?}` | 单目标 `{ok:true, items: AttentionItem[], total, truncated, degraded}`(`limit` 在过滤 + 严重度排序**之后**生效,默认 100、上限 500);`target:'*'` 时 `{ok:true, groups:[{target,label,ok,error?,data:{items,total,truncated,degraded}}]}` |
|
|
@@ -661,6 +743,11 @@ abort)、客户端断开静默中止。各自只差执行器与结束原因:
|
|
|
661
743
|
`id` 过 `assertRef` 白名单、参数经同样的夹紧。与快照不同,长流没有
|
|
662
744
|
`maxOutputKb` 上限(跟随被截断就失去意义),内存防护由客户端的 5000 行
|
|
663
745
|
环形缓冲与 2000 行着色上限承担。
|
|
746
|
+
9. **变更操作有逐条使用审计**。kit elevation 的五行审计回答「授权了」,这里回答
|
|
747
|
+
「用了」:每一次**过了能力闸**的变更 / exec 都落一行 `[dsh-docker] capability-use: …`
|
|
748
|
+
结构化审计行(出口是宿主 stdout),面板路由与 agent 工具两条入口都覆盖。行格式与
|
|
749
|
+
不记什么(403 / tier-gate ask)、以及「exec 命令会随行出现」这条代价,见「已知限制」
|
|
750
|
+
的使用审计条。
|
|
664
751
|
|
|
665
752
|
## 已知限制
|
|
666
753
|
|
|
@@ -757,12 +844,28 @@ abort)、客户端断开静默中止。各自只差执行器与结束原因:
|
|
|
757
844
|
数据路由一律 403(进行中的长流——日志 / 统计 / 拉取——也会被立即收尾)。
|
|
758
845
|
路由对象本身不卸载、靠 403 拦截;`/config` 始终可读写,设置卡片就是重新
|
|
759
846
|
启用的入口,无需重启 `dsh web`。
|
|
760
|
-
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
-
|
|
764
|
-
|
|
765
|
-
|
|
847
|
+
- **使用审计走宿主 stdout,exec 命令会随行出现**:每一次**过了能力闸**的变更 / exec
|
|
848
|
+
(面板的变更 / exec 路由与 agent 的 `docker_action` / `docker_image_remove` /
|
|
849
|
+
`docker_image_prune` / `docker_image_pull` / `docker_exec`;403 与参数校验 400 不记,
|
|
850
|
+
「使用 = 过了闸」)落一行 `[dsh-docker] capability-use: capability=allowMutations|allowExec
|
|
851
|
+
source=http|tool action=container.remove|… target=… ref=… ok=true|false durationMs=…`,
|
|
852
|
+
key=value 风格与 kit elevation 的授权行一致,`capability` 与授权行直接对得上号;pull
|
|
853
|
+
进度流拆 start / end 两行(宿主中途挂掉时至少 start 还在)。**出口是 `console.log`**
|
|
854
|
+
(docker D162:真机实测 `ctx.logger` 的行在 0.2.x 宿主上既不进 stdout 也不落盘,而审计行的
|
|
855
|
+
意义全在「出问题时翻得到」;tier-gate 的日志出口同此取向)——跑 `dsh web` 的那个终端里
|
|
856
|
+
就能看到、可 grep。kit elevation 的授权行同走宿主
|
|
857
|
+
stdout(**kit D14**,两类行同通道,可对照检索)。**代价**:exec 的命令会记进行尾 `detail`(连同退出码,
|
|
858
|
+
如 `detail=code=0 cmd=ls -la /app`),整段截断 200 字符、控制字符转义成 `\n` 等字面量——
|
|
859
|
+
行是给人看的,就得接受它出现在终端可见的日志流里,与 elevation「nonce 绝不进日志」是
|
|
860
|
+
同一类取舍的正面。docker 报错也算一次使用(`ok=false` + `detail=` 截断后的错误文案);
|
|
861
|
+
tier-gate 的 ask / deny 不记(没执行不算使用,放行与否由宿主 approval 日志覆盖)。
|
|
862
|
+
没有查询界面、没有独立审计文件——要检索就翻宿主 stdout。
|
|
863
|
+
|
|
864
|
+
- **跨目标聚合的边界**:并发上限 4;**单目标预算 90s(兜底)**,而每条命令自己的超时
|
|
865
|
+
(30s)才是被遵守的那个——到点时**底层的子进程 / SSH 通道会被真的取消**(不是只让这一格
|
|
866
|
+
返回失败),否则超时那条命令会继续占着远端会话槽(D161)。单个目标失败/超时只影响它自己
|
|
867
|
+
那一格(组里带 `error`)。目标很多时总览的请求量随目标数线性增长(每目标 2 个请求),
|
|
868
|
+
自动刷新会放大这个量——目标多时建议关掉自动刷新。
|
|
766
869
|
|
|
767
870
|
## 工作原理
|
|
768
871
|
|
|
@@ -864,7 +967,7 @@ inspect 解析(状态 / 健康 / 退出码 / 挂载 / 网络 / 端口 / 缺字
|
|
|
864
967
|
`resolveTarget` 的四种路径、`mergeTargetSecrets` 的凭证保留语义。
|
|
865
968
|
|
|
866
969
|
`scripts/route-smoke.mjs` 用**假 cordis ctx + 假 docker CLI 脚本**跑端到端:
|
|
867
|
-
插件挂载(settings / 工具 / 路由 / 能力公告注册)、**
|
|
970
|
+
插件挂载(settings / 工具 / 路由 / 能力公告注册)、**27 条路由**的实际调用与返回
|
|
868
971
|
(含 `/logs/stream`、`/stats/stream`、`/events/stream`、`/images/pull/stream` 四条 SSE
|
|
869
972
|
的事件序列与参数校验,事件流另断噪音被白名单丢掉)、`docker_events` 工具的快照
|
|
870
973
|
输出与 `since` 字符集校验、`/images/inspect` 的详情 + 构建历史、`/config` 凭证脱敏、未知配置键
|
|
@@ -942,15 +1045,28 @@ network / volume 的 ls·inspect 解析容错(字符串布尔、缺 `Mountpoin
|
|
|
942
1045
|
2. **SSH 目标**:tty 连接簿里已有条目时,用 `book` 引用它 → 容器列表 / 详情 /
|
|
943
1046
|
日志正常;首次连接日志里出现「已记录 host key 指纹(TOFU)」,第二次不再
|
|
944
1047
|
提示;手动改掉 `hostKeys` 里的指纹后重连,应**被拒绝**并给出重置指引。
|
|
945
|
-
3.
|
|
946
|
-
|
|
947
|
-
|
|
1048
|
+
3. **一键连接本机**(两处入口都要试):
|
|
1049
|
+
- **没有目标时**:把目标全部删空保存(或用一个全新 profile),打开面板 → 正文空态里
|
|
1050
|
+
有一个「连接本机」主按钮,**下拉里不该重复列**这一项 → 点一下应当出现一条
|
|
1051
|
+
`kind=local` 的 `local` 并立刻选中、容器列表出来;
|
|
1052
|
+
- **已有别的目标时**:留着几台 SSH,打开目标下拉 → 末一项是 `+ 连接本机`,
|
|
1053
|
+
**工具条上不该有**任何常驻入口 → 选中该项,应当同样加上 `local` 并切过去,
|
|
1054
|
+
且该项随即从下拉里消失(已经有本机目标了)。
|
|
1055
|
+
两处都**不该**出现「同一个入口列了两次」;全程**不该**有任何 Docker 安装 / daemon 启动 /
|
|
1056
|
+
context 切换动作。宿主上没有 docker 时,应当看到一句点名的原因(CLI 缺失 / 探测超时 /
|
|
1057
|
+
daemon 未启动 / socket 无权访问),而不是一个空列表。
|
|
1058
|
+
- **只有 podman 的机器**(把 PATH 里的 docker 拿掉、只留 podman):那句话应当
|
|
1059
|
+
**点名 podman** 并说明「把『docker CLI』填成 podman 就能用」,同时**不该**替你
|
|
1060
|
+
改 `dockerBin`(回头去设置卡片看,值仍是 `docker`)。
|
|
1061
|
+
4. **只读拦截**:两个开关都关时,`/action`、`/exec`、`/images/remove`、
|
|
1062
|
+
`/images/prune`、`/images/pull/stream` 全部 403;agent 侧恒注册 12 个只读工具
|
|
1063
|
+
(`docker_targets` / `connect_local` / `ps` / `attention` / `inspect` / `logs` / `stats` / `events` /
|
|
948
1064
|
`images` / `image_inspect` / `networks` / `volumes`,数量以工具表为准),
|
|
949
1065
|
面板的启停删 / 镜像删除 / 清理 / 拉取按钮置灰。打开「允许变更操作」后这些
|
|
950
1066
|
路由与工具立即出现(无需重启)。
|
|
951
|
-
|
|
1067
|
+
5. **日志 / 统计 / 镜像**:`tail` 与 `timestamps` / `since` 生效;统计显示
|
|
952
1068
|
CPU、内存、网络与块 IO;镜像列表含 dangling 条目标记。
|
|
953
|
-
|
|
1069
|
+
6. **FOLLOW 实时日志流**:日志页打开 `FOLLOW` → 状态行先「正在连接」后
|
|
954
1070
|
「实时跟随中」,`docker logs -f` 的新行即时出现(`docker run --rm alpine sh
|
|
955
1071
|
-c 'i=0; while :; do echo line-$i; i=$((i+1)); sleep 1; done'` 可观察);
|
|
956
1072
|
高吞吐护栏:换成长行快速打印(如 `head -c 2000 /dev/zero | tr "\\0" x; echo` 循环)
|
|
@@ -961,61 +1077,61 @@ network / volume 的 ls·inspect 解析容错(字符串布尔、缺 `Mountpoin
|
|
|
961
1077
|
(或热改插件配置)→ 状态行短暂「正在重连」后自愈,不弹错误横幅。SSH 目标
|
|
962
1078
|
同样跑一遍,确认流跑着时同一主机的 `docker ps` 面板操作不受影响(连接复用),
|
|
963
1079
|
且空闲回收(120s)不会掐断流。
|
|
964
|
-
|
|
1080
|
+
7. **统计实时跟随**:统计页打开 `FOLLOW` →「正在连接统计流…」→「实时跟随中
|
|
965
1081
|
(docker stats)」,CPU / 内存 sparkline 每秒长一点(跑 `docker run --rm
|
|
966
1082
|
alpine sh -c 'while :; do :; done'` 观察 CPU 起来);关 FOLLOW 立即回快照并
|
|
967
1083
|
恢复轮询。停掉被统计的容器 → 流收到 `end`(stats-exit)提示后自动回轮询。
|
|
968
1084
|
注意**这条流不会自然结束**:切页 / 关面板必须断掉 `EventSource`(宿主侧应
|
|
969
1085
|
看到 `docker stats` 被 SIGTERM)。
|
|
970
|
-
|
|
1086
|
+
8. **镜像详情 / 删除 / 清理**:镜像页点某行「详情」→ 概览里的层数与
|
|
971
1087
|
`docker image inspect` 一致、构建历史与 `docker history` 一致(Docker ≥ 26
|
|
972
1088
|
走 `--format`,老版本走纯文本表格兜底);dangling 行用镜像 ID 查。点「删除」→
|
|
973
1089
|
二次确认后执行 `docker image rm`;删除被容器引用的镜像应失败并给出提示。
|
|
974
1090
|
「清理 dangling」只删无标签镜像,输出末尾带 `Total reclaimed space`。
|
|
975
|
-
|
|
1091
|
+
9. **拉取进度流**:镜像页工具条的拉取图标(悬停显示「拉取镜像」)→ 输入本地没有的小镜像(如 `alpine:3.20`)
|
|
976
1092
|
→ 逐层状态行实时出现且按层原地更新;完成后提示「拉取完成」并自动刷新列表。
|
|
977
1093
|
拉取中点「停止」或离开视图 → `docker pull` 被 SIGTERM 结束,不残留进程。
|
|
978
1094
|
未开启「允许变更操作」时该按钮置灰、直接访问 `/images/pull/stream` 返回 403。
|
|
979
|
-
|
|
1095
|
+
10. **Compose 项目视图**:用一份 compose 起两三个服务(`docker compose up -d`)→
|
|
980
1096
|
工具条切到「Compose」→ 服务归到一个项目卡片下,运行数 / 服务数 / 状态正确;
|
|
981
1097
|
点进项目看服务表,切「聚合日志」→ 各服务日志按 `[service]` 前缀混流出现
|
|
982
1098
|
(`docker compose logs -f` 的等价物),过滤框可按服务名与内容过滤;关「自动
|
|
983
1099
|
滚动」后新日志继续进缓冲但视图不跳。无 compose 标签的容器归入
|
|
984
1100
|
「(非 compose 容器)」。
|
|
985
|
-
|
|
1101
|
+
11. **`allowMutations` 打开后**:stop / start / restart 成功;对运行中容器
|
|
986
1102
|
remove 报错并附「先停止再删除」提示,先 stop 再 remove 成功。
|
|
987
|
-
|
|
1103
|
+
12. **`allowExec` 打开后**:`ls -la /app` 之类命令返回 stdout 与退出码;把命令
|
|
988
1104
|
换成 `sleep 60`(`timeoutSec` 调小)应被中断并报超时;`command` 超过 8000
|
|
989
1105
|
字符被拒绝。
|
|
990
|
-
|
|
1106
|
+
13. **exec 抽屉与正文共存**(tty ≥ 0.15):卡片「终端」开抽屉后切到日志 / 统计 /
|
|
991
1107
|
另一台容器的详情,抽屉与终端会话都必须活着;**折叠**(箭头或双击顶边)把抽屉
|
|
992
1108
|
压成一条标题栏、会话继续跑(展开即回原样);**拖拽顶边**能调高度且不超过面板
|
|
993
1109
|
的 75%;此时点 backdrop 空白处或面板 ✕ 应弹「结束容器终端会话」确认,取消后
|
|
994
1110
|
会话仍在,确认才结束(tty 侧收到 kill)。
|
|
995
1111
|
离线回归:`node packages/tty/scripts/preview.mjs docker-exec-logs`
|
|
996
1112
|
(无头 Chrome 跑真实客户端:抽屉 + 日志页 + 折叠展开,断言会话未被结束)。
|
|
997
|
-
|
|
1113
|
+
14. **从连接栏进容器面板(dock 模式,需 tty ≥ 0.16)**:tty 面板里开一个 SSH 标签 →
|
|
998
1114
|
连接栏「容器」→ 容器面板应挂在**终端右侧**(不是全屏弹窗),终端仍可输入;
|
|
999
1115
|
拖左边缘可调宽(上限面板宽 72%)、标题栏箭头折叠成窄条(终端拿回全部宽度、
|
|
1000
1116
|
容器面板不卸载)、✕ 收起面板且 SSH 标签不受影响;此时点卡片「终端」应在同一
|
|
1001
1117
|
终端面板**新开一个 `<容器> · exec` 标签**,而不是再嵌一个终端抽屉。
|
|
1002
1118
|
离线回归:`node packages/tty/scripts/preview.mjs docker-dock`
|
|
1003
1119
|
(断言 docked / 无 backdrop / 折叠后终端变宽 / 容器面板存活)。
|
|
1004
|
-
|
|
1120
|
+
15. **多选聚合日志**:容器列表点 `聚合选择` → 卡片左侧出现勾选框、卡片动作条收起;
|
|
1005
1121
|
勾 2~3 个容器 → 操作条显示「已选 3 个容器」,点 `聚合日志` → 聚合视图标题为
|
|
1006
1122
|
「聚合日志 · 3 个容器」,三条流按 `[service]` / 容器名前缀混流出现;只勾 1 个时
|
|
1007
1123
|
按钮置灰并提示「至少选择 2 个容器」,勾到 9 个时置灰并提示最多 8 个;列表里
|
|
1008
1124
|
停掉其中一个容器 → 该条流走原有 `end` 语义结束,其余流不受影响。
|
|
1009
|
-
|
|
1125
|
+
16. **Esc 退出选择态**:选择态下按 Esc → 回到普通列表、勾选清空、动作条消失;
|
|
1010
1126
|
再点一次 `聚合选择`(此时标签是 `退出选择`)效果相同;切目标 / 切分段 / 关面板
|
|
1011
1127
|
也都会把选择态和勾选一起清掉。
|
|
1012
|
-
|
|
1128
|
+
17. **事件活动条**:容器列表头部出现「活动」条,状态点先黄后绿、文案「实时接收中
|
|
1013
1129
|
(docker events)」;执行 `docker restart <容器>` / `docker stop`+`start` → 1 秒内
|
|
1014
1130
|
列表状态跟着变,活动条出现 `stop` / `start`(异常退出显示 `die(137)` 这种带码
|
|
1015
1131
|
形式);点标题可折叠(事件流不中断,展开后仍是累积的最新 8 条);
|
|
1016
1132
|
`docker exec` 连续几次**不产生任何事件**(`exec_*` 已被服务端白名单丢掉);
|
|
1017
1133
|
断网 / 重启 `dsh web` 后恢复时状态点短暂黄色并自动补一次全量列表刷新。
|
|
1018
|
-
|
|
1134
|
+
18. **网络 / 卷**:工具条分段出现「网络」「卷」。网络列表应含 `bridge` / `host` / `none`
|
|
1019
1135
|
与 compose 创建的项目网络;点某条进详情 → 概览里子网 / 网关与 `docker network
|
|
1020
1136
|
inspect` 一致、`internal` 网络带徽标,「接入的容器」页签列出容器名与 IPv4。
|
|
1021
1137
|
卷列表的挂载点与 `docker volume ls` 一致(超长路径省略、悬停看全量)。
|
|
@@ -1024,7 +1140,7 @@ network / volume 的 ls·inspect 解析容错(字符串布尔、缺 `Mountpoin
|
|
|
1024
1140
|
卷清理会弹带「数据会一起删除」的确认框。**注意**:卷清理在 docker < 23 上会连
|
|
1025
1141
|
命名卷一起删,先在测试目标上确认为妙。
|
|
1026
1142
|
|
|
1027
|
-
|
|
1143
|
+
19. **多目标总览**:配两个以上目标(本机 + 一台 SSH)→ 目标选择器旁出现 `总览` pill,
|
|
1028
1144
|
点进去一屏出现两张计数卡(各带「本机」/「SSH」徽标)与异常区;卡片数字与该目标
|
|
1029
1145
|
容器列表的口径一致。把其中一个目标的 SSH 地址改错(或停掉远端 docker)再刷新 →
|
|
1030
1146
|
只有那张卡描红、顶部出现「1 个目标不可达」,另一张卡照常显示结果,**页面不会**
|
|
@@ -1032,7 +1148,7 @@ network / volume 的 ls·inspect 解析容错(字符串布尔、缺 `Mountpoin
|
|
|
1032
1148
|
**该目标**的容器列表而不是总览;点计数卡 → 该目标的容器列表。全部正常时异常区显示
|
|
1033
1149
|
「一切正常」;还有目标没答完时显示「读取中…」。打开「自动刷新」→ 按
|
|
1034
1150
|
`pollIntervalSec` 轮询全部目标(关掉即停)。
|
|
1035
|
-
|
|
1151
|
+
20. **切目标不串台(列表写入闸)**:让 目标1(不可达的 SSH)先失败出「操作失败」横幅,
|
|
1036
1152
|
随即切到正常的目标2 → 横幅应**随切换立即消失**、列表是 目标2 的容器;再等 20s
|
|
1037
1153
|
让 目标1 的超时响应**在其之后**才回来 → 横幅不得重新出现、列表也不得被换成
|
|
1038
1154
|
目标1 的容器。从总览点 目标1 那张红色计数卡进去,同样不该看到上一个目标的失败。
|