@wenbin_wb/dsh-bridge 2.4.0 → 2.5.1

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 },
@@ -308,13 +346,17 @@ var CustomTunnelConfigForm = React.memo(function CustomTunnelConfigForm2({ serve
308
346
  }
309
347
  }, [initUrl, initToken]);
310
348
  const dirty = serverUrl !== (initUrl ?? "") || accessToken !== (initToken ?? "");
349
+ const [saveErr, setSaveErr] = React.useState(null);
311
350
  const handleSave = React.useCallback(async () => {
312
351
  setSaving(true);
313
352
  setSaveSuccess(false);
353
+ setSaveErr(null);
314
354
  try {
315
355
  await onSave(serverUrl, accessToken);
316
356
  setSaveSuccess(true);
317
357
  setTimeout(() => setSaveSuccess(false), 2500);
358
+ } catch (e) {
359
+ setSaveErr(e.message || "\u4FDD\u5B58\u914D\u7F6E\u5931\u8D25");
318
360
  } finally {
319
361
  setSaving(false);
320
362
  }
@@ -322,15 +364,17 @@ var CustomTunnelConfigForm = React.memo(function CustomTunnelConfigForm2({ serve
322
364
  const handleUrlChange = React.useCallback((e) => {
323
365
  setServerUrl(e.target.value);
324
366
  setSaveSuccess(false);
367
+ setSaveErr(null);
325
368
  }, []);
326
369
  const handleTokenChange = React.useCallback((e) => {
327
370
  setAccessToken(e.target.value);
328
371
  setSaveSuccess(false);
372
+ setSaveErr(null);
329
373
  }, []);
330
374
  return React.createElement(
331
375
  "div",
332
376
  { style: s.block },
333
- React.createElement("div", { style: { ...s.muted, marginBottom: 8 } }, "\u670D\u52A1\u5668\u914D\u7F6E"),
377
+ React.createElement("div", { style: { ...s.muted, marginBottom: 8 } }, "\u96A7\u9053\u670D\u52A1\u5668\u914D\u7F6E"),
334
378
  React.createElement(
335
379
  "div",
336
380
  { style: { display: "flex", flexDirection: "column", gap: 8 } },
@@ -347,7 +391,7 @@ var CustomTunnelConfigForm = React.memo(function CustomTunnelConfigForm2({ serve
347
391
  React.createElement("input", {
348
392
  style: s.input,
349
393
  type: "password",
350
- placeholder: "\u8BBF\u95EE\u4EE4\u724C\uFF08Access Token\uFF09",
394
+ placeholder: "\u96A7\u9053\u670D\u52A1\u7AEF\u8FDE\u63A5\u4EE4\u724C\uFF08Tunnel Access Token\uFF09",
351
395
  value: accessToken,
352
396
  onChange: handleTokenChange,
353
397
  onKeyDown: (e) => {
@@ -355,6 +399,12 @@ var CustomTunnelConfigForm = React.memo(function CustomTunnelConfigForm2({ serve
355
399
  },
356
400
  disabled: saving
357
401
  }),
402
+ saveErr && React.createElement("div", { style: s.err }, `\u274C ${saveErr}`),
403
+ React.createElement(
404
+ "div",
405
+ { style: { ...s.muted, fontSize: 11 } },
406
+ "\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"
407
+ ),
358
408
  React.createElement("button", {
359
409
  style: {
360
410
  ...s.btnPri,
@@ -368,7 +418,7 @@ var CustomTunnelConfigForm = React.memo(function CustomTunnelConfigForm2({ serve
368
418
  )
369
419
  );
370
420
  });
371
- var TunnelCard = React.memo(function TunnelCard2({ title, desc, data, onStart, onStop, onReset, children }) {
421
+ var TunnelCard = React.memo(function TunnelCard2({ title, desc, data, onStart, onStop, onReset, auth, onNavigateSecurity, children }) {
372
422
  const { running, configured, url, qr, state } = data ?? {};
373
423
  const phase = state?.phase ?? "idle";
374
424
  return React.createElement(
@@ -393,7 +443,7 @@ var TunnelCard = React.memo(function TunnelCard2({ title, desc, data, onStart, o
393
443
  color: phase === "error" ? "var(--dsw-alias-state-error-primary,#dc2626)" : "var(--dsw-alias-label-secondary,#6b7280)"
394
444
  }
395
445
  }, state?.detail ?? phase),
396
- url && React.createElement(QrBlock, { url, qr, onReset }),
446
+ url && React.createElement(QrBlock, { url, qr, onReset, auth, onNavigateSecurity }),
397
447
  (onStart || onStop) && React.createElement(
398
448
  "div",
399
449
  {
@@ -409,6 +459,514 @@ var TunnelCard = React.memo(function TunnelCard2({ title, desc, data, onStart, o
409
459
  )
410
460
  );
411
461
  });
462
+ var AccessAuthCard = React.memo(function AccessAuthCard2({ auth, rpcCall, onUpdate }) {
463
+ const [enabled, setEnabled] = React.useState(auth?.enabled ?? false);
464
+ const [mode, setMode] = React.useState(auth?.mode ?? "token_and_password");
465
+ const [scope, setScope] = React.useState(auth?.scope ?? "all");
466
+ const [adminPolicy, setAdminPolicy] = React.useState(auth?.adminPolicy ?? "password_unlock");
467
+ const [accessPassword, setAccessPassword] = React.useState("");
468
+ const [showAccessPassword, setShowAccessPassword] = React.useState(false);
469
+ const [savingAccess, setSavingAccess] = React.useState(false);
470
+ const [saveAccessSuccess, setSaveAccessSuccess] = React.useState(false);
471
+ const [msgAccess, setMsgAccess] = React.useState(null);
472
+ const [adminPassword, setAdminPassword] = React.useState("");
473
+ const [showAdminPassword, setShowAdminPassword] = React.useState(false);
474
+ const [savingAdmin, setSavingAdmin] = React.useState(false);
475
+ const [saveAdminSuccess, setSaveAdminSuccess] = React.useState(false);
476
+ const [msgAdmin, setMsgAdmin] = React.useState(null);
477
+ const [topMsg, setTopMsg] = React.useState(null);
478
+ React.useEffect(() => {
479
+ if (auth) {
480
+ setEnabled(auth.enabled ?? false);
481
+ setMode(auth.mode ?? "token_and_password");
482
+ setScope(auth.scope ?? "all");
483
+ setAdminPolicy(auth.adminPolicy ?? "password_unlock");
484
+ }
485
+ }, [auth]);
486
+ const handleToggleEnabled = async () => {
487
+ const prev = enabled;
488
+ const next = !enabled;
489
+ setEnabled(next);
490
+ try {
491
+ const res = await rpcCall(BRIDGE_ENDPOINTS.authUpdateConfig, { enabled: next });
492
+ if (!res?.ok) throw new Error(res?.error?.message || "\u66F4\u65B0\u5931\u8D25");
493
+ 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" });
494
+ onUpdate?.();
495
+ } catch (e) {
496
+ setEnabled(prev);
497
+ setTopMsg({ ok: false, text: e.message || "\u66F4\u65B0\u5931\u8D25" });
498
+ }
499
+ };
500
+ const handleChangeMode = async (m) => {
501
+ const prev = mode;
502
+ setMode(m);
503
+ try {
504
+ const res = await rpcCall(BRIDGE_ENDPOINTS.authUpdateConfig, { mode: m });
505
+ if (!res?.ok) throw new Error(res?.error?.message || "\u66F4\u65B0\u5931\u8D25");
506
+ setTopMsg({ ok: true, text: "\u2713 \u5916\u90E8\u9A8C\u8BC1\u6A21\u5F0F\u5DF2\u5207\u6362\uFF0C\u5DF2\u5237\u65B0\u5168\u57DF\u767B\u5F55\u6001" });
507
+ onUpdate?.();
508
+ } catch (e) {
509
+ setMode(prev);
510
+ setTopMsg({ ok: false, text: e.message || "\u66F4\u65B0\u5931\u8D25" });
511
+ }
512
+ };
513
+ const handleChangeScope = async (sc) => {
514
+ const prev = scope;
515
+ setScope(sc);
516
+ try {
517
+ const res = await rpcCall(BRIDGE_ENDPOINTS.authUpdateConfig, { scope: sc });
518
+ if (!res?.ok) throw new Error(res?.error?.message || "\u66F4\u65B0\u5931\u8D25");
519
+ setTopMsg({ ok: true, text: "\u2713 \u9632\u62A4\u751F\u6548\u8303\u56F4\u5DF2\u66F4\u65B0" });
520
+ onUpdate?.();
521
+ } catch (e) {
522
+ setScope(prev);
523
+ setTopMsg({ ok: false, text: e.message || "\u66F4\u65B0\u5931\u8D25" });
524
+ }
525
+ };
526
+ const handleChangeAdminPolicy = async (pol) => {
527
+ const prev = adminPolicy;
528
+ setAdminPolicy(pol);
529
+ try {
530
+ const res = await rpcCall(BRIDGE_ENDPOINTS.authUpdateConfig, { adminPolicy: pol });
531
+ if (!res?.ok) throw new Error(res?.error?.message || "\u66F4\u65B0\u5931\u8D25");
532
+ setTopMsg({ ok: true, text: "\u2713 \u8FDC\u7A0B\u7BA1\u7406\u9632\u7BE1\u6539\u7B56\u7565\u5DF2\u66F4\u65B0" });
533
+ onUpdate?.();
534
+ } catch (e) {
535
+ setAdminPolicy(prev);
536
+ setTopMsg({ ok: false, text: e.message || "\u66F4\u65B0\u5931\u8D25" });
537
+ }
538
+ };
539
+ const handleSaveAccessPassword = async () => {
540
+ setSavingAccess(true);
541
+ setMsgAccess(null);
542
+ try {
543
+ const res = await rpcCall(BRIDGE_ENDPOINTS.authUpdateConfig, { password: accessPassword });
544
+ if (res?.ok) {
545
+ setSaveAccessSuccess(true);
546
+ 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" });
547
+ setAccessPassword("");
548
+ setTimeout(() => setSaveAccessSuccess(false), 3500);
549
+ onUpdate?.();
550
+ } else {
551
+ setMsgAccess({ ok: false, text: res?.error?.message || "\u4FDD\u5B58\u5931\u8D25" });
552
+ }
553
+ } catch (e) {
554
+ setMsgAccess({ ok: false, text: e.message || "\u4FDD\u5B58\u5931\u8D25" });
555
+ } finally {
556
+ setSavingAccess(false);
557
+ }
558
+ };
559
+ const handleSaveAdminPassword = async () => {
560
+ setSavingAdmin(true);
561
+ setMsgAdmin(null);
562
+ try {
563
+ const res = await rpcCall(BRIDGE_ENDPOINTS.authUpdateConfig, { adminPassword });
564
+ if (res?.ok) {
565
+ setSaveAdminSuccess(true);
566
+ 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" });
567
+ setAdminPassword("");
568
+ setTimeout(() => setSaveAdminSuccess(false), 3500);
569
+ onUpdate?.();
570
+ } else {
571
+ setMsgAdmin({ ok: false, text: res?.error?.message || "\u4FDD\u5B58\u5931\u8D25" });
572
+ }
573
+ } catch (e) {
574
+ setMsgAdmin({ ok: false, text: e.message || "\u4FDD\u5B58\u5931\u8D25" });
575
+ } finally {
576
+ setSavingAdmin(false);
577
+ }
578
+ };
579
+ const handleRegenerateToken = async () => {
580
+ 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;
581
+ try {
582
+ const res = await rpcCall(BRIDGE_ENDPOINTS.authRegenerateToken, {});
583
+ if (!res?.ok) throw new Error(res?.error?.message || "\u91CD\u7F6E\u5931\u8D25");
584
+ setTopMsg({ ok: true, text: "\u2713 \u5B89\u5168 Token \u5DF2\u91CD\u7F6E\uFF0C\u4E8C\u7EF4\u7801\u4E0E\u4E13\u5C5E\u94FE\u63A5\u5DF2\u5237\u65B0" });
585
+ onUpdate?.();
586
+ } catch (e) {
587
+ setTopMsg({ ok: false, text: e.message || "\u91CD\u7F6E\u5931\u8D25" });
588
+ }
589
+ };
590
+ 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";
591
+ const modeLabel = mode === "token_and_password" ? "\u626B\u7801\u514D\u5BC6 + \u5BC6\u7801" : mode === "password_only" ? "\u4EC5\u5BC6\u7801\u767B\u5F55" : "\u4EC5\u5B89\u5168 Token";
592
+ 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";
593
+ return React.createElement(
594
+ "div",
595
+ { style: { display: "flex", flexDirection: "column", gap: 14 } },
596
+ // ---- 顶部总控与状态概览卡片 ----
597
+ React.createElement(
598
+ "div",
599
+ { style: s.card },
600
+ React.createElement(
601
+ "div",
602
+ {
603
+ style: { display: "flex", alignItems: "center", justifyContent: "space-between", flexWrap: "wrap", gap: 12 }
604
+ },
605
+ React.createElement(
606
+ "div",
607
+ { style: { flex: "1 1 260px" } },
608
+ React.createElement(
609
+ "div",
610
+ { style: { ...s.label, fontSize: 15, display: "flex", alignItems: "center", gap: 8 } },
611
+ "\u{1F510} \u5168\u5C40\u8BBF\u95EE\u5B89\u5168\u9632\u62A4\u4F53\u7CFB"
612
+ ),
613
+ React.createElement(
614
+ "div",
615
+ { style: { ...s.muted, marginTop: 4 } },
616
+ "\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"
617
+ )
618
+ ),
619
+ React.createElement("button", {
620
+ style: { ...enabled ? s.btnPri : s.btnGhost, whiteSpace: "nowrap", flexShrink: 0 },
621
+ onClick: handleToggleEnabled
622
+ }, enabled ? "\u2713 \u5DF2\u542F\u7528\u5B89\u5168\u9632\u62A4" : "\u672A\u5F00\u542F\u5B89\u5168\u9632\u62A4")
623
+ ),
624
+ enabled && React.createElement(
625
+ "div",
626
+ {
627
+ style: {
628
+ display: "flex",
629
+ gap: 8,
630
+ flexWrap: "wrap",
631
+ marginTop: 14,
632
+ paddingTop: 12,
633
+ borderTop: "1px solid var(--dsw-alias-border-l2,#e5e7eb)"
634
+ }
635
+ },
636
+ React.createElement("div", {
637
+ style: {
638
+ display: "inline-flex",
639
+ alignItems: "center",
640
+ gap: 5,
641
+ padding: "4px 10px",
642
+ borderRadius: 16,
643
+ fontSize: 11,
644
+ background: "var(--dsw-alias-bg-layer-2,#f3f4f6)",
645
+ color: "var(--dsw-alias-label-secondary,#4b5563)"
646
+ }
647
+ }, "\u{1F310} \u4FDD\u62A4\u8303\u56F4: ", React.createElement("strong", { style: { color: "var(--dsw-alias-brand-primary,#4f6ef7)" } }, scopeLabel)),
648
+ React.createElement("div", {
649
+ style: {
650
+ display: "inline-flex",
651
+ alignItems: "center",
652
+ gap: 5,
653
+ padding: "4px 10px",
654
+ borderRadius: 16,
655
+ fontSize: 11,
656
+ background: "var(--dsw-alias-bg-layer-2,#f3f4f6)",
657
+ color: "var(--dsw-alias-label-secondary,#4b5563)"
658
+ }
659
+ }, "\u{1F511} \u5916\u90E8\u9A8C\u8BC1: ", React.createElement("strong", { style: { color: "var(--dsw-alias-brand-primary,#4f6ef7)" } }, modeLabel)),
660
+ React.createElement("div", {
661
+ style: {
662
+ display: "inline-flex",
663
+ alignItems: "center",
664
+ gap: 5,
665
+ padding: "4px 10px",
666
+ borderRadius: 16,
667
+ fontSize: 11,
668
+ background: "var(--dsw-alias-bg-layer-2,#f3f4f6)",
669
+ color: "var(--dsw-alias-label-secondary,#4b5563)"
670
+ }
671
+ }, "\u{1F512} \u540E\u53F0\u9632\u7BE1\u6539: ", React.createElement("strong", { style: { color: "var(--dsw-alias-state-success-primary,#059669)" } }, adminLabel))
672
+ ),
673
+ topMsg && React.createElement("div", {
674
+ style: {
675
+ marginTop: 12,
676
+ padding: "8px 12px",
677
+ borderRadius: 6,
678
+ fontSize: 12,
679
+ background: topMsg.ok ? "var(--dsw-alias-state-success-bg,#ecfdf5)" : "var(--dsw-alias-state-error-bg,#fef2f2)",
680
+ color: topMsg.ok ? "var(--dsw-alias-state-success-primary,#059669)" : "var(--dsw-alias-state-error-primary,#dc2626)"
681
+ }
682
+ }, topMsg.text)
683
+ ),
684
+ enabled && React.createElement(
685
+ React.Fragment,
686
+ null,
687
+ // =========================================================================
688
+ // ---- 第一道防线:外部访问门禁(控制谁能进入 Web 界面使用 AI) ----
689
+ // =========================================================================
690
+ React.createElement(
691
+ "div",
692
+ { style: s.card },
693
+ React.createElement(
694
+ "div",
695
+ { style: { marginBottom: 14 } },
696
+ React.createElement(
697
+ "div",
698
+ { style: { ...s.label, fontSize: 14, display: "flex", alignItems: "center", gap: 6 } },
699
+ "\u{1F6E1}\uFE0F \u7B2C\u4E00\u9053\u9632\u7EBF\uFF1A\u5916\u90E8\u8BBF\u95EE\u95E8\u7981\uFF08\u63A7\u5236\u8C01\u80FD\u4F7F\u7528 AI\uFF09"
700
+ ),
701
+ React.createElement(
702
+ "div",
703
+ { style: { ...s.muted, marginTop: 3 } },
704
+ "\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"
705
+ )
706
+ ),
707
+ // 验证模式选择
708
+ React.createElement(
709
+ "div",
710
+ { style: { marginBottom: 16 } },
711
+ React.createElement("label", { style: { ...s.label, display: "block", marginBottom: 8, fontSize: 12 } }, "\u9A8C\u8BC1\u6A21\u5F0F\u9009\u62E9"),
712
+ React.createElement(
713
+ "div",
714
+ { style: { display: "flex", gap: 8, flexWrap: "wrap" } },
715
+ [
716
+ { 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" },
717
+ { 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" },
718
+ { 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" }
719
+ ].map((opt) => {
720
+ const isSel = mode === opt.id;
721
+ return React.createElement(
722
+ "div",
723
+ {
724
+ key: opt.id,
725
+ onClick: () => handleChangeMode(opt.id),
726
+ style: {
727
+ flex: "1 1 200px",
728
+ padding: "10px 12px",
729
+ borderRadius: 8,
730
+ border: `1px solid ${isSel ? "var(--dsw-alias-brand-primary,#4f6ef7)" : "var(--dsw-alias-border-l2,#e5e7eb)"}`,
731
+ background: isSel ? "var(--dsw-alias-state-info-bg,#eff6ff)" : "var(--dsw-alias-bg-layer-2,#f9fafb)",
732
+ cursor: "pointer",
733
+ transition: "all 0.15s ease"
734
+ }
735
+ },
736
+ 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),
737
+ React.createElement("div", { style: { ...s.muted, fontSize: 11, marginTop: 4 } }, opt.desc)
738
+ );
739
+ })
740
+ )
741
+ ),
742
+ // 防护生效范围
743
+ React.createElement(
744
+ "div",
745
+ { style: { marginBottom: 16 } },
746
+ React.createElement("label", { style: { ...s.label, display: "block", marginBottom: 8, fontSize: 12 } }, "\u9632\u62A4\u751F\u6548\u901A\u9053"),
747
+ React.createElement(
748
+ "div",
749
+ { style: { display: "flex", gap: 8, flexWrap: "wrap" } },
750
+ [
751
+ { 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" },
752
+ { 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" },
753
+ { 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" }
754
+ ].map((opt) => {
755
+ const isSel = scope === opt.id;
756
+ return React.createElement(
757
+ "div",
758
+ {
759
+ key: opt.id,
760
+ onClick: () => handleChangeScope(opt.id),
761
+ style: {
762
+ flex: "1 1 180px",
763
+ padding: "9px 12px",
764
+ borderRadius: 8,
765
+ border: `1px solid ${isSel ? "var(--dsw-alias-brand-primary,#4f6ef7)" : "var(--dsw-alias-border-l2,#e5e7eb)"}`,
766
+ background: isSel ? "var(--dsw-alias-state-info-bg,#eff6ff)" : "var(--dsw-alias-bg-layer-2,#f9fafb)",
767
+ cursor: "pointer",
768
+ transition: "all 0.15s ease"
769
+ }
770
+ },
771
+ 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),
772
+ React.createElement("div", { style: { ...s.muted, fontSize: 11, marginTop: 3 } }, opt.desc)
773
+ );
774
+ })
775
+ )
776
+ ),
777
+ // 访客访问密码输入框 (当非 token_only 时展示)
778
+ mode !== "token_only" && React.createElement(
779
+ "div",
780
+ { style: { marginBottom: 16 } },
781
+ React.createElement(
782
+ "label",
783
+ { style: { ...s.label, display: "block", marginBottom: 6, fontSize: 12 } },
784
+ `\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)"}`
785
+ ),
786
+ React.createElement(
787
+ "div",
788
+ { style: { display: "flex", gap: 8, alignItems: "center" } },
789
+ React.createElement("input", {
790
+ type: showAccessPassword ? "text" : "password",
791
+ style: { ...s.input, flex: 1 },
792
+ 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",
793
+ value: accessPassword,
794
+ onChange: (e) => setAccessPassword(e.target.value)
795
+ }),
796
+ React.createElement("button", {
797
+ style: { ...s.btnGhost, height: 32, fontSize: 12, whiteSpace: "nowrap", flexShrink: 0 },
798
+ onClick: () => setShowAccessPassword((v) => !v)
799
+ }, showAccessPassword ? "\u9690\u85CF" : "\u663E\u793A"),
800
+ React.createElement("button", {
801
+ style: {
802
+ ...s.btnPri,
803
+ height: 32,
804
+ fontSize: 12,
805
+ whiteSpace: "nowrap",
806
+ flexShrink: 0,
807
+ background: saveAccessSuccess ? "#059669" : "var(--dsw-alias-brand-primary, #4f6ef7)",
808
+ color: "#ffffff",
809
+ cursor: savingAccess ? "wait" : "pointer"
810
+ },
811
+ onClick: handleSaveAccessPassword,
812
+ disabled: savingAccess
813
+ }, savingAccess ? "\u4FDD\u5B58\u4E2D\u2026" : saveAccessSuccess ? "\u2713 \u5DF2\u6210\u529F\u4FDD\u5B58\uFF01" : "\u4FDD\u5B58\u8BBF\u95EE\u5BC6\u7801")
814
+ ),
815
+ React.createElement(
816
+ "div",
817
+ { style: { ...s.muted, fontSize: 11, marginTop: 4 } },
818
+ "\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"
819
+ ),
820
+ msgAccess && React.createElement("div", {
821
+ style: {
822
+ marginTop: 8,
823
+ padding: "6px 12px",
824
+ borderRadius: 6,
825
+ fontSize: 12,
826
+ background: msgAccess.ok ? "var(--dsw-alias-state-success-bg,#ecfdf5)" : "var(--dsw-alias-state-error-bg,#fef2f2)",
827
+ color: msgAccess.ok ? "var(--dsw-alias-state-success-primary,#059669)" : "var(--dsw-alias-state-error-primary,#dc2626)"
828
+ }
829
+ }, msgAccess.text)
830
+ ),
831
+ // 免密 Token 管理
832
+ mode !== "password_only" && React.createElement(
833
+ "div",
834
+ { style: s.block },
835
+ React.createElement("label", { style: { ...s.label, display: "block", marginBottom: 6, fontSize: 12 } }, "\u514D\u5BC6\u626B\u7801 Token (\u4E13\u5C5E\u8BBF\u95EE\u51ED\u636E)"),
836
+ React.createElement(
837
+ "div",
838
+ { style: { display: "flex", gap: 8, alignItems: "center" } },
839
+ React.createElement(
840
+ "code",
841
+ { 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)" } },
842
+ auth?.secretToken ? `${auth.secretToken.slice(0, 10)}****************` : "\u672A\u751F\u6210"
843
+ ),
844
+ React.createElement("button", {
845
+ style: { ...s.btnGhost, height: 32, fontSize: 12, whiteSpace: "nowrap", flexShrink: 0 },
846
+ onClick: handleRegenerateToken,
847
+ title: "\u91CD\u65B0\u751F\u6210 Token\uFF0C\u4F7F\u4E4B\u524D\u5206\u4EAB\u7684\u65E7\u4E8C\u7EF4\u7801\u548C\u94FE\u63A5\u7ACB\u5373\u5931\u6548"
848
+ }, "\u{1F504} \u91CD\u7F6E\u5B89\u5168 Token")
849
+ ),
850
+ React.createElement(
851
+ "div",
852
+ { style: { ...s.muted, fontSize: 11, marginTop: 4 } },
853
+ "\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"
854
+ )
855
+ )
856
+ ),
857
+ // =========================================================================
858
+ // ---- 第二道防线:后台管理防篡改(控制谁能修改本插件所有设置) ----
859
+ // =========================================================================
860
+ React.createElement(
861
+ "div",
862
+ { style: s.card },
863
+ React.createElement(
864
+ "div",
865
+ { style: { marginBottom: 14 } },
866
+ React.createElement(
867
+ "div",
868
+ { style: { ...s.label, fontSize: 14, display: "flex", alignItems: "center", gap: 6 } },
869
+ "\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"
870
+ ),
871
+ React.createElement(
872
+ "div",
873
+ { style: { ...s.muted, marginTop: 3 } },
874
+ "\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"
875
+ )
876
+ ),
877
+ // 管理员密码设置
878
+ React.createElement(
879
+ "div",
880
+ { style: { marginBottom: 16 } },
881
+ React.createElement(
882
+ "label",
883
+ { style: { ...s.label, display: "block", marginBottom: 6, fontSize: 12 } },
884
+ `\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)"}`
885
+ ),
886
+ React.createElement(
887
+ "div",
888
+ { style: { display: "flex", gap: 8, alignItems: "center" } },
889
+ React.createElement("input", {
890
+ type: showAdminPassword ? "text" : "password",
891
+ style: { ...s.input, flex: 1 },
892
+ 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",
893
+ value: adminPassword,
894
+ onChange: (e) => setAdminPassword(e.target.value)
895
+ }),
896
+ React.createElement("button", {
897
+ style: { ...s.btnGhost, height: 32, fontSize: 12, whiteSpace: "nowrap", flexShrink: 0 },
898
+ onClick: () => setShowAdminPassword((v) => !v)
899
+ }, showAdminPassword ? "\u9690\u85CF" : "\u663E\u793A"),
900
+ React.createElement("button", {
901
+ style: {
902
+ ...s.btnPri,
903
+ height: 32,
904
+ fontSize: 12,
905
+ whiteSpace: "nowrap",
906
+ flexShrink: 0,
907
+ background: saveAdminSuccess ? "#059669" : "var(--dsw-alias-brand-primary, #4f6ef7)",
908
+ color: "#ffffff",
909
+ cursor: savingAdmin ? "wait" : "pointer"
910
+ },
911
+ onClick: handleSaveAdminPassword,
912
+ disabled: savingAdmin
913
+ }, savingAdmin ? "\u4FDD\u5B58\u4E2D\u2026" : saveAdminSuccess ? "\u2713 \u5DF2\u6210\u529F\u4FDD\u5B58\uFF01" : "\u4FDD\u5B58\u7BA1\u7406\u5BC6\u7801")
914
+ ),
915
+ React.createElement(
916
+ "div",
917
+ { style: { ...s.muted, fontSize: 11, marginTop: 4, color: "var(--dsw-alias-brand-primary,#4f6ef7)" } },
918
+ "\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"
919
+ ),
920
+ msgAdmin && React.createElement("div", {
921
+ style: {
922
+ marginTop: 8,
923
+ padding: "6px 12px",
924
+ borderRadius: 6,
925
+ fontSize: 12,
926
+ background: msgAdmin.ok ? "var(--dsw-alias-state-success-bg,#ecfdf5)" : "var(--dsw-alias-state-error-bg,#fef2f2)",
927
+ color: msgAdmin.ok ? "var(--dsw-alias-state-success-primary,#059669)" : "var(--dsw-alias-state-error-primary,#dc2626)"
928
+ }
929
+ }, msgAdmin.text)
930
+ ),
931
+ // 远程管理权限控制策略
932
+ React.createElement(
933
+ "div",
934
+ { style: s.block },
935
+ React.createElement("label", { style: { ...s.label, display: "block", marginBottom: 8, fontSize: 12 } }, "\u8FDC\u7A0B\u8BBE\u5907\u7BA1\u7406\u6743\u9650\u7B56\u7565"),
936
+ React.createElement(
937
+ "div",
938
+ { style: { display: "flex", gap: 8, flexWrap: "wrap" } },
939
+ [
940
+ { 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" },
941
+ { 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" },
942
+ { 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" }
943
+ ].map((opt) => {
944
+ const isSel = adminPolicy === opt.id;
945
+ return React.createElement(
946
+ "div",
947
+ {
948
+ key: opt.id,
949
+ onClick: () => handleChangeAdminPolicy(opt.id),
950
+ style: {
951
+ flex: "1 1 180px",
952
+ padding: "10px 12px",
953
+ borderRadius: 8,
954
+ border: `1px solid ${isSel ? "var(--dsw-alias-brand-primary,#4f6ef7)" : "var(--dsw-alias-border-l2,#e5e7eb)"}`,
955
+ background: isSel ? "var(--dsw-alias-state-info-bg,#eff6ff)" : "var(--dsw-alias-bg-layer-2,#f9fafb)",
956
+ cursor: "pointer",
957
+ transition: "all 0.15s ease"
958
+ }
959
+ },
960
+ 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),
961
+ React.createElement("div", { style: { ...s.muted, fontSize: 11, marginTop: 4 } }, opt.desc)
962
+ );
963
+ })
964
+ )
965
+ )
966
+ )
967
+ )
968
+ );
969
+ });
412
970
  function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatusChange }) {
413
971
  const [platform, setPlatform] = React.useState(null);
414
972
  const [err, setErr] = React.useState(null);
@@ -437,15 +995,23 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
437
995
  const connected2 = platform?.status === "connected" || platform?.status === "starting" || platform?.status === "reconnecting";
438
996
  onStatusChange?.(connected2);
439
997
  }, [platform?.status, onStatusChange]);
998
+ const loadInFlightRef = React.useRef(false);
999
+ const seqRef = React.useRef(0);
440
1000
  const load = React.useCallback(async (quiet = false) => {
1001
+ if (loadInFlightRef.current) return;
1002
+ loadInFlightRef.current = true;
1003
+ const currentSeq = ++seqRef.current;
441
1004
  try {
442
1005
  const r = await rpcCall(BRIDGE_ENDPOINTS.listPlatforms, {});
1006
+ if (currentSeq !== seqRef.current) return;
443
1007
  if (!r?.ok) throw new Error(r?.error?.message ?? "RPC failed");
444
1008
  const allPlatforms = r.value ?? {};
445
1009
  setPlatform(allPlatforms[platformId] ?? null);
446
1010
  if (!quiet) setErr(null);
447
1011
  } catch (e) {
448
- if (!quiet) setErr(e.message);
1012
+ if (currentSeq === seqRef.current && !quiet) setErr(e.message);
1013
+ } finally {
1014
+ loadInFlightRef.current = false;
449
1015
  }
450
1016
  }, [rpcCall, platformId]);
451
1017
  React.useEffect(() => {
@@ -476,9 +1042,20 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
476
1042
  const id = newId.trim();
477
1043
  if (!id) return;
478
1044
  const list = [...platform?.allowFrom ?? [], id];
479
- await act(BRIDGE_ENDPOINTS.platformSetAllowFrom, { allowFrom: list });
480
- setNewId("");
481
- }, [act, newId, platform?.allowFrom]);
1045
+ setBusy(true);
1046
+ try {
1047
+ const r = await rpcCall(BRIDGE_ENDPOINTS.platformSetAllowFrom, { platformId, allowFrom: list });
1048
+ if (!r?.ok) throw new Error(r?.error?.message ?? "\u6DFB\u52A0\u767D\u540D\u5355\u5931\u8D25");
1049
+ setPlatform(r.value);
1050
+ setNewId("");
1051
+ setErr(null);
1052
+ await load(true);
1053
+ } catch (e) {
1054
+ setErr(e.message);
1055
+ } finally {
1056
+ setBusy(false);
1057
+ }
1058
+ }, [rpcCall, platformId, newId, platform?.allowFrom, load]);
482
1059
  const removeAllow = React.useCallback(async (id) => {
483
1060
  const list = (platform?.allowFrom ?? []).filter((x) => x !== id);
484
1061
  await act(BRIDGE_ENDPOINTS.platformSetAllowFrom, { allowFrom: list });
@@ -524,7 +1101,7 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
524
1101
  React.createElement("div", { style: { ...s.muted, marginTop: 6 } }, "\u52A0\u8F7D\u4E2D\u2026")
525
1102
  );
526
1103
  }
527
- const connected = platform?.status === "connected" || platform?.status === "starting";
1104
+ const connected = platform?.status === "connected" || platform?.status === "starting" || platform?.status === "reconnecting";
528
1105
  const login = platform?.login ?? {};
529
1106
  const showQr = login.phase === "qr" || login.phase === "scaned";
530
1107
  const statusLabel = platform?.status === "connected" ? "\u5DF2\u8FDE\u63A5" : platform?.status === "starting" ? "\u8FDE\u63A5\u4E2D\u2026" : platform?.status === "reconnecting" ? "\u91CD\u8FDE\u4E2D\u2026" : platform?.status === "paused" ? "\u6682\u505C\uFF08\u4F1A\u8BDD\u8FC7\u671F\uFF09" : platform?.status === "error" ? "\u9519\u8BEF" : "\u672A\u8FDE\u63A5";
@@ -1178,7 +1755,8 @@ function VersionBanner({ rpcCall }) {
1178
1755
  var TABS = [
1179
1756
  { id: "lan", label: "\u5C40\u57DF\u7F51", icon: Icons.lan },
1180
1757
  { id: "tunnel", label: "\u516C\u7F51\u96A7\u9053", icon: Icons.tunnel },
1181
- { id: "im", label: "IM \u673A\u5668\u4EBA", icon: Icons.bot }
1758
+ { id: "im", label: "IM \u673A\u5668\u4EBA", icon: Icons.bot },
1759
+ { id: "security", label: "\u5B89\u5168\u8BA4\u8BC1", icon: Icons.security }
1182
1760
  ];
1183
1761
  function TabBar({ active, onChange, dots }) {
1184
1762
  return React.createElement(
@@ -1245,25 +1823,84 @@ function BridgePanel({ rpcCall }) {
1245
1823
  const [activeTab, setActiveTab] = React.useState("lan");
1246
1824
  const [platforms, setPlatforms] = React.useState(null);
1247
1825
  const [selectedPlatform, setSelectedPlatform] = React.useState("wechat");
1826
+ 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"));
1827
+ const [adminToken, setAdminToken] = React.useState("");
1828
+ const [adminUnlocked, setAdminUnlocked] = React.useState(false);
1829
+ const [unlockPassword, setUnlockPassword] = React.useState("");
1830
+ const [unlockErr, setUnlockErr] = React.useState(null);
1831
+ const [unlocking, setUnlocking] = React.useState(false);
1832
+ const [showForgotGuide, setShowForgotGuide] = React.useState(false);
1833
+ const authRpcCall = React.useCallback((endpoint, payload = {}, signal) => {
1834
+ const enriched = {
1835
+ ...payload,
1836
+ ...adminToken ? { adminToken } : {},
1837
+ ...isLocalhost ? { isLocalhost: true } : {}
1838
+ };
1839
+ return rpcCall(endpoint, enriched, signal);
1840
+ }, [rpcCall, adminToken, isLocalhost]);
1841
+ const handleUnlockAdmin = React.useCallback(async (e) => {
1842
+ e?.preventDefault?.();
1843
+ setUnlocking(true);
1844
+ setUnlockErr(null);
1845
+ try {
1846
+ const res = await rpcCall(BRIDGE_ENDPOINTS.authAdminUnlock, { password: unlockPassword });
1847
+ if (res?.ok) {
1848
+ setAdminToken(res.value?.adminToken || "");
1849
+ setAdminUnlocked(true);
1850
+ setUnlockPassword("");
1851
+ } else {
1852
+ setUnlockErr(res?.error?.message || "\u7BA1\u7406\u5458\u5BC6\u7801\u9519\u8BEF");
1853
+ }
1854
+ } catch (err2) {
1855
+ setUnlockErr(err2.message || "\u89E3\u9501\u8BF7\u6C42\u5931\u8D25");
1856
+ } finally {
1857
+ setUnlocking(false);
1858
+ }
1859
+ }, [rpcCall, unlockPassword]);
1860
+ const handleLockAdmin = React.useCallback(async () => {
1861
+ try {
1862
+ if (adminToken) {
1863
+ await rpcCall(BRIDGE_ENDPOINTS.authAdminLock, { adminToken });
1864
+ }
1865
+ } catch {
1866
+ }
1867
+ setAdminToken("");
1868
+ setAdminUnlocked(false);
1869
+ }, [rpcCall, adminToken]);
1870
+ const loadInFlightRef = React.useRef(false);
1871
+ const loadSeqRef = React.useRef(0);
1248
1872
  const load = React.useCallback(async (quiet = false) => {
1873
+ if (loadInFlightRef.current) return;
1874
+ loadInFlightRef.current = true;
1875
+ const currentSeq = ++loadSeqRef.current;
1249
1876
  try {
1250
- const r = await rpcCall(BRIDGE_ENDPOINTS.getStatus, {});
1877
+ const r = await authRpcCall(BRIDGE_ENDPOINTS.getStatus, {});
1878
+ if (currentSeq !== loadSeqRef.current) return;
1251
1879
  if (!r?.ok) throw new Error(r?.error?.message ?? "RPC failed");
1252
1880
  setStatus(r.value);
1253
1881
  if (!quiet) setErr(null);
1254
1882
  } catch (e) {
1255
- setErr(e.message);
1883
+ if (currentSeq === loadSeqRef.current) setErr(e.message);
1884
+ } finally {
1885
+ loadInFlightRef.current = false;
1256
1886
  }
1257
- }, [rpcCall]);
1887
+ }, [authRpcCall]);
1888
+ const pollPlatformsSeqRef = React.useRef(0);
1258
1889
  React.useEffect(() => {
1259
1890
  let alive = true;
1891
+ let inFlight = false;
1260
1892
  const poll = async () => {
1893
+ if (inFlight || !alive) return;
1894
+ inFlight = true;
1895
+ const currentSeq = ++pollPlatformsSeqRef.current;
1261
1896
  try {
1262
- const r = await rpcCall(BRIDGE_ENDPOINTS.listPlatforms, {});
1263
- if (alive && r?.ok) {
1897
+ const r = await authRpcCall(BRIDGE_ENDPOINTS.listPlatforms, {});
1898
+ if (alive && currentSeq === pollPlatformsSeqRef.current && r?.ok) {
1264
1899
  setPlatforms(r.value ?? {});
1265
1900
  }
1266
1901
  } catch {
1902
+ } finally {
1903
+ inFlight = false;
1267
1904
  }
1268
1905
  };
1269
1906
  poll();
@@ -1272,7 +1909,7 @@ function BridgePanel({ rpcCall }) {
1272
1909
  alive = false;
1273
1910
  clearInterval(t);
1274
1911
  };
1275
- }, [rpcCall]);
1912
+ }, [authRpcCall]);
1276
1913
  React.useEffect(() => {
1277
1914
  load();
1278
1915
  const t = setInterval(() => load(true), 3e3);
@@ -1280,14 +1917,14 @@ function BridgePanel({ rpcCall }) {
1280
1917
  }, [load]);
1281
1918
  const act = React.useCallback(async (endpoint, payload) => {
1282
1919
  try {
1283
- const r = await rpcCall(endpoint, payload ?? {});
1920
+ const r = await authRpcCall(endpoint, payload ?? {});
1284
1921
  if (!r?.ok) throw new Error(r?.error?.message ?? "RPC failed");
1285
1922
  setStatus(r.value);
1286
1923
  setErr(null);
1287
1924
  } catch (e) {
1288
1925
  setErr(e.message);
1289
1926
  }
1290
- }, [rpcCall]);
1927
+ }, [authRpcCall]);
1291
1928
  const onStartCloudflared = React.useCallback(() => act(BRIDGE_ENDPOINTS.startCloudflared), [act]);
1292
1929
  const onStopCloudflared = React.useCallback(() => act(BRIDGE_ENDPOINTS.stopCloudflared), [act]);
1293
1930
  const onResetCloudflared = React.useCallback(
@@ -1300,6 +1937,7 @@ function BridgePanel({ rpcCall }) {
1300
1937
  (serverUrl, accessToken) => act(BRIDGE_ENDPOINTS.saveCustomTunnelConfig, { serverUrl, accessToken }),
1301
1938
  [act]
1302
1939
  );
1940
+ const navSecurity = React.useCallback(() => setActiveTab("security"), []);
1303
1941
  if (!status && !err) {
1304
1942
  return React.createElement("div", {
1305
1943
  style: { padding: 32, color: "var(--dsw-alias-label-tertiary,#9ca3af)", fontSize: 13 }
@@ -1312,14 +1950,17 @@ function BridgePanel({ rpcCall }) {
1312
1950
  const dots = {
1313
1951
  lan: !!status?.proxy?.running,
1314
1952
  tunnel: !!(status?.cloudflared?.running || ct?.running),
1315
- im: !!imConnected
1953
+ im: !!imConnected,
1954
+ security: !!status?.auth?.enabled
1316
1955
  };
1317
1956
  let tabContent;
1318
1957
  if (activeTab === "lan") {
1319
1958
  tabContent = React.createElement(TunnelCard, {
1320
1959
  title: "\u5C40\u57DF\u7F51\u8BBF\u95EE",
1321
1960
  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 }
1961
+ data: { running: status?.proxy?.running, url: status?.lan?.url, qr: status?.lan?.qr },
1962
+ auth: status?.auth,
1963
+ onNavigateSecurity: navSecurity
1323
1964
  });
1324
1965
  } else if (activeTab === "tunnel") {
1325
1966
  tabContent = React.createElement(
@@ -1334,6 +1975,8 @@ function BridgePanel({ rpcCall }) {
1334
1975
  qr: status?.cloudflared?.qr,
1335
1976
  state: status?.cloudflared?.state
1336
1977
  },
1978
+ auth: status?.auth,
1979
+ onNavigateSecurity: navSecurity,
1337
1980
  onStart: onStartCloudflared,
1338
1981
  onStop: onStopCloudflared,
1339
1982
  onReset: status?.cloudflared?.running ? onResetCloudflared : null
@@ -1350,6 +1993,8 @@ function BridgePanel({ rpcCall }) {
1350
1993
  qr: ct?.qr,
1351
1994
  state: ct?.state
1352
1995
  },
1996
+ auth: status?.auth,
1997
+ onNavigateSecurity: navSecurity,
1353
1998
  onStart: onStartCustom,
1354
1999
  onStop: onStopCustom
1355
2000
  },
@@ -1361,6 +2006,12 @@ function BridgePanel({ rpcCall }) {
1361
2006
  })
1362
2007
  )
1363
2008
  );
2009
+ } else if (activeTab === "security") {
2010
+ tabContent = React.createElement(AccessAuthCard, {
2011
+ auth: status?.auth,
2012
+ rpcCall: authRpcCall,
2013
+ onUpdate: () => load(true)
2014
+ });
1364
2015
  } else if (activeTab === "im") {
1365
2016
  const IM_PLATFORMS = [
1366
2017
  { id: "wechat", label: "\u5FAE\u4FE1", icon: Icons.wechat, brandColor: "#07C160", desc: "ClawBot \u626B\u7801\u76F4\u8FDE \xB7 \u65E0\u9700\u516C\u7F51" },
@@ -1421,25 +2072,167 @@ function BridgePanel({ rpcCall }) {
1421
2072
  );
1422
2073
  })
1423
2074
  ),
1424
- // 显示选中的平台卡片
2075
+ // 显示选中的平台卡片(带有 key 保证切换时重置表单状态)
1425
2076
  selectedPlatform && platforms?.[selectedPlatform] && React.createElement(PlatformCard, {
2077
+ key: selectedPlatform,
1426
2078
  platformId: selectedPlatform,
1427
2079
  platformName: IM_PLATFORMS.find((p) => p.id === selectedPlatform)?.label ?? selectedPlatform,
1428
2080
  platformDesc: IM_PLATFORMS.find((p) => p.id === selectedPlatform)?.desc ?? "",
1429
- rpcCall,
2081
+ rpcCall: authRpcCall,
1430
2082
  onStatusChange: () => {
1431
2083
  }
1432
2084
  // 状态变化已由 listPlatforms 轮询处理,不需要回调
1433
2085
  })
1434
2086
  );
1435
2087
  }
2088
+ const auth = status?.auth;
2089
+ const policy = auth?.adminPolicy ?? "password_unlock";
2090
+ const isLocked = !isLocalhost && auth?.enabled && policy !== "open" && !adminUnlocked;
2091
+ if (isLocked) {
2092
+ return React.createElement(
2093
+ "div",
2094
+ { style: { maxWidth: 620 } },
2095
+ policy === "local_only" ? React.createElement(
2096
+ "div",
2097
+ {
2098
+ style: { ...s.card, textAlign: "center", padding: "36px 20px", marginTop: 10 }
2099
+ },
2100
+ React.createElement("div", { style: { fontSize: 40, marginBottom: 12 } }, "\u{1F6E1}\uFE0F"),
2101
+ 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"),
2102
+ React.createElement(
2103
+ "div",
2104
+ { style: { ...s.muted, maxWidth: 420, margin: "0 auto", lineHeight: 1.6, fontSize: 13 } },
2105
+ "\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"
2106
+ ),
2107
+ React.createElement(
2108
+ "div",
2109
+ { style: { marginTop: 20 } },
2110
+ React.createElement("button", {
2111
+ type: "button",
2112
+ style: { ...s.btnLink, fontSize: 12, color: "var(--dsw-alias-label-secondary,#6b7280)" },
2113
+ onClick: () => setShowForgotGuide((v) => !v)
2114
+ }, "\u2753 \u8FDC\u7A0B\u5982\u4F55\u6551\u6025\u89E3\u9664\u9501\u5B9A\uFF1F")
2115
+ ),
2116
+ showForgotGuide && React.createElement(
2117
+ "div",
2118
+ {
2119
+ style: {
2120
+ marginTop: 14,
2121
+ padding: "12px 14px",
2122
+ borderRadius: 8,
2123
+ fontSize: 12,
2124
+ lineHeight: 1.6,
2125
+ background: "var(--dsw-alias-bg-layer-2,#f3f4f6)",
2126
+ border: "1px solid var(--dsw-alias-border-l2,#e5e7eb)",
2127
+ color: "var(--dsw-alias-label-secondary,#4b5563)",
2128
+ textAlign: "left",
2129
+ maxWidth: 420,
2130
+ margin: "14px auto 0"
2131
+ }
2132
+ },
2133
+ 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"),
2134
+ 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"),
2135
+ 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")
2136
+ )
2137
+ ) : React.createElement(
2138
+ "div",
2139
+ {
2140
+ style: { ...s.card, maxWidth: 440, margin: "20px auto", padding: "32px 24px" }
2141
+ },
2142
+ React.createElement(
2143
+ "div",
2144
+ { style: { textAlign: "center", marginBottom: 20 } },
2145
+ React.createElement("div", { style: { fontSize: 40, marginBottom: 10 } }, "\u{1F512}"),
2146
+ React.createElement("div", { style: { ...s.label, fontSize: 16, fontWeight: 600 } }, "\u7BA1\u7406\u63A7\u5236\u53F0\u5DF2\u9501\u5B9A"),
2147
+ React.createElement(
2148
+ "div",
2149
+ { style: { ...s.muted, fontSize: 12, marginTop: 6, lineHeight: 1.5 } },
2150
+ "\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"
2151
+ )
2152
+ ),
2153
+ React.createElement(
2154
+ "form",
2155
+ {
2156
+ onSubmit: handleUnlockAdmin,
2157
+ style: { display: "flex", flexDirection: "column", gap: 12 }
2158
+ },
2159
+ React.createElement("input", {
2160
+ type: "password",
2161
+ style: s.input,
2162
+ placeholder: "\u8F93\u5165\u540E\u53F0\u7BA1\u7406\u5BC6\u7801",
2163
+ value: unlockPassword,
2164
+ onChange: (e) => setUnlockPassword(e.target.value),
2165
+ autoFocus: true
2166
+ }),
2167
+ unlockErr && React.createElement("div", {
2168
+ style: { fontSize: 12, color: "var(--dsw-alias-state-error-primary,#dc2626)" }
2169
+ }, unlockErr),
2170
+ React.createElement("button", {
2171
+ type: "submit",
2172
+ style: { ...s.btnPri, width: "100%", justifyContent: "center", height: 36, background: "#4f6ef7", color: "#ffffff" },
2173
+ disabled: unlocking
2174
+ }, unlocking ? "\u9A8C\u8BC1\u4E2D\u2026" : "\u89E3\u9501\u7BA1\u7406\u6743\u9650")
2175
+ ),
2176
+ React.createElement(
2177
+ "div",
2178
+ { style: { marginTop: 16, textAlign: "center" } },
2179
+ React.createElement("button", {
2180
+ type: "button",
2181
+ style: { ...s.btnLink, fontSize: 12, color: "var(--dsw-alias-label-secondary,#6b7280)" },
2182
+ onClick: () => setShowForgotGuide((v) => !v)
2183
+ }, "\u2753 \u5FD8\u8BB0\u540E\u53F0\u7BA1\u7406\u5BC6\u7801\uFF1F")
2184
+ ),
2185
+ showForgotGuide && React.createElement(
2186
+ "div",
2187
+ {
2188
+ style: {
2189
+ marginTop: 12,
2190
+ padding: "12px 14px",
2191
+ borderRadius: 8,
2192
+ fontSize: 12,
2193
+ lineHeight: 1.6,
2194
+ background: "var(--dsw-alias-bg-layer-2,#f3f4f6)",
2195
+ border: "1px solid var(--dsw-alias-border-l2,#e5e7eb)",
2196
+ color: "var(--dsw-alias-label-secondary,#4b5563)",
2197
+ textAlign: "left"
2198
+ }
2199
+ },
2200
+ 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"),
2201
+ 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"),
2202
+ 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")
2203
+ )
2204
+ )
2205
+ );
2206
+ }
1436
2207
  return React.createElement(
1437
2208
  "div",
1438
2209
  { style: { maxWidth: 620 } },
1439
2210
  err && React.createElement("div", {
1440
2211
  style: { ...s.card, background: "var(--dsw-alias-state-error-bg,#fef2f2)", color: "var(--dsw-alias-state-error-primary,#dc2626)", fontSize: 13, marginBottom: 16 }
1441
2212
  }, err),
1442
- React.createElement(VersionBanner, { rpcCall }),
2213
+ !isLocalhost && adminUnlocked && React.createElement(
2214
+ "div",
2215
+ {
2216
+ style: {
2217
+ display: "flex",
2218
+ alignItems: "center",
2219
+ justifyContent: "space-between",
2220
+ padding: "8px 14px",
2221
+ background: "var(--dsw-alias-state-info-bg,#eff6ff)",
2222
+ border: "1px solid var(--dsw-alias-brand-primary,#4f6ef7)",
2223
+ borderRadius: 8,
2224
+ marginBottom: 14,
2225
+ fontSize: 12,
2226
+ color: "var(--dsw-alias-brand-primary,#4f6ef7)"
2227
+ }
2228
+ },
2229
+ React.createElement("span", null, "\u{1F513} \u7BA1\u7406\u5458\u6743\u9650\u5DF2\u89E3\u9501\uFF08\u5F53\u524D\u4E34\u65F6\u4F1A\u8BDD\u6709\u6548\uFF09"),
2230
+ React.createElement("button", {
2231
+ style: { ...s.btnGhost, height: 24, fontSize: 11, padding: "0 8px" },
2232
+ onClick: handleLockAdmin
2233
+ }, "\u{1F512} \u91CD\u65B0\u9501\u5B9A\u540E\u53F0")
2234
+ ),
2235
+ React.createElement(VersionBanner, { rpcCall: authRpcCall }),
1443
2236
  React.createElement(TabBar, { active: activeTab, onChange: setActiveTab, dots }),
1444
2237
  tabContent
1445
2238
  );