@wenbin_wb/dsh-bridge 2.4.0 → 2.5.0

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/client/client.js CHANGED
@@ -43,6 +43,12 @@ var BRIDGE_ENDPOINTS = {
43
43
  saveCustomTunnelConfig: "saveCustomTunnelConfig",
44
44
  checkVersion: "checkVersion",
45
45
  upgradePlugin: "upgradePlugin",
46
+ // 访问安全认证(密码保护 / 扫码免密 Token)
47
+ authGetStatus: "authGetStatus",
48
+ authUpdateConfig: "authUpdateConfig",
49
+ authRegenerateToken: "authRegenerateToken",
50
+ authAdminUnlock: "authAdminUnlock",
51
+ authAdminLock: "authAdminLock",
46
52
  // 平台管理器(多 IM 平台统一接口)
47
53
  listPlatforms: "listPlatforms",
48
54
  platformLogin: "platformLogin",
@@ -135,6 +141,11 @@ var Icons = {
135
141
  { viewBox: "0 0 24 24", width: 16, height: 16, fill: "currentColor", ...props },
136
142
  React.createElement("path", { d: "M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM19 18H6c-2.21 0-4-1.79-4-4 0-2.05 1.53-3.76 3.56-3.97l1.07-.11.5-.95C8.08 7.14 9.94 6 12 6c2.62 0 4.88 1.86 5.39 4.43l.3 1.5 1.53.11c1.56.1 2.78 1.41 2.78 2.96 0 1.65-1.35 3-3 3z" })
137
143
  ),
144
+ security: (props) => React.createElement(
145
+ "svg",
146
+ { viewBox: "0 0 24 24", width: 16, height: 16, fill: "currentColor", ...props },
147
+ React.createElement("path", { d: "M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z" })
148
+ ),
138
149
  bot: (props) => React.createElement(
139
150
  "svg",
140
151
  { viewBox: "0 0 24 24", width: 16, height: 16, fill: "currentColor", ...props },
@@ -216,7 +227,7 @@ function StatusTag({ running, status }) {
216
227
  style: { ...s.tag, background: bg, color }
217
228
  }, text);
218
229
  }
