@dsh-plus/access-gate 0.3.19 → 0.3.21
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 +215 -108
- package/package.json +2 -2
- package/src/client/card.tsx +76 -63
package/lib/client.js
CHANGED
|
@@ -12,9 +12,72 @@ window.__ModuleLoader__.load({
|
|
|
12
12
|
kind: "idle",
|
|
13
13
|
text: ""
|
|
14
14
|
};
|
|
15
|
+
/** 状态行 + 动作按钮(两种形态共用,类名随形态切换度量)。 */
|
|
16
|
+
function CardFoot(props) {
|
|
17
|
+
const p = props.prefix;
|
|
18
|
+
const section = props.variant === "section";
|
|
19
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
20
|
+
className: section ? `${p}-sectionFooter` : `${p}-footer`,
|
|
21
|
+
children: [props.status !== void 0 && props.status.kind !== "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
22
|
+
className: `${p}-status${props.status.kind === "error" ? ` ${p}-statusError` : ""}`,
|
|
23
|
+
role: "status",
|
|
24
|
+
children: props.status.text
|
|
25
|
+
}) : null, props.actions.map((action) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
26
|
+
type: "button",
|
|
27
|
+
className: `${p}-btn ${action.variant === "primary" ? `${p}-btnPrimary` : `${p}-btnGhost`}`,
|
|
28
|
+
disabled: action.disabled === true,
|
|
29
|
+
onClick: action.onClick,
|
|
30
|
+
children: action.label
|
|
31
|
+
}, action.key))]
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/** 只读提示 + 字段区 + 页脚(两种形态共用)。 */
|
|
35
|
+
function CardBody(props) {
|
|
36
|
+
const p = props.prefix;
|
|
37
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
38
|
+
props.readOnlyNotice !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
39
|
+
className: `${p}-readOnly`,
|
|
40
|
+
role: "status",
|
|
41
|
+
children: props.readOnlyNotice
|
|
42
|
+
}) : null,
|
|
43
|
+
props.children,
|
|
44
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CardFoot, { ...props })
|
|
45
|
+
] });
|
|
46
|
+
}
|
|
15
47
|
function CardChrome(props) {
|
|
16
48
|
const p = props.prefix;
|
|
17
49
|
const badge = props.statusBadge;
|
|
50
|
+
if (props.variant === "section") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
51
|
+
className: `${p}-section`,
|
|
52
|
+
"aria-label": props.title,
|
|
53
|
+
children: [
|
|
54
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
55
|
+
className: `${p}-sectionHead`,
|
|
56
|
+
children: [
|
|
57
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
58
|
+
className: `${p}-sectionTitle`,
|
|
59
|
+
children: props.title
|
|
60
|
+
}),
|
|
61
|
+
badge !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
62
|
+
className: `${p}-sectionBadge ${badge.on ? `${p}-statusOn` : `${p}-statusOff`}`,
|
|
63
|
+
children: badge.text
|
|
64
|
+
}) : null,
|
|
65
|
+
props.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
66
|
+
className: `${p}-pending`,
|
|
67
|
+
children: props.dirtyLabel
|
|
68
|
+
}) : null
|
|
69
|
+
]
|
|
70
|
+
}),
|
|
71
|
+
props.description === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
72
|
+
className: `${p}-sectionDesc`,
|
|
73
|
+
children: props.description
|
|
74
|
+
}),
|
|
75
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
76
|
+
className: `${p}-sectionBody`,
|
|
77
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CardBody, { ...props })
|
|
78
|
+
})
|
|
79
|
+
]
|
|
80
|
+
});
|
|
18
81
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
19
82
|
className: `${p}-card${props.open ? ` ${p}-cardOpen` : ""}`,
|
|
20
83
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
@@ -44,38 +107,44 @@ window.__ModuleLoader__.load({
|
|
|
44
107
|
}) : null,
|
|
45
108
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `${p}-chevron${props.open ? ` ${p}-chevronOpen` : ""}` })
|
|
46
109
|
]
|
|
47
|
-
}), props.open ? /* @__PURE__ */ (0, react_jsx_runtime.
|
|
110
|
+
}), props.open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
48
111
|
className: `${p}-body`,
|
|
49
|
-
children:
|
|
50
|
-
props.readOnlyNotice !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
51
|
-
className: `${p}-readOnly`,
|
|
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` : ""}`,
|
|
60
|
-
role: "status",
|
|
61
|
-
children: props.status.text
|
|
62
|
-
}) : null, props.actions.map((action) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
63
|
-
type: "button",
|
|
64
|
-
className: `${p}-btn ${action.variant === "primary" ? `${p}-btnPrimary` : `${p}-btnGhost`}`,
|
|
65
|
-
disabled: action.disabled === true,
|
|
66
|
-
onClick: action.onClick,
|
|
67
|
-
children: action.label
|
|
68
|
-
}, action.key))]
|
|
69
|
-
})
|
|
70
|
-
]
|
|
112
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CardBody, { ...props })
|
|
71
113
|
}) : null]
|
|
72
114
|
});
|
|
73
115
|
}
|
|
116
|
+
/** 取值未到达时的占位:与 CardChrome 同形态,避免宿主页里闪一次卡片外壳。 */
|
|
117
|
+
function CardLoading(props) {
|
|
118
|
+
if (props.variant === "section") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
|
|
119
|
+
className: `${props.prefix}-section`,
|
|
120
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
121
|
+
className: `${props.prefix}-readOnly`,
|
|
122
|
+
role: "status",
|
|
123
|
+
children: props.text
|
|
124
|
+
})
|
|
125
|
+
});
|
|
126
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
127
|
+
className: `${props.prefix}-card`,
|
|
128
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
129
|
+
className: `${props.prefix}-readOnly`,
|
|
130
|
+
role: "status",
|
|
131
|
+
children: props.text
|
|
132
|
+
})
|
|
133
|
+
});
|
|
134
|
+
}
|
|
74
135
|
//#endregion
|
|
75
136
|
//#region ../shared/src/client/config-slots.ts
|
|
76
137
|
/** dsh-plus 聚合 bundle 的包名(各插件 row 均经其补丁层插入 profile)。 */
|
|
77
138
|
const DSH_PLUS_BUNDLE = "@dsh-plus/bundle-main";
|
|
78
139
|
/**
|
|
140
|
+
* 视图 → 外壳形态:插件页 page 视图渲染在官方页面容器内(宿主已画标题、图标、
|
|
141
|
+
* 面包屑与页面级内边距),再套卡片会形成卡片套卡片并压缩移动端可用宽度,
|
|
142
|
+
* 故用无边框分节;summary 与 legacy 槽位保持卡片形态。
|
|
143
|
+
*/
|
|
144
|
+
function cardVariantFor(view) {
|
|
145
|
+
return view === "page" ? "section" : "card";
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
79
148
|
* 把一张配置卡片同时注册进 legacy 与新版三个槽位。
|
|
80
149
|
* 任一槽位在当前 dsh 版本未声明时对应 inject 挂起等待,版本不命中即 no-op。
|
|
81
150
|
*/
|
|
@@ -101,6 +170,17 @@ window.__ModuleLoader__.load({
|
|
|
101
170
|
}, reg.component));
|
|
102
171
|
}
|
|
103
172
|
//#endregion
|
|
173
|
+
//#region ../shared/src/client/draft.ts
|
|
174
|
+
/**
|
|
175
|
+
* 单命名空间 staged 草稿(简单配置卡片共用):读 scope 快照、播种草稿、
|
|
176
|
+
* 脏判定、保存(revision fencing + 重新装载)与状态行。
|
|
177
|
+
*
|
|
178
|
+
* 语义与官方 staged 表单一致:Host 更新不覆盖在途编辑,只有保存才写回;
|
|
179
|
+
* 保存成功后按 Host 解析值重置草稿。`from`/`patch` 参与 effect 与 useMemo
|
|
180
|
+
* 依赖,必须是模块级稳定函数(不要内联箭头函数)。
|
|
181
|
+
* @module @dsh-plus/shared/client/draft
|
|
182
|
+
*/
|
|
183
|
+
//#endregion
|
|
104
184
|
//#region ../shared/src/client/fields.tsx
|
|
105
185
|
/**
|
|
106
186
|
* 勾选行:自绘圆角扁平开关(switch),对齐官方 WebUI 无原生 checkbox 的
|
|
@@ -269,6 +349,9 @@ window.__ModuleLoader__.load({
|
|
|
269
349
|
* - 窄屏 input/textarea 16px(防 iOS 聚焦缩放)、可点目标 ≥44px;
|
|
270
350
|
* - footer 按钮等宽换行、状态文字独占一行;
|
|
271
351
|
* - 展开态 body 底部 sticky 保存栏(长表单滚动中始终可达)。
|
|
352
|
+
* - 分节形态(-section,插件页 page 视图内嵌宿主页面):无边框/背景、横向零
|
|
353
|
+
* 内边距,页脚不 sticky——官方插件页已提供页面级内边距,再叠一层卡片会
|
|
354
|
+
* 缩窄移动端可用宽度(卡片套卡片)。
|
|
272
355
|
* 沿用官方 data-plugin / data-plugin-css 约定(HMR 据此卸载),不覆盖上游选择器。
|
|
273
356
|
* @module @dsh-plus/shared/client/styles
|
|
274
357
|
*/
|
|
@@ -315,6 +398,15 @@ window.__ModuleLoader__.load({
|
|
|
315
398
|
.${prefix}-checkRow input:checked::after{left:16px;background:var(--dsw-alias-bg-base)}
|
|
316
399
|
.${prefix}-checkRow input:disabled{opacity:.4;cursor:default}
|
|
317
400
|
.${prefix}-checkRow input:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
|
|
401
|
+
/* 宿主内嵌分节形态(插件页 page 视图):官方设置页同构——无边框/背景,
|
|
402
|
+
横向零内边距(宽度交给宿主页面容器),小节标题 13px/600,页脚非 sticky。 */
|
|
403
|
+
.${prefix}-section{box-sizing:border-box;flex-direction:column;width:100%;min-width:0;padding:16px 0;display:flex}
|
|
404
|
+
.${prefix}-sectionHead{align-items:center;gap:8px;min-width:0;display:flex}
|
|
405
|
+
.${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}
|
|
406
|
+
.${prefix}-sectionBadge{white-space:nowrap;border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
|
|
407
|
+
.${prefix}-sectionDesc{color:var(--dsw-alias-label-tertiary);margin:4px 0 0;font-size:12px;line-height:1.5}
|
|
408
|
+
.${prefix}-sectionBody{flex-direction:column;min-width:0;display:flex}
|
|
409
|
+
.${prefix}-sectionFooter{align-items:center;gap:8px;padding-top:16px;display:flex;flex-wrap:wrap}
|
|
318
410
|
.${prefix}-groupLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600;padding:12px 0 2px;margin:0}
|
|
319
411
|
.${prefix}-readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}
|
|
320
412
|
.${prefix}-warn{color:var(--dsw-alias-label-error);margin:12px 0 0;font-size:12px;line-height:1.5}
|
|
@@ -336,6 +428,8 @@ window.__ModuleLoader__.load({
|
|
|
336
428
|
.${prefix}-status{flex-basis:100%;flex:none}
|
|
337
429
|
.${prefix}-btn{min-height:44px;flex:1;min-width:96px}
|
|
338
430
|
.${prefix}-statusBadge{display:none}
|
|
431
|
+
.${prefix}-sectionBadge{display:inline-block}
|
|
432
|
+
.${prefix}-sectionFooter{position:static}
|
|
339
433
|
}
|
|
340
434
|
@media (pointer:coarse){
|
|
341
435
|
.${prefix}-btn{min-height:44px}
|
|
@@ -422,36 +516,113 @@ ${extra}
|
|
|
422
516
|
if (diag.verdict === "autologin") return t("diagAutoLogin");
|
|
423
517
|
return t("diagBlock");
|
|
424
518
|
}
|
|
519
|
+
/** 本页诊断读侧:围栏状态端点(配置保存后 snapshot 推进时重读)。 */
|
|
520
|
+
function useGateDiag(snapshot) {
|
|
521
|
+
const [diag, setDiag] = (0, react.useState)(null);
|
|
522
|
+
(0, react.useEffect)(() => {
|
|
523
|
+
let alive = true;
|
|
524
|
+
fetch("/dsh-plus/gate/status").then((res) => res.ok ? res.json() : null).then((body) => {
|
|
525
|
+
if (alive && body !== null) setDiag(body);
|
|
526
|
+
}).catch(() => {});
|
|
527
|
+
return () => {
|
|
528
|
+
alive = false;
|
|
529
|
+
};
|
|
530
|
+
}, [snapshot]);
|
|
531
|
+
return diag;
|
|
532
|
+
}
|
|
533
|
+
/** 诊断面板:本页判定 / 客户端 IP / 放行原因 / 官方登录态(附自动登录与非法条目告警)。 */
|
|
534
|
+
function DiagPanel({ t, diag }) {
|
|
535
|
+
if (diag === null) return null;
|
|
536
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
537
|
+
className: `dag-diag${diag.invalidEntries.length > 0 ? " dag-diagWarn" : ""}`,
|
|
538
|
+
children: [
|
|
539
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
540
|
+
className: "dag-diagTitle",
|
|
541
|
+
children: t("diagTitle")
|
|
542
|
+
}),
|
|
543
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
544
|
+
className: "dag-diagRow",
|
|
545
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
546
|
+
className: "dag-diagKey",
|
|
547
|
+
children: t("diagVerdict")
|
|
548
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
549
|
+
className: "dag-diagVal",
|
|
550
|
+
children: verdictLabel(t, diag)
|
|
551
|
+
})]
|
|
552
|
+
}),
|
|
553
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
554
|
+
className: "dag-diagRow",
|
|
555
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
556
|
+
className: "dag-diagKey",
|
|
557
|
+
children: t("diagClientIp")
|
|
558
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
559
|
+
className: "dag-diagVal",
|
|
560
|
+
children: diag.clientIp ?? "—"
|
|
561
|
+
})]
|
|
562
|
+
}),
|
|
563
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
564
|
+
className: "dag-diagRow",
|
|
565
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
566
|
+
className: "dag-diagKey",
|
|
567
|
+
children: t("diagReason")
|
|
568
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
569
|
+
className: "dag-diagVal",
|
|
570
|
+
children: diag.reason !== null ? reasonText(t, diag.reason) : "—"
|
|
571
|
+
})]
|
|
572
|
+
}),
|
|
573
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
574
|
+
className: "dag-diagRow",
|
|
575
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
576
|
+
className: "dag-diagKey",
|
|
577
|
+
children: t("diagOfficial")
|
|
578
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
579
|
+
className: "dag-diagVal",
|
|
580
|
+
children: diag.officialAuthed ? t("diagOfficialYes") : t("diagOfficialNo")
|
|
581
|
+
})]
|
|
582
|
+
}),
|
|
583
|
+
diag.autoLoginActive ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
584
|
+
className: "dag-diagRow",
|
|
585
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
586
|
+
className: "dag-diagKey",
|
|
587
|
+
children: t("diagAutoLogin")
|
|
588
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
589
|
+
className: "dag-diagVal",
|
|
590
|
+
children: diag.autoLoginReady ? t("diagAutoLoginReady") : t("diagAutoLoginNoSecret")
|
|
591
|
+
})]
|
|
592
|
+
}) : null,
|
|
593
|
+
diag.invalidEntries.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
594
|
+
className: "dag-diagRow",
|
|
595
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
596
|
+
className: "dag-diagKey",
|
|
597
|
+
children: "!"
|
|
598
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
599
|
+
className: "dag-diagVal",
|
|
600
|
+
children: [t("diagInvalid"), diag.invalidEntries.join("、")]
|
|
601
|
+
})]
|
|
602
|
+
}) : null
|
|
603
|
+
]
|
|
604
|
+
});
|
|
605
|
+
}
|
|
425
606
|
function AccessGateCard(props) {
|
|
426
607
|
const { t, scope, api } = props;
|
|
427
608
|
const snapshot = (0, react.useSyncExternalStore)((listener) => scope.subscribe(listener), () => scope.getSnapshot());
|
|
428
609
|
const value = snapshot.value;
|
|
429
610
|
const [open, setOpen] = (0, react.useState)(props.view === "page");
|
|
430
611
|
const [draft, setDraft] = (0, react.useState)(null);
|
|
431
|
-
const [diag, setDiag] = (0, react.useState)(null);
|
|
432
612
|
const [saving, setSaving] = (0, react.useState)(false);
|
|
433
613
|
const [status, setStatus] = (0, react.useState)(IDLE_STATUS);
|
|
614
|
+
const diag = useGateDiag(snapshot);
|
|
434
615
|
(0, react.useEffect)(() => {
|
|
435
616
|
if (value === void 0 || draft !== null) return;
|
|
436
617
|
setDraft(draftFromValue(value));
|
|
437
618
|
}, [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
619
|
const dirty = (0, react.useMemo)(() => value !== void 0 && draft !== null && JSON.stringify(toPatch(draft)) !== JSON.stringify(toPatch(draftFromValue(value))), [value, draft]);
|
|
448
620
|
if (props.view === "summary") return t("summaryLine");
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
})
|
|
621
|
+
const variant = cardVariantFor(props.view);
|
|
622
|
+
if (value === void 0 || draft === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CardLoading, {
|
|
623
|
+
prefix: "dag",
|
|
624
|
+
variant,
|
|
625
|
+
text: t("loading")
|
|
455
626
|
});
|
|
456
627
|
const edit = (key, editValue) => {
|
|
457
628
|
setDraft({
|
|
@@ -484,6 +655,7 @@ ${extra}
|
|
|
484
655
|
description: t("description"),
|
|
485
656
|
open,
|
|
486
657
|
onToggle: setOpen,
|
|
658
|
+
variant,
|
|
487
659
|
statusBadge: {
|
|
488
660
|
text: t(draft.enabled ? "enabledOn" : "enabledOff"),
|
|
489
661
|
on: draft.enabled
|
|
@@ -561,75 +733,10 @@ ${extra}
|
|
|
561
733
|
disabled,
|
|
562
734
|
onEdit: (v) => edit("trustForwardedFor", v)
|
|
563
735
|
}),
|
|
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
|
|
736
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DiagPanel, {
|
|
737
|
+
t,
|
|
738
|
+
diag
|
|
739
|
+
})
|
|
633
740
|
]
|
|
634
741
|
});
|
|
635
742
|
}
|
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.21",
|
|
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.31"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"@deepseek-ai/cordis": "^4.0.4",
|
package/src/client/card.tsx
CHANGED
|
@@ -15,12 +15,15 @@
|
|
|
15
15
|
|
|
16
16
|
import {
|
|
17
17
|
CardChrome,
|
|
18
|
+
CardLoading,
|
|
18
19
|
type CardStatusState,
|
|
19
20
|
CheckRow,
|
|
21
|
+
cardVariantFor,
|
|
20
22
|
IDLE_STATUS,
|
|
21
23
|
type NamespaceSettingsApi,
|
|
22
24
|
type PluginConfigViewProps,
|
|
23
25
|
type Scope,
|
|
26
|
+
type ScopeSnapshot,
|
|
24
27
|
} from '@dsh-plus/shared/client'
|
|
25
28
|
import { type ReactElement, useEffect, useMemo, useState, useSyncExternalStore } from 'react'
|
|
26
29
|
import type { DictKey, Translate } from './i18n.ts'
|
|
@@ -101,6 +104,72 @@ function verdictLabel(t: Translate, diag: Diag): string {
|
|
|
101
104
|
return t('diagBlock')
|
|
102
105
|
}
|
|
103
106
|
|
|
107
|
+
/** 本页诊断读侧:围栏状态端点(配置保存后 snapshot 推进时重读)。 */
|
|
108
|
+
function useGateDiag(snapshot: ScopeSnapshot): Diag | null {
|
|
109
|
+
const [diag, setDiag] = useState<Diag | null>(null)
|
|
110
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: 配置保存后刻意重取诊断
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
let alive = true
|
|
113
|
+
fetch('/dsh-plus/gate/status')
|
|
114
|
+
.then((res) => (res.ok ? res.json() : null))
|
|
115
|
+
.then((body: Diag | null) => {
|
|
116
|
+
if (alive && body !== null) setDiag(body)
|
|
117
|
+
})
|
|
118
|
+
.catch(() => {})
|
|
119
|
+
return () => {
|
|
120
|
+
alive = false
|
|
121
|
+
}
|
|
122
|
+
}, [snapshot])
|
|
123
|
+
return diag
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** 诊断面板:本页判定 / 客户端 IP / 放行原因 / 官方登录态(附自动登录与非法条目告警)。 */
|
|
127
|
+
function DiagPanel({ t, diag }: { t: Translate; diag: Diag | null }): ReactElement | null {
|
|
128
|
+
if (diag === null) return null
|
|
129
|
+
return (
|
|
130
|
+
<div className={`dag-diag${diag.invalidEntries.length > 0 ? ' dag-diagWarn' : ''}`}>
|
|
131
|
+
<p className="dag-diagTitle">{t('diagTitle')}</p>
|
|
132
|
+
<div className="dag-diagRow">
|
|
133
|
+
<span className="dag-diagKey">{t('diagVerdict')}</span>
|
|
134
|
+
<span className="dag-diagVal">{verdictLabel(t, diag)}</span>
|
|
135
|
+
</div>
|
|
136
|
+
<div className="dag-diagRow">
|
|
137
|
+
<span className="dag-diagKey">{t('diagClientIp')}</span>
|
|
138
|
+
<span className="dag-diagVal">{diag.clientIp ?? '—'}</span>
|
|
139
|
+
</div>
|
|
140
|
+
<div className="dag-diagRow">
|
|
141
|
+
<span className="dag-diagKey">{t('diagReason')}</span>
|
|
142
|
+
<span className="dag-diagVal">
|
|
143
|
+
{diag.reason !== null ? reasonText(t, diag.reason) : '—'}
|
|
144
|
+
</span>
|
|
145
|
+
</div>
|
|
146
|
+
<div className="dag-diagRow">
|
|
147
|
+
<span className="dag-diagKey">{t('diagOfficial')}</span>
|
|
148
|
+
<span className="dag-diagVal">
|
|
149
|
+
{diag.officialAuthed ? t('diagOfficialYes') : t('diagOfficialNo')}
|
|
150
|
+
</span>
|
|
151
|
+
</div>
|
|
152
|
+
{diag.autoLoginActive ? (
|
|
153
|
+
<div className="dag-diagRow">
|
|
154
|
+
<span className="dag-diagKey">{t('diagAutoLogin')}</span>
|
|
155
|
+
<span className="dag-diagVal">
|
|
156
|
+
{diag.autoLoginReady ? t('diagAutoLoginReady') : t('diagAutoLoginNoSecret')}
|
|
157
|
+
</span>
|
|
158
|
+
</div>
|
|
159
|
+
) : null}
|
|
160
|
+
{diag.invalidEntries.length > 0 ? (
|
|
161
|
+
<div className="dag-diagRow">
|
|
162
|
+
<span className="dag-diagKey">!</span>
|
|
163
|
+
<span className="dag-diagVal">
|
|
164
|
+
{t('diagInvalid')}
|
|
165
|
+
{diag.invalidEntries.join('、')}
|
|
166
|
+
</span>
|
|
167
|
+
</div>
|
|
168
|
+
) : null}
|
|
169
|
+
</div>
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
|
|
104
173
|
export function AccessGateCard(props: CardProps): ReactElement | string | null {
|
|
105
174
|
const { t, scope, api } = props
|
|
106
175
|
const snapshot = useSyncExternalStore(
|
|
@@ -111,9 +180,9 @@ export function AccessGateCard(props: CardProps): ReactElement | string | null {
|
|
|
111
180
|
// 0.1.6-alpha.2 插件页 page 视图为表单落地页,默认展开;旧槽位无 view,保持折叠。
|
|
112
181
|
const [open, setOpen] = useState(props.view === 'page')
|
|
113
182
|
const [draft, setDraft] = useState<Draft | null>(null)
|
|
114
|
-
const [diag, setDiag] = useState<Diag | null>(null)
|
|
115
183
|
const [saving, setSaving] = useState(false)
|
|
116
184
|
const [status, setStatus] = useState<CardStatusState>(IDLE_STATUS)
|
|
185
|
+
const diag = useGateDiag(snapshot)
|
|
117
186
|
|
|
118
187
|
// 首次拿到解析值后播种草稿;后续 Host 更新不覆盖在途编辑(与官方 staged 表单一致)。
|
|
119
188
|
useEffect(() => {
|
|
@@ -121,21 +190,6 @@ export function AccessGateCard(props: CardProps): ReactElement | string | null {
|
|
|
121
190
|
setDraft(draftFromValue(value))
|
|
122
191
|
}, [value, draft])
|
|
123
192
|
|
|
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
193
|
const dirty = useMemo(
|
|
140
194
|
() =>
|
|
141
195
|
value !== undefined &&
|
|
@@ -147,12 +201,11 @@ export function AccessGateCard(props: CardProps): ReactElement | string | null {
|
|
|
147
201
|
// 插件页 summary 视图只出一行简介(hooks 已全部落定,可安全提前返回)。
|
|
148
202
|
if (props.view === 'summary') return t('summaryLine')
|
|
149
203
|
|
|
204
|
+
// 插件页 page 视图内嵌宿主页面容器(已带页面级内边距与标题),用无边框分节。
|
|
205
|
+
const variant = cardVariantFor(props.view)
|
|
206
|
+
|
|
150
207
|
if (value === undefined || draft === null) {
|
|
151
|
-
return (
|
|
152
|
-
<li className="dag-card">
|
|
153
|
-
<p className="dag-readOnly">{t('loading')}</p>
|
|
154
|
-
</li>
|
|
155
|
-
)
|
|
208
|
+
return <CardLoading prefix="dag" variant={variant} text={t('loading')} />
|
|
156
209
|
}
|
|
157
210
|
const edit = <K extends keyof Draft>(key: K, editValue: Draft[K]): void => {
|
|
158
211
|
setDraft({ ...draft, [key]: editValue })
|
|
@@ -185,6 +238,7 @@ export function AccessGateCard(props: CardProps): ReactElement | string | null {
|
|
|
185
238
|
description={t('description')}
|
|
186
239
|
open={open}
|
|
187
240
|
onToggle={setOpen}
|
|
241
|
+
variant={variant}
|
|
188
242
|
statusBadge={{ text: t(draft.enabled ? 'enabledOn' : 'enabledOff'), on: draft.enabled }}
|
|
189
243
|
dirty={dirty}
|
|
190
244
|
dirtyLabel={t('unsaved')}
|
|
@@ -250,48 +304,7 @@ export function AccessGateCard(props: CardProps): ReactElement | string | null {
|
|
|
250
304
|
disabled={disabled}
|
|
251
305
|
onEdit={(v) => edit('trustForwardedFor', v)}
|
|
252
306
|
/>
|
|
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}
|
|
307
|
+
<DiagPanel t={t} diag={diag} />
|
|
295
308
|
</CardChrome>
|
|
296
309
|
)
|
|
297
310
|
}
|