@noob-stupid/dsh-plugin-console 0.3.33 → 0.3.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/README.zh.md +18 -0
- package/lib/client.js +332 -46
- package/lib/index.js +891 -73
- package/package.json +44 -44
package/README.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
> **English**: [README.md](README.md) | **中文**: [README.zh.md](README.zh.md)
|
|
2
2
|
|
|
3
|
+
> **📢 npm release delayed to Sep 15, 2026**
|
|
4
|
+
>
|
|
5
|
+
> npm still serves **0.3.33** of `@noob-stupid/dsh-plugin-console`; **the latest (v0.3.46) will be published on 2026-09-15**.
|
|
6
|
+
> Until then, get it straight from this repo / the [v0.3.46 release](https://github.com/Noob-stupid/dsh-plugin-hub/releases/tag/v0.3.46):
|
|
7
|
+
> copy `lib/` and `package.json` into `~/.dsh/profiles/<profile>/node_modules/@noob-stupid/dsh-plugin-console/` and restart DSH (identical to the npm package).
|
|
8
|
+
> After Sep 15: 「检测更新 → 更新并适配」 in the console, or `dsh plugin add @noob-stupid/dsh-plugin-console`.
|
|
9
|
+
|
|
3
10
|
---
|
|
4
11
|
|
|
5
12
|
<img width="1170" height="609" alt="image" src="https://github.com/user-attachments/assets/b802d606-14ba-4151-9956-ff642ed12b0a" />
|
|
@@ -299,3 +306,14 @@ See [CHANGELOG.md](CHANGELOG.md).
|
|
|
299
306
|
## License
|
|
300
307
|
|
|
301
308
|
MIT
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## Community
|
|
313
|
+
|
|
314
|
+
<img src="./community-group.png" alt="Scan to join the dsh-plugin-hub community group" width="240">
|
|
315
|
+
|
|
316
|
+
Scan to join the **dsh-plugin-hub community group** (QQ) — usage questions, bug reports, plugin recommendations, and framework-upgrade war stories are all welcome.
|
|
317
|
+
|
|
318
|
+
> Field reports about the framework upgrade / rollback module (version spans, whether a rollback was clean, config-migration issues) are especially welcome — in the group or in [Discussions](https://github.com/Noob-stupid/dsh-plugin-hub/discussions). Every incident becomes a regression test.
|
|
319
|
+
|
package/README.zh.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
> **English**: [README.md](README.md) | **中文**: [README.zh.md](README.zh.md)
|
|
2
2
|
|
|
3
|
+
> **📢 npm 版本顺延到 9 月 15 日发布**
|
|
4
|
+
>
|
|
5
|
+
> npm 上 `@noob-stupid/dsh-plugin-console` 目前仍是 **0.3.33**;**最新版(v0.3.46)将于 2026-09-15 发布到 npm**。
|
|
6
|
+
> 这几天想用新版,直接从本仓库 / [v0.3.46 Release](https://github.com/Noob-stupid/dsh-plugin-hub/releases/tag/v0.3.46) 取源码:
|
|
7
|
+
> 把 `lib/` 与 `package.json` 放进 `~/.dsh/profiles/<profile>/node_modules/@noob-stupid/dsh-plugin-console/` 后重启 DSH 即可(内容与 npm 包一致)。
|
|
8
|
+
> 9/15 之后在控制台点「检测更新 → 更新并适配」,或 `dsh plugin add @noob-stupid/dsh-plugin-console`。
|
|
9
|
+
|
|
3
10
|
---
|
|
4
11
|
|
|
5
12
|
<img width="1170" height="609" alt="image" src="https://github.com/user-attachments/assets/b802d606-14ba-4151-9956-ff642ed12b0a" />
|
|
@@ -266,3 +273,14 @@ git clone https://github.com/Noob-stupid/dsh-plugin-hub /tmp/dsh-plugin-console
|
|
|
266
273
|
## License
|
|
267
274
|
|
|
268
275
|
MIT
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## 交流群
|
|
280
|
+
|
|
281
|
+
<img src="./community-group.png" alt="扫码加入 dsh-plugin-hub 交流群" width="240">
|
|
282
|
+
|
|
283
|
+
扫码加入 **dsh-plugin-hub 交流群** —— 使用问题、Bug 反馈、插件推荐、框架升级踩坑,都欢迎在群里聊。
|
|
284
|
+
|
|
285
|
+
> 框架升级/回滚模块的实测反馈(版本跨度、回滚是否干净、配置迁移问题)也欢迎在 [Discussions](https://github.com/Noob-stupid/dsh-plugin-hub/discussions) 或群里说——每一个事故和异常都会变成一条回归测试。
|
|
286
|
+
|
package/lib/client.js
CHANGED
|
@@ -124,6 +124,34 @@ window.__ModuleLoader__.load({
|
|
|
124
124
|
adaptUnlockBtn: "已适配,立即解锁",
|
|
125
125
|
adaptUnlockHint: "已安装版本经源码扫描确认适配当前框架:立即移除禁用块并解锁启用",
|
|
126
126
|
adaptUnlockDone: "已适配解锁:刷新后该行可正常启用",
|
|
127
|
+
riskyEnableTitle: "强行启用不适配插件?",
|
|
128
|
+
riskyEnableBody: "该插件在当前框架下被判为不适配(框架升级时已被自动禁用)。强行启用后,DSH 下次启动可能失败、需要手动处理。确认强行启用?",
|
|
129
|
+
riskyEnableConfirm: "确认强行启用",
|
|
130
|
+
riskyEnableCancel: "取消",
|
|
131
|
+
adoptableDetected: "检测到已适配:当前版本 {version} 已通过源码扫描,点「已适配,立即解锁」即可启用(不会自动开启)",
|
|
132
|
+
compatGateLabel: "兼容门",
|
|
133
|
+
compatGateAutoDisable: "升级时自动禁用不适配插件",
|
|
134
|
+
compatGateAutoDetect: "打开时自动检测已适配(仅提示)",
|
|
135
|
+
compatGateHint: "关掉即回到纯手动:升级只提示、不动你的开关;检测也不提示,由你自己判断",
|
|
136
|
+
compatGateSaved: "兼容门设置已保存",
|
|
137
|
+
gateBtn: "门控",
|
|
138
|
+
gateTitle: "门控:升级时自动禁用不适配插件、打开时自动检测已适配",
|
|
139
|
+
gateModalTitle: "门控(兼容门总开关)",
|
|
140
|
+
gatePendingInfo: "当前待适配:{n} 行(升级预扫或启动失败隔离记下的,更新插件后可一键解锁)",
|
|
141
|
+
gatePendingNone: "当前没有待适配的行",
|
|
142
|
+
fwFailedButUpgraded: "框架本体其实已经升到 {v} 并已生效——失败的只是脚本最后「重启服务」那一步。可点「重启服务」确认,或必要时回滚。",
|
|
143
|
+
fwPanelBtn: "框架",
|
|
144
|
+
fwPanelTitle: "框架升级 / 回滚",
|
|
145
|
+
fwPanelCurrent: "当前版本",
|
|
146
|
+
fwPanelAvailable: "可升级到 {v}",
|
|
147
|
+
fwPanelUpToDate: "已是最新(latest {latest} · next {next})",
|
|
148
|
+
fwPanelCheckFailed: "版本检查失败:{err}",
|
|
149
|
+
fwPanelChecking: "检查中…",
|
|
150
|
+
fwPanelRecheck: "重新检查版本",
|
|
151
|
+
fwPanelRefreshStatus: "刷新进度",
|
|
152
|
+
fwPanelNoRecord: "还没有升级记录(这里会常驻显示最近一次升级/回滚的每一步)",
|
|
153
|
+
fwPanelConfirmBody: "确认升级到 {v}?脚本会自动:备份配置 → 预扫禁用不适配行 → 停服 → 装新版 → 拉起服务;起不来会先隔离重试,最后才整包回滚。",
|
|
154
|
+
fwRollbackDone: "已回滚到 {v}(当前就是快照版本,没有更早的可回)",
|
|
127
155
|
migrateTip: "项目已迁移(适配当前框架)",
|
|
128
156
|
migrateBtn: "迁移并适配",
|
|
129
157
|
aiEmpowerFrameworkCheck: "框架适配检测",
|
|
@@ -467,6 +495,34 @@ window.__ModuleLoader__.load({
|
|
|
467
495
|
adaptUnlockBtn: "Adapted - unlock now",
|
|
468
496
|
adaptUnlockHint: "Installed version passed the source scan for the current framework: unlock immediately",
|
|
469
497
|
adaptUnlockDone: "Unlocked: refresh and the row is enabled",
|
|
498
|
+
riskyEnableTitle: "Force-enable an incompatible plugin?",
|
|
499
|
+
riskyEnableBody: "This plugin is judged incompatible with the current framework (auto-disabled during the upgrade). Force-enabling it may make the next DSH start fail and require manual recovery. Force-enable?",
|
|
500
|
+
riskyEnableConfirm: "Force enable",
|
|
501
|
+
riskyEnableCancel: "Cancel",
|
|
502
|
+
adoptableDetected: "Now compatible: version {version} passed the source scan — click \"Adapted - unlock now\" to enable it (never auto-enabled)",
|
|
503
|
+
compatGateLabel: "Compat gate",
|
|
504
|
+
compatGateAutoDisable: "Auto-disable incompatible plugins on upgrade",
|
|
505
|
+
compatGateAutoDetect: "Detect adapted plugins on open (hint only)",
|
|
506
|
+
compatGateHint: "Turn off to go fully manual: the upgrade only warns and never touches your switches, and no detection hints are shown",
|
|
507
|
+
compatGateSaved: "Compat gate settings saved",
|
|
508
|
+
gateBtn: "Gating",
|
|
509
|
+
gateTitle: "Gating: auto-disable incompatible plugins on upgrade, detect adapted ones on open",
|
|
510
|
+
gateModalTitle: "Gating (compat gate master switches)",
|
|
511
|
+
gatePendingInfo: "Pending adaptation: {n} row(s) — recorded by the upgrade pre-scan or boot quarantine; unlock them after updating the plugin",
|
|
512
|
+
gatePendingNone: "No rows pending adaptation",
|
|
513
|
+
fwFailedButUpgraded: "The framework itself is already on {v} and active — only the final \"restart service\" step failed. Use Restart service to confirm, or roll back if needed.",
|
|
514
|
+
fwPanelBtn: "Framework",
|
|
515
|
+
fwPanelTitle: "Framework upgrade / rollback",
|
|
516
|
+
fwPanelCurrent: "Installed",
|
|
517
|
+
fwPanelAvailable: "Upgrade available: {v}",
|
|
518
|
+
fwPanelUpToDate: "Up to date (latest {latest} · next {next})",
|
|
519
|
+
fwPanelCheckFailed: "Version check failed: {err}",
|
|
520
|
+
fwPanelChecking: "checking…",
|
|
521
|
+
fwPanelRecheck: "Re-check version",
|
|
522
|
+
fwPanelRefreshStatus: "Refresh progress",
|
|
523
|
+
fwPanelNoRecord: "No upgrade record yet (the last upgrade/rollback steps will always be shown here)",
|
|
524
|
+
fwPanelConfirmBody: "Upgrade to {v}? The script will back up config, pre-disable incompatible rows, stop the service, install the new version and relaunch it; if it fails to start it quarantines and retries first, and only then rolls back the whole tree.",
|
|
525
|
+
fwRollbackDone: "Already rolled back to {v} (this is the snapshot version; nothing earlier to restore)",
|
|
470
526
|
migrateTip: "Project migrated (adapted to the current framework)",
|
|
471
527
|
migrateBtn: "Migrate & adapt",
|
|
472
528
|
aiEmpowerFrameworkCheck: "Framework compat check",
|
|
@@ -813,7 +869,10 @@ window.__ModuleLoader__.load({
|
|
|
813
869
|
data = await response.json();
|
|
814
870
|
} catch {}
|
|
815
871
|
if (!response.ok || (data !== null && data.ok === false)) {
|
|
816
|
-
|
|
872
|
+
const error = new Error(data !== null && typeof data.error === "string" ? data.error : "HTTP " + response.status);
|
|
873
|
+
// 结构化错误码随错误对象带回(如适配门软禁的 compat-confirm → 前端要弹风险确认)
|
|
874
|
+
if (data !== null && data.details !== undefined) error.details = data.details;
|
|
875
|
+
throw error;
|
|
817
876
|
}
|
|
818
877
|
return data;
|
|
819
878
|
}
|
|
@@ -1071,6 +1130,8 @@ window.__ModuleLoader__.load({
|
|
|
1071
1130
|
const [state, setState] = react.useState({ status: "loading" });
|
|
1072
1131
|
const [busy, setBusy] = react.useState(null);
|
|
1073
1132
|
const [adaptUnlocking, setAdaptUnlocking] = react.useState(null);
|
|
1133
|
+
// 待适配行「强行启用」的风险确认态:{ entryId, moduleName, frameworkVersion, checkNote } | null
|
|
1134
|
+
const [riskyConfirm, setRiskyConfirm] = react.useState(null);
|
|
1074
1135
|
const [message, setMessage] = react.useState(null);
|
|
1075
1136
|
const [reloadHint, setReloadHint] = react.useState(false);
|
|
1076
1137
|
const [restartHint, setRestartHint] = react.useState(false);
|
|
@@ -1229,18 +1290,79 @@ window.__ModuleLoader__.load({
|
|
|
1229
1290
|
);
|
|
1230
1291
|
}, 3000);
|
|
1231
1292
|
};
|
|
1293
|
+
// 功能包 → [框架] 常驻面板(用户要求:卡片被关掉后就找不回来了,框架升级/回滚要常驻可入口)
|
|
1294
|
+
const [fwPanelOpen, setFwPanelOpen] = react.useState(false);
|
|
1295
|
+
const [fwCheck, setFwCheck] = react.useState(null);
|
|
1296
|
+
const [fwCheckBusy, setFwCheckBusy] = react.useState(false);
|
|
1297
|
+
const [fwConfirmPanel, setFwConfirmPanel] = react.useState(false);
|
|
1298
|
+
// 面板里的状态刷新:**无视**「终态已关闭」标记——卡片可以被永久关掉,面板必须永远能查到真相
|
|
1299
|
+
const fwStatusRefresh = () => {
|
|
1300
|
+
call("/plugin-console/framework-upgrade-status").then(
|
|
1301
|
+
(data) => {
|
|
1302
|
+
if (data && typeof data.status === "string") {
|
|
1303
|
+
setFrameworkStatus(data);
|
|
1304
|
+
if (data.status !== "done" && data.status !== "failed") pollFrameworkStatus();
|
|
1305
|
+
}
|
|
1306
|
+
},
|
|
1307
|
+
() => {},
|
|
1308
|
+
);
|
|
1309
|
+
};
|
|
1310
|
+
const fwCheckRefresh = (refresh) => {
|
|
1311
|
+
setFwCheckBusy(true);
|
|
1312
|
+
call("/plugin-console/framework-check", refresh === true ? { refresh: true } : {}).then(
|
|
1313
|
+
(data) => { setFwCheckBusy(false); if (data && data.ok === true) setFwCheck(data); },
|
|
1314
|
+
(error) => { setFwCheckBusy(false); setMessage(t("failed") + ":" + friendlyGithubError(error).message); },
|
|
1315
|
+
);
|
|
1316
|
+
};
|
|
1317
|
+
const doFrameworkRollback = () => {
|
|
1318
|
+
setFrameworkUpgrading(true);
|
|
1319
|
+
call("/plugin-console/framework-rollback", {}).then(
|
|
1320
|
+
(data) => {
|
|
1321
|
+
setFrameworkUpgrading(false);
|
|
1322
|
+
setMessage(t("frameworkRollbackDone").replace("{from}", String(data?.from ?? "?")));
|
|
1323
|
+
setFrameworkStatus({ status: "rollback", message: t("frameworkRollbackDone").replace("{from}", String(data?.from ?? "?")) });
|
|
1324
|
+
pollFrameworkStatus();
|
|
1325
|
+
},
|
|
1326
|
+
(error) => { setFrameworkUpgrading(false); setMessage(t("failed") + ":" + friendlyGithubError(error).message); },
|
|
1327
|
+
);
|
|
1328
|
+
};
|
|
1329
|
+
// 回滚按钮可用性:已经处在「回滚目标版本」时不再提供回滚(点了等于恢复现状)。
|
|
1330
|
+
// v0.3.41:客户端**自己算**一遍,不只信服务端的 applicable ——
|
|
1331
|
+
// 服务端 0.3.38 还没这个字段时(用户实测「明明版本对了回滚还亮着」),
|
|
1332
|
+
// 只要 /state 里已有 framework.version 与 rollback.from 就能判定,刷新页面即生效。
|
|
1333
|
+
const rollbackInfo = state.status === "ready" ? state.data.rollback : null;
|
|
1334
|
+
const curFwVersion = state.status === "ready" && typeof state.data.framework?.version === "string" ? state.data.framework.version : null;
|
|
1335
|
+
const rollbackUsable = rollbackInfo !== null && rollbackInfo !== undefined
|
|
1336
|
+
&& rollbackInfo.applicable !== false
|
|
1337
|
+
&& !(curFwVersion !== null && rollbackInfo.from !== null && rollbackInfo.from !== undefined && curFwVersion === rollbackInfo.from);
|
|
1338
|
+
// 升级步骤视图(卡片与常驻面板共用;失败时按 stage 标已完成步骤,不整列红叉)
|
|
1339
|
+
const fwStepsView = (st) => {
|
|
1340
|
+
const cur = FW_STEPS.findIndex((s) => s.key === st.status);
|
|
1341
|
+
const failedStage = st.status === "failed"
|
|
1342
|
+
? (st.stage ?? (st.frameworkAtTarget ? "relaunching" : null))
|
|
1343
|
+
: null;
|
|
1344
|
+
const stageIdx = failedStage ? FW_STEPS.findIndex((s) => s.key === failedStage) : -1;
|
|
1345
|
+
return el("ul", { className: styles.market }, FW_STEPS.map((step, idx) => {
|
|
1346
|
+
const state2 = st.status === "done"
|
|
1347
|
+
? "ok"
|
|
1348
|
+
: st.status === "failed"
|
|
1349
|
+
? (stageIdx >= 0 ? (idx < stageIdx ? "ok" : (idx === stageIdx ? "fail" : "wait")) : "fail")
|
|
1350
|
+
: (idx < cur ? "ok" : (idx === cur ? "cur" : "wait"));
|
|
1351
|
+
return el("li", { key: step.key, className: styles.item },
|
|
1352
|
+
el("div", { className: styles.itemTop },
|
|
1353
|
+
el("code", { className: styles.name }, (state2 === "ok" ? "✓ " : state2 === "cur" ? "⟳ " : state2 === "fail" ? "✕ " : "· ") + step.label),
|
|
1354
|
+
state2 === "cur" ? el("span", { className: styles.spinner }) : null));
|
|
1355
|
+
}));
|
|
1356
|
+
};
|
|
1232
1357
|
// 挂载/重连后恢复升级进度(若上次升级仍在进行/刚完成)
|
|
1233
1358
|
react.useEffect(() => {
|
|
1234
1359
|
call("/plugin-console/framework-upgrade-status").then(
|
|
1235
1360
|
(data) => {
|
|
1361
|
+
// 状态照收([框架] 按钮角标与常驻面板都要用);**卡片是否显示**由 fwShowCard 统一决定,
|
|
1362
|
+
// 不再在这里用 localStorage 标记把状态整个吞掉(那会让角标也瞎掉)。
|
|
1236
1363
|
if (data && typeof data.status === "string" && data.status !== "idle") {
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
try { dismissedTerminal = localStorage.getItem("pc-fw-dismiss-terminal"); } catch {}
|
|
1240
|
-
if (data.status !== dismissedTerminal) {
|
|
1241
|
-
setFrameworkStatus(data);
|
|
1242
|
-
if (data.status !== "done" && data.status !== "failed") pollFrameworkStatus();
|
|
1243
|
-
}
|
|
1364
|
+
setFrameworkStatus(data);
|
|
1365
|
+
if (data.status !== "done" && data.status !== "failed") pollFrameworkStatus();
|
|
1244
1366
|
}
|
|
1245
1367
|
},
|
|
1246
1368
|
() => {},
|
|
@@ -1300,6 +1422,8 @@ window.__ModuleLoader__.load({
|
|
|
1300
1422
|
const [toolbarOpen, setToolbarOpen] = react.useState(() => {
|
|
1301
1423
|
try { return localStorage.getItem("pc-toolbar-open") === "1"; } catch { return false; }
|
|
1302
1424
|
});
|
|
1425
|
+
// 门控面板(用户要求:总开关收进「功能包」里的 [门控] 按钮,不再占已安装列表的表头)
|
|
1426
|
+
const [gateOpen, setGateOpen] = react.useState(false);
|
|
1303
1427
|
// 功能包位置:长按拖动,持久化 localStorage(每次打开沿用上次位置)
|
|
1304
1428
|
const [toolbarPos, setToolbarPos] = react.useState(() => {
|
|
1305
1429
|
try {
|
|
@@ -1797,18 +1921,39 @@ window.__ModuleLoader__.load({
|
|
|
1797
1921
|
(error) => setUpdateCheck({ status: "ready", latest: null, source: "npm", error: friendlyGithubError(error).message }),
|
|
1798
1922
|
);
|
|
1799
1923
|
};
|
|
1800
|
-
const toggle = (entry, enabled) => {
|
|
1924
|
+
const toggle = (entry, enabled, confirmRisky = false) => {
|
|
1801
1925
|
setBusy(entry.entryId);
|
|
1802
1926
|
setMessage(null);
|
|
1803
|
-
call("/plugin-console/toggle", { entryId: entry.entryId, enabled }).then(
|
|
1927
|
+
call("/plugin-console/toggle", { entryId: entry.entryId, enabled, confirmRisky }).then(
|
|
1804
1928
|
() => {
|
|
1929
|
+
setRiskyConfirm(null);
|
|
1805
1930
|
setMessage(t(enabled ? "toggledOn" : "toggledOff") + ":" + entry.entryId + "。" + t("autoReload"));
|
|
1806
1931
|
// HMR 应用补丁后自动强刷页面,让客户端插件的挂载/卸载即时可见
|
|
1807
1932
|
window.setTimeout(() => window.location.reload(), 1500);
|
|
1808
1933
|
},
|
|
1809
|
-
(error) =>
|
|
1934
|
+
(error) => {
|
|
1935
|
+
// 适配门软禁(用户定案):服务端不直接拒绝,而是要求确认 → 弹风险提示,由你决定开不开
|
|
1936
|
+
const details = error !== null && typeof error === "object" ? error.details : null;
|
|
1937
|
+
if (details !== null && details !== undefined && details.code === "compat-confirm") {
|
|
1938
|
+
setRiskyConfirm({ entryId: entry.entryId, moduleName: details.moduleName ?? entry.moduleName ?? null, frameworkVersion: details.frameworkVersion ?? null, checkNote: details.checkNote ?? null });
|
|
1939
|
+
return;
|
|
1940
|
+
}
|
|
1941
|
+
setMessage(t("failed") + ":" + friendlyGithubError(error).message);
|
|
1942
|
+
},
|
|
1810
1943
|
).finally(() => setBusy(null));
|
|
1811
1944
|
};
|
|
1945
|
+
/** 兼容门总开关:autoDisable(升级时自动禁用不适配)/ autoDetect(打开时自动检测已适配)。 */
|
|
1946
|
+
const setCompatGate = (patch) => {
|
|
1947
|
+
call("/plugin-console/compat-gate", patch).then(
|
|
1948
|
+
(data) => {
|
|
1949
|
+
setState((prev) => (prev !== null && prev.status === "ready"
|
|
1950
|
+
? { ...prev, data: { ...prev.data, compatGate: data.compatGate } }
|
|
1951
|
+
: prev));
|
|
1952
|
+
setMessage(t("compatGateSaved"));
|
|
1953
|
+
},
|
|
1954
|
+
(error) => setMessage(t("failed") + ":" + friendlyGithubError(error).message),
|
|
1955
|
+
);
|
|
1956
|
+
};
|
|
1812
1957
|
const levenshtein = (a, b) => {
|
|
1813
1958
|
const m = a.length, n = b.length;
|
|
1814
1959
|
if (m === 0) return n;
|
|
@@ -2469,8 +2614,8 @@ window.__ModuleLoader__.load({
|
|
|
2469
2614
|
setFrameworkUpgrading(false);
|
|
2470
2615
|
setConfirmFrameworkUpgrade(false);
|
|
2471
2616
|
if (data && data.ok === true) {
|
|
2472
|
-
//
|
|
2473
|
-
|
|
2617
|
+
// 新一轮升级开始:让卡片恢复显示(清掉"本次会话隐藏",运行身份由时间戳自然区分)
|
|
2618
|
+
setFwStatusDismissed(false);
|
|
2474
2619
|
setMessage(t("frameworkUpgradeDone")
|
|
2475
2620
|
+ (data.hasUpdate === true ? ":" + (data.current ?? "?") + " → " + (data.target ?? data.latest ?? "?") : (data.registryError ? t("frameworkUpgradeCheckFailed") : t("frameworkUpgradeUpToDate")))
|
|
2476
2621
|
+ "。备份:" + (data.backupDir ?? "") + "。" + (Array.isArray(data.hints) ? data.hints.join(" ") : ""));
|
|
@@ -2561,7 +2706,10 @@ window.__ModuleLoader__.load({
|
|
|
2561
2706
|
detailPanel = el("div", { className: styles.detail },
|
|
2562
2707
|
meta !== null ? el("p", { className: styles.desc }, meta.description ?? t("noDetail")) : null,
|
|
2563
2708
|
entry.pendingCompat === true
|
|
2564
|
-
? el("p", { className: styles.status, "data-pending":
|
|
2709
|
+
? el("p", { className: styles.status, "data-pending": entry.adoptable ? "false" : "true" },
|
|
2710
|
+
entry.adoptable
|
|
2711
|
+
? t("adoptableDetected").replace("{version}", String(entry.adoptable.version ?? "?")).replace("{prev}", String(compatInfo?.version ?? "?"))
|
|
2712
|
+
: t("pendingCompatHint") + (compatInfo && compatInfo.checkNote ? ";已知校验:" + compatInfo.checkNote : ""))
|
|
2565
2713
|
: null,
|
|
2566
2714
|
meta !== null && meta.version ? el("div", { className: styles.rowTop },
|
|
2567
2715
|
el("p", { className: styles.status }, t("versionLabel") + ":" + meta.version),
|
|
@@ -3062,13 +3210,29 @@ window.__ModuleLoader__.load({
|
|
|
3062
3210
|
);
|
|
3063
3211
|
}
|
|
3064
3212
|
}
|
|
3065
|
-
//
|
|
3066
|
-
//
|
|
3067
|
-
//
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
const
|
|
3213
|
+
// 升级卡片显示条件(v0.3.40 明确化,2026-09-11 用户连问两次「卡片为什么还在」):
|
|
3214
|
+
// - 升级/回滚**进行中** → 显示(实时进度);
|
|
3215
|
+
// - 结果**是自愈出来的**(脚本被强杀后按现实判定,用户根本没看着它跑)→ 不弹卡片,
|
|
3216
|
+
// 结果常驻在「功能包 → 框架」里,不打扰人;
|
|
3217
|
+
// - 结束的卡片点过叉号 → 只关闭**那一次**(按状态文件时间戳认身份),
|
|
3218
|
+
// 下次升级时间戳变了才会再弹;旧实现按 status 字符串记,导致"关了 failed 又冒出 done"。
|
|
3219
|
+
const fwRunKey = frameworkStatus !== null ? String(frameworkStatus.at ?? "") : "";
|
|
3220
|
+
const fwDismissedRun = (() => {
|
|
3221
|
+
try { return frameworkStatus !== null && localStorage.getItem("pc-fw-dismiss-at") === fwRunKey; } catch { return false; }
|
|
3222
|
+
})();
|
|
3223
|
+
const fwTerminal = frameworkStatus !== null && (frameworkStatus.status === "done" || frameworkStatus.status === "failed");
|
|
3224
|
+
const fwShowCard = frameworkStatus !== null && frameworkStatus.status !== "idle" && !fwStatusDismissed
|
|
3225
|
+
&& !fwDismissedRun
|
|
3226
|
+
&& !(fwTerminal && frameworkStatus.reconciled !== undefined);
|
|
3227
|
+
// 门控(兼容门总开关)取值:state 未就绪时按默认「都开」,与服务端默认一致
|
|
3228
|
+
const gateReady = state !== null && state.status === "ready";
|
|
3229
|
+
const gateAutoDisable = !gateReady || (state.data.compatGate ?? {}).autoDisable !== false;
|
|
3230
|
+
const gateAutoDetect = !gateReady || (state.data.compatGate ?? {}).autoDetect !== false;
|
|
3231
|
+
const gatePendingCount = gateReady ? (state.data.compatPending?.pending ?? []).length : 0;
|
|
3232
|
+
// 「功能包 → 框架」按钮角标:升级进行中 ⟳ / 上次失败 ✕ / 有可用更新 ↑(优先级从高到低)
|
|
3233
|
+
const fwRunning = frameworkStatus !== null
|
|
3234
|
+
&& frameworkStatus.status !== "idle" && frameworkStatus.status !== "done" && frameworkStatus.status !== "failed";
|
|
3235
|
+
const fwBadge = fwRunning ? " ⟳" : (frameworkStatus?.status === "failed" ? " ✕" : (fwCheck?.target ? " ↑" : ""));
|
|
3072
3236
|
return el("section", { ref: sectionRef, className: styles.section, "aria-busy": state.status === "loading" },
|
|
3073
3237
|
state.status === "ready" && state.data.compat && !state.data.compat.supported
|
|
3074
3238
|
? el("p", { className: styles.message, "data-error": "true" }, state.data.compat.notice)
|
|
@@ -3121,6 +3285,93 @@ window.__ModuleLoader__.load({
|
|
|
3121
3285
|
el("button", { type: "button", className: styles.toggle, disabled: compBusy === c.id, onClick: () => compStatusOne(c.id) }, t("compStatus")),
|
|
3122
3286
|
el("button", { type: "button", className: styles.toggle, disabled: !c.uiUrl, title: c.uiUrl ?? "", onClick: () => { if (c.uiUrl) window.open(c.uiUrl, "_blank", "noopener"); } }, t("compOpen"))))))
|
|
3123
3287
|
: null,
|
|
3288
|
+
fwPanelOpen
|
|
3289
|
+
? el("div", { className: styles.modalBackdrop, onClick: (event) => { if (event.target === event.currentTarget) { setFwPanelOpen(false); setFwConfirmPanel(false); } } },
|
|
3290
|
+
el("div", { className: styles.modalCard, style: { maxWidth: 560 } },
|
|
3291
|
+
el("div", { className: styles.rowTop, style: { justifyContent: "space-between" } },
|
|
3292
|
+
el("strong", { className: styles.name }, t("fwPanelTitle")),
|
|
3293
|
+
el("button", { type: "button", className: styles.trashBtn, style: { fontSize: 14, padding: "4px 8px" }, title: t("closeModal"), "aria-label": t("closeModal"), onClick: () => { setFwPanelOpen(false); setFwConfirmPanel(false); } }, "✕")),
|
|
3294
|
+
// ① 版本信息:当前 / 可用更新
|
|
3295
|
+
el("p", { className: styles.status },
|
|
3296
|
+
t("fwPanelCurrent") + ":" + (fwCheck?.current ?? (state.status === "ready" ? state.data.framework?.version : null) ?? "?")
|
|
3297
|
+
+ (fwCheckBusy ? "(" + t("fwPanelChecking") + ")" : "")),
|
|
3298
|
+
fwCheck === null
|
|
3299
|
+
? null
|
|
3300
|
+
: fwCheck.target
|
|
3301
|
+
? el("p", { className: styles.status, "data-pending": "true" }, t("fwPanelAvailable").replace("{v}", fwCheck.target) + "(latest " + (fwCheck.latest ?? "—") + " · next " + (fwCheck.next ?? "—") + ")")
|
|
3302
|
+
: fwCheck.registryError
|
|
3303
|
+
? el("p", { className: styles.message, "data-error": "true" }, t("fwPanelCheckFailed").replace("{err}", fwCheck.registryError))
|
|
3304
|
+
: el("p", { className: styles.status }, t("fwPanelUpToDate").replace("{latest}", fwCheck.latest ?? "—").replace("{next}", fwCheck.next ?? "—")),
|
|
3305
|
+
// ② 上次/当前升级记录(卡片可被永久关闭,这里是常驻的真相入口)
|
|
3306
|
+
frameworkStatus !== null && frameworkStatus.status !== "idle"
|
|
3307
|
+
? el("div", { style: { flexDirection: "column", gap: 8, display: "flex" } },
|
|
3308
|
+
el("strong", { className: styles.name }, t("frameworkUpgradeBtn") + " " + (frameworkStatus.status === "done" ? "✓" : (frameworkStatus.status === "failed" ? "✕" : "⟳"))),
|
|
3309
|
+
fwStepsView(frameworkStatus),
|
|
3310
|
+
frameworkStatus.status === "failed" && frameworkStatus.frameworkAtTarget
|
|
3311
|
+
? el("p", { className: styles.status }, t("fwFailedButUpgraded").replace("{v}", frameworkStatus.frameworkAtTarget))
|
|
3312
|
+
: null,
|
|
3313
|
+
frameworkStatus.reconciled
|
|
3314
|
+
? el("p", { className: styles.status, "data-pending": "true" }, "⚠ " + frameworkStatus.reconciled.note)
|
|
3315
|
+
: null,
|
|
3316
|
+
frameworkStatus.stalled && frameworkStatus.note
|
|
3317
|
+
? el("p", { className: styles.message, "data-warn": "true" }, "⚠ " + frameworkStatus.note)
|
|
3318
|
+
: null,
|
|
3319
|
+
frameworkStatus.message ? el("p", { className: styles.status, "data-error": frameworkStatus.status === "failed" ? "true" : undefined }, frameworkStatus.message) : null)
|
|
3320
|
+
: el("p", { className: styles.status }, t("fwPanelNoRecord")),
|
|
3321
|
+
// ③ 操作:升级(两步确认)/ 回滚 / 重启 / 重新检查
|
|
3322
|
+
el("div", { className: styles.rowTop, style: { flexWrap: "wrap" } },
|
|
3323
|
+
fwCheck?.target
|
|
3324
|
+
? (fwConfirmPanel
|
|
3325
|
+
? el("div", { style: { flexDirection: "column", gap: 8, display: "flex", flexBasis: "100%" } },
|
|
3326
|
+
el("p", { className: styles.message, "data-warn": "true" }, t("fwPanelConfirmBody").replace("{v}", fwCheck.target)),
|
|
3327
|
+
el("div", { className: styles.rowTop },
|
|
3328
|
+
el("button", { type: "button", className: styles.toggle, disabled: frameworkUpgrading, onClick: () => { setFwConfirmPanel(false); doFrameworkUpgrade(); } }, frameworkUpgrading ? t("installingLocal") : t("frameworkUpgradeConfirm")),
|
|
3329
|
+
el("button", { type: "button", className: styles.toggle, onClick: () => setFwConfirmPanel(false) }, t("closeModal"))))
|
|
3330
|
+
: el("button", { type: "button", className: styles.toggle, disabled: frameworkUpgrading, title: t("frameworkUpgradeTitle"), onClick: () => setFwConfirmPanel(true) }, t("frameworkUpgradeBtn") + " → v" + fwCheck.target))
|
|
3331
|
+
: null,
|
|
3332
|
+
rollbackUsable
|
|
3333
|
+
? el("button", { type: "button", className: styles.toggle, title: t("frameworkRollbackTitle"), disabled: frameworkUpgrading, onClick: doFrameworkRollback }, t("frameworkRollbackBtn") + " → " + (rollbackInfo.from ?? "?"))
|
|
3334
|
+
: (rollbackInfo !== null && rollbackInfo !== undefined
|
|
3335
|
+
? el("span", { className: styles.status }, t("fwRollbackDone").replace("{v}", String(rollbackInfo.from ?? "?")))
|
|
3336
|
+
: null),
|
|
3337
|
+
el("button", { type: "button", className: styles.toggle, onClick: fwStatusRefresh }, t("fwPanelRefreshStatus")),
|
|
3338
|
+
el("button", { type: "button", className: styles.toggle, disabled: fwCheckBusy, onClick: () => fwCheckRefresh(true) }, fwCheckBusy ? t("fwPanelChecking") : t("fwPanelRecheck"))),
|
|
3339
|
+
el("div", { className: styles.rowTop },
|
|
3340
|
+
el("button", { type: "button", className: styles.toggle, onClick: () => { setMessage(t("restarting")); call("/plugin-console/restart", {}).then(() => { window.setTimeout(() => window.location.reload(), 12000); }, () => {}); } }, t("restartService")))))
|
|
3341
|
+
: null,
|
|
3342
|
+
gateOpen
|
|
3343
|
+
? el("div", { className: styles.modalBackdrop, onClick: (event) => { if (event.target === event.currentTarget) setGateOpen(false); } },
|
|
3344
|
+
el("div", { className: styles.modalCard, style: { maxWidth: 520 } },
|
|
3345
|
+
el("div", { className: styles.rowTop, style: { justifyContent: "space-between" } },
|
|
3346
|
+
el("strong", { className: styles.name }, t("gateModalTitle")),
|
|
3347
|
+
el("button", { type: "button", className: styles.trashBtn, style: { fontSize: 14, padding: "4px 8px" }, title: t("closeModal"), "aria-label": t("closeModal"), onClick: () => setGateOpen(false) }, "✕")),
|
|
3348
|
+
el("div", { style: { flexDirection: "column", gap: 12, display: "flex" } },
|
|
3349
|
+
el("div", { className: styles.rowTop, style: { justifyContent: "space-between", gap: 12 } },
|
|
3350
|
+
el("span", { className: styles.status, style: { flex: 1 } }, t("compatGateAutoDisable")),
|
|
3351
|
+
el("div", {
|
|
3352
|
+
className: styles.pcSwitch,
|
|
3353
|
+
"data-on": gateAutoDisable ? "true" : "false",
|
|
3354
|
+
role: "switch",
|
|
3355
|
+
"aria-checked": gateAutoDisable ? "true" : "false",
|
|
3356
|
+
"aria-label": t("compatGateAutoDisable"),
|
|
3357
|
+
title: t("compatGateHint"),
|
|
3358
|
+
onClick: () => setCompatGate({ autoDisable: !gateAutoDisable }),
|
|
3359
|
+
}, el("div", { className: styles.pcSwitchKnob }))),
|
|
3360
|
+
el("div", { className: styles.rowTop, style: { justifyContent: "space-between", gap: 12 } },
|
|
3361
|
+
el("span", { className: styles.status, style: { flex: 1 } }, t("compatGateAutoDetect")),
|
|
3362
|
+
el("div", {
|
|
3363
|
+
className: styles.pcSwitch,
|
|
3364
|
+
"data-on": gateAutoDetect ? "true" : "false",
|
|
3365
|
+
role: "switch",
|
|
3366
|
+
"aria-checked": gateAutoDetect ? "true" : "false",
|
|
3367
|
+
"aria-label": t("compatGateAutoDetect"),
|
|
3368
|
+
title: t("compatGateHint"),
|
|
3369
|
+
onClick: () => setCompatGate({ autoDetect: !gateAutoDetect }),
|
|
3370
|
+
}, el("div", { className: styles.pcSwitchKnob }))),
|
|
3371
|
+
el("p", { className: styles.message }, t("compatGateHint")),
|
|
3372
|
+
el("p", { className: styles.status, "data-pending": gatePendingCount > 0 ? "true" : undefined },
|
|
3373
|
+
gatePendingCount > 0 ? t("gatePendingInfo").replace("{n}", String(gatePendingCount)) : t("gatePendingNone")))))
|
|
3374
|
+
: null,
|
|
3124
3375
|
aiOpen
|
|
3125
3376
|
? el("div", { className: styles.modalBackdrop },
|
|
3126
3377
|
el("div", { className: styles.modalCard, style: { maxWidth: 560 } },
|
|
@@ -3247,44 +3498,55 @@ window.__ModuleLoader__.load({
|
|
|
3247
3498
|
el("div", { className: styles.rowTop },
|
|
3248
3499
|
el("strong", { className: styles.name }, t("frameworkUpgradeBtn") + " " + (frameworkStatus.status === "done" ? "✓" : (frameworkStatus.status === "failed" ? "✕" : t("installingLocal")))),
|
|
3249
3500
|
el("button", { type: "button", className: styles.trashBtn, title: t("closeModal"), "aria-label": t("closeModal"), onClick: () => {
|
|
3250
|
-
//
|
|
3251
|
-
if (
|
|
3252
|
-
try { localStorage.setItem("pc-fw-dismiss-
|
|
3501
|
+
// 结束的卡片关闭 = 只关这一次(按运行时间戳认身份);进行中的关闭 = 仅本会话隐藏
|
|
3502
|
+
if (fwTerminal) {
|
|
3503
|
+
try { localStorage.setItem("pc-fw-dismiss-at", fwRunKey); } catch {}
|
|
3253
3504
|
}
|
|
3254
3505
|
setFwStatusDismissed(true);
|
|
3255
3506
|
} }, "✕")),
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3507
|
+
fwStepsView(frameworkStatus),
|
|
3508
|
+
frameworkStatus.status === "failed" && frameworkStatus.frameworkAtTarget
|
|
3509
|
+
? el("p", { className: styles.status }, t("fwFailedButUpgraded").replace("{v}", frameworkStatus.frameworkAtTarget))
|
|
3510
|
+
: null,
|
|
3511
|
+
frameworkStatus.reconciled
|
|
3512
|
+
? el("p", { className: styles.status, "data-pending": "true" }, "⚠ " + frameworkStatus.reconciled.note)
|
|
3513
|
+
: null,
|
|
3514
|
+
frameworkStatus.stalled && frameworkStatus.note
|
|
3515
|
+
? el("p", { className: styles.message, "data-warn": "true" }, "⚠ " + frameworkStatus.note)
|
|
3516
|
+
: null,
|
|
3264
3517
|
frameworkStatus.message ? el("p", { className: styles.status, "data-error": frameworkStatus.status === "failed" ? "true" : undefined }, frameworkStatus.message) : null,
|
|
3265
|
-
|
|
3518
|
+
rollbackUsable
|
|
3266
3519
|
? el("div", { className: styles.rowTop },
|
|
3267
3520
|
el("button", {
|
|
3268
3521
|
type: "button",
|
|
3269
3522
|
className: styles.toggle,
|
|
3270
3523
|
title: t("frameworkRollbackTitle"),
|
|
3271
3524
|
disabled: frameworkUpgrading,
|
|
3272
|
-
onClick:
|
|
3273
|
-
|
|
3274
|
-
call("/plugin-console/framework-rollback", {}).then(
|
|
3275
|
-
(data) => {
|
|
3276
|
-
setFrameworkUpgrading(false);
|
|
3277
|
-
setMessage(t("frameworkRollbackDone").replace("{from}", String(data?.from ?? "?")));
|
|
3278
|
-
setFrameworkStatus({ status: "rollback", message: t("frameworkRollbackDone").replace("{from}", String(data?.from ?? "?")) });
|
|
3279
|
-
pollFrameworkStatus();
|
|
3280
|
-
},
|
|
3281
|
-
(error) => { setFrameworkUpgrading(false); setMessage(t("failed") + ":" + friendlyGithubError(error).message); },
|
|
3282
|
-
);
|
|
3283
|
-
},
|
|
3284
|
-
}, t("frameworkRollbackBtn") + " → " + (state.data.rollback.from ?? "?")))
|
|
3525
|
+
onClick: doFrameworkRollback,
|
|
3526
|
+
}, t("frameworkRollbackBtn") + " → " + (rollbackInfo.from ?? "?")))
|
|
3285
3527
|
: null)
|
|
3286
3528
|
: null,
|
|
3287
3529
|
message !== null ? el("div", { className: styles.messageRow }, el("p", { className: styles.message }, message), reloadHint ? el("button", { type: "button", className: styles.toggle, onClick: () => window.location.reload() }, t("reloadPage")) : null, restartHint ? el("button", { type: "button", className: styles.toggle, onClick: () => { setMessage(t("restarting")); call("/plugin-console/restart", {}).then(() => { window.setTimeout(() => window.location.reload(), 12000); }, () => {}); } }, t("restartService")) : null) : null,
|
|
3530
|
+
// 适配门「软禁」的风险确认(用户定案:自动禁用,但允许手动强行启用)
|
|
3531
|
+
riskyConfirm !== null
|
|
3532
|
+
? el("div", { className: styles.detail, "data-risky": "true" },
|
|
3533
|
+
el("strong", { className: styles.name, "data-pending": "true" }, t("riskyEnableTitle") + "(" + riskyConfirm.entryId + ")"),
|
|
3534
|
+
el("p", { className: styles.status, "data-pending": "true" }, t("riskyEnableBody")
|
|
3535
|
+
+ (riskyConfirm.frameworkVersion ? "(框架 " + riskyConfirm.frameworkVersion + ")" : "")
|
|
3536
|
+
+ (riskyConfirm.checkNote ? ";判定依据:" + riskyConfirm.checkNote : "")),
|
|
3537
|
+
el("div", { className: styles.rowTop },
|
|
3538
|
+
el("button", {
|
|
3539
|
+
type: "button",
|
|
3540
|
+
className: styles.toggle,
|
|
3541
|
+
disabled: busy === riskyConfirm.entryId,
|
|
3542
|
+
onClick: () => {
|
|
3543
|
+
const target = (state.status === "ready" ? state.data.entries : []).find((e) => e.entryId === riskyConfirm.entryId) ?? null;
|
|
3544
|
+
if (target === null) { setRiskyConfirm(null); return; }
|
|
3545
|
+
toggle(target, true, true);
|
|
3546
|
+
},
|
|
3547
|
+
}, t("riskyEnableConfirm")),
|
|
3548
|
+
el("button", { type: "button", className: styles.toggle, onClick: () => setRiskyConfirm(null) }, t("riskyEnableCancel"))))
|
|
3549
|
+
: null,
|
|
3288
3550
|
el("div", { className: styles.marketHead },
|
|
3289
3551
|
el("h3", null, mode === "skills"
|
|
3290
3552
|
? (searchSource === "github"
|
|
@@ -3460,6 +3722,30 @@ setMessage(t("failed") + ":" + (data?.reason ?? "未知"));
|
|
|
3460
3722
|
"aria-label": t("sourcesBtn"),
|
|
3461
3723
|
onClick: openSources,
|
|
3462
3724
|
}, t("sourcesBtn")),
|
|
3725
|
+
el("button", {
|
|
3726
|
+
type: "button",
|
|
3727
|
+
className: styles.toolItem,
|
|
3728
|
+
"data-active": gateOpen ? "true" : "false",
|
|
3729
|
+
title: t("gateTitle"),
|
|
3730
|
+
"aria-label": t("gateBtn"),
|
|
3731
|
+
onClick: () => setGateOpen((v) => !v),
|
|
3732
|
+
}, t("gateBtn") + (gatePendingCount > 0 ? " " + gatePendingCount : "")),
|
|
3733
|
+
el("button", {
|
|
3734
|
+
type: "button",
|
|
3735
|
+
className: styles.toolItem,
|
|
3736
|
+
"data-active": fwPanelOpen ? "true" : "false",
|
|
3737
|
+
"data-mode": fwRunning ? "true" : "false",
|
|
3738
|
+
title: t("fwPanelTitle"),
|
|
3739
|
+
"aria-label": t("fwPanelBtn"),
|
|
3740
|
+
onClick: () => {
|
|
3741
|
+
setFwPanelOpen((v) => {
|
|
3742
|
+
const next = !v;
|
|
3743
|
+
// 打开时拉一次真实状态(无视卡片的「已关闭」标记)+ 版本检查
|
|
3744
|
+
if (next) { fwStatusRefresh(); if (fwCheck === null) fwCheckRefresh(false); }
|
|
3745
|
+
return next;
|
|
3746
|
+
});
|
|
3747
|
+
},
|
|
3748
|
+
}, t("fwPanelBtn") + fwBadge),
|
|
3463
3749
|
el("button", {
|
|
3464
3750
|
type: "button",
|
|
3465
3751
|
className: styles.toolItem,
|