@wenbin_wb/dsh-bridge 2.3.3 → 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(
262
+ "div",
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(
251
284
  "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"
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);
@@ -421,16 +959,18 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
421
959
  setCfgDraft({
422
960
  digestIntervalSec: String(platform.config.digestIntervalSec ?? 300),
423
961
  approvalTimeoutSec: String(platform.config.approvalTimeoutSec ?? 600),
424
- maxMessageChars: String(platform.config.maxMessageChars ?? 2e3),
962
+ maxMessageChars: String(platform.config.maxMessageChars ?? (platformId === "telegram" ? 4096 : 2e3)),
425
963
  sendChunkDelayMs: String(platform.config.sendChunkDelayMs ?? 1500),
426
964
  appId: platform.config.appId ?? "",
427
965
  // Secret 不由后端回传;空值表示沿用已保存密钥
428
966
  clientSecret: "",
429
967
  appSecret: "",
430
- domain: platform.config.domain ?? "feishu"
968
+ domain: platform.config.domain ?? "feishu",
969
+ botToken: "",
970
+ proxy: platform.config.proxy ?? ""
431
971
  });
432
972
  }
433
- }, [platform?.config]);
973
+ }, [platform?.config, platformId]);
434
974
  React.useEffect(() => {
435
975
  const connected2 = platform?.status === "connected" || platform?.status === "starting" || platform?.status === "reconnecting";
436
976
  onStatusChange?.(connected2);
@@ -488,10 +1028,10 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
488
1028
  ...d,
489
1029
  digestIntervalSec: "300",
490
1030
  approvalTimeoutSec: "600",
491
- maxMessageChars: "2000",
1031
+ maxMessageChars: platformId === "telegram" ? "4096" : "2000",
492
1032
  sendChunkDelayMs: "1500"
493
1033
  }));
494
- }, []);
1034
+ }, [platformId]);
495
1035
  const saveConfig = React.useCallback(async () => {
496
1036
  if (!cfgDraft) return;
497
1037
  const payload = {
@@ -507,10 +1047,13 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
507
1047
  payload.appId = cfgDraft.appId.trim();
508
1048
  payload.appSecret = cfgDraft.appSecret.trim();
509
1049
  payload.domain = cfgDraft.domain || "feishu";
1050
+ } else if (platformId === "telegram") {
1051
+ payload.botToken = cfgDraft.botToken.trim();
1052
+ payload.proxy = cfgDraft.proxy.trim();
510
1053
  }
511
1054
  await act(BRIDGE_ENDPOINTS.platformSetConfig, payload);
512
1055
  }, [act, cfgDraft, platformId]);
513
- const cfgDirty = cfgDraft && platform?.config && (Number(cfgDraft.digestIntervalSec) !== platform.config.digestIntervalSec || Number(cfgDraft.approvalTimeoutSec) !== platform.config.approvalTimeoutSec || Number(cfgDraft.maxMessageChars) !== platform.config.maxMessageChars || Number(cfgDraft.sendChunkDelayMs) !== platform.config.sendChunkDelayMs || platformId === "qq" && (cfgDraft.appId !== (platform.config.appId ?? "") || cfgDraft.clientSecret !== (platform.config.clientSecret ?? "")) || platformId === "feishu" && (cfgDraft.appId !== (platform.config.appId ?? "") || cfgDraft.appSecret !== (platform.config.appSecret ?? "")));
1056
+ const cfgDirty = cfgDraft && platform?.config && (Number(cfgDraft.digestIntervalSec) !== platform.config.digestIntervalSec || Number(cfgDraft.approvalTimeoutSec) !== platform.config.approvalTimeoutSec || Number(cfgDraft.maxMessageChars) !== platform.config.maxMessageChars || Number(cfgDraft.sendChunkDelayMs) !== platform.config.sendChunkDelayMs || platformId === "qq" && (cfgDraft.appId !== (platform.config.appId ?? "") || cfgDraft.clientSecret !== (platform.config.clientSecret ?? "")) || platformId === "feishu" && (cfgDraft.appId !== (platform.config.appId ?? "") || cfgDraft.appSecret !== (platform.config.appSecret ?? "")) || platformId === "telegram" && (cfgDraft.botToken !== "" || cfgDraft.proxy !== (platform.config.proxy ?? "")));
514
1057
  if (!platform && !err) {
515
1058
  return React.createElement(
516
1059
  "div",
@@ -579,6 +1122,18 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
579
1122
  rel: "noopener noreferrer",
580
1123
  style: s.btnGhost
581
1124
  }, "\u{1F310} \u98DE\u4E66\u5F00\u653E\u5E73\u53F0"),
