@linxin666/dsh-ssh 0.3.6 → 0.3.9
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 +474 -471
- package/lib/client.js.map +1 -1
- package/lib/index.js +19 -14
- package/lib/types/client/index.d.ts +2 -1
- package/lib/types/client/locales.d.ts +1 -0
- package/lib/types/index.d.ts +2 -1
- package/package.json +17 -17
- package/src/client/api.ts +5 -1
- package/src/client/index.ts +4 -1
- package/src/client/locales.ts +2 -0
- package/src/dsh-home.ts +1 -1
- package/src/index.ts +27 -14
package/lib/client.js
CHANGED
|
@@ -24,128 +24,484 @@ window.__ModuleLoader__.load({
|
|
|
24
24
|
terminal: "/api/dsh-ssh/terminal"
|
|
25
25
|
};
|
|
26
26
|
//#endregion
|
|
27
|
-
//#region src/client/
|
|
27
|
+
//#region src/client/locales.ts
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
30
|
-
* access path the panel components use — plain fetch/WebSocket, same origin.
|
|
29
|
+
* dsh-ssh surface copy: zh is the key source, en mirrors every key.
|
|
31
30
|
*/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
const zh = {
|
|
32
|
+
"entry.label": "SSH",
|
|
33
|
+
"entry.tooltip": "远程 SSH 运维面板",
|
|
34
|
+
"panel.title": "远程 SSH",
|
|
35
|
+
"panel.backToConversation": "返回会话",
|
|
36
|
+
"tab.hosts": "主机",
|
|
37
|
+
"tab.terminal": "终端",
|
|
38
|
+
"tab.transfer": "传输",
|
|
39
|
+
"tab.tunnels": "隧道",
|
|
40
|
+
"tab.cluster": "集群",
|
|
41
|
+
"hosts.empty": "尚未配置主机。点击「新增主机」添加,或从 ~/.ssh/config 导入。",
|
|
42
|
+
"hosts.add": "新增主机",
|
|
43
|
+
"hosts.import": "导入 ~/.ssh/config",
|
|
44
|
+
"hosts.search": "搜索别名 / 描述 / 标签…",
|
|
45
|
+
"hosts.groupBy.label": "分组方式",
|
|
46
|
+
"hosts.groupBy.none": "不分组",
|
|
47
|
+
"hosts.groupBy.environment": "按环境分组",
|
|
48
|
+
"hosts.groupBy.tags": "按标签分组",
|
|
49
|
+
"hosts.group.ungrouped": "未设置环境",
|
|
50
|
+
"hosts.group.noTags": "无标签",
|
|
51
|
+
"hosts.group.count": "{count} 台",
|
|
52
|
+
"hosts.group.testAll": "测试全部",
|
|
53
|
+
"hosts.col.alias": "别名",
|
|
54
|
+
"hosts.col.host": "主机",
|
|
55
|
+
"hosts.col.user": "用户",
|
|
56
|
+
"hosts.col.auth": "认证",
|
|
57
|
+
"hosts.col.environment": "环境",
|
|
58
|
+
"hosts.col.tags": "标签",
|
|
59
|
+
"hosts.col.description": "备注",
|
|
60
|
+
"hosts.col.actions": "操作",
|
|
61
|
+
"hosts.test": "测试",
|
|
62
|
+
"hosts.edit": "编辑",
|
|
63
|
+
"hosts.delete": "删除",
|
|
64
|
+
"hosts.deleteConfirm": "确定删除主机「{alias}」?将同时停止其隧道。",
|
|
65
|
+
"hosts.imported": "解析 {parsed} 个配置块,新增 {added} 台主机,跳过 {skipped} 个。",
|
|
66
|
+
"hosts.testing": "连接测试中…",
|
|
67
|
+
"hosts.testOk": "连接成功({latency} ms)",
|
|
68
|
+
"hosts.testFail": "连接失败:{error}",
|
|
69
|
+
"hosts.connected": "连接",
|
|
70
|
+
"hosts.notConnected": "未连接",
|
|
71
|
+
"form.title.create": "新增主机",
|
|
72
|
+
"form.title.edit": "编辑主机 {alias}",
|
|
73
|
+
"form.alias": "别名",
|
|
74
|
+
"form.aliasHint": "小写字母、数字与单连字符,操作时使用",
|
|
75
|
+
"form.host": "主机地址",
|
|
76
|
+
"form.port": "端口",
|
|
77
|
+
"form.user": "用户名",
|
|
78
|
+
"form.auth": "认证方式",
|
|
79
|
+
"form.auth.key": "密钥",
|
|
80
|
+
"form.auth.password": "密码",
|
|
81
|
+
"form.auth.agent": "SSH Agent",
|
|
82
|
+
"form.agentPath": "Agent 路径",
|
|
83
|
+
"form.agentPathHint": "留空自动检测(SSH_AUTH_SOCK / Windows Pageant);也可填 socket 路径或 pageant",
|
|
84
|
+
"form.authKeepHint": "编辑时密钥/密码/Agent 留空将保留原认证方式与已存凭据;如需更换认证方式请填写新凭据。",
|
|
85
|
+
"form.keyPath": "私钥路径",
|
|
86
|
+
"form.keyPathHint": "如 ~/.ssh/id_ed25519",
|
|
87
|
+
"form.passphrase": "密钥口令",
|
|
88
|
+
"form.password": "密码",
|
|
89
|
+
"form.passwordHint": "明文保存在 $DSH_HOME/dsh-ssh.json(本机私有文件)",
|
|
90
|
+
"form.passwordRequired": "密码认证需要填写密码。",
|
|
91
|
+
"form.proxyJump": "跳板机别名",
|
|
92
|
+
"form.proxyJumpHint": "逗号分隔的本地主机别名,按顺序连接",
|
|
93
|
+
"form.description": "备注",
|
|
94
|
+
"form.environment": "环境",
|
|
95
|
+
"form.environmentHint": "如 development / production",
|
|
96
|
+
"form.tags": "标签",
|
|
97
|
+
"form.tagsHint": "逗号分隔",
|
|
98
|
+
"form.location": "位置",
|
|
99
|
+
"form.save": "保存",
|
|
100
|
+
"form.cancel": "取消",
|
|
101
|
+
"form.required": "请填写必填字段:别名、主机地址、用户名。",
|
|
102
|
+
"form.portInvalid": "端口必须是 1-65535 之间的整数。",
|
|
103
|
+
"terminal.selectHost": "选择主机",
|
|
104
|
+
"terminal.connect": "连接",
|
|
105
|
+
"terminal.disconnect": "断开",
|
|
106
|
+
"terminal.placeholder": "选择主机后点击「连接」打开远程终端。",
|
|
107
|
+
"terminal.connecting": "正在连接…",
|
|
108
|
+
"terminal.ready": "终端已连接({alias})",
|
|
109
|
+
"terminal.exited": "终端已退出({alias})",
|
|
110
|
+
"terminal.error": "终端错误:{error}",
|
|
111
|
+
"terminal.auth.title": "二次身份验证 (2FA / 交互式认证)",
|
|
112
|
+
"terminal.auth.submit": "提交验证码",
|
|
113
|
+
"terminal.auth.cancel": "取消",
|
|
114
|
+
"terminal.auth.placeholder": "请输入验证码或口令",
|
|
115
|
+
"terminal.auth.hint": "服务器请求进行交互式二次验证(输入仅本次生效,不落盘):",
|
|
116
|
+
"transfer.selectHost": "选择主机",
|
|
117
|
+
"transfer.remotePath": "远程路径",
|
|
118
|
+
"transfer.remotePathHint": "如 /tmp/ 或 /home/user/app.tar.gz",
|
|
119
|
+
"transfer.upload": "上传",
|
|
120
|
+
"transfer.uploading": "正在上传 {file}…",
|
|
121
|
+
"transfer.download": "下载",
|
|
122
|
+
"transfer.downloading": "正在下载…",
|
|
123
|
+
"transfer.done": "完成:{bytes} 字节",
|
|
124
|
+
"transfer.failed": "传输失败:{error}",
|
|
125
|
+
"transfer.browseRemote": "浏览远程目录",
|
|
126
|
+
"transfer.refresh": "刷新",
|
|
127
|
+
"transfer.name": "名称",
|
|
128
|
+
"transfer.upLevel": "上级目录",
|
|
129
|
+
"transfer.size": "大小",
|
|
130
|
+
"transfer.modified": "修改时间",
|
|
131
|
+
"transfer.bytes": "{value} B",
|
|
132
|
+
"transfer.kib": "{value} KB",
|
|
133
|
+
"transfer.mib": "{value} MB",
|
|
134
|
+
"transfer.gib": "{value} GB",
|
|
135
|
+
"transfer.percent": "{value}%",
|
|
136
|
+
"transfer.speed": "{value}/s",
|
|
137
|
+
"transfer.dir": "目录",
|
|
138
|
+
"transfer.file": "文件",
|
|
139
|
+
"tunnel.empty": "没有活跃的隧道。",
|
|
140
|
+
"tunnel.start": "新建隧道",
|
|
141
|
+
"tunnel.alias": "主机别名",
|
|
142
|
+
"tunnel.remotePort": "远程端口",
|
|
143
|
+
"tunnel.remoteHost": "远程主机",
|
|
144
|
+
"tunnel.remoteHostHint": "默认 127.0.0.1(服务器自身)",
|
|
145
|
+
"tunnel.localPort": "本地端口",
|
|
146
|
+
"tunnel.localPortHint": "留空自动分配",
|
|
147
|
+
"tunnel.stop": "停止",
|
|
148
|
+
"tunnel.stopAll": "全部停止",
|
|
149
|
+
"tunnel.stopAllConfirm": "确定停止全部隧道吗?",
|
|
150
|
+
"tunnel.row": "{alias}: 127.0.0.1:{localPort} -> {remoteHost}:{remotePort}",
|
|
151
|
+
"tunnel.started": "隧道已建立:127.0.0.1:{localPort}",
|
|
152
|
+
"tunnel.failed": "隧道启动失败:{error}",
|
|
153
|
+
"cluster.command": "命令",
|
|
154
|
+
"cluster.run": "执行",
|
|
155
|
+
"cluster.confirm": "确定在匹配的主机上执行该命令吗?该操作会真实运行在远程服务器上。",
|
|
156
|
+
"cluster.aliases": "主机别名(逗号分隔,留空为全部)",
|
|
157
|
+
"cluster.environment": "环境过滤",
|
|
158
|
+
"cluster.tags": "标签过滤(逗号分隔)",
|
|
159
|
+
"cluster.results": "执行结果",
|
|
160
|
+
"cluster.empty": "输入命令后点击「执行」。",
|
|
161
|
+
"cluster.noMatch": "没有匹配的主机。",
|
|
162
|
+
"cluster.ok": "成功",
|
|
163
|
+
"cluster.fail": "失败",
|
|
164
|
+
"cluster.timeout": "超时",
|
|
165
|
+
"cluster.col.alias": "主机",
|
|
166
|
+
"cluster.col.status": "状态",
|
|
167
|
+
"cluster.col.exit": "退出码",
|
|
168
|
+
"cluster.col.stdout": "标准输出",
|
|
169
|
+
"cluster.col.stderr": "标准错误",
|
|
170
|
+
"cluster.col.error": "错误",
|
|
171
|
+
"cluster.col.duration": "耗时",
|
|
172
|
+
"common.loading": "加载中…",
|
|
173
|
+
"common.error": "出错:{error}",
|
|
174
|
+
"common.refresh": "刷新",
|
|
175
|
+
"common.confirm": "确定",
|
|
176
|
+
"error.disabled": "SSH 插件在宿主端未启用。请前往「设置 → Web 插件 → SSH」打开 enabled 开关。"
|
|
38
177
|
};
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
178
|
+
const en = {
|
|
179
|
+
"entry.label": "SSH",
|
|
180
|
+
"entry.tooltip": "Remote SSH operations panel",
|
|
181
|
+
"panel.title": "Remote SSH",
|
|
182
|
+
"panel.backToConversation": "Back to chat",
|
|
183
|
+
"tab.hosts": "Hosts",
|
|
184
|
+
"tab.terminal": "Terminal",
|
|
185
|
+
"tab.transfer": "Transfer",
|
|
186
|
+
"tab.tunnels": "Tunnels",
|
|
187
|
+
"tab.cluster": "Cluster",
|
|
188
|
+
"hosts.empty": "No hosts configured. Click \"Add host\" or import from ~/.ssh/config.",
|
|
189
|
+
"hosts.add": "Add host",
|
|
190
|
+
"hosts.import": "Import ~/.ssh/config",
|
|
191
|
+
"hosts.search": "Search alias / description / tags…",
|
|
192
|
+
"hosts.groupBy.label": "Group by",
|
|
193
|
+
"hosts.groupBy.none": "No grouping",
|
|
194
|
+
"hosts.groupBy.environment": "Group by environment",
|
|
195
|
+
"hosts.groupBy.tags": "Group by tags",
|
|
196
|
+
"hosts.group.ungrouped": "No environment",
|
|
197
|
+
"hosts.group.noTags": "No tags",
|
|
198
|
+
"hosts.group.count": "{count} hosts",
|
|
199
|
+
"hosts.group.testAll": "Test all",
|
|
200
|
+
"hosts.col.alias": "Alias",
|
|
201
|
+
"hosts.col.host": "Host",
|
|
202
|
+
"hosts.col.user": "User",
|
|
203
|
+
"hosts.col.auth": "Auth",
|
|
204
|
+
"hosts.col.environment": "Environment",
|
|
205
|
+
"hosts.col.tags": "Tags",
|
|
206
|
+
"hosts.col.description": "Description",
|
|
207
|
+
"hosts.col.actions": "Actions",
|
|
208
|
+
"hosts.test": "Test",
|
|
209
|
+
"hosts.edit": "Edit",
|
|
210
|
+
"hosts.delete": "Delete",
|
|
211
|
+
"hosts.deleteConfirm": "Delete host \"{alias}\"? Its tunnels will be stopped.",
|
|
212
|
+
"hosts.imported": "Parsed {parsed} blocks, added {added} hosts, skipped {skipped}.",
|
|
213
|
+
"hosts.testing": "Testing connection…",
|
|
214
|
+
"hosts.testOk": "Connected ({latency} ms)",
|
|
215
|
+
"hosts.testFail": "Connection failed: {error}",
|
|
216
|
+
"hosts.connected": "Connect",
|
|
217
|
+
"hosts.notConnected": "Not connected",
|
|
218
|
+
"form.title.create": "Add host",
|
|
219
|
+
"form.title.edit": "Edit host {alias}",
|
|
220
|
+
"form.alias": "Alias",
|
|
221
|
+
"form.aliasHint": "Lowercase letters, digits and single hyphens; used in every operation",
|
|
222
|
+
"form.host": "Host",
|
|
223
|
+
"form.port": "Port",
|
|
224
|
+
"form.user": "User",
|
|
225
|
+
"form.auth": "Authentication",
|
|
226
|
+
"form.auth.key": "Key",
|
|
227
|
+
"form.auth.password": "Password",
|
|
228
|
+
"form.auth.agent": "SSH Agent",
|
|
229
|
+
"form.agentPath": "Agent path",
|
|
230
|
+
"form.agentPathHint": "Leave empty to auto-detect (SSH_AUTH_SOCK / Pageant on Windows), or set a socket path or \"pageant\"",
|
|
231
|
+
"form.authKeepHint": "While editing, empty secret fields keep the stored authentication and credentials; enter new values to switch methods.",
|
|
232
|
+
"form.keyPath": "Private key path",
|
|
233
|
+
"form.keyPathHint": "e.g. ~/.ssh/id_ed25519",
|
|
234
|
+
"form.passphrase": "Key passphrase",
|
|
235
|
+
"form.password": "Password",
|
|
236
|
+
"form.passwordHint": "Stored in plaintext in $DSH_HOME/dsh-ssh.json (private local file)",
|
|
237
|
+
"form.passwordRequired": "Password auth requires a password.",
|
|
238
|
+
"form.proxyJump": "Jump host aliases",
|
|
239
|
+
"form.proxyJumpHint": "Comma-separated local aliases, chained in order",
|
|
240
|
+
"form.description": "Description",
|
|
241
|
+
"form.environment": "Environment",
|
|
242
|
+
"form.environmentHint": "e.g. development / production",
|
|
243
|
+
"form.tags": "Tags",
|
|
244
|
+
"form.tagsHint": "Comma-separated",
|
|
245
|
+
"form.location": "Location",
|
|
246
|
+
"form.save": "Save",
|
|
247
|
+
"form.cancel": "Cancel",
|
|
248
|
+
"form.required": "Required fields missing: alias, host, user.",
|
|
249
|
+
"form.portInvalid": "Port must be an integer between 1 and 65535.",
|
|
250
|
+
"terminal.selectHost": "Select host",
|
|
251
|
+
"terminal.connect": "Connect",
|
|
252
|
+
"terminal.disconnect": "Disconnect",
|
|
253
|
+
"terminal.placeholder": "Select a host and click \"Connect\" to open the remote terminal.",
|
|
254
|
+
"terminal.connecting": "Connecting…",
|
|
255
|
+
"terminal.ready": "Terminal connected ({alias})",
|
|
256
|
+
"terminal.exited": "Terminal exited ({alias})",
|
|
257
|
+
"terminal.error": "Terminal error: {error}",
|
|
258
|
+
"terminal.auth.title": "Two-Factor Authentication (2FA)",
|
|
259
|
+
"terminal.auth.submit": "Submit Code",
|
|
260
|
+
"terminal.auth.cancel": "Cancel",
|
|
261
|
+
"terminal.auth.placeholder": "Enter code or passphrase",
|
|
262
|
+
"terminal.auth.hint": "The server requested interactive 2FA (session only, never saved):",
|
|
263
|
+
"transfer.selectHost": "Select host",
|
|
264
|
+
"transfer.remotePath": "Remote path",
|
|
265
|
+
"transfer.remotePathHint": "e.g. /tmp/ or /home/user/app.tar.gz",
|
|
266
|
+
"transfer.upload": "Upload",
|
|
267
|
+
"transfer.uploading": "Uploading {file}…",
|
|
268
|
+
"transfer.download": "Download",
|
|
269
|
+
"transfer.downloading": "Downloading…",
|
|
270
|
+
"transfer.done": "Done: {bytes} bytes",
|
|
271
|
+
"transfer.failed": "Transfer failed: {error}",
|
|
272
|
+
"transfer.browseRemote": "Browse remote",
|
|
273
|
+
"transfer.refresh": "Refresh",
|
|
274
|
+
"transfer.name": "Name",
|
|
275
|
+
"transfer.upLevel": "Parent directory",
|
|
276
|
+
"transfer.size": "Size",
|
|
277
|
+
"transfer.modified": "Modified",
|
|
278
|
+
"transfer.bytes": "{value} B",
|
|
279
|
+
"transfer.kib": "{value} KB",
|
|
280
|
+
"transfer.mib": "{value} MB",
|
|
281
|
+
"transfer.gib": "{value} GB",
|
|
282
|
+
"transfer.percent": "{value}%",
|
|
283
|
+
"transfer.speed": "{value}/s",
|
|
284
|
+
"transfer.dir": "Directory",
|
|
285
|
+
"transfer.file": "File",
|
|
286
|
+
"tunnel.empty": "No active tunnels.",
|
|
287
|
+
"tunnel.start": "New tunnel",
|
|
288
|
+
"tunnel.alias": "Host alias",
|
|
289
|
+
"tunnel.remotePort": "Remote port",
|
|
290
|
+
"tunnel.remoteHost": "Remote host",
|
|
291
|
+
"tunnel.remoteHostHint": "Default 127.0.0.1 (the server itself)",
|
|
292
|
+
"tunnel.localPort": "Local port",
|
|
293
|
+
"tunnel.localPortHint": "Leave empty for auto-assign",
|
|
294
|
+
"tunnel.stop": "Stop",
|
|
295
|
+
"tunnel.stopAll": "Stop all",
|
|
296
|
+
"tunnel.stopAllConfirm": "Stop all tunnels?",
|
|
297
|
+
"tunnel.row": "{alias}: 127.0.0.1:{localPort} -> {remoteHost}:{remotePort}",
|
|
298
|
+
"tunnel.started": "Tunnel up: 127.0.0.1:{localPort}",
|
|
299
|
+
"tunnel.failed": "Tunnel failed: {error}",
|
|
300
|
+
"cluster.command": "Command",
|
|
301
|
+
"cluster.run": "Run",
|
|
302
|
+
"cluster.confirm": "Run this command on the matching hosts? It executes for real on the remote servers.",
|
|
303
|
+
"cluster.aliases": "Host aliases (comma-separated; empty = all)",
|
|
304
|
+
"cluster.environment": "Environment filter",
|
|
305
|
+
"cluster.tags": "Tag filter (comma-separated)",
|
|
306
|
+
"cluster.results": "Results",
|
|
307
|
+
"cluster.empty": "Type a command and click \"Run\".",
|
|
308
|
+
"cluster.noMatch": "No hosts matched.",
|
|
309
|
+
"cluster.ok": "OK",
|
|
310
|
+
"cluster.fail": "Failed",
|
|
311
|
+
"cluster.timeout": "Timed out",
|
|
312
|
+
"cluster.col.alias": "Host",
|
|
313
|
+
"cluster.col.status": "Status",
|
|
314
|
+
"cluster.col.exit": "Exit",
|
|
315
|
+
"cluster.col.stdout": "Stdout",
|
|
316
|
+
"cluster.col.stderr": "Stderr",
|
|
317
|
+
"cluster.col.error": "Error",
|
|
318
|
+
"cluster.col.duration": "Duration",
|
|
319
|
+
"common.loading": "Loading…",
|
|
320
|
+
"common.error": "Error: {error}",
|
|
321
|
+
"common.refresh": "Refresh",
|
|
322
|
+
"common.confirm": "Confirm",
|
|
323
|
+
"error.disabled": "The SSH plugin is disabled on the host. Please enable it in Settings → Web Plugins → SSH."
|
|
324
|
+
};
|
|
325
|
+
/** Tiny interpolation: {name} -> value. */
|
|
326
|
+
function t(dictionary, key, values) {
|
|
327
|
+
let text = dictionary[key] ?? key;
|
|
328
|
+
if (values !== void 0) for (const [name, value] of Object.entries(values)) text = text.replaceAll(`{${name}}`, String(value));
|
|
329
|
+
return text;
|
|
330
|
+
}
|
|
331
|
+
//#endregion
|
|
332
|
+
//#region src/client/panel/helpers.ts
|
|
333
|
+
/**
|
|
334
|
+
* Shared panel helpers: the active-dictionary pick (document-language based,
|
|
335
|
+
* task-board precedent) bound to the dsh-ssh interpolator in locales.ts, plus
|
|
336
|
+
* a small error-message extractor. All copy stays in the locale dictionaries.
|
|
337
|
+
*/
|
|
338
|
+
/** Active dictionary, picked by the document language at call time. */
|
|
339
|
+
function dictionary() {
|
|
340
|
+
return (typeof document !== "undefined" ? document.documentElement.lang : "zh").toLowerCase().startsWith("en") ? { ...en } : { ...zh };
|
|
341
|
+
}
|
|
342
|
+
/** Translate a key with optional {name} template params (current language). */
|
|
343
|
+
function tt(key, values) {
|
|
344
|
+
return t(dictionary(), key, values);
|
|
345
|
+
}
|
|
346
|
+
/** Human-readable error text from an unknown thrown value. */
|
|
347
|
+
function errorMessage(error) {
|
|
348
|
+
if (error instanceof Error) return error.message;
|
|
349
|
+
return String(error);
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* The terminal font stack used before issue #577, kept as the last resort
|
|
353
|
+
* when neither the plugin setting nor a CSS custom property names a font.
|
|
354
|
+
*/
|
|
355
|
+
const TERMINAL_FONT_FALLBACK = "Menlo, Consolas, \"Liberation Mono\", monospace";
|
|
356
|
+
/**
|
|
357
|
+
* Resolve the xterm `fontFamily` (issue #577). xterm's DOM renderer takes
|
|
358
|
+
* the font only from constructor/options, so a plain stylesheet rule cannot
|
|
359
|
+
* retarget it — the value must be read back into the options. The chain,
|
|
360
|
+
* first non-empty value wins:
|
|
361
|
+
* 1. `override` — the `terminalFontFamily` plugin setting;
|
|
362
|
+
* 2. `--dsh-ssh-terminal-font` — dedicated hook for skins and user CSS
|
|
363
|
+
* (e.g. a Nerd Font for powerline glyphs);
|
|
364
|
+
* 3. `--ds-font-family-code` — the official code-font token several skins
|
|
365
|
+
* already remap;
|
|
366
|
+
* 4. {@link TERMINAL_FONT_FALLBACK}.
|
|
367
|
+
*/
|
|
368
|
+
function resolveTerminalFontFamily(override) {
|
|
369
|
+
const trimmed = override?.trim();
|
|
370
|
+
if (trimmed !== void 0 && trimmed !== "") return trimmed;
|
|
371
|
+
if (typeof getComputedStyle === "function" && typeof document !== "undefined") {
|
|
372
|
+
const target = document.body ?? document.documentElement;
|
|
373
|
+
const style = getComputedStyle(target);
|
|
374
|
+
for (const name of ["--dsh-ssh-terminal-font", "--ds-font-family-code"]) {
|
|
375
|
+
const value = style.getPropertyValue(name).trim();
|
|
376
|
+
if (value !== "") return value;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return TERMINAL_FONT_FALLBACK;
|
|
380
|
+
}
|
|
381
|
+
//#endregion
|
|
382
|
+
//#region src/client/api.ts
|
|
383
|
+
/**
|
|
384
|
+
* Browser-side API client for the /api/dsh-ssh route family. The only data
|
|
385
|
+
* access path the panel components use — plain fetch/WebSocket, same origin.
|
|
386
|
+
*/
|
|
387
|
+
/** Error carrying the route's JSON error message. */
|
|
388
|
+
var SshApiError = class extends Error {
|
|
389
|
+
constructor(message) {
|
|
390
|
+
super(message);
|
|
391
|
+
this.name = "SshApiError";
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
/** Extract the `.error` message from a JSON error body; undefined when absent. */
|
|
395
|
+
function extractError(text) {
|
|
396
|
+
if (text === "") return void 0;
|
|
397
|
+
try {
|
|
398
|
+
const parsed = JSON.parse(text);
|
|
399
|
+
return typeof parsed?.error === "string" ? parsed.error : void 0;
|
|
400
|
+
} catch {
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
/** Parse a JSON response or throw an SshApiError. */
|
|
405
|
+
async function readJson(response) {
|
|
406
|
+
let body;
|
|
407
|
+
try {
|
|
408
|
+
body = await response.json();
|
|
409
|
+
} catch {
|
|
410
|
+
if (response.status === 404) throw new SshApiError(tt("error.disabled"));
|
|
411
|
+
throw new SshApiError(`HTTP ${response.status}: invalid JSON response`);
|
|
412
|
+
}
|
|
413
|
+
if (!response.ok) throw new SshApiError(typeof body === "object" && body !== null && typeof body.error === "string" ? body.error : response.status === 404 ? tt("error.disabled") : `HTTP ${response.status}`);
|
|
414
|
+
return body;
|
|
415
|
+
}
|
|
416
|
+
/** Query-string helper. */
|
|
417
|
+
function query(params) {
|
|
418
|
+
const search = new URLSearchParams();
|
|
419
|
+
for (const [key, value] of Object.entries(params)) if (value !== void 0 && value !== "") search.set(key, String(value));
|
|
420
|
+
const text = search.toString();
|
|
421
|
+
return text === "" ? "" : "?" + text;
|
|
422
|
+
}
|
|
423
|
+
/** The browser half's only data entry point. */
|
|
424
|
+
var SshApi = class {
|
|
425
|
+
async listHosts(queryText) {
|
|
426
|
+
return (await readJson(await fetch(SSH_API.hosts + query({ query: queryText })))).hosts;
|
|
427
|
+
}
|
|
428
|
+
async createHost(payload) {
|
|
429
|
+
return (await readJson(await fetch(SSH_API.hosts, {
|
|
142
430
|
method: "POST",
|
|
143
431
|
headers: { "content-type": "application/json" },
|
|
144
|
-
body: JSON.stringify(
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
})
|
|
432
|
+
body: JSON.stringify(payload)
|
|
433
|
+
}))).host;
|
|
434
|
+
}
|
|
435
|
+
async updateHost(alias, patch) {
|
|
436
|
+
return (await readJson(await fetch(SSH_API.hosts + query({ alias }), {
|
|
437
|
+
method: "PATCH",
|
|
438
|
+
headers: { "content-type": "application/json" },
|
|
439
|
+
body: JSON.stringify(patch)
|
|
440
|
+
}))).host;
|
|
441
|
+
}
|
|
442
|
+
async deleteHost(alias) {
|
|
443
|
+
await readJson(await fetch(SSH_API.hosts + query({ alias }), { method: "DELETE" }));
|
|
444
|
+
}
|
|
445
|
+
async importSshConfig() {
|
|
446
|
+
return (await readJson(await fetch(SSH_API.importSshConfig, { method: "POST" }))).result;
|
|
447
|
+
}
|
|
448
|
+
async testHost(alias) {
|
|
449
|
+
return (await readJson(await fetch(SSH_API.test, {
|
|
450
|
+
method: "POST",
|
|
451
|
+
headers: { "content-type": "application/json" },
|
|
452
|
+
body: JSON.stringify({ alias })
|
|
453
|
+
}))).result;
|
|
454
|
+
}
|
|
455
|
+
async exec(alias, command, timeoutMs) {
|
|
456
|
+
return (await readJson(await fetch(SSH_API.exec, {
|
|
457
|
+
method: "POST",
|
|
458
|
+
headers: { "content-type": "application/json" },
|
|
459
|
+
body: JSON.stringify({
|
|
460
|
+
alias,
|
|
461
|
+
command,
|
|
462
|
+
timeoutMs
|
|
463
|
+
})
|
|
464
|
+
}))).result;
|
|
465
|
+
}
|
|
466
|
+
async cluster(options) {
|
|
467
|
+
return (await readJson(await fetch(SSH_API.cluster, {
|
|
468
|
+
method: "POST",
|
|
469
|
+
headers: { "content-type": "application/json" },
|
|
470
|
+
body: JSON.stringify(options)
|
|
471
|
+
}))).results;
|
|
472
|
+
}
|
|
473
|
+
async ls(alias, path) {
|
|
474
|
+
return (await readJson(await fetch(SSH_API.ls + query({
|
|
475
|
+
alias,
|
|
476
|
+
path
|
|
477
|
+
})))).entries;
|
|
478
|
+
}
|
|
479
|
+
async listTunnels() {
|
|
480
|
+
return (await readJson(await fetch(SSH_API.tunnel, {
|
|
481
|
+
method: "POST",
|
|
482
|
+
headers: { "content-type": "application/json" },
|
|
483
|
+
body: JSON.stringify({ action: "list" })
|
|
484
|
+
}))).tunnels;
|
|
485
|
+
}
|
|
486
|
+
async startTunnel(options) {
|
|
487
|
+
return (await readJson(await fetch(SSH_API.tunnel, {
|
|
488
|
+
method: "POST",
|
|
489
|
+
headers: { "content-type": "application/json" },
|
|
490
|
+
body: JSON.stringify({
|
|
491
|
+
action: "start",
|
|
492
|
+
...options
|
|
493
|
+
})
|
|
494
|
+
}))).tunnel;
|
|
495
|
+
}
|
|
496
|
+
async stopTunnel(tunnelId) {
|
|
497
|
+
return (await readJson(await fetch(SSH_API.tunnel, {
|
|
498
|
+
method: "POST",
|
|
499
|
+
headers: { "content-type": "application/json" },
|
|
500
|
+
body: JSON.stringify({
|
|
501
|
+
action: "stop",
|
|
502
|
+
tunnelId
|
|
503
|
+
})
|
|
504
|
+
}))).ok;
|
|
149
505
|
}
|
|
150
506
|
async stopAllTunnels(alias) {
|
|
151
507
|
return (await readJson(await fetch(SSH_API.tunnel, {
|
|
@@ -320,359 +676,6 @@ window.__ModuleLoader__.load({
|
|
|
320
676
|
}
|
|
321
677
|
};
|
|
322
678
|
//#endregion
|
|
323
|
-
//#region src/client/locales.ts
|
|
324
|
-
/**
|
|
325
|
-
* dsh-ssh surface copy: zh is the key source, en mirrors every key.
|
|
326
|
-
*/
|
|
327
|
-
const zh = {
|
|
328
|
-
"entry.label": "SSH",
|
|
329
|
-
"entry.tooltip": "远程 SSH 运维面板",
|
|
330
|
-
"panel.title": "远程 SSH",
|
|
331
|
-
"panel.backToConversation": "返回会话",
|
|
332
|
-
"tab.hosts": "主机",
|
|
333
|
-
"tab.terminal": "终端",
|
|
334
|
-
"tab.transfer": "传输",
|
|
335
|
-
"tab.tunnels": "隧道",
|
|
336
|
-
"tab.cluster": "集群",
|
|
337
|
-
"hosts.empty": "尚未配置主机。点击「新增主机」添加,或从 ~/.ssh/config 导入。",
|
|
338
|
-
"hosts.add": "新增主机",
|
|
339
|
-
"hosts.import": "导入 ~/.ssh/config",
|
|
340
|
-
"hosts.search": "搜索别名 / 描述 / 标签…",
|
|
341
|
-
"hosts.groupBy.label": "分组方式",
|
|
342
|
-
"hosts.groupBy.none": "不分组",
|
|
343
|
-
"hosts.groupBy.environment": "按环境分组",
|
|
344
|
-
"hosts.groupBy.tags": "按标签分组",
|
|
345
|
-
"hosts.group.ungrouped": "未设置环境",
|
|
346
|
-
"hosts.group.noTags": "无标签",
|
|
347
|
-
"hosts.group.count": "{count} 台",
|
|
348
|
-
"hosts.group.testAll": "测试全部",
|
|
349
|
-
"hosts.col.alias": "别名",
|
|
350
|
-
"hosts.col.host": "主机",
|
|
351
|
-
"hosts.col.user": "用户",
|
|
352
|
-
"hosts.col.auth": "认证",
|
|
353
|
-
"hosts.col.environment": "环境",
|
|
354
|
-
"hosts.col.tags": "标签",
|
|
355
|
-
"hosts.col.description": "备注",
|
|
356
|
-
"hosts.col.actions": "操作",
|
|
357
|
-
"hosts.test": "测试",
|
|
358
|
-
"hosts.edit": "编辑",
|
|
359
|
-
"hosts.delete": "删除",
|
|
360
|
-
"hosts.deleteConfirm": "确定删除主机「{alias}」?将同时停止其隧道。",
|
|
361
|
-
"hosts.imported": "解析 {parsed} 个配置块,新增 {added} 台主机,跳过 {skipped} 个。",
|
|
362
|
-
"hosts.testing": "连接测试中…",
|
|
363
|
-
"hosts.testOk": "连接成功({latency} ms)",
|
|
364
|
-
"hosts.testFail": "连接失败:{error}",
|
|
365
|
-
"hosts.connected": "连接",
|
|
366
|
-
"hosts.notConnected": "未连接",
|
|
367
|
-
"form.title.create": "新增主机",
|
|
368
|
-
"form.title.edit": "编辑主机 {alias}",
|
|
369
|
-
"form.alias": "别名",
|
|
370
|
-
"form.aliasHint": "小写字母、数字与单连字符,操作时使用",
|
|
371
|
-
"form.host": "主机地址",
|
|
372
|
-
"form.port": "端口",
|
|
373
|
-
"form.user": "用户名",
|
|
374
|
-
"form.auth": "认证方式",
|
|
375
|
-
"form.auth.key": "密钥",
|
|
376
|
-
"form.auth.password": "密码",
|
|
377
|
-
"form.auth.agent": "SSH Agent",
|
|
378
|
-
"form.agentPath": "Agent 路径",
|
|
379
|
-
"form.agentPathHint": "留空自动检测(SSH_AUTH_SOCK / Windows Pageant);也可填 socket 路径或 pageant",
|
|
380
|
-
"form.authKeepHint": "编辑时密钥/密码/Agent 留空将保留原认证方式与已存凭据;如需更换认证方式请填写新凭据。",
|
|
381
|
-
"form.keyPath": "私钥路径",
|
|
382
|
-
"form.keyPathHint": "如 ~/.ssh/id_ed25519",
|
|
383
|
-
"form.passphrase": "密钥口令",
|
|
384
|
-
"form.password": "密码",
|
|
385
|
-
"form.passwordHint": "明文保存在 $DSH_HOME/dsh-ssh.json(本机私有文件)",
|
|
386
|
-
"form.passwordRequired": "密码认证需要填写密码。",
|
|
387
|
-
"form.proxyJump": "跳板机别名",
|
|
388
|
-
"form.proxyJumpHint": "逗号分隔的本地主机别名,按顺序连接",
|
|
389
|
-
"form.description": "备注",
|
|
390
|
-
"form.environment": "环境",
|
|
391
|
-
"form.environmentHint": "如 development / production",
|
|
392
|
-
"form.tags": "标签",
|
|
393
|
-
"form.tagsHint": "逗号分隔",
|
|
394
|
-
"form.location": "位置",
|
|
395
|
-
"form.save": "保存",
|
|
396
|
-
"form.cancel": "取消",
|
|
397
|
-
"form.required": "请填写必填字段:别名、主机地址、用户名。",
|
|
398
|
-
"form.portInvalid": "端口必须是 1-65535 之间的整数。",
|
|
399
|
-
"terminal.selectHost": "选择主机",
|
|
400
|
-
"terminal.connect": "连接",
|
|
401
|
-
"terminal.disconnect": "断开",
|
|
402
|
-
"terminal.placeholder": "选择主机后点击「连接」打开远程终端。",
|
|
403
|
-
"terminal.connecting": "正在连接…",
|
|
404
|
-
"terminal.ready": "终端已连接({alias})",
|
|
405
|
-
"terminal.exited": "终端已退出({alias})",
|
|
406
|
-
"terminal.error": "终端错误:{error}",
|
|
407
|
-
"terminal.auth.title": "二次身份验证 (2FA / 交互式认证)",
|
|
408
|
-
"terminal.auth.submit": "提交验证码",
|
|
409
|
-
"terminal.auth.cancel": "取消",
|
|
410
|
-
"terminal.auth.placeholder": "请输入验证码或口令",
|
|
411
|
-
"terminal.auth.hint": "服务器请求进行交互式二次验证(输入仅本次生效,不落盘):",
|
|
412
|
-
"transfer.selectHost": "选择主机",
|
|
413
|
-
"transfer.remotePath": "远程路径",
|
|
414
|
-
"transfer.remotePathHint": "如 /tmp/ 或 /home/user/app.tar.gz",
|
|
415
|
-
"transfer.upload": "上传",
|
|
416
|
-
"transfer.uploading": "正在上传 {file}…",
|
|
417
|
-
"transfer.download": "下载",
|
|
418
|
-
"transfer.downloading": "正在下载…",
|
|
419
|
-
"transfer.done": "完成:{bytes} 字节",
|
|
420
|
-
"transfer.failed": "传输失败:{error}",
|
|
421
|
-
"transfer.browseRemote": "浏览远程目录",
|
|
422
|
-
"transfer.refresh": "刷新",
|
|
423
|
-
"transfer.name": "名称",
|
|
424
|
-
"transfer.upLevel": "上级目录",
|
|
425
|
-
"transfer.size": "大小",
|
|
426
|
-
"transfer.modified": "修改时间",
|
|
427
|
-
"transfer.bytes": "{value} B",
|
|
428
|
-
"transfer.kib": "{value} KB",
|
|
429
|
-
"transfer.mib": "{value} MB",
|
|
430
|
-
"transfer.gib": "{value} GB",
|
|
431
|
-
"transfer.percent": "{value}%",
|
|
432
|
-
"transfer.speed": "{value}/s",
|
|
433
|
-
"transfer.dir": "目录",
|
|
434
|
-
"transfer.file": "文件",
|
|
435
|
-
"tunnel.empty": "没有活跃的隧道。",
|
|
436
|
-
"tunnel.start": "新建隧道",
|
|
437
|
-
"tunnel.alias": "主机别名",
|
|
438
|
-
"tunnel.remotePort": "远程端口",
|
|
439
|
-
"tunnel.remoteHost": "远程主机",
|
|
440
|
-
"tunnel.remoteHostHint": "默认 127.0.0.1(服务器自身)",
|
|
441
|
-
"tunnel.localPort": "本地端口",
|
|
442
|
-
"tunnel.localPortHint": "留空自动分配",
|
|
443
|
-
"tunnel.stop": "停止",
|
|
444
|
-
"tunnel.stopAll": "全部停止",
|
|
445
|
-
"tunnel.stopAllConfirm": "确定停止全部隧道吗?",
|
|
446
|
-
"tunnel.row": "{alias}: 127.0.0.1:{localPort} -> {remoteHost}:{remotePort}",
|
|
447
|
-
"tunnel.started": "隧道已建立:127.0.0.1:{localPort}",
|
|
448
|
-
"tunnel.failed": "隧道启动失败:{error}",
|
|
449
|
-
"cluster.command": "命令",
|
|
450
|
-
"cluster.run": "执行",
|
|
451
|
-
"cluster.confirm": "确定在匹配的主机上执行该命令吗?该操作会真实运行在远程服务器上。",
|
|
452
|
-
"cluster.aliases": "主机别名(逗号分隔,留空为全部)",
|
|
453
|
-
"cluster.environment": "环境过滤",
|
|
454
|
-
"cluster.tags": "标签过滤(逗号分隔)",
|
|
455
|
-
"cluster.results": "执行结果",
|
|
456
|
-
"cluster.empty": "输入命令后点击「执行」。",
|
|
457
|
-
"cluster.noMatch": "没有匹配的主机。",
|
|
458
|
-
"cluster.ok": "成功",
|
|
459
|
-
"cluster.fail": "失败",
|
|
460
|
-
"cluster.timeout": "超时",
|
|
461
|
-
"cluster.col.alias": "主机",
|
|
462
|
-
"cluster.col.status": "状态",
|
|
463
|
-
"cluster.col.exit": "退出码",
|
|
464
|
-
"cluster.col.stdout": "标准输出",
|
|
465
|
-
"cluster.col.stderr": "标准错误",
|
|
466
|
-
"cluster.col.error": "错误",
|
|
467
|
-
"cluster.col.duration": "耗时",
|
|
468
|
-
"common.loading": "加载中…",
|
|
469
|
-
"common.error": "出错:{error}",
|
|
470
|
-
"common.refresh": "刷新",
|
|
471
|
-
"common.confirm": "确定"
|
|
472
|
-
};
|
|
473
|
-
const en = {
|
|
474
|
-
"entry.label": "SSH",
|
|
475
|
-
"entry.tooltip": "Remote SSH operations panel",
|
|
476
|
-
"panel.title": "Remote SSH",
|
|
477
|
-
"panel.backToConversation": "Back to chat",
|
|
478
|
-
"tab.hosts": "Hosts",
|
|
479
|
-
"tab.terminal": "Terminal",
|
|
480
|
-
"tab.transfer": "Transfer",
|
|
481
|
-
"tab.tunnels": "Tunnels",
|
|
482
|
-
"tab.cluster": "Cluster",
|
|
483
|
-
"hosts.empty": "No hosts configured. Click \"Add host\" or import from ~/.ssh/config.",
|
|
484
|
-
"hosts.add": "Add host",
|
|
485
|
-
"hosts.import": "Import ~/.ssh/config",
|
|
486
|
-
"hosts.search": "Search alias / description / tags…",
|
|
487
|
-
"hosts.groupBy.label": "Group by",
|
|
488
|
-
"hosts.groupBy.none": "No grouping",
|
|
489
|
-
"hosts.groupBy.environment": "Group by environment",
|
|
490
|
-
"hosts.groupBy.tags": "Group by tags",
|
|
491
|
-
"hosts.group.ungrouped": "No environment",
|
|
492
|
-
"hosts.group.noTags": "No tags",
|
|
493
|
-
"hosts.group.count": "{count} hosts",
|
|
494
|
-
"hosts.group.testAll": "Test all",
|
|
495
|
-
"hosts.col.alias": "Alias",
|
|
496
|
-
"hosts.col.host": "Host",
|
|
497
|
-
"hosts.col.user": "User",
|
|
498
|
-
"hosts.col.auth": "Auth",
|
|
499
|
-
"hosts.col.environment": "Environment",
|
|
500
|
-
"hosts.col.tags": "Tags",
|
|
501
|
-
"hosts.col.description": "Description",
|
|
502
|
-
"hosts.col.actions": "Actions",
|
|
503
|
-
"hosts.test": "Test",
|
|
504
|
-
"hosts.edit": "Edit",
|
|
505
|
-
"hosts.delete": "Delete",
|
|
506
|
-
"hosts.deleteConfirm": "Delete host \"{alias}\"? Its tunnels will be stopped.",
|
|
507
|
-
"hosts.imported": "Parsed {parsed} blocks, added {added} hosts, skipped {skipped}.",
|
|
508
|
-
"hosts.testing": "Testing connection…",
|
|
509
|
-
"hosts.testOk": "Connected ({latency} ms)",
|
|
510
|
-
"hosts.testFail": "Connection failed: {error}",
|
|
511
|
-
"hosts.connected": "Connect",
|
|
512
|
-
"hosts.notConnected": "Not connected",
|
|
513
|
-
"form.title.create": "Add host",
|
|
514
|
-
"form.title.edit": "Edit host {alias}",
|
|
515
|
-
"form.alias": "Alias",
|
|
516
|
-
"form.aliasHint": "Lowercase letters, digits and single hyphens; used in every operation",
|
|
517
|
-
"form.host": "Host",
|
|
518
|
-
"form.port": "Port",
|
|
519
|
-
"form.user": "User",
|
|
520
|
-
"form.auth": "Authentication",
|
|
521
|
-
"form.auth.key": "Key",
|
|
522
|
-
"form.auth.password": "Password",
|
|
523
|
-
"form.auth.agent": "SSH Agent",
|
|
524
|
-
"form.agentPath": "Agent path",
|
|
525
|
-
"form.agentPathHint": "Leave empty to auto-detect (SSH_AUTH_SOCK / Pageant on Windows), or set a socket path or \"pageant\"",
|
|
526
|
-
"form.authKeepHint": "While editing, empty secret fields keep the stored authentication and credentials; enter new values to switch methods.",
|
|
527
|
-
"form.keyPath": "Private key path",
|
|
528
|
-
"form.keyPathHint": "e.g. ~/.ssh/id_ed25519",
|
|
529
|
-
"form.passphrase": "Key passphrase",
|
|
530
|
-
"form.password": "Password",
|
|
531
|
-
"form.passwordHint": "Stored in plaintext in $DSH_HOME/dsh-ssh.json (private local file)",
|
|
532
|
-
"form.passwordRequired": "Password auth requires a password.",
|
|
533
|
-
"form.proxyJump": "Jump host aliases",
|
|
534
|
-
"form.proxyJumpHint": "Comma-separated local aliases, chained in order",
|
|
535
|
-
"form.description": "Description",
|
|
536
|
-
"form.environment": "Environment",
|
|
537
|
-
"form.environmentHint": "e.g. development / production",
|
|
538
|
-
"form.tags": "Tags",
|
|
539
|
-
"form.tagsHint": "Comma-separated",
|
|
540
|
-
"form.location": "Location",
|
|
541
|
-
"form.save": "Save",
|
|
542
|
-
"form.cancel": "Cancel",
|
|
543
|
-
"form.required": "Required fields missing: alias, host, user.",
|
|
544
|
-
"form.portInvalid": "Port must be an integer between 1 and 65535.",
|
|
545
|
-
"terminal.selectHost": "Select host",
|
|
546
|
-
"terminal.connect": "Connect",
|
|
547
|
-
"terminal.disconnect": "Disconnect",
|
|
548
|
-
"terminal.placeholder": "Select a host and click \"Connect\" to open the remote terminal.",
|
|
549
|
-
"terminal.connecting": "Connecting…",
|
|
550
|
-
"terminal.ready": "Terminal connected ({alias})",
|
|
551
|
-
"terminal.exited": "Terminal exited ({alias})",
|
|
552
|
-
"terminal.error": "Terminal error: {error}",
|
|
553
|
-
"terminal.auth.title": "Two-Factor Authentication (2FA)",
|
|
554
|
-
"terminal.auth.submit": "Submit Code",
|
|
555
|
-
"terminal.auth.cancel": "Cancel",
|
|
556
|
-
"terminal.auth.placeholder": "Enter code or passphrase",
|
|
557
|
-
"terminal.auth.hint": "The server requested interactive 2FA (session only, never saved):",
|
|
558
|
-
"transfer.selectHost": "Select host",
|
|
559
|
-
"transfer.remotePath": "Remote path",
|
|
560
|
-
"transfer.remotePathHint": "e.g. /tmp/ or /home/user/app.tar.gz",
|
|
561
|
-
"transfer.upload": "Upload",
|
|
562
|
-
"transfer.uploading": "Uploading {file}…",
|
|
563
|
-
"transfer.download": "Download",
|
|
564
|
-
"transfer.downloading": "Downloading…",
|
|
565
|
-
"transfer.done": "Done: {bytes} bytes",
|
|
566
|
-
"transfer.failed": "Transfer failed: {error}",
|
|
567
|
-
"transfer.browseRemote": "Browse remote",
|
|
568
|
-
"transfer.refresh": "Refresh",
|
|
569
|
-
"transfer.name": "Name",
|
|
570
|
-
"transfer.upLevel": "Parent directory",
|
|
571
|
-
"transfer.size": "Size",
|
|
572
|
-
"transfer.modified": "Modified",
|
|
573
|
-
"transfer.bytes": "{value} B",
|
|
574
|
-
"transfer.kib": "{value} KB",
|
|
575
|
-
"transfer.mib": "{value} MB",
|
|
576
|
-
"transfer.gib": "{value} GB",
|
|
577
|
-
"transfer.percent": "{value}%",
|
|
578
|
-
"transfer.speed": "{value}/s",
|
|
579
|
-
"transfer.dir": "Directory",
|
|
580
|
-
"transfer.file": "File",
|
|
581
|
-
"tunnel.empty": "No active tunnels.",
|
|
582
|
-
"tunnel.start": "New tunnel",
|
|
583
|
-
"tunnel.alias": "Host alias",
|
|
584
|
-
"tunnel.remotePort": "Remote port",
|
|
585
|
-
"tunnel.remoteHost": "Remote host",
|
|
586
|
-
"tunnel.remoteHostHint": "Default 127.0.0.1 (the server itself)",
|
|
587
|
-
"tunnel.localPort": "Local port",
|
|
588
|
-
"tunnel.localPortHint": "Leave empty for auto-assign",
|
|
589
|
-
"tunnel.stop": "Stop",
|
|
590
|
-
"tunnel.stopAll": "Stop all",
|
|
591
|
-
"tunnel.stopAllConfirm": "Stop all tunnels?",
|
|
592
|
-
"tunnel.row": "{alias}: 127.0.0.1:{localPort} -> {remoteHost}:{remotePort}",
|
|
593
|
-
"tunnel.started": "Tunnel up: 127.0.0.1:{localPort}",
|
|
594
|
-
"tunnel.failed": "Tunnel failed: {error}",
|
|
595
|
-
"cluster.command": "Command",
|
|
596
|
-
"cluster.run": "Run",
|
|
597
|
-
"cluster.confirm": "Run this command on the matching hosts? It executes for real on the remote servers.",
|
|
598
|
-
"cluster.aliases": "Host aliases (comma-separated; empty = all)",
|
|
599
|
-
"cluster.environment": "Environment filter",
|
|
600
|
-
"cluster.tags": "Tag filter (comma-separated)",
|
|
601
|
-
"cluster.results": "Results",
|
|
602
|
-
"cluster.empty": "Type a command and click \"Run\".",
|
|
603
|
-
"cluster.noMatch": "No hosts matched.",
|
|
604
|
-
"cluster.ok": "OK",
|
|
605
|
-
"cluster.fail": "Failed",
|
|
606
|
-
"cluster.timeout": "Timed out",
|
|
607
|
-
"cluster.col.alias": "Host",
|
|
608
|
-
"cluster.col.status": "Status",
|
|
609
|
-
"cluster.col.exit": "Exit",
|
|
610
|
-
"cluster.col.stdout": "Stdout",
|
|
611
|
-
"cluster.col.stderr": "Stderr",
|
|
612
|
-
"cluster.col.error": "Error",
|
|
613
|
-
"cluster.col.duration": "Duration",
|
|
614
|
-
"common.loading": "Loading…",
|
|
615
|
-
"common.error": "Error: {error}",
|
|
616
|
-
"common.refresh": "Refresh",
|
|
617
|
-
"common.confirm": "Confirm"
|
|
618
|
-
};
|
|
619
|
-
/** Tiny interpolation: {name} -> value. */
|
|
620
|
-
function t(dictionary, key, values) {
|
|
621
|
-
let text = dictionary[key] ?? key;
|
|
622
|
-
if (values !== void 0) for (const [name, value] of Object.entries(values)) text = text.replaceAll(`{${name}}`, String(value));
|
|
623
|
-
return text;
|
|
624
|
-
}
|
|
625
|
-
//#endregion
|
|
626
|
-
//#region src/client/panel/helpers.ts
|
|
627
|
-
/**
|
|
628
|
-
* Shared panel helpers: the active-dictionary pick (document-language based,
|
|
629
|
-
* task-board precedent) bound to the dsh-ssh interpolator in locales.ts, plus
|
|
630
|
-
* a small error-message extractor. All copy stays in the locale dictionaries.
|
|
631
|
-
*/
|
|
632
|
-
/** Active dictionary, picked by the document language at call time. */
|
|
633
|
-
function dictionary() {
|
|
634
|
-
return (typeof document !== "undefined" ? document.documentElement.lang : "zh").toLowerCase().startsWith("en") ? { ...en } : { ...zh };
|
|
635
|
-
}
|
|
636
|
-
/** Translate a key with optional {name} template params (current language). */
|
|
637
|
-
function tt(key, values) {
|
|
638
|
-
return t(dictionary(), key, values);
|
|
639
|
-
}
|
|
640
|
-
/** Human-readable error text from an unknown thrown value. */
|
|
641
|
-
function errorMessage(error) {
|
|
642
|
-
if (error instanceof Error) return error.message;
|
|
643
|
-
return String(error);
|
|
644
|
-
}
|
|
645
|
-
/**
|
|
646
|
-
* The terminal font stack used before issue #577, kept as the last resort
|
|
647
|
-
* when neither the plugin setting nor a CSS custom property names a font.
|
|
648
|
-
*/
|
|
649
|
-
const TERMINAL_FONT_FALLBACK = "Menlo, Consolas, \"Liberation Mono\", monospace";
|
|
650
|
-
/**
|
|
651
|
-
* Resolve the xterm `fontFamily` (issue #577). xterm's DOM renderer takes
|
|
652
|
-
* the font only from constructor/options, so a plain stylesheet rule cannot
|
|
653
|
-
* retarget it — the value must be read back into the options. The chain,
|
|
654
|
-
* first non-empty value wins:
|
|
655
|
-
* 1. `override` — the `terminalFontFamily` plugin setting;
|
|
656
|
-
* 2. `--dsh-ssh-terminal-font` — dedicated hook for skins and user CSS
|
|
657
|
-
* (e.g. a Nerd Font for powerline glyphs);
|
|
658
|
-
* 3. `--ds-font-family-code` — the official code-font token several skins
|
|
659
|
-
* already remap;
|
|
660
|
-
* 4. {@link TERMINAL_FONT_FALLBACK}.
|
|
661
|
-
*/
|
|
662
|
-
function resolveTerminalFontFamily(override) {
|
|
663
|
-
const trimmed = override?.trim();
|
|
664
|
-
if (trimmed !== void 0 && trimmed !== "") return trimmed;
|
|
665
|
-
if (typeof getComputedStyle === "function" && typeof document !== "undefined") {
|
|
666
|
-
const target = document.body ?? document.documentElement;
|
|
667
|
-
const style = getComputedStyle(target);
|
|
668
|
-
for (const name of ["--dsh-ssh-terminal-font", "--ds-font-family-code"]) {
|
|
669
|
-
const value = style.getPropertyValue(name).trim();
|
|
670
|
-
if (value !== "") return value;
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
return TERMINAL_FONT_FALLBACK;
|
|
674
|
-
}
|
|
675
|
-
//#endregion
|
|
676
679
|
//#region \0dsh-css:packages/dsh-ssh/src/client/panel/panel.module.css.mjs
|
|
677
680
|
const css = "[data-pane=conversation],[class*=centerCol]{position:relative}[data-dsh-ssh-view]{z-index:60;background:var(--dsw-alias-bg-base);display:none;position:absolute;inset:0}html[data-dsh-ssh-active]:not([data-dsh-taskboard-active]) [data-dsh-ssh-view]{display:block}html[data-dsh-ssh-active]:not([data-dsh-taskboard-active]) [data-pane=conversation]>:not([data-dsh-ssh-view]),html[data-dsh-ssh-active]:not([data-dsh-taskboard-active]) [class*=centerCol]>:not([data-dsh-ssh-view]){display:none!important}.mL8Uca_entry{box-sizing:border-box;width:100%;min-height:36px;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-radius:8px;align-items:center;gap:10px;padding:0 10px;font-size:13px;display:flex}.mL8Uca_entry:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.mL8Uca_entry[data-active]{background:var(--dsw-alias-interactive-bg-active);color:var(--dsw-alias-label-primary);font-weight:600}.mL8Uca_entryIcon{flex:none;justify-content:center;align-items:center;width:24px;height:24px;display:inline-flex}.mL8Uca_entryIcon svg{width:18px;height:18px;display:block}.mL8Uca_entryLabel{text-overflow:ellipsis;overflow:hidden}[data-dsh-frame][data-sidebar-collapsed] .mL8Uca_entry{border-radius:50%;justify-content:center;width:36px;min-height:36px;margin:0 auto 12px;padding:0}[data-dsh-frame][data-sidebar-collapsed] .mL8Uca_entryLabel{display:none}.mL8Uca_view{overflow:hidden}.mL8Uca_panel{background:var(--dsw-alias-bg-base);min-width:0;height:100%;min-height:0;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);flex-direction:column;gap:10px;padding:14px 16px 16px;display:flex}.mL8Uca_panelHeader{flex:none;align-items:center;gap:10px;display:flex}.mL8Uca_panelTitle{color:var(--dsw-alias-label-primary);white-space:nowrap;flex:1;margin:0;font-size:16px;font-weight:700}.mL8Uca_backButton{align-items:center;gap:4px;display:inline-flex}.mL8Uca_tabBar{border-bottom:1px solid var(--dsw-alias-border-l1);flex:none;gap:2px;display:flex}.mL8Uca_tab{color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-bottom:2px solid #0000;border-radius:6px 6px 0 0;padding:7px 14px;font-size:13px}.mL8Uca_tab:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}.mL8Uca_tab[data-active]{color:var(--dsw-alias-label-primary);border-bottom-color:var(--dsw-alias-state-business-primary);font-weight:600}.mL8Uca_panelContent{flex-direction:column;flex:1;min-height:0;display:flex;overflow:hidden}.mL8Uca_tabBody{flex-direction:column;flex:1;gap:10px;min-height:0;display:flex;overflow-y:auto}.mL8Uca_fillBody{flex-direction:column;flex:1;gap:10px;min-height:0;display:flex;overflow:hidden}.mL8Uca_toolbar,.mL8Uca_controls{flex-wrap:wrap;flex:none;align-items:center;gap:8px;display:flex}.mL8Uca_controls .mL8Uca_input{flex:0 260px;min-width:150px}.mL8Uca_toolbarSpacer{flex:1}.mL8Uca_search{min-width:120px;color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;flex:0 260px;padding:6px 10px;font-size:13px}.mL8Uca_search::placeholder{color:var(--dsw-alias-label-tertiary)}.mL8Uca_groupBySelect{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;padding:6px 8px;font-size:13px}.mL8Uca_groupSection+.mL8Uca_groupSection{border-top:1px solid var(--dsw-alias-border-l1)}.mL8Uca_groupHeader{background:var(--dsw-alias-bg-base,var(--dsw-specific-input-major));align-items:center;gap:8px;padding:4px 8px;display:flex;position:sticky;top:0}.mL8Uca_groupToggle{min-width:0;color:var(--dsw-alias-label-primary);cursor:pointer;text-align:left;background:0 0;border:none;border-radius:6px;flex:1;align-items:center;gap:6px;padding:4px 6px;font-size:12.5px;font-weight:500;display:flex}.mL8Uca_groupToggle:hover{background:var(--dsw-alias-interactive-bg-hover)}.mL8Uca_groupChevron{border-right:1.5px solid var(--dsw-alias-label-tertiary);border-bottom:1.5px solid var(--dsw-alias-label-tertiary);flex:none;width:7px;height:7px;margin-bottom:2px;transition:transform .12s;transform:rotate(45deg)}.mL8Uca_groupChevron[data-collapsed]{margin-bottom:0;transform:rotate(-45deg)}.mL8Uca_groupName{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.mL8Uca_groupCount{color:var(--dsw-alias-label-tertiary);flex:none;font-weight:400}.mL8Uca_tableWrap{border:1px solid var(--dsw-alias-border-l1);border-radius:10px;flex:1;min-height:0;overflow:auto}.mL8Uca_table{border-collapse:collapse;width:100%;font-size:12.5px}.mL8Uca_table th{z-index:1;text-align:left;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);border-bottom:1px solid var(--dsw-alias-border-l1);white-space:nowrap;padding:8px 10px;font-weight:600;position:sticky;top:0}.mL8Uca_table td{border-bottom:1px solid var(--dsw-alias-separator-primary);vertical-align:top;padding:7px 10px}.mL8Uca_table tbody tr:last-child td{border-bottom:none}.mL8Uca_table tbody tr:hover td{background:var(--dsw-alias-interactive-bg-hover)}.mL8Uca_mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.mL8Uca_cellMuted{color:var(--dsw-alias-label-tertiary)}.mL8Uca_actions{white-space:nowrap;align-items:center;gap:8px;display:flex}.mL8Uca_inlineTest{color:var(--dsw-alias-label-secondary);align-items:center;gap:6px;font-size:11.5px;display:inline-flex}.mL8Uca_inlineTest[data-status=ok]{color:var(--dsw-alias-state-success-primary)}.mL8Uca_inlineTest[data-status=fail]{color:var(--dsw-alias-state-error-primary)}.mL8Uca_badge{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);white-space:nowrap;border-radius:999px;padding:1px 8px;font-size:11px;line-height:1.6;display:inline-block}.mL8Uca_badge[data-kind=key]{color:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary)}.mL8Uca_badge[data-kind=password]{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}.mL8Uca_badge[data-kind=agent],.mL8Uca_badge[data-status=ok]{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}.mL8Uca_badge[data-status=fail]{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}.mL8Uca_badge[data-status=timeout]{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}.mL8Uca_primaryButton{color:var(--dsw-alias-label-primary-foreground);background:var(--dsw-alias-button-info-fill);cursor:pointer;white-space:nowrap;border:none;border-radius:8px;padding:6px 14px;font-size:13px;font-weight:600}.mL8Uca_primaryButton:hover:not(:disabled){background:var(--dsw-alias-button-info-hover)}.mL8Uca_primaryButton:disabled{opacity:.5;cursor:default}.mL8Uca_ghostButton{color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;white-space:nowrap;background:0 0;border-radius:8px;padding:5px 12px;font-size:12px}.mL8Uca_ghostButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.mL8Uca_ghostButton:disabled{opacity:.45;cursor:default}.mL8Uca_iconButton{width:26px;height:26px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;padding:0;font-size:13px;display:inline-flex}.mL8Uca_iconButton:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.mL8Uca_linkButton{color:var(--dsw-alias-state-business-primary);cursor:pointer;white-space:nowrap;background:0 0;border:none;padding:0;font-size:12px}.mL8Uca_linkButton:hover:not(:disabled){text-decoration:underline}.mL8Uca_linkButton:disabled{opacity:.45;cursor:default}.mL8Uca_linkButton[data-danger]{color:var(--dsw-alias-state-error-primary)}.mL8Uca_spinner{border:2px solid var(--dsw-alias-state-business-primary);vertical-align:-1px;border-top-color:#0000;border-radius:50%;flex:none;width:11px;height:11px;animation:.8s linear infinite mL8Uca_dshSshSpin;display:inline-block}@keyframes mL8Uca_dshSshSpin{to{transform:rotate(360deg)}}.mL8Uca_banner{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere;border-radius:8px;padding:8px 12px;font-size:12.5px;line-height:1.5}.mL8Uca_banner[data-kind=ok]{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}.mL8Uca_banner[data-kind=error]{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}.mL8Uca_banner[data-kind=info]{color:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary)}.mL8Uca_empty,.mL8Uca_loading{text-align:center;color:var(--dsw-alias-label-tertiary);padding:28px 12px;font-size:12.5px}.mL8Uca_modalBackdrop{z-index:40;background:var(--dsw-alias-bg-mask-1);justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.mL8Uca_modal{background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);width:min(560px,100vw - 48px);max-height:calc(100vh - 96px);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);border-radius:14px;flex-direction:column;gap:12px;padding:18px;display:flex;overflow-y:auto}.mL8Uca_modalTitle{margin:0;font-size:15px;font-weight:700}.mL8Uca_modalFooter{justify-content:flex-end;gap:10px;margin-top:4px;display:flex}.mL8Uca_formError{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px}.mL8Uca_hint{color:var(--dsw-alias-label-tertiary);font-size:11.5px}.mL8Uca_field{flex-direction:column;gap:5px;display:flex}.mL8Uca_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600}.mL8Uca_input{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);resize:vertical;border-radius:8px;outline:none;padding:7px 10px;font-family:inherit;font-size:13px}.mL8Uca_input:focus{border-color:var(--dsw-alias-state-business-primary)}.mL8Uca_input::placeholder{color:var(--dsw-alias-label-tertiary)}.mL8Uca_input:disabled{opacity:.55}.mL8Uca_formRow{grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px 12px;display:grid}.mL8Uca_radioRow{align-items:center;gap:16px;display:flex}.mL8Uca_radioLabel{color:var(--dsw-alias-label-primary);cursor:pointer;align-items:center;gap:6px;font-size:13px;display:inline-flex}.mL8Uca_termBody{flex-direction:column;flex:1;gap:8px;min-height:0;display:flex;overflow:hidden}.mL8Uca_termWrap{border:1px solid var(--dsw-alias-border-l1);background:#0b0e14;border-radius:10px;flex:1;min-height:0;position:relative;overflow:hidden}.mL8Uca_termContainer{position:absolute;inset:8px 10px}.mL8Uca_termPlaceholder{z-index:2;text-align:center;color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-bg-base);justify-content:center;align-items:center;padding:0 24px;font-size:12.5px;display:flex;position:absolute;inset:0}.mL8Uca_hiddenFile{display:none}.mL8Uca_browsePanel{border:1px solid var(--dsw-alias-border-l1);border-radius:10px;flex-direction:column;flex:none;display:flex;overflow:hidden}.mL8Uca_browseHeader{background:var(--dsw-alias-bg-layer-2);border-bottom:1px solid var(--dsw-alias-border-l1);align-items:center;gap:8px;padding:8px 10px;display:flex}.mL8Uca_browsePath{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-secondary);flex:1;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;overflow:hidden}.mL8Uca_browseList{max-height:240px;overflow-y:auto}.mL8Uca_dirRow{text-align:left;width:100%;font:inherit;color:var(--dsw-alias-label-primary);border:none;border-bottom:1px solid var(--dsw-alias-separator-primary);cursor:pointer;background:0 0;grid-template-columns:minmax(0,1fr) auto auto;gap:12px;padding:5px 10px;font-size:12.5px;display:grid}.mL8Uca_dirRow:hover{background:var(--dsw-alias-interactive-bg-hover)}.mL8Uca_dirRow[data-type=dir] .mL8Uca_dirName{color:var(--dsw-alias-state-business-primary)}.mL8Uca_dirRow[data-up]{color:var(--dsw-alias-label-secondary)}.mL8Uca_dirName{text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;overflow:hidden}.mL8Uca_dirType{color:var(--dsw-alias-label-tertiary);white-space:nowrap}.mL8Uca_dirSize{color:var(--dsw-alias-label-tertiary);white-space:nowrap;text-align:right}.mL8Uca_transferBlock{flex-direction:column;flex:none;gap:6px;display:flex}.mL8Uca_progressMeta{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;gap:12px;font-size:12px;display:flex}.mL8Uca_progressTrack{background:var(--dsw-alias-interactive-bg-hover);border-radius:999px;height:8px;overflow:hidden}.mL8Uca_progressBar{background:var(--dsw-alias-state-business-primary);border-radius:999px;height:100%;transition:width .12s linear}.mL8Uca_tunnelList{flex-direction:column;flex:none;gap:8px;display:flex}.mL8Uca_tunnelRow{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;align-items:center;gap:10px;padding:8px 12px;display:flex}.mL8Uca_tunnelRow[data-state=forwarding]{border-color:var(--dsw-alias-state-success-primary)}.mL8Uca_tunnelRow[data-state=connecting]{border-color:var(--dsw-alias-state-warn-primary)}.mL8Uca_tunnelRow[data-state=failed]{border-color:var(--dsw-alias-state-error-primary)}.mL8Uca_tunnelLabel{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12.5px;overflow:hidden}.mL8Uca_formCard{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;flex-direction:column;flex:none;gap:10px;padding:12px;display:flex}.mL8Uca_clusterForm{flex-direction:column;flex:none;gap:8px;display:flex}.mL8Uca_clusterFilters{grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:10px;display:grid}.mL8Uca_commandInput{min-height:96px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.mL8Uca_cellDetails summary{cursor:pointer;color:var(--dsw-alias-label-secondary);font-size:12px}.mL8Uca_cellPre{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);white-space:pre-wrap;word-break:break-all;border-radius:6px;max-height:180px;margin:4px 0 0;padding:6px 8px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px;overflow:auto}";
|
|
678
681
|
const tagId = "@linxin666/dsh-ssh/panel.module.css";
|
|
@@ -16082,7 +16085,7 @@ window.__ModuleLoader__.load({
|
|
|
16082
16085
|
/** The building package's version, when the bundle carries it. */
|
|
16083
16086
|
function bakedVersion() {
|
|
16084
16087
|
try {
|
|
16085
|
-
return "0.3.
|
|
16088
|
+
return "0.3.9";
|
|
16086
16089
|
} catch {
|
|
16087
16090
|
return;
|
|
16088
16091
|
}
|