@linxin666/dsh-ssh 0.2.4 → 0.2.5
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 +18 -5
- package/lib/client.js.map +1 -1
- package/lib/types/client/locales.d.ts +1 -0
- package/package.json +1 -1
- package/src/client/api.ts +16 -2
- package/src/client/locales.ts +2 -0
- package/src/client/panel/HostFormDialog.tsx +6 -0
|
@@ -57,6 +57,7 @@ export declare const zh: {
|
|
|
57
57
|
readonly 'form.passphrase': "密钥口令";
|
|
58
58
|
readonly 'form.password': "密码";
|
|
59
59
|
readonly 'form.passwordHint': "明文保存在 ~/.dsh/dsh-ssh.json(本机私有文件)";
|
|
60
|
+
readonly 'form.passwordRequired': "密码认证需要填写密码。";
|
|
60
61
|
readonly 'form.proxyJump': "跳板机别名";
|
|
61
62
|
readonly 'form.proxyJumpHint': "逗号分隔的本地主机别名,按顺序连接";
|
|
62
63
|
readonly 'form.description': "备注";
|
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.5",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "^22.19.0 || >=24.0.0"
|
package/src/client/api.ts
CHANGED
|
@@ -34,6 +34,17 @@ export class SshApiError extends Error {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/** Extract the `.error` message from a JSON error body; undefined when absent. */
|
|
38
|
+
function extractError(text: string): string | undefined {
|
|
39
|
+
if (text === '') return undefined
|
|
40
|
+
try {
|
|
41
|
+
const parsed = JSON.parse(text) as { error?: unknown } | null
|
|
42
|
+
return typeof parsed?.error === 'string' ? parsed.error : undefined
|
|
43
|
+
} catch {
|
|
44
|
+
return undefined
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
37
48
|
/** Parse a JSON response or throw an SshApiError. */
|
|
38
49
|
async function readJson<T>(response: Response): Promise<T> {
|
|
39
50
|
let body: unknown
|
|
@@ -219,7 +230,9 @@ export class SshApi {
|
|
|
219
230
|
body: file,
|
|
220
231
|
})
|
|
221
232
|
if (!response.ok || response.body === null) {
|
|
222
|
-
|
|
233
|
+
const text = await response.text().catch(() => '')
|
|
234
|
+
const error = extractError(text)
|
|
235
|
+
throw new SshApiError(error ?? `upload failed: HTTP ${response.status}`)
|
|
223
236
|
}
|
|
224
237
|
const reader = response.body.getReader()
|
|
225
238
|
const decoder = new TextDecoder()
|
|
@@ -268,7 +281,8 @@ export class SshApi {
|
|
|
268
281
|
const response = await fetch(SSH_API.download + query({ alias, remotePath }))
|
|
269
282
|
if (!response.ok || response.body === null) {
|
|
270
283
|
const text = await response.text().catch(() => '')
|
|
271
|
-
|
|
284
|
+
const error = extractError(text)
|
|
285
|
+
throw new SshApiError(error ?? `download failed: HTTP ${response.status}`)
|
|
272
286
|
}
|
|
273
287
|
const total = Number(response.headers.get('content-length') ?? '0')
|
|
274
288
|
const disposition = response.headers.get('content-disposition') ?? ''
|
package/src/client/locales.ts
CHANGED
|
@@ -60,6 +60,7 @@ export const zh = {
|
|
|
60
60
|
'form.passphrase': '密钥口令',
|
|
61
61
|
'form.password': '密码',
|
|
62
62
|
'form.passwordHint': '明文保存在 ~/.dsh/dsh-ssh.json(本机私有文件)',
|
|
63
|
+
'form.passwordRequired': '密码认证需要填写密码。',
|
|
63
64
|
'form.proxyJump': '跳板机别名',
|
|
64
65
|
'form.proxyJumpHint': '逗号分隔的本地主机别名,按顺序连接',
|
|
65
66
|
'form.description': '备注',
|
|
@@ -203,6 +204,7 @@ export const en: Record<keyof typeof zh, string> = {
|
|
|
203
204
|
'form.passphrase': 'Key passphrase',
|
|
204
205
|
'form.password': 'Password',
|
|
205
206
|
'form.passwordHint': 'Stored in plaintext in ~/.dsh/dsh-ssh.json (private local file)',
|
|
207
|
+
'form.passwordRequired': 'Password auth requires a password.',
|
|
206
208
|
'form.proxyJump': 'Jump host aliases',
|
|
207
209
|
'form.proxyJumpHint': 'Comma-separated local aliases, chained in order',
|
|
208
210
|
'form.description': 'Description',
|
|
@@ -92,6 +92,12 @@ export function HostFormDialog({ api, editing, onClose, onSaved }: HostFormDialo
|
|
|
92
92
|
setError(tt('form.portInvalid'))
|
|
93
93
|
return
|
|
94
94
|
}
|
|
95
|
+
// A password-auth host needs a password on create; when editing, a blank
|
|
96
|
+
// secret field preserves the stored credential instead.
|
|
97
|
+
if (editing == null && form.authKind === 'password' && form.password === '') {
|
|
98
|
+
setError(tt('form.passwordRequired'))
|
|
99
|
+
return
|
|
100
|
+
}
|
|
95
101
|
// Secrets are never echoed back by the API: when editing and the secret
|
|
96
102
|
// fields are left empty, the auth block is omitted so the stored
|
|
97
103
|
// authentication is preserved.
|