@hyzyn/dsh-docker 0.6.4 → 0.7.1
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 +31 -16
- package/README.md +31 -18
- package/client-src/current-session.js +52 -0
- package/client-src/docker.css +2180 -0
- package/client-src/index.js +6691 -0
- package/client-src/session-target.js +92 -0
- package/client.js +46 -24
- package/lib/docker.d.ts +62 -5
- package/lib/docker.js +287 -48
- package/lib/docker.js.map +1 -1
- package/lib/index.d.ts +19 -1
- package/lib/index.js +597 -82
- package/lib/index.js.map +1 -1
- package/lib/ssh-exec.d.ts +30 -11
- package/lib/ssh-exec.js +360 -143
- package/lib/ssh-exec.js.map +1 -1
- package/package.json +4 -2
- package/scripts/build-client.mjs +33 -0
- package/scripts/client-smoke.mjs +1851 -0
- package/scripts/route-smoke.mjs +1091 -0
- package/scripts/smoke.mjs +874 -0
package/README.en.md
CHANGED
|
@@ -430,7 +430,7 @@ return 400).
|
|
|
430
430
|
|
|
431
431
|
| Item | Default | Description |
|
|
432
432
|
| --- | --- | --- |
|
|
433
|
-
| `enabled` | true | disables the whole plugin (**takes effect
|
|
433
|
+
| `enabled` | true | disables the whole plugin (**takes effect on save**: tools are unregistered immediately, the announcement is withdrawn, and all data routes except `/config` return 403; `/config` stays readable and writable — the settings card is the way back in. Unlike tty, which needs a restart) |
|
|
434
434
|
| `announceToAgent` | true | whether to inject a capability announcement into the agent (systemPrompt section `plugin:dsh-docker`) |
|
|
435
435
|
| `dockerBin` | `docker` | the docker CLI executable name or path (`podman` works here); only letters, digits and `_ . / \ : -` plus interior spaces are allowed, and it may not start with `-` (**Windows drive letters and `\` must be allowed**, otherwise no absolute path can be entered at all) |
|
|
436
436
|
| `allowMutations` | false | allows **mutating operations**: container start / stop / restart / remove, image removal / dangling pruning / pulling (the panel buttons and the `docker_action`, `docker_image_remove`, `docker_image_prune`, `docker_image_pull` tools; while off, `/action`, `/images/remove`, `/images/prune`, `/images/pull/stream` return 403 and the corresponding tools are not registered) |
|
|
@@ -478,11 +478,13 @@ the config snapshot only provides the two booleans `passwordSet` / `passphraseSe
|
|
|
478
478
|
| --- | --- |
|
|
479
479
|
| `host` | hostname or IP (required) |
|
|
480
480
|
| `port` | port, default 22 (unique per host:port) |
|
|
481
|
-
| `
|
|
481
|
+
| `fingerprints` | **fingerprint set** (required, at least one): the verbatim hex fingerprints received by the `hostHash: 'sha256'` callback. One host:port may hold several host keys (rsa / ed25519 …); matching any of them counts as a match |
|
|
482
|
+
| `fingerprint` | legacy single-fingerprint field (string): migration input only, merged into `fingerprints` on load; do not use it in new configs |
|
|
482
483
|
|
|
483
484
|
The first connection is recorded automatically and written to disk; every later connection must match, and
|
|
484
485
|
**a changed fingerprint rejects the connection outright**, with guidance in the error to "delete this host's record
|
|
485
|
-
and reconnect". The record list can be deleted / reset in the settings card
|
|
486
|
+
and reconnect". The record list can be deleted / reset in the settings card (deletion is submitted as the explicit
|
|
487
|
+
`hostKeysRemove: [{host, port}]`, so a union merge cannot silently undo it).
|
|
486
488
|
|
|
487
489
|
## Agent tools
|
|
488
490
|
|
|
@@ -518,9 +520,19 @@ and reconnect". The record list can be deleted / reset in the settings card.
|
|
|
518
520
|
|
|
519
521
|
## HTTP routes (under the `/api/dsh-docker` prefix, all behind the loopback fence)
|
|
520
522
|
|
|
521
|
-
The fence validates `remoteAddress` (127
|
|
522
|
-
`
|
|
523
|
-
|
|
523
|
+
The fence validates `remoteAddress` (the whole 127/8 range + `::1` + `::ffff:` mappings), `Host` (literal
|
|
524
|
+
loopback, `localhost`, or a hostname / `/etc/hosts` alias that **resolves to this machine** — resolution has a
|
|
525
|
+
500ms timeout and a 60s cache), `Origin` and `sec-fetch-site`; any non-local request gets 403
|
|
526
|
+
`forbidden: loopback-only`. The request body is capped at 1MB, and responses are uniformly `application/json` +
|
|
527
|
+
`referrer-policy: no-referrer`.
|
|
528
|
+
|
|
529
|
+
**On top of loopback there is a second gate, the "same-origin proof"**: the four SSE streams (`/logs/stream`,
|
|
530
|
+
`/stats/stream`, `/events/stream`, `/images/pull/stream`) and eight mutating sub-routes (`/action`,
|
|
531
|
+
`/images/remove|prune`, `/networks/remove|prune`, `/volumes/remove|prune`, `/exec`) require either
|
|
532
|
+
`Origin: <same origin>` or `Sec-Fetch-Site: same-origin`, otherwise 403 `缺少同源证明` (missing same-origin
|
|
533
|
+
proof). This blocks cross-site side effects such as a malicious page using
|
|
534
|
+
`<img src=.../images/pull/stream>` to trigger a real pull; curl, old Safari and some WebViews do not send those
|
|
535
|
+
headers and will hit it (normal browser use is unaffected). Read-only routes do not require the proof.
|
|
524
536
|
|
|
525
537
|
| Route | Method | Request body | Response |
|
|
526
538
|
| --- | --- | --- | --- |
|
|
@@ -529,7 +541,7 @@ The request body is capped at 1MB, and responses are uniformly `application/json
|
|
|
529
541
|
| `/targets` | GET / POST | — | `{ok:true, targets:[{name, kind, label?\|error?}]}` |
|
|
530
542
|
| `/probe` | POST | `{target?}` | `{ok:true, probe:{ok, bin, serverVersion, error, target}}` |
|
|
531
543
|
| `/containers` | POST | `{target?, all?}` | `{ok:true, containers: ContainerSummary[]}`; with `target:'*'` it returns `{ok:true, groups:[{target,label,ok,error?,data?}]}` (concurrent cross-target aggregation) |
|
|
532
|
-
| `/attention` | POST | `{target?}` | for a single target `{ok:true, items: AttentionItem[]}
|
|
544
|
+
| `/attention` | POST | `{target?, limit?}` | for a single target `{ok:true, items: AttentionItem[], total, truncated, degraded}` (`limit` applies **after** filtering + severity sorting; default 100, max 500); with `target:'*'` `{ok:true, groups:[{target,label,ok,error?,data:{items,total,truncated,degraded}}]}` |
|
|
533
545
|
| `/inspect` | POST | `{target?, id}` | `{ok:true, details: ContainerDetail[]}` |
|
|
534
546
|
| `/stats` | POST | `{target?, ids?: string[]}` | `{ok:true, stats: ContainerStats[]}` |
|
|
535
547
|
| `/logs` | POST | `{target?, id, tail?, timestamps?, since?}` | `{ok:true, logs:{id, text, truncated}}` |
|
|
@@ -685,7 +697,7 @@ read-only first:
|
|
|
685
697
|
appears in `docker volume ls -f dangling=true`, and `docker volume prune -f` reports `Total reclaimed space: 0B`
|
|
686
698
|
(measured; an anonymous volume, by contrast, is deleted and named in the output). That matches this plugin's
|
|
687
699
|
deliberate refusal to pass `--all`; use `/volumes/remove` (the panel's volume delete) for named volumes.
|
|
688
|
-
- **Podman compatibility through `dockerBin`**: filling in `podman` runs, but the fields and output formats of
|
|
700
|
+
- **Podman compatibility through `dockerBin`**: filling in `podman` runs, but the fields and output formats of
|
|
689
701
|
`stats` and `--format '{{json .}}'` differ from docker's, so only the parser's degradation paths are relied on;
|
|
690
702
|
this has not been verified item by item.
|
|
691
703
|
- **No image builds / Compose orchestration changes**: images support pulling / removal / dangling pruning, but there
|
|
@@ -714,9 +726,10 @@ read-only first:
|
|
|
714
726
|
connection bar on local tabs is hidden entirely.
|
|
715
727
|
Target additions and removals are picked up within at most 30 seconds (saving the settings card refreshes
|
|
716
728
|
immediately).
|
|
717
|
-
- **`enabled: false`
|
|
718
|
-
streams in progress — logs / stats / pulling —
|
|
719
|
-
|
|
729
|
+
- **`enabled: false` takes effect on save**: after saving, tools are unregistered immediately, the announcement is
|
|
730
|
+
withdrawn, and data routes other than `/config` return 403 (long streams in progress — logs / stats / pulling —
|
|
731
|
+
are wrapped up immediately too). The route objects themselves are not unloaded; they are blocked by the 403, and
|
|
732
|
+
`/config` stays readable and writable — the settings card is the way back in, with no restart of `dsh web` needed.
|
|
720
733
|
- **Mutating operations have no separate audit log**: only docker's own records and the host `ctx.logger`'s
|
|
721
734
|
ordinary output.
|
|
722
735
|
|
|
@@ -797,10 +810,10 @@ Host half (src/index.ts)
|
|
|
797
810
|
pnpm --filter @hyzyn/dsh-docker build # tsc → lib/ (host half) + esbuild → client.js (browser half)
|
|
798
811
|
pnpm --filter @hyzyn/dsh-docker typecheck
|
|
799
812
|
pnpm --filter @hyzyn/dsh-docker smoke # three offline regression suites, none needing a docker daemon
|
|
800
|
-
pnpm test # repo-level vitest (including this package's logs-stream / streams suites)
|
|
813
|
+
pnpm test # repo-level vitest (including this package's config-route / current-session / session-target / logs-stream / streams / ssh-stream-budget suites — six in total)
|
|
801
814
|
```
|
|
802
815
|
|
|
803
|
-
`scripts/smoke.mjs` (
|
|
816
|
+
`scripts/smoke.mjs` (reading the `lib/` build output; item counts are self-reported at the end of the script) covers pure logic: ps parsing (field mapping /
|
|
804
817
|
compose labels / ports / deriving a missing `State` / noise lines / JSON arrays), port-string parsing and
|
|
805
818
|
deduplication, stats parsing (percentages / memory / IO / PIDs), abnormal input for size and percent, images parsing
|
|
806
819
|
(dangling), **image inspect / history parsing (both the JSON and the plain-text-table path)**,
|
|
@@ -811,7 +824,7 @@ injection)**, `assertBin`, `formatBytes`, `shJoin` escaping, `DockerApi`'s argv
|
|
|
811
824
|
`normalizeConfig` defaults and clamping, `sanitizeTargets` / `sanitizeHostKeys`,
|
|
812
825
|
`resolveTarget`'s four paths, and `mergeTargetSecrets`' credential-preserving semantics.
|
|
813
826
|
|
|
814
|
-
`scripts/route-smoke.mjs`
|
|
827
|
+
`scripts/route-smoke.mjs` runs end to end with **a fake cordis ctx + a fake docker CLI script**: plugin
|
|
815
828
|
mounting (settings / tools / routes / capability-announcement registration), the actual calls and returns of
|
|
816
829
|
**26 routes** (including the event sequences and parameter validation of the four SSE streams `/logs/stream`,
|
|
817
830
|
`/stats/stream`, `/events/stream`, `/images/pull/stream`, with the event stream additionally asserting that noise is
|
|
@@ -823,7 +836,7 @@ corresponding tools not being registered, immediate unlocking after the switches
|
|
|
823
836
|
image references rejected by the allowlist when injection is attempted, the fallback when `target` is omitted and the
|
|
824
837
|
error with several targets, and 403 on the stream routes after being disabled.
|
|
825
838
|
|
|
826
|
-
`scripts/client-smoke.mjs`
|
|
839
|
+
`scripts/client-smoke.mjs` executes the build artifact
|
|
827
840
|
`client.js` in Node with minimal DOM / React stubs: verifying the registration id and factory shape, that it only
|
|
828
841
|
requires modules provided by the platform seed
|
|
829
842
|
(`react` / `react/jsx-runtime` / `react-dom/client`), that the settings card key registered by `apply` equals the
|
|
@@ -867,7 +880,9 @@ lines), and the gating of the eight `/networks` and `/volumes` endpoints (403 fo
|
|
|
867
880
|
prompt again; after manually changing the fingerprint in `hostKeys` and reconnecting, the connection should
|
|
868
881
|
**be rejected** with reset guidance.
|
|
869
882
|
3. **Read-only interception**: with both switches off, `/action`, `/exec`, `/images/remove`,
|
|
870
|
-
`/images/prune`, `/images/pull/stream` all return 403; on the agent side
|
|
883
|
+
`/images/prune`, `/images/pull/stream` all return 403; on the agent side exactly 11 read-only tools are
|
|
884
|
+
registered (`docker_targets` / `ps` / `attention` / `inspect` / `logs` / `stats` / `events` / `images` /
|
|
885
|
+
`image_inspect` / `networks` / `volumes` — see the tool table for the authoritative list), and
|
|
871
886
|
the panel's start / stop / remove, image removal, pruning and pull buttons are greyed out. After turning on
|
|
872
887
|
"allow mutations" these routes and tools appear immediately (no restart needed).
|
|
873
888
|
4. **Logs / stats / images**: `tail` and `timestamps` / `since` take effect; stats show
|
package/README.md
CHANGED
|
@@ -384,7 +384,7 @@ add。装完重启 `dsh web`,侧边栏出现「容器」入口;设置 →
|
|
|
384
384
|
|
|
385
385
|
| 项 | 默认 | 说明 |
|
|
386
386
|
| --- | --- | --- |
|
|
387
|
-
| `enabled` | true |
|
|
387
|
+
| `enabled` | true | 关闭整个插件(**保存即热生效**:工具立即注销、公告撤下、除 `/config` 外的数据路由 403;`/config` 始终可读写——设置卡片就是重新启用的入口。与 tty 不同:tty 是重启生效) |
|
|
388
388
|
| `announceToAgent` | true | 是否向 agent 注入能力公告(systemPrompt section `plugin:dsh-docker`) |
|
|
389
389
|
| `dockerBin` | `docker` | docker CLI 可执行名或路径(podman 可填 `podman`);只允许字母、数字与 `_ . / \ : -` 及内部空格,且不能以 `-` 开头(**Windows 盘符与 `\` 必须放行**,否则任何绝对路径都填不进来) |
|
|
390
390
|
| `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,对应工具不注册) |
|
|
@@ -432,10 +432,12 @@ add。装完重启 `dsh web`,侧边栏出现「容器」入口;设置 →
|
|
|
432
432
|
| --- | --- |
|
|
433
433
|
| `host` | 主机名或 IP(必填) |
|
|
434
434
|
| `port` | 端口,默认 22(按 host:port 唯一) |
|
|
435
|
-
| `
|
|
435
|
+
| `fingerprints` | **指纹集合**(必填,至少一条):`hostHash: 'sha256'` 回调收到的原样十六进制指纹。同一 host:port 可含多把主机密钥(rsa / ed25519 …),命中任意一条即视为匹配 |
|
|
436
|
+
| `fingerprint` | 旧版单指纹字段(字符串):只作迁移输入,清洗时并入 `fingerprints`,新配置不要再用 |
|
|
436
437
|
|
|
437
438
|
首次连接自动记录并落盘;之后每次连接必须匹配,**指纹变更直接拒绝连接**,
|
|
438
|
-
|
|
439
|
+
错误信息带「删除该主机记录再重连」的指引。记录列表在设置卡片里可删除重置
|
|
440
|
+
(删除会以显式的 `hostKeysRemove: [{host, port}]` 提交,不会被并集合并悄悄撤销)。
|
|
439
441
|
|
|
440
442
|
## agent 工具
|
|
441
443
|
|
|
@@ -470,9 +472,17 @@ add。装完重启 `dsh web`,侧边栏出现「容器」入口;设置 →
|
|
|
470
472
|
|
|
471
473
|
## HTTP 路由(`/api/dsh-docker` 前缀,全部 loopback 围栏)
|
|
472
474
|
|
|
473
|
-
围栏校验 `remoteAddress
|
|
474
|
-
`Origin` 与 `sec-fetch-site
|
|
475
|
-
|
|
475
|
+
围栏校验 `remoteAddress`(整个 127/8 + `::1` + `::ffff:` 映射)、`Host`(字面量环回、`localhost`,
|
|
476
|
+
或**解析到本机的主机名 / `/etc/hosts` 别名**,解析带 500ms 超时与 60s 缓存)、`Origin` 与 `sec-fetch-site`;
|
|
477
|
+
非本机请求一律 403 `forbidden: loopback-only`。请求体上限 1MB,响应统一 `application/json` +
|
|
478
|
+
`referrer-policy: no-referrer`。
|
|
479
|
+
|
|
480
|
+
**除 loopback 之外还有一道「同源证明」**:四条 SSE(`/logs/stream`、`/stats/stream`、`/events/stream`、
|
|
481
|
+
`/images/pull/stream`)与八条变更子路由(`/action`、`/images/remove|prune`、`/networks/remove|prune`、
|
|
482
|
+
`/volumes/remove|prune`、`/exec`)要求请求带 `Origin: <同源>` 或 `Sec-Fetch-Site: same-origin`,
|
|
483
|
+
否则 403 `缺少同源证明`。这是为了挡住「恶意页面用 `<img src=.../images/pull/stream>` 触发一次真实拉取」
|
|
484
|
+
这类跨站副作用;curl / 老 Safari / 部分 WebView 不带这两个头时会撞上它(浏览器正常使用不受影响)。
|
|
485
|
+
只读路由不要求同源证明。
|
|
476
486
|
|
|
477
487
|
| 路由 | 方法 | 请求体 | 返回 |
|
|
478
488
|
| --- | --- | --- | --- |
|
|
@@ -481,7 +491,7 @@ add。装完重启 `dsh web`,侧边栏出现「容器」入口;设置 →
|
|
|
481
491
|
| `/targets` | GET / POST | — | `{ok:true, targets:[{name, kind, label?\|error?}]}` |
|
|
482
492
|
| `/probe` | POST | `{target?}` | `{ok:true, probe:{ok, bin, serverVersion, error, target}}` |
|
|
483
493
|
| `/containers` | POST | `{target?, all?}` | `{ok:true, containers: ContainerSummary[]}`;`target:'*'` 时返回 `{ok:true, groups:[{target,label,ok,error?,data?}]}`(跨目标并发聚合) |
|
|
484
|
-
| `/attention` | POST | `{target?}` | 单目标 `{ok:true, items: AttentionItem[]}
|
|
494
|
+
| `/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}}]}` |
|
|
485
495
|
| `/inspect` | POST | `{target?, id}` | `{ok:true, details: ContainerDetail[]}` |
|
|
486
496
|
| `/stats` | POST | `{target?, ids?: string[]}` | `{ok:true, stats: ContainerStats[]}` |
|
|
487
497
|
| `/logs` | POST | `{target?, id, tail?, timestamps?, since?}` | `{ok:true, logs:{id, text, truncated}}` |
|
|
@@ -520,7 +530,7 @@ abort)、客户端断开静默中止。各自只差执行器与结束原因:
|
|
|
520
530
|
| 事件 | data | 说明 |
|
|
521
531
|
| --- | --- | --- |
|
|
522
532
|
| `line` | `{"d":"..."}` / `{"e":"..."}` | stdout / stderr 分片(不保证按行切,客户端自行拼行)。日志流与拉取流用 |
|
|
523
|
-
| `stats` | `ContainerStats` | 统计流专用:每秒每个容器一帧,字段与
|
|
533
|
+
| `stats` | `ContainerStats` | 统计流专用:每秒每个容器一帧,字段与 `/stats` 快照完全一致。服务端按**扁平 `{...}` 抽取**(`docker stats` 即便 stdout 是管道也走 TTY 渲染器,帧里混着 `ESC[H/ESC[K/ESC[J`,按行解析会整行丢掉)并折叠同一采样的重复渲染,客户端不必再解析 docker 的 PascalCase 字符串 |
|
|
524
534
|
| `event` | `{action, name, image, composeProject?, time?, exitCode?}` | 事件流专用:一帧一个容器事件(白名单外的事件行在服务端就丢了;值为 null 的字段省略) |
|
|
525
535
|
| `end` | `{"reason":"container-exit"\|"stats-exit"\|"events-exit"\|"pull-exit","code":N, ...}` | 执行器自然退出。日志流附容器退出码、统计流 reason=`stats-exit`、事件流 reason=`events-exit`、拉取流附 `ref` |
|
|
526
536
|
| `error` | `{"message":"..."}` | 参数 / 执行失败,随后关闭;连接层断路不会发这个事件 |
|
|
@@ -638,7 +648,7 @@ abort)、客户端断开静默中止。各自只差执行器与结束原因:
|
|
|
638
648
|
(真机实测:命名卷留着,回 `Total reclaimed space: 0B`;匿名卷被删并列出名字)。
|
|
639
649
|
这与本插件「刻意不加 `--all`、避免误删」的取向一致 —— 要删命名卷请用
|
|
640
650
|
`/volumes/remove`(面板的卷删除)。
|
|
641
|
-
- **podman 兼容靠 `dockerBin`**:填 `podman` 即可跑,但 `stats`
|
|
651
|
+
- **podman 兼容靠 `dockerBin`**:填 `podman` 即可跑,但 `stats` 与
|
|
642
652
|
`--format '{{json .}}'` 的字段和输出格式与 docker 有差异,只能依赖解析器
|
|
643
653
|
的降级路径,未逐项验证。
|
|
644
654
|
- **没有镜像构建 / Compose 编排变更**:镜像支持拉取 / 删除 / 清理 dangling,
|
|
@@ -661,9 +671,10 @@ abort)、客户端断开静默中止。各自只差执行器与结束原因:
|
|
|
661
671
|
主机没有对应的 `kind=ssh` 目标(连接簿名或 `host:port` 都匹配不上),点开只会看到
|
|
662
672
|
「尚未配置为 Docker 目标」的提示而不是容器列表;本地标签的连接栏本身隐藏。
|
|
663
673
|
目标增删后最多 30 秒内刷新(设置卡片保存会立即刷新)。
|
|
664
|
-
- **`enabled: false`
|
|
665
|
-
|
|
666
|
-
|
|
674
|
+
- **`enabled: false` 是热生效**:保存后工具立即注销、公告撤下、除 `/config` 外的
|
|
675
|
+
数据路由一律 403(进行中的长流——日志 / 统计 / 拉取——也会被立即收尾)。
|
|
676
|
+
路由对象本身不卸载、靠 403 拦截;`/config` 始终可读写,设置卡片就是重新
|
|
677
|
+
启用的入口,无需重启 `dsh web`。
|
|
667
678
|
- **变更操作无独立审计日志**:只有 docker 自身的记录与宿主 `ctx.logger` 的
|
|
668
679
|
常规输出。
|
|
669
680
|
|
|
@@ -742,8 +753,8 @@ abort)、客户端断开静默中止。各自只差执行器与结束原因:
|
|
|
742
753
|
```bash
|
|
743
754
|
pnpm --filter @hyzyn/dsh-docker build # tsc → lib/(宿主半体)+ esbuild → client.js(浏览器半体)
|
|
744
755
|
pnpm --filter @hyzyn/dsh-docker typecheck
|
|
745
|
-
pnpm --filter @hyzyn/dsh-docker smoke # 三套离线回归,都不需要 docker daemon
|
|
746
|
-
pnpm test # 仓库级 vitest(含本包 logs-stream / streams / ssh-stream-budget
|
|
756
|
+
pnpm --filter @hyzyn/dsh-docker smoke # 三套离线回归,都不需要 docker daemon(项数以脚本尾部自报为准)
|
|
757
|
+
pnpm test # 仓库级 vitest(含本包 config-route / current-session / session-target / logs-stream / streams / ssh-stream-budget / ssh-connect 共七套)
|
|
747
758
|
```
|
|
748
759
|
|
|
749
760
|
> **改了哪一半、怎么才生效**(踩过两次的坑):
|
|
@@ -758,7 +769,7 @@ pnpm test # 仓库级 vitest(含本包 logs
|
|
|
758
769
|
> 逻辑全都不生效,看起来像「改了没用」。判断依据是**文案**——宿主侧新增的提示语如果没出现,
|
|
759
770
|
> 那就是旧进程。
|
|
760
771
|
|
|
761
|
-
`scripts/smoke.mjs
|
|
772
|
+
`scripts/smoke.mjs`(读取 `lib/` 构建产物,项数以脚本尾部自报为准)覆盖纯逻辑:ps 解析(字段映射 /
|
|
762
773
|
compose 标签 / 端口 / `State` 缺失推导 / 噪声行 / JSON 数组)、端口串解析与去重、
|
|
763
774
|
stats 解析(百分比 / 内存 / IO / PIDs)、size 与 percent 的异常输入、images 解析
|
|
764
775
|
(dangling)、**image inspect / history(JSON 与纯文本表格两条路径)解析**、
|
|
@@ -769,7 +780,7 @@ inspect 解析(状态 / 健康 / 退出码 / 挂载 / 网络 / 端口 / 缺字
|
|
|
769
780
|
`normalizeConfig` 默认值与夹紧、`sanitizeTargets` / `sanitizeHostKeys`、
|
|
770
781
|
`resolveTarget` 的四种路径、`mergeTargetSecrets` 的凭证保留语义。
|
|
771
782
|
|
|
772
|
-
`scripts/route-smoke.mjs
|
|
783
|
+
`scripts/route-smoke.mjs` 用**假 cordis ctx + 假 docker CLI 脚本**跑端到端:
|
|
773
784
|
插件挂载(settings / 工具 / 路由 / 能力公告注册)、**26 条路由**的实际调用与返回
|
|
774
785
|
(含 `/logs/stream`、`/stats/stream`、`/events/stream`、`/images/pull/stream` 四条 SSE
|
|
775
786
|
的事件序列与参数校验,事件流另断噪音被白名单丢掉)、`docker_events` 工具的快照
|
|
@@ -779,7 +790,7 @@ inspect 解析(状态 / 健康 / 退出码 / 挂载 / 网络 / 端口 / 缺字
|
|
|
779
790
|
loopback 403(含三条流路由)、容器名 / 镜像引用注入尝试被白名单拒绝、省略
|
|
780
791
|
`target` 的回落与多目标报错、禁用后流路由 403。
|
|
781
792
|
|
|
782
|
-
`scripts/client-smoke.mjs
|
|
793
|
+
`scripts/client-smoke.mjs` 在 Node 里用最小 DOM / React 桩执行构建产物
|
|
783
794
|
`client.js`:验证注册 id 与 factory 形状、只 require 平台 seed 提供的模块
|
|
784
795
|
(`react` / `react/jsx-runtime` / `react-dom/client`)、`apply` 注册的 settings
|
|
785
796
|
卡片 key 等于命名空间 `docker`、找不到宿主侧边栏时安静降级且卸载可重复调用,ttyConnbar 集成的四条路径(连接簿名命中 / host:port 命中 / 未配置主机不加按钮 / tty 未安装静默跳过),FOLLOW 的 SSE 订阅与「回到底部」交互(静态断言),**镜像详情 / 拉取流 / 删除 / prune 入口**、**统计 FOLLOW + sparkline 钩子**、**Compose 分组与聚合日志**、**「活动」条装配 + 事件环形缓冲 / 动作标签 / 防抖**(纯逻辑经 `__events` 测试缝),以及侧边栏折叠态(`data-sidebar-collapsed`)隐藏入口标签的样式规则。
|
|
@@ -814,7 +825,9 @@ network / volume 的 ls·inspect 解析容错(字符串布尔、缺 `Mountpoin
|
|
|
814
825
|
日志正常;首次连接日志里出现「已记录 host key 指纹(TOFU)」,第二次不再
|
|
815
826
|
提示;手动改掉 `hostKeys` 里的指纹后重连,应**被拒绝**并给出重置指引。
|
|
816
827
|
3. **只读拦截**:两个开关都关时,`/action`、`/exec`、`/images/remove`、
|
|
817
|
-
`/images/prune`、`/images/pull/stream` 全部 403;agent
|
|
828
|
+
`/images/prune`、`/images/pull/stream` 全部 403;agent 侧恒注册 11 个只读工具
|
|
829
|
+
(`docker_targets` / `ps` / `attention` / `inspect` / `logs` / `stats` / `events` /
|
|
830
|
+
`images` / `image_inspect` / `networks` / `volumes`,数量以工具表为准),
|
|
818
831
|
面板的启停删 / 镜像删除 / 清理 / 拉取按钮置灰。打开「允许变更操作」后这些
|
|
819
832
|
路由与工具立即出现(无需重启)。
|
|
820
833
|
4. **日志 / 统计 / 镜像**:`tail` 与 `timestamps` / `since` 生效;统计显示
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hyzyn/dsh-docker — 「当前会话」的取值(跨 DSH 版本)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么单独成文件:`client-src/index.js` 是浏览器 IIFE 入口,没有导出,逻辑放在
|
|
5
|
+
* 里面就只能靠真机点。这里是纯函数、不碰模块级缓存,抽出来可以直接用 vitest 钉住
|
|
6
|
+
* (esbuild 打包时按普通本地模块内联,产物形态不变)。
|
|
7
|
+
*
|
|
8
|
+
* 两个版本的事实来源(对着装好的宿主实测 + 官方源码):
|
|
9
|
+
*
|
|
10
|
+
* - **≤ DSH 0.1.5**:会话控制器自己持有视图选中项 —— `sessions.list` 的快照里带
|
|
11
|
+
* `current`(官方注释:「carries `current`, the persisted selection …)」。
|
|
12
|
+
* - **≥ DSH 0.1.6**:选中项搬出了会话域(会话控制器 0.1.6 的接口注释:「Host catalog
|
|
13
|
+
* and local reference allocator; **view selection remains outside the Controller**」),
|
|
14
|
+
* 快照只剩 `ids / byId / phase / subagentsByParent / jobsBySession`。此时的权威标记
|
|
15
|
+
* 是 **`retainedBy.mainView > 0`**:主视图正在展示的会话被视图持有者 retain 着,官方
|
|
16
|
+
* `dsh-client-ui-session` 与本地 codegraph 插件都这么取(见 codegraph 的
|
|
17
|
+
* `activeSessionCwd`)。
|
|
18
|
+
*
|
|
19
|
+
* 只认老字段的实测后果:宿主升到 0.1.6 后 `snapshot.current` 恒为 `undefined`,日志右键
|
|
20
|
+
* 「问 Agent」被判成「当前没有打开的会话」——两个菜单项全灰,功能整体失效且不报错。
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 主视图正在展示的会话 id。
|
|
25
|
+
*
|
|
26
|
+
* 判据 `retainedBy.mainView > 0`:视图持有者(ui-workspace)在切会话时 retain 新会话、
|
|
27
|
+
* release 旧的,所以列表快照里同一时刻只有一行带这个计数。找不到返回 `undefined`。
|
|
28
|
+
*
|
|
29
|
+
* @param snapshot - `sessions.list.getSnapshot()` 的返回值(形状见 SessionListState)。
|
|
30
|
+
*/
|
|
31
|
+
export function mainViewSessionId(snapshot) {
|
|
32
|
+
const byId = snapshot?.byId
|
|
33
|
+
if (byId === null || typeof byId !== 'object') return undefined
|
|
34
|
+
for (const id of Object.keys(byId)) {
|
|
35
|
+
const retained = byId[id]?.retainedBy?.mainView ?? 0
|
|
36
|
+
if (typeof retained === 'number' && retained > 0) return id
|
|
37
|
+
}
|
|
38
|
+
return undefined
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 当前会话 id(跨版本):老版本的 `current` 优先(它在 ≤0.1.5 是权威字段),
|
|
43
|
+
* 没有才看 0.1.6 的 `retainedBy.mainView` 标记。
|
|
44
|
+
*
|
|
45
|
+
* @param snapshot - `sessions.list.getSnapshot()` 的返回值。
|
|
46
|
+
* @returns 会话 id;读不到(没有打开的会话 / 宿主形状不认识)时 `undefined`。
|
|
47
|
+
*/
|
|
48
|
+
export function currentSessionIdOf(snapshot) {
|
|
49
|
+
const legacy = snapshot?.current
|
|
50
|
+
if (typeof legacy === 'string' && legacy !== '') return legacy
|
|
51
|
+
return mainViewSessionId(snapshot)
|
|
52
|
+
}
|