@linxin666/dsh-remote-web-ui 0.1.13 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +5 -2
- package/README.zh.md +1 -1
- package/lib/client.js +30 -17
- package/lib/client.js.map +1 -1
- package/lib/index.js +177 -34
- package/lib/mobile.js +44 -25
- package/lib/mobile.js.map +1 -1
- package/lib/types/client/PluginSettingsCard.d.ts +26 -11
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +27 -7
- package/lib/types/client/locales.js +2 -2
- package/lib/types/client/settings-form.d.ts +14 -5
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/settings-form.js +38 -10
- package/lib/types/mobile/mux.d.ts +13 -4
- package/lib/types/mobile/mux.d.ts.map +1 -1
- package/lib/types/mobile/mux.js +50 -30
- package/lib/types/mobile/mux.test.js +75 -6
- package/lib/types/mobile-api.d.ts +2 -0
- package/lib/types/mobile-api.d.ts.map +1 -1
- package/lib/types/mobile-api.js +4 -1
- package/lib/types/routes.d.ts +17 -0
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +49 -10
- package/lib/types/update.d.ts +5 -1
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +122 -26
- package/package.json +1 -1
- package/src/client/PluginSettingsCard.tsx +83 -17
- package/src/client/locales.ts +2 -2
- package/src/client/settings-card.module.css +6 -0
- package/src/client/settings-form.ts +41 -9
- package/src/mobile/mux.test.ts +84 -6
- package/src/mobile/mux.ts +46 -25
- package/src/mobile-api.ts +6 -1
- package/src/routes.ts +51 -10
- package/src/update.ts +116 -26
package/lib/types/mobile-api.js
CHANGED
|
@@ -40,6 +40,8 @@ const MOBILE_ALLOWLIST = new Set([
|
|
|
40
40
|
const MOBILE_PREFERENCES_METHOD = 'mobile.preferences';
|
|
41
41
|
/** One session.list page (thin phones load incrementally). */
|
|
42
42
|
const SESSION_PAGE_SIZE = 20;
|
|
43
|
+
/** SSE keep-alive ping cadence for the live mux stream (single connection). */
|
|
44
|
+
const DEFAULT_EVENTS_HEARTBEAT_MS = 15_000;
|
|
43
45
|
/** Encode one list position as an opaque continuation cursor. */
|
|
44
46
|
function sessionListCursor(updatedAt, sessionId) {
|
|
45
47
|
return `${updatedAt}:${sessionId}`;
|
|
@@ -74,6 +76,7 @@ const MOBILE_API_METHOD_PREFIX = `${MOBILE_API_PREFIX}/`;
|
|
|
74
76
|
*/
|
|
75
77
|
export function makeMobileApiRoutes(deps) {
|
|
76
78
|
const { service, apiProxy, mobileEnterToSend } = deps;
|
|
79
|
+
const eventsHeartbeatMs = deps.eventsHeartbeatMs ?? DEFAULT_EVENTS_HEARTBEAT_MS;
|
|
77
80
|
/** The phone gate: a live paired-device cookie, or nothing else proceeds. */
|
|
78
81
|
const gateOk = (req) => {
|
|
79
82
|
const deviceId = readCookie(req.headers.cookie, service.config.cookieName);
|
|
@@ -167,7 +170,7 @@ export function makeMobileApiRoutes(deps) {
|
|
|
167
170
|
catch {
|
|
168
171
|
// The write failed; the close handler tears the subscription down.
|
|
169
172
|
}
|
|
170
|
-
},
|
|
173
|
+
}, eventsHeartbeatMs);
|
|
171
174
|
const onClose = () => {
|
|
172
175
|
if (closed)
|
|
173
176
|
return;
|
package/lib/types/routes.d.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
11
11
|
import type { WebRoute } from '@deepseek-ai/dsh-host-webserver';
|
|
12
|
+
import { z } from 'zod';
|
|
12
13
|
import { type PairingService, type PairingSnapshot } from './pairing.ts';
|
|
13
14
|
/**
|
|
14
15
|
* Browser-trust fence for the /api/pair routes, mirroring the connection
|
|
@@ -43,6 +44,22 @@ export declare const PAIR_PATHS: {
|
|
|
43
44
|
readonly status: "/api/pair/status";
|
|
44
45
|
readonly events: "/api/pair/events";
|
|
45
46
|
};
|
|
47
|
+
/**
|
|
48
|
+
* /api/pair request payload contracts. Each POST endpoint validates its body
|
|
49
|
+
* against one of these instead of reaching into a hand-parsed object: the
|
|
50
|
+
* control-plane endpoints that carry no meaningful payload use the permissive
|
|
51
|
+
* pairActionPayloadSchema so their smoke calls keep working unchanged, while
|
|
52
|
+
* issue/accept enforce their optional/required fields. Unknown (extra) keys
|
|
53
|
+
* are tolerated exactly as the previous manual reads ignored them.
|
|
54
|
+
*/
|
|
55
|
+
export declare const issuePayloadSchema: z.ZodObject<{
|
|
56
|
+
workspaceId: z.ZodOptional<z.ZodString>;
|
|
57
|
+
address: z.ZodOptional<z.ZodString>;
|
|
58
|
+
}, z.core.$strip>;
|
|
59
|
+
export declare const acceptPayloadSchema: z.ZodObject<{
|
|
60
|
+
token: z.ZodDefault<z.ZodString>;
|
|
61
|
+
}, z.core.$strip>;
|
|
62
|
+
export declare const pairActionPayloadSchema: z.ZodObject<{}, z.core.$loose>;
|
|
46
63
|
/** The SSE fan-out for desktop panel status. */
|
|
47
64
|
export declare class PairingEventsStream {
|
|
48
65
|
private readonly streams;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAChE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAA;AAC/D,OAAO,EAA0B,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAA;AAGhG;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CA6BtG;AAKD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAOxE;AAKD,8CAA8C;AAC9C,eAAO,MAAM,UAAU;;;;;;;CAOb,CAAA;
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAChE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAA;AAC/D,OAAO,EAAE,CAAC,EAAgB,MAAM,KAAK,CAAA;AACrC,OAAO,EAA0B,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAA;AAGhG;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CA6BtG;AAKD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAOxE;AAKD,8CAA8C;AAC9C,eAAO,MAAM,UAAU;;;;;;;CAOb,CAAA;AAEV;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB;;;iBAG7B,CAAA;AACF,eAAO,MAAM,mBAAmB;;iBAE9B,CAAA;AACF,eAAO,MAAM,uBAAuB,gCAA6B,CAAA;AA4CjE,gDAAgD;AAChD,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;IAElD;;OAEG;gBACS,OAAO,EAAE,cAAc;IAInC,4DAA4D;IAC5D,IAAI,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,GAAG,IAAI;IAiBrD,kEAAkE;IAClE,IAAI,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI;IAYrC,wCAAwC;IACxC,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF;AAED,6CAA6C;AAC7C,MAAM,WAAW,cAAc;IAC7B,2BAA2B;IAC3B,OAAO,EAAE,cAAc,CAAA;IACvB,0EAA0E;IAC1E,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;CAChC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,cAAc,GAAG,QAAQ,EAAE,CA4K3D"}
|
package/lib/types/routes.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* cookie set on accept is the device identity the api/gate listener checks
|
|
8
8
|
* on every other /api request.
|
|
9
9
|
*/
|
|
10
|
+
import { z } from 'zod';
|
|
10
11
|
import { UnknownLanAddressError } from "./pairing.js";
|
|
11
12
|
import { isLoopbackClient, readCookie } from "./gate.js";
|
|
12
13
|
/**
|
|
@@ -90,6 +91,32 @@ export const PAIR_PATHS = {
|
|
|
90
91
|
status: '/api/pair/status',
|
|
91
92
|
events: '/api/pair/events',
|
|
92
93
|
};
|
|
94
|
+
/**
|
|
95
|
+
* /api/pair request payload contracts. Each POST endpoint validates its body
|
|
96
|
+
* against one of these instead of reaching into a hand-parsed object: the
|
|
97
|
+
* control-plane endpoints that carry no meaningful payload use the permissive
|
|
98
|
+
* pairActionPayloadSchema so their smoke calls keep working unchanged, while
|
|
99
|
+
* issue/accept enforce their optional/required fields. Unknown (extra) keys
|
|
100
|
+
* are tolerated exactly as the previous manual reads ignored them.
|
|
101
|
+
*/
|
|
102
|
+
export const issuePayloadSchema = z.object({
|
|
103
|
+
workspaceId: z.string().min(1).optional(),
|
|
104
|
+
address: z.string().min(1).optional(),
|
|
105
|
+
});
|
|
106
|
+
export const acceptPayloadSchema = z.object({
|
|
107
|
+
token: z.string().default(''),
|
|
108
|
+
});
|
|
109
|
+
export const pairActionPayloadSchema = z.object({}).passthrough();
|
|
110
|
+
/**
|
|
111
|
+
* Parse a pair request body through schema. A missing/empty or non-object
|
|
112
|
+
* body is treated as an empty object (the same way the previous manual reads
|
|
113
|
+
* yielded absent fields), and a value that fails the schema returns
|
|
114
|
+
* `undefined` so the caller can answer with the existing error shape.
|
|
115
|
+
*/
|
|
116
|
+
function parsePairPayload(schema, body) {
|
|
117
|
+
const result = schema.safeParse(body ?? {});
|
|
118
|
+
return result.success ? result.data : undefined;
|
|
119
|
+
}
|
|
93
120
|
/** One JSON response. */
|
|
94
121
|
function writeJson(res, status, body) {
|
|
95
122
|
const payload = JSON.stringify(body);
|
|
@@ -205,12 +232,12 @@ export function makeRoutes(deps) {
|
|
|
205
232
|
return;
|
|
206
233
|
}
|
|
207
234
|
const body = await readJsonBody(req);
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
:
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
235
|
+
const payload = parsePairPayload(issuePayloadSchema, body);
|
|
236
|
+
if (payload === undefined) {
|
|
237
|
+
writeJson(res, 400, { ok: false, code: 'bad-payload' });
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const { workspaceId, address } = payload;
|
|
214
241
|
try {
|
|
215
242
|
const { token, expiresAt } = service.issue(workspaceId, address);
|
|
216
243
|
// The default base is the public (tunneled) URL when configured — a
|
|
@@ -255,8 +282,12 @@ export function makeRoutes(deps) {
|
|
|
255
282
|
return;
|
|
256
283
|
}
|
|
257
284
|
const body = await readJsonBody(req);
|
|
258
|
-
const
|
|
259
|
-
|
|
285
|
+
const payload = parsePairPayload(acceptPayloadSchema, body);
|
|
286
|
+
if (payload === undefined) {
|
|
287
|
+
writeJson(res, 400, { ok: false, code: 'bad-payload' });
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
const result = service.accept(payload.token);
|
|
260
291
|
if (!result.ok) {
|
|
261
292
|
writeJson(res, result.code === 'used' ? 409 : 404, { ok: false, code: result.code });
|
|
262
293
|
return;
|
|
@@ -276,7 +307,11 @@ export function makeRoutes(deps) {
|
|
|
276
307
|
writeJson(res, 403, { ok: false, code: 'forbidden' });
|
|
277
308
|
return;
|
|
278
309
|
}
|
|
279
|
-
await readJsonBody(req);
|
|
310
|
+
const body = await readJsonBody(req);
|
|
311
|
+
if (parsePairPayload(pairActionPayloadSchema, body) === undefined) {
|
|
312
|
+
writeJson(res, 400, { ok: false, code: 'bad-payload' });
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
280
315
|
service.stop();
|
|
281
316
|
writeJson(res, 200, { ok: true });
|
|
282
317
|
};
|
|
@@ -287,7 +322,11 @@ export function makeRoutes(deps) {
|
|
|
287
322
|
writeJson(res, 403, { ok: false, code: 'forbidden' });
|
|
288
323
|
return;
|
|
289
324
|
}
|
|
290
|
-
await readJsonBody(req);
|
|
325
|
+
const body = await readJsonBody(req);
|
|
326
|
+
if (parsePairPayload(pairActionPayloadSchema, body) === undefined) {
|
|
327
|
+
writeJson(res, 400, { ok: false, code: 'bad-payload' });
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
291
330
|
const deviceId = readCookie(req.headers.cookie, service.config.cookieName);
|
|
292
331
|
if (deviceId === undefined || !service.heartbeat(deviceId)) {
|
|
293
332
|
writeJson(res, 401, { ok: false, code: 'unpaired' });
|
package/lib/types/update.d.ts
CHANGED
|
@@ -180,9 +180,13 @@ export interface UpdateRunDeps {
|
|
|
180
180
|
spawnImpl?: typeof spawn;
|
|
181
181
|
/** Hard timeout; the child is killed on expiry. */
|
|
182
182
|
timeoutMs?: number;
|
|
183
|
+
/** Platform override (defaults to process.platform; test seam). */
|
|
184
|
+
platform?: NodeJS.Platform;
|
|
183
185
|
}
|
|
184
186
|
/**
|
|
185
|
-
* Run the update
|
|
187
|
+
* Run the update inside the profile directory. Tries pnpm first, falls back
|
|
188
|
+
* to corepack and then npx when the previous command is missing (ENOENT);
|
|
189
|
+
* all candidates share one hard timeout and keep accumulating output.
|
|
186
190
|
* @param deps - profile dir, package list, and spawn/timeout seams.
|
|
187
191
|
* @returns the outcome with captured output.
|
|
188
192
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../src/update.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../src/update.ts"],"names":[],"mappings":"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"}
|
package/lib/types/update.js
CHANGED
|
@@ -286,48 +286,144 @@ export async function checkUpdates(deps) {
|
|
|
286
286
|
/** Cap on captured pnpm output (keeps error payloads bounded). */
|
|
287
287
|
const OUTPUT_CAP = 16 * 1024;
|
|
288
288
|
/**
|
|
289
|
-
*
|
|
289
|
+
* Windows cmd command-not-found stderr. With shell:true a missing shim
|
|
290
|
+
* exits with code 1 (cmd cannot report ENOENT), so the fallback chain
|
|
291
|
+
* detects this message instead of the spawn error event.
|
|
292
|
+
*/
|
|
293
|
+
const WIN_CMD_MISSING_RE = /not recognized as an internal or external command/i;
|
|
294
|
+
/**
|
|
295
|
+
* Run the update inside the profile directory. Tries pnpm first, falls back
|
|
296
|
+
* to corepack and then npx when the previous command is missing (ENOENT);
|
|
297
|
+
* all candidates share one hard timeout and keep accumulating output.
|
|
290
298
|
* @param deps - profile dir, package list, and spawn/timeout seams.
|
|
291
299
|
* @returns the outcome with captured output.
|
|
292
300
|
*/
|
|
293
301
|
export function runUpdate(deps) {
|
|
294
302
|
return new Promise((resolve) => {
|
|
295
|
-
const
|
|
303
|
+
const spawnImpl = deps.spawnImpl ?? spawn;
|
|
304
|
+
const packages = deps.packages;
|
|
305
|
+
const platform = deps.platform ?? process.platform;
|
|
306
|
+
// Windows ships pnpm/corepack/npx as .cmd shims; Node's spawn cannot
|
|
307
|
+
// start .cmd files directly (ENOENT even when installed), so route
|
|
308
|
+
// them through cmd.exe there. POSIX spawns stay shell-free.
|
|
309
|
+
const spawnOptions = {
|
|
296
310
|
cwd: deps.profileDir,
|
|
297
311
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
298
|
-
|
|
312
|
+
...(platform === 'win32' ? { shell: true } : {}),
|
|
313
|
+
};
|
|
314
|
+
// Ordered fallback chain: each is tried only when the previous one is
|
|
315
|
+
// missing on PATH (ENOENT on the spawn error event, never on close).
|
|
316
|
+
const candidates = [
|
|
317
|
+
{ command: 'pnpm', args: ['update', ...packages] },
|
|
318
|
+
{ command: 'corepack', args: ['pnpm', 'update', ...packages] },
|
|
319
|
+
{ command: 'npx', args: ['--yes', 'pnpm', 'update', ...packages] },
|
|
320
|
+
];
|
|
321
|
+
// `output` accumulates across candidates for UI display; `currentOutput`
|
|
322
|
+
// is reset per candidate and carries only that candidate's own diagnostics
|
|
323
|
+
// (its tail is subject to OUTPUT_CAP). The win32 missing-command test must
|
|
324
|
+
// run against `currentOutput` so a previous candidate's 'not recognized'
|
|
325
|
+
// stderr cannot misclassify a real failure in the next one.
|
|
299
326
|
let output = '';
|
|
327
|
+
let currentOutput = '';
|
|
300
328
|
const append = (chunk) => {
|
|
301
329
|
output += chunk.toString('utf8');
|
|
302
330
|
if (output.length > OUTPUT_CAP)
|
|
303
331
|
output = output.slice(output.length - OUTPUT_CAP);
|
|
332
|
+
currentOutput += chunk.toString('utf8');
|
|
333
|
+
if (currentOutput.length > OUTPUT_CAP)
|
|
334
|
+
currentOutput = currentOutput.slice(currentOutput.length - OUTPUT_CAP);
|
|
335
|
+
};
|
|
336
|
+
let currentChild;
|
|
337
|
+
// Terminal guard: once a result is produced the promise is settled and no
|
|
338
|
+
// further candidate is started (a killed child's late close must not
|
|
339
|
+
// respawn anything after a timeout resolution).
|
|
340
|
+
let finished = false;
|
|
341
|
+
const finish = (result) => {
|
|
342
|
+
if (finished)
|
|
343
|
+
return;
|
|
344
|
+
finished = true;
|
|
345
|
+
clearTimeout(timer);
|
|
346
|
+
resolve(result);
|
|
304
347
|
};
|
|
305
|
-
child.stdout?.on('data', append);
|
|
306
|
-
child.stderr?.on('data', append);
|
|
307
348
|
const timer = setTimeout(() => {
|
|
308
|
-
|
|
309
|
-
|
|
349
|
+
if (finished)
|
|
350
|
+
return;
|
|
351
|
+
if (platform === 'win32') {
|
|
352
|
+
// cmd.exe under shell:true only kills the shell wrapper; kill the whole
|
|
353
|
+
// process tree best-effort so pnpm/npx do not keep running.
|
|
354
|
+
const pid = currentChild?.pid;
|
|
355
|
+
if (pid !== undefined && pid > 0) {
|
|
356
|
+
try {
|
|
357
|
+
spawnImpl('taskkill', ['/pid', String(pid), '/t', '/f'], { stdio: 'ignore' });
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
// Best-effort kill; fall through to the timeout result.
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
currentChild?.kill('SIGTERM');
|
|
366
|
+
}
|
|
367
|
+
finish({ ok: false, exitCode: null, output, error: 'update timed out; install process killed', errorCode: 'timeout' });
|
|
310
368
|
}, deps.timeoutMs ?? 10 * 60_000);
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
369
|
+
const runCandidate = (index) => {
|
|
370
|
+
if (finished)
|
|
371
|
+
return;
|
|
372
|
+
if (index >= candidates.length) {
|
|
373
|
+
finish({
|
|
374
|
+
ok: false,
|
|
375
|
+
exitCode: null,
|
|
376
|
+
output,
|
|
377
|
+
error: 'pnpm not found on PATH (tried pnpm, corepack, npx); install pnpm and restart the app',
|
|
378
|
+
errorCode: 'pnpm-missing',
|
|
379
|
+
});
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const candidate = candidates[index];
|
|
383
|
+
currentOutput = '';
|
|
384
|
+
const child = spawnImpl(candidate.command, candidate.args, spawnOptions);
|
|
385
|
+
currentChild = child;
|
|
386
|
+
let settled = false;
|
|
387
|
+
const once = (fn) => {
|
|
388
|
+
if (settled)
|
|
389
|
+
return;
|
|
390
|
+
settled = true;
|
|
391
|
+
fn();
|
|
392
|
+
};
|
|
393
|
+
child.stdout?.on('data', append);
|
|
394
|
+
child.stderr?.on('data', append);
|
|
395
|
+
child.on('error', (error) => {
|
|
396
|
+
once(() => {
|
|
397
|
+
if (error.code === 'ENOENT') {
|
|
398
|
+
// Command missing: try the next candidate in the chain.
|
|
399
|
+
runCandidate(index + 1);
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
finish({ ok: false, exitCode: null, output, error: error.message, errorCode: undefined });
|
|
403
|
+
}
|
|
404
|
+
});
|
|
320
405
|
});
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
406
|
+
child.on('close', (code) => {
|
|
407
|
+
if (settled || finished)
|
|
408
|
+
return;
|
|
409
|
+
// Windows + shell: a missing command reports exit 1 with
|
|
410
|
+
// 'not recognized' instead of ENOENT — keep the chain going. Checked
|
|
411
|
+
// against this candidate's own output so a prior fallback's stderr
|
|
412
|
+
// cannot misclassify a real failure here.
|
|
413
|
+
if (platform === 'win32' && code !== 0 && WIN_CMD_MISSING_RE.test(currentOutput)) {
|
|
414
|
+
runCandidate(index + 1);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
settled = true;
|
|
418
|
+
finish({
|
|
419
|
+
ok: code === 0,
|
|
420
|
+
exitCode: code,
|
|
421
|
+
output,
|
|
422
|
+
error: code === 0 ? undefined : 'pnpm exited with code ' + String(code),
|
|
423
|
+
...(code === 0 ? {} : { errorCode: 'pnpm-failed' }),
|
|
424
|
+
});
|
|
330
425
|
});
|
|
331
|
-
}
|
|
426
|
+
};
|
|
427
|
+
runCandidate(0);
|
|
332
428
|
});
|
|
333
429
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linxin666/dsh-remote-web-ui",
|
|
3
3
|
"description": "Mobile remote control for the dsh web GUI: scan-to-pair QR entry beside the settings button, one-time pairing tokens, live device status, and revocable mobile sessions",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.15",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "^22.19.0 || >=24.0.0"
|
|
@@ -1,25 +1,44 @@
|
|
|
1
|
+
// Generated by scripts/sync-shared.mjs from shared/client/settings/PluginSettingsCard.tsx. Do not edit this copy; edit the shared source and run "node scripts/sync-shared.mjs".
|
|
1
2
|
/**
|
|
2
|
-
*
|
|
3
|
-
* plugin and what its settings govern, the controls inside, and the save
|
|
4
|
-
* writes them. Renders nothing while the namespace is unavailable — a
|
|
3
|
+
* Family-shared chrome for plugin settings cards: a disclosure header naming
|
|
4
|
+
* the plugin and what its settings govern, the controls inside, and the save
|
|
5
|
+
* that writes them. Renders nothing while the namespace is unavailable — a
|
|
5
6
|
* deployment that does not compose the owning plugin should show no trace of
|
|
6
|
-
* it.
|
|
7
|
-
*
|
|
7
|
+
* it. Inlined into each consumer's client bundle; mirrors the official
|
|
8
|
+
* ui-plugin-config PluginCard in a self-contained slice.
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
11
|
import { useState, type ReactNode } from 'react'
|
|
11
12
|
import type { CardShell } from './settings-form.ts'
|
|
12
|
-
import type { SettingsCardKey } from './locales.ts'
|
|
13
13
|
import css from './settings-card.module.css'
|
|
14
14
|
|
|
15
|
+
/** Copy keys the card chrome itself reads; every consumer locale carries this shared vocabulary. */
|
|
16
|
+
export const CARD_COPY_KEYS = [
|
|
17
|
+
'settings.collapse',
|
|
18
|
+
'settings.expand',
|
|
19
|
+
'settings.notExposed',
|
|
20
|
+
'settings.unsaved',
|
|
21
|
+
'settings.readOnly',
|
|
22
|
+
'settings.saveFailed',
|
|
23
|
+
'settings.discard',
|
|
24
|
+
'settings.save',
|
|
25
|
+
'settings.saving',
|
|
26
|
+
] as const
|
|
27
|
+
|
|
28
|
+
/** Copy key the card chrome itself reads. */
|
|
29
|
+
export type CardCopyKey = (typeof CARD_COPY_KEYS)[number]
|
|
30
|
+
|
|
31
|
+
/** Key domain for the plugin's own copy (inferred per consumer). */
|
|
32
|
+
export type SettingsCardKey<TKey extends string = string> = TKey | CardCopyKey
|
|
33
|
+
|
|
15
34
|
/** Card chrome shared by every plugin settings card. */
|
|
16
|
-
export interface PluginSettingsCardProps {
|
|
35
|
+
export interface PluginSettingsCardProps<TKey extends string = string> {
|
|
17
36
|
/** Locale reader for this card's copy. */
|
|
18
|
-
t: (key: SettingsCardKey) => string
|
|
37
|
+
t: (key: SettingsCardKey<TKey>, params?: Record<string, string | number>) => string
|
|
19
38
|
/** Locale key of the plugin's name. */
|
|
20
|
-
titleKey:
|
|
39
|
+
titleKey: TKey
|
|
21
40
|
/** Locale key of the line describing what this plugin's settings govern. */
|
|
22
|
-
descriptionKey:
|
|
41
|
+
descriptionKey: TKey
|
|
23
42
|
/** The card's form state: availability, writability, and what a save would do. */
|
|
24
43
|
state: CardShell
|
|
25
44
|
/** Write every staged edit. */
|
|
@@ -35,11 +54,12 @@ export interface PluginSettingsCardProps {
|
|
|
35
54
|
* @param props - the plugin's copy keys, its form state, and its controls.
|
|
36
55
|
* @returns the card, or nothing while the namespace is still loading.
|
|
37
56
|
*/
|
|
38
|
-
export function PluginSettingsCard(props: PluginSettingsCardProps) {
|
|
57
|
+
export function PluginSettingsCard<TKey extends string = string>(props: PluginSettingsCardProps<TKey>) {
|
|
39
58
|
const [open, setOpen] = useState(false)
|
|
40
59
|
const { state } = props
|
|
41
60
|
if (!state.available) return null
|
|
42
61
|
const title = props.t(props.titleKey)
|
|
62
|
+
const description = props.t(props.descriptionKey)
|
|
43
63
|
const blocked = !state.dirty || state.invalid || state.saving
|
|
44
64
|
const cardClass = open ? `${css.cardOpen} ${css.card}` : css.card
|
|
45
65
|
// The namespace exists but the Host does not serve it to this client (the
|
|
@@ -57,8 +77,8 @@ export function PluginSettingsCard(props: PluginSettingsCardProps) {
|
|
|
57
77
|
onClick={() => { setOpen(!open) }}
|
|
58
78
|
>
|
|
59
79
|
<span className={css.headText}>
|
|
60
|
-
<span className={css.name}>{title}</span>
|
|
61
|
-
<span className={css.description}>{
|
|
80
|
+
<span className={css.name} title={title}>{title}</span>
|
|
81
|
+
<span className={css.description} title={description}>{description}</span>
|
|
62
82
|
</span>
|
|
63
83
|
<svg
|
|
64
84
|
width="14"
|
|
@@ -94,10 +114,10 @@ export function PluginSettingsCard(props: PluginSettingsCardProps) {
|
|
|
94
114
|
onClick={() => { setOpen(!open) }}
|
|
95
115
|
>
|
|
96
116
|
<span className={css.headText}>
|
|
97
|
-
<span className={css.name}>{title}</span>
|
|
98
|
-
<span className={css.description}>{
|
|
117
|
+
<span className={css.name} title={title}>{title}</span>
|
|
118
|
+
<span className={css.description} title={description}>{description}</span>
|
|
99
119
|
</span>
|
|
100
|
-
{state.dirty ? <span className={css.pending}>{props.t('settings.unsaved')}</span> : null}
|
|
120
|
+
{state.dirty ? <span className={css.pending} title={props.t('settings.unsaved')}>{props.t('settings.unsaved')}</span> : null}
|
|
101
121
|
<svg
|
|
102
122
|
width="14"
|
|
103
123
|
height="14"
|
|
@@ -259,4 +279,50 @@ export function BooleanField(props: FieldProps & {
|
|
|
259
279
|
<p className={css.hint}>{props.hint}</p>
|
|
260
280
|
</div>
|
|
261
281
|
)
|
|
262
|
-
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** A staged enumerated field rendered as a select. */
|
|
285
|
+
export function ChoiceField(props: FieldProps & {
|
|
286
|
+
/** Copy for the inherit option (draft text is the empty string). */
|
|
287
|
+
inheritLabel: string
|
|
288
|
+
/** Choices rendered in order; `value` is the draft/stored text. */
|
|
289
|
+
choices: ReadonlyArray<{ value: string; label: string }>
|
|
290
|
+
}) {
|
|
291
|
+
return (
|
|
292
|
+
<div className={css.field}>
|
|
293
|
+
<div className={css.head}>
|
|
294
|
+
<label className={css.label} htmlFor={props.id}>{props.label}</label>
|
|
295
|
+
{props.overridden
|
|
296
|
+
? (
|
|
297
|
+
<span className={css.badges}>
|
|
298
|
+
<span className={css.badge}>{props.overriddenLabel}</span>
|
|
299
|
+
<button
|
|
300
|
+
type="button"
|
|
301
|
+
className={css.reset}
|
|
302
|
+
disabled={props.disabled}
|
|
303
|
+
onClick={props.onReset}
|
|
304
|
+
>
|
|
305
|
+
{props.resetLabel}
|
|
306
|
+
</button>
|
|
307
|
+
</span>
|
|
308
|
+
)
|
|
309
|
+
: null}
|
|
310
|
+
</div>
|
|
311
|
+
<select
|
|
312
|
+
id={props.id}
|
|
313
|
+
className={css.select}
|
|
314
|
+
value={props.text}
|
|
315
|
+
disabled={props.disabled}
|
|
316
|
+
onChange={(event) => { props.onEdit(event.target.value) }}
|
|
317
|
+
>
|
|
318
|
+
<option value="">{props.inheritLabel}</option>
|
|
319
|
+
{props.choices.map(choice => (
|
|
320
|
+
<option key={choice.value} value={choice.value}>{choice.label}</option>
|
|
321
|
+
))}
|
|
322
|
+
</select>
|
|
323
|
+
<p className={props.invalid ? css.invalid : css.hint}>
|
|
324
|
+
{props.invalid ? props.invalidLabel : props.hint}
|
|
325
|
+
</p>
|
|
326
|
+
</div>
|
|
327
|
+
)
|
|
328
|
+
}
|
package/src/client/locales.ts
CHANGED
|
@@ -97,7 +97,7 @@ export const zh = {
|
|
|
97
97
|
'update.packages': '组件版本',
|
|
98
98
|
'update.retry': '重新检查',
|
|
99
99
|
'update.close': '关闭更新面板',
|
|
100
|
-
'update.error.pnpmMissing': '未找到 pnpm
|
|
100
|
+
'update.error.pnpmMissing': '未找到 pnpm,已尝试 pnpm / corepack / npx 仍不可用。请安装 pnpm 后重启应用(Windows: npm install -g pnpm 或 corepack enable;macOS: brew install pnpm 或 corepack enable)。',
|
|
101
101
|
'update.error.timeout': '更新超时,已终止安装进程,请重试。',
|
|
102
102
|
'update.error.notFound': '未找到 dsh-web-ui 聚合包安装,无法更新。',
|
|
103
103
|
'update.error.link': '当前为本地开发模式(链接安装),无法自动更新。',
|
|
@@ -208,7 +208,7 @@ export const en = {
|
|
|
208
208
|
'update.packages': 'Component versions',
|
|
209
209
|
'update.retry': 'Check again',
|
|
210
210
|
'update.close': 'Close update panel',
|
|
211
|
-
'update.error.pnpmMissing': 'pnpm was not found. Install
|
|
211
|
+
'update.error.pnpmMissing': 'pnpm was not found (tried pnpm / corepack / npx). Install pnpm and restart the app (Windows: npm install -g pnpm or corepack enable; macOS: brew install pnpm or corepack enable).',
|
|
212
212
|
'update.error.timeout': 'The update timed out and the install process was killed; try again.',
|
|
213
213
|
'update.error.notFound': 'The dsh-web-ui aggregate package is not installed; nothing to update.',
|
|
214
214
|
'update.error.link': 'Local development mode (link install) — automatic update is unavailable.',
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* Generated by scripts/sync-shared.mjs from shared/client/settings/settings-card.module.css. Do not edit this copy; edit the shared source and run "node scripts/sync-shared.mjs". */
|
|
1
2
|
/* Plugin settings card chrome + staged form fields.
|
|
2
3
|
* Aligned with the official ui-settings-plugins PluginCard / fields CSS:
|
|
3
4
|
* same semantic tokens, radius, typography and states so family cards read
|
|
@@ -229,6 +230,11 @@
|
|
|
229
230
|
outline-offset: 2px;
|
|
230
231
|
}
|
|
231
232
|
|
|
233
|
+
.reset:focus-visible {
|
|
234
|
+
outline: 2px solid var(--dsw-alias-brand-primary);
|
|
235
|
+
outline-offset: 2px;
|
|
236
|
+
}
|
|
237
|
+
|
|
232
238
|
.input,
|
|
233
239
|
.select {
|
|
234
240
|
border: 1px solid var(--dsw-alias-border-l2);
|