1125
+ platformId === "telegram" && React.createElement("a", {
1126
+ href: "https://github.com/wenbin-wb/dsh-bridge/blob/main/docs/telegram-usage.md",
1127
+ target: "_blank",
1128
+ rel: "noopener noreferrer",
1129
+ style: s.btnGhost
1130
+ }, "\u{1F4D6} Telegram \u4F7F\u7528\u8BF4\u660E"),
1131
+ platformId === "telegram" && React.createElement("a", {
1132
+ href: "https://t.me/BotFather",
1133
+ target: "_blank",
1134
+ rel: "noopener noreferrer",
1135
+ style: s.btnGhost
1136
+ }, "\u{1F310} @BotFather \u7533\u8BF7 Bot"),
582
1137
  React.createElement("button", {
583
1138
  style: s.btnGhost,
584
1139
  onClick: () => setShowHelp((v) => !v)
@@ -724,8 +1279,8 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
724
1279
  title: "\u6E05\u9664\u767B\u5F55\u51ED\u8BC1\uFF0C\u4E0B\u6B21\u9700\u91CD\u65B0\u914D\u7F6E"
725
1280
  }, "\u89E3\u7ED1\u8D26\u53F7")
726
1281
  ),
727
- // 飞书专属:手机扫码直达 Bot 对话
728
- platformId === "feishu" && platform.botQr && React.createElement(
1282
+ // 飞书 / Telegram 扫码直达对话引导卡片
1283
+ (platformId === "feishu" || platformId === "telegram") && platform.botQr && React.createElement(
729
1284
  "div",
730
1285
  {
731
1286
  style: {
@@ -740,15 +1295,15 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
740
1295
  flexWrap: "wrap"
741
1296
  }
742
1297
  },
743
- React.createElement("img", { src: platform.botQr, alt: "Feishu Bot QR", style: { width: 110, height: 110, borderRadius: 8, border: "1px solid var(--dsw-alias-border-l2,#e5e7eb)", padding: 4, background: "#fff" } }),
1298
+ React.createElement("img", { src: platform.botQr, alt: `${platformName} Bot QR`, style: { width: 110, height: 110, borderRadius: 8, border: "1px solid var(--dsw-alias-border-l2,#e5e7eb)", padding: 4, background: "#fff" } }),
744
1299
  React.createElement(
745
1300
  "div",
746
1301
  { style: { flex: 1, minWidth: 160 } },
747
- React.createElement("div", { style: { ...s.label, fontSize: 13, fontWeight: 600 } }, "\u{1F4F1} \u624B\u673A\u98DE\u4E66\u626B\u7801\u76F4\u8FBE\u5BF9\u8BDD"),
1302
+ React.createElement("div", { style: { ...s.label, fontSize: 13, fontWeight: 600 } }, `\u{1F4F1} \u624B\u673A ${platformName} \u626B\u7801\u76F4\u8FBE\u5BF9\u8BDD`),
748
1303
  React.createElement(
749
1304
  "div",
750
1305
  { style: { ...s.muted, fontSize: 12, marginTop: 4, lineHeight: 1.5 } },
751
- "\u7528\u98DE\u4E66\u624B\u673A App \u626B\u63CF\u5DE6\u4FA7\u4E8C\u7EF4\u7801\uFF0C\u7ACB\u5373\u6253\u5F00\u4E0E Bot \u5BF9\u8BDD\uFF1B\u53D1\u9001\u9996\u6761\u6D88\u606F\u81EA\u52A8\u5B8C\u6210\u767D\u540D\u5355\u6388\u6743\u3002"
1306
+ `\u7528 ${platformName} \u626B\u63CF\u5DE6\u4FA7\u4E8C\u7EF4\u7801\uFF0C\u7ACB\u5373\u6253\u5F00\u4E0E Bot \u5BF9\u8BDD\uFF1B\u53D1\u9001\u9996\u6761\u6D88\u606F\u81EA\u52A8\u5B8C\u6210\u767D\u540D\u5355\u6388\u6743\u3002`
752
1307
  ),
753
1308
  platform.botLink && React.createElement(
754
1309
  "div",
@@ -758,12 +1313,12 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
758
1313
  target: "_blank",
759
1314
  rel: "noopener noreferrer",
760
1315
  style: { ...s.btnGhost, textDecoration: "none", display: "inline-flex", alignItems: "center", gap: 4, padding: "4px 10px", fontSize: 12 }
761
- }, "\u5728\u98DE\u4E66\u5BA2\u6237\u7AEF\u6253\u5F00 \u2197")
1316
+ }, `\u5728 ${platformName} \u5BA2\u6237\u7AEF\u6253\u5F00 \u2197`)
762
1317
  )
