dsh-cloudq 0.2.0 → 0.2.1
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 +124 -1
- package/lib/index.js +188 -6
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -408,6 +408,46 @@ linear-gradient(-30deg, transparent 49.5%, #2c2c31 49.5%, #2c2c31 50.5%, transpa
|
|
|
408
408
|
overflow: hidden;
|
|
409
409
|
text-overflow: ellipsis;
|
|
410
410
|
}
|
|
411
|
+
/* Outdated-plugin badge appended after the sidebar button label. */
|
|
412
|
+
.dsh-cloudq-version-badge {
|
|
413
|
+
position: relative;
|
|
414
|
+
flex: none;
|
|
415
|
+
display: inline-flex;
|
|
416
|
+
align-items: center;
|
|
417
|
+
justify-content: center;
|
|
418
|
+
width: 16px;
|
|
419
|
+
height: 16px;
|
|
420
|
+
margin-left: 6px;
|
|
421
|
+
border-radius: 50%;
|
|
422
|
+
background: var(--dsw-alias-state-warning, #d18400);
|
|
423
|
+
color: #fff;
|
|
424
|
+
font-size: 11px;
|
|
425
|
+
font-weight: 700;
|
|
426
|
+
line-height: 16px;
|
|
427
|
+
cursor: pointer;
|
|
428
|
+
}
|
|
429
|
+
.dsh-cloudq-version-badge.is-busy {
|
|
430
|
+
opacity: .7;
|
|
431
|
+
cursor: default;
|
|
432
|
+
}
|
|
433
|
+
.dsh-cloudq-version-badge__tip {
|
|
434
|
+
display: none;
|
|
435
|
+
position: fixed;
|
|
436
|
+
z-index: 1000;
|
|
437
|
+
padding: 6px 10px;
|
|
438
|
+
border-radius: 6px;
|
|
439
|
+
background: rgba(20, 24, 31, .92);
|
|
440
|
+
color: #fff;
|
|
441
|
+
font-size: 12px;
|
|
442
|
+
font-weight: 400;
|
|
443
|
+
line-height: 18px;
|
|
444
|
+
white-space: nowrap;
|
|
445
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, .18);
|
|
446
|
+
}
|
|
447
|
+
.dsh-cloudq-version-badge:hover .dsh-cloudq-version-badge__tip,
|
|
448
|
+
.dsh-cloudq-version-badge:focus .dsh-cloudq-version-badge__tip {
|
|
449
|
+
display: block;
|
|
450
|
+
}
|
|
411
451
|
/* Persisted CloudQ marker shown on rows whose session id is in the registry. */
|
|
412
452
|
.dsh-cloudq-session-badge {
|
|
413
453
|
flex: none;
|
|
@@ -773,6 +813,75 @@ linear-gradient(-30deg, transparent 49.5%, #2c2c31 49.5%, #2c2c31 50.5%, transpa
|
|
|
773
813
|
if (button) button.remove();
|
|
774
814
|
};
|
|
775
815
|
}
|
|
816
|
+
const API_VERSION = "/api/dsh-cloudq/version";
|
|
817
|
+
const API_UPDATE = "/api/dsh-cloudq/update";
|
|
818
|
+
const API_RESTART = "/api/dsh-cloudq/restart";
|
|
819
|
+
let cloudqOutdatedInfo = null;
|
|
820
|
+
let cloudqSelfUpdateRunning = false;
|
|
821
|
+
function positionVersionTip(badge, tip) {
|
|
822
|
+
const rect = badge.getBoundingClientRect();
|
|
823
|
+
tip.style.left = `${Math.max(8, rect.left)}px`;
|
|
824
|
+
tip.style.top = `${rect.bottom + 6}px`;
|
|
825
|
+
tip.style.bottom = "auto";
|
|
826
|
+
}
|
|
827
|
+
/** Append the "!" badge after the sidebar button label (retries while the
|
|
828
|
+
* button is not mounted yet). The button node is reused across sidebar
|
|
829
|
+
* re-mounts, so a child badge survives them. */
|
|
830
|
+
function attachVersionBadge(attempt = 0) {
|
|
831
|
+
if (!cloudqOutdatedInfo) return;
|
|
832
|
+
const button = document.getElementById("dsh-cloudq-sidebar-entry");
|
|
833
|
+
if (!button) {
|
|
834
|
+
if (attempt < 20) window.setTimeout(() => attachVersionBadge(attempt + 1), 500);
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
if (button.querySelector(".dsh-cloudq-version-badge")) return;
|
|
838
|
+
const badge = document.createElement("span");
|
|
839
|
+
badge.className = "dsh-cloudq-version-badge";
|
|
840
|
+
badge.dataset.testid = "cloudq-version-badge";
|
|
841
|
+
badge.textContent = "!";
|
|
842
|
+
badge.setAttribute("role", "button");
|
|
843
|
+
badge.setAttribute("aria-label", `CloudQ 插件有新版本 ${cloudqOutdatedInfo.latest},点击更新`);
|
|
844
|
+
const tip = document.createElement("span");
|
|
845
|
+
tip.className = "dsh-cloudq-version-badge__tip";
|
|
846
|
+
tip.textContent = `当前版本 ${cloudqOutdatedInfo.current} 不是最新版本 ${cloudqOutdatedInfo.latest},点击自动更新`;
|
|
847
|
+
badge.appendChild(tip);
|
|
848
|
+
badge.addEventListener("mouseenter", () => positionVersionTip(badge, tip));
|
|
849
|
+
badge.addEventListener("click", (event) => {
|
|
850
|
+
event.preventDefault();
|
|
851
|
+
event.stopPropagation();
|
|
852
|
+
runCloudqSelfUpdate(badge, tip);
|
|
853
|
+
});
|
|
854
|
+
button.appendChild(badge);
|
|
855
|
+
}
|
|
856
|
+
/** Badge click flow: update → restart host → wait for it → reload page. */
|
|
857
|
+
async function runCloudqSelfUpdate(badge, tip) {
|
|
858
|
+
if (cloudqSelfUpdateRunning) return;
|
|
859
|
+
cloudqSelfUpdateRunning = true;
|
|
860
|
+
badge.classList.add("is-busy");
|
|
861
|
+
tip.textContent = "正在更新到最新版本…";
|
|
862
|
+
try {
|
|
863
|
+
await cloudqRequest(API_UPDATE, { method: "POST" }, 15e4);
|
|
864
|
+
} catch (error) {
|
|
865
|
+
tip.textContent = `更新失败:${error.message}`;
|
|
866
|
+
badge.classList.remove("is-busy");
|
|
867
|
+
cloudqSelfUpdateRunning = false;
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
tip.textContent = "更新完成,正在重启 DSH 服务…";
|
|
871
|
+
try {
|
|
872
|
+
await cloudqRequest(API_RESTART, { method: "POST" }, 5e3);
|
|
873
|
+
} catch {}
|
|
874
|
+
const sleep = (ms) => new Promise((resolvePromise) => window.setTimeout(resolvePromise, ms));
|
|
875
|
+
const deadline = Date.now() + 6e4;
|
|
876
|
+
while (Date.now() < deadline) try {
|
|
877
|
+
await cloudqRequest(API_VERSION, void 0, 3e3);
|
|
878
|
+
window.location.reload();
|
|
879
|
+
return;
|
|
880
|
+
} catch {
|
|
881
|
+
await sleep(1e3);
|
|
882
|
+
}
|
|
883
|
+
tip.textContent = "服务重启超时,请手动重启 DSH 后刷新页面。";
|
|
884
|
+
}
|
|
776
885
|
/** Build the exact visible row order from Host workspace membership. */
|
|
777
886
|
function orderedVisibleSessions(snapshot, workspaces, sortByUpdated) {
|
|
778
887
|
const archived = new Set(workspaces?.archivedSessionIds ?? []);
|
|
@@ -3276,7 +3385,7 @@ display: none;
|
|
|
3276
3385
|
});
|
|
3277
3386
|
} catch (error) {
|
|
3278
3387
|
setValidated(false);
|
|
3279
|
-
const invalid = error instanceof CloudQApiError && error.code !== "network-error" && error.code !== "invalid-response";
|
|
3388
|
+
const invalid = error instanceof CloudQApiError && error.code !== "network-error" && error.code !== "invalid-response" && error.code !== "script-launch-failed";
|
|
3280
3389
|
setFeedback({
|
|
3281
3390
|
kind: "error",
|
|
3282
3391
|
text: invalid ? "AKSK 无效,请检查后重新配置。" : error.message
|
|
@@ -3696,6 +3805,20 @@ display: none;
|
|
|
3696
3805
|
}
|
|
3697
3806
|
});
|
|
3698
3807
|
}, "dsh-cloudq: refresh artifacts on turn completion");
|
|
3808
|
+
ctx.effect(() => {
|
|
3809
|
+
let disposed = false;
|
|
3810
|
+
cloudqRequest(API_VERSION).then((data) => {
|
|
3811
|
+
if (disposed || data?.outdated !== true || typeof data?.latest !== "string") return;
|
|
3812
|
+
cloudqOutdatedInfo = {
|
|
3813
|
+
current: String(data.current ?? ""),
|
|
3814
|
+
latest: data.latest
|
|
3815
|
+
};
|
|
3816
|
+
attachVersionBadge();
|
|
3817
|
+
}).catch(() => {});
|
|
3818
|
+
return () => {
|
|
3819
|
+
disposed = true;
|
|
3820
|
+
};
|
|
3821
|
+
}, "dsh-cloudq: version check");
|
|
3699
3822
|
ctx.effect(() => {
|
|
3700
3823
|
const markIfCloudqClaim = () => {
|
|
3701
3824
|
const textarea = document.querySelector("textarea[class*=input]");
|
package/lib/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
3
|
import { existsSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { get } from "node:https";
|
|
3
5
|
import { dirname, join, resolve } from "node:path";
|
|
4
6
|
import { fileURLToPath } from "node:url";
|
|
5
7
|
import Schema from "@deepseek-ai/schemastery";
|
|
6
8
|
import { Buffer as Buffer$1 } from "node:buffer";
|
|
7
9
|
import yaml from "js-yaml";
|
|
8
|
-
import { spawn } from "node:child_process";
|
|
9
10
|
//#region src/http.js
|
|
10
11
|
/** Maximum accepted JSON request body size. */
|
|
11
12
|
const MAX_JSON_BODY_BYTES = 65536;
|
|
@@ -147,13 +148,13 @@ function readJsonBody(request, { maxBytes = MAX_JSON_BODY_BYTES } = {}) {
|
|
|
147
148
|
//#region src/plugin-manager.js
|
|
148
149
|
/** Host-side management of optional profile bundle entries. */
|
|
149
150
|
const PROTECTED_BUNDLES = /* @__PURE__ */ new Set(["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app"]);
|
|
150
|
-
function profileDirectory(baseUrl) {
|
|
151
|
+
function profileDirectory$1(baseUrl) {
|
|
151
152
|
const url = new URL(".", baseUrl);
|
|
152
153
|
if (url.protocol !== "file:") throw new Error("The active DSH profile URL must use the file protocol.");
|
|
153
154
|
return fileURLToPath(url);
|
|
154
155
|
}
|
|
155
156
|
function patchPath(baseUrl) {
|
|
156
|
-
return resolve(profileDirectory(baseUrl), "cordis.patch.yml");
|
|
157
|
+
return resolve(profileDirectory$1(baseUrl), "cordis.patch.yml");
|
|
157
158
|
}
|
|
158
159
|
function parsePatchList(content) {
|
|
159
160
|
if (!content.trim()) return [];
|
|
@@ -226,7 +227,7 @@ function writePatchAtomically(path, content, originalSnapshot) {
|
|
|
226
227
|
* @returns {Array<{id: string, name: string, bundle: string, disabled: boolean, self: boolean}>}
|
|
227
228
|
*/
|
|
228
229
|
function listPlugins(baseUrl) {
|
|
229
|
-
const profileDir = profileDirectory(baseUrl);
|
|
230
|
+
const profileDir = profileDirectory$1(baseUrl);
|
|
230
231
|
const userPatchPath = patchPath(baseUrl);
|
|
231
232
|
const overrides = (existsSync(userPatchPath) ? parsePatchList(readFileSync(userPatchPath, "utf8")) : []).filter((entry) => entry && typeof entry === "object" && typeof entry.id === "string");
|
|
232
233
|
const plugins = [];
|
|
@@ -289,6 +290,24 @@ function setPluginDisabled(baseUrl, id, disabled) {
|
|
|
289
290
|
//#endregion
|
|
290
291
|
//#region src/script-runner.js
|
|
291
292
|
const MAX_SCRIPT_OUTPUT_BYTES = 1048576;
|
|
293
|
+
let resolvedPythonCommand = null;
|
|
294
|
+
function pythonCommand() {
|
|
295
|
+
if (resolvedPythonCommand) return resolvedPythonCommand;
|
|
296
|
+
for (const candidate of [
|
|
297
|
+
"python3",
|
|
298
|
+
"python",
|
|
299
|
+
"py"
|
|
300
|
+
]) try {
|
|
301
|
+
if (spawnSync(candidate, ["--version"], {
|
|
302
|
+
stdio: "ignore",
|
|
303
|
+
timeout: 5e3
|
|
304
|
+
}).status === 0) {
|
|
305
|
+
resolvedPythonCommand = candidate;
|
|
306
|
+
return candidate;
|
|
307
|
+
}
|
|
308
|
+
} catch {}
|
|
309
|
+
return "python3";
|
|
310
|
+
}
|
|
292
311
|
function safeCode(value) {
|
|
293
312
|
return typeof value === "string" && /^[a-zA-Z0-9._-]{1,80}$/.test(value) ? value : "script-failed";
|
|
294
313
|
}
|
|
@@ -307,7 +326,8 @@ function redact(value, sensitiveValues) {
|
|
|
307
326
|
*/
|
|
308
327
|
function runScript$1(scriptsDirectory, scriptName, args, { timeoutMs = 3e4, jsonOnly = true, stdin, sensitiveValues = [], spawnProcess = spawn } = {}) {
|
|
309
328
|
return new Promise((resolveRun, rejectRun) => {
|
|
310
|
-
const
|
|
329
|
+
const script = resolve(scriptsDirectory, scriptName);
|
|
330
|
+
const child = spawnProcess(pythonCommand(), [script, ...args], {
|
|
311
331
|
stdio: [
|
|
312
332
|
stdin === void 0 ? "ignore" : "pipe",
|
|
313
333
|
"pipe",
|
|
@@ -346,7 +366,7 @@ function runScript$1(scriptsDirectory, scriptName, args, { timeoutMs = 3e4, json
|
|
|
346
366
|
stderr = collect(stderr, chunk);
|
|
347
367
|
});
|
|
348
368
|
child.on("error", () => {
|
|
349
|
-
rejectOnce(new HttpError(502, "script-launch-failed", "
|
|
369
|
+
rejectOnce(new HttpError(502, "script-launch-failed", "无法启动 Python 运行环境,请先安装 Python 3 后重试。"));
|
|
350
370
|
});
|
|
351
371
|
child.on("close", (code) => {
|
|
352
372
|
if (settled) return;
|
|
@@ -405,6 +425,121 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
405
425
|
function skillDirectory() {
|
|
406
426
|
return resolve(__dirname, "../skills/cloudq");
|
|
407
427
|
}
|
|
428
|
+
/** This plugin's own version, read from the installed package manifest. */
|
|
429
|
+
const PACKAGE_VERSION = (() => {
|
|
430
|
+
try {
|
|
431
|
+
return JSON.parse(readFileSync(resolve(__dirname, "../package.json"), "utf8"))?.version ?? "0.0.0";
|
|
432
|
+
} catch {
|
|
433
|
+
return "0.0.0";
|
|
434
|
+
}
|
|
435
|
+
})();
|
|
436
|
+
/**
|
|
437
|
+
* Profile directory holding this plugin (`<profile>/node_modules/dsh-cloudq`).
|
|
438
|
+
* Both src/ (dev) and lib/ (packed) sit one level under the package root.
|
|
439
|
+
*/
|
|
440
|
+
function profileDirectory() {
|
|
441
|
+
return resolve(__dirname, "../../..");
|
|
442
|
+
}
|
|
443
|
+
/** Fetch the latest published version from the npm registry (best effort). */
|
|
444
|
+
function fetchLatestPackageVersion() {
|
|
445
|
+
return new Promise((resolvePromise) => {
|
|
446
|
+
const request = get("https://registry.npmjs.org/dsh-cloudq/latest", { timeout: 8e3 }, (res) => {
|
|
447
|
+
if (res.statusCode !== 200) {
|
|
448
|
+
res.resume();
|
|
449
|
+
resolvePromise(null);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
let body = "";
|
|
453
|
+
res.on("data", (chunk) => {
|
|
454
|
+
body += chunk;
|
|
455
|
+
if (body.length > 65536) request.destroy();
|
|
456
|
+
});
|
|
457
|
+
res.on("end", () => {
|
|
458
|
+
try {
|
|
459
|
+
resolvePromise(JSON.parse(body)?.version ?? null);
|
|
460
|
+
} catch {
|
|
461
|
+
resolvePromise(null);
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
|
+
request.on("error", () => resolvePromise(null));
|
|
466
|
+
request.on("timeout", () => {
|
|
467
|
+
request.destroy();
|
|
468
|
+
resolvePromise(null);
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
let latestVersionCache = {
|
|
473
|
+
at: 0,
|
|
474
|
+
version: null
|
|
475
|
+
};
|
|
476
|
+
async function latestPackageVersion() {
|
|
477
|
+
if (latestVersionCache.version && Date.now() - latestVersionCache.at < 6e5) return latestVersionCache.version;
|
|
478
|
+
const version = await fetchLatestPackageVersion();
|
|
479
|
+
if (version) latestVersionCache = {
|
|
480
|
+
at: Date.now(),
|
|
481
|
+
version
|
|
482
|
+
};
|
|
483
|
+
return version;
|
|
484
|
+
}
|
|
485
|
+
/** Semver-ish compare: is `latest` strictly newer than `current`? */
|
|
486
|
+
function isNewerVersion(latest, current) {
|
|
487
|
+
const parse = (value) => String(value).split(".").map((part) => parseInt(part, 10) || 0);
|
|
488
|
+
const next = parse(latest);
|
|
489
|
+
const now = parse(current);
|
|
490
|
+
for (let index = 0; index < 3; index += 1) if (next[index] !== now[index]) return next[index] > now[index];
|
|
491
|
+
return false;
|
|
492
|
+
}
|
|
493
|
+
const UPDATE_TIMEOUT_MS = 12e4;
|
|
494
|
+
/** Install the latest published plugin version into the active profile. */
|
|
495
|
+
function runProfileUpdate() {
|
|
496
|
+
return new Promise((resolveRun, rejectRun) => {
|
|
497
|
+
const child = spawn("pnpm", [
|
|
498
|
+
"add",
|
|
499
|
+
"dsh-cloudq@latest",
|
|
500
|
+
"--registry=https://registry.npmjs.org/"
|
|
501
|
+
], { cwd: profileDirectory() });
|
|
502
|
+
let output = "";
|
|
503
|
+
const collect = (chunk) => {
|
|
504
|
+
output += chunk;
|
|
505
|
+
if (output.length > 65536) child.kill();
|
|
506
|
+
};
|
|
507
|
+
child.stdout.on("data", collect);
|
|
508
|
+
child.stderr.on("data", collect);
|
|
509
|
+
const timer = setTimeout(() => {
|
|
510
|
+
child.kill();
|
|
511
|
+
rejectRun(new HttpError(504, "update-timeout", "更新超时,请检查网络后重试。"));
|
|
512
|
+
}, UPDATE_TIMEOUT_MS);
|
|
513
|
+
child.on("error", () => {
|
|
514
|
+
clearTimeout(timer);
|
|
515
|
+
rejectRun(new HttpError(502, "update-failed", "无法启动 pnpm,请在终端手动执行:dsh plugin --profile web add dsh-cloudq"));
|
|
516
|
+
});
|
|
517
|
+
child.on("close", (code) => {
|
|
518
|
+
clearTimeout(timer);
|
|
519
|
+
if (code === 0) resolveRun();
|
|
520
|
+
else rejectRun(new HttpError(502, "update-failed", `更新失败:${output.trim().slice(-200) || "pnpm 执行异常"}`));
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Restart the DSH host after an update. A detached watcher respawns the same
|
|
526
|
+
* command line the moment this process exits; there is no supervisor, so the
|
|
527
|
+
* plugin exits itself once the watcher is armed.
|
|
528
|
+
*/
|
|
529
|
+
function scheduleSelfRestart() {
|
|
530
|
+
if (process.platform === "win32") throw new HttpError(501, "restart-unsupported", "当前系统不支持自动重启,请手动重启 DSH 服务。");
|
|
531
|
+
const quote = (value) => `'${String(value).replaceAll("'", "'\\''")}'`;
|
|
532
|
+
const command = [
|
|
533
|
+
`while kill -0 ${process.pid} 2>/dev/null; do sleep 0.5; done`,
|
|
534
|
+
"sleep 1",
|
|
535
|
+
`cd ${quote(process.cwd())} && nohup ${quote(process.argv[0])} ${process.argv.slice(1).map(quote).join(" ")} >> /tmp/dsh-cloudq-restart.log 2>&1 &`
|
|
536
|
+
].join("; ");
|
|
537
|
+
spawn("/bin/sh", ["-c", command], {
|
|
538
|
+
detached: true,
|
|
539
|
+
stdio: "ignore"
|
|
540
|
+
}).unref();
|
|
541
|
+
setTimeout(() => process.exit(0), 300).unref();
|
|
542
|
+
}
|
|
408
543
|
/** Raw SKILL.md body. */
|
|
409
544
|
function rawSkillContent() {
|
|
410
545
|
return readFileSync(resolve(skillDirectory(), "SKILL.md"), "utf8");
|
|
@@ -676,6 +811,53 @@ function apply(ctx) {
|
|
|
676
811
|
}
|
|
677
812
|
}
|
|
678
813
|
}));
|
|
814
|
+
disposers.push(ctx.webServer.register({
|
|
815
|
+
kind: "exact",
|
|
816
|
+
path: "/api/dsh-cloudq/version",
|
|
817
|
+
handler: async (request, response) => {
|
|
818
|
+
try {
|
|
819
|
+
assertSafeRequest(request, "GET");
|
|
820
|
+
const latest = await latestPackageVersion();
|
|
821
|
+
sendJson(response, 200, {
|
|
822
|
+
ok: true,
|
|
823
|
+
current: PACKAGE_VERSION,
|
|
824
|
+
latest,
|
|
825
|
+
outdated: latest !== null && isNewerVersion(latest, PACKAGE_VERSION)
|
|
826
|
+
});
|
|
827
|
+
} catch (error) {
|
|
828
|
+
sendError(response, error);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}));
|
|
832
|
+
disposers.push(ctx.webServer.register({
|
|
833
|
+
kind: "exact",
|
|
834
|
+
path: "/api/dsh-cloudq/update",
|
|
835
|
+
handler: async (request, response) => {
|
|
836
|
+
try {
|
|
837
|
+
assertSafeRequest(request, "POST");
|
|
838
|
+
await runProfileUpdate();
|
|
839
|
+
sendJson(response, 200, { ok: true });
|
|
840
|
+
} catch (error) {
|
|
841
|
+
sendError(response, error);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}));
|
|
845
|
+
disposers.push(ctx.webServer.register({
|
|
846
|
+
kind: "exact",
|
|
847
|
+
path: "/api/dsh-cloudq/restart",
|
|
848
|
+
handler: async (request, response) => {
|
|
849
|
+
try {
|
|
850
|
+
assertSafeRequest(request, "POST");
|
|
851
|
+
sendJson(response, 200, {
|
|
852
|
+
ok: true,
|
|
853
|
+
restarting: true
|
|
854
|
+
});
|
|
855
|
+
scheduleSelfRestart();
|
|
856
|
+
} catch (error) {
|
|
857
|
+
sendError(response, error);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}));
|
|
679
861
|
disposers.push(ctx.webServer.register({
|
|
680
862
|
kind: "exact",
|
|
681
863
|
path: "/api/dsh-cloudq/credential/test",
|