219
- function QrBlock({ url, qr, onReset }) {
230
+ function QrBlock({ url, qr, onReset, auth, onNavigateSecurity }) {
220
231
  const [copied, setCopied] = React.useState(false);
221
232
  const [showQr, setShowQr] = React.useState(true);
222
233
  const copy = React.useCallback(() => {
@@ -247,14 +258,41 @@ function QrBlock({ url, qr, onReset }) {
247
258
  return React.createElement(
248
259
  "div",
249
260
  { style: { marginTop: 10 } },
250
- React.createElement(
261
+ auth?.enabled ? React.createElement(
251
262
  "div",
252
- { style: s.warn },
253
- "\u26A0\uFE0F \u8BF7\u52FF\u5C06\u6B64\u94FE\u63A5\u6216\u4E8C\u7EF4\u7801\u5206\u4EAB\u7ED9\u4ED6\u4EBA\uFF0C\u4EFB\u4F55\u4EBA\u626B\u7801\u540E\u90FD\u53EF\u76F4\u63A5\u8BBF\u95EE\u60A8\u7684 DSH\u3002"
263
+ {
264
+ style: {
265
+ display: "inline-flex",
266
+ alignItems: "center",
267
+ gap: 6,
268
+ padding: "4px 10px",
269
+ background: "var(--dsw-alias-state-success-bg,#ecfdf5)",
270
+ border: "1px solid var(--dsw-alias-state-success-primary,#10b981)",
271
+ borderRadius: 8,
272
+ fontSize: 12,
273
+ color: "var(--dsw-alias-state-success-primary,#059669)",
274
+ marginBottom: 8,
275
+ fontWeight: 500,
276
+ cursor: onNavigateSecurity ? "pointer" : "default"
277
+ },
278
+ onClick: onNavigateSecurity,
279
+ title: onNavigateSecurity ? "\u70B9\u51FB\u524D\u5F80\u300C\u5B89\u5168\u8BA4\u8BC1\u300D\u914D\u7F6E" : void 0
280
+ },
281
+ "\u{1F6E1}\uFE0F \u8BBF\u95EE\u5B89\u5168\u8BA4\u8BC1\u5DF2\u751F\u6548 \xB7 \u626B\u7801\u8BBE\u5907\u81EA\u52A8\u5B8C\u6210\u514D\u5BC6\u6388\u6743",
282
+ onNavigateSecurity && React.createElement("span", { style: { textDecoration: "underline", fontSize: 11, marginLeft: 4 } }, "\u8BBE\u7F6E \u2794")
283
+ ) : React.createElement(
284
+ "div",
285
+ {
286
+ style: { ...s.warn, cursor: onNavigateSecurity ? "pointer" : "default", display: "flex", alignItems: "center", justifyContent: "space-between", flexWrap: "wrap", gap: 6 },
287
+ onClick: onNavigateSecurity,
288
+ title: onNavigateSecurity ? "\u70B9\u51FB\u524D\u5F80\u300C\u5B89\u5168\u8BA4\u8BC1\u300D\u5F00\u542F\u8BBF\u95EE\u4FDD\u62A4" : void 0
289
+ },
290
+ React.createElement("span", null, "\u26A0\uFE0F \u5F53\u524D\u672A\u5F00\u542F\u8BBF\u95EE\u8BA4\u8BC1\uFF0C\u5EFA\u8BAE\u5728\u300C\u5B89\u5168\u8BA4\u8BC1\u300D\u5F00\u542F\u5BC6\u7801\u6216\u626B\u7801\u4FDD\u62A4\u3002"),
291
+ onNavigateSecurity && React.createElement("span", { style: { fontWeight: 600, textDecoration: "underline", fontSize: 12, color: "var(--dsw-alias-brand-primary,#4f6ef7)" } }, "\u53BB\u5F00\u542F \u2794")
254
292
  ),
255
293
  React.createElement(
256
294
  "div",
257
- { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 10 } },
295
+ { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 6 } },
258
296
  React.createElement("code", { style: { ...s.code, flex: 1 } }, url),
259
297
  React.createElement("button", {
260
298
  style: { ...s.btnGhost, height: 26, padding: "0 10px", fontSize: 12, flexShrink: 0 },
@@ -330,7 +368,7 @@ var CustomTunnelConfigForm = React.memo(function CustomTunnelConfigForm2({ serve
330
368
  return React.createElement(
331
369
  "div",
332
370
  { style: s.block },
333
- React.createElement("div", { style: { ...s.muted, marginBottom: 8 } }, "\u670D\u52A1\u5668\u914D\u7F6E"),
371
+ React.createElement("div", { style: { ...s.muted, marginBottom: 8 } }, "\u96A7\u9053\u670D\u52A1\u5668\u914D\u7F6E"),
334
372
  React.createElement(
335
373
  "div",
336
374
  { style: { display: "flex", flexDirection: "column", gap: 8 } },
@@ -347,7 +385,7 @@ var CustomTunnelConfigForm = React.memo(function CustomTunnelConfigForm2({ serve
347
385
  React.createElement("input", {
348
386
  style: s.input,
349
387
  type: "password",
350
- placeholder: "\u8BBF\u95EE\u4EE4\u724C\uFF08Access Token\uFF09",
388
+ placeholder: "\u96A7\u9053\u670D\u52A1\u7AEF\u8FDE\u63A5\u4EE4\u724C\uFF08Tunnel Access Token\uFF09",
351
389
  value: accessToken,
352
390
  onChange: handleTokenChange,
353
391
  onKeyDown: (e) => {
@@ -355,6 +393,11 @@ var CustomTunnelConfigForm = React.memo(function CustomTunnelConfigForm2({ serve
355
393
  },
356
394
  disabled: saving
357
395
  }),
396
+ React.createElement(
397
+ "div",
398
+ { style: { ...s.muted, fontSize: 11 } },
399
+ "\u{1F4A1} \u7528\u4E8E\u4E0E\u60A8\u7684 VPS \u96A7\u9053\u670D\u52A1\u7AEF\u5EFA\u7ACB\u53CD\u5411\u901A\u9053\uFF08\u4E0E Web \u7F51\u9875\u8BBF\u5BA2\u8BBF\u95EE\u5BC6\u7801\u4E92\u76F8\u72EC\u7ACB\uFF09\u3002"
400
+ ),
358
401
  React.createElement("button", {
359
402
  style: {
360
403
  ...s.btnPri,
@@ -368,7 +411,7 @@ var CustomTunnelConfigForm = React.memo(function CustomTunnelConfigForm2({ serve
368
411
  )
369
412
  );
370
413
  });
371
- var TunnelCard = React.memo(function TunnelCard2({ title, desc, data, onStart, onStop, onReset, children }) {
414
+ var TunnelCard = React.memo(function TunnelCard2({ title, desc, data, onStart, onStop, onReset, auth, onNavigateSecurity, children }) {
372
415
  const { running, configured, url, qr, state } = data ?? {};
373
416
  const phase = state?.phase ?? "idle";
374
417
  return React.createElement(
@@ -393,7 +436,7 @@ var TunnelCard = React.memo(function TunnelCard2({ title, desc, data, onStart, o
393
436
  color: phase === "error" ? "var(--dsw-alias-state-error-primary,#dc2626)" : "var(--dsw-alias-label-secondary,#6b7280)"
394
437
  }
395
438
  }, state?.detail ?? phase),
396
- url && React.createElement(QrBlock, { url, qr, onReset }),
439
+ url && React.createElement(QrBlock, { url, qr, onReset, auth, onNavigateSecurity }),
397
440
  (onStart || onStop) && React.createElement(
398
441
  "div",
399
442
  {
@@ -409,6 +452,501 @@ var TunnelCard = React.memo(function TunnelCard2({ title, desc, data, onStart, o
409
452
  )
410
453
  );
411
454
  });
455
+ var AccessAuthCard = React.memo(function AccessAuthCard2({ auth, rpcCall, onUpdate }) {
456
+ const [enabled, setEnabled] = React.useState(auth?.enabled ?? false);
457
+ const [mode, setMode] = React.useState(auth?.mode ?? "token_and_password");
458
+ const [scope, setScope] = React.useState(auth?.scope ?? "all");
459
+ const [adminPolicy, setAdminPolicy] = React.useState(auth?.adminPolicy ?? "password_unlock");
460
+ const [accessPassword, setAccessPassword] = React.useState("");
461
+ const [showAccessPassword, setShowAccessPassword] = React.useState(false);
462
+ const [savingAccess, setSavingAccess] = React.useState(false);
463
+ const [saveAccessSuccess, setSaveAccessSuccess] = React.useState(false);
464
+ const [msgAccess, setMsgAccess] = React.useState(null);
465
+ const [adminPassword, setAdminPassword] = React.useState("");
466
+ const [showAdminPassword, setShowAdminPassword] = React.useState(false);
467
+ const [savingAdmin, setSavingAdmin] = React.useState(false);
468
+ const [saveAdminSuccess, setSaveAdminSuccess] = React.useState(false);
469
+ const [msgAdmin, setMsgAdmin] = React.useState(null);
470
+ const [topMsg, setTopMsg] = React.useState(null);
471
+ React.useEffect(() => {
472
+ if (auth) {
473
+ setEnabled(auth.enabled ?? false);
474
+ setMode(auth.mode ?? "token_and_password");
475
+ setScope(auth.scope ?? "all");
476
+ setAdminPolicy(auth.adminPolicy ?? "password_unlock");
477
+ }
478
+ }, [auth]);
479
+ const handleToggleEnabled = async () => {
480
+ const next = !enabled;
481
+ setEnabled(next);
482
+ try {
483
+ await rpcCall(BRIDGE_ENDPOINTS.authUpdateConfig, { enabled: next });
484
+ setTopMsg({ ok: true, text: next ? "\u2713 \u8BBF\u95EE\u5B89\u5168\u8BA4\u8BC1\u5DF2\u5F00\u542F\uFF08\u73B0\u6709\u767B\u5F55\u6001\u5DF2\u5237\u65B0\uFF09" : "\u2713 \u8BBF\u95EE\u5B89\u5168\u8BA4\u8BC1\u5DF2\u5173\u95ED" });
485
+ onUpdate?.();
486
+ } catch (e) {
487
+ setTopMsg({ ok: false, text: e.message || "\u66F4\u65B0\u5931\u8D25" });
488
+ }
489
+ };
490
+ const handleChangeMode = async (m) => {
491
+ setMode(m);
492
+ try {
493
+ await rpcCall(BRIDGE_ENDPOINTS.authUpdateConfig, { mode: m });
494
+ setTopMsg({ ok: true, text: "\u2713 \u5916\u90E8\u9A8C\u8BC1\u6A21\u5F0F\u5DF2\u5207\u6362\uFF0C\u5DF2\u5237\u65B0\u5168\u57DF\u767B\u5F55\u6001" });
495
+ onUpdate?.();
496
+ } catch (e) {
497
+ setTopMsg({ ok: false, text: e.message || "\u66F4\u65B0\u5931\u8D25" });
498
+ }
499
+ };
500
+ const handleChangeScope = async (sc) => {
501
+ setScope(sc);
502
+ try {
503
+ await rpcCall(BRIDGE_ENDPOINTS.authUpdateConfig, { scope: sc });
504
+ setTopMsg({ ok: true, text: "\u2713 \u9632\u62A4\u751F\u6548\u8303\u56F4\u5DF2\u66F4\u65B0" });
505
+ onUpdate?.();
506
+ } catch (e) {
507
+ setTopMsg({ ok: false, text: e.message || "\u66F4\u65B0\u5931\u8D25" });
508
+ }
509
+ };
510
+ const handleChangeAdminPolicy = async (pol) => {
511
+ setAdminPolicy(pol);
512
+ try {
513
+ await rpcCall(BRIDGE_ENDPOINTS.authUpdateConfig, { adminPolicy: pol });
514
+ setTopMsg({ ok: true, text: "\u2713 \u8FDC\u7A0B\u7BA1\u7406\u9632\u7BE1\u6539\u7B56\u7565\u5DF2\u66F4\u65B0" });
515
+ onUpdate?.();
516
+ } catch (e) {
517
+ setTopMsg({ ok: false, text: e.message || "\u66F4\u65B0\u5931\u8D25" });
518
+ }
519
+ };
520
+ const handleSaveAccessPassword = async () => {
521
+ setSavingAccess(true);
522
+ setMsgAccess(null);
523
+ try {
524
+ const res = await rpcCall(BRIDGE_ENDPOINTS.authUpdateConfig, { password: accessPassword });
525
+ if (res?.ok) {
526
+ setSaveAccessSuccess(true);
527
+ setMsgAccess({ ok: true, text: "\u2713 \u8BBF\u5BA2\u8BBF\u95EE\u5BC6\u7801\u5DF2\u6210\u529F\u4FDD\u5B58\uFF01\u539F\u6709\u7684\u5386\u53F2\u8BBF\u5BA2\u4F1A\u8BDD\u5DF2\u5168\u90E8\u5B89\u5168\u5237\u65B0\u3002" });
528
+ setAccessPassword("");
529
+ setTimeout(() => setSaveAccessSuccess(false), 3500);
530
+ onUpdate?.();
531
+ } else {
532
+ setMsgAccess({ ok: false, text: res?.error?.message || "\u4FDD\u5B58\u5931\u8D25" });
533
+ }
534
+ } catch (e) {
535
+ setMsgAccess({ ok: false, text: e.message || "\u4FDD\u5B58\u5931\u8D25" });
536
+ } finally {
537
+ setSavingAccess(false);
538
+ }
539
+ };
540
+ const handleSaveAdminPassword = async () => {
541
+ setSavingAdmin(true);
542
+ setMsgAdmin(null);
543
+ try {
544
+ const res = await rpcCall(BRIDGE_ENDPOINTS.authUpdateConfig, { adminPassword });
545
+ if (res?.ok) {
546
+ setSaveAdminSuccess(true);
547
+ setMsgAdmin({ ok: true, text: "\u2713 \u540E\u53F0\u7BA1\u7406\u5BC6\u7801\u5DF2\u6210\u529F\u4FDD\u5B58\uFF01\u8FDC\u7A0B\u7BA1\u7406\u89E3\u9501\u72B6\u6001\u5DF2\u91CD\u7F6E\u751F\u6548\u3002" });
548
+ setAdminPassword("");
549
+ setTimeout(() => setSaveAdminSuccess(false), 3500);
550
+ onUpdate?.();
551
+ } else {
552
+ setMsgAdmin({ ok: false, text: res?.error?.message || "\u4FDD\u5B58\u5931\u8D25" });
553
+ }
554
+ } catch (e) {
555
+ setMsgAdmin({ ok: false, text: e.message || "\u4FDD\u5B58\u5931\u8D25" });
556
+ } finally {
557
+ setSavingAdmin(false);
558
+ }
559
+ };
560
+ const handleRegenerateToken = async () => {
561
+ if (!confirm("\u91CD\u7F6E\u540E\uFF0C\u4E4B\u524D\u5305\u542B\u65E7 Token \u7684\u4E8C\u7EF4\u7801\u548C\u5206\u4EAB\u94FE\u63A5\u5C06\u7ACB\u5373\u5931\u6548\u3002\u662F\u5426\u786E\u8BA4\u91CD\u7F6E\uFF1F")) return;
562
+ try {
563
+ await rpcCall(BRIDGE_ENDPOINTS.authRegenerateToken, {});
564
+ setTopMsg({ ok: true, text: "\u2713 \u5B89\u5168 Token \u5DF2\u91CD\u7F6E\uFF0C\u4E8C\u7EF4\u7801\u4E0E\u4E13\u5C5E\u94FE\u63A5\u5DF2\u5237\u65B0" });
565
+ onUpdate?.();
566
+ } catch (e) {
567
+ setTopMsg({ ok: false, text: e.message || "\u91CD\u7F6E\u5931\u8D25" });
568
+ }
569
+ };
570
+ const scopeLabel = scope === "all" ? "\u5168\u90E8\u901A\u9053 (\u5C40\u57DF\u7F51+\u516C\u7F51)" : scope === "public_only" ? "\u4EC5\u516C\u7F51\u96A7\u9053" : "\u4EC5\u5C40\u57DF\u7F51";
571
+ const modeLabel = mode === "token_and_password" ? "\u626B\u7801\u514D\u5BC6 + \u5BC6\u7801" : mode === "password_only" ? "\u4EC5\u5BC6\u7801\u767B\u5F55" : "\u4EC5\u5B89\u5168 Token";
572
+ const adminLabel = adminPolicy === "password_unlock" ? "\u9700\u5BC6\u7801\u89E3\u9501" : adminPolicy === "local_only" ? "\u4EC5\u9650\u7535\u8111\u672C\u673A\u7BA1\u7406" : "\u5BBD\u677E\u6A21\u5F0F";
573
+ return React.createElement(
574
+ "div",
575
+ { style: { display: "flex", flexDirection: "column", gap: 14 } },
576
+ // ---- 顶部总控与状态概览卡片 ----
577
+ React.createElement(
578
+ "div",
579
+ { style: s.card },
580
+ React.createElement(
581
+ "div",
582
+ {
583
+ style: { display: "flex", alignItems: "center", justifyContent: "space-between", flexWrap: "wrap", gap: 12 }
584
+ },
585
+ React.createElement(
586
+ "div",
587
+ { style: { flex: "1 1 260px" } },
588
+ React.createElement(
589
+ "div",
590
+ { style: { ...s.label, fontSize: 15, display: "flex", alignItems: "center", gap: 8 } },
591
+ "\u{1F510} \u5168\u5C40\u8BBF\u95EE\u5B89\u5168\u9632\u62A4\u4F53\u7CFB"
592
+ ),
593
+ React.createElement(
594
+ "div",
595
+ { style: { ...s.muted, marginTop: 4 } },
596
+ "\u96C6\u6210\u5916\u90E8\u8BBF\u95EE\u95E8\u7981\u62E6\u622A\u4E0E\u7BA1\u7406\u540E\u53F0\u9632\u7BE1\u6539\u63A7\u5236\uFF0C\u53CC\u91CD\u5B88\u62A4\u8FDC\u7A0B\u4F1A\u8BDD\u4E0E\u7F51\u7EDC\u914D\u7F6E\u5B89\u5168"
597
+ )
598
+ ),
599
+ React.createElement("button", {
600
+ style: { ...enabled ? s.btnPri : s.btnGhost, whiteSpace: "nowrap", flexShrink: 0 },
601
+ onClick: handleToggleEnabled
602
+ }, enabled ? "\u2713 \u5DF2\u542F\u7528\u5B89\u5168\u9632\u62A4" : "\u672A\u5F00\u542F\u5B89\u5168\u9632\u62A4")
603
+ ),
604
+ enabled && React.createElement(
605
+ "div",
606
+ {
607
+ style: {
608
+ display: "flex",
609
+ gap: 8,
610
+ flexWrap: "wrap",
611
+ marginTop: 14,
612
+ paddingTop: 12,
613
+ borderTop: "1px solid var(--dsw-alias-border-l2,#e5e7eb)"
614
+ }
615
+ },
616
+ React.createElement("div", {
617
+ style: {
618
+ display: "inline-flex",
619
+ alignItems: "center",
620
+ gap: 5,
621
+ padding: "4px 10px",
622
+ borderRadius: 16,
623
+ fontSize: 11,
624
+ background: "var(--dsw-alias-bg-layer-2,#f3f4f6)",
625
+ color: "var(--dsw-alias-label-secondary,#4b5563)"
626
+ }
627
+ }, "\u{1F310} \u4FDD\u62A4\u8303\u56F4: ", React.createElement("strong", { style: { color: "var(--dsw-alias-brand-primary,#4f6ef7)" } }, scopeLabel)),
628
+ React.createElement("div", {
629
+ style: {
630
+ display: "inline-flex",
631
+ alignItems: "center",
632
+ gap: 5,
633
+ padding: "4px 10px",
634
+ borderRadius: 16,
635
+ fontSize: 11,
636
+ background: "var(--dsw-alias-bg-layer-2,#f3f4f6)",
637
+ color: "var(--dsw-alias-label-secondary,#4b5563)"
638
+ }
639
+ }, "\u{1F511} \u5916\u90E8\u9A8C\u8BC1: ", React.createElement("strong", { style: { color: "var(--dsw-alias-brand-primary,#4f6ef7)" } }, modeLabel)),
640
+ React.createElement("div", {
641
+ style: {
642
+ display: "inline-flex",
643
+ alignItems: "center",
644
+ gap: 5,
645
+ padding: "4px 10px",
646
+ borderRadius: 16,
647
+ fontSize: 11,
648
+ background: "var(--dsw-alias-bg-layer-2,#f3f4f6)",
649
+ color: "var(--dsw-alias-label-secondary,#4b5563)"
650
+ }
651
+ }, "\u{1F512} \u540E\u53F0\u9632\u7BE1\u6539: ", React.createElement("strong", { style: { color: "var(--dsw-alias-state-success-primary,#059669)" } }, adminLabel))
652
+ ),
653
+ topMsg && React.createElement("div", {
654
+ style: {
655
+ marginTop: 12,
656
+ padding: "8px 12px",
657
+ borderRadius: 6,
658
+ fontSize: 12,
659
+ background: topMsg.ok ? "var(--dsw-alias-state-success-bg,#ecfdf5)" : "var(--dsw-alias-state-error-bg,#fef2f2)",
660
+ color: topMsg.ok ? "var(--dsw-alias-state-success-primary,#059669)" : "var(--dsw-alias-state-error-primary,#dc2626)"
661
+ }
662
+ }, topMsg.text)
663
+ ),
664
+ enabled && React.createElement(
665
+ React.Fragment,
666
+ null,
667
+ // =========================================================================
668
+ // ---- 第一道防线:外部访问门禁(控制谁能进入 Web 界面使用 AI) ----
669
+ // =========================================================================
670
+ React.createElement(
671
+ "div",
672
+ { style: s.card },
673
+ React.createElement(
674
+ "div",
675
+ { style: { marginBottom: 14 } },
676
+ React.createElement(
677
+ "div",
678
+ { style: { ...s.label, fontSize: 14, display: "flex", alignItems: "center", gap: 6 } },
679
+ "\u{1F6E1}\uFE0F \u7B2C\u4E00\u9053\u9632\u7EBF\uFF1A\u5916\u90E8\u8BBF\u95EE\u95E8\u7981\uFF08\u63A7\u5236\u8C01\u80FD\u4F7F\u7528 AI\uFF09"
680
+ ),
681
+ React.createElement(
682
+ "div",
683
+ { style: { ...s.muted, marginTop: 3 } },
684
+ "\u63A7\u5236\u5916\u90E8\u8BBE\u5907\u901A\u8FC7\u5C40\u57DF\u7F51 IP \u6216\u516C\u7F51\u96A7\u9053\uFF08Cloudflare/\u81EA\u5EFA\u96A7\u9053\uFF09\u8FDB\u5165 DSH \u804A\u5929\u754C\u9762\u65F6\u7684\u8EAB\u4EFD\u9A8C\u8BC1\u65B9\u5F0F"
685
+ )
686
+ ),
687
+ // 验证模式选择
688
+ React.createElement(
689
+ "div",
690
+ { style: { marginBottom: 16 } },
691
+ React.createElement("label", { style: { ...s.label, display: "block", marginBottom: 8, fontSize: 12 } }, "\u9A8C\u8BC1\u6A21\u5F0F\u9009\u62E9"),
692
+ React.createElement(
693
+ "div",
694
+ { style: { display: "flex", gap: 8, flexWrap: "wrap" } },
695
+ [
696
+ { id: "token_and_password", title: "\u{1F7E2} \u626B\u7801\u514D\u5BC6 + \u5BC6\u7801\u8BA4\u8BC1 (\u63A8\u8350)", desc: "\u4E8C\u7EF4\u7801\u81EA\u5E26\u4E13\u5C5E Token \u626B\u7801\u79D2\u8FDB\uFF1B\u76F4\u63A5\u8F93 IP/\u516C\u7F51\u57DF\u540D\u9700\u8F93\u5BC6\u7801" },
697
+ { id: "password_only", title: "\u{1F511} \u4EC5\u5BC6\u7801 / PIN \u7801\u767B\u5F55", desc: "\u6240\u6709\u5916\u90E8\u8BBF\u95EE\u5FC5\u987B\u624B\u52A8\u8F93\u5165\u8BBF\u95EE\u5BC6\u7801\u65B9\u53EF\u8FDB\u5165" },
698
+ { id: "token_only", title: "\u{1F3AB} \u4EC5\u5B89\u5168 Token \u514D\u5BC6", desc: "\u4EC5\u6301\u6709\u5E26\u5B89\u5168 Token \u7684\u4E8C\u7EF4\u7801\u6216\u4E13\u5C5E\u5206\u4EAB\u94FE\u63A5\u65B9\u53EF\u8FDB\u5165" }
699
+ ].map((opt) => {
700
+ const isSel = mode === opt.id;
701
+ return React.createElement(
702
+ "div",
703
+ {
704
+ key: opt.id,
705
+ onClick: () => handleChangeMode(opt.id),
706
+ style: {
707
+ flex: "1 1 200px",
708
+ padding: "10px 12px",
709
+ borderRadius: 8,
710
+ border: `1px solid ${isSel ? "var(--dsw-alias-brand-primary,#4f6ef7)" : "var(--dsw-alias-border-l2,#e5e7eb)"}`,
711
+ background: isSel ? "var(--dsw-alias-state-info-bg,#eff6ff)" : "var(--dsw-alias-bg-layer-2,#f9fafb)",
712
+ cursor: "pointer",
713
+ transition: "all 0.15s ease"
714
+ }
715
+ },
716
+ React.createElement("div", { style: { fontSize: 13, fontWeight: isSel ? 600 : 500, color: isSel ? "var(--dsw-alias-brand-primary,#4f6ef7)" : "var(--dsw-alias-label-primary,currentColor)" } }, opt.title),
717
+ React.createElement("div", { style: { ...s.muted, fontSize: 11, marginTop: 4 } }, opt.desc)
718
+ );
719
+ })
720
+ )
721
+ ),
722
+ // 防护生效范围
723
+ React.createElement(
724
+ "div",
725
+ { style: { marginBottom: 16 } },
726
+ React.createElement("label", { style: { ...s.label, display: "block", marginBottom: 8, fontSize: 12 } }, "\u9632\u62A4\u751F\u6548\u901A\u9053"),
727
+ React.createElement(
728
+ "div",
729
+ { style: { display: "flex", gap: 8, flexWrap: "wrap" } },
730
+ [
731
+ { id: "all", title: "\u5168\u90E8\u901A\u9053\u9632\u62A4 (\u63A8\u8350)", desc: "\u5C40\u57DF\u7F51 IP \u76F4\u8FDE\u4E0E\u516C\u7F51\u96A7\u9053\u5168\u90E8\u53D7\u5B89\u5168\u4FDD\u62A4" },
732
+ { id: "public_only", title: "\u4EC5\u516C\u7F51\u96A7\u9053\u5F00\u542F\u9632\u62A4", desc: "\u5C40\u57DF\u7F51\u5185\u8BBE\u5907\u76F4\u63A5\u514D\u5BC6\u76F4\u8FDE\uFF0C\u516C\u7F51\u96A7\u9053\u5F3A\u5236\u9A8C\u8BC1" },
733
+ { id: "lan_only", title: "\u4EC5\u5C40\u57DF\u7F51\u5F00\u542F\u9632\u62A4", desc: "\u4EC5\u5C40\u57DF\u7F51\u76F4\u8FDE\u9700\u9A8C\u8BC1\uFF0C\u516C\u7F51\u96A7\u9053\u4E0D\u5F00\u542F" }
734
+ ].map((opt) => {
735
+ const isSel = scope === opt.id;
736
+ return React.createElement(
737
+ "div",
738
+ {
739
+ key: opt.id,
740
+ onClick: () => handleChangeScope(opt.id),
741
+ style: {
742
+ flex: "1 1 180px",
743
+ padding: "9px 12px",
744
+ borderRadius: 8,
745
+ border: `1px solid ${isSel ? "var(--dsw-alias-brand-primary,#4f6ef7)" : "var(--dsw-alias-border-l2,#e5e7eb)"}`,
746
+ background: isSel ? "var(--dsw-alias-state-info-bg,#eff6ff)" : "var(--dsw-alias-bg-layer-2,#f9fafb)",
747
+ cursor: "pointer",
748
+ transition: "all 0.15s ease"
749
+ }
750
+ },
751
+ React.createElement("div", { style: { fontSize: 13, fontWeight: isSel ? 600 : 500, color: isSel ? "var(--dsw-alias-brand-primary,#4f6ef7)" : "var(--dsw-alias-label-primary,currentColor)" } }, opt.title),
752
+ React.createElement("div", { style: { ...s.muted, fontSize: 11, marginTop: 3 } }, opt.desc)
753
+ );
754
+ })
755
+ )
756
+ ),
757
+ // 访客访问密码输入框 (当非 token_only 时展示)
758
+ mode !== "token_only" && React.createElement(
759
+ "div",
760
+ { style: { marginBottom: 16 } },
761
+ React.createElement(
762
+ "label",
763
+ { style: { ...s.label, display: "block", marginBottom: 6, fontSize: 12 } },
764
+ `\u8BBE\u7F6E\u5916\u90E8\u8BBF\u5BA2\u8BBF\u95EE\u5BC6\u7801 ${auth?.hasPassword ? "(\u2713 \u5DF2\u8BBE\u7F6E\u8BBF\u5BA2\u5BC6\u7801)" : "(\u26A0\uFE0F \u5C1A\u672A\u8BBE\u7F6E\u5BC6\u7801\uFF0C\u76F4\u63A5\u8F93\u5165 IP \u5C06\u514D\u5BC6)"}`
765
+ ),
766
+ React.createElement(
767
+ "div",
768
+ { style: { display: "flex", gap: 8, alignItems: "center" } },
769
+ React.createElement("input", {
770
+ type: showAccessPassword ? "text" : "password",
771
+ style: { ...s.input, flex: 1 },
772
+ placeholder: auth?.hasPassword ? "\u8F93\u5165\u65B0\u5BC6\u7801\u4EE5\u4FEE\u6539\uFF08\u7559\u7A7A\u4FDD\u5B58\u53EF\u6E05\u9664\u8BBF\u5BA2\u5BC6\u7801\uFF09" : "\u8BBE\u7F6E\u5916\u90E8\u8BBF\u5BA2\u8BBF\u95EE\u5BC6\u7801 / PIN \u7801",
773
+ value: accessPassword,
774
+ onChange: (e) => setAccessPassword(e.target.value)
775
+ }),
776
+ React.createElement("button", {
777
+ style: { ...s.btnGhost, height: 32, fontSize: 12, whiteSpace: "nowrap", flexShrink: 0 },
778
+ onClick: () => setShowAccessPassword((v) => !v)
779
+ }, showAccessPassword ? "\u9690\u85CF" : "\u663E\u793A"),
780
+ React.createElement("button", {
781
+ style: {
782
+ ...s.btnPri,
783
+ height: 32,
784
+ fontSize: 12,
785
+ whiteSpace: "nowrap",
786
+ flexShrink: 0,
787
+ background: saveAccessSuccess ? "#059669" : "var(--dsw-alias-brand-primary, #4f6ef7)",
788
+ color: "#ffffff",
789
+ cursor: savingAccess ? "wait" : "pointer"
790
+ },
791
+ onClick: handleSaveAccessPassword,
792
+ disabled: savingAccess
793
+ }, savingAccess ? "\u4FDD\u5B58\u4E2D\u2026" : saveAccessSuccess ? "\u2713 \u5DF2\u6210\u529F\u4FDD\u5B58\uFF01" : "\u4FDD\u5B58\u8BBF\u95EE\u5BC6\u7801")
794
+ ),
795
+ React.createElement(
796
+ "div",
797
+ { style: { ...s.muted, fontSize: 11, marginTop: 4 } },
798
+ "\u{1F4A1} \u5F53\u5916\u90E8\u670B\u53CB\u6216\u540C\u4E8B\u672A\u901A\u8FC7\u4E8C\u7EF4\u7801\u626B\u7801\uFF0C\u800C\u662F\u76F4\u63A5\u8F93\u5165 IP \u6216\u516C\u7F51\u57DF\u540D\u8BBF\u95EE\u65F6\uFF0C\u9700\u8F93\u5165\u6B64\u5BC6\u7801\u767B\u5F55\u3002"
799
+ ),
800
+ msgAccess && React.createElement("div", {
801
+ style: {
802
+ marginTop: 8,
803
+ padding: "6px 12px",
804
+ borderRadius: 6,
805
+ fontSize: 12,
806
+ background: msgAccess.ok ? "var(--dsw-alias-state-success-bg,#ecfdf5)" : "var(--dsw-alias-state-error-bg,#fef2f2)",
807
+ color: msgAccess.ok ? "var(--dsw-alias-state-success-primary,#059669)" : "var(--dsw-alias-state-error-primary,#dc2626)"
808
+ }
809
+ }, msgAccess.text)
810
+ ),
811
+ // 免密 Token 管理
812
+ mode !== "password_only" && React.createElement(
813
+ "div",
814
+ { style: s.block },
815
+ React.createElement("label", { style: { ...s.label, display: "block", marginBottom: 6, fontSize: 12 } }, "\u514D\u5BC6\u626B\u7801 Token (\u4E13\u5C5E\u8BBF\u95EE\u51ED\u636E)"),
816
+ React.createElement(
817
+ "div",
818
+ { style: { display: "flex", gap: 8, alignItems: "center" } },
819
+ React.createElement(
820
+ "code",
821
+ { style: { ...s.code, flex: 1, padding: "6px 10px", background: "var(--dsw-alias-bg-layer-1,#fff)", borderRadius: 6, border: "1px solid var(--dsw-alias-border-l2,#e5e7eb)" } },
822
+ auth?.secretToken ? `${auth.secretToken.slice(0, 10)}****************` : "\u672A\u751F\u6210"
823
+ ),
824
+ React.createElement("button", {
825
+ style: { ...s.btnGhost, height: 32, fontSize: 12, whiteSpace: "nowrap", flexShrink: 0 },
826
+ onClick: handleRegenerateToken,
827
+ title: "\u91CD\u65B0\u751F\u6210 Token\uFF0C\u4F7F\u4E4B\u524D\u5206\u4EAB\u7684\u65E7\u4E8C\u7EF4\u7801\u548C\u94FE\u63A5\u7ACB\u5373\u5931\u6548"
828
+ }, "\u{1F504} \u91CD\u7F6E\u5B89\u5168 Token")
829
+ ),
830
+ React.createElement(
831
+ "div",
832
+ { style: { ...s.muted, fontSize: 11, marginTop: 4 } },
833
+ "\u{1F4A1} \u63A7\u5236\u53F0\u751F\u6210\u7684\u5C40\u57DF\u7F51\u4E0E\u516C\u7F51\u4E8C\u7EF4\u7801\u5DF2\u81EA\u52A8\u5D4C\u5165\u6B64 Token\uFF0C\u624B\u673A\u626B\u7801\u5373\u53EF\u514D\u5BC6\u8FDB\u5165\u804A\u5929\u754C\u9762\uFF08\u4F46\u4E0D\u8D4B\u4E88\u540E\u53F0\u7BA1\u7406\u8BBE\u7F6E\u6743\u9650\uFF09\u3002"
834
+ )
835
+ )
836
+ ),
837
+ // =========================================================================
838
+ // ---- 第二道防线:后台管理防篡改(控制谁能修改本插件所有设置) ----
839
+ // =========================================================================
840
+ React.createElement(
841
+ "div",
842
+ { style: s.card },
843
+ React.createElement(
844
+ "div",
845
+ { style: { marginBottom: 14 } },
846
+ React.createElement(
847
+ "div",
848
+ { style: { ...s.label, fontSize: 14, display: "flex", alignItems: "center", gap: 6 } },
849
+ "\u{1F512} \u7B2C\u4E8C\u9053\u9632\u7EBF\uFF1A\u7BA1\u7406\u540E\u53F0\u9632\u7BE1\u6539\uFF08\u63A7\u5236\u8C01\u80FD\u4FEE\u6539\u672C\u63D2\u4EF6\u6240\u6709\u8BBE\u7F6E\uFF09"
850
+ ),
851
+ React.createElement(
852
+ "div",
853
+ { style: { ...s.muted, marginTop: 3 } },
854
+ "\u9501\u5B9A\u6574\u4E2A\u63D2\u4EF6\u8BBE\u7F6E\u540E\u53F0\uFF08\u5305\u542B\u5C40\u57DF\u7F51\u3001\u516C\u7F51\u96A7\u9053\u3001IM \u673A\u5668\u4EBA\u5BC6\u94A5\u4E0E\u5B89\u5168\u8BBE\u7F6E\uFF09\uFF0C\u9632\u6B62\u4ED6\u4EBA\u968F\u610F\u7BE1\u6539\u914D\u7F6E"
855
+ )
856
+ ),
857
+ // 管理员密码设置
858
+ React.createElement(
859
+ "div",
860
+ { style: { marginBottom: 16 } },
861
+ React.createElement(
862
+ "label",
863
+ { style: { ...s.label, display: "block", marginBottom: 6, fontSize: 12 } },
864
+ `\u8BBE\u7F6E\u72EC\u7ACB\u7BA1\u7406\u5458\u5BC6\u7801 ${auth?.hasAdminPassword ? "(\u2713 \u5DF2\u8BBE\u7F6E\u72EC\u7ACB\u7BA1\u7406\u5BC6\u7801)" : "(\u672A\u5355\u72EC\u8BBE\u7F6E\uFF0C\u9ED8\u8BA4\u4F7F\u7528\u4E0A\u8FF0\u8BBF\u5BA2\u8BBF\u95EE\u5BC6\u7801)"}`
865
+ ),
866
+ React.createElement(
867
+ "div",
868
+ { style: { display: "flex", gap: 8, alignItems: "center" } },
869
+ React.createElement("input", {
870
+ type: showAdminPassword ? "text" : "password",
871
+ style: { ...s.input, flex: 1 },
872
+ placeholder: auth?.hasAdminPassword ? "\u8F93\u5165\u65B0\u5BC6\u7801\u4EE5\u4FEE\u6539\uFF08\u7559\u7A7A\u4FDD\u5B58\u53EF\u6E05\u9664\u72EC\u7ACB\u7BA1\u7406\u5BC6\u7801\uFF09" : "\u8BBE\u7F6E\u540E\u53F0\u7BA1\u7406\u89E3\u9501\u5BC6\u7801\uFF08\u5EFA\u8BAE\u4E0E\u8BBF\u5BA2\u5BC6\u7801\u4E0D\u540C\uFF09",
873
+ value: adminPassword,
874
+ onChange: (e) => setAdminPassword(e.target.value)
875
+ }),
876
+ React.createElement("button", {
877
+ style: { ...s.btnGhost, height: 32, fontSize: 12, whiteSpace: "nowrap", flexShrink: 0 },
878
+ onClick: () => setShowAdminPassword((v) => !v)
879
+ }, showAdminPassword ? "\u9690\u85CF" : "\u663E\u793A"),
880
+ React.createElement("button", {
881
+ style: {
882
+ ...s.btnPri,
883
+ height: 32,
884
+ fontSize: 12,
885
+ whiteSpace: "nowrap",
886
+ flexShrink: 0,
887
+ background: saveAdminSuccess ? "#059669" : "var(--dsw-alias-brand-primary, #4f6ef7)",
888
+ color: "#ffffff",
889
+ cursor: savingAdmin ? "wait" : "pointer"
890
+ },
891
+ onClick: handleSaveAdminPassword,
892
+ disabled: savingAdmin
893
+ }, savingAdmin ? "\u4FDD\u5B58\u4E2D\u2026" : saveAdminSuccess ? "\u2713 \u5DF2\u6210\u529F\u4FDD\u5B58\uFF01" : "\u4FDD\u5B58\u7BA1\u7406\u5BC6\u7801")
894
+ ),
895
+ React.createElement(
896
+ "div",
897
+ { style: { ...s.muted, fontSize: 11, marginTop: 4, color: "var(--dsw-alias-brand-primary,#4f6ef7)" } },
898
+ "\u{1F511} \u6838\u5FC3\u4F5C\u7528\uFF1A\u7528\u4E8E\u8FDC\u7A0B\u8BBE\u5907\u8FDB\u5165\u8BBE\u7F6E\u540E\u53F0\u65F6\u7684\u89E3\u9501\u9A8C\u8BC1\u3002\u8BBE\u7F6E\u540E\uFF0C\u5373\u4FBF\u628A\u8BBF\u95EE\u5BC6\u7801\u544A\u77E5\u4ED6\u4EBA\uFF0C\u4ED6\u4EBA\u4E5F\u65E0\u6CD5\u8FDB\u5165\u8BBE\u7F6E\u540E\u53F0\u6539\u914D\u7F6E\u3002"
899
+ ),
900
+ msgAdmin && React.createElement("div", {
901
+ style: {
902
+ marginTop: 8,
903
+ padding: "6px 12px",
904
+ borderRadius: 6,
905
+ fontSize: 12,
906
+ background: msgAdmin.ok ? "var(--dsw-alias-state-success-bg,#ecfdf5)" : "var(--dsw-alias-state-error-bg,#fef2f2)",
907
+ color: msgAdmin.ok ? "var(--dsw-alias-state-success-primary,#059669)" : "var(--dsw-alias-state-error-primary,#dc2626)"
908
+ }
909
+ }, msgAdmin.text)
910
+ ),
911
+ // 远程管理权限控制策略
912
+ React.createElement(
913
+ "div",
914
+ { style: s.block },
915
+ React.createElement("label", { style: { ...s.label, display: "block", marginBottom: 8, fontSize: 12 } }, "\u8FDC\u7A0B\u8BBE\u5907\u7BA1\u7406\u6743\u9650\u7B56\u7565"),
916
+ React.createElement(
917
+ "div",
918
+ { style: { display: "flex", gap: 8, flexWrap: "wrap" } },
919
+ [
920
+ { id: "password_unlock", title: "\u{1F512} \u9700\u5BC6\u7801\u89E3\u9501 (\u63A8\u8350)", desc: "\u8FDC\u7A0B\u624B\u673A/\u5916\u7F51\u6253\u5F00\u672C\u63D2\u4EF6\u8BBE\u7F6E\u65F6\u9ED8\u8BA4\u5168\u5C40\u9501\u5B9A\uFF0C\u8F93\u5165\u7BA1\u7406\u5BC6\u7801\u89E3\u9501\u540E\u65B9\u53EF\u4F7F\u7528" },
921
+ { id: "local_only", title: "\u{1F6AB} \u4EC5\u9650\u7535\u8111\u672C\u673A\u7BA1\u7406 (\u6700\u4E25\u683C)", desc: "\u8FDC\u7A0B\u8BBE\u5907\u5F7B\u5E95\u9501\u5B9A\u6574\u4E2A\u8BBE\u7F6E\u540E\u53F0\uFF0C\u4EC5\u5141\u8BB8\u5728 127.0.0.1 \u7535\u8111\u672C\u673A\u4E0A\u64CD\u4F5C" },
922
+ { id: "open", title: "\u{1F513} \u5BBD\u677E\u6A21\u5F0F", desc: "\u4EFB\u4F55\u5DF2\u901A\u8FC7\u7B2C\u4E00\u9053\u9632\u7EBF\u767B\u5F55\u7684\u8BBE\u5907\u5747\u53EF\u76F4\u63A5\u4FEE\u6539\u6240\u6709\u914D\u7F6E" }
923
+ ].map((opt) => {
924
+ const isSel = adminPolicy === opt.id;
925
+ return React.createElement(
926
+ "div",
927
+ {
928
+ key: opt.id,
929
+ onClick: () => handleChangeAdminPolicy(opt.id),
930
+ style: {
931
+ flex: "1 1 180px",
932
+ padding: "10px 12px",
933
+ borderRadius: 8,
934
+ border: `1px solid ${isSel ? "var(--dsw-alias-brand-primary,#4f6ef7)" : "var(--dsw-alias-border-l2,#e5e7eb)"}`,
935
+ background: isSel ? "var(--dsw-alias-state-info-bg,#eff6ff)" : "var(--dsw-alias-bg-layer-2,#f9fafb)",
936
+ cursor: "pointer",
937
+ transition: "all 0.15s ease"
938
+ }
939
+ },
940
+ React.createElement("div", { style: { fontSize: 13, fontWeight: isSel ? 600 : 500, color: isSel ? "var(--dsw-alias-brand-primary,#4f6ef7)" : "var(--dsw-alias-label-primary,currentColor)" } }, opt.title),
941
+ React.createElement("div", { style: { ...s.muted, fontSize: 11, marginTop: 4 } }, opt.desc)
942
+ );
943
+ })
944
+ )
945
+ )
946
+ )
947
+ )
948
+ );
949
+ });
412
950
  function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatusChange }) {
413
951
  const [platform, setPlatform] = React.useState(null);
414
952
  const [err, setErr] = React.useState(null);
@@ -1178,7 +1716,8 @@ function VersionBanner({ rpcCall }) {
1178
1716
  var TABS = [
1179
1717
  { id: "lan", label: "\u5C40\u57DF\u7F51", icon: Icons.lan },
1180
1718
  { id: "tunnel", label: "\u516C\u7F51\u96A7\u9053", icon: Icons.tunnel },
1181
- { id: "im", label: "IM \u673A\u5668\u4EBA", icon: Icons.bot }
1719
+ { id: "im", label: "IM \u673A\u5668\u4EBA", icon: Icons.bot },
1720
+ { id: "security", label: "\u5B89\u5168\u8BA4\u8BC1", icon: Icons.security }
1182
1721
  ];
1183
1722
  function TabBar({ active, onChange, dots }) {
1184
1723
  return React.createElement(
@@ -1245,6 +1784,30 @@ function BridgePanel({ rpcCall }) {
1245
1784
  const [activeTab, setActiveTab] = React.useState("lan");
1246
1785
  const [platforms, setPlatforms] = React.useState(null);
1247
1786
  const [selectedPlatform, setSelectedPlatform] = React.useState("wechat");
1787
+ const isLocalhost = typeof window === "undefined" || (!window.location.hostname || window.location.hostname === "127.0.0.1" || window.location.hostname === "localhost" || window.location.hostname === "::1" || window.location.hostname === "" || window.location.protocol === "file:" || window.location.protocol === "vscode-webview:" || window.location.protocol === "app:" || window.location.hostname.endsWith(".local"));
1788
+ const [adminUnlocked, setAdminUnlocked] = React.useState(false);
1789
+ const [unlockPassword, setUnlockPassword] = React.useState("");
1790
+ const [unlockErr, setUnlockErr] = React.useState(null);
1791
+ const [unlocking, setUnlocking] = React.useState(false);
1792
+ const [showForgotGuide, setShowForgotGuide] = React.useState(false);
1793
+ const handleUnlockAdmin = React.useCallback(async (e) => {
1794
+ e?.preventDefault?.();
1795
+ setUnlocking(true);
1796
+ setUnlockErr(null);
1797
+ try {
1798
+ const res = await rpcCall(BRIDGE_ENDPOINTS.authAdminUnlock, { password: unlockPassword });
1799
+ if (res?.ok) {
1800
+ setAdminUnlocked(true);
1801
+ setUnlockPassword("");
1802
+ } else {
1803
+ setUnlockErr(res?.error?.message || "\u7BA1\u7406\u5458\u5BC6\u7801\u9519\u8BEF");
1804
+ }
1805
+ } catch (err2) {
1806
+ setUnlockErr(err2.message || "\u89E3\u9501\u8BF7\u6C42\u5931\u8D25");
1807
+ } finally {
1808
+ setUnlocking(false);
1809
+ }
1810
+ }, [rpcCall, unlockPassword]);
1248
1811
  const load = React.useCallback(async (quiet = false) => {
1249
1812
  try {
1250
1813
  const r = await rpcCall(BRIDGE_ENDPOINTS.getStatus, {});
@@ -1300,6 +1863,7 @@ function BridgePanel({ rpcCall }) {
1300
1863
  (serverUrl, accessToken) => act(BRIDGE_ENDPOINTS.saveCustomTunnelConfig, { serverUrl, accessToken }),
1301
1864
  [act]
1302
1865
  );
1866
+ const navSecurity = React.useCallback(() => setActiveTab("security"), []);
1303
1867
  if (!status && !err) {
1304
1868
  return React.createElement("div", {
1305
1869
  style: { padding: 32, color: "var(--dsw-alias-label-tertiary,#9ca3af)", fontSize: 13 }
@@ -1312,14 +1876,17 @@ function BridgePanel({ rpcCall }) {
1312
1876
  const dots = {
1313
1877
  lan: !!status?.proxy?.running,
1314
1878
  tunnel: !!(status?.cloudflared?.running || ct?.running),
1315
- im: !!imConnected
1879
+ im: !!imConnected,
1880
+ security: !!status?.auth?.enabled
1316
1881
  };
1317
1882
  let tabContent;
1318
1883
  if (activeTab === "lan") {
1319
1884
  tabContent = React.createElement(TunnelCard, {
1320
1885
  title: "\u5C40\u57DF\u7F51\u8BBF\u95EE",
1321
1886
  desc: "\u540C\u4E00 Wi-Fi \u4E0B\u7684\u8BBE\u5907\u53EF\u76F4\u63A5\u626B\u7801\u8BBF\u95EE",
1322
- data: { running: status?.proxy?.running, url: status?.lan?.url, qr: status?.lan?.qr }
1887
+ data: { running: status?.proxy?.running, url: status?.lan?.url, qr: status?.lan?.qr },
1888
+ auth: status?.auth,
1889
+ onNavigateSecurity: navSecurity
1323
1890
  });
1324
1891
  } else if (activeTab === "tunnel") {
1325
1892
  tabContent = React.createElement(
@@ -1334,6 +1901,8 @@ function BridgePanel({ rpcCall }) {
1334
1901
  qr: status?.cloudflared?.qr,
1335
1902
  state: status?.cloudflared?.state
1336
1903
  },
1904
+ auth: status?.auth,
1905
+ onNavigateSecurity: navSecurity,
1337
1906
  onStart: onStartCloudflared,
1338
1907
  onStop: onStopCloudflared,
1339
1908
  onReset: status?.cloudflared?.running ? onResetCloudflared : null
@@ -1350,6 +1919,8 @@ function BridgePanel({ rpcCall }) {
1350
1919
  qr: ct?.qr,
1351
1920
  state: ct?.state
1352
1921
  },
1922
+ auth: status?.auth,
1923
+ onNavigateSecurity: navSecurity,
1353
1924
  onStart: onStartCustom,
1354
1925
  onStop: onStopCustom
1355
1926
  },
@@ -1361,6 +1932,12 @@ function BridgePanel({ rpcCall }) {
1361
1932
  })
1362
1933
  )
1363
1934
  );
1935
+ } else if (activeTab === "security") {
1936
+ tabContent = React.createElement(AccessAuthCard, {
1937
+ auth: status?.auth,
1938
+ rpcCall,
1939
+ onUpdate: () => load(true)
1940
+ });
1364
1941
  } else if (activeTab === "im") {
1365
1942
  const IM_PLATFORMS = [
1366
1943
  { id: "wechat", label: "\u5FAE\u4FE1", icon: Icons.wechat, brandColor: "#07C160", desc: "ClawBot \u626B\u7801\u76F4\u8FDE \xB7 \u65E0\u9700\u516C\u7F51" },
@@ -1433,12 +2010,153 @@ function BridgePanel({ rpcCall }) {
1433
2010
  })
1434
2011
  );
1435
2012
  }
2013
+ const auth = status?.auth;
2014
+ const policy = auth?.adminPolicy ?? "password_unlock";
2015
+ const isLocked = !isLocalhost && auth?.enabled && policy !== "open" && !adminUnlocked;
2016
+ if (isLocked) {
2017
+ return React.createElement(
2018
+ "div",
2019
+ { style: { maxWidth: 620 } },
2020
+ policy === "local_only" ? React.createElement(
2021
+ "div",
2022
+ {
2023
+ style: { ...s.card, textAlign: "center", padding: "36px 20px", marginTop: 10 }
2024
+ },
2025
+ React.createElement("div", { style: { fontSize: 40, marginBottom: 12 } }, "\u{1F6E1}\uFE0F"),
2026
+ React.createElement("div", { style: { ...s.label, fontSize: 16, fontWeight: 600, marginBottom: 8 } }, "\u7BA1\u7406\u63A7\u5236\u53F0\u5DF2\u9501\u5B9A\uFF08\u4EC5\u9650\u7535\u8111\u672C\u673A\u7BA1\u7406\uFF09"),
2027
+ React.createElement(
2028
+ "div",
2029
+ { style: { ...s.muted, maxWidth: 420, margin: "0 auto", lineHeight: 1.6, fontSize: 13 } },
2030
+ "\u5F53\u524D\u8BBE\u5907\u901A\u8FC7\u8FDC\u7A0B\u5C40\u57DF\u7F51\u6216\u516C\u7F51\u63A5\u5165\u3002\u5DF2\u5F00\u542F\u300C\u4EC5\u9650\u7535\u8111\u672C\u673A\u7BA1\u7406\u300D\u6700\u9AD8\u5B89\u5168\u7B56\u7565\uFF0C\u8FDC\u7A0B\u8BBE\u5907\u7981\u6B62\u67E5\u770B\u4E0E\u4FEE\u6539\u4EFB\u4F55\u7F51\u7EDC\u4E0E\u673A\u5668\u4EBA\u914D\u7F6E\u3002\u5982\u9700\u7BA1\u7406\u8BF7\u5728\u7535\u8111\u672C\u673A\uFF08127.0.0.1\uFF09\u4E0A\u64CD\u4F5C\u3002"
2031
+ ),
2032
+ React.createElement(
2033
+ "div",
2034
+ { style: { marginTop: 20 } },
2035
+ React.createElement("button", {
2036
+ type: "button",
2037
+ style: { ...s.btnLink, fontSize: 12, color: "var(--dsw-alias-label-secondary,#6b7280)" },
2038
+ onClick: () => setShowForgotGuide((v) => !v)
2039
+ }, "\u2753 \u8FDC\u7A0B\u5982\u4F55\u6551\u6025\u89E3\u9664\u9501\u5B9A\uFF1F")
2040
+ ),
2041
+ showForgotGuide && React.createElement(
2042
+ "div",
2043
+ {
2044
+ style: {
2045
+ marginTop: 14,
2046
+ padding: "12px 14px",
2047
+ borderRadius: 8,
2048
+ fontSize: 12,
2049
+ lineHeight: 1.6,
2050
+ background: "var(--dsw-alias-bg-layer-2,#f3f4f6)",
2051
+ border: "1px solid var(--dsw-alias-border-l2,#e5e7eb)",
2052
+ color: "var(--dsw-alias-label-secondary,#4b5563)",
2053
+ textAlign: "left",
2054
+ maxWidth: 420,
2055
+ margin: "14px auto 0"
2056
+ }
2057
+ },
2058
+ React.createElement("div", { style: { fontWeight: 600, color: "var(--dsw-alias-label-primary,currentColor)", marginBottom: 4 } }, "\u{1F6DF} \u6551\u6025\u89E3\u9664\u9501\u5B9A\u6307\u5F15\uFF1A"),
2059
+ React.createElement("div", null, "1. ", React.createElement("strong", null, "\u7535\u8111\u672C\u673A\u76F4\u8FDE\u4FEE\u6539"), "\uFF1A\u76F4\u63A5\u5728\u8FD0\u884C\u672C\u7A0B\u5E8F\u7684\u7535\u8111\u672C\u673A\u6253\u5F00\u672C\u63A7\u5236\u53F0\uFF08127.0.0.1 \u4EAB\u6709\u7269\u7406\u514D\u9501\u7279\u6743\uFF09\uFF0C\u53EF\u968F\u65F6\u4FEE\u6539\u7B56\u7565\u6216\u6E05\u9664\u5BC6\u7801\u3002"),
2060
+ React.createElement("div", { style: { marginTop: 4 } }, "2. ", React.createElement("strong", null, "\u670D\u52A1\u5668\u6551\u6025\u6307\u4EE4"), "\uFF1A\u5728\u5BBF\u4E3B\u7535\u8111/\u670D\u52A1\u5668\u7EC8\u7AEF\u6267\u884C ", React.createElement("code", { style: s.code }, "touch ~/.dsh/dsh-bridge/reset-auth"), " \u5373\u53EF\u77AC\u95F4\u6E05\u7A7A\u5BC6\u7801\u6062\u590D\u521D\u59CB\u72B6\u6001\u3002")
2061
+ )
2062
+ ) : React.createElement(
2063
+ "div",
2064
+ {
2065
+ style: { ...s.card, maxWidth: 440, margin: "20px auto", padding: "32px 24px" }
2066
+ },
2067
+ React.createElement(
2068
+ "div",
2069
+ { style: { textAlign: "center", marginBottom: 20 } },
2070
+ React.createElement("div", { style: { fontSize: 40, marginBottom: 10 } }, "\u{1F512}"),
2071
+ React.createElement("div", { style: { ...s.label, fontSize: 16, fontWeight: 600 } }, "\u7BA1\u7406\u63A7\u5236\u53F0\u5DF2\u9501\u5B9A"),
2072
+ React.createElement(
2073
+ "div",
2074
+ { style: { ...s.muted, fontSize: 12, marginTop: 6, lineHeight: 1.5 } },
2075
+ "\u5F53\u524D\u8BBE\u5907\u4E3A\u8FDC\u7A0B\u8BBF\u95EE\u3002\u4E3A\u4FDD\u62A4\u60A8\u7684\u7F51\u7EDC\u4E0E\u5E73\u53F0\u914D\u7F6E\u5B89\u5168\uFF0C\u8BF7\u8F93\u5165\u7BA1\u7406\u5458\u5BC6\u7801\u89E3\u9501\u7BA1\u7406\u6743\u9650\u3002"
2076
+ )
2077
+ ),
2078
+ React.createElement(
2079
+ "form",
2080
+ {
2081
+ onSubmit: handleUnlockAdmin,
2082
+ style: { display: "flex", flexDirection: "column", gap: 12 }
2083
+ },
2084
+ React.createElement("input", {
2085
+ type: "password",
2086
+ style: s.input,
2087
+ placeholder: "\u8F93\u5165\u540E\u53F0\u7BA1\u7406\u5BC6\u7801",
2088
+ value: unlockPassword,
2089
+ onChange: (e) => setUnlockPassword(e.target.value),
2090
+ autoFocus: true
2091
+ }),
2092
+ unlockErr && React.createElement("div", {
2093
+ style: { fontSize: 12, color: "var(--dsw-alias-state-error-primary,#dc2626)" }
2094
+ }, unlockErr),
2095
+ React.createElement("button", {
2096
+ type: "submit",
2097
+ style: { ...s.btnPri, width: "100%", justifyContent: "center", height: 36, background: "#4f6ef7", color: "#ffffff" },
2098
+ disabled: unlocking
2099
+ }, unlocking ? "\u9A8C\u8BC1\u4E2D\u2026" : "\u89E3\u9501\u7BA1\u7406\u6743\u9650")
2100
+ ),
2101
+ React.createElement(
2102
+ "div",
2103
+ { style: { marginTop: 16, textAlign: "center" } },
2104
+ React.createElement("button", {
2105
+ type: "button",
2106
+ style: { ...s.btnLink, fontSize: 12, color: "var(--dsw-alias-label-secondary,#6b7280)" },
2107
+ onClick: () => setShowForgotGuide((v) => !v)
2108
+ }, "\u2753 \u5FD8\u8BB0\u540E\u53F0\u7BA1\u7406\u5BC6\u7801\uFF1F")
2109
+ ),
2110
+ showForgotGuide && React.createElement(
2111
+ "div",
2112
+ {
2113
+ style: {
2114
+ marginTop: 12,
2115
+ padding: "12px 14px",
2116
+ borderRadius: 8,
2117
+ fontSize: 12,
2118
+ lineHeight: 1.6,
2119
+ background: "var(--dsw-alias-bg-layer-2,#f3f4f6)",
2120
+ border: "1px solid var(--dsw-alias-border-l2,#e5e7eb)",
2121
+ color: "var(--dsw-alias-label-secondary,#4b5563)",
2122
+ textAlign: "left"
2123
+ }
2124
+ },
2125
+ React.createElement("div", { style: { fontWeight: 600, color: "var(--dsw-alias-label-primary,currentColor)", marginBottom: 4 } }, "\u{1F6DF} \u627E\u56DE\u4E0E\u91CD\u7F6E\u5BC6\u7801\u6307\u5F15\uFF1A"),
2126
+ React.createElement("div", null, "1. ", React.createElement("strong", null, "\u7535\u8111\u672C\u673A\u76F4\u8FDE\u4FEE\u6539"), "\uFF1A\u76F4\u63A5\u5728\u8FD0\u884C\u672C\u7A0B\u5E8F\u7684\u7535\u8111\u672C\u673A\u6253\u5F00\u672C\u63A7\u5236\u53F0\uFF08127.0.0.1 \u4EAB\u6709\u7269\u7406\u514D\u9501\u7279\u6743\uFF09\uFF0C\u53EF\u968F\u65F6\u4FEE\u6539\u7BA1\u7406\u5BC6\u7801\u3002"),
2127
+ React.createElement("div", { style: { marginTop: 4 } }, "2. ", React.createElement("strong", null, "\u670D\u52A1\u5668\u6551\u6025\u6307\u4EE4"), "\uFF1A\u5728\u5BBF\u4E3B\u7535\u8111\u7EC8\u7AEF\u6267\u884C ", React.createElement("code", { style: s.code }, "touch ~/.dsh/dsh-bridge/reset-auth"), " \u5373\u53EF\u77AC\u95F4\u6E05\u7A7A\u5BC6\u7801\u6062\u590D\u521D\u59CB\u72B6\u6001\u3002")
2128
+ )
2129
+ )
2130
+ );
2131
+ }
1436
2132
  return React.createElement(
1437
2133
  "div",
1438
2134
  { style: { maxWidth: 620 } },
1439
2135
  err && React.createElement("div", {
1440
2136
  style: { ...s.card, background: "var(--dsw-alias-state-error-bg,#fef2f2)", color: "var(--dsw-alias-state-error-primary,#dc2626)", fontSize: 13, marginBottom: 16 }
1441
2137
  }, err),
2138
+ !isLocalhost && adminUnlocked && React.createElement(
2139
+ "div",
2140
+ {
2141
+ style: {
2142
+ display: "flex",
2143
+ alignItems: "center",
2144
+ justifyContent: "space-between",
2145
+ padding: "8px 14px",
2146
+ background: "var(--dsw-alias-state-info-bg,#eff6ff)",
2147
+ border: "1px solid var(--dsw-alias-brand-primary,#4f6ef7)",
2148
+ borderRadius: 8,
2149
+ marginBottom: 14,
2150
+ fontSize: 12,
2151
+ color: "var(--dsw-alias-brand-primary,#4f6ef7)"
2152
+ }
2153
+ },
2154
+ React.createElement("span", null, "\u{1F513} \u7BA1\u7406\u5458\u6743\u9650\u5DF2\u89E3\u9501\uFF08\u5F53\u524D\u4E34\u65F6\u4F1A\u8BDD\u6709\u6548\uFF09"),
2155
+ React.createElement("button", {
2156
+ style: { ...s.btnGhost, height: 24, fontSize: 11, padding: "0 8px" },
2157
+ onClick: () => setAdminUnlocked(false)
2158
+ }, "\u{1F512} \u91CD\u65B0\u9501\u5B9A\u540E\u53F0")
2159
+ ),
1442
2160
  React.createElement(VersionBanner, { rpcCall }),
1443
2161
  React.createElement(TabBar, { active: activeTab, onChange: setActiveTab, dots }),
1444
2162
  tabContent