763
1318
  )
764
1319
  )
765
1320
  ),
766
- // 未配置 / 登录中:表单(QQ / 飞书)或二维码(微信)
1321
+ // 未配置 / 登录中:表单(QQ / 飞书 / Telegram)或二维码(微信)
767
1322
  (!platform?.configured || showQr) && React.createElement(
768
1323
  "div",
769
1324
  { style: s.block },
@@ -777,7 +1332,7 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
777
1332
  login.phase === "scaned" ? "\u5DF2\u626B\u7801\uFF0C\u8BF7\u5728\u624B\u673A\u4E0A\u786E\u8BA4\u2026" : platformId === "wechat" ? "\u8BF7\u4F7F\u7528\u5FAE\u4FE1\u626B\u7801\u767B\u5F55\uFF08ClawBot\uFF09" : "\u8BF7\u626B\u7801\u767B\u5F55"
778
1333
  ),
779
1334
  login.error && React.createElement("div", { style: { ...s.muted, marginTop: 4, color: "var(--dsw-alias-state-warn-primary,#92400e)" } }, login.error)
780
- ) : platformId === "qq" || platformId === "feishu" ? React.createElement(
1335
+ ) : platformId === "qq" || platformId === "feishu" || platformId === "telegram" ? React.createElement(
781
1336
  "div",
782
1337
  { style: { display: "flex", flexDirection: "column", gap: 10, marginTop: 4 } },
783
1338
  platformId === "feishu" && React.createElement(
@@ -797,52 +1352,92 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
797
1352
  React.createElement("code", { style: { ...s.code, fontSize: 11, background: "var(--dsw-alias-bg-layer-2,#f3f4f6)", padding: "2px 4px", borderRadius: 4 } }, "npx feishu-bot-bootstrap"),
798
1353
  React.createElement("span", { style: s.muted }, " \u624B\u673A\u626B\u7801\u4E00\u952E\u81EA\u52A8\u521B\u5EFA\u5E94\u7528\u5E76\u8F93\u51FA\u51ED\u8BC1\uFF1B\u6216\u5728\u4E0B\u65B9\u624B\u52A8\u586B\u5165\u51ED\u8BC1\u3002")
799
1354
  ),
800
- React.createElement(
801
- "div",
1355
+ platformId === "telegram" ? React.createElement(
1356
+ React.Fragment,
802
1357
  null,
803
1358
  React.createElement(
804
1359
  "div",
805
- { style: { ...s.muted, marginBottom: 4 } },
806
- platformId === "qq" ? "AppID \u2014 QQ \u5F00\u653E\u5E73\u53F0\u673A\u5668\u4EBA\u5E94\u7528 ID" : "App ID \u2014 \u98DE\u4E66\u5F00\u653E\u5E73\u53F0\u81EA\u5EFA\u5E94\u7528 ID (cli_xxx)"
1360
+ null,
1361
+ React.createElement("div", { style: { ...s.muted, marginBottom: 4 } }, "Bot Token \u2014 Telegram @BotFather \u4E0B\u53D1\u7684\u673A\u5668\u4EBA Token"),
1362
+ React.createElement(
1363
+ "div",
1364
+ { style: { display: "flex", gap: 6, alignItems: "center" } },
1365
+ React.createElement("input", {
1366
+ style: { ...s.input, flex: 1 },
1367
+ type: showSecret ? "text" : "password",
1368
+ placeholder: "\u8BF7\u8F93\u5165 Telegram Bot Token (\u5982 123456789:ABCdef...)",
1369
+ value: cfgDraft?.botToken ?? "",
1370
+ onChange: (e) => setCfgDraft((d) => ({ ...d, botToken: e.target.value }))
1371
+ }),
1372
+ React.createElement("button", {
1373
+ style: { ...s.btnGhost, height: 32, padding: "0 10px", fontSize: 13, flexShrink: 0 },
1374
+ onClick: () => setShowSecret((v) => !v),
1375
+ type: "button",
1376
+ title: showSecret ? "\u9690\u85CF\u5BC6\u94A5" : "\u663E\u793A\u660E\u6587"
1377
+ }, showSecret ? "\u{1F648} \u9690\u85CF" : "\u{1F441}\uFE0F \u663E\u793A")
1378
+ )
807
1379
  ),
808
- React.createElement("input", {
809
- style: { ...s.input, width: "100%" },
810
- placeholder: platformId === "qq" ? "\u8BF7\u8F93\u5165 AppID" : "\u8BF7\u8F93\u5165 App ID (\u5982 cli_a1b2c3d4...)",
811
- value: cfgDraft?.appId ?? "",
812
- onChange: (e) => setCfgDraft((d) => ({ ...d, appId: e.target.value }))
813
- })
814
- ),
815
- React.createElement(
816
- "div",
1380
+ React.createElement(
1381
+ "div",
1382
+ null,
1383
+ React.createElement("div", { style: { ...s.muted, marginBottom: 4 } }, "\u7F51\u7EDC\u4EE3\u7406 (\u53EF\u9009) \u2014 \u652F\u6301\u56FD\u5185 HTTP / HTTPS \u4EE3\u7406"),
1384
+ React.createElement("input", {
1385
+ style: { ...s.input, width: "100%" },
1386
+ placeholder: "\u53EF\u9009\uFF0C\u4F8B\u5982 http://127.0.0.1:7890\uFF08\u4E3A\u7A7A\u5219\u76F4\u8FDE\u6216\u8BFB\u53D6\u73AF\u5883\u53D8\u91CF\uFF09",
1387
+ value: cfgDraft?.proxy ?? "",
1388
+ onChange: (e) => setCfgDraft((d) => ({ ...d, proxy: e.target.value }))
1389
+ })
1390
+ )
1391
+ ) : React.createElement(
1392
+ React.Fragment,
817
1393
  null,
818
1394
  React.createElement(
819
1395
  "div",
820
- { style: { ...s.muted, marginBottom: 4 } },
821
- platformId === "qq" ? "ClientSecret \u2014 QQ \u5F00\u653E\u5E73\u53F0\u673A\u5668\u4EBA\u5BC6\u94A5" : "App Secret \u2014 \u98DE\u4E66\u5F00\u653E\u5E73\u53F0\u5E94\u7528\u5BC6\u94A5"
1396
+ null,
1397
+ React.createElement(
1398
+ "div",
1399
+ { style: { ...s.muted, marginBottom: 4 } },
1400
+ platformId === "qq" ? "AppID \u2014 QQ \u5F00\u653E\u5E73\u53F0\u673A\u5668\u4EBA\u5E94\u7528 ID" : "App ID \u2014 \u98DE\u4E66\u5F00\u653E\u5E73\u53F0\u81EA\u5EFA\u5E94\u7528 ID (cli_xxx)"
1401
+ ),
1402
+ React.createElement("input", {
1403
+ style: { ...s.input, width: "100%" },
1404
+ placeholder: platformId === "qq" ? "\u8BF7\u8F93\u5165 AppID" : "\u8BF7\u8F93\u5165 App ID (\u5982 cli_a1b2c3d4...)",
1405
+ value: cfgDraft?.appId ?? "",
1406
+ onChange: (e) => setCfgDraft((d) => ({ ...d, appId: e.target.value }))
1407
+ })
822
1408
  ),
823
1409
  React.createElement(
824
1410
  "div",
825
- { style: { display: "flex", gap: 6, alignItems: "center" } },
826
- React.createElement("input", {
827
- style: { ...s.input, flex: 1 },
828
- type: showSecret ? "text" : "password",
829
- placeholder: platformId === "qq" ? "\u8BF7\u8F93\u5165 ClientSecret" : "\u8BF7\u8F93\u5165 App Secret",
830
- value: platformId === "qq" ? cfgDraft?.clientSecret ?? "" : cfgDraft?.appSecret ?? "",
831
- onChange: (e) => setCfgDraft((d) => platformId === "qq" ? { ...d, clientSecret: e.target.value } : { ...d, appSecret: e.target.value })
832
- }),
833
- React.createElement("button", {
834
- style: { ...s.btnGhost, height: 32, padding: "0 10px", fontSize: 13, flexShrink: 0 },
835
- onClick: () => setShowSecret((v) => !v),
836
- type: "button",
837
- title: showSecret ? "\u9690\u85CF\u5BC6\u94A5" : "\u663E\u793A\u660E\u6587"
838
- }, showSecret ? "\u{1F648} \u9690\u85CF" : "\u{1F441}\uFE0F \u663E\u793A")
1411
+ null,
1412
+ React.createElement(
1413
+ "div",
1414
+ { style: { ...s.muted, marginBottom: 4 } },
1415
+ platformId === "qq" ? "ClientSecret \u2014 QQ \u5F00\u653E\u5E73\u53F0\u673A\u5668\u4EBA\u5BC6\u94A5" : "App Secret \u2014 \u98DE\u4E66\u5F00\u653E\u5E73\u53F0\u5E94\u7528\u5BC6\u94A5"
1416
+ ),
1417
+ React.createElement(
1418
+ "div",
1419
+ { style: { display: "flex", gap: 6, alignItems: "center" } },
1420
+ React.createElement("input", {
1421
+ style: { ...s.input, flex: 1 },
1422
+ type: showSecret ? "text" : "password",
1423
+ placeholder: platformId === "qq" ? "\u8BF7\u8F93\u5165 ClientSecret" : "\u8BF7\u8F93\u5165 App Secret",
1424
+ value: platformId === "qq" ? cfgDraft?.clientSecret ?? "" : cfgDraft?.appSecret ?? "",
1425
+ onChange: (e) => setCfgDraft((d) => platformId === "qq" ? { ...d, clientSecret: e.target.value } : { ...d, appSecret: e.target.value })
1426
+ }),
1427
+ React.createElement("button", {
1428
+ style: { ...s.btnGhost, height: 32, padding: "0 10px", fontSize: 13, flexShrink: 0 },
1429
+ onClick: () => setShowSecret((v) => !v),
1430
+ type: "button",
1431
+ title: showSecret ? "\u9690\u85CF\u5BC6\u94A5" : "\u663E\u793A\u660E\u6587"
1432
+ }, showSecret ? "\u{1F648} \u9690\u85CF" : "\u{1F441}\uFE0F \u663E\u793A")
1433
+ )
839
1434
  )
840
1435
  ),
841
1436
  React.createElement(
842
1437
  "div",
843
1438
  null,
844
1439
  React.createElement("a", {
845
- href: platformId === "qq" ? "https://bot.q.qq.com/wiki/develop/api-v2/" : "https://open.feishu.cn/app",
1440
+ href: platformId === "qq" ? "https://bot.q.qq.com/wiki/develop/api-v2/" : platformId === "feishu" ? "https://open.feishu.cn/app" : "https://t.me/BotFather",
846
1441
  target: "_blank",
847
1442
  rel: "noopener noreferrer",
848
1443
  style: s.btnLink
@@ -854,7 +1449,7 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
854
1449
  React.createElement("button", {
855
1450
  style: { ...s.btnPri, opacity: busy ? 0.5 : 1 },
856
1451
  onClick: saveConfig,
857
- disabled: busy || !cfgDraft?.appId?.trim() || (platformId === "qq" ? !cfgDraft?.clientSecret?.trim() : !cfgDraft?.appSecret?.trim())
1452
+ disabled: busy || (platformId === "telegram" ? !cfgDraft?.botToken?.trim() : !cfgDraft?.appId?.trim() || (platformId === "qq" ? !cfgDraft?.clientSecret?.trim() : !cfgDraft?.appSecret?.trim()))
858
1453
  }, busy ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58\u5E76\u8FDE\u63A5"),
859
1454
  login.phase === "error" && React.createElement("div", { style: { ...s.muted, fontSize: 12 } }, login.error ?? "\u8FDE\u63A5\u5931\u8D25")
860
1455
  )
@@ -1121,7 +1716,8 @@ function VersionBanner({ rpcCall }) {
1121
1716
  var TABS = [
1122
1717
  { id: "lan", label: "\u5C40\u57DF\u7F51", icon: Icons.lan },
1123
1718
  { id: "tunnel", label: "\u516C\u7F51\u96A7\u9053", icon: Icons.tunnel },
1124
- { 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 }
1125
1721
  ];
1126
1722
  function TabBar({ active, onChange, dots }) {
1127
1723
  return React.createElement(
@@ -1188,6 +1784,30 @@ function BridgePanel({ rpcCall }) {
1188
1784
  const [activeTab, setActiveTab] = React.useState("lan");
1189
1785
  const [platforms, setPlatforms] = React.useState(null);
1190
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]);
1191
1811
  const load = React.useCallback(async (quiet = false) => {
1192
1812
  try {
1193
1813
  const r = await rpcCall(BRIDGE_ENDPOINTS.getStatus, {});
@@ -1243,6 +1863,7 @@ function BridgePanel({ rpcCall }) {
1243
1863
  (serverUrl, accessToken) => act(BRIDGE_ENDPOINTS.saveCustomTunnelConfig, { serverUrl, accessToken }),
1244
1864
  [act]
1245
1865
  );
1866
+ const navSecurity = React.useCallback(() => setActiveTab("security"), []);
1246
1867
  if (!status && !err) {
1247
1868
  return React.createElement("div", {
1248
1869
  style: { padding: 32, color: "var(--dsw-alias-label-tertiary,#9ca3af)", fontSize: 13 }
@@ -1255,14 +1876,17 @@ function BridgePanel({ rpcCall }) {
1255
1876
  const dots = {
1256
1877
  lan: !!status?.proxy?.running,
1257
1878
  tunnel: !!(status?.cloudflared?.running || ct?.running),
1258
- im: !!imConnected
1879
+ im: !!imConnected,
1880
+ security: !!status?.auth?.enabled
1259
1881
  };
1260
1882
  let tabContent;
1261
1883
  if (activeTab === "lan") {
1262
1884
  tabContent = React.createElement(TunnelCard, {
1263
1885
  title: "\u5C40\u57DF\u7F51\u8BBF\u95EE",
1264
1886
  desc: "\u540C\u4E00 Wi-Fi \u4E0B\u7684\u8BBE\u5907\u53EF\u76F4\u63A5\u626B\u7801\u8BBF\u95EE",
1265
- 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
1266
1890
  });
1267
1891
  } else if (activeTab === "tunnel") {
1268
1892
  tabContent = React.createElement(
@@ -1277,6 +1901,8 @@ function BridgePanel({ rpcCall }) {
1277
1901
  qr: status?.cloudflared?.qr,
1278
1902
  state: status?.cloudflared?.state
1279
1903
  },
1904
+ auth: status?.auth,
1905
+ onNavigateSecurity: navSecurity,
1280
1906
  onStart: onStartCloudflared,
1281
1907
  onStop: onStopCloudflared,
1282
1908
  onReset: status?.cloudflared?.running ? onResetCloudflared : null
@@ -1293,6 +1919,8 @@ function BridgePanel({ rpcCall }) {
1293
1919
  qr: ct?.qr,
1294
1920
  state: ct?.state
1295
1921
  },
1922
+ auth: status?.auth,
1923
+ onNavigateSecurity: navSecurity,
1296
1924
  onStart: onStartCustom,
1297
1925
  onStop: onStopCustom
1298
1926
  },
@@ -1304,6 +1932,12 @@ function BridgePanel({ rpcCall }) {
1304
1932
  })
1305
1933
  )
1306
1934
  );
1935
+ } else if (activeTab === "security") {
1936
+ tabContent = React.createElement(AccessAuthCard, {
1937
+ auth: status?.auth,
1938
+ rpcCall,
1939
+ onUpdate: () => load(true)
1940
+ });
1307
1941
  } else if (activeTab === "im") {
1308
1942
  const IM_PLATFORMS = [
1309
1943
  { id: "wechat", label: "\u5FAE\u4FE1", icon: Icons.wechat, brandColor: "#07C160", desc: "ClawBot \u626B\u7801\u76F4\u8FDE \xB7 \u65E0\u9700\u516C\u7F51" },
@@ -1376,12 +2010,153 @@ function BridgePanel({ rpcCall }) {
1376
2010
  })
1377
2011
  );
1378
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
+ }
1379
2132
  return React.createElement(
1380
2133
  "div",
1381
2134
  { style: { maxWidth: 620 } },
1382
2135
  err && React.createElement("div", {
1383
2136
  style: { ...s.card, background: "var(--dsw-alias-state-error-bg,#fef2f2)", color: "var(--dsw-alias-state-error-primary,#dc2626)", fontSize: 13, marginBottom: 16 }
1384
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
+ ),
1385
2160
  React.createElement(VersionBanner, { rpcCall }),
1386
2161
  React.createElement(TabBar, { active: activeTab, onChange: setActiveTab, dots }),
1387
2162
  tabContent