packages/dsh-remote-web-ui
zhu1090093659/dsh-web手机/PC 远程操控 dsh web 工作区:扫码配对、令牌门控通道、SSE 实时同步,提供移动端与完整桌面 GUI 两种远程形态。
DSH Web GUI 多服务器 SSH 终端面板:浮动终端窗口支持标签页、并排组合、广播、宿主侧持久会话,以及密码/私钥/SSH Agent 认证。
Multi-server SSH terminal panel for the DeepSeek Harness (DSH) Web GUI.
Manage a list of SSH servers and open multiple interactive terminals at once — a lightweight, Wave Terminal-style SSH client built into your DSH conversation. Terminals live in one Terminal Window floating over the GUI: drag it anywhere, resize it, maximize it with a double-click, and arrange sessions as tabs or side-by-side groups — merge two tabs by dragging one onto the other, reorder group members, magnify one to fill the window.
</kbd> or the SSH 终端 sidebar button): drag by the title bar (kept on screen), resize from the corner, double-click to maximize. <kbd>Esc</kbd> walks out of magnify/maximize one level; closing the window is the explicit ✕. Opening uses a subtle animation (disabled under prefers-reduced-motion`); when the window loses focus only the frame dims — terminal output always stays readableprefers-color-scheme when the theme service is absent), with a toolbar cycle button to pin 跟随界面 / 深色 / 浅色 (auto / dark / light) per browser. Open terminals hot-swap in place. Both palettes are held to WCAG contrast floors (foreground/background ≥ 7:1, ANSI colors ≥ 4.5:1) enforced in npm test0600), are never returned by the API, and can be kept unchanged on editdsh ≥ 0.1.0-rc.7). The Server Defaults settings card requires 0.1.0-rc.7+; on older builds the panel works exactly as before, just without the settings card. The panel is bind-address agnostic: it works identically whether DSH listens on the default loopback 127.0.0.1:3080 or on 0.0.0.0 for LAN / reverse-proxy access — all requests are derived from the page origin, so no configuration is needed either way. See the security note below before exposing DSH on a non-loopback address.From the GitHub repository (recommended):
dsh plugin --profile web add github:JUNQINGV587/dsh-ssh-hub
Git-hosted installs build the package on the spot, and pnpm blocks build scripts by default. If the add fails with an "Ignored build scripts" error, add the build key pnpm printed under
allowBuildsin the profile'spnpm-workspace.yaml, then re-run the add.
or from npm, once published:
dsh plugin --profile web add dsh-ssh-hub
or install from a local checkout:
dsh plugin --profile web add /path/to/dsh-ssh-hub
Restart DSH afterwards, refresh the browser, and the terminal panel is available.
Click SSH 终端 at the sidebar foot, or press Ctrl+Shift+` — the Terminal Window opens (centred, remembered position/size). Double-click its title bar to maximize; Esc exits magnify/maximize one level (closing the window is the explicit ✕). Drag the title bar to move (it stays on screen), the corner handle to resize.
Click 管理服务器 (Manage servers) → 添加服务器 (Add server) and fill in:
| Field | Description |
|---|---|
| 名称 (Name) | Display name, e.g. prod-db-1 |
| 主机 (Host) | IP or hostname |
| 端口 (Port) | SSH port, default 22 |
| 用户名 (Username) | SSH login user |
| 认证方式 (Auth) | password / privateKey / SSH Agent / none |
| 密码 / 私钥 | Secret — left blank on edit keeps the stored one |
| 远程初始目录 (Cwd) | Optional initial working directory on the remote |
| 连接超时 (Ready timeout) | Optional, in seconds; blank = inherit the Server Default (default 15 s) |
| Keepalive 间隔 | Optional, in seconds, 0 disables; blank = inherit the Server Default (default 30 s) |
| 严格主机密钥校验 (Strict host key) | Inherit / on / off; on requires a known-hosts entry |
Use 测试连接 (Test) to verify before saving.
Open a session: click the 新会话 (New session) button in the window's tab bar and pick a server (a session opens as its own tab; 加入当前 appends it to the active group). Or use the 会话 (Sessions) panel, or 管理服务器 (Manage servers) → click/double-click a row. The empty state guides you by what you have: no servers → 添加第一台服务器; servers but no sessions → 新会话; sessions in the background → 打开会话面板.
Watch several at once: click the 新建组合 (New group) button, tick the sessions, and they open side-by-side. Or drag a tab onto another tab to merge them into a group, and onto a group's tab to append a member. Connect more servers with 加入当前 (Join current) to append them to the active group; drag the dividers to resize; magnify a member (hover it, or Alt+m) to fill the window. Drag members within a group to reorder them (before/after/swap); drag one out onto the tab strip to make it its own tab again. A group's ✕ dissolves it back into its member tabs.
Sessions you're not watching: closing a tab removes the view only — the session keeps running on the host (a toast reminds you) and appears in the 会话 (Sessions) panel. From there you can place it back (放入当前 into the active item, or 新标签 as its own tab) or terminate it (终止, two clicks to confirm). A detached session shows its reclaim countdown in the panel.
Type, select-to-copy, right-click-to-paste. Theme: the toolbar 跟随界面 / 深色 / 浅色 button cycles the terminal theme; remembered per browser, applied to every open terminal instantly.
Your shells survive: closing the window, removing a view, refreshing the page, or switching conversations detaches the UI but the sessions keep running on the host, reattaching with recent output replayed. A brief network blip reconnects automatically. Closing a session for good: 终止 it in the 会话 (Sessions) panel (or delete the server). Sessions with no viewers are reclaimed after 30 minutes by default — the window is configurable in the settings card (会话回收时长), and the countdown shows in the window's status strip. If DSH restarts (sessions and layout live in memory), the window comes back empty — the status strip offers 恢复上次布局 (Restore last layout) to reopen the same sessions.
Shortcuts are configurable: 设置 → 插件 → 插件配置 → DSH-SSH-HUB → 快捷键 (toggle window, maximize, new tab, close block, close tab, magnify). Values are validated, DSH conflicts warn (not blocked), and changes apply immediately.
On DSH ≥ 0.1.0-rc.7, the same card also sets the Server Defaults blank server fields inherit — including a default terminal theme used when a browser's override is 跟随界面. The card's 管理服务器… link jumps straight to the window's server drawer.
Moving to a new machine? Use 导出配置 (Export) / 导入配置 (Import) in the manage-servers dialog. The exported JSON contains no secrets — re-enter passwords/keys after importing. Import always adds entries as new servers and never overwrites existing ones.
$DSH_HOME/plugin-data/ssh-hub/servers.json (default ~/.dsh/…), written with mode 0600. File permissions are the only line of defense — do not commit, sync, or back up this file anywhere plaintext credentials would be unacceptable. Machine-key encryption was considered and rejected: a process running as your user could read the key anyway (see docs/adr/0001-credential-security-posture.md).SSH Agent wipes the stored password).hasPassword / hasPrivateKey flags. The export file follows the same rule.strictHostKey (default off, or the Server Default you set); turn it on for stricter verification.0.0.0.0 (or any non-loopback address) exposes this panel to your network. The DSH webserver has no authentication by design, and the same-origin gate deliberately allows requests without an Origin header (non-browser clients) — so anyone who can reach the port (e.g. via a LAN IP) can list your configured servers (host/port/username) and open SSH terminals. If you need remote access, put DSH behind an authenticating reverse proxy or restrict the port at the firewall; do not rely on the same-origin gate as an access control.npm install
npm run build # bundles lib/index.js (host) + lib/client.js (client) + lib/client.css
npm test # integration tests against a local test sshd (see tests/)
# after bumping + committing a version, keep package.json / tag / release in sync:
npm version minor --no-git-tag-version && git commit -am "chore: bump version"
scripts/release.sh # tags HEAD, pushes, and creates the GitHub release
src/host/) is a cordis plugin (inject: ['webServer']) exposing a REST API under /ssh-hub plus per-session WebSocket upgrade routes. SSH is driven by ssh2. Terminal Sessions are host-owned: they survive any client detaching, keep a bounded scrollback ring (replayed on reattach), and are reclaimed after an idle window without viewers (default 30 min, configurable — src/host/registry.ts, src/host/scrollback.ts). The global item collection (tabs and flat groups) is served at /ssh-hub/workspace and broadcast over /workspace/events; dead-session items are dropped everywhere by the host.src/client/) is a prebuilt React bundle: the Terminal Window renders in the shell.overlay slot (frame-wide, root scope), and a sidebar entry in sidebar.footer.action opens it. The flat groups (src/shared/group.mjs) and keybinding parsing (src/shared/keybind.mjs) are pure DOM-free modules. Terminals use @xterm/xterm.xterm → ws → ssh2 stream → remote shell, and back. Windowed and maximized states attach to the same sessions (multi-client broadcast).tests/integration.mjs spins up a mock of the DSH server (HTTP + WS), applies the plugin, and drives a real SSH session against a test sshd (default 127.0.0.1:2222, key auth). Override with SSH_TEST_HOST, SSH_TEST_PORT, SSH_TEST_KEY. See scripts/setup-test-sshd.sh for the CI-ready test daemon setup.
npm test runs scripts/check-contrast.mjs and scripts/check-splittree.mjs first (Terminal Theme contrast floors — see docs/adr/0002-adaptive-terminal-theme.md — and the pure split-tree rules), then the integration suite.
MIT © JUNQINGV587