@dsh-plus/access-gate 0.3.20 → 0.3.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +192 -176
- package/package.json +2 -2
- package/src/client/card.tsx +73 -69
- package/src/client/client.ts +2 -2
- package/src/ns.ts +2 -2
package/lib/client.js
CHANGED
|
@@ -4,9 +4,9 @@ window.__ModuleLoader__.load({
|
|
|
4
4
|
var module = { exports: {} };
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
-
let react = require("react");
|
|
8
7
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
|
-
let
|
|
8
|
+
let react = require("react");
|
|
9
|
+
require("@deepseek-ai/dsh-client-ui-primitives");
|
|
10
10
|
//#region ../shared/src/client/card.tsx
|
|
11
11
|
const IDLE_STATUS = {
|
|
12
12
|
kind: "idle",
|
|
@@ -15,60 +15,68 @@ window.__ModuleLoader__.load({
|
|
|
15
15
|
function CardChrome(props) {
|
|
16
16
|
const p = props.prefix;
|
|
17
17
|
const badge = props.statusBadge;
|
|
18
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("
|
|
19
|
-
className: `${p}-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
28
|
-
className: `${p}-headText`,
|
|
29
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
30
|
-
className: `${p}-name`,
|
|
18
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
19
|
+
className: `${p}-section`,
|
|
20
|
+
"aria-label": props.title,
|
|
21
|
+
children: [
|
|
22
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
23
|
+
className: `${p}-sectionHead`,
|
|
24
|
+
children: [
|
|
25
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
26
|
+
className: `${p}-sectionTitle`,
|
|
31
27
|
children: props.title
|
|
32
|
-
}),
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
role: "status",
|
|
53
|
-
children: props.readOnlyNotice
|
|
54
|
-
}) : null,
|
|
55
|
-
props.children,
|
|
56
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
57
|
-
className: `${p}-footer`,
|
|
58
|
-
children: [props.status !== void 0 && props.status.kind !== "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
59
|
-
className: `${p}-status${props.status.kind === "error" ? ` ${p}-statusError` : ""}`,
|
|
28
|
+
}),
|
|
29
|
+
badge !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
30
|
+
className: `${p}-sectionBadge ${badge.on ? `${p}-statusOn` : `${p}-statusOff`}`,
|
|
31
|
+
children: badge.text
|
|
32
|
+
}) : null,
|
|
33
|
+
props.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
34
|
+
className: `${p}-pending`,
|
|
35
|
+
children: props.dirtyLabel
|
|
36
|
+
}) : null
|
|
37
|
+
]
|
|
38
|
+
}),
|
|
39
|
+
props.description === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
40
|
+
className: `${p}-sectionDesc`,
|
|
41
|
+
children: props.description
|
|
42
|
+
}),
|
|
43
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
44
|
+
className: `${p}-sectionBody`,
|
|
45
|
+
children: [
|
|
46
|
+
props.readOnlyNotice !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
47
|
+
className: `${p}-readOnly`,
|
|
60
48
|
role: "status",
|
|
61
|
-
children: props.
|
|
62
|
-
}) : null,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
49
|
+
children: props.readOnlyNotice
|
|
50
|
+
}) : null,
|
|
51
|
+
props.children,
|
|
52
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
53
|
+
className: `${p}-sectionFooter`,
|
|
54
|
+
children: [props.status !== void 0 && props.status.kind !== "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
55
|
+
className: `${p}-status${props.status.kind === "error" ? ` ${p}-statusError` : ""}`,
|
|
56
|
+
role: "status",
|
|
57
|
+
children: props.status.text
|
|
58
|
+
}) : null, props.actions.map((action) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
59
|
+
type: "button",
|
|
60
|
+
className: `${p}-btn ${action.variant === "primary" ? `${p}-btnPrimary` : `${p}-btnGhost`}`,
|
|
61
|
+
disabled: action.disabled === true,
|
|
62
|
+
onClick: action.onClick,
|
|
63
|
+
children: action.label
|
|
64
|
+
}, action.key))]
|
|
65
|
+
})
|
|
66
|
+
]
|
|
67
|
+
})
|
|
68
|
+
]
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/** 取值未到达时的占位:与 CardChrome 同形态,避免宿主页里闪一次卡片外壳。 */
|
|
72
|
+
function CardLoading(props) {
|
|
73
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
|
|
74
|
+
className: `${props.prefix}-section`,
|
|
75
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
76
|
+
className: `${props.prefix}-readOnly`,
|
|
77
|
+
role: "status",
|
|
78
|
+
children: props.text
|
|
79
|
+
})
|
|
72
80
|
});
|
|
73
81
|
}
|
|
74
82
|
//#endregion
|
|
@@ -76,19 +84,14 @@ window.__ModuleLoader__.load({
|
|
|
76
84
|
/** dsh-plus 聚合 bundle 的包名(各插件 row 均经其补丁层插入 profile)。 */
|
|
77
85
|
const DSH_PLUS_BUNDLE = "@dsh-plus/bundle-main";
|
|
78
86
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
87
|
+
* 把一张配置卡片注册进插件页的两个配置槽位。
|
|
88
|
+
* 两个槽位都由 0.2.1-alpha.1 的插件页声明,任一缺席即报错——不做版本探测。
|
|
81
89
|
*/
|
|
82
90
|
function injectPluginConfigCard(slots, reg) {
|
|
83
91
|
const base = {
|
|
84
92
|
locale: reg.ns,
|
|
85
93
|
inject: reg.inject
|
|
86
94
|
};
|
|
87
|
-
slots.inject("settings.plugin.item", () => slots.register({
|
|
88
|
-
name: "settings.plugin.item",
|
|
89
|
-
key: reg.ns,
|
|
90
|
-
...base
|
|
91
|
-
}, reg.component));
|
|
92
95
|
slots.inject("plugins.row.config", () => slots.register({
|
|
93
96
|
name: "plugins.row.config",
|
|
94
97
|
key: `${DSH_PLUS_BUNDLE}#${reg.rowId}`,
|
|
@@ -101,6 +104,17 @@ window.__ModuleLoader__.load({
|
|
|
101
104
|
}, reg.component));
|
|
102
105
|
}
|
|
103
106
|
//#endregion
|
|
107
|
+
//#region ../shared/src/client/draft.ts
|
|
108
|
+
/**
|
|
109
|
+
* 单命名空间 staged 草稿(简单配置卡片共用):读 scope 快照、播种草稿、
|
|
110
|
+
* 脏判定、保存(revision fencing + 重新装载)与状态行。
|
|
111
|
+
*
|
|
112
|
+
* 语义与官方 staged 表单一致:Host 更新不覆盖在途编辑,只有保存才写回;
|
|
113
|
+
* 保存成功后按 Host 解析值重置草稿。`from`/`patch` 参与 effect 与 useMemo
|
|
114
|
+
* 依赖,必须是模块级稳定函数(不要内联箭头函数)。
|
|
115
|
+
* @module @dsh-plus/shared/client/draft
|
|
116
|
+
*/
|
|
117
|
+
//#endregion
|
|
104
118
|
//#region ../shared/src/client/fields.tsx
|
|
105
119
|
/**
|
|
106
120
|
* 勾选行:自绘圆角扁平开关(switch),对齐官方 WebUI 无原生 checkbox 的
|
|
@@ -263,33 +277,33 @@ window.__ModuleLoader__.load({
|
|
|
263
277
|
//#endregion
|
|
264
278
|
//#region ../shared/src/client/styles.ts
|
|
265
279
|
/**
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
280
|
+
* 配置分节样式工厂(各插件 client 的公共收编版)。
|
|
281
|
+
*
|
|
282
|
+
* 目标宿主 dsh 0.2.1-alpha.1:插件页把配置视图放进已有页面级内边距的容器里
|
|
283
|
+
* (`div.detailSections[data-plugin-config]` 或 `section.detailSection[data-plugin-config]`),
|
|
284
|
+
* 外层无边框——故这里输出的是**无边框分节**(小节标题 13px/600、字段 12px 0、
|
|
285
|
+
* 横向零内边距、页脚非 sticky),度量对齐官方设置页
|
|
286
|
+
* (`@deepseek-ai/dsh-client-ui-primitives` 的 settings-form)。
|
|
287
|
+
*
|
|
288
|
+
* 移动端与可达性(767px 断点 + pointer:coarse,对齐 ui-mobile-fit 约定):
|
|
269
289
|
* - 窄屏 input/textarea 16px(防 iOS 聚焦缩放)、可点目标 ≥44px;
|
|
270
|
-
* -
|
|
271
|
-
* - 展开态 body 底部 sticky 保存栏(长表单滚动中始终可达)。
|
|
290
|
+
* - 页脚按钮等宽换行、状态文字独占一行。
|
|
272
291
|
* 沿用官方 data-plugin / data-plugin-css 约定(HMR 据此卸载),不覆盖上游选择器。
|
|
273
292
|
* @module @dsh-plus/shared/client/styles
|
|
274
293
|
*/
|
|
275
|
-
/**
|
|
294
|
+
/** 生成全套分节 CSS;prefix 为插件类名前缀(如 'dne'),extra 为插件特有规则。 */
|
|
276
295
|
function cardCss(prefix, extra = "") {
|
|
277
296
|
return `
|
|
278
|
-
.${prefix}-
|
|
279
|
-
.${prefix}-
|
|
280
|
-
.${prefix}-
|
|
281
|
-
.${prefix}-
|
|
282
|
-
.${prefix}-
|
|
283
|
-
.${prefix}-
|
|
284
|
-
.${prefix}-
|
|
285
|
-
.${prefix}-description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}
|
|
286
|
-
.${prefix}-chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s;display:inline-flex}
|
|
287
|
-
.${prefix}-chevronOpen{transform:rotate(180deg)}
|
|
297
|
+
.${prefix}-section{box-sizing:border-box;flex-direction:column;width:100%;min-width:0;padding:16px 0;display:flex}
|
|
298
|
+
.${prefix}-sectionHead{align-items:center;gap:8px;min-width:0;display:flex}
|
|
299
|
+
.${prefix}-sectionTitle{min-width:0;color:var(--dsw-alias-label-primary);flex:1;margin:0;font-size:13px;font-weight:600;line-height:1.5}
|
|
300
|
+
.${prefix}-sectionBadge{white-space:nowrap;border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
|
|
301
|
+
.${prefix}-sectionDesc{color:var(--dsw-alias-label-tertiary);margin:4px 0 0;font-size:12px;line-height:1.5}
|
|
302
|
+
.${prefix}-sectionBody{flex-direction:column;min-width:0;display:flex}
|
|
303
|
+
.${prefix}-sectionFooter{align-items:center;gap:8px;padding-top:16px;display:flex;flex-wrap:wrap}
|
|
288
304
|
.${prefix}-pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
|
|
289
|
-
.${prefix}-statusBadge{white-space:nowrap;border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
|
|
290
305
|
.${prefix}-statusOn{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary)}
|
|
291
306
|
.${prefix}-statusOff{color:var(--dsw-alias-label-tertiary)}
|
|
292
|
-
.${prefix}-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}
|
|
293
307
|
.${prefix}-field{flex-direction:column;gap:6px;padding:12px 0;display:flex}
|
|
294
308
|
.${prefix}-field+.${prefix}-field{border-top:1px solid var(--dsw-alias-border-l2)}
|
|
295
309
|
.${prefix}-head{align-items:center;gap:8px;display:flex}
|
|
@@ -318,7 +332,6 @@ window.__ModuleLoader__.load({
|
|
|
318
332
|
.${prefix}-groupLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600;padding:12px 0 2px;margin:0}
|
|
319
333
|
.${prefix}-readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}
|
|
320
334
|
.${prefix}-warn{color:var(--dsw-alias-label-error);margin:12px 0 0;font-size:12px;line-height:1.5}
|
|
321
|
-
.${prefix}-footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex;flex-wrap:wrap}
|
|
322
335
|
.${prefix}-status{min-width:0;color:var(--dsw-alias-label-secondary);flex:1;margin:0;font-size:12px;line-height:1.5}
|
|
323
336
|
.${prefix}-statusError{color:var(--dsw-alias-label-error)}
|
|
324
337
|
.${prefix}-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}
|
|
@@ -328,14 +341,10 @@ window.__ModuleLoader__.load({
|
|
|
328
341
|
.${prefix}-btn:disabled{opacity:.4;cursor:default}
|
|
329
342
|
.${prefix}-btn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}
|
|
330
343
|
@media (max-width:767px){
|
|
331
|
-
.${prefix}-body{margin:0 10px}
|
|
332
|
-
.${prefix}-header{padding:12px 12px}
|
|
333
344
|
.${prefix}-input,.${prefix}-select{font-size:16px;height:40px}
|
|
334
345
|
.${prefix}-checkRow{min-height:44px;padding:8px 0}
|
|
335
|
-
.${prefix}-footer{position:sticky;bottom:0;background:var(--dsw-alias-bg-layer-2);margin:0 -10px;padding:10px 10px 6px;border-top:1px solid var(--dsw-alias-border-l2);border-bottom-left-radius:12px;border-bottom-right-radius:12px}
|
|
336
346
|
.${prefix}-status{flex-basis:100%;flex:none}
|
|
337
347
|
.${prefix}-btn{min-height:44px;flex:1;min-width:96px}
|
|
338
|
-
.${prefix}-statusBadge{display:none}
|
|
339
348
|
}
|
|
340
349
|
@media (pointer:coarse){
|
|
341
350
|
.${prefix}-btn{min-height:44px}
|
|
@@ -366,8 +375,8 @@ ${extra}
|
|
|
366
375
|
* settings 命名空间字面量(纯常量,零依赖,浏览器半可安全引入)。
|
|
367
376
|
* 服务端直接以字面量注册(0.1.2-alpha.2 起 dsh-settings 对命名空间做编译期
|
|
368
377
|
* 语法校验,见 config.ts);浏览器半把它作为配置卡片的注册键
|
|
369
|
-
*
|
|
370
|
-
*
|
|
378
|
+
* (插件页配置卡片挂在 row/bundle config 槽位上,与命名空间解耦,
|
|
379
|
+
* 见 shared/config-slots),
|
|
371
380
|
* 同一字面量两处共用,防止漂移。
|
|
372
381
|
* @module access-gate/ns
|
|
373
382
|
*/
|
|
@@ -375,8 +384,8 @@ ${extra}
|
|
|
375
384
|
//#endregion
|
|
376
385
|
//#region src/client/card.tsx
|
|
377
386
|
/**
|
|
378
|
-
* 「访问控制」配置卡片:经 injectPluginConfigCard
|
|
379
|
-
*
|
|
387
|
+
* 「访问控制」配置卡片:经 injectPluginConfigCard 注册(插件页 plugins.row.config /
|
|
388
|
+
* plugins.bundle.config,view 分发 summary/page)。
|
|
380
389
|
* 外壳与基础控件走 @dsh-plus/shared/client 套件(CardChrome/TextField/CheckRow),
|
|
381
390
|
* 本文件保留业务字段(白名单 textarea/诊断面板)与保存逻辑。
|
|
382
391
|
* 配置读写经共享层 ctx.remote.settings 直连(0.1.7 起 settingsScope 已由
|
|
@@ -422,36 +431,110 @@ ${extra}
|
|
|
422
431
|
if (diag.verdict === "autologin") return t("diagAutoLogin");
|
|
423
432
|
return t("diagBlock");
|
|
424
433
|
}
|
|
434
|
+
/** 本页诊断读侧:围栏状态端点(配置保存后 snapshot 推进时重读)。 */
|
|
435
|
+
function useGateDiag(snapshot) {
|
|
436
|
+
const [diag, setDiag] = (0, react.useState)(null);
|
|
437
|
+
(0, react.useEffect)(() => {
|
|
438
|
+
let alive = true;
|
|
439
|
+
fetch("/dsh-plus/gate/status").then((res) => res.ok ? res.json() : null).then((body) => {
|
|
440
|
+
if (alive && body !== null) setDiag(body);
|
|
441
|
+
}).catch(() => {});
|
|
442
|
+
return () => {
|
|
443
|
+
alive = false;
|
|
444
|
+
};
|
|
445
|
+
}, [snapshot]);
|
|
446
|
+
return diag;
|
|
447
|
+
}
|
|
448
|
+
/** 诊断面板:本页判定 / 客户端 IP / 放行原因 / 官方登录态(附自动登录与非法条目告警)。 */
|
|
449
|
+
function DiagPanel({ t, diag }) {
|
|
450
|
+
if (diag === null) return null;
|
|
451
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
452
|
+
className: `dag-diag${diag.invalidEntries.length > 0 ? " dag-diagWarn" : ""}`,
|
|
453
|
+
children: [
|
|
454
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
455
|
+
className: "dag-diagTitle",
|
|
456
|
+
children: t("diagTitle")
|
|
457
|
+
}),
|
|
458
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
459
|
+
className: "dag-diagRow",
|
|
460
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
461
|
+
className: "dag-diagKey",
|
|
462
|
+
children: t("diagVerdict")
|
|
463
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
464
|
+
className: "dag-diagVal",
|
|
465
|
+
children: verdictLabel(t, diag)
|
|
466
|
+
})]
|
|
467
|
+
}),
|
|
468
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
469
|
+
className: "dag-diagRow",
|
|
470
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
471
|
+
className: "dag-diagKey",
|
|
472
|
+
children: t("diagClientIp")
|
|
473
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
474
|
+
className: "dag-diagVal",
|
|
475
|
+
children: diag.clientIp ?? "—"
|
|
476
|
+
})]
|
|
477
|
+
}),
|
|
478
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
479
|
+
className: "dag-diagRow",
|
|
480
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
481
|
+
className: "dag-diagKey",
|
|
482
|
+
children: t("diagReason")
|
|
483
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
484
|
+
className: "dag-diagVal",
|
|
485
|
+
children: diag.reason !== null ? reasonText(t, diag.reason) : "—"
|
|
486
|
+
})]
|
|
487
|
+
}),
|
|
488
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
489
|
+
className: "dag-diagRow",
|
|
490
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
491
|
+
className: "dag-diagKey",
|
|
492
|
+
children: t("diagOfficial")
|
|
493
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
494
|
+
className: "dag-diagVal",
|
|
495
|
+
children: diag.officialAuthed ? t("diagOfficialYes") : t("diagOfficialNo")
|
|
496
|
+
})]
|
|
497
|
+
}),
|
|
498
|
+
diag.autoLoginActive ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
499
|
+
className: "dag-diagRow",
|
|
500
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
501
|
+
className: "dag-diagKey",
|
|
502
|
+
children: t("diagAutoLogin")
|
|
503
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
504
|
+
className: "dag-diagVal",
|
|
505
|
+
children: diag.autoLoginReady ? t("diagAutoLoginReady") : t("diagAutoLoginNoSecret")
|
|
506
|
+
})]
|
|
507
|
+
}) : null,
|
|
508
|
+
diag.invalidEntries.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
509
|
+
className: "dag-diagRow",
|
|
510
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
511
|
+
className: "dag-diagKey",
|
|
512
|
+
children: "!"
|
|
513
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
514
|
+
className: "dag-diagVal",
|
|
515
|
+
children: [t("diagInvalid"), diag.invalidEntries.join("、")]
|
|
516
|
+
})]
|
|
517
|
+
}) : null
|
|
518
|
+
]
|
|
519
|
+
});
|
|
520
|
+
}
|
|
425
521
|
function AccessGateCard(props) {
|
|
426
522
|
const { t, scope, api } = props;
|
|
427
523
|
const snapshot = (0, react.useSyncExternalStore)((listener) => scope.subscribe(listener), () => scope.getSnapshot());
|
|
428
524
|
const value = snapshot.value;
|
|
429
|
-
const [open, setOpen] = (0, react.useState)(props.view === "page");
|
|
430
525
|
const [draft, setDraft] = (0, react.useState)(null);
|
|
431
|
-
const [diag, setDiag] = (0, react.useState)(null);
|
|
432
526
|
const [saving, setSaving] = (0, react.useState)(false);
|
|
433
527
|
const [status, setStatus] = (0, react.useState)(IDLE_STATUS);
|
|
528
|
+
const diag = useGateDiag(snapshot);
|
|
434
529
|
(0, react.useEffect)(() => {
|
|
435
530
|
if (value === void 0 || draft !== null) return;
|
|
436
531
|
setDraft(draftFromValue(value));
|
|
437
532
|
}, [value, draft]);
|
|
438
|
-
(0, react.useEffect)(() => {
|
|
439
|
-
let alive = true;
|
|
440
|
-
fetch("/dsh-plus/gate/status").then((res) => res.ok ? res.json() : null).then((body) => {
|
|
441
|
-
if (alive && body !== null) setDiag(body);
|
|
442
|
-
}).catch(() => {});
|
|
443
|
-
return () => {
|
|
444
|
-
alive = false;
|
|
445
|
-
};
|
|
446
|
-
}, [snapshot]);
|
|
447
533
|
const dirty = (0, react.useMemo)(() => value !== void 0 && draft !== null && JSON.stringify(toPatch(draft)) !== JSON.stringify(toPatch(draftFromValue(value))), [value, draft]);
|
|
448
534
|
if (props.view === "summary") return t("summaryLine");
|
|
449
|
-
if (value === void 0 || draft === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
className: "dag-readOnly",
|
|
453
|
-
children: t("loading")
|
|
454
|
-
})
|
|
535
|
+
if (value === void 0 || draft === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CardLoading, {
|
|
536
|
+
prefix: "dag",
|
|
537
|
+
text: t("loading")
|
|
455
538
|
});
|
|
456
539
|
const edit = (key, editValue) => {
|
|
457
540
|
setDraft({
|
|
@@ -482,8 +565,6 @@ ${extra}
|
|
|
482
565
|
prefix: "dag",
|
|
483
566
|
title: t("title"),
|
|
484
567
|
description: t("description"),
|
|
485
|
-
open,
|
|
486
|
-
onToggle: setOpen,
|
|
487
568
|
statusBadge: {
|
|
488
569
|
text: t(draft.enabled ? "enabledOn" : "enabledOff"),
|
|
489
570
|
on: draft.enabled
|
|
@@ -561,75 +642,10 @@ ${extra}
|
|
|
561
642
|
disabled,
|
|
562
643
|
onEdit: (v) => edit("trustForwardedFor", v)
|
|
563
644
|
}),
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
className: "dag-diagTitle",
|
|
569
|
-
children: t("diagTitle")
|
|
570
|
-
}),
|
|
571
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
572
|
-
className: "dag-diagRow",
|
|
573
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
574
|
-
className: "dag-diagKey",
|
|
575
|
-
children: t("diagVerdict")
|
|
576
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
577
|
-
className: "dag-diagVal",
|
|
578
|
-
children: verdictLabel(t, diag)
|
|
579
|
-
})]
|
|
580
|
-
}),
|
|
581
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
582
|
-
className: "dag-diagRow",
|
|
583
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
584
|
-
className: "dag-diagKey",
|
|
585
|
-
children: t("diagClientIp")
|
|
586
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
587
|
-
className: "dag-diagVal",
|
|
588
|
-
children: diag.clientIp ?? "—"
|
|
589
|
-
})]
|
|
590
|
-
}),
|
|
591
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
592
|
-
className: "dag-diagRow",
|
|
593
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
594
|
-
className: "dag-diagKey",
|
|
595
|
-
children: t("diagReason")
|
|
596
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
597
|
-
className: "dag-diagVal",
|
|
598
|
-
children: diag.reason !== null ? reasonText(t, diag.reason) : "—"
|
|
599
|
-
})]
|
|
600
|
-
}),
|
|
601
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
602
|
-
className: "dag-diagRow",
|
|
603
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
604
|
-
className: "dag-diagKey",
|
|
605
|
-
children: t("diagOfficial")
|
|
606
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
607
|
-
className: "dag-diagVal",
|
|
608
|
-
children: diag.officialAuthed ? t("diagOfficialYes") : t("diagOfficialNo")
|
|
609
|
-
})]
|
|
610
|
-
}),
|
|
611
|
-
diag.autoLoginActive ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
612
|
-
className: "dag-diagRow",
|
|
613
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
614
|
-
className: "dag-diagKey",
|
|
615
|
-
children: t("diagAutoLogin")
|
|
616
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
617
|
-
className: "dag-diagVal",
|
|
618
|
-
children: diag.autoLoginReady ? t("diagAutoLoginReady") : t("diagAutoLoginNoSecret")
|
|
619
|
-
})]
|
|
620
|
-
}) : null,
|
|
621
|
-
diag.invalidEntries.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
622
|
-
className: "dag-diagRow",
|
|
623
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
624
|
-
className: "dag-diagKey",
|
|
625
|
-
children: "!"
|
|
626
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
627
|
-
className: "dag-diagVal",
|
|
628
|
-
children: [t("diagInvalid"), diag.invalidEntries.join("、")]
|
|
629
|
-
})]
|
|
630
|
-
}) : null
|
|
631
|
-
]
|
|
632
|
-
}) : null
|
|
645
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DiagPanel, {
|
|
646
|
+
t,
|
|
647
|
+
diag
|
|
648
|
+
})
|
|
633
649
|
]
|
|
634
650
|
});
|
|
635
651
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-plus/access-gate",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.22",
|
|
4
4
|
"description": "dsh-plus service+ui plugin: dsh web 全量访问围栏——与官方 browser-auth 合并:官方 cookie 为唯一凭据,可选 IP 附加围栏,未认证导航渲染 token 输入页(PWA 可恢复),loopback 管理通道",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@dsh-plus/shared": "0.1.
|
|
34
|
+
"@dsh-plus/shared": "0.1.32"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"@deepseek-ai/cordis": "^4.0.4",
|
package/src/client/card.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 「访问控制」配置卡片:经 injectPluginConfigCard
|
|
3
|
-
*
|
|
2
|
+
* 「访问控制」配置卡片:经 injectPluginConfigCard 注册(插件页 plugins.row.config /
|
|
3
|
+
* plugins.bundle.config,view 分发 summary/page)。
|
|
4
4
|
* 外壳与基础控件走 @dsh-plus/shared/client 套件(CardChrome/TextField/CheckRow),
|
|
5
5
|
* 本文件保留业务字段(白名单 textarea/诊断面板)与保存逻辑。
|
|
6
6
|
* 配置读写经共享层 ctx.remote.settings 直连(0.1.7 起 settingsScope 已由
|
|
@@ -15,12 +15,14 @@
|
|
|
15
15
|
|
|
16
16
|
import {
|
|
17
17
|
CardChrome,
|
|
18
|
+
CardLoading,
|
|
18
19
|
type CardStatusState,
|
|
19
20
|
CheckRow,
|
|
20
21
|
IDLE_STATUS,
|
|
21
22
|
type NamespaceSettingsApi,
|
|
22
23
|
type PluginConfigViewProps,
|
|
23
24
|
type Scope,
|
|
25
|
+
type ScopeSnapshot,
|
|
24
26
|
} from '@dsh-plus/shared/client'
|
|
25
27
|
import { type ReactElement, useEffect, useMemo, useState, useSyncExternalStore } from 'react'
|
|
26
28
|
import type { DictKey, Translate } from './i18n.ts'
|
|
@@ -101,6 +103,72 @@ function verdictLabel(t: Translate, diag: Diag): string {
|
|
|
101
103
|
return t('diagBlock')
|
|
102
104
|
}
|
|
103
105
|
|
|
106
|
+
/** 本页诊断读侧:围栏状态端点(配置保存后 snapshot 推进时重读)。 */
|
|
107
|
+
function useGateDiag(snapshot: ScopeSnapshot): Diag | null {
|
|
108
|
+
const [diag, setDiag] = useState<Diag | null>(null)
|
|
109
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: 配置保存后刻意重取诊断
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
let alive = true
|
|
112
|
+
fetch('/dsh-plus/gate/status')
|
|
113
|
+
.then((res) => (res.ok ? res.json() : null))
|
|
114
|
+
.then((body: Diag | null) => {
|
|
115
|
+
if (alive && body !== null) setDiag(body)
|
|
116
|
+
})
|
|
117
|
+
.catch(() => {})
|
|
118
|
+
return () => {
|
|
119
|
+
alive = false
|
|
120
|
+
}
|
|
121
|
+
}, [snapshot])
|
|
122
|
+
return diag
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** 诊断面板:本页判定 / 客户端 IP / 放行原因 / 官方登录态(附自动登录与非法条目告警)。 */
|
|
126
|
+
function DiagPanel({ t, diag }: { t: Translate; diag: Diag | null }): ReactElement | null {
|
|
127
|
+
if (diag === null) return null
|
|
128
|
+
return (
|
|
129
|
+
<div className={`dag-diag${diag.invalidEntries.length > 0 ? ' dag-diagWarn' : ''}`}>
|
|
130
|
+
<p className="dag-diagTitle">{t('diagTitle')}</p>
|
|
131
|
+
<div className="dag-diagRow">
|
|
132
|
+
<span className="dag-diagKey">{t('diagVerdict')}</span>
|
|
133
|
+
<span className="dag-diagVal">{verdictLabel(t, diag)}</span>
|
|
134
|
+
</div>
|
|
135
|
+
<div className="dag-diagRow">
|
|
136
|
+
<span className="dag-diagKey">{t('diagClientIp')}</span>
|
|
137
|
+
<span className="dag-diagVal">{diag.clientIp ?? '—'}</span>
|
|
138
|
+
</div>
|
|
139
|
+
<div className="dag-diagRow">
|
|
140
|
+
<span className="dag-diagKey">{t('diagReason')}</span>
|
|
141
|
+
<span className="dag-diagVal">
|
|
142
|
+
{diag.reason !== null ? reasonText(t, diag.reason) : '—'}
|
|
143
|
+
</span>
|
|
144
|
+
</div>
|
|
145
|
+
<div className="dag-diagRow">
|
|
146
|
+
<span className="dag-diagKey">{t('diagOfficial')}</span>
|
|
147
|
+
<span className="dag-diagVal">
|
|
148
|
+
{diag.officialAuthed ? t('diagOfficialYes') : t('diagOfficialNo')}
|
|
149
|
+
</span>
|
|
150
|
+
</div>
|
|
151
|
+
{diag.autoLoginActive ? (
|
|
152
|
+
<div className="dag-diagRow">
|
|
153
|
+
<span className="dag-diagKey">{t('diagAutoLogin')}</span>
|
|
154
|
+
<span className="dag-diagVal">
|
|
155
|
+
{diag.autoLoginReady ? t('diagAutoLoginReady') : t('diagAutoLoginNoSecret')}
|
|
156
|
+
</span>
|
|
157
|
+
</div>
|
|
158
|
+
) : null}
|
|
159
|
+
{diag.invalidEntries.length > 0 ? (
|
|
160
|
+
<div className="dag-diagRow">
|
|
161
|
+
<span className="dag-diagKey">!</span>
|
|
162
|
+
<span className="dag-diagVal">
|
|
163
|
+
{t('diagInvalid')}
|
|
164
|
+
{diag.invalidEntries.join('、')}
|
|
165
|
+
</span>
|
|
166
|
+
</div>
|
|
167
|
+
) : null}
|
|
168
|
+
</div>
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
|
|
104
172
|
export function AccessGateCard(props: CardProps): ReactElement | string | null {
|
|
105
173
|
const { t, scope, api } = props
|
|
106
174
|
const snapshot = useSyncExternalStore(
|
|
@@ -108,12 +176,10 @@ export function AccessGateCard(props: CardProps): ReactElement | string | null {
|
|
|
108
176
|
() => scope.getSnapshot(),
|
|
109
177
|
)
|
|
110
178
|
const value = snapshot.value as ConfigValue | undefined
|
|
111
|
-
// 0.1.6-alpha.2 插件页 page 视图为表单落地页,默认展开;旧槽位无 view,保持折叠。
|
|
112
|
-
const [open, setOpen] = useState(props.view === 'page')
|
|
113
179
|
const [draft, setDraft] = useState<Draft | null>(null)
|
|
114
|
-
const [diag, setDiag] = useState<Diag | null>(null)
|
|
115
180
|
const [saving, setSaving] = useState(false)
|
|
116
181
|
const [status, setStatus] = useState<CardStatusState>(IDLE_STATUS)
|
|
182
|
+
const diag = useGateDiag(snapshot)
|
|
117
183
|
|
|
118
184
|
// 首次拿到解析值后播种草稿;后续 Host 更新不覆盖在途编辑(与官方 staged 表单一致)。
|
|
119
185
|
useEffect(() => {
|
|
@@ -121,21 +187,6 @@ export function AccessGateCard(props: CardProps): ReactElement | string | null {
|
|
|
121
187
|
setDraft(draftFromValue(value))
|
|
122
188
|
}, [value, draft])
|
|
123
189
|
|
|
124
|
-
// 本页诊断:读围栏状态端点(配置保存后 snapshot 推进时重读)。
|
|
125
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: 配置保存后刻意重取诊断
|
|
126
|
-
useEffect(() => {
|
|
127
|
-
let alive = true
|
|
128
|
-
fetch('/dsh-plus/gate/status')
|
|
129
|
-
.then((res) => (res.ok ? res.json() : null))
|
|
130
|
-
.then((body: Diag | null) => {
|
|
131
|
-
if (alive && body !== null) setDiag(body)
|
|
132
|
-
})
|
|
133
|
-
.catch(() => {})
|
|
134
|
-
return () => {
|
|
135
|
-
alive = false
|
|
136
|
-
}
|
|
137
|
-
}, [snapshot])
|
|
138
|
-
|
|
139
190
|
const dirty = useMemo(
|
|
140
191
|
() =>
|
|
141
192
|
value !== undefined &&
|
|
@@ -148,11 +199,7 @@ export function AccessGateCard(props: CardProps): ReactElement | string | null {
|
|
|
148
199
|
if (props.view === 'summary') return t('summaryLine')
|
|
149
200
|
|
|
150
201
|
if (value === undefined || draft === null) {
|
|
151
|
-
return (
|
|
152
|
-
<li className="dag-card">
|
|
153
|
-
<p className="dag-readOnly">{t('loading')}</p>
|
|
154
|
-
</li>
|
|
155
|
-
)
|
|
202
|
+
return <CardLoading prefix="dag" text={t('loading')} />
|
|
156
203
|
}
|
|
157
204
|
const edit = <K extends keyof Draft>(key: K, editValue: Draft[K]): void => {
|
|
158
205
|
setDraft({ ...draft, [key]: editValue })
|
|
@@ -183,8 +230,6 @@ export function AccessGateCard(props: CardProps): ReactElement | string | null {
|
|
|
183
230
|
prefix="dag"
|
|
184
231
|
title={t('title')}
|
|
185
232
|
description={t('description')}
|
|
186
|
-
open={open}
|
|
187
|
-
onToggle={setOpen}
|
|
188
233
|
statusBadge={{ text: t(draft.enabled ? 'enabledOn' : 'enabledOff'), on: draft.enabled }}
|
|
189
234
|
dirty={dirty}
|
|
190
235
|
dirtyLabel={t('unsaved')}
|
|
@@ -250,48 +295,7 @@ export function AccessGateCard(props: CardProps): ReactElement | string | null {
|
|
|
250
295
|
disabled={disabled}
|
|
251
296
|
onEdit={(v) => edit('trustForwardedFor', v)}
|
|
252
297
|
/>
|
|
253
|
-
{diag
|
|
254
|
-
<div className={`dag-diag${diag.invalidEntries.length > 0 ? ' dag-diagWarn' : ''}`}>
|
|
255
|
-
<p className="dag-diagTitle">{t('diagTitle')}</p>
|
|
256
|
-
<div className="dag-diagRow">
|
|
257
|
-
<span className="dag-diagKey">{t('diagVerdict')}</span>
|
|
258
|
-
<span className="dag-diagVal">{verdictLabel(t, diag)}</span>
|
|
259
|
-
</div>
|
|
260
|
-
<div className="dag-diagRow">
|
|
261
|
-
<span className="dag-diagKey">{t('diagClientIp')}</span>
|
|
262
|
-
<span className="dag-diagVal">{diag.clientIp ?? '—'}</span>
|
|
263
|
-
</div>
|
|
264
|
-
<div className="dag-diagRow">
|
|
265
|
-
<span className="dag-diagKey">{t('diagReason')}</span>
|
|
266
|
-
<span className="dag-diagVal">
|
|
267
|
-
{diag.reason !== null ? reasonText(t, diag.reason) : '—'}
|
|
268
|
-
</span>
|
|
269
|
-
</div>
|
|
270
|
-
<div className="dag-diagRow">
|
|
271
|
-
<span className="dag-diagKey">{t('diagOfficial')}</span>
|
|
272
|
-
<span className="dag-diagVal">
|
|
273
|
-
{diag.officialAuthed ? t('diagOfficialYes') : t('diagOfficialNo')}
|
|
274
|
-
</span>
|
|
275
|
-
</div>
|
|
276
|
-
{diag.autoLoginActive ? (
|
|
277
|
-
<div className="dag-diagRow">
|
|
278
|
-
<span className="dag-diagKey">{t('diagAutoLogin')}</span>
|
|
279
|
-
<span className="dag-diagVal">
|
|
280
|
-
{diag.autoLoginReady ? t('diagAutoLoginReady') : t('diagAutoLoginNoSecret')}
|
|
281
|
-
</span>
|
|
282
|
-
</div>
|
|
283
|
-
) : null}
|
|
284
|
-
{diag.invalidEntries.length > 0 ? (
|
|
285
|
-
<div className="dag-diagRow">
|
|
286
|
-
<span className="dag-diagKey">!</span>
|
|
287
|
-
<span className="dag-diagVal">
|
|
288
|
-
{t('diagInvalid')}
|
|
289
|
-
{diag.invalidEntries.join('、')}
|
|
290
|
-
</span>
|
|
291
|
-
</div>
|
|
292
|
-
) : null}
|
|
293
|
-
</div>
|
|
294
|
-
) : null}
|
|
298
|
+
<DiagPanel t={t} diag={diag} />
|
|
295
299
|
</CardChrome>
|
|
296
300
|
)
|
|
297
301
|
}
|
package/src/client/client.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 浏览器半入口:注册 locale 字典 + 注册「访问控制」卡片
|
|
3
|
-
* (injectPluginConfigCard
|
|
4
|
-
*
|
|
3
|
+
* (injectPluginConfigCard 两槽位:插件页 plugins.row.config /
|
|
4
|
+
* plugins.bundle.config)。
|
|
5
5
|
* 构建产物为 window.__ModuleLoader__.load({id, factory}) 形式的 CJS factory
|
|
6
6
|
* (包装见 tsdown.config.ts);样式沿用官方 data-plugin-css 约定,HMR 据此卸载。
|
|
7
7
|
*
|
package/src/ns.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* settings 命名空间字面量(纯常量,零依赖,浏览器半可安全引入)。
|
|
3
3
|
* 服务端直接以字面量注册(0.1.2-alpha.2 起 dsh-settings 对命名空间做编译期
|
|
4
4
|
* 语法校验,见 config.ts);浏览器半把它作为配置卡片的注册键
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* (插件页配置卡片挂在 row/bundle config 槽位上,与命名空间解耦,
|
|
6
|
+
* 见 shared/config-slots),
|
|
7
7
|
* 同一字面量两处共用,防止漂移。
|
|
8
8
|
* @module access-gate/ns
|
|
9
9
|
*/
|