dsh-over-ssh 0.1.0 → 0.2.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.md +19 -17
- package/agents/agent-manifest.json +6 -6
- package/agents/linux-amd64/dsh-over-ssh-agent +0 -0
- package/agents/linux-arm64/dsh-over-ssh-agent +0 -0
- package/lib/client.js +422 -337
- package/lib/file-references.js +92 -35
- package/lib/fs.js +295 -113
- package/lib/index.js +6912 -4330
- package/lib/terminal-wrapper.js +9 -4
- package/lib/types/src/config.d.ts +156 -9
- package/lib/types/src/file-references.d.ts +0 -1
- package/lib/types/src/host/app/backend.d.ts +44 -3
- package/lib/types/src/host/app/recycle-bin/recycle-bin-contract.d.ts +0 -1
- package/lib/types/src/host/app/recycle-bin/recycle-bin-service.d.ts +0 -1
- package/lib/types/src/host/app/session-admin/session-admin-contract.d.ts +12 -1
- package/lib/types/src/host/app/session-admin/session-admin-service.d.ts +0 -1
- package/lib/types/src/host/app/transport/transport-selector.d.ts +0 -1
- package/lib/types/src/host/app/transport/transport-service.d.ts +0 -1
- package/lib/types/src/host/domain/capability.d.ts +0 -1
- package/lib/types/src/host/domain/errors.d.ts +0 -1
- package/lib/types/src/host/domain/roots/connection.d.ts +0 -1
- package/lib/types/src/host/domain/roots/root.d.ts +24 -9
- package/lib/types/src/host/domain/roots/session-roots.d.ts +23 -1
- package/lib/types/src/host/domain/roots/ssh-config.d.ts +0 -1
- package/lib/types/src/host/domain/session/session-admin.d.ts +0 -1
- package/lib/types/src/host/domain/transfer/recycle-bin.d.ts +0 -1
- package/lib/types/src/host/domain/transfer/transfer.d.ts +0 -1
- package/lib/types/src/host/infra/transport/agent/agent-installer.d.ts +0 -1
- package/lib/types/src/host/infra/transport/forward.d.ts +75 -0
- package/lib/types/src/host/infra/transport/ssh/ssh2-transport.d.ts +5 -1
- package/lib/types/src/host/infra/transport/transport-base.d.ts +0 -1
- package/lib/types/src/host/infra/transport/transport.d.ts +42 -2
- package/lib/types/src/host/integration/agent/agent-preflight.d.ts +0 -1
- package/lib/types/src/host/integration/fs/file-reference-provider.d.ts +0 -1
- package/lib/types/src/host/integration/fs/fs-provider.d.ts +18 -3
- package/lib/types/src/host/integration/transfer/transfer-service.d.ts +0 -1
- package/lib/types/src/index.d.ts +23 -1
- package/lib/types/src/local-bridge-refs.d.ts +0 -1
- package/lib/types/src/local-bridge.d.ts +0 -1
- package/lib/types/src/shared/root-keys.d.ts +120 -0
- package/lib/types/src/wiring/helpers.d.ts +0 -1
- package/package.json +5 -3
- package/lib/types/src/client/controls.d.ts +0 -131
- package/lib/types/src/client/core/card-focus-trap.d.ts +0 -45
- package/lib/types/src/client/core/controls.d.ts +0 -164
- package/lib/types/src/client/core/latest.d.ts +0 -48
- package/lib/types/src/client/core/locale.d.ts +0 -38
- package/lib/types/src/client/core/message.d.ts +0 -37
- package/lib/types/src/client/core/panel-model.d.ts +0 -134
- package/lib/types/src/client/core/portal.d.ts +0 -266
- package/lib/types/src/client/core/remote.d.ts +0 -84
- package/lib/types/src/client/core/styles.d.ts +0 -458
- package/lib/types/src/client/file-tree-io.d.ts +0 -19
- package/lib/types/src/client/file-tree.d.ts +0 -271
- package/lib/types/src/client/flows/directory-browser.d.ts +0 -587
- package/lib/types/src/client/flows/session-directory.d.ts +0 -415
- package/lib/types/src/client/flows/workspace-directory-flow.d.ts +0 -480
- package/lib/types/src/client/locale.d.ts +0 -31
- package/lib/types/src/client/panel-model.d.ts +0 -37
- package/lib/types/src/client/panel.d.ts +0 -77
- package/lib/types/src/client/roots/connection-test.d.ts +0 -69
- package/lib/types/src/client/roots/roots-panel-state.d.ts +0 -307
- package/lib/types/src/client/roots/roots-panel-view.d.ts +0 -280
- package/lib/types/src/client/roots/roots-panel.d.ts +0 -364
- package/lib/types/src/client/roots/transfer-panel-view.d.ts +0 -35
- package/lib/types/src/client/roots/transfer-panel.d.ts +0 -241
- package/lib/types/src/client/roots/transfer-progress.d.ts +0 -67
- package/lib/types/src/client/roots-panel-view.d.ts +0 -236
- package/lib/types/src/client/roots-panel.d.ts +0 -347
- package/lib/types/src/client/session/session-panel-view.d.ts +0 -336
- package/lib/types/src/client/session/session-panel.d.ts +0 -322
- package/lib/types/src/client/session/session-remote-mark.d.ts +0 -110
- package/lib/types/src/client/session/session-row-actions.d.ts +0 -398
- package/lib/types/src/client/session-panel.d.ts +0 -152
- package/lib/types/src/client/styles.d.ts +0 -252
- package/lib/types/src/client/workspace-directory-flow.d.ts +0 -342
- package/lib/types/src/fs.d.ts +0 -46
- package/lib/types/src/host/app/recycle-bin/recycle-bin-move.d.ts +0 -18
- package/lib/types/src/host/app/recycle-bin/recycle-bin-world.d.ts +0 -38
- package/lib/types/src/host/app/recycle-bin-contract.d.ts +0 -215
- package/lib/types/src/host/app/recycle-bin-move.d.ts +0 -18
- package/lib/types/src/host/app/recycle-bin-service.d.ts +0 -45
- package/lib/types/src/host/app/recycle-bin-world.d.ts +0 -35
- package/lib/types/src/host/app/roots/roots-api-connections.d.ts +0 -49
- package/lib/types/src/host/app/roots/roots-api-contract.d.ts +0 -369
- package/lib/types/src/host/app/roots/roots-api-directories.d.ts +0 -13
- package/lib/types/src/host/app/roots/roots-api-http.d.ts +0 -77
- package/lib/types/src/host/app/roots/roots-api-long-poll.d.ts +0 -130
- package/lib/types/src/host/app/roots/roots-api-projections.d.ts +0 -102
- package/lib/types/src/host/app/roots/roots-api-roots.d.ts +0 -13
- package/lib/types/src/host/app/roots/roots-api-state.d.ts +0 -22
- package/lib/types/src/host/app/roots/roots-api-transfers.d.ts +0 -26
- package/lib/types/src/host/app/roots/roots-api.d.ts +0 -55
- package/lib/types/src/host/app/roots/user-roots.d.ts +0 -552
- package/lib/types/src/host/app/roots-api-connections.d.ts +0 -49
- package/lib/types/src/host/app/roots-api-contract.d.ts +0 -339
- package/lib/types/src/host/app/roots-api-directories.d.ts +0 -13
- package/lib/types/src/host/app/roots-api-http.d.ts +0 -77
- package/lib/types/src/host/app/roots-api-projections.d.ts +0 -102
- package/lib/types/src/host/app/roots-api-roots.d.ts +0 -13
- package/lib/types/src/host/app/roots-api-state.d.ts +0 -22
- package/lib/types/src/host/app/roots-api-support.d.ts +0 -182
- package/lib/types/src/host/app/roots-api-transfers.d.ts +0 -13
- package/lib/types/src/host/app/roots-api.d.ts +0 -55
- package/lib/types/src/host/app/routed-fs.d.ts +0 -239
- package/lib/types/src/host/app/serialize.d.ts +0 -37
- package/lib/types/src/host/app/session-admin/session-admin-context.d.ts +0 -36
- package/lib/types/src/host/app/session-admin/session-admin-delete.d.ts +0 -34
- package/lib/types/src/host/app/session-admin-context.d.ts +0 -36
- package/lib/types/src/host/app/session-admin-contract.d.ts +0 -214
- package/lib/types/src/host/app/session-admin-delete.d.ts +0 -34
- package/lib/types/src/host/app/session-admin-service.d.ts +0 -38
- package/lib/types/src/host/app/takeover/takeover-plan.d.ts +0 -227
- package/lib/types/src/host/app/takeover/takeover-rows.d.ts +0 -55
- package/lib/types/src/host/app/transport-selector.d.ts +0 -98
- package/lib/types/src/host/app/transport-service.d.ts +0 -75
- package/lib/types/src/host/app/user-roots.d.ts +0 -461
- package/lib/types/src/host/domain/bin-location.d.ts +0 -79
- package/lib/types/src/host/domain/connection.d.ts +0 -95
- package/lib/types/src/host/domain/glob-pattern.d.ts +0 -62
- package/lib/types/src/host/domain/literal-text.d.ts +0 -63
- package/lib/types/src/host/domain/path-grammar.d.ts +0 -84
- package/lib/types/src/host/domain/recycle-bin.d.ts +0 -337
- package/lib/types/src/host/domain/root.d.ts +0 -74
- package/lib/types/src/host/domain/roots/bin-location.d.ts +0 -91
- package/lib/types/src/host/domain/roots/route-table.d.ts +0 -102
- package/lib/types/src/host/domain/route-table.d.ts +0 -80
- package/lib/types/src/host/domain/session/terminal-target.d.ts +0 -136
- package/lib/types/src/host/domain/session-admin.d.ts +0 -330
- package/lib/types/src/host/domain/shell.d.ts +0 -47
- package/lib/types/src/host/domain/ssh-config.d.ts +0 -73
- package/lib/types/src/host/domain/terminal-target.d.ts +0 -132
- package/lib/types/src/host/domain/tool-requests.d.ts +0 -369
- package/lib/types/src/host/domain/transfer.d.ts +0 -260
- package/lib/types/src/host/infra/backend/local-backend.d.ts +0 -84
- package/lib/types/src/host/infra/backend/local-version.d.ts +0 -43
- package/lib/types/src/host/infra/backend/platform-local-backend.d.ts +0 -89
- package/lib/types/src/host/infra/backend/remote-backend.d.ts +0 -239
- package/lib/types/src/host/infra/cache/lru.d.ts +0 -52
- package/lib/types/src/host/infra/cache/versioned.d.ts +0 -77
- package/lib/types/src/host/infra/temp-name.d.ts +0 -19
- package/lib/types/src/host/infra/transport/agent/agent-protocol-client.d.ts +0 -132
- package/lib/types/src/host/infra/transport/agent/agent-protocol-codec.d.ts +0 -140
- package/lib/types/src/host/infra/transport/agent/agent-protocol-errors.d.ts +0 -41
- package/lib/types/src/host/infra/transport/agent/agent-protocol-frames.d.ts +0 -197
- package/lib/types/src/host/infra/transport/agent/agent-protocol-types.d.ts +0 -564
- package/lib/types/src/host/infra/transport/agent/agent-transport.d.ts +0 -418
- package/lib/types/src/host/infra/transport/agent-installer.d.ts +0 -121
- package/lib/types/src/host/infra/transport/agent-protocol-client.d.ts +0 -123
- package/lib/types/src/host/infra/transport/agent-protocol-codec.d.ts +0 -131
- package/lib/types/src/host/infra/transport/agent-protocol-errors.d.ts +0 -41
- package/lib/types/src/host/infra/transport/agent-protocol-frames.d.ts +0 -197
- package/lib/types/src/host/infra/transport/agent-protocol-types.d.ts +0 -509
- package/lib/types/src/host/infra/transport/agent-transport.d.ts +0 -353
- package/lib/types/src/host/infra/transport/alias-transport.d.ts +0 -206
- package/lib/types/src/host/infra/transport/askpass-fs.d.ts +0 -20
- package/lib/types/src/host/infra/transport/askpass.d.ts +0 -133
- package/lib/types/src/host/infra/transport/command-pool.d.ts +0 -61
- package/lib/types/src/host/infra/transport/fs-commands.d.ts +0 -140
- package/lib/types/src/host/infra/transport/git-status-route.d.ts +0 -63
- package/lib/types/src/host/infra/transport/git-status.d.ts +0 -101
- package/lib/types/src/host/infra/transport/node-spawn.d.ts +0 -26
- package/lib/types/src/host/infra/transport/session-window.d.ts +0 -51
- package/lib/types/src/host/infra/transport/shell-quote.d.ts +0 -59
- package/lib/types/src/host/infra/transport/ssh/alias-transport.d.ts +0 -206
- package/lib/types/src/host/infra/transport/ssh/askpass-fs.d.ts +0 -20
- package/lib/types/src/host/infra/transport/ssh/askpass.d.ts +0 -133
- package/lib/types/src/host/infra/transport/ssh/shell-quote.d.ts +0 -62
- package/lib/types/src/host/infra/transport/ssh/ssh-argv.d.ts +0 -43
- package/lib/types/src/host/infra/transport/ssh-argv.d.ts +0 -43
- package/lib/types/src/host/infra/transport/ssh2-transport.d.ts +0 -302
- package/lib/types/src/host/integration/agent/agent-install-io.d.ts +0 -33
- package/lib/types/src/host/integration/agent/askpass-registry.d.ts +0 -66
- package/lib/types/src/host/integration/agent-install-io.d.ts +0 -33
- package/lib/types/src/host/integration/agent-preflight.d.ts +0 -56
- package/lib/types/src/host/integration/anchor-directory.d.ts +0 -56
- package/lib/types/src/host/integration/askpass-registry.d.ts +0 -59
- package/lib/types/src/host/integration/bin-root-resolver.d.ts +0 -81
- package/lib/types/src/host/integration/connection-test.d.ts +0 -73
- package/lib/types/src/host/integration/file-reference-provider.d.ts +0 -115
- package/lib/types/src/host/integration/fs/git-status-wiring.d.ts +0 -21
- package/lib/types/src/host/integration/fs/platform-errors.d.ts +0 -94
- package/lib/types/src/host/integration/fs/remote-file-tools.d.ts +0 -369
- package/lib/types/src/host/integration/fs/remote-index.d.ts +0 -177
- package/lib/types/src/host/integration/fs-provider.d.ts +0 -226
- package/lib/types/src/host/integration/lsp/lsp-framing.d.ts +0 -38
- package/lib/types/src/host/integration/lsp/lsp-registry.d.ts +0 -67
- package/lib/types/src/host/integration/lsp/lsp-session.d.ts +0 -130
- package/lib/types/src/host/integration/management-tools.d.ts +0 -64
- package/lib/types/src/host/integration/recycle-bin-service.d.ts +0 -247
- package/lib/types/src/host/integration/remote/connection-test.d.ts +0 -73
- package/lib/types/src/host/integration/remote/remote-face.d.ts +0 -127
- package/lib/types/src/host/integration/remote/remote-tool-backend.d.ts +0 -108
- package/lib/types/src/host/integration/remote-face.d.ts +0 -127
- package/lib/types/src/host/integration/remote-file-tools.d.ts +0 -241
- package/lib/types/src/host/integration/remote-home.d.ts +0 -69
- package/lib/types/src/host/integration/remote-index.d.ts +0 -165
- package/lib/types/src/host/integration/remote-job-tools.d.ts +0 -295
- package/lib/types/src/host/integration/remote-tool-backend.d.ts +0 -97
- package/lib/types/src/host/integration/remote-tools.d.ts +0 -206
- package/lib/types/src/host/integration/root-registry.d.ts +0 -115
- package/lib/types/src/host/integration/roots/anchor-directory.d.ts +0 -56
- package/lib/types/src/host/integration/roots/bin-root-resolver.d.ts +0 -81
- package/lib/types/src/host/integration/roots/remote-home.d.ts +0 -69
- package/lib/types/src/host/integration/roots/root-registry.d.ts +0 -144
- package/lib/types/src/host/integration/roots/roots-wiring.d.ts +0 -219
- package/lib/types/src/host/integration/roots/session-root-index.d.ts +0 -52
- package/lib/types/src/host/integration/roots-api.d.ts +0 -407
- package/lib/types/src/host/integration/roots-wiring.d.ts +0 -211
- package/lib/types/src/host/integration/session/session-api.d.ts +0 -88
- package/lib/types/src/host/integration/session/session-bin-lock.d.ts +0 -80
- package/lib/types/src/host/integration/session/session-header.d.ts +0 -151
- package/lib/types/src/host/integration/session/session-labels.d.ts +0 -56
- package/lib/types/src/host/integration/session/session-route-wiring.d.ts +0 -50
- package/lib/types/src/host/integration/session-admin-service.d.ts +0 -236
- package/lib/types/src/host/integration/session-api.d.ts +0 -81
- package/lib/types/src/host/integration/session-bin-lock.d.ts +0 -80
- package/lib/types/src/host/integration/session-header.d.ts +0 -118
- package/lib/types/src/host/integration/session-labels.d.ts +0 -56
- package/lib/types/src/host/integration/session-route-wiring.d.ts +0 -50
- package/lib/types/src/host/integration/session-scope.d.ts +0 -113
- package/lib/types/src/host/integration/session-tools.d.ts +0 -44
- package/lib/types/src/host/integration/strict-arguments.d.ts +0 -79
- package/lib/types/src/host/integration/takeover/takeover-api.d.ts +0 -62
- package/lib/types/src/host/integration/takeover/takeover-route-wiring.d.ts +0 -60
- package/lib/types/src/host/integration/takeover/takeover-service.d.ts +0 -167
- package/lib/types/src/host/integration/terminal-manifest.d.ts +0 -94
- package/lib/types/src/host/integration/tools/management-tools.d.ts +0 -245
- package/lib/types/src/host/integration/tools/remote-job-tools.d.ts +0 -316
- package/lib/types/src/host/integration/tools/remote-lsp-tools.d.ts +0 -43
- package/lib/types/src/host/integration/tools/remote-tools.d.ts +0 -236
- package/lib/types/src/host/integration/tools/session-target.d.ts +0 -52
- package/lib/types/src/host/integration/tools/session-tools.d.ts +0 -46
- package/lib/types/src/host/integration/tools/strict-arguments.d.ts +0 -79
- package/lib/types/src/host/integration/tools/terminal-manifest.d.ts +0 -94
- package/lib/types/src/host/integration/transfer/remote-copy.d.ts +0 -66
- package/lib/types/src/host/integration/transfer/transfer-adapters.d.ts +0 -48
- package/lib/types/src/host/integration/transfer/transfer-tracker.d.ts +0 -106
- package/lib/types/src/host/integration/transfer-adapters.d.ts +0 -44
- package/lib/types/src/host/integration/transfer-service.d.ts +0 -164
- package/lib/types/src/host/integration/transfer-tracker.d.ts +0 -106
- package/lib/types/src/host/integration/user-roots.d.ts +0 -461
- package/lib/types/src/wiring/connections.d.ts +0 -189
- package/lib/types/src/wiring/fs.d.ts +0 -13
- package/lib/types/src/wiring/install-memo.d.ts +0 -32
- package/lib/types/src/wiring/legacy-dirs.d.ts +0 -62
- package/lib/types/src/wiring/package-paths.d.ts +0 -35
- package/lib/types/src/wiring/roots.d.ts +0 -21
- package/lib/types/src/wiring/state-dir.d.ts +0 -29
- package/lib/types/src/wiring/tools.d.ts +0 -35
|
@@ -1,364 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The roots panel's whole decision surface: wire parsing, the HTTP calls to the host route, input validation
|
|
3
|
-
* and the view model. **No React, no DOM, no `@deepseek-ai/*`** — the component renders what this module
|
|
4
|
-
* returns and nothing else, which is what makes the panel testable without a browser (红线 2).
|
|
5
|
-
*
|
|
6
|
-
* Two rules this module owns:
|
|
7
|
-
*
|
|
8
|
-
* 1. **No credentials, ever.** {@link parseRootsSnapshot} copies field by field and drops anything else, so a
|
|
9
|
-
* host answer that leaked `keyPath` / `password` still cannot reach the DOM. The host route filters its
|
|
10
|
-
* side; this is the client's half of the same rule.
|
|
11
|
-
* 2. **Password authentication is not editable here.** A password must never round-trip through a browser
|
|
12
|
-
* form and a same-origin POST, so {@link validateConnectionDraft} refuses it outright rather than
|
|
13
|
-
* growing a field that leaks secrets into web storage.
|
|
14
|
-
*
|
|
15
|
-
* @module dsh-over-ssh/client/roots-panel
|
|
16
|
-
*/
|
|
17
|
-
import type { MessageKey } from '../core/message.ts';
|
|
18
|
-
/**
|
|
19
|
-
* Where the host route lives, as the browser must request it.
|
|
20
|
-
*
|
|
21
|
-
* **Relative on purpose, and this is the platform's own convention.** The GUI is served from two different
|
|
22
|
-
* origins depending on the carrier, and only a relative path is correct in both:
|
|
23
|
-
*
|
|
24
|
-
* - `dsh web` (browser): the page origin *is* the host, so `/api/…` reaches
|
|
25
|
-
* `ctx.webServer` directly;
|
|
26
|
-
* - the Desktop shell: the page is `dsh-app://app/` and `/api/…` resolves to
|
|
27
|
-
* `dsh-app://app/api/…`, which Electron's `protocol.handle('dsh-app')` forwards to the owned host with
|
|
28
|
-
* the browser-session cookie the shell keeps out of the page
|
|
29
|
-
* (`resources/app.asar/lib/main.js`, `forwardWebRequest`: non-asset paths are proxied with
|
|
30
|
-
* `headers.set('cookie', cookie)`; `serveWebDocument` never sees `/api/…`).
|
|
31
|
-
*
|
|
32
|
-
* The absolute host origin (`globalThis.__DSH_TRANSPORT__.streamBaseUrl`) is deliberately **not** used here:
|
|
33
|
-
* the shell strips `set-cookie` from proxied responses and the session cookie is host-only + `HttpOnly`, so
|
|
34
|
-
* a direct fetch to the host origin cannot authenticate — it would turn a working request into a 401. The
|
|
35
|
-
* platform uses that origin for the WebSocket only (`dsh-api-gateway`, `REMOTE_STREAM_MUX_PATH`), and its
|
|
36
|
-
* own HTTP callers resolve against `document.baseURI` (`dsh-client-file-upload`, `new URL(request.path,
|
|
37
|
-
* document.baseURI)`).
|
|
38
|
-
*/
|
|
39
|
-
export declare const ROOTS_API_PATH = "/api/over-ssh/roots";
|
|
40
|
-
/** A root either lives on this machine or behind a connection. */
|
|
41
|
-
export type RootLocation = 'local' | 'remote';
|
|
42
|
-
/** One root as the panel shows it. */
|
|
43
|
-
export interface RootRow {
|
|
44
|
-
/** Stable identity: `<location>:<connectionId or '-'>:<path>`. */
|
|
45
|
-
readonly key: string;
|
|
46
|
-
readonly location: RootLocation;
|
|
47
|
-
readonly path: string;
|
|
48
|
-
readonly connectionId?: string;
|
|
49
|
-
readonly label?: string;
|
|
50
|
-
readonly writable: boolean;
|
|
51
|
-
/** `config` rows come from the profile patch; `user` rows were added in this panel. */
|
|
52
|
-
readonly source: 'config' | 'user';
|
|
53
|
-
/** Whether this root also appears in the panel's own file — only those may be deleted. */
|
|
54
|
-
readonly inUserFile: boolean;
|
|
55
|
-
/**
|
|
56
|
-
* The workspaces that list this root; absent means **no session lists it yet**.
|
|
57
|
-
*
|
|
58
|
-
* The composer's card shows exactly the roots whose list names its own workspace (one session must not
|
|
59
|
-
* inherit another's directories). The settings panel ignores it, because that panel manages the whole list
|
|
60
|
-
* and registers roots with **no** scope of its own — its form has no `workspaces` member (`RootsPanelDrafts`),
|
|
61
|
-
* so a root added there is not listed by any session until one adds it (audit 0056).
|
|
62
|
-
*/
|
|
63
|
-
readonly workspaces?: readonly string[];
|
|
64
|
-
/**
|
|
65
|
-
* The **sessions** that added this root (ADR-0013); absent on entries written before it.
|
|
66
|
-
*
|
|
67
|
-
* The composer's card prefers this over `workspaces`: two conversations opened on the same directory share
|
|
68
|
-
* the workspace path and differ only here.
|
|
69
|
-
*/
|
|
70
|
-
readonly sessions?: readonly string[];
|
|
71
|
-
}
|
|
72
|
-
/** A connection, narrowed to what the panel may display. **No credential fields exist on this type.** */
|
|
73
|
-
export interface ConnectionSummary {
|
|
74
|
-
readonly id: string;
|
|
75
|
-
readonly host: string;
|
|
76
|
-
readonly user?: string;
|
|
77
|
-
readonly port?: number;
|
|
78
|
-
readonly authKind: 'agent' | 'key' | 'password';
|
|
79
|
-
/**
|
|
80
|
-
* The transport in force, **when the host reports it**.
|
|
81
|
-
*
|
|
82
|
-
* `publicRoot`/`publicUserState` do not carry it today (the transport is a live fact of the platform's remote
|
|
83
|
-
* namespace, not of the profile), so production answers leave this `undefined` and the panel says 「未知」
|
|
84
|
-
* rather than claiming `unknown` — but a host that starts publishing it is honoured without another round
|
|
85
|
-
* (audit 2026-11-01b ui P2-6).
|
|
86
|
-
*/
|
|
87
|
-
readonly transport?: ConnectionTransport;
|
|
88
|
-
/** True when that transport is a degraded substitute, when the host reports it. */
|
|
89
|
-
readonly degraded?: boolean;
|
|
90
|
-
}
|
|
91
|
-
/** The transports a connection row may name; `unknown` means "the host did not say". */
|
|
92
|
-
/**
|
|
93
|
-
* Re-exported from `core/panel-model.ts`, which **owns** it: `core` must not depend on `roots`, so the
|
|
94
|
-
* definition lives in the lower layer and this file points up at it. `export type {…} from` alone does not put
|
|
95
|
-
* the name in this module's scope, hence the separate `import type` as well.
|
|
96
|
-
*/
|
|
97
|
-
export type { ConnectionTransport } from '../core/panel-model.ts';
|
|
98
|
-
import type { ConnectionTransport } from '../core/panel-model.ts';
|
|
99
|
-
/** Everything one GET answers. */
|
|
100
|
-
export interface RootsSnapshot {
|
|
101
|
-
readonly roots: readonly RootRow[];
|
|
102
|
-
readonly connections: readonly ConnectionSummary[];
|
|
103
|
-
readonly warnings: readonly string[];
|
|
104
|
-
/**
|
|
105
|
-
* The plugin's anchor root (`<home>/.dsh/over-ssh/roots`), when the host publishes one.
|
|
106
|
-
*
|
|
107
|
-
* Not a row: it is what lets a client recognise that a session's cwd *is* a remote workspace (see
|
|
108
|
-
* `client/session-remote-mark.ts`). Absent means "the host did not say", and every caller must treat that
|
|
109
|
-
* as unknown rather than as "no remote sessions".
|
|
110
|
-
*/
|
|
111
|
-
readonly anchorRoot?: string | undefined;
|
|
112
|
-
}
|
|
113
|
-
/** The outcome of one call: the panel never throws on a host failure, it shows it. */
|
|
114
|
-
export type ApiResult<T> = {
|
|
115
|
-
readonly ok: true;
|
|
116
|
-
readonly value: T;
|
|
117
|
-
} | {
|
|
118
|
-
readonly ok: false;
|
|
119
|
-
readonly error: string;
|
|
120
|
-
};
|
|
121
|
-
/** What this module needs from `fetch`; injected so tests run without a server (红线 3). */
|
|
122
|
-
export type FetchLike = (input: string, init?: {
|
|
123
|
-
readonly method?: string;
|
|
124
|
-
readonly headers?: Record<string, string>;
|
|
125
|
-
readonly body?: string;
|
|
126
|
-
readonly signal?: AbortSignal;
|
|
127
|
-
}) => Promise<{
|
|
128
|
-
readonly ok: boolean;
|
|
129
|
-
readonly status: number;
|
|
130
|
-
json: () => Promise<unknown>;
|
|
131
|
-
}>;
|
|
132
|
-
/**
|
|
133
|
-
* A message is a key plus its parameters; the component resolves it against the locale dictionaries.
|
|
134
|
-
*
|
|
135
|
-
* The shape (and the resolver, `resolveMessage`) live in `../core/message.ts` because the session panel and the
|
|
136
|
-
* transfer panel produce messages too, and both are pure models that must not import React. Re-exported here so
|
|
137
|
-
* every existing `import { MessageKey } from './roots-panel.ts'` keeps working.
|
|
138
|
-
*/
|
|
139
|
-
export type { MessageKey };
|
|
140
|
-
/** A validated draft, or the reason it was refused. */
|
|
141
|
-
export type Validation<T> = {
|
|
142
|
-
readonly ok: true;
|
|
143
|
-
readonly value: T;
|
|
144
|
-
} | {
|
|
145
|
-
readonly ok: false;
|
|
146
|
-
readonly message: MessageKey;
|
|
147
|
-
};
|
|
148
|
-
/** The payload the panel sends to add a root. */
|
|
149
|
-
export interface RootDraft {
|
|
150
|
-
readonly location: RootLocation;
|
|
151
|
-
readonly path: string;
|
|
152
|
-
readonly connectionId?: string;
|
|
153
|
-
readonly label?: string;
|
|
154
|
-
readonly writable?: boolean;
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* The transport a connection record asks for.
|
|
158
|
-
*
|
|
159
|
-
* It stays in the wire payload type because the host still accepts and persists it, but the **panel no
|
|
160
|
-
* longer offers a choice**: every save asks for `agent`, and the host's selector/preflight degrades to the
|
|
161
|
-
* zero-install transports automatically when the Tier 2 ladder cannot serve the connection. Keeping the type
|
|
162
|
-
* here documents what the payload can contain without re-opening the UI decision.
|
|
163
|
-
*/
|
|
164
|
-
export type ConnectionTransportChoice = 'alias' | 'ssh2' | 'agent';
|
|
165
|
-
/**
|
|
166
|
-
* The payload the panel sends to add a connection.
|
|
167
|
-
*
|
|
168
|
-
* `password` is the **only** secret this client ever holds, and only long enough to post it: the host writes
|
|
169
|
-
* it into `ctx.credentials` and answers with a projection that never contains it (FR-2.1.2).
|
|
170
|
-
*/
|
|
171
|
-
export interface ConnectionDraft {
|
|
172
|
-
readonly id: string;
|
|
173
|
-
readonly host: string;
|
|
174
|
-
readonly user: string;
|
|
175
|
-
readonly authKind: 'agent' | 'key' | 'password';
|
|
176
|
-
/**
|
|
177
|
-
* The transport the payload asks for.
|
|
178
|
-
*
|
|
179
|
-
* Always `agent` now: the panel offers no choice, and the host degrades automatically. Kept in the payload
|
|
180
|
-
* (rather than omitted) so an older record's `alias`/`ssh2` is rewritten to the preferred path on the first
|
|
181
|
-
* save, and so the host keeps the "always explicit" contract it already validates.
|
|
182
|
-
*/
|
|
183
|
-
readonly transport?: ConnectionTransportChoice;
|
|
184
|
-
readonly keyPath?: string;
|
|
185
|
-
readonly password?: string;
|
|
186
|
-
readonly port?: number;
|
|
187
|
-
}
|
|
188
|
-
/** The identity the host also uses, so removals address the same row the list shows. */
|
|
189
|
-
export declare function rootKey(location: RootLocation, path: string, connectionId?: string): string;
|
|
190
|
-
/**
|
|
191
|
-
* Narrow one GET answer to the panel's model, field by field.
|
|
192
|
-
*
|
|
193
|
-
* Unknown fields are dropped rather than copied: an answer carrying `keyPath` reaches the DOM only as a
|
|
194
|
-
* missing field, never as a value. A malformed answer becomes an empty snapshot with a warning instead of a
|
|
195
|
-
* throw — a broken channel must degrade the panel, not break the whole settings page.
|
|
196
|
-
*
|
|
197
|
-
* @param value - the parsed JSON the route answered with.
|
|
198
|
-
* @returns a snapshot that is safe to render.
|
|
199
|
-
*/
|
|
200
|
-
export declare function parseRootsSnapshot(value: unknown): RootsSnapshot;
|
|
201
|
-
/** True when a string is an absolute path this machine can open (drive letter, POSIX root, or UNC). */
|
|
202
|
-
export declare function isAbsoluteLocalPath(path: string): boolean;
|
|
203
|
-
/**
|
|
204
|
-
* Refuse a draft before it becomes a request.
|
|
205
|
-
*
|
|
206
|
-
* @param draft - what the form holds.
|
|
207
|
-
* @param existing - roots already shown, to reject a duplicate.
|
|
208
|
-
* @returns the wire payload, or the message key to show next to the field.
|
|
209
|
-
*/
|
|
210
|
-
export declare function validateRootDraft(draft: RootDraft, existing?: readonly RootRow[]): Validation<RootDraft>;
|
|
211
|
-
/**
|
|
212
|
-
* Refuse a connection draft before it becomes a request.
|
|
213
|
-
*
|
|
214
|
-
* The id rule is the **host's** rule (a user-chosen name, Chinese included), so the panel refuses exactly
|
|
215
|
-
* what `path-grammar.ts` would refuse instead of sending a request that comes back 400.
|
|
216
|
-
*
|
|
217
|
-
* Password auth is supported: the draft carries the password once, the host stores it in `ctx.credentials`,
|
|
218
|
-
* and nothing in any answer ever carries it back (FR-2.1.2).
|
|
219
|
-
*
|
|
220
|
-
* The payload always carries `transport: 'agent'`: the panel no longer offers a choice, and the host downgrades
|
|
221
|
-
* automatically when the Tier 2 ladder cannot serve this connection.
|
|
222
|
-
*
|
|
223
|
-
* @param draft - what the form holds.
|
|
224
|
-
* @param existing - connections already shown, to reject a duplicate id.
|
|
225
|
-
* @returns the wire payload, or the message key to show next to the field.
|
|
226
|
-
*/
|
|
227
|
-
export declare function validateConnectionDraft(draft: ConnectionDraft, existing?: readonly ConnectionSummary[]): Validation<ConnectionDraft>;
|
|
228
|
-
/**
|
|
229
|
-
* Refuse an **edit** draft before it becomes a request.
|
|
230
|
-
*
|
|
231
|
-
* The same rules as {@link validateConnectionDraft} for every field, with the three differences an edit
|
|
232
|
-
* needs, and each one exists for a reason:
|
|
233
|
-
*
|
|
234
|
-
* 1. **A rename is allowed but must not collide with a *different* connection.** Uniqueness is checked
|
|
235
|
-
* against the rows except the one being edited - `validateConnectionDraft` cannot express that, and using
|
|
236
|
-
* it here would refuse every save that kept the id.
|
|
237
|
-
* 2. **An empty password means "do not touch the stored secret", not "refuse".** No answer ever carries a
|
|
238
|
-
* secret back, so the form's password box is always empty on render; requiring it would make it
|
|
239
|
-
* impossible to change a host without retyping the password, and treating empty as "clear" would delete
|
|
240
|
-
* the password on every unrelated edit. The field is therefore **omitted** from the payload unless the
|
|
241
|
-
* user typed one, which is exactly how the host reads it.
|
|
242
|
-
* 3. **`keyPath` follows the same rule as the password: blank means "keep the stored one".** The panel
|
|
243
|
-
* cannot prefill it, because the host's answer deliberately never carries a `keyPath` (it names a real
|
|
244
|
-
* file). Refusing a blank one would therefore make it impossible to edit a key connection's host without
|
|
245
|
-
* retyping its private-key path, and *filling* it with a guess would silently repoint the connection.
|
|
246
|
-
* So it is omitted unless the user typed one - the host's merge keeps what is stored.
|
|
247
|
-
*
|
|
248
|
-
* @param draft - what the form holds.
|
|
249
|
-
* @param originalId - the id of the connection being edited (the lookup key the host needs).
|
|
250
|
-
* @param existing - connections already shown, to reject a colliding rename.
|
|
251
|
-
* @returns the wire payload, or the message key to show next to the field.
|
|
252
|
-
*/
|
|
253
|
-
export declare function validateConnectionEdit(draft: ConnectionDraft, originalId: string, existing?: readonly ConnectionSummary[]): Validation<Record<string, unknown>>;
|
|
254
|
-
/** One row as the component renders it. */
|
|
255
|
-
export interface RootRowView {
|
|
256
|
-
readonly key: string;
|
|
257
|
-
readonly location: RootLocation;
|
|
258
|
-
readonly title: string;
|
|
259
|
-
readonly subtitle: string;
|
|
260
|
-
readonly badgeKey: string;
|
|
261
|
-
readonly removable: boolean;
|
|
262
|
-
}
|
|
263
|
-
/** The panel's complete render input. */
|
|
264
|
-
export interface RootsView {
|
|
265
|
-
readonly rows: readonly RootRowView[];
|
|
266
|
-
readonly connections: readonly ConnectionSummary[];
|
|
267
|
-
/** Set when the host did not answer, so the panel can say why instead of looking empty. */
|
|
268
|
-
readonly notice?: MessageKey;
|
|
269
|
-
/** False while a request is in flight, so the form cannot double-submit. */
|
|
270
|
-
readonly canEdit: boolean;
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Turn a snapshot (or the failure to get one) into render input.
|
|
274
|
-
*
|
|
275
|
-
* @param input - the last answer, or the last error, plus whether a request is running.
|
|
276
|
-
* @returns rows, connections, notice and the edit gate.
|
|
277
|
-
*/
|
|
278
|
-
export declare function buildRootsView(input: {
|
|
279
|
-
readonly snapshot?: RootsSnapshot;
|
|
280
|
-
readonly error?: string;
|
|
281
|
-
readonly pending?: boolean;
|
|
282
|
-
}): RootsView;
|
|
283
|
-
/** The calls the panel makes. */
|
|
284
|
-
export interface RootsApi {
|
|
285
|
-
load(signal?: AbortSignal): Promise<ApiResult<RootsSnapshot>>;
|
|
286
|
-
send(body: Record<string, unknown>): Promise<ApiResult<RootsSnapshot>>;
|
|
287
|
-
/**
|
|
288
|
-
* A POST whose answer is **not** a snapshot.
|
|
289
|
-
*
|
|
290
|
-
* The import actions answer a candidate list and an import report, neither of which is a roots snapshot. It
|
|
291
|
-
* is a separate method rather than a looser return type on {@link RootsApi.send} so that every existing call
|
|
292
|
-
* keeps the snapshot it already depends on: widening `send` would make `state` optional for all of them.
|
|
293
|
-
*/
|
|
294
|
-
sendRaw(body: Record<string, unknown>): Promise<ApiResult<Record<string, unknown>>>;
|
|
295
|
-
}
|
|
296
|
-
/** One importable `~/.ssh/config` alias, as the import dialog renders it. */
|
|
297
|
-
export interface SshConfigCandidate {
|
|
298
|
-
/** The literal alias; also the `ssh` target. Shown verbatim so the user recognises their own config. */
|
|
299
|
-
readonly alias: string;
|
|
300
|
-
/** Resolved host name. */
|
|
301
|
-
readonly host: string;
|
|
302
|
-
readonly user?: string;
|
|
303
|
-
readonly port?: number;
|
|
304
|
-
/** Whether the block named an identity file. The path itself never reaches the client. */
|
|
305
|
-
readonly hasIdentityFile: boolean;
|
|
306
|
-
readonly hasProxyJump: boolean;
|
|
307
|
-
/** The id the import would create. Editable in spirit: the user sees it before committing. */
|
|
308
|
-
readonly suggestedId: string;
|
|
309
|
-
}
|
|
310
|
-
/** One alias the host refused, with the machine-readable reason. */
|
|
311
|
-
export interface SshConfigSkipped {
|
|
312
|
-
readonly pattern: string;
|
|
313
|
-
readonly reason: string;
|
|
314
|
-
readonly line: number;
|
|
315
|
-
}
|
|
316
|
-
/** What `list-ssh-config` answers. */
|
|
317
|
-
export interface SshConfigListing {
|
|
318
|
-
readonly candidates: readonly SshConfigCandidate[];
|
|
319
|
-
readonly skipped: readonly SshConfigSkipped[];
|
|
320
|
-
}
|
|
321
|
-
/**
|
|
322
|
-
* Narrow the host's `list-ssh-config` answer.
|
|
323
|
-
*
|
|
324
|
-
* Defensive in the same way {@link parseRootsSnapshot} is: the client never trusts the wire's shape, and a row
|
|
325
|
-
* without a usable alias or id is **dropped** rather than rendered as a checkbox that would import nothing.
|
|
326
|
-
*
|
|
327
|
-
* @param value - the parsed JSON body.
|
|
328
|
-
* @returns the listing, with unusable rows removed.
|
|
329
|
-
*/
|
|
330
|
-
export declare function parseSshConfigListing(value: unknown): SshConfigListing;
|
|
331
|
-
/**
|
|
332
|
-
* The human-readable reason one alias was refused.
|
|
333
|
-
*
|
|
334
|
-
* The mapping lives here rather than in the view so the reasons are testable without rendering, and an unknown
|
|
335
|
-
* reason from a newer host still produces a sentence instead of an empty cell.
|
|
336
|
-
*
|
|
337
|
-
* @param reason - the machine-readable reason.
|
|
338
|
-
* @returns a message key.
|
|
339
|
-
*/
|
|
340
|
-
export declare function sshConfigSkipKey(reason: string): string;
|
|
341
|
-
/** A candidate's one-line summary: `user@host:port`, with the flags that matter for the import. */
|
|
342
|
-
export declare function sshConfigCandidateSummary(candidate: SshConfigCandidate): string;
|
|
343
|
-
/**
|
|
344
|
-
* The reason a failed response reports, when the host did not name one.
|
|
345
|
-
*
|
|
346
|
-
* The path is part of the text on purpose: `http-404` alone cannot distinguish "this route is not
|
|
347
|
-
* registered" (the host half never claimed it — that was a real defect) from "the request never reached the
|
|
348
|
-
* host" (the shell's proxy or the composition is at fault). Both are shown verbatim in the panel's notice.
|
|
349
|
-
*
|
|
350
|
-
* @param status - the HTTP status.
|
|
351
|
-
* @param path - the exact URL that was requested.
|
|
352
|
-
* @returns the machine-readable reason.
|
|
353
|
-
*/
|
|
354
|
-
export declare function transportFailureReason(status: number, path: string): string;
|
|
355
|
-
/**
|
|
356
|
-
* Bind the route to the panel's two operations.
|
|
357
|
-
*
|
|
358
|
-
* @param fetchImpl - `window.fetch` in the app, a stub in tests.
|
|
359
|
-
* @param path - override for tests; defaults to {@link ROOTS_API_PATH}.
|
|
360
|
-
* @returns the calls the component uses.
|
|
361
|
-
*/
|
|
362
|
-
export declare function createRootsApi(fetchImpl: FetchLike, path?: string): RootsApi;
|
|
363
|
-
/** The field names the panel must never send, asserted by tests rather than trusted to reviewers. */
|
|
364
|
-
export declare function isForbiddenField(name: string): boolean;
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 传输面板:进度列表、**长轮询驱动**与文案。
|
|
3
|
-
*
|
|
4
|
-
* 从 `roots-panel-view.ts` 抽出(审计 0076);task-19 把定时轮询换成「回答一到就再问一次」的
|
|
5
|
-
* 长轮询循环,循环本身在 `transfer-progress.ts`(可注入时钟,因此可在离线测试里驱动)。
|
|
6
|
-
*
|
|
7
|
-
* 容器只做三件事:建 io、把 controller 挂到 React 生命周期、把状态与回调交给纯模板。
|
|
8
|
-
*
|
|
9
|
-
* @module dsh-over-ssh/client/transfer-panel-view
|
|
10
|
-
*/
|
|
11
|
-
import { type ReactNode } from 'react';
|
|
12
|
-
import type { RootsPanelCopy, RootsPanelState } from './roots-panel-state.ts';
|
|
13
|
-
/**
|
|
14
|
-
* The panel, driven by {@link createTransferProgress}.
|
|
15
|
-
*
|
|
16
|
-
* `state` is accepted but not read: the panel's own snapshot is what it renders, and the roots state it used to
|
|
17
|
-
* receive was never read either (the prop is kept so the section's call site does not have to change).
|
|
18
|
-
*
|
|
19
|
-
* @param props - the dictionary (the section's `state` is unused).
|
|
20
|
-
* @returns the transfer section.
|
|
21
|
-
*/
|
|
22
|
-
export declare function TransferProgressSection({ copy }: {
|
|
23
|
-
readonly copy: RootsPanelCopy;
|
|
24
|
-
readonly state?: RootsPanelState;
|
|
25
|
-
}): ReactNode;
|
|
26
|
-
/**
|
|
27
|
-
* The tree's strings, read out of the panel's dictionary.
|
|
28
|
-
*
|
|
29
|
-
* Kept as an explicit projection rather than passing the dictionary itself: the tree's copy type is its own
|
|
30
|
-
* contract, so a key it needs and the dictionary does not have is a compile error here, not a blank label at
|
|
31
|
-
* runtime.
|
|
32
|
-
*
|
|
33
|
-
* @param copy - the panel's dictionary.
|
|
34
|
-
* @returns the tree's strings.
|
|
35
|
-
*/
|
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The transfer progress panel (FR-2.6.4): rate, bytes done, time left, and the failure list.
|
|
3
|
-
*
|
|
4
|
-
* The client **polls** rather than subscribes: this plugin's client half talks to the host over one HTTP route,
|
|
5
|
-
* so "what is transferring" is a question it asks. Polling only while the panel is mounted is what keeps that
|
|
6
|
-
* from being a permanent request loop.
|
|
7
|
-
*
|
|
8
|
-
* Two rules it inherits from the rest of this half:
|
|
9
|
-
*
|
|
10
|
-
* - the answer is narrowed field by field (the wire is not trusted), and
|
|
11
|
-
* - **nothing is inferred**: an unknown status is `running`, an unknown direction is `upload`, and a missing
|
|
12
|
-
* `remainingMs` renders as "unknown" rather than as a guess.
|
|
13
|
-
*
|
|
14
|
-
* @module dsh-over-ssh/client/transfer-panel
|
|
15
|
-
*/
|
|
16
|
-
import { type ReactNode } from 'react';
|
|
17
|
-
import { type MessageKey } from '../core/message.ts';
|
|
18
|
-
import { type ApiResult, type FetchLike } from './roots-panel.ts';
|
|
19
|
-
/** One tracked transfer, as the panel renders it. */
|
|
20
|
-
export interface TransferRow {
|
|
21
|
-
readonly id: string;
|
|
22
|
-
readonly direction: 'upload' | 'download';
|
|
23
|
-
readonly connectionId: string;
|
|
24
|
-
readonly remotePath: string;
|
|
25
|
-
readonly localPath: string;
|
|
26
|
-
readonly status: 'running' | 'done' | 'failed' | 'cancelled';
|
|
27
|
-
readonly transferredBytes: number;
|
|
28
|
-
readonly totalBytes: number;
|
|
29
|
-
readonly percent: number;
|
|
30
|
-
readonly bytesPerSecond: number;
|
|
31
|
-
readonly elapsedMs: number;
|
|
32
|
-
readonly remainingMs: number | null;
|
|
33
|
-
readonly error?: string;
|
|
34
|
-
readonly errorCode?: string;
|
|
35
|
-
}
|
|
36
|
-
/** How one snapshot may be asked for. */
|
|
37
|
-
export interface TransferListOptions {
|
|
38
|
-
/**
|
|
39
|
-
* Host-side long-poll budget: the host parks the request until the table changes or this many milliseconds pass.
|
|
40
|
-
*
|
|
41
|
-
* Omitted/0 answers immediately (the pre-task-19 behaviour) — which is what a **user refresh** wants, because the
|
|
42
|
-
* user is waiting for an answer now, not for a change.
|
|
43
|
-
*/
|
|
44
|
-
readonly waitMs?: number;
|
|
45
|
-
/** Abort the request (panel unmount, or a user refresh superseding a parked one). */
|
|
46
|
-
readonly signal?: AbortSignal;
|
|
47
|
-
}
|
|
48
|
-
/** The calls the panel makes. Injected, so the model is testable with no server. */
|
|
49
|
-
export interface TransferPanelIo {
|
|
50
|
-
list(options?: TransferListOptions): Promise<ApiResult<readonly TransferRow[]>>;
|
|
51
|
-
clear(): Promise<ApiResult<number>>;
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Narrow one `list-transfers` answer.
|
|
55
|
-
*
|
|
56
|
-
* @param value - the parsed body.
|
|
57
|
-
* @returns the rows, with unusable entries dropped.
|
|
58
|
-
*/
|
|
59
|
-
export declare function parseTransferRows(value: unknown): readonly TransferRow[];
|
|
60
|
-
/**
|
|
61
|
-
* The rows still worth showing: everything running, plus the most recent finished ones.
|
|
62
|
-
*
|
|
63
|
-
* A cap on finished rows because the requirement's "failure list" is a list of *recent* problems; the host keeps
|
|
64
|
-
* the full history, and the panel's job is to be readable.
|
|
65
|
-
*
|
|
66
|
-
* @param rows - every row the host reported.
|
|
67
|
-
* @param finishedLimit - how many finished rows to keep.
|
|
68
|
-
* @returns the rows to render, running first.
|
|
69
|
-
*/
|
|
70
|
-
export declare function visibleTransfers(rows: readonly TransferRow[], finishedLimit?: number): readonly TransferRow[];
|
|
71
|
-
/**
|
|
72
|
-
* One row's one-line summary.
|
|
73
|
-
*
|
|
74
|
-
* @param row - the transfer.
|
|
75
|
-
* @param format - the byte/duration formatters plus the localized direction label (injected so this stays free of
|
|
76
|
-
* the domain layer).
|
|
77
|
-
* @param copy - the sentence templates; see {@link TransferPanelCopy.summaryRunning}.
|
|
78
|
-
* @returns e.g. `上传 40.0%(4.0 KiB/10.0 KiB,2.0 KiB/s,剩余约 3.0s)` in Chinese, and the English template in `en`.
|
|
79
|
-
*/
|
|
80
|
-
export declare function transferSummary(row: TransferRow, format: {
|
|
81
|
-
readonly bytes: (value: number) => string;
|
|
82
|
-
readonly duration: (value: number) => string;
|
|
83
|
-
readonly label: string;
|
|
84
|
-
}, copy: Pick<TransferPanelCopy, 'summaryRunning' | 'summaryDone' | 'remaining' | 'remainingUnknown'>): string;
|
|
85
|
-
/**
|
|
86
|
-
* The accessible name of one transfer's progress bar.
|
|
87
|
-
*
|
|
88
|
-
* Separate from {@link transferSummary} on purpose. The summary is the row's *visible* line, and it
|
|
89
|
-
* deliberately leaves the path out because the path is rendered next to it; a `<progress>` element has no
|
|
90
|
-
* other text of its own, so a screen reader user needs the path **inside** the bar's name. Every bar used to
|
|
91
|
-
* carry `copy.title` ("传输进度"), which meant N bars with N identical names and no way to tell which transfer
|
|
92
|
-
* was which.
|
|
93
|
-
*
|
|
94
|
-
* The target falls back to the local path when the remote one is empty, so a row can never end up with a name
|
|
95
|
-
* that is only a percentage.
|
|
96
|
-
*
|
|
97
|
-
* @param row - the transfer.
|
|
98
|
-
* @param format - the label plus the byte/duration formatters.
|
|
99
|
-
* @returns e.g. `上传 40.0%(400B/1000B,200B/s,剩余约 3000ms) — /srv/a.bin`.
|
|
100
|
-
*/
|
|
101
|
-
export declare function transferProgressLabel(row: TransferRow, format: {
|
|
102
|
-
readonly bytes: (value: number) => string;
|
|
103
|
-
readonly duration: (value: number) => string;
|
|
104
|
-
readonly label: string;
|
|
105
|
-
}, copy: Pick<TransferPanelCopy, 'summaryRunning' | 'summaryDone' | 'remaining' | 'remainingUnknown'>): string;
|
|
106
|
-
/** Everything the panel renders from. */
|
|
107
|
-
export interface TransferPanelState {
|
|
108
|
-
readonly rows: readonly TransferRow[];
|
|
109
|
-
/**
|
|
110
|
-
* Why the last list read failed, **as a key the view resolves**.
|
|
111
|
-
*
|
|
112
|
-
* It used to be the raw transport string, so the failure notice printed `http-500 @ /api/over-ssh/roots`
|
|
113
|
-
* or `TypeError` — machine text, in both locales, naming an internal URL (audit 2026-11-01b ui P2-5).
|
|
114
|
-
*/
|
|
115
|
-
readonly error?: MessageKey;
|
|
116
|
-
readonly loading: boolean;
|
|
117
|
-
/** True while the user's 「清除」 request is in flight, so the two buttons can be disabled. */
|
|
118
|
-
readonly clearing: boolean;
|
|
119
|
-
/** Why the last 「清除」 failed, as a key. Kept apart from {@link TransferPanelState.error}: one is a read, the
|
|
120
|
-
* other a command, and they need different sentences. */
|
|
121
|
-
readonly clearError?: MessageKey;
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Turn one transport-level failure string into a sentence key.
|
|
125
|
-
*
|
|
126
|
-
* The three cases are the three things the panel can honestly say (audit 2026-11-01b ui P2-5):
|
|
127
|
-
*
|
|
128
|
-
* - a status the route answered → 「…(HTTP {status})」, the number is the useful part;
|
|
129
|
-
* - a transport spelling (`network`, `TypeError`, `AbortError`, `http-500 @ /api/…`) → carrying no information at
|
|
130
|
-
* all, so the panel says the host did not answer instead of printing the spelling;
|
|
131
|
-
* - anything else is the **host's own** explanation (this route answers prose for a refused capability), and that
|
|
132
|
-
* is the one case worth quoting.
|
|
133
|
-
*
|
|
134
|
-
* @param reason - the failure string the io produced.
|
|
135
|
-
* @returns the dictionary key plus its parameters.
|
|
136
|
-
*/
|
|
137
|
-
export declare function transferFailureKey(reason: string): MessageKey;
|
|
138
|
-
/** @returns the starting state. */
|
|
139
|
-
export declare function initialTransferPanelState(): TransferPanelState;
|
|
140
|
-
/** A poll started. */
|
|
141
|
-
export declare function transferPanelLoading(state: TransferPanelState): TransferPanelState;
|
|
142
|
-
/** A poll answered. */
|
|
143
|
-
export declare function transferPanelLoaded(state: TransferPanelState, rows: readonly TransferRow[]): TransferPanelState;
|
|
144
|
-
/** A poll failed; the **last known rows stay** (a blip must not blank the panel). */
|
|
145
|
-
export declare function transferPanelFailed(state: TransferPanelState, error: string): TransferPanelState;
|
|
146
|
-
/** The user pressed 「清除」: the request is in flight and the previous clear failure is superseded. */
|
|
147
|
-
export declare function transferPanelClearing(state: TransferPanelState): TransferPanelState;
|
|
148
|
-
/** 「清除」 succeeded: the next poll shows the empty table. */
|
|
149
|
-
export declare function transferPanelCleared(state: TransferPanelState): TransferPanelState;
|
|
150
|
-
/**
|
|
151
|
-
* 「清除」 failed.
|
|
152
|
-
*
|
|
153
|
-
* It used to be dropped on the floor (`void io.clear().then(...)` had no failure branch), so the button did
|
|
154
|
-
* nothing at all on a refusal and the user could click it again and again (audit 2026-11-01b ui P2-5).
|
|
155
|
-
*/
|
|
156
|
-
export declare function transferPanelClearFailed(state: TransferPanelState, error: string): TransferPanelState;
|
|
157
|
-
/**
|
|
158
|
-
* The state a poll starts from.
|
|
159
|
-
*
|
|
160
|
-
* `showLoading` separates the user's own action from the timer, and that distinction is the whole point: the
|
|
161
|
-
* view renders a **visible** "reading…" notice for `loading` (`TransferPanelSection`), so a background poll
|
|
162
|
-
* that raised it made the panel alternate between "reading…" and "nothing is transferring" once a second while
|
|
163
|
-
* nothing was happening (audit 0057). Only opening the panel or pressing refresh asks to be seen doing it.
|
|
164
|
-
*
|
|
165
|
-
* A named function rather than an `if` inside the component because this repository's tests render no hooks: a
|
|
166
|
-
* branch buried in a `useEffect` cannot be executed by a test, so the decision lives out here where it can be
|
|
167
|
-
* (removing that branch used to leave every test green).
|
|
168
|
-
*
|
|
169
|
-
* @param state - the current panel state.
|
|
170
|
-
* @param showLoading - `true` when the user asked for this poll.
|
|
171
|
-
* @returns the state to render while the request is in flight.
|
|
172
|
-
*/
|
|
173
|
-
export declare function transferPanelPollStarted(state: TransferPanelState, showLoading: boolean): TransferPanelState;
|
|
174
|
-
/**
|
|
175
|
-
* Bind the panel to the route.
|
|
176
|
-
*
|
|
177
|
-
* @param fetchImpl - `window.fetch` in the app, a stub in tests.
|
|
178
|
-
* @param path - override for tests.
|
|
179
|
-
* @returns the two calls the panel makes.
|
|
180
|
-
*/
|
|
181
|
-
export declare function createTransferPanelIo(fetchImpl: FetchLike, path?: string): TransferPanelIo;
|
|
182
|
-
/** The strings the panel renders. */
|
|
183
|
-
export interface TransferPanelCopy {
|
|
184
|
-
readonly title: string;
|
|
185
|
-
/** The panel's description line (it existed as a locale key and was never rendered). */
|
|
186
|
-
readonly description: string;
|
|
187
|
-
readonly empty: string;
|
|
188
|
-
readonly loading: string;
|
|
189
|
-
readonly refresh: string;
|
|
190
|
-
readonly clear: string;
|
|
191
|
-
readonly failedTitle: string;
|
|
192
|
-
readonly cancelled: string;
|
|
193
|
-
readonly done: string;
|
|
194
|
-
readonly uploadLabel: string;
|
|
195
|
-
readonly downloadLabel: string;
|
|
196
|
-
/**
|
|
197
|
-
* The one-line summary templates, with `{label} {percent} {done} {total} {rate} {elapsed} {remaining}`.
|
|
198
|
-
*
|
|
199
|
-
* Templates rather than a sentence spelled here: this module is a pure model, and the sentence it used to
|
|
200
|
-
* build carried Chinese full-width punctuation (`%(…,…)`) into the English UI. The dictionaries own the
|
|
201
|
-
* sentence now; the numbers stay here.
|
|
202
|
-
*/
|
|
203
|
-
readonly summaryRunning: string;
|
|
204
|
-
readonly summaryDone: string;
|
|
205
|
-
/** `剩余约 {duration}` */
|
|
206
|
-
readonly remaining: string;
|
|
207
|
-
/** `剩余时间未知` */
|
|
208
|
-
readonly remainingUnknown: string;
|
|
209
|
-
/** What a duration formatter prints when it was handed a value it cannot render. */
|
|
210
|
-
readonly durationUnknown: string;
|
|
211
|
-
/** Resolve one deferred failure message against the active dictionary. */
|
|
212
|
-
readonly resolve: (message: MessageKey) => string;
|
|
213
|
-
/** One failed transfer's line: `{path}{sep}{reason}` — the punctuation belongs to the locale. */
|
|
214
|
-
readonly failureLine: string;
|
|
215
|
-
/** The badge text when the host gave no `errorCode` (it used to be the English literal `error`). */
|
|
216
|
-
readonly errorCodeUnknown: string;
|
|
217
|
-
}
|
|
218
|
-
/** What the panel's controls call. */
|
|
219
|
-
export interface TransferPanelActions {
|
|
220
|
-
readonly onRefresh: () => void;
|
|
221
|
-
readonly onClear: () => void;
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* The panel's markup.
|
|
225
|
-
*
|
|
226
|
-
* A `<progress>` element for the bar rather than a styled `div`: it is the platform's own primitive, it is
|
|
227
|
-
* announced correctly by a screen reader, and it needs no CSS to be a progress bar at all (which matters here,
|
|
228
|
-
* because this plugin's stylesheet is injected and can be missing in a composition that never loaded it).
|
|
229
|
-
*
|
|
230
|
-
* @param props - state, copy, formatters and callbacks.
|
|
231
|
-
* @returns the panel section.
|
|
232
|
-
*/
|
|
233
|
-
export declare function TransferPanelSection(props: {
|
|
234
|
-
readonly copy: TransferPanelCopy;
|
|
235
|
-
readonly state: TransferPanelState;
|
|
236
|
-
readonly actions: TransferPanelActions;
|
|
237
|
-
readonly format: {
|
|
238
|
-
readonly bytes: (value: number) => string;
|
|
239
|
-
readonly duration: (value: number) => string;
|
|
240
|
-
};
|
|
241
|
-
}): ReactNode;
|