@co0ontty/wand 1.21.11 → 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 +40 -0
- package/dist/structured-session-manager.js +4 -0
- package/dist/web-ui/content/scripts.js +261 -73
- package/dist/web-ui/content/styles.css +448 -0
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -13,6 +13,7 @@ import { WebSocketServer } from "ws";
|
|
|
13
13
|
import { ensureAvatarSeed, getAvatarSvg } from "./avatar.js";
|
|
14
14
|
import { createSession, revokeSession, setAuthStorage, validateSession } from "./auth.js";
|
|
15
15
|
import { ensureCertificates } from "./cert.js";
|
|
16
|
+
import { buildChildEnv } from "./env-utils.js";
|
|
16
17
|
import { isExecutionMode, PREFERENCE_KEYS, resolveConfigDir, saveConfig, writePreferenceToStorage, } from "./config.js";
|
|
17
18
|
import { getCachedModels, refreshModels } from "./models.js";
|
|
18
19
|
import { ProcessManager } from "./process-manager.js";
|
|
@@ -804,6 +805,45 @@ export async function startServer(config, configPath) {
|
|
|
804
805
|
github: ghApk ? { fileName: ghApk.fileName, version: ghApk.version, size: ghApk.size, downloadUrl: ghApk.downloadUrl } : null,
|
|
805
806
|
});
|
|
806
807
|
});
|
|
808
|
+
// 返回当前 inheritEnv 配置下,wand 启动 PTY / 结构化子进程时实际会传给
|
|
809
|
+
// claude / codex 的环境变量集合。值会按下面的规则做掩码:
|
|
810
|
+
// - 名字里含 KEY/TOKEN/SECRET/PASSWORD/AUTH/CREDENTIAL/COOKIE/SESSION 的视为敏感
|
|
811
|
+
// - 敏感值默认显示为 ***(保留长度提示),可通过 ?reveal=1 取消掩码
|
|
812
|
+
// 即使开启 reveal,仍只对已认证用户可见(路由由全局 requireAuth 保护)。
|
|
813
|
+
app.get("/api/settings/env-preview", (req, res) => {
|
|
814
|
+
const inheritEnv = config.inheritEnv !== false;
|
|
815
|
+
// 复用与 process-manager / structured-session-manager 相同的组装逻辑,
|
|
816
|
+
// 这样 UI 上看到的就是真正会被注入到子进程的那一份环境。
|
|
817
|
+
const env = buildChildEnv(inheritEnv, {
|
|
818
|
+
// PTY runner 还会注入 WAND_* 用于 mode 协调,这里也展示出来便于排查。
|
|
819
|
+
WAND_MODE: "<runtime>",
|
|
820
|
+
WAND_AUTO_CONFIRM: "<runtime>",
|
|
821
|
+
WAND_AUTO_EDIT: "<runtime>",
|
|
822
|
+
});
|
|
823
|
+
const reveal = req.query.reveal === "1" || req.query.reveal === "true";
|
|
824
|
+
const SENSITIVE_PATTERN = /(KEY|TOKEN|SECRET|PASSWORD|AUTH|CREDENTIAL|COOKIE|SESSION)/i;
|
|
825
|
+
const entries = Object.keys(env)
|
|
826
|
+
.sort()
|
|
827
|
+
.map((name) => {
|
|
828
|
+
const raw = env[name] ?? "";
|
|
829
|
+
const sensitive = SENSITIVE_PATTERN.test(name);
|
|
830
|
+
const masked = sensitive && !reveal;
|
|
831
|
+
// WAND_* 占位值不算敏感,保持原样。
|
|
832
|
+
const isPlaceholder = raw.startsWith("<") && raw.endsWith(">");
|
|
833
|
+
return {
|
|
834
|
+
name,
|
|
835
|
+
value: masked && !isPlaceholder ? "***" : raw,
|
|
836
|
+
length: raw.length,
|
|
837
|
+
sensitive,
|
|
838
|
+
};
|
|
839
|
+
});
|
|
840
|
+
res.json({
|
|
841
|
+
inheritEnv,
|
|
842
|
+
total: entries.length,
|
|
843
|
+
reveal,
|
|
844
|
+
entries,
|
|
845
|
+
});
|
|
846
|
+
});
|
|
807
847
|
app.get("/api/app-connect-code", requireAuth, (req, res) => {
|
|
808
848
|
const dbPassword = storage.getPassword();
|
|
809
849
|
const effectivePassword = dbPassword ?? config.password;
|
|
@@ -1536,9 +1536,13 @@ export class StructuredSessionManager {
|
|
|
1536
1536
|
: `Please respond in ${language}. Use ${language} for all your explanations, comments, and conversational text.`);
|
|
1537
1537
|
}
|
|
1538
1538
|
const sdkClaudeBinary = resolveSdkClaudeBinary();
|
|
1539
|
+
// SDK 默认会把整个 process.env 透传给 claude 子进程;这里显式按 inheritEnv 配置组装,
|
|
1540
|
+
// 否则关闭"继承环境变量"开关时 SDK 路径会被静默忽略。
|
|
1541
|
+
const sdkEnv = buildChildEnv(this.config.inheritEnv !== false);
|
|
1539
1542
|
const sdkOptions = {
|
|
1540
1543
|
cwd: session.cwd,
|
|
1541
1544
|
abortController,
|
|
1545
|
+
env: sdkEnv,
|
|
1542
1546
|
permissionMode,
|
|
1543
1547
|
...(permissionMode === "bypassPermissions" ? { allowDangerouslySkipPermissions: true } : {}),
|
|
1544
1548
|
...(allowedToolsForRoot ? { allowedTools: allowedToolsForRoot } : {}),
|
|
@@ -2169,10 +2169,13 @@
|
|
|
2169
2169
|
'<label class="settings-toggle-title" for="cfg-inherit-env">继承环境变量</label>' +
|
|
2170
2170
|
'<span class="settings-toggle-desc">启动 PTY / 结构化子进程时,把当前服务进程的环境变量传给 claude / codex。关闭后子进程仅获得最小可用环境(PATH/HOME/SHELL/LANG/TERM 等),可用于隔离 API key 等敏感凭据。</span>' +
|
|
2171
2171
|
'</div>' +
|
|
2172
|
-
'<
|
|
2173
|
-
'<
|
|
2174
|
-
'<
|
|
2175
|
-
|
|
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>' +
|
|
2176
2179
|
'</div>' +
|
|
2177
2180
|
'<div class="field">' +
|
|
2178
2181
|
'<label class="field-label" for="cfg-default-model">默认模型</label>' +
|
|
@@ -4020,6 +4023,8 @@
|
|
|
4020
4023
|
if (saveConfigBtn) saveConfigBtn.addEventListener("click", saveConfigSettings);
|
|
4021
4024
|
var defaultModelRefreshBtn = document.getElementById("cfg-default-model-refresh");
|
|
4022
4025
|
if (defaultModelRefreshBtn) defaultModelRefreshBtn.addEventListener("click", refreshAvailableModels);
|
|
4026
|
+
var viewEnvBtn = document.getElementById("cfg-view-env-btn");
|
|
4027
|
+
if (viewEnvBtn) viewEnvBtn.addEventListener("click", openEnvPreviewModal);
|
|
4023
4028
|
var saveDisplayBtn = document.getElementById("save-display-button");
|
|
4024
4029
|
if (saveDisplayBtn) saveDisplayBtn.addEventListener("click", saveDisplaySettings);
|
|
4025
4030
|
// App icon picker (APK only)
|
|
@@ -6002,31 +6007,14 @@
|
|
|
6002
6007
|
}
|
|
6003
6008
|
|
|
6004
6009
|
function getComposerPlaceholder(session, terminalInteractive) {
|
|
6005
|
-
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
if (session &&
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
: "向 Claude 发送消息;chat 为结构化对话视图";
|
|
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 "会话已结束";
|
|
6012
6016
|
}
|
|
6013
|
-
|
|
6014
|
-
if (session.status !== "running") {
|
|
6015
|
-
return "Codex 会话已结束,无法继续发送";
|
|
6016
|
-
}
|
|
6017
|
-
return state.currentView === "terminal"
|
|
6018
|
-
? "向 Codex 发送输入;terminal 为原始 TUI 输出"
|
|
6019
|
-
: "向 Codex 发送输入;chat 为解析后的阅读视图";
|
|
6020
|
-
}
|
|
6021
|
-
if (session && !isStructuredSession(session) && session.status !== "running") {
|
|
6022
|
-
if (canAutoResumeSession(session)) {
|
|
6023
|
-
return "输入消息...";
|
|
6024
|
-
}
|
|
6025
|
-
return "会话已结束,无法继续发送";
|
|
6026
|
-
}
|
|
6027
|
-
return session && isStructuredSession(session) && session.structuredState && session.structuredState.inFlight
|
|
6028
|
-
? "思考中 · 发送新消息将中断当前回复"
|
|
6029
|
-
: "输入消息...";
|
|
6017
|
+
return "";
|
|
6030
6018
|
}
|
|
6031
6019
|
|
|
6032
6020
|
function getToolModeHint(tool, mode) {
|
|
@@ -6207,6 +6195,143 @@
|
|
|
6207
6195
|
});
|
|
6208
6196
|
}
|
|
6209
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
|
+
|
|
6210
6335
|
function updateSettingsDefaultModelSelect(data) {
|
|
6211
6336
|
var select = document.getElementById("cfg-default-model");
|
|
6212
6337
|
if (!select) return;
|
|
@@ -9253,7 +9378,7 @@
|
|
|
9253
9378
|
var todoEl = document.getElementById("todo-progress");
|
|
9254
9379
|
if (todoEl) todoEl.classList.add("hidden");
|
|
9255
9380
|
welcomeInput.value = "";
|
|
9256
|
-
welcomeInput.placeholder = "
|
|
9381
|
+
welcomeInput.placeholder = "正在启动…";
|
|
9257
9382
|
welcomeInput.disabled = true;
|
|
9258
9383
|
var mode = state.chatMode || "managed";
|
|
9259
9384
|
var defaultCwd = getEffectiveCwd();
|
|
@@ -9274,7 +9399,7 @@
|
|
|
9274
9399
|
.then(function(data) {
|
|
9275
9400
|
if (data.error) {
|
|
9276
9401
|
showToast(data.error, "error");
|
|
9277
|
-
welcomeInput.placeholder = "
|
|
9402
|
+
welcomeInput.placeholder = "输入消息";
|
|
9278
9403
|
welcomeInput.disabled = false;
|
|
9279
9404
|
return;
|
|
9280
9405
|
}
|
|
@@ -9287,7 +9412,7 @@
|
|
|
9287
9412
|
switchToSessionView(data.id);
|
|
9288
9413
|
subscribeToSession(data.id);
|
|
9289
9414
|
loadOutput(data.id).then(function() {
|
|
9290
|
-
welcomeInput.placeholder = "
|
|
9415
|
+
welcomeInput.placeholder = "输入消息";
|
|
9291
9416
|
welcomeInput.disabled = false;
|
|
9292
9417
|
focusInputBox(true);
|
|
9293
9418
|
});
|
|
@@ -9296,7 +9421,7 @@
|
|
|
9296
9421
|
showToast((error && error.message) || (preferredTool === "codex"
|
|
9297
9422
|
? "无法启动 Codex 会话。"
|
|
9298
9423
|
: "无法启动 Claude 会话。"), "error");
|
|
9299
|
-
welcomeInput.placeholder = "
|
|
9424
|
+
welcomeInput.placeholder = "输入消息";
|
|
9300
9425
|
welcomeInput.disabled = false;
|
|
9301
9426
|
});
|
|
9302
9427
|
}
|
|
@@ -10672,7 +10797,7 @@
|
|
|
10672
10797
|
function createSessionFromWelcomeInput(value) {
|
|
10673
10798
|
var welcomeInput = document.getElementById("welcome-input");
|
|
10674
10799
|
if (!welcomeInput) return;
|
|
10675
|
-
welcomeInput.placeholder = "
|
|
10800
|
+
welcomeInput.placeholder = "正在思考…";
|
|
10676
10801
|
welcomeInput.disabled = true;
|
|
10677
10802
|
var mode = state.chatMode || "managed";
|
|
10678
10803
|
var defaultCwd = getEffectiveCwd();
|
|
@@ -10694,7 +10819,7 @@
|
|
|
10694
10819
|
.then(function(data) {
|
|
10695
10820
|
if (data.error) {
|
|
10696
10821
|
showToast(data.error, "error");
|
|
10697
|
-
welcomeInput.placeholder = "
|
|
10822
|
+
welcomeInput.placeholder = "输入消息";
|
|
10698
10823
|
welcomeInput.disabled = false;
|
|
10699
10824
|
return null;
|
|
10700
10825
|
}
|
|
@@ -10702,11 +10827,11 @@
|
|
|
10702
10827
|
})
|
|
10703
10828
|
.catch(function(error) {
|
|
10704
10829
|
showToast((error && error.message) || "无法启动会话。", "error");
|
|
10705
|
-
welcomeInput.placeholder = "
|
|
10830
|
+
welcomeInput.placeholder = "输入消息";
|
|
10706
10831
|
welcomeInput.disabled = false;
|
|
10707
10832
|
})
|
|
10708
10833
|
.finally(function() {
|
|
10709
|
-
welcomeInput.placeholder = "
|
|
10834
|
+
welcomeInput.placeholder = "输入消息";
|
|
10710
10835
|
welcomeInput.disabled = false;
|
|
10711
10836
|
});
|
|
10712
10837
|
}
|
|
@@ -15972,43 +16097,87 @@
|
|
|
15972
16097
|
playNotificationSound();
|
|
15973
16098
|
|
|
15974
16099
|
var id = ++notificationIdCounter;
|
|
15975
|
-
var
|
|
15976
|
-
|
|
15977
|
-
|
|
15978
|
-
|
|
15979
|
-
|
|
15980
|
-
|
|
15981
|
-
|
|
15982
|
-
|
|
15983
|
-
'<
|
|
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>' +
|
|
15984
16129
|
'</div>' +
|
|
15985
|
-
'<div class="
|
|
15986
|
-
|
|
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>' +
|
|
15987
16132
|
'</div>' +
|
|
15988
|
-
'<div class="
|
|
15989
|
-
|
|
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>' +
|
|
15990
16138
|
'</div>';
|
|
15991
16139
|
|
|
15992
|
-
document.body.appendChild(
|
|
16140
|
+
document.body.appendChild(card);
|
|
15993
16141
|
|
|
15994
|
-
var entry = { id: id, el:
|
|
16142
|
+
var entry = { id: id, el: card };
|
|
15995
16143
|
notificationStack.push(entry);
|
|
15996
16144
|
repositionNotifications();
|
|
15997
16145
|
|
|
15998
|
-
var closeBtn =
|
|
16146
|
+
var closeBtn = card.querySelector(".update-card-close");
|
|
15999
16147
|
if (closeBtn) closeBtn.onclick = function() {
|
|
16000
16148
|
dismissNotification(id);
|
|
16001
16149
|
state._updateBubbleShown = false;
|
|
16002
16150
|
};
|
|
16003
16151
|
|
|
16004
|
-
var actionBtn =
|
|
16005
|
-
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
|
+
}
|
|
16006
16172
|
|
|
16007
16173
|
if (actionBtn) actionBtn.onclick = function() {
|
|
16008
16174
|
// Phase 1: Performing update
|
|
16009
16175
|
actionBtn.disabled = true;
|
|
16010
|
-
|
|
16011
|
-
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("");
|
|
16012
16181
|
|
|
16013
16182
|
fetch("/api/update", {
|
|
16014
16183
|
method: "POST",
|
|
@@ -16017,39 +16186,58 @@
|
|
|
16017
16186
|
})
|
|
16018
16187
|
.then(function(res) { return res.json(); })
|
|
16019
16188
|
.then(function(data) {
|
|
16189
|
+
setProgress(false);
|
|
16190
|
+
card.classList.remove("is-busy");
|
|
16020
16191
|
if (data.error) {
|
|
16021
16192
|
// Update failed
|
|
16022
|
-
|
|
16023
|
-
|
|
16024
|
-
bodyEl.style.color = "var(--error)";
|
|
16025
|
-
}
|
|
16193
|
+
setSubtitle("\u66f4\u65b0\u672a\u5b8c\u6210");
|
|
16194
|
+
setStatus(data.error, "error");
|
|
16026
16195
|
actionBtn.disabled = false;
|
|
16027
|
-
|
|
16196
|
+
if (actionLabel) actionLabel.textContent = "\u91cd\u8bd5";
|
|
16028
16197
|
return;
|
|
16029
16198
|
}
|
|
16030
16199
|
// Phase 2: Update succeeded, show restart button
|
|
16031
|
-
|
|
16032
|
-
|
|
16033
|
-
|
|
16034
|
-
|
|
16035
|
-
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";
|
|
16036
16204
|
actionBtn.disabled = false;
|
|
16037
|
-
actionBtn.className = "primary success";
|
|
16038
16205
|
actionBtn.onclick = function() {
|
|
16039
|
-
|
|
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);
|
|
16040
16211
|
};
|
|
16041
16212
|
})
|
|
16042
16213
|
.catch(function() {
|
|
16043
|
-
|
|
16044
|
-
|
|
16045
|
-
|
|
16046
|
-
|
|
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");
|
|
16047
16218
|
actionBtn.disabled = false;
|
|
16048
|
-
|
|
16219
|
+
if (actionLabel) actionLabel.textContent = "\u91cd\u8bd5";
|
|
16049
16220
|
});
|
|
16050
16221
|
};
|
|
16051
16222
|
}
|
|
16052
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
|
+
|
|
16053
16241
|
/**
|
|
16054
16242
|
* Call POST /api/restart and show the restart overlay.
|
|
16055
16243
|
*/
|
|
@@ -9185,6 +9185,258 @@
|
|
|
9185
9185
|
|
|
9186
9186
|
/* .btn-success defined globally in the button system above */
|
|
9187
9187
|
|
|
9188
|
+
/* ── Update Card (richer than the generic notification bubble) ── */
|
|
9189
|
+
.notification-bubble.update-card {
|
|
9190
|
+
width: min(360px, calc(100vw - 24px));
|
|
9191
|
+
max-width: min(360px, calc(100vw - 24px));
|
|
9192
|
+
min-width: 0;
|
|
9193
|
+
padding: 14px 14px 12px;
|
|
9194
|
+
border-radius: var(--radius-md);
|
|
9195
|
+
background:
|
|
9196
|
+
radial-gradient(120% 120% at 0% 0%, var(--accent-muted) 0%, transparent 55%),
|
|
9197
|
+
linear-gradient(180deg, var(--bg-elevated) 0%, var(--bg-primary) 100%);
|
|
9198
|
+
border: 1px solid var(--border-strong);
|
|
9199
|
+
box-shadow: var(--shadow-xl), 0 0 0 1px rgba(255,255,255,0.4) inset;
|
|
9200
|
+
overflow: hidden;
|
|
9201
|
+
isolation: isolate;
|
|
9202
|
+
}
|
|
9203
|
+
.update-card-shine {
|
|
9204
|
+
position: absolute;
|
|
9205
|
+
inset: 0;
|
|
9206
|
+
pointer-events: none;
|
|
9207
|
+
background: linear-gradient(115deg,
|
|
9208
|
+
transparent 0%,
|
|
9209
|
+
rgba(255, 255, 255, 0.18) 45%,
|
|
9210
|
+
rgba(255, 255, 255, 0.32) 50%,
|
|
9211
|
+
rgba(255, 255, 255, 0.18) 55%,
|
|
9212
|
+
transparent 100%);
|
|
9213
|
+
background-size: 220% 100%;
|
|
9214
|
+
background-position: 120% 0;
|
|
9215
|
+
mix-blend-mode: overlay;
|
|
9216
|
+
opacity: 0.65;
|
|
9217
|
+
animation: update-card-shine 6s ease-in-out infinite;
|
|
9218
|
+
z-index: 0;
|
|
9219
|
+
}
|
|
9220
|
+
@keyframes update-card-shine {
|
|
9221
|
+
0% { background-position: 120% 0; }
|
|
9222
|
+
55% { background-position: -20% 0; }
|
|
9223
|
+
100% { background-position: -20% 0; }
|
|
9224
|
+
}
|
|
9225
|
+
.update-card.is-success {
|
|
9226
|
+
background:
|
|
9227
|
+
radial-gradient(120% 120% at 0% 0%, var(--success-muted) 0%, transparent 55%),
|
|
9228
|
+
linear-gradient(180deg, var(--bg-elevated) 0%, var(--bg-primary) 100%);
|
|
9229
|
+
border-color: var(--success);
|
|
9230
|
+
box-shadow: var(--shadow-xl), 0 0 0 1px rgba(255,255,255,0.4) inset, var(--shadow-glow-success);
|
|
9231
|
+
}
|
|
9232
|
+
.update-card-header {
|
|
9233
|
+
position: relative;
|
|
9234
|
+
z-index: 1;
|
|
9235
|
+
display: flex;
|
|
9236
|
+
align-items: flex-start;
|
|
9237
|
+
gap: 10px;
|
|
9238
|
+
}
|
|
9239
|
+
.update-card-icon {
|
|
9240
|
+
width: 32px;
|
|
9241
|
+
height: 32px;
|
|
9242
|
+
flex-shrink: 0;
|
|
9243
|
+
border-radius: var(--radius-sm);
|
|
9244
|
+
display: flex;
|
|
9245
|
+
align-items: center;
|
|
9246
|
+
justify-content: center;
|
|
9247
|
+
color: white;
|
|
9248
|
+
background: linear-gradient(135deg, var(--accent) 0%, var(--accent-hover) 100%);
|
|
9249
|
+
box-shadow: 0 4px 12px var(--accent-glow), inset 0 1px 0 rgba(255,255,255,0.35);
|
|
9250
|
+
}
|
|
9251
|
+
.update-card.is-success .update-card-icon {
|
|
9252
|
+
background: linear-gradient(135deg, var(--success) 0%, var(--success-hover) 100%);
|
|
9253
|
+
box-shadow: 0 4px 12px var(--success-glow), inset 0 1px 0 rgba(255,255,255,0.35);
|
|
9254
|
+
}
|
|
9255
|
+
.update-card-heading {
|
|
9256
|
+
flex: 1;
|
|
9257
|
+
min-width: 0;
|
|
9258
|
+
display: flex;
|
|
9259
|
+
flex-direction: column;
|
|
9260
|
+
gap: 2px;
|
|
9261
|
+
}
|
|
9262
|
+
.update-card-title {
|
|
9263
|
+
font-size: 0.88rem;
|
|
9264
|
+
font-weight: 700;
|
|
9265
|
+
color: var(--text-primary);
|
|
9266
|
+
letter-spacing: 0.01em;
|
|
9267
|
+
line-height: 1.25;
|
|
9268
|
+
}
|
|
9269
|
+
.update-card-subtitle {
|
|
9270
|
+
font-size: 0.72rem;
|
|
9271
|
+
color: var(--text-tertiary);
|
|
9272
|
+
line-height: 1.35;
|
|
9273
|
+
}
|
|
9274
|
+
.update-card-close {
|
|
9275
|
+
flex-shrink: 0;
|
|
9276
|
+
width: 24px;
|
|
9277
|
+
height: 24px;
|
|
9278
|
+
border: none;
|
|
9279
|
+
border-radius: var(--radius-sm);
|
|
9280
|
+
background: transparent;
|
|
9281
|
+
color: var(--text-muted);
|
|
9282
|
+
cursor: pointer;
|
|
9283
|
+
display: flex;
|
|
9284
|
+
align-items: center;
|
|
9285
|
+
justify-content: center;
|
|
9286
|
+
padding: 0;
|
|
9287
|
+
transition: background 0.15s, color 0.15s;
|
|
9288
|
+
}
|
|
9289
|
+
.update-card-close:hover {
|
|
9290
|
+
background: var(--bg-tertiary);
|
|
9291
|
+
color: var(--text-primary);
|
|
9292
|
+
}
|
|
9293
|
+
|
|
9294
|
+
.update-card-version {
|
|
9295
|
+
position: relative;
|
|
9296
|
+
z-index: 1;
|
|
9297
|
+
margin: 12px 0 10px;
|
|
9298
|
+
display: flex;
|
|
9299
|
+
align-items: center;
|
|
9300
|
+
gap: 8px;
|
|
9301
|
+
flex-wrap: nowrap;
|
|
9302
|
+
}
|
|
9303
|
+
.update-card-version-chip {
|
|
9304
|
+
font-family: var(--font-mono, ui-monospace, "SF Mono", Menlo, Consolas, monospace);
|
|
9305
|
+
font-size: 0.74rem;
|
|
9306
|
+
font-weight: 600;
|
|
9307
|
+
padding: 3px 10px;
|
|
9308
|
+
border-radius: var(--radius-full);
|
|
9309
|
+
border: 1px solid var(--border-subtle);
|
|
9310
|
+
white-space: nowrap;
|
|
9311
|
+
}
|
|
9312
|
+
.update-card-version-current {
|
|
9313
|
+
color: var(--text-tertiary);
|
|
9314
|
+
background: var(--bg-tertiary);
|
|
9315
|
+
}
|
|
9316
|
+
.update-card-version-latest {
|
|
9317
|
+
color: var(--accent);
|
|
9318
|
+
background: var(--accent-muted);
|
|
9319
|
+
border-color: var(--accent);
|
|
9320
|
+
box-shadow: 0 1px 6px var(--accent-glow);
|
|
9321
|
+
}
|
|
9322
|
+
.update-card.is-success .update-card-version-latest {
|
|
9323
|
+
color: var(--success);
|
|
9324
|
+
background: var(--success-muted);
|
|
9325
|
+
border-color: var(--success);
|
|
9326
|
+
box-shadow: 0 1px 6px var(--success-glow);
|
|
9327
|
+
}
|
|
9328
|
+
.update-card-version-arrow {
|
|
9329
|
+
color: var(--text-muted);
|
|
9330
|
+
flex-shrink: 0;
|
|
9331
|
+
}
|
|
9332
|
+
|
|
9333
|
+
.update-card-progress {
|
|
9334
|
+
position: relative;
|
|
9335
|
+
z-index: 1;
|
|
9336
|
+
height: 0;
|
|
9337
|
+
overflow: hidden;
|
|
9338
|
+
margin: 0;
|
|
9339
|
+
transition: height 0.25s ease, margin 0.25s ease;
|
|
9340
|
+
}
|
|
9341
|
+
.update-card-progress.active {
|
|
9342
|
+
height: 6px;
|
|
9343
|
+
margin: 0 0 10px;
|
|
9344
|
+
}
|
|
9345
|
+
.update-card-progress-track {
|
|
9346
|
+
position: relative;
|
|
9347
|
+
width: 100%;
|
|
9348
|
+
height: 100%;
|
|
9349
|
+
background: var(--bg-tertiary);
|
|
9350
|
+
border-radius: var(--radius-full);
|
|
9351
|
+
overflow: hidden;
|
|
9352
|
+
}
|
|
9353
|
+
.update-card-progress-fill {
|
|
9354
|
+
position: absolute;
|
|
9355
|
+
top: 0;
|
|
9356
|
+
left: -40%;
|
|
9357
|
+
width: 40%;
|
|
9358
|
+
height: 100%;
|
|
9359
|
+
border-radius: var(--radius-full);
|
|
9360
|
+
background: linear-gradient(90deg, var(--accent) 0%, var(--accent-hover) 100%);
|
|
9361
|
+
box-shadow: 0 0 10px var(--accent-glow);
|
|
9362
|
+
animation: update-card-progress-sweep 1.4s cubic-bezier(0.65, 0.05, 0.35, 1) infinite;
|
|
9363
|
+
}
|
|
9364
|
+
@keyframes update-card-progress-sweep {
|
|
9365
|
+
0% { left: -45%; width: 40%; }
|
|
9366
|
+
50% { left: 25%; width: 55%; }
|
|
9367
|
+
100% { left: 105%; width: 40%; }
|
|
9368
|
+
}
|
|
9369
|
+
|
|
9370
|
+
.update-card-status {
|
|
9371
|
+
position: relative;
|
|
9372
|
+
z-index: 1;
|
|
9373
|
+
font-size: 0.72rem;
|
|
9374
|
+
line-height: 1.4;
|
|
9375
|
+
padding: 6px 10px;
|
|
9376
|
+
border-radius: var(--radius-sm);
|
|
9377
|
+
margin-bottom: 10px;
|
|
9378
|
+
background: var(--bg-tertiary);
|
|
9379
|
+
color: var(--text-secondary);
|
|
9380
|
+
border: 1px solid var(--border-subtle);
|
|
9381
|
+
}
|
|
9382
|
+
.update-card-status.error {
|
|
9383
|
+
background: var(--danger-muted);
|
|
9384
|
+
color: var(--danger);
|
|
9385
|
+
border-color: var(--danger);
|
|
9386
|
+
}
|
|
9387
|
+
.update-card-status.success {
|
|
9388
|
+
background: var(--success-muted);
|
|
9389
|
+
color: var(--success);
|
|
9390
|
+
border-color: var(--success);
|
|
9391
|
+
}
|
|
9392
|
+
|
|
9393
|
+
.update-card-actions {
|
|
9394
|
+
position: relative;
|
|
9395
|
+
z-index: 1;
|
|
9396
|
+
display: flex;
|
|
9397
|
+
justify-content: flex-end;
|
|
9398
|
+
gap: 8px;
|
|
9399
|
+
}
|
|
9400
|
+
.update-card-action {
|
|
9401
|
+
font-size: 0.78rem;
|
|
9402
|
+
font-weight: 600;
|
|
9403
|
+
padding: 7px 16px;
|
|
9404
|
+
border-radius: var(--radius-sm);
|
|
9405
|
+
border: 1px solid transparent;
|
|
9406
|
+
cursor: pointer;
|
|
9407
|
+
display: inline-flex;
|
|
9408
|
+
align-items: center;
|
|
9409
|
+
gap: 6px;
|
|
9410
|
+
transition: transform 0.12s ease, filter 0.15s ease, box-shadow 0.15s ease;
|
|
9411
|
+
}
|
|
9412
|
+
.update-card-action-primary {
|
|
9413
|
+
background: linear-gradient(135deg, var(--accent) 0%, var(--accent-hover) 100%);
|
|
9414
|
+
color: white;
|
|
9415
|
+
box-shadow: 0 4px 12px var(--accent-glow), inset 0 1px 0 rgba(255,255,255,0.25);
|
|
9416
|
+
}
|
|
9417
|
+
.update-card-action-primary:hover:not(:disabled) {
|
|
9418
|
+
filter: brightness(1.05);
|
|
9419
|
+
transform: translateY(-1px);
|
|
9420
|
+
box-shadow: 0 6px 16px var(--accent-glow), inset 0 1px 0 rgba(255,255,255,0.3);
|
|
9421
|
+
}
|
|
9422
|
+
.update-card-action-primary:active:not(:disabled) {
|
|
9423
|
+
transform: translateY(0);
|
|
9424
|
+
filter: brightness(0.96);
|
|
9425
|
+
}
|
|
9426
|
+
.update-card-action:disabled {
|
|
9427
|
+
opacity: 0.7;
|
|
9428
|
+
cursor: progress;
|
|
9429
|
+
filter: none;
|
|
9430
|
+
transform: none;
|
|
9431
|
+
}
|
|
9432
|
+
.update-card.is-success .update-card-action-primary {
|
|
9433
|
+
background: linear-gradient(135deg, var(--success) 0%, var(--success-hover) 100%);
|
|
9434
|
+
box-shadow: 0 4px 12px var(--success-glow), inset 0 1px 0 rgba(255,255,255,0.25);
|
|
9435
|
+
}
|
|
9436
|
+
.update-card.is-success .update-card-action-primary:hover:not(:disabled) {
|
|
9437
|
+
box-shadow: 0 6px 16px var(--success-glow), inset 0 1px 0 rgba(255,255,255,0.3);
|
|
9438
|
+
}
|
|
9439
|
+
|
|
9188
9440
|
/* Restart overlay */
|
|
9189
9441
|
.restart-overlay {
|
|
9190
9442
|
position: fixed;
|
|
@@ -11676,6 +11928,202 @@
|
|
|
11676
11928
|
cursor: pointer;
|
|
11677
11929
|
display: inline-block;
|
|
11678
11930
|
}
|
|
11931
|
+
/* Toggle 行右侧附属操作(如「查看」按钮 + 开关同列) */
|
|
11932
|
+
.settings-toggle-aside {
|
|
11933
|
+
flex: 0 0 auto;
|
|
11934
|
+
display: flex;
|
|
11935
|
+
align-items: center;
|
|
11936
|
+
gap: 10px;
|
|
11937
|
+
}
|
|
11938
|
+
|
|
11939
|
+
/* ── Env preview modal ── */
|
|
11940
|
+
.env-preview-modal {
|
|
11941
|
+
max-width: 720px;
|
|
11942
|
+
}
|
|
11943
|
+
.env-preview-body {
|
|
11944
|
+
display: flex;
|
|
11945
|
+
flex-direction: column;
|
|
11946
|
+
gap: 12px;
|
|
11947
|
+
padding: 18px 24px 8px;
|
|
11948
|
+
min-height: 0;
|
|
11949
|
+
}
|
|
11950
|
+
.env-preview-toolbar {
|
|
11951
|
+
display: flex;
|
|
11952
|
+
align-items: center;
|
|
11953
|
+
justify-content: space-between;
|
|
11954
|
+
gap: 12px;
|
|
11955
|
+
flex-wrap: wrap;
|
|
11956
|
+
}
|
|
11957
|
+
.env-preview-meta {
|
|
11958
|
+
display: inline-flex;
|
|
11959
|
+
align-items: center;
|
|
11960
|
+
gap: 8px;
|
|
11961
|
+
font-size: 0.78rem;
|
|
11962
|
+
color: var(--text-secondary);
|
|
11963
|
+
}
|
|
11964
|
+
.env-preview-pill {
|
|
11965
|
+
display: inline-flex;
|
|
11966
|
+
align-items: center;
|
|
11967
|
+
padding: 2px 10px;
|
|
11968
|
+
border-radius: var(--radius-full);
|
|
11969
|
+
font-size: 0.72rem;
|
|
11970
|
+
font-weight: 600;
|
|
11971
|
+
letter-spacing: 0.02em;
|
|
11972
|
+
border: 1px solid var(--border-subtle);
|
|
11973
|
+
}
|
|
11974
|
+
.env-preview-pill.is-inherit {
|
|
11975
|
+
color: var(--accent);
|
|
11976
|
+
background: var(--accent-muted);
|
|
11977
|
+
border-color: var(--accent);
|
|
11978
|
+
}
|
|
11979
|
+
.env-preview-pill.is-minimal {
|
|
11980
|
+
color: var(--success);
|
|
11981
|
+
background: var(--success-muted);
|
|
11982
|
+
border-color: var(--success);
|
|
11983
|
+
}
|
|
11984
|
+
.env-preview-count {
|
|
11985
|
+
font-size: 0.72rem;
|
|
11986
|
+
color: var(--text-muted);
|
|
11987
|
+
}
|
|
11988
|
+
.env-preview-controls {
|
|
11989
|
+
display: flex;
|
|
11990
|
+
align-items: center;
|
|
11991
|
+
gap: 10px;
|
|
11992
|
+
flex-wrap: wrap;
|
|
11993
|
+
}
|
|
11994
|
+
.env-preview-search {
|
|
11995
|
+
width: 200px;
|
|
11996
|
+
max-width: 100%;
|
|
11997
|
+
padding: 6px 10px;
|
|
11998
|
+
border-radius: var(--radius-sm);
|
|
11999
|
+
border: 1px solid var(--border);
|
|
12000
|
+
background: var(--bg-elevated);
|
|
12001
|
+
font-size: 0.78rem;
|
|
12002
|
+
color: var(--text-primary);
|
|
12003
|
+
}
|
|
12004
|
+
.env-preview-search:focus {
|
|
12005
|
+
outline: none;
|
|
12006
|
+
border-color: var(--border-focus);
|
|
12007
|
+
box-shadow: 0 0 0 3px var(--accent-glow);
|
|
12008
|
+
}
|
|
12009
|
+
.env-preview-reveal {
|
|
12010
|
+
display: inline-flex;
|
|
12011
|
+
align-items: center;
|
|
12012
|
+
gap: 6px;
|
|
12013
|
+
font-size: 0.74rem;
|
|
12014
|
+
color: var(--text-secondary);
|
|
12015
|
+
cursor: pointer;
|
|
12016
|
+
user-select: none;
|
|
12017
|
+
}
|
|
12018
|
+
.env-preview-reveal input {
|
|
12019
|
+
accent-color: var(--accent);
|
|
12020
|
+
cursor: pointer;
|
|
12021
|
+
}
|
|
12022
|
+
.env-preview-list {
|
|
12023
|
+
max-height: min(56vh, 460px);
|
|
12024
|
+
overflow-y: auto;
|
|
12025
|
+
border-radius: var(--radius-sm);
|
|
12026
|
+
border: 1px solid var(--border-subtle);
|
|
12027
|
+
background: var(--bg-elevated);
|
|
12028
|
+
padding: 4px;
|
|
12029
|
+
}
|
|
12030
|
+
.env-preview-list:focus { outline: none; }
|
|
12031
|
+
.env-preview-row {
|
|
12032
|
+
display: grid;
|
|
12033
|
+
grid-template-columns: minmax(0, 220px) minmax(0, 1fr) auto;
|
|
12034
|
+
gap: 12px;
|
|
12035
|
+
align-items: center;
|
|
12036
|
+
padding: 8px 10px;
|
|
12037
|
+
border-radius: var(--radius-xs);
|
|
12038
|
+
font-family: var(--font-mono, ui-monospace, "SF Mono", Menlo, Consolas, monospace);
|
|
12039
|
+
font-size: 0.74rem;
|
|
12040
|
+
transition: background 0.12s;
|
|
12041
|
+
}
|
|
12042
|
+
.env-preview-row:hover {
|
|
12043
|
+
background: var(--bg-tertiary);
|
|
12044
|
+
}
|
|
12045
|
+
.env-preview-row + .env-preview-row {
|
|
12046
|
+
border-top: 1px dashed var(--border-subtle);
|
|
12047
|
+
}
|
|
12048
|
+
.env-preview-name {
|
|
12049
|
+
color: var(--text-primary);
|
|
12050
|
+
font-weight: 600;
|
|
12051
|
+
display: inline-flex;
|
|
12052
|
+
align-items: center;
|
|
12053
|
+
gap: 6px;
|
|
12054
|
+
flex-wrap: wrap;
|
|
12055
|
+
overflow-wrap: anywhere;
|
|
12056
|
+
}
|
|
12057
|
+
.env-preview-row.is-sensitive .env-preview-name {
|
|
12058
|
+
color: var(--warning);
|
|
12059
|
+
}
|
|
12060
|
+
.env-preview-badge {
|
|
12061
|
+
display: inline-flex;
|
|
12062
|
+
align-items: center;
|
|
12063
|
+
padding: 1px 6px;
|
|
12064
|
+
border-radius: var(--radius-full);
|
|
12065
|
+
font-family: inherit;
|
|
12066
|
+
font-size: 0.62rem;
|
|
12067
|
+
font-weight: 600;
|
|
12068
|
+
letter-spacing: 0.04em;
|
|
12069
|
+
background: var(--warning-muted);
|
|
12070
|
+
color: var(--warning);
|
|
12071
|
+
border: 1px solid var(--warning);
|
|
12072
|
+
}
|
|
12073
|
+
.env-preview-badge-runtime {
|
|
12074
|
+
background: var(--info-muted);
|
|
12075
|
+
color: var(--info);
|
|
12076
|
+
border-color: var(--info);
|
|
12077
|
+
}
|
|
12078
|
+
.env-preview-value {
|
|
12079
|
+
color: var(--text-secondary);
|
|
12080
|
+
overflow: hidden;
|
|
12081
|
+
text-overflow: ellipsis;
|
|
12082
|
+
white-space: nowrap;
|
|
12083
|
+
min-width: 0;
|
|
12084
|
+
}
|
|
12085
|
+
.env-preview-value.is-runtime {
|
|
12086
|
+
font-style: italic;
|
|
12087
|
+
color: var(--text-muted);
|
|
12088
|
+
}
|
|
12089
|
+
.env-preview-len {
|
|
12090
|
+
color: var(--text-muted);
|
|
12091
|
+
font-size: 0.68rem;
|
|
12092
|
+
white-space: nowrap;
|
|
12093
|
+
}
|
|
12094
|
+
.env-preview-loading,
|
|
12095
|
+
.env-preview-empty {
|
|
12096
|
+
padding: 24px 8px;
|
|
12097
|
+
text-align: center;
|
|
12098
|
+
color: var(--text-muted);
|
|
12099
|
+
font-size: 0.78rem;
|
|
12100
|
+
}
|
|
12101
|
+
.env-preview-footer {
|
|
12102
|
+
display: flex;
|
|
12103
|
+
align-items: center;
|
|
12104
|
+
justify-content: space-between;
|
|
12105
|
+
gap: 12px;
|
|
12106
|
+
padding: 14px 24px 18px;
|
|
12107
|
+
}
|
|
12108
|
+
.env-preview-hint {
|
|
12109
|
+
font-size: 0.7rem;
|
|
12110
|
+
color: var(--text-muted);
|
|
12111
|
+
flex: 1;
|
|
12112
|
+
min-width: 0;
|
|
12113
|
+
}
|
|
12114
|
+
|
|
12115
|
+
@media (max-width: 640px) {
|
|
12116
|
+
.env-preview-modal { max-width: calc(100vw - 24px); }
|
|
12117
|
+
.env-preview-row {
|
|
12118
|
+
grid-template-columns: 1fr;
|
|
12119
|
+
gap: 4px;
|
|
12120
|
+
}
|
|
12121
|
+
.env-preview-len {
|
|
12122
|
+
justify-self: end;
|
|
12123
|
+
}
|
|
12124
|
+
.env-preview-search { width: 100%; }
|
|
12125
|
+
.env-preview-controls { width: 100%; }
|
|
12126
|
+
}
|
|
11679
12127
|
|
|
11680
12128
|
/* Range 行(音量等) */
|
|
11681
12129
|
.settings-range-row {
|