@linxin666/dsh-remote-web-ui 0.1.9 → 0.1.11
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.md +17 -6
- package/lib/client.js +442 -17
- package/lib/client.js.map +1 -1
- package/lib/index.js +406 -17
- package/lib/types/client/PluginSettingsCard.d.ts +1 -1
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +10 -1
- package/lib/types/client/RemoteEntry.d.ts.map +1 -1
- package/lib/types/client/RemoteEntry.js +9 -7
- package/lib/types/client/UpdateEntry.d.ts +14 -0
- package/lib/types/client/UpdateEntry.d.ts.map +1 -0
- package/lib/types/client/UpdateEntry.js +68 -0
- package/lib/types/client/UpdatePanel.d.ts +40 -0
- package/lib/types/client/UpdatePanel.d.ts.map +1 -0
- package/lib/types/client/UpdatePanel.js +82 -0
- package/lib/types/client/index.d.ts +2 -0
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/locales.d.ts +58 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +60 -0
- package/lib/types/client/settings-form.d.ts +9 -1
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/settings-form.js +2 -1
- package/lib/types/client/update-api.d.ts +19 -0
- package/lib/types/client/update-api.d.ts.map +1 -0
- package/lib/types/client/update-api.js +27 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +45 -1
- package/lib/types/routes.d.ts +15 -0
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +1 -1
- package/lib/types/update-routes.d.ts +30 -0
- package/lib/types/update-routes.d.ts.map +1 -0
- package/lib/types/update-routes.js +52 -0
- package/lib/types/update.d.ts +191 -0
- package/lib/types/update.d.ts.map +1 -0
- package/lib/types/update.js +333 -0
- package/package.json +1 -1
- package/src/client/PluginSettingsCard.tsx +31 -1
- package/src/client/RemoteEntry.tsx +12 -7
- package/src/client/UpdateEntry.tsx +96 -0
- package/src/client/UpdatePanel.tsx +203 -0
- package/src/client/index.ts +2 -0
- package/src/client/locales.ts +60 -0
- package/src/client/remote.module.css +109 -0
- package/src/client/settings-card.module.css +7 -0
- package/src/client/settings-form.ts +11 -2
- package/src/client/update-api.ts +29 -0
- package/src/index.ts +51 -1
- package/src/routes.ts +1 -1
- package/src/update-routes.ts +71 -0
- package/src/update.ts +435 -0
package/lib/index.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
1
2
|
import { setInterval as setInterval$1 } from "node:timers";
|
|
2
3
|
import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
3
4
|
import z from "schemastery";
|
|
4
5
|
import { randomBytes } from "node:crypto";
|
|
5
6
|
import { readFile } from "node:fs/promises";
|
|
6
|
-
import { existsSync } from "node:fs";
|
|
7
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
7
8
|
import { fileURLToPath } from "node:url";
|
|
8
9
|
import { RpcId } from "@deepseek-ai/dsh-host-apiproxy/api/rpc";
|
|
9
10
|
import { networkInterfaces } from "node:os";
|
|
10
11
|
import { Tunnel, bin, install } from "cloudflared";
|
|
12
|
+
import { dirname, join } from "node:path";
|
|
13
|
+
import { spawn } from "node:child_process";
|
|
11
14
|
//#region src/pairing.ts
|
|
12
15
|
/**
|
|
13
16
|
* Pairing state machine: one active one-time token, a device-session table,
|
|
@@ -439,7 +442,7 @@ const PAIR_PATHS = {
|
|
|
439
442
|
events: "/api/pair/events"
|
|
440
443
|
};
|
|
441
444
|
/** One JSON response. */
|
|
442
|
-
function writeJson(res, status, body) {
|
|
445
|
+
function writeJson$1(res, status, body) {
|
|
443
446
|
const payload = JSON.stringify(body);
|
|
444
447
|
res.writeHead(status, {
|
|
445
448
|
"content-type": "application/json; charset=utf-8",
|
|
@@ -555,7 +558,7 @@ function makeRoutes(deps) {
|
|
|
555
558
|
const handleIssue = async (req, res) => {
|
|
556
559
|
if (!requireMethod(req, res, "POST")) return;
|
|
557
560
|
if (!loopbackFence(req)) {
|
|
558
|
-
writeJson(res, 403, {
|
|
561
|
+
writeJson$1(res, 403, {
|
|
559
562
|
ok: false,
|
|
560
563
|
code: "forbidden"
|
|
561
564
|
});
|
|
@@ -568,7 +571,7 @@ function makeRoutes(deps) {
|
|
|
568
571
|
const { token, expiresAt } = service.issue(workspaceId, address);
|
|
569
572
|
const base = address === void 0 ? service.publicBaseUrl ?? service.lanBaseUrl : service.lanBaseUrlFor(address);
|
|
570
573
|
if (base === void 0) throw new Error("remote-web-ui: base unavailable");
|
|
571
|
-
writeJson(res, 200, {
|
|
574
|
+
writeJson$1(res, 200, {
|
|
572
575
|
ok: true,
|
|
573
576
|
url: `${base}/?pair=${token}${workspaceId === void 0 ? "" : `&workspace=${encodeURIComponent(workspaceId)}`}`,
|
|
574
577
|
token,
|
|
@@ -578,7 +581,7 @@ function makeRoutes(deps) {
|
|
|
578
581
|
});
|
|
579
582
|
} catch (error) {
|
|
580
583
|
const unknownAddress = error instanceof UnknownLanAddressError;
|
|
581
|
-
writeJson(res, unknownAddress ? 400 : 409, {
|
|
584
|
+
writeJson$1(res, unknownAddress ? 400 : 409, {
|
|
582
585
|
ok: false,
|
|
583
586
|
code: unknownAddress ? "unknown-address" : "lan-required"
|
|
584
587
|
});
|
|
@@ -587,14 +590,14 @@ function makeRoutes(deps) {
|
|
|
587
590
|
const handleAccept = async (req, res) => {
|
|
588
591
|
if (!requireMethod(req, res, "POST")) return;
|
|
589
592
|
if (!lanFence(req)) {
|
|
590
|
-
writeJson(res, 403, {
|
|
593
|
+
writeJson$1(res, 403, {
|
|
591
594
|
ok: false,
|
|
592
595
|
code: "forbidden"
|
|
593
596
|
});
|
|
594
597
|
return;
|
|
595
598
|
}
|
|
596
599
|
if (rateLimitAccept(req)) {
|
|
597
|
-
writeJson(res, 429, {
|
|
600
|
+
writeJson$1(res, 429, {
|
|
598
601
|
ok: false,
|
|
599
602
|
code: "rate-limited"
|
|
600
603
|
});
|
|
@@ -604,7 +607,7 @@ function makeRoutes(deps) {
|
|
|
604
607
|
const token = typeof body?.token === "string" ? body.token : "";
|
|
605
608
|
const result = service.accept(token);
|
|
606
609
|
if (!result.ok) {
|
|
607
|
-
writeJson(res, result.code === "used" ? 409 : 404, {
|
|
610
|
+
writeJson$1(res, result.code === "used" ? 409 : 404, {
|
|
608
611
|
ok: false,
|
|
609
612
|
code: result.code
|
|
610
613
|
});
|
|
@@ -622,7 +625,7 @@ function makeRoutes(deps) {
|
|
|
622
625
|
const handleStop = async (req, res) => {
|
|
623
626
|
if (!requireMethod(req, res, "POST")) return;
|
|
624
627
|
if (!loopbackFence(req)) {
|
|
625
|
-
writeJson(res, 403, {
|
|
628
|
+
writeJson$1(res, 403, {
|
|
626
629
|
ok: false,
|
|
627
630
|
code: "forbidden"
|
|
628
631
|
});
|
|
@@ -630,12 +633,12 @@ function makeRoutes(deps) {
|
|
|
630
633
|
}
|
|
631
634
|
await readJsonBody$1(req);
|
|
632
635
|
service.stop();
|
|
633
|
-
writeJson(res, 200, { ok: true });
|
|
636
|
+
writeJson$1(res, 200, { ok: true });
|
|
634
637
|
};
|
|
635
638
|
const handleHeartbeat = async (req, res) => {
|
|
636
639
|
if (!requireMethod(req, res, "POST")) return;
|
|
637
640
|
if (!lanFence(req)) {
|
|
638
|
-
writeJson(res, 403, {
|
|
641
|
+
writeJson$1(res, 403, {
|
|
639
642
|
ok: false,
|
|
640
643
|
code: "forbidden"
|
|
641
644
|
});
|
|
@@ -644,25 +647,25 @@ function makeRoutes(deps) {
|
|
|
644
647
|
await readJsonBody$1(req);
|
|
645
648
|
const deviceId = readCookie(req.headers.cookie, service.config.cookieName);
|
|
646
649
|
if (deviceId === void 0 || !service.heartbeat(deviceId)) {
|
|
647
|
-
writeJson(res, 401, {
|
|
650
|
+
writeJson$1(res, 401, {
|
|
648
651
|
ok: false,
|
|
649
652
|
code: "unpaired"
|
|
650
653
|
});
|
|
651
654
|
return;
|
|
652
655
|
}
|
|
653
|
-
writeJson(res, 200, { ok: true });
|
|
656
|
+
writeJson$1(res, 200, { ok: true });
|
|
654
657
|
};
|
|
655
658
|
const handleStatus = async (req, res) => {
|
|
656
659
|
if (!requireMethod(req, res, "GET")) return;
|
|
657
660
|
if (!lanFence(req)) {
|
|
658
|
-
writeJson(res, 403, {
|
|
661
|
+
writeJson$1(res, 403, {
|
|
659
662
|
ok: false,
|
|
660
663
|
code: "forbidden"
|
|
661
664
|
});
|
|
662
665
|
return;
|
|
663
666
|
}
|
|
664
667
|
const deviceId = readCookie(req.headers.cookie, service.config.cookieName);
|
|
665
|
-
writeJson(res, 200, {
|
|
668
|
+
writeJson$1(res, 200, {
|
|
666
669
|
ok: true,
|
|
667
670
|
paired: deviceId !== void 0 && service.hasDevice(deviceId),
|
|
668
671
|
...service.snapshot()
|
|
@@ -671,7 +674,7 @@ function makeRoutes(deps) {
|
|
|
671
674
|
const handleEvents = (req, res) => {
|
|
672
675
|
if (!requireMethod(req, res, "GET")) return;
|
|
673
676
|
if (!loopbackFence(req)) {
|
|
674
|
-
writeJson(res, 403, {
|
|
677
|
+
writeJson$1(res, 403, {
|
|
675
678
|
ok: false,
|
|
676
679
|
code: "forbidden"
|
|
677
680
|
});
|
|
@@ -1261,6 +1264,358 @@ var TunnelManager = class {
|
|
|
1261
1264
|
} catch {}
|
|
1262
1265
|
}
|
|
1263
1266
|
};
|
|
1267
|
+
/** The aggregate package that is the canonical update entry point. */
|
|
1268
|
+
const AGGREGATE_PACKAGE = "@linxin666/dsh-web-ui-all";
|
|
1269
|
+
/** Fallback anchor: this plugin's own package when the aggregate is absent. */
|
|
1270
|
+
const SELF_PACKAGE = "@linxin666/dsh-remote-web-ui";
|
|
1271
|
+
/** A profile manifest `name` prefix (e.g. `dsh-profile-web`). */
|
|
1272
|
+
const PROFILE_NAME_PREFIX = "dsh-profile-";
|
|
1273
|
+
/** How many ancestor directories a profile search walks before giving up. */
|
|
1274
|
+
const PROFILE_WALK_DEPTH = 12;
|
|
1275
|
+
/**
|
|
1276
|
+
* Parse a semantic version string (leading `v` tolerated, build metadata
|
|
1277
|
+
* ignored). Returns undefined for unparseable input.
|
|
1278
|
+
* @param value - the version string, e.g. `0.1.10` or `0.1.11-rc.1`.
|
|
1279
|
+
* @returns the parsed parts, or undefined.
|
|
1280
|
+
*/
|
|
1281
|
+
function parseSemver(value) {
|
|
1282
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(value.trim());
|
|
1283
|
+
if (match === null) return void 0;
|
|
1284
|
+
return {
|
|
1285
|
+
major: Number(match[1]),
|
|
1286
|
+
minor: Number(match[2]),
|
|
1287
|
+
patch: Number(match[3]),
|
|
1288
|
+
prerelease: match[4] === void 0 ? [] : match[4].split(".")
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Compare two semantic versions per the semver precedence rules (a release
|
|
1293
|
+
* outranks any of its prereleases; numeric prerelease identifiers compare
|
|
1294
|
+
* numerically and sort below alphanumeric ones). An unparseable version sorts
|
|
1295
|
+
* below every parseable one; two unparseable versions compare equal.
|
|
1296
|
+
* @param a - first version.
|
|
1297
|
+
* @param b - second version.
|
|
1298
|
+
* @returns negative when a < b, 0 when equal, positive when a > b.
|
|
1299
|
+
*/
|
|
1300
|
+
function compareVersions(a, b) {
|
|
1301
|
+
const pa = parseSemver(a);
|
|
1302
|
+
const pb = parseSemver(b);
|
|
1303
|
+
if (pa === void 0 && pb === void 0) return 0;
|
|
1304
|
+
if (pa === void 0) return -1;
|
|
1305
|
+
if (pb === void 0) return 1;
|
|
1306
|
+
for (const key of [
|
|
1307
|
+
"major",
|
|
1308
|
+
"minor",
|
|
1309
|
+
"patch"
|
|
1310
|
+
]) if (pa[key] !== pb[key]) return pa[key] < pb[key] ? -1 : 1;
|
|
1311
|
+
if (pa.prerelease.length === 0 && pb.prerelease.length === 0) return 0;
|
|
1312
|
+
if (pa.prerelease.length === 0) return 1;
|
|
1313
|
+
if (pb.prerelease.length === 0) return -1;
|
|
1314
|
+
for (let index = 0; index < Math.max(pa.prerelease.length, pb.prerelease.length); index++) {
|
|
1315
|
+
const ra = pa.prerelease[index];
|
|
1316
|
+
const rb = pb.prerelease[index];
|
|
1317
|
+
if (ra === void 0) return -1;
|
|
1318
|
+
if (rb === void 0) return 1;
|
|
1319
|
+
if (ra === rb) continue;
|
|
1320
|
+
const numericA = /^\d+$/.test(ra);
|
|
1321
|
+
const numericB = /^\d+$/.test(rb);
|
|
1322
|
+
if (numericA && numericB) return Number(ra) < Number(rb) ? -1 : 1;
|
|
1323
|
+
if (numericA) return -1;
|
|
1324
|
+
if (numericB) return 1;
|
|
1325
|
+
return ra < rb ? -1 : 1;
|
|
1326
|
+
}
|
|
1327
|
+
return 0;
|
|
1328
|
+
}
|
|
1329
|
+
/** Read a package.json at a path, tolerating any parse/IO failure. */
|
|
1330
|
+
function readManifest(path) {
|
|
1331
|
+
try {
|
|
1332
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
1333
|
+
return typeof parsed === "object" && parsed !== null ? parsed : void 0;
|
|
1334
|
+
} catch {
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
/**
|
|
1339
|
+
* Locate the owning dsh profile by walking up from an installed package's
|
|
1340
|
+
* manifest until a manifest named `dsh-profile-*` appears (the profile
|
|
1341
|
+
* directory is the first ancestor whose package.json carries that name).
|
|
1342
|
+
* @param anchorManifestPath - absolute path of the anchor package.json.
|
|
1343
|
+
* @returns the profile name/dir, or undefined when not profile-installed.
|
|
1344
|
+
*/
|
|
1345
|
+
function findProfile(anchorManifestPath) {
|
|
1346
|
+
let dir = dirname(anchorManifestPath);
|
|
1347
|
+
for (let depth = 0; depth < PROFILE_WALK_DEPTH; depth++) {
|
|
1348
|
+
const name = readManifest(join(dir, "package.json"))?.name;
|
|
1349
|
+
if (typeof name === "string" && name.startsWith(PROFILE_NAME_PREFIX)) return {
|
|
1350
|
+
name: name.slice(12),
|
|
1351
|
+
dir
|
|
1352
|
+
};
|
|
1353
|
+
const parent = dirname(dir);
|
|
1354
|
+
if (parent === dir) return void 0;
|
|
1355
|
+
dir = parent;
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
/** Whether a dependency spec is a local link/file/dev-mode install. */
|
|
1359
|
+
function isLinkedSpec(spec) {
|
|
1360
|
+
if (typeof spec !== "string") return false;
|
|
1361
|
+
return /^(?:link|file):|^\.{1,2}(?:[/\\]|$)/.test(spec);
|
|
1362
|
+
}
|
|
1363
|
+
/**
|
|
1364
|
+
* Resolve the anchor package's manifest path. The aggregate package is the
|
|
1365
|
+
* canonical entry point; this plugin's own package is the fallback.
|
|
1366
|
+
* @param resolve - a Node resolve implementation scoped to the host process.
|
|
1367
|
+
* @returns the absolute manifest path, or undefined when neither is installed.
|
|
1368
|
+
*/
|
|
1369
|
+
function resolveAnchorManifest(resolve) {
|
|
1370
|
+
for (const name of [AGGREGATE_PACKAGE, SELF_PACKAGE]) try {
|
|
1371
|
+
return resolve(name + "/package.json");
|
|
1372
|
+
} catch {}
|
|
1373
|
+
}
|
|
1374
|
+
/**
|
|
1375
|
+
* Resolve what an update would touch: the owning profile directory and the
|
|
1376
|
+
* family package list. Fails with an error code when the anchor is missing
|
|
1377
|
+
* ('not-found') or is a local dev install ('link').
|
|
1378
|
+
* @param deps - the anchor manifest path (resolveAnchorManifest output).
|
|
1379
|
+
* @returns the target, or the failure code.
|
|
1380
|
+
*/
|
|
1381
|
+
function resolveUpdateTarget(deps) {
|
|
1382
|
+
const manifestPath = deps.anchorManifestPath;
|
|
1383
|
+
if (manifestPath === void 0) return { error: "not-found" };
|
|
1384
|
+
const manifest = readManifest(manifestPath);
|
|
1385
|
+
if (manifest === void 0) return { error: "not-found" };
|
|
1386
|
+
const anchor = typeof manifest.name === "string" ? manifest.name : void 0;
|
|
1387
|
+
if (anchor === void 0) return { error: "not-found" };
|
|
1388
|
+
const profile = findProfile(manifestPath);
|
|
1389
|
+
if (profile === void 0) return { error: "link" };
|
|
1390
|
+
const spec = (readManifest(join(profile.dir, "package.json"))?.dependencies)?.[anchor];
|
|
1391
|
+
if (isLinkedSpec(spec)) return { error: "link" };
|
|
1392
|
+
return {
|
|
1393
|
+
profileName: profile.name,
|
|
1394
|
+
profileDir: profile.dir,
|
|
1395
|
+
packages: [anchor, ...familyChildren(manifest)]
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1398
|
+
/** Family children of the anchor: its dependencies under the family scope. */
|
|
1399
|
+
function familyChildren(anchorManifest) {
|
|
1400
|
+
const dependencies = anchorManifest.dependencies;
|
|
1401
|
+
if (typeof dependencies !== "object" || dependencies === null) return [];
|
|
1402
|
+
const names = [];
|
|
1403
|
+
for (const [name, spec] of Object.entries(dependencies)) if (name.startsWith("@linxin666/") && typeof spec === "string") names.push(name);
|
|
1404
|
+
return names;
|
|
1405
|
+
}
|
|
1406
|
+
/**
|
|
1407
|
+
* Probe the npm registry for one package's latest release.
|
|
1408
|
+
* @param name - the package name (scope slash URL-encoded).
|
|
1409
|
+
* @param fetchImpl - the fetch implementation (global fetch in the host).
|
|
1410
|
+
* @param timeoutMs - probe timeout.
|
|
1411
|
+
* @returns the latest version string, or undefined on any failure.
|
|
1412
|
+
*/
|
|
1413
|
+
async function fetchLatestVersion(name, fetchImpl, timeoutMs = 1e4) {
|
|
1414
|
+
try {
|
|
1415
|
+
const controller = new AbortController();
|
|
1416
|
+
const timer = setTimeout(() => {
|
|
1417
|
+
controller.abort();
|
|
1418
|
+
}, timeoutMs);
|
|
1419
|
+
try {
|
|
1420
|
+
const response = await fetchImpl("https://registry.npmjs.org/" + name.replace("/", "%2F") + "/latest");
|
|
1421
|
+
if (!response.ok) return void 0;
|
|
1422
|
+
const body = await response.json();
|
|
1423
|
+
if (typeof body !== "object" || body === null) return void 0;
|
|
1424
|
+
const version = body.version;
|
|
1425
|
+
return typeof version === "string" ? version : void 0;
|
|
1426
|
+
} finally {
|
|
1427
|
+
clearTimeout(timer);
|
|
1428
|
+
}
|
|
1429
|
+
} catch {
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
/** The resolved current version of one family package (probe failure tolerated). */
|
|
1434
|
+
function readInstalledVersion(resolve, name) {
|
|
1435
|
+
try {
|
|
1436
|
+
const path = resolve(name + "/package.json");
|
|
1437
|
+
const version = path === void 0 ? void 0 : readManifest(path)?.version;
|
|
1438
|
+
return typeof version === "string" ? version : "0.0.0";
|
|
1439
|
+
} catch {
|
|
1440
|
+
return "0.0.0";
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
/**
|
|
1444
|
+
* Build the update status: locate the anchor, detect the install mode, and
|
|
1445
|
+
* compare every family package against the npm registry.
|
|
1446
|
+
* @param deps - manifest resolution + registry probe seams.
|
|
1447
|
+
* @returns the status snapshot.
|
|
1448
|
+
*/
|
|
1449
|
+
async function checkUpdates(deps) {
|
|
1450
|
+
const manifestPath = deps.anchorManifestPath;
|
|
1451
|
+
if (manifestPath === void 0) return {
|
|
1452
|
+
mode: "missing",
|
|
1453
|
+
packages: [],
|
|
1454
|
+
outdated: false
|
|
1455
|
+
};
|
|
1456
|
+
const manifest = readManifest(manifestPath);
|
|
1457
|
+
if (manifest === void 0) return {
|
|
1458
|
+
mode: "missing",
|
|
1459
|
+
packages: [],
|
|
1460
|
+
outdated: false
|
|
1461
|
+
};
|
|
1462
|
+
const anchor = typeof manifest.name === "string" ? manifest.name : void 0;
|
|
1463
|
+
if (anchor === void 0) return {
|
|
1464
|
+
mode: "missing",
|
|
1465
|
+
packages: [],
|
|
1466
|
+
outdated: false
|
|
1467
|
+
};
|
|
1468
|
+
const profile = findProfile(manifestPath);
|
|
1469
|
+
const profileManifest = profile === void 0 ? void 0 : readManifest(join(profile.dir, "package.json"));
|
|
1470
|
+
const linked = profile === void 0 || isLinkedSpec((profileManifest?.dependencies)?.[anchor]);
|
|
1471
|
+
if (profile === void 0) return {
|
|
1472
|
+
mode: "link",
|
|
1473
|
+
packages: [],
|
|
1474
|
+
outdated: false
|
|
1475
|
+
};
|
|
1476
|
+
const names = [anchor, ...familyChildren(manifest)];
|
|
1477
|
+
const packages = [];
|
|
1478
|
+
let probeFailures = 0;
|
|
1479
|
+
for (const name of names) {
|
|
1480
|
+
const latest = await deps.fetchLatest(name);
|
|
1481
|
+
if (latest === void 0) probeFailures++;
|
|
1482
|
+
const current = readInstalledVersion(deps.resolve, name);
|
|
1483
|
+
packages.push({
|
|
1484
|
+
name,
|
|
1485
|
+
current,
|
|
1486
|
+
latest,
|
|
1487
|
+
outdated: latest !== void 0 && latest !== current && compareVersions(latest, current) > 0
|
|
1488
|
+
});
|
|
1489
|
+
}
|
|
1490
|
+
const error = probeFailures === names.length && names.length > 0 ? "registry-unreachable" : void 0;
|
|
1491
|
+
return {
|
|
1492
|
+
mode: linked ? "link" : "npm",
|
|
1493
|
+
profileName: profile.name,
|
|
1494
|
+
anchor,
|
|
1495
|
+
packages,
|
|
1496
|
+
outdated: packages.some((packageStatus) => packageStatus.outdated),
|
|
1497
|
+
...error !== void 0 ? { error } : {}
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1500
|
+
/** Cap on captured pnpm output (keeps error payloads bounded). */
|
|
1501
|
+
const OUTPUT_CAP = 16 * 1024;
|
|
1502
|
+
/**
|
|
1503
|
+
* Run the update: `pnpm update <packages>` inside the profile directory.
|
|
1504
|
+
* @param deps - profile dir, package list, and spawn/timeout seams.
|
|
1505
|
+
* @returns the outcome with captured output.
|
|
1506
|
+
*/
|
|
1507
|
+
function runUpdate(deps) {
|
|
1508
|
+
return new Promise((resolve) => {
|
|
1509
|
+
const child = (deps.spawnImpl ?? spawn)("pnpm", ["update", ...deps.packages], {
|
|
1510
|
+
cwd: deps.profileDir,
|
|
1511
|
+
stdio: [
|
|
1512
|
+
"ignore",
|
|
1513
|
+
"pipe",
|
|
1514
|
+
"pipe"
|
|
1515
|
+
]
|
|
1516
|
+
});
|
|
1517
|
+
let output = "";
|
|
1518
|
+
const append = (chunk) => {
|
|
1519
|
+
output += chunk.toString("utf8");
|
|
1520
|
+
if (output.length > OUTPUT_CAP) output = output.slice(output.length - OUTPUT_CAP);
|
|
1521
|
+
};
|
|
1522
|
+
child.stdout?.on("data", append);
|
|
1523
|
+
child.stderr?.on("data", append);
|
|
1524
|
+
const timer = setTimeout(() => {
|
|
1525
|
+
child.kill("SIGTERM");
|
|
1526
|
+
resolve({
|
|
1527
|
+
ok: false,
|
|
1528
|
+
exitCode: null,
|
|
1529
|
+
output,
|
|
1530
|
+
error: "update timed out; install process killed",
|
|
1531
|
+
errorCode: "timeout"
|
|
1532
|
+
});
|
|
1533
|
+
}, deps.timeoutMs ?? 10 * 6e4);
|
|
1534
|
+
child.on("error", (error) => {
|
|
1535
|
+
clearTimeout(timer);
|
|
1536
|
+
const missing = error.code === "ENOENT";
|
|
1537
|
+
resolve({
|
|
1538
|
+
ok: false,
|
|
1539
|
+
exitCode: null,
|
|
1540
|
+
output,
|
|
1541
|
+
error: missing ? "pnpm not found on PATH" : error.message,
|
|
1542
|
+
errorCode: missing ? "pnpm-missing" : void 0
|
|
1543
|
+
});
|
|
1544
|
+
});
|
|
1545
|
+
child.on("close", (code) => {
|
|
1546
|
+
clearTimeout(timer);
|
|
1547
|
+
resolve({
|
|
1548
|
+
ok: code === 0,
|
|
1549
|
+
exitCode: code,
|
|
1550
|
+
output,
|
|
1551
|
+
error: code === 0 ? void 0 : "pnpm exited with code " + String(code),
|
|
1552
|
+
...code === 0 ? {} : { errorCode: "pnpm-failed" }
|
|
1553
|
+
});
|
|
1554
|
+
});
|
|
1555
|
+
});
|
|
1556
|
+
}
|
|
1557
|
+
//#endregion
|
|
1558
|
+
//#region src/update-routes.ts
|
|
1559
|
+
/** Route paths (exact matches under /api). */
|
|
1560
|
+
const UPDATE_PATHS = {
|
|
1561
|
+
status: "/api/update/status",
|
|
1562
|
+
run: "/api/update/run"
|
|
1563
|
+
};
|
|
1564
|
+
/** One JSON response. */
|
|
1565
|
+
function writeJson(res, status, body) {
|
|
1566
|
+
const payload = JSON.stringify(body);
|
|
1567
|
+
res.writeHead(status, {
|
|
1568
|
+
"content-type": "application/json; charset=utf-8",
|
|
1569
|
+
"referrer-policy": "no-referrer"
|
|
1570
|
+
});
|
|
1571
|
+
res.end(payload);
|
|
1572
|
+
}
|
|
1573
|
+
/**
|
|
1574
|
+
* Build the /api/update route family.
|
|
1575
|
+
* @param deps - fence + check/run seams.
|
|
1576
|
+
* @returns the exact routes to register on webServer.
|
|
1577
|
+
*/
|
|
1578
|
+
function makeUpdateRoutes(deps) {
|
|
1579
|
+
const handleStatus = async (req, res) => {
|
|
1580
|
+
if (req.method !== "GET") {
|
|
1581
|
+
res.writeHead(405, { "content-type": "text/plain; charset=utf-8" });
|
|
1582
|
+
res.end("method not allowed");
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1585
|
+
if (!deps.fence(req)) {
|
|
1586
|
+
writeJson(res, 403, {
|
|
1587
|
+
ok: false,
|
|
1588
|
+
code: "forbidden"
|
|
1589
|
+
});
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1592
|
+
writeJson(res, 200, await deps.check());
|
|
1593
|
+
};
|
|
1594
|
+
const handleRun = async (req, res) => {
|
|
1595
|
+
if (req.method !== "POST") {
|
|
1596
|
+
res.writeHead(405, { "content-type": "text/plain; charset=utf-8" });
|
|
1597
|
+
res.end("method not allowed");
|
|
1598
|
+
return;
|
|
1599
|
+
}
|
|
1600
|
+
if (!deps.fence(req)) {
|
|
1601
|
+
writeJson(res, 403, {
|
|
1602
|
+
ok: false,
|
|
1603
|
+
code: "forbidden"
|
|
1604
|
+
});
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
writeJson(res, 200, await deps.run());
|
|
1608
|
+
};
|
|
1609
|
+
return [{
|
|
1610
|
+
kind: "exact",
|
|
1611
|
+
path: UPDATE_PATHS.status,
|
|
1612
|
+
handler: handleStatus
|
|
1613
|
+
}, {
|
|
1614
|
+
kind: "exact",
|
|
1615
|
+
path: UPDATE_PATHS.run,
|
|
1616
|
+
handler: handleRun
|
|
1617
|
+
}];
|
|
1618
|
+
}
|
|
1264
1619
|
//#endregion
|
|
1265
1620
|
//#region src/index.ts
|
|
1266
1621
|
/**
|
|
@@ -1375,6 +1730,39 @@ function apply(ctx, config) {
|
|
|
1375
1730
|
let disposeSweep;
|
|
1376
1731
|
const apiProxy = ctx.get("apiProxy");
|
|
1377
1732
|
if (apiProxy === void 0) console.warn("remote-web-ui: apiProxy service unavailable — the mobile data channel is disabled");
|
|
1733
|
+
const requireFromHost = createRequire(import.meta.url);
|
|
1734
|
+
const anchorManifestPath = resolveAnchorManifest((specifier) => requireFromHost.resolve(specifier));
|
|
1735
|
+
const updateRoutes = makeUpdateRoutes({
|
|
1736
|
+
fence: (request) => isTrustedApiRequest(request, []),
|
|
1737
|
+
check: () => checkUpdates({
|
|
1738
|
+
anchorManifestPath,
|
|
1739
|
+
resolve: (specifier) => {
|
|
1740
|
+
try {
|
|
1741
|
+
return requireFromHost.resolve(specifier);
|
|
1742
|
+
} catch {
|
|
1743
|
+
return;
|
|
1744
|
+
}
|
|
1745
|
+
},
|
|
1746
|
+
fetchLatest: (name) => fetchLatestVersion(name, fetch)
|
|
1747
|
+
}),
|
|
1748
|
+
run: async () => {
|
|
1749
|
+
const target = resolveUpdateTarget({ anchorManifestPath });
|
|
1750
|
+
if ("error" in target) {
|
|
1751
|
+
const code = target.error;
|
|
1752
|
+
return {
|
|
1753
|
+
ok: false,
|
|
1754
|
+
exitCode: null,
|
|
1755
|
+
output: "",
|
|
1756
|
+
error: code === "not-found" ? "dsh-web-ui aggregate not installed" : "local link install — update unavailable",
|
|
1757
|
+
errorCode: code
|
|
1758
|
+
};
|
|
1759
|
+
}
|
|
1760
|
+
return runUpdate({
|
|
1761
|
+
profileDir: target.profileDir,
|
|
1762
|
+
packages: target.packages
|
|
1763
|
+
});
|
|
1764
|
+
}
|
|
1765
|
+
});
|
|
1378
1766
|
const routes = [
|
|
1379
1767
|
...makeRoutes({
|
|
1380
1768
|
service,
|
|
@@ -1384,7 +1772,8 @@ function apply(ctx, config) {
|
|
|
1384
1772
|
...apiProxy !== void 0 ? makeMobileApiRoutes({
|
|
1385
1773
|
service,
|
|
1386
1774
|
apiProxy
|
|
1387
|
-
}) : []
|
|
1775
|
+
}) : [],
|
|
1776
|
+
...updateRoutes
|
|
1388
1777
|
];
|
|
1389
1778
|
const gate = makeGateListener(service, () => resolve().requirePairingForLan, () => resolve().enabled);
|
|
1390
1779
|
ctx.effect(() => ctx.on("api/gate", gate), "remote-web-ui: api gate");
|
|
@@ -29,7 +29,7 @@ export interface PluginSettingsCardProps {
|
|
|
29
29
|
/**
|
|
30
30
|
* Render one plugin settings card.
|
|
31
31
|
* @param props - the plugin's copy keys, its form state, and its controls.
|
|
32
|
-
* @returns the card, or nothing
|
|
32
|
+
* @returns the card, or nothing while the namespace is still loading.
|
|
33
33
|
*/
|
|
34
34
|
export declare function PluginSettingsCard(props: PluginSettingsCardProps): import("react").JSX.Element | null;
|
|
35
35
|
/** Props every field control needs regardless of its value type. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PluginSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PluginSettingsCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAGnD,wDAAwD;AACxD,MAAM,WAAW,uBAAuB;IACtC,0CAA0C;IAC1C,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,MAAM,CAAA;IACnC,uCAAuC;IACvC,QAAQ,EAAE,eAAe,CAAA;IACzB,4EAA4E;IAC5E,cAAc,EAAE,eAAe,CAAA;IAC/B,kFAAkF;IAClF,KAAK,EAAE,SAAS,CAAA;IAChB,+BAA+B;IAC/B,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,8BAA8B;IAC9B,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,6BAA6B;IAC7B,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,
|
|
1
|
+
{"version":3,"file":"PluginSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PluginSettingsCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAGnD,wDAAwD;AACxD,MAAM,WAAW,uBAAuB;IACtC,0CAA0C;IAC1C,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,MAAM,CAAA;IACnC,uCAAuC;IACvC,QAAQ,EAAE,eAAe,CAAA;IACzB,4EAA4E;IAC5E,cAAc,EAAE,eAAe,CAAA;IAC/B,kFAAkF;IAClF,KAAK,EAAE,SAAS,CAAA;IAChB,+BAA+B;IAC/B,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,8BAA8B;IAC9B,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,6BAA6B;IAC7B,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,sCAiFhE;AAED,oEAAoE;AACpE,MAAM,WAAW,UAAU;IACzB,wDAAwD;IACxD,EAAE,EAAE,MAAM,CAAA;IACV,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAA;IACZ,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAA;IACZ,sEAAsE;IACtE,UAAU,EAAE,OAAO,CAAA;IACnB,6DAA6D;IAC7D,OAAO,EAAE,OAAO,CAAA;IAChB,qCAAqC;IACrC,eAAe,EAAE,MAAM,CAAA;IACvB,kCAAkC;IAClC,UAAU,EAAE,MAAM,CAAA;IAClB,kEAAkE;IAClE,YAAY,EAAE,MAAM,CAAA;IACpB,gFAAgF;IAChF,QAAQ,EAAE,OAAO,CAAA;IACjB,wBAAwB;IACxB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9B,oEAAoE;IACpE,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED,kHAAkH;AAClH,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG;IAC7C,yEAAyE;IACzE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,+BAqCA;AAED,0CAA0C;AAC1C,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG;IAC/C,mCAAmC;IACnC,YAAY,EAAE,MAAM,CAAA;IACpB,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAA;CACjB,+BAmCA"}
|
|
@@ -12,7 +12,7 @@ import css from './settings-card.module.css';
|
|
|
12
12
|
/**
|
|
13
13
|
* Render one plugin settings card.
|
|
14
14
|
* @param props - the plugin's copy keys, its form state, and its controls.
|
|
15
|
-
* @returns the card, or nothing
|
|
15
|
+
* @returns the card, or nothing while the namespace is still loading.
|
|
16
16
|
*/
|
|
17
17
|
export function PluginSettingsCard(props) {
|
|
18
18
|
const [open, setOpen] = useState(false);
|
|
@@ -21,6 +21,15 @@ export function PluginSettingsCard(props) {
|
|
|
21
21
|
return null;
|
|
22
22
|
const title = props.t(props.titleKey);
|
|
23
23
|
const blocked = !state.dirty || state.invalid || state.saving;
|
|
24
|
+
// The namespace exists but the Host does not serve it to this client (the
|
|
25
|
+
// official settings allowlist omits third-party namespaces): show a card
|
|
26
|
+
// that explains the gap instead of vanishing, so a missing card never
|
|
27
|
+
// reads as a missing plugin.
|
|
28
|
+
if (!state.exposed) {
|
|
29
|
+
return (_jsxs("li", { className: css.card, children: [_jsxs("button", { type: "button", className: css.header, "aria-expanded": open, "aria-label": `${props.t(open ? 'settings.collapse' : 'settings.expand')}: ${title}`, onClick: () => { setOpen(!open); }, children: [_jsxs("span", { className: css.headText, children: [_jsx("span", { className: css.name, children: title }), _jsx("span", { className: css.description, children: props.t(props.descriptionKey) })] }), _jsx("span", { className: open ? css.chevronOpen : css.chevron, children: "\u25BE" })] }), open
|
|
30
|
+
? (_jsx("div", { className: css.body, children: _jsx("p", { className: css.notExposed, role: "status", children: props.t('settings.notExposed') }) }))
|
|
31
|
+
: null] }));
|
|
32
|
+
}
|
|
24
33
|
return (_jsxs("li", { className: css.card, children: [_jsxs("button", { type: "button", className: css.header, "aria-expanded": open, "aria-label": `${props.t(open ? 'settings.collapse' : 'settings.expand')}: ${title}`, onClick: () => { setOpen(!open); }, children: [_jsxs("span", { className: css.headText, children: [_jsx("span", { className: css.name, children: title }), _jsx("span", { className: css.description, children: props.t(props.descriptionKey) })] }), state.dirty ? _jsx("span", { className: css.pending, children: props.t('settings.unsaved') }) : null, _jsx("span", { className: open ? css.chevronOpen : css.chevron, children: "\u25BE" })] }), open
|
|
25
34
|
? (_jsxs("div", { className: css.body, children: [!state.writable ? _jsx("p", { className: css.readOnly, role: "status", children: props.t('settings.readOnly') }) : null, props.children, _jsxs("div", { className: css.footer, children: [state.failed ? _jsx("p", { className: css.failed, role: "status", children: props.t('settings.saveFailed') }) : null, _jsx("button", { type: "button", className: css.discard, disabled: !state.dirty || state.saving, onClick: props.onDiscard, children: props.t('settings.discard') }), _jsx("button", { type: "button", className: css.save, disabled: blocked, onClick: props.onSave, children: props.t(!state.saving ? 'settings.save' : 'settings.saving') })] })] }))
|
|
26
35
|
: null] }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RemoteEntry.d.ts","sourceRoot":"","sources":["../../../src/client/RemoteEntry.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"RemoteEntry.d.ts","sourceRoot":"","sources":["../../../src/client/RemoteEntry.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAQjF,wEAAwE;AACxE,MAAM,MAAM,gBAAgB,GAAG,YAAY,CAAC,gBAAgB,CAAC,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAA;AAcrF;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,EAAE,EAAE,gBAAgB,+BAuJvE"}
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
|
-
* The sidebar remote-control seat: the
|
|
4
|
-
* settings button
|
|
5
|
-
* token minting on open, the status SSE subscription,
|
|
6
|
-
* and renders the pure {@link RemotePanel} body.
|
|
7
|
-
* the
|
|
8
|
-
*
|
|
3
|
+
* The sidebar remote-control seat: the update trigger plus the phone-icon
|
|
4
|
+
* trigger beside the settings button, and the pairing panel modal. Owns the
|
|
5
|
+
* panel behavior — token minting on open, the status SSE subscription,
|
|
6
|
+
* stop/refresh/copy — and renders the pure {@link RemotePanel} body. The
|
|
7
|
+
* update seat (the dsh-web-ui self-update flow) rides the same footer row,
|
|
8
|
+
* rendered by {@link UpdateEntry}. Component-local state per the client
|
|
9
|
+
* stack rules: nothing here survives remounts or crosses entries.
|
|
9
10
|
*/
|
|
10
11
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
11
12
|
import { createPortal } from 'react-dom';
|
|
12
13
|
import { RemotePanel } from "./RemotePanel.js";
|
|
13
14
|
import { copyText, issuePair, stopPair } from "./pair-api.js";
|
|
14
15
|
import { PhoneIcon } from "./PhoneIcon.js";
|
|
16
|
+
import { UpdateEntry } from "./UpdateEntry.js";
|
|
15
17
|
import css from './remote.module.css';
|
|
16
18
|
/** Apply one status frame onto the current ready state. */
|
|
17
19
|
function mergeFrame(state, frame) {
|
|
@@ -154,7 +156,7 @@ export function RemoteEntry({ wide, useWorkspaces, t }) {
|
|
|
154
156
|
window.setTimeout(() => { setCopied(false); }, 1500);
|
|
155
157
|
});
|
|
156
158
|
}, [state]);
|
|
157
|
-
return (_jsxs(_Fragment, { children: [_jsx(TooltipAnchor, { wide: wide, label: t('entry.label'), onClick: openPanel }), open && createPortal((_jsxs("div", { className: css.overlay, role: "presentation", children: [_jsx("div", { className: css.mask, "aria-hidden": "true", onClick: closePanel }), _jsx(RemotePanel, { t: t, state: state, copied: copied, onClose: closePanel, onStop: handleStop, onRefresh: handleRefresh, onCopy: handleCopy, onPickAddress: handlePickAddress, onPickPublic: handlePickPublic })] })), document.body)] }));
|
|
159
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: css.entryRow, "data-rail": wide ? undefined : 'rail', children: [_jsx(UpdateEntry, { wide: wide, t: t }), _jsx(TooltipAnchor, { wide: wide, label: t('entry.label'), onClick: openPanel })] }), open && createPortal((_jsxs("div", { className: css.overlay, role: "presentation", children: [_jsx("div", { className: css.mask, "aria-hidden": "true", onClick: closePanel }), _jsx(RemotePanel, { t: t, state: state, copied: copied, onClose: closePanel, onStop: handleStop, onRefresh: handleRefresh, onCopy: handleCopy, onPickAddress: handlePickAddress, onPickPublic: handlePickPublic })] })), document.body)] }));
|
|
158
160
|
}
|
|
159
161
|
/** The trigger: an icon button matching the settings rail/row geometry. */
|
|
160
162
|
function TooltipAnchor({ wide, label, onClick }) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { TranslateNS } from '@deepseek-ai/dsh-client-ui-slots';
|
|
2
|
+
/** Entry props: the sidebar column state + the standard locale seat. */
|
|
3
|
+
export interface UpdateEntryProps {
|
|
4
|
+
/** Whether the sidebar renders wide content (false = 56px rail). */
|
|
5
|
+
wide: boolean;
|
|
6
|
+
t: TranslateNS<'remote'>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Render the update trigger and panel.
|
|
10
|
+
* @param props - column state and locale seat.
|
|
11
|
+
* @returns the entry element tree.
|
|
12
|
+
*/
|
|
13
|
+
export declare function UpdateEntry({ wide, t }: UpdateEntryProps): import("react").JSX.Element;
|
|
14
|
+
//# sourceMappingURL=UpdateEntry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UpdateEntry.d.ts","sourceRoot":"","sources":["../../../src/client/UpdateEntry.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAOnE,wEAAwE;AACxE,MAAM,WAAW,gBAAgB;IAC/B,oEAAoE;IACpE,IAAI,EAAE,OAAO,CAAA;IACb,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAA;CACzB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,gBAAgB,+BAmExD"}
|