dsh-over-ssh 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +119 -2
- package/agents/agent-manifest.json +20 -0
- package/agents/linux-amd64/dsh-over-ssh-agent +0 -0
- package/agents/linux-arm64/dsh-over-ssh-agent +0 -0
- package/cordis.patch.yml +84 -0
- package/lib/client.js +6611 -0
- package/lib/file-references.js +366 -0
- package/lib/fs.js +1368 -0
- package/lib/index.js +19034 -0
- package/lib/local-bridge-refs.js +16 -0
- package/lib/local-bridge.js +16 -0
- package/lib/terminal-wrapper.js +294 -0
- package/lib/types/src/client/controls.d.ts +131 -0
- package/lib/types/src/client/core/card-focus-trap.d.ts +45 -0
- package/lib/types/src/client/core/controls.d.ts +164 -0
- package/lib/types/src/client/core/latest.d.ts +48 -0
- package/lib/types/src/client/core/locale.d.ts +38 -0
- package/lib/types/src/client/core/message.d.ts +37 -0
- package/lib/types/src/client/core/panel-model.d.ts +134 -0
- package/lib/types/src/client/core/portal.d.ts +266 -0
- package/lib/types/src/client/core/remote.d.ts +84 -0
- package/lib/types/src/client/core/styles.d.ts +458 -0
- package/lib/types/src/client/file-tree-io.d.ts +19 -0
- package/lib/types/src/client/file-tree.d.ts +271 -0
- package/lib/types/src/client/flows/directory-browser.d.ts +587 -0
- package/lib/types/src/client/flows/session-directory.d.ts +415 -0
- package/lib/types/src/client/flows/workspace-directory-flow.d.ts +480 -0
- package/lib/types/src/client/index.d.ts +49 -0
- package/lib/types/src/client/locale.d.ts +31 -0
- package/lib/types/src/client/panel-model.d.ts +37 -0
- package/lib/types/src/client/panel.d.ts +77 -0
- package/lib/types/src/client/roots/connection-test.d.ts +69 -0
- package/lib/types/src/client/roots/roots-panel-state.d.ts +307 -0
- package/lib/types/src/client/roots/roots-panel-view.d.ts +280 -0
- package/lib/types/src/client/roots/roots-panel.d.ts +364 -0
- package/lib/types/src/client/roots/transfer-panel-view.d.ts +35 -0
- package/lib/types/src/client/roots/transfer-panel.d.ts +241 -0
- package/lib/types/src/client/roots/transfer-progress.d.ts +67 -0
- package/lib/types/src/client/roots-panel-view.d.ts +236 -0
- package/lib/types/src/client/roots-panel.d.ts +347 -0
- package/lib/types/src/client/session/session-panel-view.d.ts +336 -0
- package/lib/types/src/client/session/session-panel.d.ts +322 -0
- package/lib/types/src/client/session/session-remote-mark.d.ts +110 -0
- package/lib/types/src/client/session/session-row-actions.d.ts +398 -0
- package/lib/types/src/client/session-panel.d.ts +152 -0
- package/lib/types/src/client/styles.d.ts +252 -0
- package/lib/types/src/client/workspace-directory-flow.d.ts +342 -0
- package/lib/types/src/config.d.ts +312 -0
- package/lib/types/src/file-references.d.ts +25 -0
- package/lib/types/src/fs.d.ts +46 -0
- package/lib/types/src/host/app/backend.d.ts +170 -0
- package/lib/types/src/host/app/recycle-bin/recycle-bin-contract.d.ts +247 -0
- package/lib/types/src/host/app/recycle-bin/recycle-bin-move.d.ts +18 -0
- package/lib/types/src/host/app/recycle-bin/recycle-bin-service.d.ts +45 -0
- package/lib/types/src/host/app/recycle-bin/recycle-bin-world.d.ts +38 -0
- package/lib/types/src/host/app/recycle-bin-contract.d.ts +215 -0
- package/lib/types/src/host/app/recycle-bin-move.d.ts +18 -0
- package/lib/types/src/host/app/recycle-bin-service.d.ts +45 -0
- package/lib/types/src/host/app/recycle-bin-world.d.ts +35 -0
- package/lib/types/src/host/app/roots/roots-api-connections.d.ts +49 -0
- package/lib/types/src/host/app/roots/roots-api-contract.d.ts +369 -0
- package/lib/types/src/host/app/roots/roots-api-directories.d.ts +13 -0
- package/lib/types/src/host/app/roots/roots-api-http.d.ts +77 -0
- package/lib/types/src/host/app/roots/roots-api-long-poll.d.ts +130 -0
- package/lib/types/src/host/app/roots/roots-api-projections.d.ts +102 -0
- package/lib/types/src/host/app/roots/roots-api-roots.d.ts +13 -0
- package/lib/types/src/host/app/roots/roots-api-state.d.ts +22 -0
- package/lib/types/src/host/app/roots/roots-api-transfers.d.ts +26 -0
- package/lib/types/src/host/app/roots/roots-api.d.ts +55 -0
- package/lib/types/src/host/app/roots/user-roots.d.ts +552 -0
- package/lib/types/src/host/app/roots-api-connections.d.ts +49 -0
- package/lib/types/src/host/app/roots-api-contract.d.ts +339 -0
- package/lib/types/src/host/app/roots-api-directories.d.ts +13 -0
- package/lib/types/src/host/app/roots-api-http.d.ts +77 -0
- package/lib/types/src/host/app/roots-api-projections.d.ts +102 -0
- package/lib/types/src/host/app/roots-api-roots.d.ts +13 -0
- package/lib/types/src/host/app/roots-api-state.d.ts +22 -0
- package/lib/types/src/host/app/roots-api-support.d.ts +182 -0
- package/lib/types/src/host/app/roots-api-transfers.d.ts +13 -0
- package/lib/types/src/host/app/roots-api.d.ts +55 -0
- package/lib/types/src/host/app/routed-fs.d.ts +239 -0
- package/lib/types/src/host/app/serialize.d.ts +37 -0
- package/lib/types/src/host/app/session-admin/session-admin-context.d.ts +36 -0
- package/lib/types/src/host/app/session-admin/session-admin-contract.d.ts +221 -0
- package/lib/types/src/host/app/session-admin/session-admin-delete.d.ts +34 -0
- package/lib/types/src/host/app/session-admin/session-admin-service.d.ts +40 -0
- package/lib/types/src/host/app/session-admin-context.d.ts +36 -0
- package/lib/types/src/host/app/session-admin-contract.d.ts +214 -0
- package/lib/types/src/host/app/session-admin-delete.d.ts +34 -0
- package/lib/types/src/host/app/session-admin-service.d.ts +38 -0
- package/lib/types/src/host/app/takeover/takeover-plan.d.ts +227 -0
- package/lib/types/src/host/app/takeover/takeover-rows.d.ts +55 -0
- package/lib/types/src/host/app/transport/transport-selector.d.ts +98 -0
- package/lib/types/src/host/app/transport/transport-service.d.ts +82 -0
- package/lib/types/src/host/app/transport-selector.d.ts +98 -0
- package/lib/types/src/host/app/transport-service.d.ts +75 -0
- package/lib/types/src/host/app/user-roots.d.ts +461 -0
- package/lib/types/src/host/domain/bin-location.d.ts +79 -0
- package/lib/types/src/host/domain/capability.d.ts +78 -0
- package/lib/types/src/host/domain/connection.d.ts +95 -0
- package/lib/types/src/host/domain/errors.d.ts +55 -0
- package/lib/types/src/host/domain/glob-pattern.d.ts +62 -0
- package/lib/types/src/host/domain/literal-text.d.ts +63 -0
- package/lib/types/src/host/domain/path-grammar.d.ts +84 -0
- package/lib/types/src/host/domain/recycle-bin.d.ts +337 -0
- package/lib/types/src/host/domain/root.d.ts +74 -0
- package/lib/types/src/host/domain/roots/bin-location.d.ts +91 -0
- package/lib/types/src/host/domain/roots/connection.d.ts +106 -0
- package/lib/types/src/host/domain/roots/root.d.ts +108 -0
- package/lib/types/src/host/domain/roots/route-table.d.ts +102 -0
- package/lib/types/src/host/domain/roots/session-roots.d.ts +151 -0
- package/lib/types/src/host/domain/roots/ssh-config.d.ts +73 -0
- package/lib/types/src/host/domain/route-table.d.ts +80 -0
- package/lib/types/src/host/domain/session/session-admin.d.ts +345 -0
- package/lib/types/src/host/domain/session/terminal-target.d.ts +136 -0
- package/lib/types/src/host/domain/session-admin.d.ts +330 -0
- package/lib/types/src/host/domain/shell.d.ts +47 -0
- package/lib/types/src/host/domain/ssh-config.d.ts +73 -0
- package/lib/types/src/host/domain/terminal-target.d.ts +132 -0
- package/lib/types/src/host/domain/tool-requests.d.ts +369 -0
- package/lib/types/src/host/domain/transfer/recycle-bin.d.ts +337 -0
- package/lib/types/src/host/domain/transfer/transfer.d.ts +260 -0
- package/lib/types/src/host/domain/transfer.d.ts +260 -0
- package/lib/types/src/host/infra/backend/local-backend.d.ts +84 -0
- package/lib/types/src/host/infra/backend/local-version.d.ts +43 -0
- package/lib/types/src/host/infra/backend/platform-local-backend.d.ts +89 -0
- package/lib/types/src/host/infra/backend/remote-backend.d.ts +239 -0
- package/lib/types/src/host/infra/cache/lru.d.ts +52 -0
- package/lib/types/src/host/infra/cache/versioned.d.ts +77 -0
- package/lib/types/src/host/infra/temp-name.d.ts +19 -0
- package/lib/types/src/host/infra/transport/agent/agent-installer.d.ts +121 -0
- package/lib/types/src/host/infra/transport/agent/agent-protocol-client.d.ts +132 -0
- package/lib/types/src/host/infra/transport/agent/agent-protocol-codec.d.ts +140 -0
- package/lib/types/src/host/infra/transport/agent/agent-protocol-errors.d.ts +41 -0
- package/lib/types/src/host/infra/transport/agent/agent-protocol-frames.d.ts +197 -0
- package/lib/types/src/host/infra/transport/agent/agent-protocol-types.d.ts +564 -0
- package/lib/types/src/host/infra/transport/agent/agent-transport.d.ts +418 -0
- package/lib/types/src/host/infra/transport/agent-installer.d.ts +121 -0
- package/lib/types/src/host/infra/transport/agent-protocol-client.d.ts +123 -0
- package/lib/types/src/host/infra/transport/agent-protocol-codec.d.ts +131 -0
- package/lib/types/src/host/infra/transport/agent-protocol-errors.d.ts +41 -0
- package/lib/types/src/host/infra/transport/agent-protocol-frames.d.ts +197 -0
- package/lib/types/src/host/infra/transport/agent-protocol-types.d.ts +509 -0
- package/lib/types/src/host/infra/transport/agent-transport.d.ts +353 -0
- package/lib/types/src/host/infra/transport/alias-transport.d.ts +206 -0
- package/lib/types/src/host/infra/transport/askpass-fs.d.ts +20 -0
- package/lib/types/src/host/infra/transport/askpass.d.ts +133 -0
- package/lib/types/src/host/infra/transport/command-pool.d.ts +61 -0
- package/lib/types/src/host/infra/transport/fs-commands.d.ts +140 -0
- package/lib/types/src/host/infra/transport/git-status-route.d.ts +63 -0
- package/lib/types/src/host/infra/transport/git-status.d.ts +101 -0
- package/lib/types/src/host/infra/transport/node-spawn.d.ts +26 -0
- package/lib/types/src/host/infra/transport/session-window.d.ts +51 -0
- package/lib/types/src/host/infra/transport/shell-quote.d.ts +59 -0
- package/lib/types/src/host/infra/transport/ssh/alias-transport.d.ts +206 -0
- package/lib/types/src/host/infra/transport/ssh/askpass-fs.d.ts +20 -0
- package/lib/types/src/host/infra/transport/ssh/askpass.d.ts +133 -0
- package/lib/types/src/host/infra/transport/ssh/shell-quote.d.ts +62 -0
- package/lib/types/src/host/infra/transport/ssh/ssh-argv.d.ts +43 -0
- package/lib/types/src/host/infra/transport/ssh/ssh2-transport.d.ts +328 -0
- package/lib/types/src/host/infra/transport/ssh-argv.d.ts +43 -0
- package/lib/types/src/host/infra/transport/ssh2-transport.d.ts +302 -0
- package/lib/types/src/host/infra/transport/transport-base.d.ts +34 -0
- package/lib/types/src/host/infra/transport/transport.d.ts +527 -0
- package/lib/types/src/host/integration/agent/agent-install-io.d.ts +33 -0
- package/lib/types/src/host/integration/agent/agent-preflight.d.ts +56 -0
- package/lib/types/src/host/integration/agent/askpass-registry.d.ts +66 -0
- package/lib/types/src/host/integration/agent-install-io.d.ts +33 -0
- package/lib/types/src/host/integration/agent-preflight.d.ts +56 -0
- package/lib/types/src/host/integration/anchor-directory.d.ts +56 -0
- package/lib/types/src/host/integration/askpass-registry.d.ts +59 -0
- package/lib/types/src/host/integration/bin-root-resolver.d.ts +81 -0
- package/lib/types/src/host/integration/connection-test.d.ts +73 -0
- package/lib/types/src/host/integration/file-reference-provider.d.ts +115 -0
- package/lib/types/src/host/integration/fs/file-reference-provider.d.ts +142 -0
- package/lib/types/src/host/integration/fs/fs-provider.d.ts +368 -0
- package/lib/types/src/host/integration/fs/git-status-wiring.d.ts +21 -0
- package/lib/types/src/host/integration/fs/platform-errors.d.ts +94 -0
- package/lib/types/src/host/integration/fs/remote-file-tools.d.ts +369 -0
- package/lib/types/src/host/integration/fs/remote-index.d.ts +177 -0
- package/lib/types/src/host/integration/fs-provider.d.ts +226 -0
- package/lib/types/src/host/integration/lsp/lsp-framing.d.ts +38 -0
- package/lib/types/src/host/integration/lsp/lsp-registry.d.ts +67 -0
- package/lib/types/src/host/integration/lsp/lsp-session.d.ts +130 -0
- package/lib/types/src/host/integration/management-tools.d.ts +64 -0
- package/lib/types/src/host/integration/recycle-bin-service.d.ts +247 -0
- package/lib/types/src/host/integration/remote/connection-test.d.ts +73 -0
- package/lib/types/src/host/integration/remote/remote-face.d.ts +127 -0
- package/lib/types/src/host/integration/remote/remote-tool-backend.d.ts +108 -0
- package/lib/types/src/host/integration/remote-face.d.ts +127 -0
- package/lib/types/src/host/integration/remote-file-tools.d.ts +241 -0
- package/lib/types/src/host/integration/remote-home.d.ts +69 -0
- package/lib/types/src/host/integration/remote-index.d.ts +165 -0
- package/lib/types/src/host/integration/remote-job-tools.d.ts +295 -0
- package/lib/types/src/host/integration/remote-tool-backend.d.ts +97 -0
- package/lib/types/src/host/integration/remote-tools.d.ts +206 -0
- package/lib/types/src/host/integration/root-registry.d.ts +115 -0
- package/lib/types/src/host/integration/roots/anchor-directory.d.ts +56 -0
- package/lib/types/src/host/integration/roots/bin-root-resolver.d.ts +81 -0
- package/lib/types/src/host/integration/roots/remote-home.d.ts +69 -0
- package/lib/types/src/host/integration/roots/root-registry.d.ts +144 -0
- package/lib/types/src/host/integration/roots/roots-wiring.d.ts +219 -0
- package/lib/types/src/host/integration/roots/session-root-index.d.ts +52 -0
- package/lib/types/src/host/integration/roots-api.d.ts +407 -0
- package/lib/types/src/host/integration/roots-wiring.d.ts +211 -0
- package/lib/types/src/host/integration/session/session-api.d.ts +88 -0
- package/lib/types/src/host/integration/session/session-bin-lock.d.ts +80 -0
- package/lib/types/src/host/integration/session/session-header.d.ts +151 -0
- package/lib/types/src/host/integration/session/session-labels.d.ts +56 -0
- package/lib/types/src/host/integration/session/session-route-wiring.d.ts +50 -0
- package/lib/types/src/host/integration/session-admin-service.d.ts +236 -0
- package/lib/types/src/host/integration/session-api.d.ts +81 -0
- package/lib/types/src/host/integration/session-bin-lock.d.ts +80 -0
- package/lib/types/src/host/integration/session-header.d.ts +118 -0
- package/lib/types/src/host/integration/session-labels.d.ts +56 -0
- package/lib/types/src/host/integration/session-route-wiring.d.ts +50 -0
- package/lib/types/src/host/integration/session-scope.d.ts +113 -0
- package/lib/types/src/host/integration/session-tools.d.ts +44 -0
- package/lib/types/src/host/integration/strict-arguments.d.ts +79 -0
- package/lib/types/src/host/integration/takeover/takeover-api.d.ts +62 -0
- package/lib/types/src/host/integration/takeover/takeover-route-wiring.d.ts +60 -0
- package/lib/types/src/host/integration/takeover/takeover-service.d.ts +167 -0
- package/lib/types/src/host/integration/terminal-manifest.d.ts +94 -0
- package/lib/types/src/host/integration/tools/management-tools.d.ts +245 -0
- package/lib/types/src/host/integration/tools/remote-job-tools.d.ts +316 -0
- package/lib/types/src/host/integration/tools/remote-lsp-tools.d.ts +43 -0
- package/lib/types/src/host/integration/tools/remote-tools.d.ts +236 -0
- package/lib/types/src/host/integration/tools/session-target.d.ts +52 -0
- package/lib/types/src/host/integration/tools/session-tools.d.ts +46 -0
- package/lib/types/src/host/integration/tools/strict-arguments.d.ts +79 -0
- package/lib/types/src/host/integration/tools/terminal-manifest.d.ts +94 -0
- package/lib/types/src/host/integration/transfer/remote-copy.d.ts +66 -0
- package/lib/types/src/host/integration/transfer/transfer-adapters.d.ts +48 -0
- package/lib/types/src/host/integration/transfer/transfer-service.d.ts +212 -0
- package/lib/types/src/host/integration/transfer/transfer-tracker.d.ts +106 -0
- package/lib/types/src/host/integration/transfer-adapters.d.ts +44 -0
- package/lib/types/src/host/integration/transfer-service.d.ts +164 -0
- package/lib/types/src/host/integration/transfer-tracker.d.ts +106 -0
- package/lib/types/src/host/integration/user-roots.d.ts +461 -0
- package/lib/types/src/index.d.ts +66 -0
- package/lib/types/src/local-bridge-refs.d.ts +26 -0
- package/lib/types/src/local-bridge.d.ts +36 -0
- package/lib/types/src/shared/format.d.ts +13 -0
- package/lib/types/src/wiring/connections.d.ts +189 -0
- package/lib/types/src/wiring/fs.d.ts +13 -0
- package/lib/types/src/wiring/helpers.d.ts +144 -0
- package/lib/types/src/wiring/install-memo.d.ts +32 -0
- package/lib/types/src/wiring/legacy-dirs.d.ts +62 -0
- package/lib/types/src/wiring/package-paths.d.ts +35 -0
- package/lib/types/src/wiring/roots.d.ts +21 -0
- package/lib/types/src/wiring/state-dir.d.ts +29 -0
- package/lib/types/src/wiring/tools.d.ts +35 -0
- package/package.json +120 -4
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The settings panel: a pure template over {@link PanelModel}.
|
|
3
|
+
*
|
|
4
|
+
* Written with `createElement` instead of JSX on purpose. The repository's test runner is plain Node with
|
|
5
|
+
* native TypeScript stripping, which does **not** transform JSX - a `.tsx` component could not be imported
|
|
6
|
+
* by any test, and every source file must have a test (AGENTS.md 红线 1). `createElement` keeps the
|
|
7
|
+
* component in a `.ts` module that Node can load, and the bundle step treats it exactly the same.
|
|
8
|
+
*
|
|
9
|
+
* It receives everything through props (the platform's five-share contract puts the business face on props
|
|
10
|
+
* via `inject`, and components never see `ctx`), so there is no lookup, no subscription and no async state
|
|
11
|
+
* here - which is what keeps the panel renderable in isolation.
|
|
12
|
+
*
|
|
13
|
+
* The visual layer is `./styles.ts` (theme tokens) and `./controls.ts` (hover/focus state). The token names
|
|
14
|
+
* this file used to hard-code (`--dsw-alias-text-primary`, `--dsw-alias-border-subtle`,
|
|
15
|
+
* `--dsw-alias-surface-subtle`, `--dsw-alias-surface-warning`) **are not in the shipped theme**, so every one
|
|
16
|
+
* of them silently fell back - which is part of why this page looked unstyled.
|
|
17
|
+
*
|
|
18
|
+
* @module dsh-over-ssh/client/panel
|
|
19
|
+
*/
|
|
20
|
+
import { type ReactNode } from 'react';
|
|
21
|
+
import type { PanelModel } from './core/panel-model.ts';
|
|
22
|
+
import { type RootsPanelController, type RootsPanelCopy } from './roots/roots-panel-view.ts';
|
|
23
|
+
/** The editable section's injection: the dictionary plus the controller that owns its state. */
|
|
24
|
+
export interface SshDevPanelRootsProps {
|
|
25
|
+
/** The active locale's dictionary; the section resolves its own dynamic keys through it. */
|
|
26
|
+
readonly copy: RootsPanelCopy;
|
|
27
|
+
/** Live state and the two host calls; created once per plugin load by the entry (`index.ts`). */
|
|
28
|
+
readonly controller: RootsPanelController;
|
|
29
|
+
}
|
|
30
|
+
/** Props composed by the slot framework: the injected business face plus the owner's `close`. */
|
|
31
|
+
export interface SshDevPanelProps {
|
|
32
|
+
/** Model built by the registration's inject factory. */
|
|
33
|
+
readonly model: PanelModel;
|
|
34
|
+
/** Close the settings panel (the shell owns the open state). */
|
|
35
|
+
readonly close: () => void;
|
|
36
|
+
/**
|
|
37
|
+
* The remote-workspaces editor. Omitted by a caller that has no controller (a probe, or a test), in which
|
|
38
|
+
* case the panel is exactly the read-only page it was before.
|
|
39
|
+
*/
|
|
40
|
+
readonly roots?: SshDevPanelRootsProps;
|
|
41
|
+
/**
|
|
42
|
+
* Start a connectivity probe for one connection (the 「测试」 button on its row).
|
|
43
|
+
*
|
|
44
|
+
* Omitted by a caller with no tester, in which case rows render without the button - the same rule as
|
|
45
|
+
* {@link SshDevPanelProps.roots}.
|
|
46
|
+
*/
|
|
47
|
+
readonly onTestConnection?: (id: string) => void;
|
|
48
|
+
/**
|
|
49
|
+
* The row actions that come from the **live** panel state: which connection is being edited, which removal
|
|
50
|
+
* is awaiting confirmation, and whether a request is in flight.
|
|
51
|
+
*
|
|
52
|
+
* A separate prop from {@link SshDevPanelProps.onTestConnection} because the tester is a different store
|
|
53
|
+
* (it belongs to the transport probe, not to the editor). Omitted by a caller with no editor.
|
|
54
|
+
*/
|
|
55
|
+
readonly rowState?: {
|
|
56
|
+
readonly editingId?: string;
|
|
57
|
+
readonly confirmingId?: string;
|
|
58
|
+
readonly remoteRootsOf: (connectionId: string) => number;
|
|
59
|
+
readonly disabled: boolean;
|
|
60
|
+
readonly onEdit: (id: string) => void;
|
|
61
|
+
readonly onRemove: (id: string) => void;
|
|
62
|
+
readonly onCancelRemove: () => void;
|
|
63
|
+
readonly onConfirmRemove: (id: string) => void;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** The row actions a caller with an editor hands in. All of them are omitted by a read-only caller. */
|
|
67
|
+
export interface ConnectionRowActions {
|
|
68
|
+
readonly onTest?: (id: string) => void;
|
|
69
|
+
/** Open the edit form for this connection. */
|
|
70
|
+
readonly onEdit?: (id: string) => void;
|
|
71
|
+
/** Ask for confirmation before removing this connection. */
|
|
72
|
+
readonly onRemove?: (id: string) => void;
|
|
73
|
+
/** True while a request is in flight, so every action is disabled at once. */
|
|
74
|
+
readonly disabled?: boolean;
|
|
75
|
+
}
|
|
76
|
+
/** Render the panel. */
|
|
77
|
+
export declare function SshDevPanel({ model, close, roots, onTestConnection, rowState }: SshDevPanelProps): ReactNode;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The 「测试」 button's state: one connection at a time, answer or failure, and nothing else.
|
|
3
|
+
*
|
|
4
|
+
* Kept out of the component (which is a pure template) and out of the roots editor (which owns the *file*):
|
|
5
|
+
* this is transient UI state whose only job is to hold "what did the last probe say" long enough to render it.
|
|
6
|
+
*
|
|
7
|
+
* The probe itself runs on the host (a real connect plus one command); this module only carries the request and
|
|
8
|
+
* narrows the answer, so the four fields it reads are the four the host whitelists - an answer that grew a
|
|
9
|
+
* field cannot reach the panel.
|
|
10
|
+
*
|
|
11
|
+
* @module dsh-over-ssh/client/connection-test
|
|
12
|
+
*/
|
|
13
|
+
import type { MessageKey } from '../core/message.ts';
|
|
14
|
+
import type { FetchLike } from './roots-panel.ts';
|
|
15
|
+
/** Where one connection's probe stands. */
|
|
16
|
+
/**
|
|
17
|
+
* Where one connection's probe stands.
|
|
18
|
+
*
|
|
19
|
+
* `idle` is the state a row is in **before** anyone clicked - the button must already be there (it is what the
|
|
20
|
+
* user clicks), so "no answer yet" is a status, not a missing field.
|
|
21
|
+
*/
|
|
22
|
+
export type ConnectionTestStatus = 'idle' | 'running' | 'ok' | 'failed';
|
|
23
|
+
/** The last answer for one connection. */
|
|
24
|
+
export interface ConnectionTestState {
|
|
25
|
+
readonly status: ConnectionTestStatus;
|
|
26
|
+
/** Transport the host actually used (`alias` / `ssh2` / `agent`), once it answered. */
|
|
27
|
+
readonly transport?: string;
|
|
28
|
+
/** Whether that transport was a degraded substitute. */
|
|
29
|
+
readonly degraded?: boolean;
|
|
30
|
+
/** Machine-readable failure code (`AUTH_FAILED`, `TIMEOUT`, …). */
|
|
31
|
+
readonly code?: string;
|
|
32
|
+
/** The host's own sentence about the failure (or the carrier's, e.g. `Failed to fetch`). */
|
|
33
|
+
readonly message?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Our own sentence, when the failure was ours.
|
|
36
|
+
*
|
|
37
|
+
* A key rather than text: this module has no dictionary, and the three sentences it used to spell were Chinese
|
|
38
|
+
* in both locales (`roots-panel-view` renders them through `resolveMessage` with the active dictionary).
|
|
39
|
+
*/
|
|
40
|
+
readonly messageKey?: MessageKey;
|
|
41
|
+
/** Round trip in milliseconds, either way. */
|
|
42
|
+
readonly ms?: number;
|
|
43
|
+
}
|
|
44
|
+
/** The tester the panel drives. */
|
|
45
|
+
export interface ConnectionTester {
|
|
46
|
+
/** Current answers by connection id; a new map after every change (safe for a snapshot comparison). */
|
|
47
|
+
readonly getState: () => ReadonlyMap<string, ConnectionTestState>;
|
|
48
|
+
/** Probe one connection; resolves when the answer (or the failure) has been recorded. */
|
|
49
|
+
readonly test: (id: string) => Promise<void>;
|
|
50
|
+
/** Watch for changes; returns the disposer. */
|
|
51
|
+
readonly subscribe: (listener: () => void) => () => void;
|
|
52
|
+
/** Drop listeners; an in-flight answer is discarded rather than delivered. */
|
|
53
|
+
readonly dispose: () => void;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* How long a probe may stay 「测试中…」 before the client gives up on it.
|
|
57
|
+
*
|
|
58
|
+
* A real connect plus one command can take seconds, but it can also hang forever: the host's own connect has no
|
|
59
|
+
* deadline the client can see, and a `fetch` with no signal keeps its promise (and the row's button) in limbo.
|
|
60
|
+
* Without this the 「测试」 button stayed disabled and read 「测试中…」 **forever**, with no cancel and no way to try
|
|
61
|
+
* again short of reloading the page (audit 2026-11-01b ui P2-11).
|
|
62
|
+
*/
|
|
63
|
+
export declare const CONNECTION_TEST_TIMEOUT_MS = 15000;
|
|
64
|
+
export declare function createConnectionTester(options: {
|
|
65
|
+
readonly fetchImpl: FetchLike;
|
|
66
|
+
readonly path?: string;
|
|
67
|
+
/** Override for {@link CONNECTION_TEST_TIMEOUT_MS}; a test drives the timeout instead of waiting 15 s. */
|
|
68
|
+
readonly timeoutMs?: number;
|
|
69
|
+
}): ConnectionTester;
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 根面板的**状态层**:草稿、状态机、reducer、选择器与文案解析。
|
|
3
|
+
*
|
|
4
|
+
* 从 `roots-panel-view.ts` 抽出(审计 0076):这一层是纯函数,不碰 React,
|
|
5
|
+
* 也是测试最密集的部分;拆开后 view 只负责渲染与接线。
|
|
6
|
+
*
|
|
7
|
+
* @module dsh-over-ssh/client/roots-panel-state
|
|
8
|
+
*/
|
|
9
|
+
import type { OverSshKey } from '../core/locale.ts';
|
|
10
|
+
import { type MessageKey, type RootsSnapshot, type RootsView, type SshConfigListing } from './roots-panel.ts';
|
|
11
|
+
export type RootsPanelCopy = Record<OverSshKey, string>;
|
|
12
|
+
/** The three forms' contents. Kept flat so a patch is one spread and a test can name a single field. */
|
|
13
|
+
export interface RootsPanelDrafts {
|
|
14
|
+
/** Local root path (form 1). */
|
|
15
|
+
readonly localPath: string;
|
|
16
|
+
/** Remote root path (form 2). */
|
|
17
|
+
readonly remotePath: string;
|
|
18
|
+
/** Connection chosen for the remote root (form 2). */
|
|
19
|
+
readonly remoteConnectionId: string;
|
|
20
|
+
/** Optional label for the remote root (form 2). */
|
|
21
|
+
readonly remoteLabel: string;
|
|
22
|
+
/** New connection: id. */
|
|
23
|
+
readonly newId: string;
|
|
24
|
+
/** New connection: host. */
|
|
25
|
+
readonly newHost: string;
|
|
26
|
+
/** New connection: user. */
|
|
27
|
+
readonly newUser: string;
|
|
28
|
+
/** New connection: authentication mode. */
|
|
29
|
+
readonly newAuthKind: 'agent' | 'key' | 'password';
|
|
30
|
+
/** New connection: key path, only meaningful for `newAuthKind === 'key'`. */
|
|
31
|
+
readonly newKeyPath: string;
|
|
32
|
+
/**
|
|
33
|
+
* New connection: password, only meaningful for `newAuthKind === 'password'`.
|
|
34
|
+
*
|
|
35
|
+
* Held only between typing and the POST that follows it — the drafts are reset once the request settles, and
|
|
36
|
+
* no answer ever carries it back (the host stores it in `ctx.credentials`).
|
|
37
|
+
*/
|
|
38
|
+
readonly newPassword: string;
|
|
39
|
+
/** New connection: port as typed; parsed by the controller so a bad value can be refused, not coerced. */
|
|
40
|
+
readonly newPort: string;
|
|
41
|
+
}
|
|
42
|
+
/** A change to the drafts; every field is optional so one handler can serve every input. */
|
|
43
|
+
export type RootsPanelDraftPatch = Partial<RootsPanelDrafts>;
|
|
44
|
+
/**
|
|
45
|
+
* The **edit dialog's** own fields, kept apart from {@link RootsPanelDrafts}.
|
|
46
|
+
*
|
|
47
|
+
* The two forms used to share one flat draft set, and that is exactly what went wrong (audit 2026-11-01b ui
|
|
48
|
+
* P1-4 — three symptoms, one cause):
|
|
49
|
+
*
|
|
50
|
+
* - `edit-started` overwrote the add form's drafts with the row being edited, so opening 「编辑」 to look at a
|
|
51
|
+
* record threw away whatever the operator had already typed into 「新增连接」;
|
|
52
|
+
* - `edit-ended` reset **all** drafts, so even cancelling the dialog wiped the add form;
|
|
53
|
+
* - both forms read the same fields, so submitting the add form (reachable while the dialog is up, in a
|
|
54
|
+
* composition where the card has no backdrop) posted the edit dialog's values as a *new* connection.
|
|
55
|
+
*
|
|
56
|
+
* The password box belongs here, not in {@link RootsPanelDrafts}: it is always empty on render, and empty means
|
|
57
|
+
* 「keep the stored password」 — a rule about the edit path only (「新增连接」 has its own required-password rule).
|
|
58
|
+
*/
|
|
59
|
+
export interface RootsPanelEditDrafts {
|
|
60
|
+
/** The connection's id. Editable: a change is a rename that re-keys its remote roots. */
|
|
61
|
+
readonly id: string;
|
|
62
|
+
readonly host: string;
|
|
63
|
+
readonly user: string;
|
|
64
|
+
readonly authKind: 'agent' | 'key' | 'password';
|
|
65
|
+
readonly keyPath: string;
|
|
66
|
+
/** Always empty on render; empty means 「keep the stored password」. */
|
|
67
|
+
readonly password: string;
|
|
68
|
+
/** Port as typed; parsed by the controller so a bad value can be refused, not coerced. */
|
|
69
|
+
readonly port: string;
|
|
70
|
+
}
|
|
71
|
+
/** A change to the edit dialog's fields. */
|
|
72
|
+
export type RootsPanelEditDraftPatch = Partial<RootsPanelEditDrafts>;
|
|
73
|
+
/** @returns the edit dialog's fields in their fresh state. */
|
|
74
|
+
export declare function emptyRootsPanelEditDrafts(): RootsPanelEditDrafts;
|
|
75
|
+
/** Where an inline form message belongs. */
|
|
76
|
+
export type RootsPanelFormScope = 'roots' | 'connection' | 'edit';
|
|
77
|
+
/** A refusal or a failed submit, shown next to the form it belongs to. */
|
|
78
|
+
export interface RootsPanelFormNotice {
|
|
79
|
+
readonly scope: RootsPanelFormScope;
|
|
80
|
+
readonly message: MessageKey;
|
|
81
|
+
}
|
|
82
|
+
/** Everything the section renders from. */
|
|
83
|
+
export interface RootsPanelState {
|
|
84
|
+
/** True once an initial `load()` has been asked for, so a remount does not re-fetch. */
|
|
85
|
+
readonly started: boolean;
|
|
86
|
+
/** True while a request is in flight; the edit gate is `buildRootsView`'s `canEdit`. */
|
|
87
|
+
readonly pending: boolean;
|
|
88
|
+
/** True once a `load()` answered (ok or not). */
|
|
89
|
+
readonly loaded: boolean;
|
|
90
|
+
/** The last host answer, if any. */
|
|
91
|
+
readonly snapshot?: RootsSnapshot;
|
|
92
|
+
/** The last transport-level failure, if any. */
|
|
93
|
+
readonly error?: string;
|
|
94
|
+
/** The forms' contents. */
|
|
95
|
+
readonly drafts: RootsPanelDrafts;
|
|
96
|
+
/**
|
|
97
|
+
* The **edit dialog's** fields (see {@link RootsPanelEditDrafts}).
|
|
98
|
+
*
|
|
99
|
+
* Always present, empty while no dialog is open: an optional field would make "the dialog is closed" and "the
|
|
100
|
+
* value was dropped" the same state, which is how the two forms ended up sharing one draft set.
|
|
101
|
+
*/
|
|
102
|
+
readonly editDrafts: RootsPanelEditDrafts;
|
|
103
|
+
/**
|
|
104
|
+
* The connection currently open in the edit form, if any.
|
|
105
|
+
*
|
|
106
|
+
* Only the **id** is remembered: every other value lives in the drafts, so the form cannot show a stale copy
|
|
107
|
+
* of a record the host has since changed. `undefined` means the add form is the one on screen.
|
|
108
|
+
*/
|
|
109
|
+
readonly editingId?: string;
|
|
110
|
+
/**
|
|
111
|
+
* The connection whose removal is awaiting confirmation.
|
|
112
|
+
*
|
|
113
|
+
* Deleting a connection also deletes its roots (the host does that), which is not obvious from a row that
|
|
114
|
+
* shows only `id · host` - hence the confirmation, the same shape the session panel uses for its
|
|
115
|
+
* irreversible actions.
|
|
116
|
+
*/
|
|
117
|
+
readonly confirmingRemove?: string;
|
|
118
|
+
/** The last validation refusal or submit failure, with the form it belongs to. */
|
|
119
|
+
readonly formNotice?: RootsPanelFormNotice;
|
|
120
|
+
/**
|
|
121
|
+
* The last **successful** submit, with the form it belongs to.
|
|
122
|
+
*
|
|
123
|
+
* Adding a root, adding a connection or saving an edit used to end silently: the form emptied itself and the
|
|
124
|
+
* only evidence was the list changing somewhere else on the page. The success line reuses the same
|
|
125
|
+
* per-form slot mechanism as {@link RootsPanelState.formNotice}, with the opposite tone
|
|
126
|
+
* (audit 2026-11-01b ui P3-5).
|
|
127
|
+
*/
|
|
128
|
+
readonly formSuccess?: RootsPanelFormNotice;
|
|
129
|
+
/**
|
|
130
|
+
* True while the `~/.ssh/config` import dialog is open (FR-2.1.4).
|
|
131
|
+
*
|
|
132
|
+
* The dialog is state in this controller rather than local `useState` in the view for the same reason the
|
|
133
|
+
* forms are: the controller owns every request, and an import is a request. Keeping the listing here also
|
|
134
|
+
* means reopening the dialog does not re-read the file unless the user asks.
|
|
135
|
+
*/
|
|
136
|
+
readonly importOpen?: boolean;
|
|
137
|
+
/** The aliases the host offered, once `list-ssh-config` answered. */
|
|
138
|
+
readonly importListing?: SshConfigListing;
|
|
139
|
+
/** Aliases ticked in the dialog, in the order they were ticked. */
|
|
140
|
+
readonly importSelected?: readonly string[];
|
|
141
|
+
/** True while `list-ssh-config` or `import-ssh-config` is in flight. */
|
|
142
|
+
readonly importBusy?: boolean;
|
|
143
|
+
/** The last import's report, shown inside the dialog (ids imported, aliases refused). */
|
|
144
|
+
readonly importReport?: {
|
|
145
|
+
readonly imported: readonly string[];
|
|
146
|
+
readonly refused: readonly {
|
|
147
|
+
readonly alias: string;
|
|
148
|
+
readonly reason: string;
|
|
149
|
+
}[];
|
|
150
|
+
};
|
|
151
|
+
/** Why the last import attempt failed, shown inside the dialog. */
|
|
152
|
+
readonly importError?: string;
|
|
153
|
+
}
|
|
154
|
+
/** What the reducer accepts. */
|
|
155
|
+
export type RootsPanelAction =
|
|
156
|
+
/** A request is being sent; also marks the panel as started. */
|
|
157
|
+
{
|
|
158
|
+
readonly type: 'load';
|
|
159
|
+
} | {
|
|
160
|
+
readonly type: 'loaded';
|
|
161
|
+
readonly snapshot: RootsSnapshot;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* A request failed. With a `scope` it is a submit failure and is shown next to that form; without one it
|
|
165
|
+
* is the initial load failing, which the view's own `notice` already reports - showing both would say the
|
|
166
|
+
* same thing twice.
|
|
167
|
+
*/
|
|
168
|
+
| {
|
|
169
|
+
readonly type: 'failed';
|
|
170
|
+
readonly error: string;
|
|
171
|
+
readonly scope?: RootsPanelFormScope;
|
|
172
|
+
}
|
|
173
|
+
/** One form field changed: clears the inline message, because the user is fixing it. */
|
|
174
|
+
| {
|
|
175
|
+
readonly type: 'draft-changed';
|
|
176
|
+
readonly patch: RootsPanelDraftPatch;
|
|
177
|
+
}
|
|
178
|
+
/** One field of the **edit** dialog changed; the add form's drafts are untouched. */
|
|
179
|
+
| {
|
|
180
|
+
readonly type: 'edit-draft-changed';
|
|
181
|
+
readonly patch: RootsPanelEditDraftPatch;
|
|
182
|
+
}
|
|
183
|
+
/** A submit is in flight. */
|
|
184
|
+
| {
|
|
185
|
+
readonly type: 'submitted';
|
|
186
|
+
}
|
|
187
|
+
/** A draft was refused before any request; nothing was sent. */
|
|
188
|
+
| {
|
|
189
|
+
readonly type: 'refused';
|
|
190
|
+
readonly scope: RootsPanelFormScope;
|
|
191
|
+
readonly message: MessageKey;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* A request succeeded. `resetDrafts` clears the forms (an add did); a removal keeps them.
|
|
195
|
+
*
|
|
196
|
+
* `scope` decides which form gets the "saved" line — a removal passes none, because its evidence is the row
|
|
197
|
+
* leaving the list, not a sentence.
|
|
198
|
+
*/
|
|
199
|
+
| {
|
|
200
|
+
readonly type: 'succeeded';
|
|
201
|
+
readonly resetDrafts?: boolean;
|
|
202
|
+
readonly scope?: RootsPanelFormScope;
|
|
203
|
+
}
|
|
204
|
+
/** A removal is in flight; the reducer itself refuses a row that is not removable. */
|
|
205
|
+
| {
|
|
206
|
+
readonly type: 'remove';
|
|
207
|
+
readonly key: string;
|
|
208
|
+
}
|
|
209
|
+
/** Open the edit form for one connection, prefilled from the last answer. */
|
|
210
|
+
| {
|
|
211
|
+
readonly type: 'edit-started';
|
|
212
|
+
readonly id: string;
|
|
213
|
+
}
|
|
214
|
+
/** Close the edit form (cancelled, or the edit settled). */
|
|
215
|
+
| {
|
|
216
|
+
readonly type: 'edit-ended';
|
|
217
|
+
}
|
|
218
|
+
/** Ask for confirmation before removing a connection (it takes its roots with it). */
|
|
219
|
+
| {
|
|
220
|
+
readonly type: 'remove-connection-asked';
|
|
221
|
+
readonly id: string;
|
|
222
|
+
}
|
|
223
|
+
/** Dismiss the confirmation without removing anything. */
|
|
224
|
+
| {
|
|
225
|
+
readonly type: 'remove-connection-dismissed';
|
|
226
|
+
}
|
|
227
|
+
/** Open the ssh-config import dialog (FR-2.1.4). */
|
|
228
|
+
| {
|
|
229
|
+
readonly type: 'import-opened';
|
|
230
|
+
}
|
|
231
|
+
/** Close it, keeping nothing but the panel's own state. */
|
|
232
|
+
| {
|
|
233
|
+
readonly type: 'import-closed';
|
|
234
|
+
}
|
|
235
|
+
/** A read or an import is in flight. */
|
|
236
|
+
| {
|
|
237
|
+
readonly type: 'import-busy';
|
|
238
|
+
}
|
|
239
|
+
/** The host's candidate list arrived. */
|
|
240
|
+
| {
|
|
241
|
+
readonly type: 'import-loaded';
|
|
242
|
+
readonly listing: SshConfigListing;
|
|
243
|
+
}
|
|
244
|
+
/** One alias was ticked or unticked. */
|
|
245
|
+
| {
|
|
246
|
+
readonly type: 'import-toggled';
|
|
247
|
+
readonly alias: string;
|
|
248
|
+
}
|
|
249
|
+
/** Tick every candidate. */
|
|
250
|
+
| {
|
|
251
|
+
readonly type: 'import-all';
|
|
252
|
+
}
|
|
253
|
+
/** Untick everything. */
|
|
254
|
+
| {
|
|
255
|
+
readonly type: 'import-none';
|
|
256
|
+
}
|
|
257
|
+
/** The import settled: what went in, and what was refused with its reason. */
|
|
258
|
+
| {
|
|
259
|
+
readonly type: 'import-settled';
|
|
260
|
+
readonly imported: readonly string[];
|
|
261
|
+
readonly refused: readonly {
|
|
262
|
+
readonly alias: string;
|
|
263
|
+
readonly reason: string;
|
|
264
|
+
}[];
|
|
265
|
+
}
|
|
266
|
+
/** The read or the import failed. */
|
|
267
|
+
| {
|
|
268
|
+
readonly type: 'import-failed';
|
|
269
|
+
readonly error: string;
|
|
270
|
+
} | {
|
|
271
|
+
readonly type: 'import-failed';
|
|
272
|
+
readonly error: string;
|
|
273
|
+
};
|
|
274
|
+
/** The starting state: nothing loaded, no drafts, no queue. */
|
|
275
|
+
export declare function emptyRootsPanelDrafts(): RootsPanelDrafts;
|
|
276
|
+
/** @returns the state a freshly created controller holds. */
|
|
277
|
+
export declare function initialRootsPanelState(): RootsPanelState;
|
|
278
|
+
/**
|
|
279
|
+
* The panel's state machine. Pure: same state + same action => same result, and an action that changes
|
|
280
|
+
* nothing returns the **same object**, so a subscriber is never woken for a no-op.
|
|
281
|
+
*
|
|
282
|
+
* @param state - the current state.
|
|
283
|
+
* @param action - what happened.
|
|
284
|
+
* @returns the next state.
|
|
285
|
+
*/
|
|
286
|
+
export declare function rootsPanelReducer(state: RootsPanelState, action: RootsPanelAction): RootsPanelState;
|
|
287
|
+
/**
|
|
288
|
+
* The view model for a state.
|
|
289
|
+
*
|
|
290
|
+
* @param state - the panel state.
|
|
291
|
+
* @returns exactly what `buildRootsView` says: rows with `removable`, connections, notice and the edit gate.
|
|
292
|
+
*/
|
|
293
|
+
export declare function selectRootsView(state: RootsPanelState): RootsView;
|
|
294
|
+
/** True when the panel may accept input right now: an answer arrived and no request is in flight. */
|
|
295
|
+
export declare function canEdit(state: RootsPanelState): boolean;
|
|
296
|
+
/**
|
|
297
|
+
* Turn one `MessageKey` into text, with its parameters substituted.
|
|
298
|
+
*
|
|
299
|
+
* The implementation is `core/message.ts`'s (the session panel and the transfer panel need the same two lines and
|
|
300
|
+
* must not import this React-bearing module); what stays here is the roots panel's own convenience: no message
|
|
301
|
+
* renders as `''`, because every caller in this file is a template slot that simply renders nothing.
|
|
302
|
+
*
|
|
303
|
+
* @param copy - the dictionary of the active locale.
|
|
304
|
+
* @param message - the key plus optional `{name}` parameters.
|
|
305
|
+
* @returns the rendered text, or `''` when there is no message.
|
|
306
|
+
*/
|
|
307
|
+
export declare function resolveMessage(copy: RootsPanelCopy, message: MessageKey | undefined): string;
|