@linxin666/dsh-ssh 0.2.7 → 0.2.8
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/lib/client.js +17 -8
- package/lib/client.js.map +1 -1
- package/lib/index.js +38 -6
- package/lib/types/client/locales.d.ts +1 -1
- package/lib/types/dsh-home.d.ts +16 -0
- package/lib/types/index.d.ts +1 -1
- package/lib/types/protocol.d.ts +1 -1
- package/lib/types/store.d.ts +3 -2
- package/package.json +16 -13
- package/src/client/locales.ts +2 -2
- package/src/client/panel/HostsTab.tsx +15 -3
- package/src/client/panel/panel.module.css +15 -4
- package/src/client/sidebar-entry.ts +2 -2
- package/src/dsh-home.ts +36 -0
- package/src/index.ts +1 -1
- package/src/protocol.ts +1 -1
- package/src/store.ts +5 -3
package/lib/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-sett
|
|
|
2
2
|
import { closeSync, createReadStream, createWriteStream, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { Client } from "ssh2";
|
|
4
4
|
import { homedir, tmpdir } from "node:os";
|
|
5
|
-
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
5
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
6
6
|
import { createServer } from "node:net";
|
|
7
7
|
import { unlink } from "node:fs/promises";
|
|
8
8
|
import { randomBytes } from "node:crypto";
|
|
@@ -412,7 +412,7 @@ var require_lib$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
412
412
|
});
|
|
413
413
|
}));
|
|
414
414
|
//#endregion
|
|
415
|
-
//#region src/
|
|
415
|
+
//#region src/dsh-home.ts
|
|
416
416
|
var import_lib = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
417
417
|
var __defProp = Object.defineProperty;
|
|
418
418
|
var __name = (target, value) => __defProp(target, "name", {
|
|
@@ -1025,7 +1025,39 @@ var import_lib = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((export
|
|
|
1025
1025
|
module.exports = Schema;
|
|
1026
1026
|
})))(), 1);
|
|
1027
1027
|
/**
|
|
1028
|
-
*
|
|
1028
|
+
* DSH_HOME resolution shared by the plugin family's Host halves: the
|
|
1029
|
+
* environment override wins, the platform home fallback follows. Mirrors
|
|
1030
|
+
* what dsh-pet and dsh-liangshen each used to implement locally.
|
|
1031
|
+
*/
|
|
1032
|
+
/** Expand a leading ~ (or ~user) in a path, platform-style. */
|
|
1033
|
+
function expandHome$1(path, home = homedir()) {
|
|
1034
|
+
if (path === "~") return home;
|
|
1035
|
+
if (path.startsWith("~/") || path.startsWith("~\\")) return join(home, path.slice(2));
|
|
1036
|
+
return path;
|
|
1037
|
+
}
|
|
1038
|
+
/**
|
|
1039
|
+
* Resolve the DSH home directory.
|
|
1040
|
+
* @param env - process environment to read DSH_HOME from.
|
|
1041
|
+
* @param home - platform home directory fallback (test seam).
|
|
1042
|
+
* @returns the absolute DSH home path.
|
|
1043
|
+
*/
|
|
1044
|
+
function resolveDshHome(env = process.env, home = homedir()) {
|
|
1045
|
+
const raw = env.DSH_HOME;
|
|
1046
|
+
if (raw !== void 0 && raw.trim() !== "") {
|
|
1047
|
+
const expanded = expandHome$1(raw.trim(), home);
|
|
1048
|
+
return isAbsolute(expanded) ? expanded : join(process.cwd(), expanded);
|
|
1049
|
+
}
|
|
1050
|
+
return join(home, ".dsh");
|
|
1051
|
+
}
|
|
1052
|
+
/** Resolve the DSH home directory from the live environment. */
|
|
1053
|
+
function dshHome() {
|
|
1054
|
+
return resolveDshHome();
|
|
1055
|
+
}
|
|
1056
|
+
//#endregion
|
|
1057
|
+
//#region src/store.ts
|
|
1058
|
+
/**
|
|
1059
|
+
* Host config store: one JSON file (`$DSH_HOME/dsh-ssh.json`, defaulting
|
|
1060
|
+
* to `~/.dsh`) holding every
|
|
1029
1061
|
* SSH host entry, written atomically (tmp + rename). Also parses the user's
|
|
1030
1062
|
* standard `~/.ssh/config` for one-shot import. Secrets (passwords,
|
|
1031
1063
|
* passphrases) live in this user-owned file in plaintext — same trust model
|
|
@@ -1033,9 +1065,9 @@ var import_lib = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((export
|
|
|
1033
1065
|
*/
|
|
1034
1066
|
/** File format version. */
|
|
1035
1067
|
const FORMAT_VERSION = 1;
|
|
1036
|
-
/** Store file location:
|
|
1068
|
+
/** Store file location: $DSH_HOME/dsh-ssh.json (defaults to ~/.dsh). */
|
|
1037
1069
|
function storePath() {
|
|
1038
|
-
return join(
|
|
1070
|
+
return join(dshHome(), "dsh-ssh.json");
|
|
1039
1071
|
}
|
|
1040
1072
|
/** The user's standard OpenSSH config path. */
|
|
1041
1073
|
function sshConfigPath() {
|
|
@@ -3457,7 +3489,7 @@ const DEFAULT_ANNOUNCE = false;
|
|
|
3457
3489
|
/** Order of the announcement section within the tool-guidance band. */
|
|
3458
3490
|
const SECTION_ORDER = 150;
|
|
3459
3491
|
/** Model-facing announcement: plugin presence, capabilities, and limits. */
|
|
3460
|
-
const SSH_GUIDANCE = "本机已安装 dsh-ssh 插件(DSH 远程 SSH 运维):侧边栏「SSH」入口;在 dsh-web-ui 插件全家桶仓库(packages/dsh-ssh)统一维护。能力:主机配置存
|
|
3492
|
+
const SSH_GUIDANCE = "本机已安装 dsh-ssh 插件(DSH 远程 SSH 运维):侧边栏「SSH」入口;在 dsh-web-ui 插件全家桶仓库(packages/dsh-ssh)统一维护。能力:主机配置存 $DSH_HOME/dsh-ssh.json(默认 ~/.dsh)(可从 ~/.ssh/config 导入);持久连接池复用长连接(空闲 30 分钟自动断开);ssh_list 列出主机、ssh_exec 执行远程命令、ssh_upload/ssh_download 传输文件、ssh_tunnel 本地端口转发(访问远程数据库/内网服务)、ssh_cluster 集群并发执行;支持密钥/密码/ssh-agent 认证、passphrase 密钥与 ProxyJump 跳板机;Web 终端走 WebSocket。限制:主机操作由用户在 GUI 中配置后 agent 方可使用;密码以明文存在用户主目录私有文件(权限 0600);命令输出原样返回、可能含敏感信息;断线重连可能重放非幂等命令;传输/执行消耗真实远程资源,先确认再操作。用户提到「SSH / 远程服务器 / 服务器操作 / 跳板机 / 隧道 / 部署 / 上传下载」时即指本插件,请据此协作。";
|
|
3461
3493
|
/**
|
|
3462
3494
|
* Mount the SSH engine, routes, tools, and announcement.
|
|
3463
3495
|
* @param ctx - host plugin context carrying webServer/tools/systemPrompt.
|
|
@@ -59,7 +59,7 @@ export declare const zh: {
|
|
|
59
59
|
readonly 'form.keyPathHint': "如 ~/.ssh/id_ed25519";
|
|
60
60
|
readonly 'form.passphrase': "密钥口令";
|
|
61
61
|
readonly 'form.password': "密码";
|
|
62
|
-
readonly 'form.passwordHint': "明文保存在
|
|
62
|
+
readonly 'form.passwordHint': "明文保存在 $DSH_HOME/dsh-ssh.json(本机私有文件)";
|
|
63
63
|
readonly 'form.passwordRequired': "密码认证需要填写密码。";
|
|
64
64
|
readonly 'form.proxyJump': "跳板机别名";
|
|
65
65
|
readonly 'form.proxyJumpHint': "逗号分隔的本地主机别名,按顺序连接";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DSH_HOME resolution shared by the plugin family's Host halves: the
|
|
3
|
+
* environment override wins, the platform home fallback follows. Mirrors
|
|
4
|
+
* what dsh-pet and dsh-liangshen each used to implement locally.
|
|
5
|
+
*/
|
|
6
|
+
/** Expand a leading ~ (or ~user) in a path, platform-style. */
|
|
7
|
+
export declare function expandHome(path: string, home?: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Resolve the DSH home directory.
|
|
10
|
+
* @param env - process environment to read DSH_HOME from.
|
|
11
|
+
* @param home - platform home directory fallback (test seam).
|
|
12
|
+
* @returns the absolute DSH home path.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveDshHome(env?: NodeJS.ProcessEnv, home?: string): string;
|
|
15
|
+
/** Resolve the DSH home directory from the live environment. */
|
|
16
|
+
export declare function dshHome(): string;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -38,7 +38,7 @@ export interface Config {
|
|
|
38
38
|
}
|
|
39
39
|
export declare const Config: z<Config>;
|
|
40
40
|
/** Model-facing announcement: plugin presence, capabilities, and limits. */
|
|
41
|
-
export declare const SSH_GUIDANCE = "\u672C\u673A\u5DF2\u5B89\u88C5 dsh-ssh \u63D2\u4EF6\uFF08DSH \u8FDC\u7A0B SSH \u8FD0\u7EF4\uFF09\uFF1A\u4FA7\u8FB9\u680F\u300CSSH\u300D\u5165\u53E3\uFF1B\u5728 dsh-web-ui \u63D2\u4EF6\u5168\u5BB6\u6876\u4ED3\u5E93\uFF08packages/dsh-ssh\uFF09\u7EDF\u4E00\u7EF4\u62A4\u3002\u80FD\u529B\uFF1A\u4E3B\u673A\u914D\u7F6E\u5B58
|
|
41
|
+
export declare const SSH_GUIDANCE = "\u672C\u673A\u5DF2\u5B89\u88C5 dsh-ssh \u63D2\u4EF6\uFF08DSH \u8FDC\u7A0B SSH \u8FD0\u7EF4\uFF09\uFF1A\u4FA7\u8FB9\u680F\u300CSSH\u300D\u5165\u53E3\uFF1B\u5728 dsh-web-ui \u63D2\u4EF6\u5168\u5BB6\u6876\u4ED3\u5E93\uFF08packages/dsh-ssh\uFF09\u7EDF\u4E00\u7EF4\u62A4\u3002\u80FD\u529B\uFF1A\u4E3B\u673A\u914D\u7F6E\u5B58 $DSH_HOME/dsh-ssh.json\uFF08\u9ED8\u8BA4 ~/.dsh\uFF09\uFF08\u53EF\u4ECE ~/.ssh/config \u5BFC\u5165\uFF09\uFF1B\u6301\u4E45\u8FDE\u63A5\u6C60\u590D\u7528\u957F\u8FDE\u63A5\uFF08\u7A7A\u95F2 30 \u5206\u949F\u81EA\u52A8\u65AD\u5F00\uFF09\uFF1Bssh_list \u5217\u51FA\u4E3B\u673A\u3001ssh_exec \u6267\u884C\u8FDC\u7A0B\u547D\u4EE4\u3001ssh_upload/ssh_download \u4F20\u8F93\u6587\u4EF6\u3001ssh_tunnel \u672C\u5730\u7AEF\u53E3\u8F6C\u53D1\uFF08\u8BBF\u95EE\u8FDC\u7A0B\u6570\u636E\u5E93/\u5185\u7F51\u670D\u52A1\uFF09\u3001ssh_cluster \u96C6\u7FA4\u5E76\u53D1\u6267\u884C\uFF1B\u652F\u6301\u5BC6\u94A5/\u5BC6\u7801/ssh-agent \u8BA4\u8BC1\u3001passphrase \u5BC6\u94A5\u4E0E ProxyJump \u8DF3\u677F\u673A\uFF1BWeb \u7EC8\u7AEF\u8D70 WebSocket\u3002\u9650\u5236\uFF1A\u4E3B\u673A\u64CD\u4F5C\u7531\u7528\u6237\u5728 GUI \u4E2D\u914D\u7F6E\u540E agent \u65B9\u53EF\u4F7F\u7528\uFF1B\u5BC6\u7801\u4EE5\u660E\u6587\u5B58\u5728\u7528\u6237\u4E3B\u76EE\u5F55\u79C1\u6709\u6587\u4EF6\uFF08\u6743\u9650 0600\uFF09\uFF1B\u547D\u4EE4\u8F93\u51FA\u539F\u6837\u8FD4\u56DE\u3001\u53EF\u80FD\u542B\u654F\u611F\u4FE1\u606F\uFF1B\u65AD\u7EBF\u91CD\u8FDE\u53EF\u80FD\u91CD\u653E\u975E\u5E42\u7B49\u547D\u4EE4\uFF1B\u4F20\u8F93/\u6267\u884C\u6D88\u8017\u771F\u5B9E\u8FDC\u7A0B\u8D44\u6E90\uFF0C\u5148\u786E\u8BA4\u518D\u64CD\u4F5C\u3002\u7528\u6237\u63D0\u5230\u300CSSH / \u8FDC\u7A0B\u670D\u52A1\u5668 / \u670D\u52A1\u5668\u64CD\u4F5C / \u8DF3\u677F\u673A / \u96A7\u9053 / \u90E8\u7F72 / \u4E0A\u4F20\u4E0B\u8F7D\u300D\u65F6\u5373\u6307\u672C\u63D2\u4EF6\uFF0C\u8BF7\u636E\u6B64\u534F\u4F5C\u3002";
|
|
42
42
|
/**
|
|
43
43
|
* Mount the SSH engine, routes, tools, and announcement.
|
|
44
44
|
* @param ctx - host plugin context carrying webServer/tools/systemPrompt.
|
package/lib/types/protocol.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
/** Authentication flavors a host entry may carry. */
|
|
7
7
|
export type SshAuthKind = 'key' | 'password' | 'agent';
|
|
8
|
-
/** One stored host entry (the
|
|
8
|
+
/** One stored host entry (the $DSH_HOME/dsh-ssh.json store shape). */
|
|
9
9
|
export interface SshHostEntry {
|
|
10
10
|
/** Stable, user-chosen identifier used by every operation. */
|
|
11
11
|
alias: string;
|
package/lib/types/store.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Host config store: one JSON file (
|
|
2
|
+
* Host config store: one JSON file (`$DSH_HOME/dsh-ssh.json`, defaulting
|
|
3
|
+
* to `~/.dsh`) holding every
|
|
3
4
|
* SSH host entry, written atomically (tmp + rename). Also parses the user's
|
|
4
5
|
* standard `~/.ssh/config` for one-shot import. Secrets (passwords,
|
|
5
6
|
* passphrases) live in this user-owned file in plaintext — same trust model
|
|
6
7
|
* as ssh-skill's annotated ssh-config comments; document it, never log it.
|
|
7
8
|
*/
|
|
8
9
|
import type { HostPayload, ImportResult, SshHostEntry, SshHostSummary } from './protocol.ts';
|
|
9
|
-
/** Store file location:
|
|
10
|
+
/** Store file location: $DSH_HOME/dsh-ssh.json (defaults to ~/.dsh). */
|
|
10
11
|
export declare function storePath(): string;
|
|
11
12
|
/** The user's standard OpenSSH config path. */
|
|
12
13
|
export declare function sshConfigPath(): string;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linxin666/dsh-ssh",
|
|
3
3
|
"description": "Remote SSH operations for the dsh web GUI: host config store (~/.dsh/dsh-ssh.json, import from ~/.ssh/config), a persistent ssh2 connection pool with jump-host support, exec / PTY web terminal / SFTP transfer / local port-forward tunnels / cluster execution, plus agent tools (ssh_list, ssh_exec, ssh_upload, ssh_download, ssh_tunnel, ssh_cluster). Hot-pluggable — mounted via ~/.dsh/cordis.patch.yml + a profile node_modules symlink, no dsh source changes.",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.8",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "^22.19.0 || >=24.0.0"
|
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dsh": {
|
|
28
|
+
"engines": {
|
|
29
|
+
"dsh": ">=0.1.1-rc.1"
|
|
30
|
+
},
|
|
28
31
|
"bundle": {
|
|
29
32
|
"patch": "./cordis.patch.yml"
|
|
30
33
|
},
|
|
@@ -49,18 +52,18 @@
|
|
|
49
52
|
},
|
|
50
53
|
"devDependencies": {
|
|
51
54
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
52
|
-
"@deepseek-ai/dsh-client-connection": "^0.1.1-rc.
|
|
53
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.1-rc.
|
|
54
|
-
"@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.
|
|
55
|
-
"@deepseek-ai/dsh-client-ui-settings": "^0.1.1-rc.
|
|
56
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.1.1-rc.
|
|
57
|
-
"@deepseek-ai/dsh-host-webserver": "^0.1.1-rc.
|
|
58
|
-
"@deepseek-ai/dsh-llm": "^0.1.1-rc.
|
|
59
|
-
"@deepseek-ai/dsh-scope": "^0.1.1-rc.
|
|
60
|
-
"@deepseek-ai/dsh-settings": "^0.1.1-rc.
|
|
61
|
-
"@deepseek-ai/dsh-system-prompt": "^0.1.1-rc.
|
|
62
|
-
"@deepseek-ai/dsh-timeout": "^0.1.1-rc.
|
|
63
|
-
"@deepseek-ai/dsh-tools": "^0.1.1-rc.
|
|
55
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.1-rc.2",
|
|
56
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.1-rc.2",
|
|
57
|
+
"@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.2",
|
|
58
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.1-rc.2",
|
|
59
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.1-rc.2",
|
|
60
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.1-rc.2",
|
|
61
|
+
"@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
|
|
62
|
+
"@deepseek-ai/dsh-scope": "^0.1.1-rc.2",
|
|
63
|
+
"@deepseek-ai/dsh-settings": "^0.1.1-rc.2",
|
|
64
|
+
"@deepseek-ai/dsh-system-prompt": "^0.1.1-rc.2",
|
|
65
|
+
"@deepseek-ai/dsh-timeout": "^0.1.1-rc.2",
|
|
66
|
+
"@deepseek-ai/dsh-tools": "^0.1.1-rc.2",
|
|
64
67
|
"@types/node": "^22.20.0",
|
|
65
68
|
"@types/react": "~18.3.1",
|
|
66
69
|
"@types/react-dom": "^18.3.5",
|
package/src/client/locales.ts
CHANGED
|
@@ -62,7 +62,7 @@ export const zh = {
|
|
|
62
62
|
'form.keyPathHint': '如 ~/.ssh/id_ed25519',
|
|
63
63
|
'form.passphrase': '密钥口令',
|
|
64
64
|
'form.password': '密码',
|
|
65
|
-
'form.passwordHint': '明文保存在
|
|
65
|
+
'form.passwordHint': '明文保存在 $DSH_HOME/dsh-ssh.json(本机私有文件)',
|
|
66
66
|
'form.passwordRequired': '密码认证需要填写密码。',
|
|
67
67
|
'form.proxyJump': '跳板机别名',
|
|
68
68
|
'form.proxyJumpHint': '逗号分隔的本地主机别名,按顺序连接',
|
|
@@ -209,7 +209,7 @@ export const en: Record<keyof typeof zh, string> = {
|
|
|
209
209
|
'form.keyPathHint': 'e.g. ~/.ssh/id_ed25519',
|
|
210
210
|
'form.passphrase': 'Key passphrase',
|
|
211
211
|
'form.password': 'Password',
|
|
212
|
-
'form.passwordHint': 'Stored in plaintext in
|
|
212
|
+
'form.passwordHint': 'Stored in plaintext in $DSH_HOME/dsh-ssh.json (private local file)',
|
|
213
213
|
'form.passwordRequired': 'Password auth requires a password.',
|
|
214
214
|
'form.proxyJump': 'Jump host aliases',
|
|
215
215
|
'form.proxyJumpHint': 'Comma-separated local aliases, chained in order',
|
|
@@ -105,15 +105,21 @@ export function HostsTab({ api, onConnect }: HostsTabProps) {
|
|
|
105
105
|
return () => { clearTimeout(timer) }
|
|
106
106
|
}, [search, load])
|
|
107
107
|
|
|
108
|
+
// Every async setState path guards with mountedRef, not just load(): a
|
|
109
|
+
// promise settling after unmount would setState against the torn-down
|
|
110
|
+
// environment (window is not defined, main-CI flake).
|
|
108
111
|
const runTest = async (alias: string): Promise<void> => {
|
|
112
|
+
if (!mountedRef.current) return
|
|
109
113
|
setTestingAlias(alias)
|
|
110
114
|
try {
|
|
111
115
|
const result = await api.testHost(alias)
|
|
116
|
+
if (!mountedRef.current) return
|
|
112
117
|
setTestResults(prev => ({ ...prev, [alias]: result }))
|
|
113
118
|
} catch (cause) {
|
|
119
|
+
if (!mountedRef.current) return
|
|
114
120
|
setTestResults(prev => ({ ...prev, [alias]: { ok: false, error: errorMessage(cause) } }))
|
|
115
121
|
} finally {
|
|
116
|
-
setTestingAlias(null)
|
|
122
|
+
if (mountedRef.current) setTestingAlias(null)
|
|
117
123
|
}
|
|
118
124
|
}
|
|
119
125
|
|
|
@@ -121,32 +127,38 @@ export function HostsTab({ api, onConnect }: HostsTabProps) {
|
|
|
121
127
|
if (!window.confirm(tt('hosts.deleteConfirm', { alias }))) return
|
|
122
128
|
try {
|
|
123
129
|
await api.deleteHost(alias)
|
|
130
|
+
if (!mountedRef.current) return
|
|
124
131
|
void load()
|
|
125
132
|
} catch (cause) {
|
|
133
|
+
if (!mountedRef.current) return
|
|
126
134
|
setError(errorMessage(cause))
|
|
127
135
|
}
|
|
128
136
|
}
|
|
129
137
|
|
|
130
138
|
// Group-header batch action (#379): test every host in the group.
|
|
131
139
|
const testGroup = async (group: HostGroup): Promise<void> => {
|
|
140
|
+
if (!mountedRef.current) return
|
|
132
141
|
setTestingGroup(group.key)
|
|
133
142
|
try {
|
|
134
143
|
await Promise.all(group.hosts.map(host => runTest(host.alias)))
|
|
135
144
|
} finally {
|
|
136
|
-
setTestingGroup(null)
|
|
145
|
+
if (mountedRef.current) setTestingGroup(null)
|
|
137
146
|
}
|
|
138
147
|
}
|
|
139
148
|
|
|
140
149
|
const importConfig = async (): Promise<void> => {
|
|
150
|
+
if (!mountedRef.current) return
|
|
141
151
|
setImporting(true)
|
|
142
152
|
try {
|
|
143
153
|
const result = await api.importSshConfig()
|
|
154
|
+
if (!mountedRef.current) return
|
|
144
155
|
setNotice(tt('hosts.imported', { parsed: result.parsed, added: result.added, skipped: result.skipped }))
|
|
145
156
|
void load()
|
|
146
157
|
} catch (cause) {
|
|
158
|
+
if (!mountedRef.current) return
|
|
147
159
|
setError(errorMessage(cause))
|
|
148
160
|
} finally {
|
|
149
|
-
setImporting(false)
|
|
161
|
+
if (mountedRef.current) setImporting(false)
|
|
150
162
|
}
|
|
151
163
|
}
|
|
152
164
|
|
|
@@ -49,12 +49,13 @@ html[data-dsh-ssh-active]:not([data-dsh-taskboard-active]) [class*='centerCol']
|
|
|
49
49
|
/* --- sidebar entry row ------------------------------------------------------- */
|
|
50
50
|
|
|
51
51
|
.entry {
|
|
52
|
+
box-sizing: border-box;
|
|
52
53
|
display: flex;
|
|
53
54
|
align-items: center;
|
|
54
|
-
gap:
|
|
55
|
+
gap: 10px;
|
|
55
56
|
width: 100%;
|
|
56
|
-
height:
|
|
57
|
-
padding: 0
|
|
57
|
+
min-height: 36px;
|
|
58
|
+
padding: 0 10px;
|
|
58
59
|
background: transparent;
|
|
59
60
|
border: none;
|
|
60
61
|
border-radius: 8px;
|
|
@@ -79,9 +80,17 @@ html[data-dsh-ssh-active]:not([data-dsh-taskboard-active]) [class*='centerCol']
|
|
|
79
80
|
display: inline-flex;
|
|
80
81
|
align-items: center;
|
|
81
82
|
justify-content: center;
|
|
83
|
+
width: 24px;
|
|
84
|
+
height: 24px;
|
|
82
85
|
flex: none;
|
|
83
86
|
}
|
|
84
87
|
|
|
88
|
+
.entryIcon svg {
|
|
89
|
+
display: block;
|
|
90
|
+
width: 18px;
|
|
91
|
+
height: 18px;
|
|
92
|
+
}
|
|
93
|
+
|
|
85
94
|
.entryLabel {
|
|
86
95
|
overflow: hidden;
|
|
87
96
|
text-overflow: ellipsis;
|
|
@@ -91,7 +100,9 @@ html[data-dsh-ssh-active]:not([data-dsh-taskboard-active]) [class*='centerCol']
|
|
|
91
100
|
[data-dsh-frame][data-sidebar-collapsed] .entry {
|
|
92
101
|
justify-content: center;
|
|
93
102
|
padding: 0;
|
|
94
|
-
width:
|
|
103
|
+
width: 40px;
|
|
104
|
+
min-height: 40px;
|
|
105
|
+
margin-inline: auto;
|
|
95
106
|
}
|
|
96
107
|
|
|
97
108
|
[data-dsh-frame][data-sidebar-collapsed] .entryLabel {
|
|
@@ -16,8 +16,8 @@ import { mountSidebarEntry as mountSharedSidebarEntry } from './sidebar-entry-co
|
|
|
16
16
|
/** Stable data attribute identifying the injected entry row. */
|
|
17
17
|
export const ENTRY_SELECTOR = '[data-dsh-ssh-entry]'
|
|
18
18
|
|
|
19
|
-
/** Inline
|
|
20
|
-
const ICON = '<svg viewBox="0 0 16 16" width="
|
|
19
|
+
/** Inline terminal glyph sized to the shell's current sidebar navigation icons. */
|
|
20
|
+
const ICON = '<svg viewBox="0 0 16 16" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="1.75" y="2.25" width="12.5" height="11.5" rx="1.75"/><path d="M4.25 5.25l2.75 2.75-2.75 2.75"/><path d="M8.5 10.75h3.25"/></svg>'
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Mount the sidebar entry, waiting for the shell to render and self-healing
|
package/src/dsh-home.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Generated by scripts/sync-shared.mjs from shared/host/dsh-home.ts. Do not edit this copy; edit the shared source and run "node scripts/sync-shared.mjs".
|
|
2
|
+
/**
|
|
3
|
+
* DSH_HOME resolution shared by the plugin family's Host halves: the
|
|
4
|
+
* environment override wins, the platform home fallback follows. Mirrors
|
|
5
|
+
* what dsh-pet and dsh-liangshen each used to implement locally.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { homedir } from 'node:os'
|
|
9
|
+
import { isAbsolute, join } from 'node:path'
|
|
10
|
+
|
|
11
|
+
/** Expand a leading ~ (or ~user) in a path, platform-style. */
|
|
12
|
+
export function expandHome(path: string, home: string = homedir()): string {
|
|
13
|
+
if (path === '~') return home
|
|
14
|
+
if (path.startsWith('~/') || path.startsWith('~\\')) return join(home, path.slice(2))
|
|
15
|
+
return path
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Resolve the DSH home directory.
|
|
20
|
+
* @param env - process environment to read DSH_HOME from.
|
|
21
|
+
* @param home - platform home directory fallback (test seam).
|
|
22
|
+
* @returns the absolute DSH home path.
|
|
23
|
+
*/
|
|
24
|
+
export function resolveDshHome(env: NodeJS.ProcessEnv = process.env, home: string = homedir()): string {
|
|
25
|
+
const raw = env.DSH_HOME
|
|
26
|
+
if (raw !== undefined && raw.trim() !== '') {
|
|
27
|
+
const expanded = expandHome(raw.trim(), home)
|
|
28
|
+
return isAbsolute(expanded) ? expanded : join(process.cwd(), expanded)
|
|
29
|
+
}
|
|
30
|
+
return join(home, '.dsh')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Resolve the DSH home directory from the live environment. */
|
|
34
|
+
export function dshHome(): string {
|
|
35
|
+
return resolveDshHome()
|
|
36
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -64,7 +64,7 @@ const DEFAULT_ANNOUNCE = false
|
|
|
64
64
|
const SECTION_ORDER = 150
|
|
65
65
|
|
|
66
66
|
/** Model-facing announcement: plugin presence, capabilities, and limits. */
|
|
67
|
-
export const SSH_GUIDANCE = '本机已安装 dsh-ssh 插件(DSH 远程 SSH 运维):侧边栏「SSH」入口;在 dsh-web-ui 插件全家桶仓库(packages/dsh-ssh)统一维护。能力:主机配置存
|
|
67
|
+
export const SSH_GUIDANCE = '本机已安装 dsh-ssh 插件(DSH 远程 SSH 运维):侧边栏「SSH」入口;在 dsh-web-ui 插件全家桶仓库(packages/dsh-ssh)统一维护。能力:主机配置存 $DSH_HOME/dsh-ssh.json(默认 ~/.dsh)(可从 ~/.ssh/config 导入);持久连接池复用长连接(空闲 30 分钟自动断开);ssh_list 列出主机、ssh_exec 执行远程命令、ssh_upload/ssh_download 传输文件、ssh_tunnel 本地端口转发(访问远程数据库/内网服务)、ssh_cluster 集群并发执行;支持密钥/密码/ssh-agent 认证、passphrase 密钥与 ProxyJump 跳板机;Web 终端走 WebSocket。限制:主机操作由用户在 GUI 中配置后 agent 方可使用;密码以明文存在用户主目录私有文件(权限 0600);命令输出原样返回、可能含敏感信息;断线重连可能重放非幂等命令;传输/执行消耗真实远程资源,先确认再操作。用户提到「SSH / 远程服务器 / 服务器操作 / 跳板机 / 隧道 / 部署 / 上传下载」时即指本插件,请据此协作。'
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
70
|
* Mount the SSH engine, routes, tools, and announcement.
|
package/src/protocol.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
/** Authentication flavors a host entry may carry. */
|
|
8
8
|
export type SshAuthKind = 'key' | 'password' | 'agent'
|
|
9
9
|
|
|
10
|
-
/** One stored host entry (the
|
|
10
|
+
/** One stored host entry (the $DSH_HOME/dsh-ssh.json store shape). */
|
|
11
11
|
export interface SshHostEntry {
|
|
12
12
|
/** Stable, user-chosen identifier used by every operation. */
|
|
13
13
|
alias: string
|
package/src/store.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Host config store: one JSON file (
|
|
2
|
+
* Host config store: one JSON file (`$DSH_HOME/dsh-ssh.json`, defaulting
|
|
3
|
+
* to `~/.dsh`) holding every
|
|
3
4
|
* SSH host entry, written atomically (tmp + rename). Also parses the user's
|
|
4
5
|
* standard `~/.ssh/config` for one-shot import. Secrets (passwords,
|
|
5
6
|
* passphrases) live in this user-owned file in plaintext — same trust model
|
|
@@ -9,14 +10,15 @@
|
|
|
9
10
|
import { existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs'
|
|
10
11
|
import { homedir } from 'node:os'
|
|
11
12
|
import { dirname, join, resolve } from 'node:path'
|
|
13
|
+
import { dshHome } from './dsh-home.ts'
|
|
12
14
|
import type { HostPayload, ImportResult, SshHostEntry, SshHostSummary } from './protocol.ts'
|
|
13
15
|
|
|
14
16
|
/** File format version. */
|
|
15
17
|
const FORMAT_VERSION = 1
|
|
16
18
|
|
|
17
|
-
/** Store file location:
|
|
19
|
+
/** Store file location: $DSH_HOME/dsh-ssh.json (defaults to ~/.dsh). */
|
|
18
20
|
export function storePath(): string {
|
|
19
|
-
return join(
|
|
21
|
+
return join(dshHome(), 'dsh-ssh.json')
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
/** The user's standard OpenSSH config path. */
|