@co0ontty/wand 1.21.10 → 1.21.12
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/dist/server.js +41 -0
- package/dist/structured-session-manager.js +4 -0
- package/dist/web-ui/content/scripts.js +394 -76
- package/dist/web-ui/content/styles.css +564 -0
- package/dist/web-ui/content/vendor/qrcode/qrcode.bundle.js +9 -0
- package/dist/web-ui/index.js +1 -0
- package/package.json +3 -2
|
@@ -1233,6 +1233,13 @@
|
|
|
1233
1233
|
'<p id="login-error" class="error-message hidden" role="alert"></p>' +
|
|
1234
1234
|
'</div>' +
|
|
1235
1235
|
'<button id="login-button" class="btn btn-primary btn-block">进入控制台</button>' +
|
|
1236
|
+
(hasNativeSwitchServer() ?
|
|
1237
|
+
'<button id="login-switch-server-button" class="btn btn-ghost btn-block login-switch-server" type="button">' +
|
|
1238
|
+
'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="8" rx="2"/><rect x="2" y="13" width="20" height="8" rx="2"/><line x1="6" y1="7" x2="6.01" y2="7"/><line x1="6" y1="17" x2="6.01" y2="17"/></svg>' +
|
|
1239
|
+
'<span>切换服务器</span>' +
|
|
1240
|
+
'</button>'
|
|
1241
|
+
: ''
|
|
1242
|
+
) +
|
|
1236
1243
|
'</div>' +
|
|
1237
1244
|
'</div>' +
|
|
1238
1245
|
'</div>';
|
|
@@ -1300,6 +1307,13 @@
|
|
|
1300
1307
|
'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>' +
|
|
1301
1308
|
'<span>安装</span>' +
|
|
1302
1309
|
'</button>' +
|
|
1310
|
+
(hasNativeSwitchServer() ?
|
|
1311
|
+
'<button id="switch-server-button" class="btn btn-ghost btn-sm sidebar-switch-server" type="button" title="切换服务器">' +
|
|
1312
|
+
'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="8" rx="2"/><rect x="2" y="13" width="20" height="8" rx="2"/><line x1="6" y1="7" x2="6.01" y2="7"/><line x1="6" y1="17" x2="6.01" y2="17"/></svg>' +
|
|
1313
|
+
'<span>切换</span>' +
|
|
1314
|
+
'</button>'
|
|
1315
|
+
: ''
|
|
1316
|
+
) +
|
|
1303
1317
|
'<button id="logout-button" class="btn btn-ghost btn-sm sidebar-logout" type="button" title="退出登录">' +
|
|
1304
1318
|
'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>' +
|
|
1305
1319
|
'<span>退出</span>' +
|
|
@@ -1338,6 +1352,7 @@
|
|
|
1338
1352
|
'<button class="topbar-more-item" data-action="settings" type="button" role="menuitem"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg><span>设置</span></button>' +
|
|
1339
1353
|
'<button class="topbar-more-item" data-action="refresh" type="button" role="menuitem"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg><span>刷新</span></button>' +
|
|
1340
1354
|
'<button class="topbar-more-item' + (state.showInstallPrompt && state.deferredPrompt ? '' : ' hidden') + '" id="topbar-install-item" data-action="install" type="button" role="menuitem"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg><span>安装应用</span></button>' +
|
|
1355
|
+
(hasNativeSwitchServer() ? '<button class="topbar-more-item" data-action="switch-server" type="button" role="menuitem"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="8" rx="2"/><rect x="2" y="13" width="20" height="8" rx="2"/><line x1="6" y1="7" x2="6.01" y2="7"/><line x1="6" y1="17" x2="6.01" y2="17"/></svg><span>切换服务器</span></button>' : '') +
|
|
1341
1356
|
'<button class="topbar-more-item topbar-more-item-danger" data-action="logout" type="button" role="menuitem"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg><span>退出</span></button>' +
|
|
1342
1357
|
'</div>' +
|
|
1343
1358
|
'</div>' +
|
|
@@ -1982,6 +1997,13 @@
|
|
|
1982
1997
|
'<code id="android-connect-code" class="settings-connect-url-text">-</code>' +
|
|
1983
1998
|
'<button id="copy-connect-code-button" class="btn btn-secondary btn-sm" type="button" title="复制连接码">复制</button>' +
|
|
1984
1999
|
'</div>' +
|
|
2000
|
+
'<div class="settings-connect-qr-box">' +
|
|
2001
|
+
'<div class="settings-connect-qr-wrap" id="android-connect-qr-wrap" title="点击放大">' +
|
|
2002
|
+
'<canvas id="android-connect-qr" width="180" height="180"></canvas>' +
|
|
2003
|
+
'<div class="settings-connect-qr-empty" id="android-connect-qr-empty">生成中…</div>' +
|
|
2004
|
+
'</div>' +
|
|
2005
|
+
'<p class="settings-connect-qr-hint">用 Wand App 扫一扫,即可一键填入服务器地址与连接码。</p>' +
|
|
2006
|
+
'</div>' +
|
|
1985
2007
|
'</div>' +
|
|
1986
2008
|
'</div>' +
|
|
1987
2009
|
|
|
@@ -2147,10 +2169,13 @@
|
|
|
2147
2169
|
'<label class="settings-toggle-title" for="cfg-inherit-env">继承环境变量</label>' +
|
|
2148
2170
|
'<span class="settings-toggle-desc">启动 PTY / 结构化子进程时,把当前服务进程的环境变量传给 claude / codex。关闭后子进程仅获得最小可用环境(PATH/HOME/SHELL/LANG/TERM 等),可用于隔离 API key 等敏感凭据。</span>' +
|
|
2149
2171
|
'</div>' +
|
|
2150
|
-
'<
|
|
2151
|
-
'<
|
|
2152
|
-
'<
|
|
2153
|
-
|
|
2172
|
+
'<div class="settings-toggle-aside">' +
|
|
2173
|
+
'<button type="button" id="cfg-view-env-btn" class="btn btn-secondary btn-sm" title="查看实际会注入到子进程的环境变量">查看</button>' +
|
|
2174
|
+
'<label class="settings-switch">' +
|
|
2175
|
+
'<input id="cfg-inherit-env" type="checkbox" class="switch-toggle" />' +
|
|
2176
|
+
'<span class="switch-slider"></span>' +
|
|
2177
|
+
'</label>' +
|
|
2178
|
+
'</div>' +
|
|
2154
2179
|
'</div>' +
|
|
2155
2180
|
'<div class="field">' +
|
|
2156
2181
|
'<label class="field-label" for="cfg-default-model">默认模型</label>' +
|
|
@@ -3809,6 +3834,8 @@
|
|
|
3809
3834
|
var loginButton = document.getElementById("login-button");
|
|
3810
3835
|
if (loginButton) {
|
|
3811
3836
|
loginButton.addEventListener("click", login);
|
|
3837
|
+
var loginSwitchServerBtn = document.getElementById("login-switch-server-button");
|
|
3838
|
+
if (loginSwitchServerBtn) loginSwitchServerBtn.addEventListener("click", switchServer);
|
|
3812
3839
|
var passwordEl = document.getElementById("password");
|
|
3813
3840
|
var togglePasswordButton = document.getElementById("toggle-password-button");
|
|
3814
3841
|
if (togglePasswordButton && passwordEl) {
|
|
@@ -3971,6 +3998,8 @@
|
|
|
3971
3998
|
});
|
|
3972
3999
|
var logoutBtn = document.getElementById("logout-button");
|
|
3973
4000
|
if (logoutBtn) logoutBtn.addEventListener("click", logout);
|
|
4001
|
+
var switchServerBtn = document.getElementById("switch-server-button");
|
|
4002
|
+
if (switchServerBtn) switchServerBtn.addEventListener("click", switchServer);
|
|
3974
4003
|
var settingsBtn = document.getElementById("settings-button");
|
|
3975
4004
|
if (settingsBtn) settingsBtn.addEventListener("click", openSettingsModal);
|
|
3976
4005
|
var closeSettingsBtn = document.getElementById("close-settings-button");
|
|
@@ -3994,6 +4023,8 @@
|
|
|
3994
4023
|
if (saveConfigBtn) saveConfigBtn.addEventListener("click", saveConfigSettings);
|
|
3995
4024
|
var defaultModelRefreshBtn = document.getElementById("cfg-default-model-refresh");
|
|
3996
4025
|
if (defaultModelRefreshBtn) defaultModelRefreshBtn.addEventListener("click", refreshAvailableModels);
|
|
4026
|
+
var viewEnvBtn = document.getElementById("cfg-view-env-btn");
|
|
4027
|
+
if (viewEnvBtn) viewEnvBtn.addEventListener("click", openEnvPreviewModal);
|
|
3997
4028
|
var saveDisplayBtn = document.getElementById("save-display-button");
|
|
3998
4029
|
if (saveDisplayBtn) saveDisplayBtn.addEventListener("click", saveDisplaySettings);
|
|
3999
4030
|
// App icon picker (APK only)
|
|
@@ -4408,6 +4439,9 @@
|
|
|
4408
4439
|
case "logout":
|
|
4409
4440
|
logout();
|
|
4410
4441
|
break;
|
|
4442
|
+
case "switch-server":
|
|
4443
|
+
switchServer();
|
|
4444
|
+
break;
|
|
4411
4445
|
}
|
|
4412
4446
|
});
|
|
4413
4447
|
// Close on outside click
|
|
@@ -5918,6 +5952,15 @@
|
|
|
5918
5952
|
});
|
|
5919
5953
|
}
|
|
5920
5954
|
|
|
5955
|
+
function hasNativeSwitchServer() {
|
|
5956
|
+
return typeof WandNative !== "undefined" && typeof WandNative.switchServer === "function";
|
|
5957
|
+
}
|
|
5958
|
+
|
|
5959
|
+
function switchServer() {
|
|
5960
|
+
if (!hasNativeSwitchServer()) return;
|
|
5961
|
+
try { WandNative.switchServer(); } catch (e) {}
|
|
5962
|
+
}
|
|
5963
|
+
|
|
5921
5964
|
function logout() {
|
|
5922
5965
|
fetch("/api/logout", { method: "POST", credentials: "same-origin" }).catch(function() {});
|
|
5923
5966
|
stopPolling();
|
|
@@ -5964,31 +6007,14 @@
|
|
|
5964
6007
|
}
|
|
5965
6008
|
|
|
5966
6009
|
function getComposerPlaceholder(session, terminalInteractive) {
|
|
5967
|
-
|
|
5968
|
-
|
|
6010
|
+
// Keep placeholders short so they don't wrap on portrait mobile screens.
|
|
6011
|
+
// Only show informative state hints; drop the redundant "send to X" labels.
|
|
6012
|
+
if (terminalInteractive) return "终端交互中";
|
|
6013
|
+
if (session && session.status !== "running") {
|
|
6014
|
+
if (canAutoResumeSession(session)) return "";
|
|
6015
|
+
return "会话已结束";
|
|
5969
6016
|
}
|
|
5970
|
-
|
|
5971
|
-
return session.provider === "codex"
|
|
5972
|
-
? "向 Codex 发送消息;chat 为结构化对话视图"
|
|
5973
|
-
: "向 Claude 发送消息;chat 为结构化对话视图";
|
|
5974
|
-
}
|
|
5975
|
-
if (session && session.provider === "codex") {
|
|
5976
|
-
if (session.status !== "running") {
|
|
5977
|
-
return "Codex 会话已结束,无法继续发送";
|
|
5978
|
-
}
|
|
5979
|
-
return state.currentView === "terminal"
|
|
5980
|
-
? "向 Codex 发送输入;terminal 为原始 TUI 输出"
|
|
5981
|
-
: "向 Codex 发送输入;chat 为解析后的阅读视图";
|
|
5982
|
-
}
|
|
5983
|
-
if (session && !isStructuredSession(session) && session.status !== "running") {
|
|
5984
|
-
if (canAutoResumeSession(session)) {
|
|
5985
|
-
return "输入消息...";
|
|
5986
|
-
}
|
|
5987
|
-
return "会话已结束,无法继续发送";
|
|
5988
|
-
}
|
|
5989
|
-
return session && isStructuredSession(session) && session.structuredState && session.structuredState.inFlight
|
|
5990
|
-
? "思考中 · 发送新消息将中断当前回复"
|
|
5991
|
-
: "输入消息...";
|
|
6017
|
+
return "";
|
|
5992
6018
|
}
|
|
5993
6019
|
|
|
5994
6020
|
function getToolModeHint(tool, mode) {
|
|
@@ -6169,6 +6195,143 @@
|
|
|
6169
6195
|
});
|
|
6170
6196
|
}
|
|
6171
6197
|
|
|
6198
|
+
// ── Environment-variable preview modal ──
|
|
6199
|
+
// Lazily creates a modal showing the exact env vars wand will inject
|
|
6200
|
+
// into PTY / structured child processes (mirrors buildChildEnv()).
|
|
6201
|
+
function openEnvPreviewModal() {
|
|
6202
|
+
var modal = document.getElementById("env-preview-modal");
|
|
6203
|
+
if (!modal) {
|
|
6204
|
+
modal = document.createElement("section");
|
|
6205
|
+
modal.id = "env-preview-modal";
|
|
6206
|
+
modal.className = "modal-backdrop hidden";
|
|
6207
|
+
modal.innerHTML =
|
|
6208
|
+
'<div class="modal env-preview-modal" role="dialog" aria-labelledby="env-preview-title" aria-modal="true">' +
|
|
6209
|
+
'<div class="modal-header">' +
|
|
6210
|
+
'<div>' +
|
|
6211
|
+
'<h2 class="modal-title" id="env-preview-title">将注入子进程的环境变量</h2>' +
|
|
6212
|
+
'<p class="modal-subtitle" id="env-preview-subtitle">这些变量会被传给 claude / codex(PTY 与结构化运行器一致)。</p>' +
|
|
6213
|
+
'</div>' +
|
|
6214
|
+
'<button id="env-preview-close" class="btn btn-ghost btn-icon modal-close-btn" type="button" aria-label="关闭">' +
|
|
6215
|
+
'<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" aria-hidden="true">' +
|
|
6216
|
+
'<line x1="6" y1="6" x2="18" y2="18"/><line x1="18" y1="6" x2="6" y2="18"/>' +
|
|
6217
|
+
'</svg>' +
|
|
6218
|
+
'</button>' +
|
|
6219
|
+
'</div>' +
|
|
6220
|
+
'<div class="modal-body env-preview-body">' +
|
|
6221
|
+
'<div class="env-preview-toolbar">' +
|
|
6222
|
+
'<div class="env-preview-meta" id="env-preview-meta">加载中…</div>' +
|
|
6223
|
+
'<div class="env-preview-controls">' +
|
|
6224
|
+
'<input id="env-preview-search" class="env-preview-search" type="search" placeholder="搜索变量名…" />' +
|
|
6225
|
+
'<label class="env-preview-reveal">' +
|
|
6226
|
+
'<input id="env-preview-reveal-toggle" type="checkbox" />' +
|
|
6227
|
+
'<span>显示敏感值</span>' +
|
|
6228
|
+
'</label>' +
|
|
6229
|
+
'</div>' +
|
|
6230
|
+
'</div>' +
|
|
6231
|
+
'<div class="env-preview-list" id="env-preview-list" tabindex="0">' +
|
|
6232
|
+
'<div class="env-preview-loading">加载中…</div>' +
|
|
6233
|
+
'</div>' +
|
|
6234
|
+
'</div>' +
|
|
6235
|
+
'<div class="modal-footer env-preview-footer">' +
|
|
6236
|
+
'<span class="env-preview-hint">敏感字段(含 KEY/TOKEN/SECRET 等)默认掩码,可勾选「显示敏感值」临时还原。</span>' +
|
|
6237
|
+
'<button id="env-preview-close-2" class="btn btn-secondary btn-sm" type="button">关闭</button>' +
|
|
6238
|
+
'</div>' +
|
|
6239
|
+
'</div>';
|
|
6240
|
+
document.body.appendChild(modal);
|
|
6241
|
+
|
|
6242
|
+
// Click outside to close
|
|
6243
|
+
modal.addEventListener("click", function(e) {
|
|
6244
|
+
if (e.target === modal) closeEnvPreviewModal();
|
|
6245
|
+
});
|
|
6246
|
+
var closeBtn = modal.querySelector("#env-preview-close");
|
|
6247
|
+
if (closeBtn) closeBtn.addEventListener("click", closeEnvPreviewModal);
|
|
6248
|
+
var closeBtn2 = modal.querySelector("#env-preview-close-2");
|
|
6249
|
+
if (closeBtn2) closeBtn2.addEventListener("click", closeEnvPreviewModal);
|
|
6250
|
+
var searchEl = modal.querySelector("#env-preview-search");
|
|
6251
|
+
if (searchEl) searchEl.addEventListener("input", function() { renderEnvPreviewList(); });
|
|
6252
|
+
var revealEl = modal.querySelector("#env-preview-reveal-toggle");
|
|
6253
|
+
if (revealEl) revealEl.addEventListener("change", function() { loadEnvPreview(revealEl.checked); });
|
|
6254
|
+
}
|
|
6255
|
+
|
|
6256
|
+
modal.classList.remove("closing");
|
|
6257
|
+
modal.classList.remove("hidden");
|
|
6258
|
+
var revealEl = modal.querySelector("#env-preview-reveal-toggle");
|
|
6259
|
+
if (revealEl) revealEl.checked = false;
|
|
6260
|
+
var searchEl = modal.querySelector("#env-preview-search");
|
|
6261
|
+
if (searchEl) searchEl.value = "";
|
|
6262
|
+
loadEnvPreview(false);
|
|
6263
|
+
}
|
|
6264
|
+
|
|
6265
|
+
function closeEnvPreviewModal() {
|
|
6266
|
+
var modal = document.getElementById("env-preview-modal");
|
|
6267
|
+
if (!modal) return;
|
|
6268
|
+
animateModalClose(modal);
|
|
6269
|
+
}
|
|
6270
|
+
|
|
6271
|
+
function loadEnvPreview(reveal) {
|
|
6272
|
+
var listEl = document.getElementById("env-preview-list");
|
|
6273
|
+
var metaEl = document.getElementById("env-preview-meta");
|
|
6274
|
+
if (listEl) listEl.innerHTML = '<div class="env-preview-loading">加载中…</div>';
|
|
6275
|
+
if (metaEl) metaEl.textContent = "加载中…";
|
|
6276
|
+
var url = "/api/settings/env-preview" + (reveal ? "?reveal=1" : "");
|
|
6277
|
+
fetch(url, { credentials: "same-origin" })
|
|
6278
|
+
.then(function(res) { return res.json(); })
|
|
6279
|
+
.then(function(data) {
|
|
6280
|
+
if (!data || !Array.isArray(data.entries)) {
|
|
6281
|
+
if (listEl) listEl.innerHTML = '<div class="env-preview-empty">读取失败。</div>';
|
|
6282
|
+
if (metaEl) metaEl.textContent = "读取失败";
|
|
6283
|
+
return;
|
|
6284
|
+
}
|
|
6285
|
+
state._envPreview = data;
|
|
6286
|
+
if (metaEl) {
|
|
6287
|
+
var inheritLabel = data.inheritEnv ? "继承父进程" : "最小白名单";
|
|
6288
|
+
metaEl.innerHTML =
|
|
6289
|
+
'<span class="env-preview-pill ' + (data.inheritEnv ? "is-inherit" : "is-minimal") + '">' + inheritLabel + '</span>' +
|
|
6290
|
+
'<span class="env-preview-count">共 ' + data.total + ' 项</span>';
|
|
6291
|
+
}
|
|
6292
|
+
renderEnvPreviewList();
|
|
6293
|
+
})
|
|
6294
|
+
.catch(function() {
|
|
6295
|
+
if (listEl) listEl.innerHTML = '<div class="env-preview-empty">读取失败,请稍后重试。</div>';
|
|
6296
|
+
if (metaEl) metaEl.textContent = "读取失败";
|
|
6297
|
+
});
|
|
6298
|
+
}
|
|
6299
|
+
|
|
6300
|
+
function renderEnvPreviewList() {
|
|
6301
|
+
var listEl = document.getElementById("env-preview-list");
|
|
6302
|
+
if (!listEl) return;
|
|
6303
|
+
var data = state._envPreview;
|
|
6304
|
+
if (!data || !Array.isArray(data.entries)) {
|
|
6305
|
+
listEl.innerHTML = '<div class="env-preview-empty">尚未加载。</div>';
|
|
6306
|
+
return;
|
|
6307
|
+
}
|
|
6308
|
+
var searchEl = document.getElementById("env-preview-search");
|
|
6309
|
+
var query = (searchEl && searchEl.value || "").trim().toLowerCase();
|
|
6310
|
+
var html = "";
|
|
6311
|
+
var shown = 0;
|
|
6312
|
+
for (var i = 0; i < data.entries.length; i++) {
|
|
6313
|
+
var entry = data.entries[i];
|
|
6314
|
+
if (query && entry.name.toLowerCase().indexOf(query) === -1) continue;
|
|
6315
|
+
shown++;
|
|
6316
|
+
var isPlaceholder = typeof entry.value === "string" && entry.value.charAt(0) === "<" && entry.value.charAt(entry.value.length - 1) === ">";
|
|
6317
|
+
html += '<div class="env-preview-row' + (entry.sensitive ? " is-sensitive" : "") + '">' +
|
|
6318
|
+
'<div class="env-preview-name">' +
|
|
6319
|
+
escapeHtml(entry.name) +
|
|
6320
|
+
(entry.sensitive ? '<span class="env-preview-badge" title="被识别为敏感字段">敏感</span>' : '') +
|
|
6321
|
+
(isPlaceholder ? '<span class="env-preview-badge env-preview-badge-runtime" title="按会话动态注入">运行时</span>' : '') +
|
|
6322
|
+
'</div>' +
|
|
6323
|
+
'<div class="env-preview-value' + (isPlaceholder ? " is-runtime" : "") + '" title="' + escapeHtml(String(entry.value)) + '">' +
|
|
6324
|
+
escapeHtml(String(entry.value)) +
|
|
6325
|
+
'</div>' +
|
|
6326
|
+
'<div class="env-preview-len">' + entry.length + ' 字符</div>' +
|
|
6327
|
+
'</div>';
|
|
6328
|
+
}
|
|
6329
|
+
if (shown === 0) {
|
|
6330
|
+
html = '<div class="env-preview-empty">没有匹配的变量。</div>';
|
|
6331
|
+
}
|
|
6332
|
+
listEl.innerHTML = html;
|
|
6333
|
+
}
|
|
6334
|
+
|
|
6172
6335
|
function updateSettingsDefaultModelSelect(data) {
|
|
6173
6336
|
var select = document.getElementById("cfg-default-model");
|
|
6174
6337
|
if (!select) return;
|
|
@@ -7336,6 +7499,78 @@
|
|
|
7336
7499
|
}
|
|
7337
7500
|
|
|
7338
7501
|
|
|
7502
|
+
function renderConnectQrCode(code) {
|
|
7503
|
+
var canvas = document.getElementById("android-connect-qr");
|
|
7504
|
+
var empty = document.getElementById("android-connect-qr-empty");
|
|
7505
|
+
var lib = window.QRCodeLib;
|
|
7506
|
+
if (!canvas) return;
|
|
7507
|
+
if (!lib || typeof lib.toCanvas !== "function") {
|
|
7508
|
+
if (empty) empty.textContent = "二维码库未加载";
|
|
7509
|
+
return;
|
|
7510
|
+
}
|
|
7511
|
+
try {
|
|
7512
|
+
lib.toCanvas(canvas, code, {
|
|
7513
|
+
width: 220,
|
|
7514
|
+
margin: 1,
|
|
7515
|
+
errorCorrectionLevel: "M",
|
|
7516
|
+
color: { dark: "#1f1b17", light: "#ffffff00" }
|
|
7517
|
+
}, function(err) {
|
|
7518
|
+
if (err) {
|
|
7519
|
+
if (empty) {
|
|
7520
|
+
empty.textContent = "二维码生成失败";
|
|
7521
|
+
empty.style.display = "";
|
|
7522
|
+
}
|
|
7523
|
+
canvas.style.visibility = "hidden";
|
|
7524
|
+
return;
|
|
7525
|
+
}
|
|
7526
|
+
if (empty) empty.style.display = "none";
|
|
7527
|
+
canvas.style.visibility = "visible";
|
|
7528
|
+
});
|
|
7529
|
+
} catch (e) {
|
|
7530
|
+
if (empty) {
|
|
7531
|
+
empty.textContent = "二维码生成失败";
|
|
7532
|
+
empty.style.display = "";
|
|
7533
|
+
}
|
|
7534
|
+
canvas.style.visibility = "hidden";
|
|
7535
|
+
}
|
|
7536
|
+
}
|
|
7537
|
+
|
|
7538
|
+
function showConnectQrModal(code) {
|
|
7539
|
+
var lib = window.QRCodeLib;
|
|
7540
|
+
if (!lib || typeof lib.toCanvas !== "function") return;
|
|
7541
|
+
// Reuse existing overlay if open
|
|
7542
|
+
var existing = document.getElementById("connect-qr-modal");
|
|
7543
|
+
if (existing) existing.remove();
|
|
7544
|
+
var overlay = document.createElement("div");
|
|
7545
|
+
overlay.id = "connect-qr-modal";
|
|
7546
|
+
overlay.className = "connect-qr-modal-overlay";
|
|
7547
|
+
overlay.innerHTML =
|
|
7548
|
+
'<div class="connect-qr-modal-card">' +
|
|
7549
|
+
'<canvas id="connect-qr-modal-canvas"></canvas>' +
|
|
7550
|
+
'<p class="connect-qr-modal-hint">用 Wand App 扫一扫,连接当前服务器</p>' +
|
|
7551
|
+
'<button type="button" class="btn btn-secondary btn-sm connect-qr-modal-close">关闭</button>' +
|
|
7552
|
+
'</div>';
|
|
7553
|
+
document.body.appendChild(overlay);
|
|
7554
|
+
var modalCanvas = overlay.querySelector("#connect-qr-modal-canvas");
|
|
7555
|
+
var size = Math.min(window.innerWidth, window.innerHeight) * 0.7;
|
|
7556
|
+
if (size < 240) size = 240;
|
|
7557
|
+
if (size > 480) size = 480;
|
|
7558
|
+
try {
|
|
7559
|
+
lib.toCanvas(modalCanvas, code, {
|
|
7560
|
+
width: size,
|
|
7561
|
+
margin: 2,
|
|
7562
|
+
errorCorrectionLevel: "M",
|
|
7563
|
+
color: { dark: "#1f1b17", light: "#ffffff" }
|
|
7564
|
+
});
|
|
7565
|
+
} catch (e) {}
|
|
7566
|
+
function close() { overlay.remove(); }
|
|
7567
|
+
overlay.addEventListener("click", function(e) {
|
|
7568
|
+
if (e.target === overlay) close();
|
|
7569
|
+
});
|
|
7570
|
+
var closeBtn = overlay.querySelector(".connect-qr-modal-close");
|
|
7571
|
+
if (closeBtn) closeBtn.addEventListener("click", close);
|
|
7572
|
+
}
|
|
7573
|
+
|
|
7339
7574
|
function copyToClipboard(text, triggerBtn, successCallback) {
|
|
7340
7575
|
if (!text) return;
|
|
7341
7576
|
function onSuccess() {
|
|
@@ -7521,12 +7756,32 @@
|
|
|
7521
7756
|
|
|
7522
7757
|
// App connect code (encrypted)
|
|
7523
7758
|
var connectCodeEl = document.getElementById("android-connect-code");
|
|
7759
|
+
var connectQrCanvas = document.getElementById("android-connect-qr");
|
|
7760
|
+
var connectQrEmpty = document.getElementById("android-connect-qr-empty");
|
|
7761
|
+
var connectQrWrap = document.getElementById("android-connect-qr-wrap");
|
|
7524
7762
|
if (connectCodeEl) {
|
|
7525
7763
|
connectCodeEl.textContent = "加载中...";
|
|
7764
|
+
if (connectQrEmpty) connectQrEmpty.textContent = "生成中…";
|
|
7765
|
+
if (connectQrCanvas) connectQrCanvas.style.visibility = "hidden";
|
|
7526
7766
|
fetch("/api/app-connect-code").then(function(r) { return r.json(); }).then(function(d) {
|
|
7527
|
-
if (d.code)
|
|
7528
|
-
|
|
7529
|
-
|
|
7767
|
+
if (d.code) {
|
|
7768
|
+
connectCodeEl.textContent = d.code;
|
|
7769
|
+
state.androidConnectCode = d.code;
|
|
7770
|
+
renderConnectQrCode(d.code);
|
|
7771
|
+
} else {
|
|
7772
|
+
connectCodeEl.textContent = "生成失败";
|
|
7773
|
+
if (connectQrEmpty) connectQrEmpty.textContent = "生成失败";
|
|
7774
|
+
}
|
|
7775
|
+
}).catch(function() {
|
|
7776
|
+
connectCodeEl.textContent = "获取失败";
|
|
7777
|
+
if (connectQrEmpty) connectQrEmpty.textContent = "获取失败";
|
|
7778
|
+
});
|
|
7779
|
+
}
|
|
7780
|
+
if (connectQrWrap && !connectQrWrap.dataset.bound) {
|
|
7781
|
+
connectQrWrap.dataset.bound = "1";
|
|
7782
|
+
connectQrWrap.addEventListener("click", function() {
|
|
7783
|
+
if (state.androidConnectCode) showConnectQrModal(state.androidConnectCode);
|
|
7784
|
+
});
|
|
7530
7785
|
}
|
|
7531
7786
|
|
|
7532
7787
|
// Config fields
|
|
@@ -9123,7 +9378,7 @@
|
|
|
9123
9378
|
var todoEl = document.getElementById("todo-progress");
|
|
9124
9379
|
if (todoEl) todoEl.classList.add("hidden");
|
|
9125
9380
|
welcomeInput.value = "";
|
|
9126
|
-
welcomeInput.placeholder = "
|
|
9381
|
+
welcomeInput.placeholder = "正在启动…";
|
|
9127
9382
|
welcomeInput.disabled = true;
|
|
9128
9383
|
var mode = state.chatMode || "managed";
|
|
9129
9384
|
var defaultCwd = getEffectiveCwd();
|
|
@@ -9144,7 +9399,7 @@
|
|
|
9144
9399
|
.then(function(data) {
|
|
9145
9400
|
if (data.error) {
|
|
9146
9401
|
showToast(data.error, "error");
|
|
9147
|
-
welcomeInput.placeholder = "
|
|
9402
|
+
welcomeInput.placeholder = "输入消息";
|
|
9148
9403
|
welcomeInput.disabled = false;
|
|
9149
9404
|
return;
|
|
9150
9405
|
}
|
|
@@ -9157,7 +9412,7 @@
|
|
|
9157
9412
|
switchToSessionView(data.id);
|
|
9158
9413
|
subscribeToSession(data.id);
|
|
9159
9414
|
loadOutput(data.id).then(function() {
|
|
9160
|
-
welcomeInput.placeholder = "
|
|
9415
|
+
welcomeInput.placeholder = "输入消息";
|
|
9161
9416
|
welcomeInput.disabled = false;
|
|
9162
9417
|
focusInputBox(true);
|
|
9163
9418
|
});
|
|
@@ -9166,7 +9421,7 @@
|
|
|
9166
9421
|
showToast((error && error.message) || (preferredTool === "codex"
|
|
9167
9422
|
? "无法启动 Codex 会话。"
|
|
9168
9423
|
: "无法启动 Claude 会话。"), "error");
|
|
9169
|
-
welcomeInput.placeholder = "
|
|
9424
|
+
welcomeInput.placeholder = "输入消息";
|
|
9170
9425
|
welcomeInput.disabled = false;
|
|
9171
9426
|
});
|
|
9172
9427
|
}
|
|
@@ -10542,7 +10797,7 @@
|
|
|
10542
10797
|
function createSessionFromWelcomeInput(value) {
|
|
10543
10798
|
var welcomeInput = document.getElementById("welcome-input");
|
|
10544
10799
|
if (!welcomeInput) return;
|
|
10545
|
-
welcomeInput.placeholder = "
|
|
10800
|
+
welcomeInput.placeholder = "正在思考…";
|
|
10546
10801
|
welcomeInput.disabled = true;
|
|
10547
10802
|
var mode = state.chatMode || "managed";
|
|
10548
10803
|
var defaultCwd = getEffectiveCwd();
|
|
@@ -10564,7 +10819,7 @@
|
|
|
10564
10819
|
.then(function(data) {
|
|
10565
10820
|
if (data.error) {
|
|
10566
10821
|
showToast(data.error, "error");
|
|
10567
|
-
welcomeInput.placeholder = "
|
|
10822
|
+
welcomeInput.placeholder = "输入消息";
|
|
10568
10823
|
welcomeInput.disabled = false;
|
|
10569
10824
|
return null;
|
|
10570
10825
|
}
|
|
@@ -10572,11 +10827,11 @@
|
|
|
10572
10827
|
})
|
|
10573
10828
|
.catch(function(error) {
|
|
10574
10829
|
showToast((error && error.message) || "无法启动会话。", "error");
|
|
10575
|
-
welcomeInput.placeholder = "
|
|
10830
|
+
welcomeInput.placeholder = "输入消息";
|
|
10576
10831
|
welcomeInput.disabled = false;
|
|
10577
10832
|
})
|
|
10578
10833
|
.finally(function() {
|
|
10579
|
-
welcomeInput.placeholder = "
|
|
10834
|
+
welcomeInput.placeholder = "输入消息";
|
|
10580
10835
|
welcomeInput.disabled = false;
|
|
10581
10836
|
});
|
|
10582
10837
|
}
|
|
@@ -15842,43 +16097,87 @@
|
|
|
15842
16097
|
playNotificationSound();
|
|
15843
16098
|
|
|
15844
16099
|
var id = ++notificationIdCounter;
|
|
15845
|
-
var
|
|
15846
|
-
|
|
15847
|
-
|
|
15848
|
-
|
|
15849
|
-
|
|
15850
|
-
|
|
15851
|
-
|
|
15852
|
-
|
|
15853
|
-
'<
|
|
16100
|
+
var card = document.createElement("div");
|
|
16101
|
+
// Reuse the notification stacking system but with a richer card style.
|
|
16102
|
+
card.className = "notification-bubble update-card";
|
|
16103
|
+
card.setAttribute("data-nid", id);
|
|
16104
|
+
|
|
16105
|
+
card.innerHTML =
|
|
16106
|
+
'<div class="update-card-shine" aria-hidden="true"></div>' +
|
|
16107
|
+
'<div class="update-card-header">' +
|
|
16108
|
+
'<div class="update-card-icon" aria-hidden="true">' +
|
|
16109
|
+
'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">' +
|
|
16110
|
+
'<path d="M12 19V5"/><path d="M5 12l7-7 7 7"/>' +
|
|
16111
|
+
'</svg>' +
|
|
16112
|
+
'</div>' +
|
|
16113
|
+
'<div class="update-card-heading">' +
|
|
16114
|
+
'<div class="update-card-title">\u53d1\u73b0\u65b0\u7248\u672c</div>' +
|
|
16115
|
+
'<div class="update-card-subtitle" id="update-card-subtitle">\u70b9\u51fb\u4e0b\u65b9\u6309\u94ae\u4e00\u952e\u66f4\u65b0</div>' +
|
|
16116
|
+
'</div>' +
|
|
16117
|
+
'<button class="update-card-close" title="\u7a0d\u540e\u63d0\u9192" aria-label="\u5173\u95ed">' +
|
|
16118
|
+
'<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">' +
|
|
16119
|
+
'<path d="M18 6L6 18"/><path d="M6 6l12 12"/>' +
|
|
16120
|
+
'</svg>' +
|
|
16121
|
+
'</button>' +
|
|
16122
|
+
'</div>' +
|
|
16123
|
+
'<div class="update-card-version">' +
|
|
16124
|
+
'<span class="update-card-version-chip update-card-version-current">v' + escapeHtml(String(currentVer).replace(/^v/, "")) + '</span>' +
|
|
16125
|
+
'<svg class="update-card-version-arrow" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' +
|
|
16126
|
+
'<path d="M5 12h14"/><path d="M13 5l7 7-7 7"/>' +
|
|
16127
|
+
'</svg>' +
|
|
16128
|
+
'<span class="update-card-version-chip update-card-version-latest">v' + escapeHtml(String(latestVer).replace(/^v/, "")) + '</span>' +
|
|
15854
16129
|
'</div>' +
|
|
15855
|
-
'<div class="
|
|
15856
|
-
|
|
16130
|
+
'<div class="update-card-progress" id="update-card-progress" aria-hidden="true">' +
|
|
16131
|
+
'<div class="update-card-progress-track"><div class="update-card-progress-fill"></div></div>' +
|
|
15857
16132
|
'</div>' +
|
|
15858
|
-
'<div class="
|
|
15859
|
-
|
|
16133
|
+
'<div class="update-card-status hidden" id="update-card-status"></div>' +
|
|
16134
|
+
'<div class="update-card-actions">' +
|
|
16135
|
+
'<button class="update-card-action update-card-action-primary" id="update-bubble-action" type="button">' +
|
|
16136
|
+
'<span class="update-card-action-label">\u7acb\u5373\u66f4\u65b0</span>' +
|
|
16137
|
+
'</button>' +
|
|
15860
16138
|
'</div>';
|
|
15861
16139
|
|
|
15862
|
-
document.body.appendChild(
|
|
16140
|
+
document.body.appendChild(card);
|
|
15863
16141
|
|
|
15864
|
-
var entry = { id: id, el:
|
|
16142
|
+
var entry = { id: id, el: card };
|
|
15865
16143
|
notificationStack.push(entry);
|
|
15866
16144
|
repositionNotifications();
|
|
15867
16145
|
|
|
15868
|
-
var closeBtn =
|
|
16146
|
+
var closeBtn = card.querySelector(".update-card-close");
|
|
15869
16147
|
if (closeBtn) closeBtn.onclick = function() {
|
|
15870
16148
|
dismissNotification(id);
|
|
15871
16149
|
state._updateBubbleShown = false;
|
|
15872
16150
|
};
|
|
15873
16151
|
|
|
15874
|
-
var actionBtn =
|
|
15875
|
-
var
|
|
16152
|
+
var actionBtn = card.querySelector("#update-bubble-action");
|
|
16153
|
+
var actionLabel = card.querySelector(".update-card-action-label");
|
|
16154
|
+
var subtitleEl = card.querySelector("#update-card-subtitle");
|
|
16155
|
+
var statusEl = card.querySelector("#update-card-status");
|
|
16156
|
+
var progressEl = card.querySelector("#update-card-progress");
|
|
16157
|
+
|
|
16158
|
+
function setStatus(text, kind) {
|
|
16159
|
+
if (!statusEl) return;
|
|
16160
|
+
statusEl.textContent = text || "";
|
|
16161
|
+
statusEl.classList.remove("hidden", "error", "success");
|
|
16162
|
+
if (!text) { statusEl.classList.add("hidden"); return; }
|
|
16163
|
+
if (kind) statusEl.classList.add(kind);
|
|
16164
|
+
}
|
|
16165
|
+
function setSubtitle(text) {
|
|
16166
|
+
if (subtitleEl) subtitleEl.textContent = text || "";
|
|
16167
|
+
}
|
|
16168
|
+
function setProgress(active) {
|
|
16169
|
+
if (!progressEl) return;
|
|
16170
|
+
progressEl.classList.toggle("active", !!active);
|
|
16171
|
+
}
|
|
15876
16172
|
|
|
15877
16173
|
if (actionBtn) actionBtn.onclick = function() {
|
|
15878
16174
|
// Phase 1: Performing update
|
|
15879
16175
|
actionBtn.disabled = true;
|
|
15880
|
-
|
|
15881
|
-
if (
|
|
16176
|
+
card.classList.add("is-busy");
|
|
16177
|
+
if (actionLabel) actionLabel.textContent = "\u66f4\u65b0\u4e2d\u2026";
|
|
16178
|
+
setSubtitle("\u6b63\u5728\u4e0b\u8f7d\u5e76\u5b89\u88c5\u65b0\u7248\u672c\u2026");
|
|
16179
|
+
setProgress(true);
|
|
16180
|
+
setStatus("");
|
|
15882
16181
|
|
|
15883
16182
|
fetch("/api/update", {
|
|
15884
16183
|
method: "POST",
|
|
@@ -15887,39 +16186,58 @@
|
|
|
15887
16186
|
})
|
|
15888
16187
|
.then(function(res) { return res.json(); })
|
|
15889
16188
|
.then(function(data) {
|
|
16189
|
+
setProgress(false);
|
|
16190
|
+
card.classList.remove("is-busy");
|
|
15890
16191
|
if (data.error) {
|
|
15891
16192
|
// Update failed
|
|
15892
|
-
|
|
15893
|
-
|
|
15894
|
-
bodyEl.style.color = "var(--error)";
|
|
15895
|
-
}
|
|
16193
|
+
setSubtitle("\u66f4\u65b0\u672a\u5b8c\u6210");
|
|
16194
|
+
setStatus(data.error, "error");
|
|
15896
16195
|
actionBtn.disabled = false;
|
|
15897
|
-
|
|
16196
|
+
if (actionLabel) actionLabel.textContent = "\u91cd\u8bd5";
|
|
15898
16197
|
return;
|
|
15899
16198
|
}
|
|
15900
16199
|
// Phase 2: Update succeeded, show restart button
|
|
15901
|
-
|
|
15902
|
-
|
|
15903
|
-
|
|
15904
|
-
|
|
15905
|
-
actionBtn.textContent = "\u91cd\u542f\u751f\u6548";
|
|
16200
|
+
setSubtitle(data.message || "\u66f4\u65b0\u5b8c\u6210\uff0c\u91cd\u542f\u540e\u751f\u6548");
|
|
16201
|
+
setStatus("");
|
|
16202
|
+
card.classList.add("is-success");
|
|
16203
|
+
if (actionLabel) actionLabel.textContent = "\u91cd\u542f\u751f\u6548";
|
|
15906
16204
|
actionBtn.disabled = false;
|
|
15907
|
-
actionBtn.className = "primary success";
|
|
15908
16205
|
actionBtn.onclick = function() {
|
|
15909
|
-
|
|
16206
|
+
actionBtn.disabled = true;
|
|
16207
|
+
if (actionLabel) actionLabel.textContent = "\u6b63\u5728\u91cd\u542f\u2026";
|
|
16208
|
+
setSubtitle("\u670d\u52a1\u6b63\u5728\u91cd\u542f\u2026");
|
|
16209
|
+
setProgress(true);
|
|
16210
|
+
performRestartCard(actionBtn, actionLabel, subtitleEl, statusEl, progressEl);
|
|
15910
16211
|
};
|
|
15911
16212
|
})
|
|
15912
16213
|
.catch(function() {
|
|
15913
|
-
|
|
15914
|
-
|
|
15915
|
-
|
|
15916
|
-
|
|
16214
|
+
setProgress(false);
|
|
16215
|
+
card.classList.remove("is-busy");
|
|
16216
|
+
setSubtitle("\u66f4\u65b0\u672a\u5b8c\u6210");
|
|
16217
|
+
setStatus("\u8bf7\u68c0\u67e5\u7f51\u7edc\u8fde\u63a5\u540e\u91cd\u8bd5", "error");
|
|
15917
16218
|
actionBtn.disabled = false;
|
|
15918
|
-
|
|
16219
|
+
if (actionLabel) actionLabel.textContent = "\u91cd\u8bd5";
|
|
15919
16220
|
});
|
|
15920
16221
|
};
|
|
15921
16222
|
}
|
|
15922
16223
|
|
|
16224
|
+
// Restart driver used by the new update card.
|
|
16225
|
+
function performRestartCard(btn, labelEl, subtitleEl, statusEl, progressEl) {
|
|
16226
|
+
fetch("/api/restart", {
|
|
16227
|
+
method: "POST",
|
|
16228
|
+
headers: { "Content-Type": "application/json" },
|
|
16229
|
+
credentials: "same-origin"
|
|
16230
|
+
})
|
|
16231
|
+
.then(function(res) { return res.json(); })
|
|
16232
|
+
.then(function() {
|
|
16233
|
+
showRestartOverlay();
|
|
16234
|
+
})
|
|
16235
|
+
.catch(function() {
|
|
16236
|
+
// Network error likely means the server already shut down \u2014 show overlay anyway
|
|
16237
|
+
showRestartOverlay();
|
|
16238
|
+
});
|
|
16239
|
+
}
|
|
16240
|
+
|
|
15923
16241
|
/**
|
|
15924
16242
|
* Call POST /api/restart and show the restart overlay.
|
|
15925
16243
|
*/
|