@wenbin_wb/dsh-bridge 2.10.5 → 2.10.7

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/index.js CHANGED
@@ -84,6 +84,7 @@ const GITHUB_URL = 'https://github.com/wenbin-wb/dsh-bridge';
84
84
  const RELEASES_URL = 'https://github.com/wenbin-wb/dsh-bridge/releases';
85
85
  const ISSUES_URL = 'https://github.com/wenbin-wb/dsh-bridge/issues/new';
86
86
  const TUNNEL_DOCS_URL = 'https://github.com/wenbin-wb/dsh-bridge/blob/main/docs/custom-tunnel.md';
87
+ const CLOUDFLARE_TUTORIAL_URL = 'https://github.com/wenbin-wb/dsh-bridge/blob/main/docs/cloudflare-fixed-domain.md';
87
88
 
88
89
  // 生成升级命令(拼接具体版本号;用 add 而非 update,update --latest 受已安装依赖版本约束可能无法升级到最新版)
89
90
  function upgradeCommands(latest) {
@@ -213,14 +214,14 @@ function StatusTag({ running, status }) {
213
214
  bg = 'var(--dsw-alias-state-success-bg,#ecfdf5)';
214
215
  color = 'var(--dsw-alias-state-success-primary,#059669)';
215
216
  text = '已连接';
216
- } else if (status === 'starting') {
217
+ } else if (status === 'starting' || status === 'connecting' || status === 'downloading') {
217
218
  bg = 'var(--dsw-alias-state-info-bg,#eff6ff)';
218
219
  color = 'var(--dsw-alias-state-info-primary,#3b82f6)';
219
- text = '连接中…';
220
+ text = status === 'downloading' ? '下载中…' : '连接中…';
220
221
  } else if (status === 'reconnecting') {
221
222
  bg = 'var(--dsw-alias-state-warn-bg,#fffbeb)';
222
223
  color = 'var(--dsw-alias-state-warn-primary,#d97706)';
223
- text = '重连中…';
224
+ text = '自动重连中…';
224
225
  } else if (status === 'paused') {
225
226
  bg = 'var(--dsw-alias-state-warn-bg,#fffbeb)';
226
227
  color = 'var(--dsw-alias-state-warn-primary,#d97706)';
@@ -416,6 +417,146 @@ const CustomTunnelGuide = React.memo(function CustomTunnelGuide() {
416
417
  );
417
418
  });
418
419
 
420
+ // ── 公网入口总览卡:把"当前生效的访问地址"提到页面主角位置 ──────────────
421
+ const TunnelEntryCard = React.memo(function TunnelEntryCard({
422
+ entry, onCopy, copied, autoStart, onToggleAutoStart, onStart, onStop, onReset,
423
+ }) {
424
+ const [showQr, setShowQr] = React.useState(false);
425
+ const hasUrl = Boolean(entry && entry.url);
426
+ const active = Boolean(entry && entry.running);
427
+
428
+ return React.createElement('div', {
429
+ style: {
430
+ ...s.card,
431
+ borderColor: active ? 'var(--dsw-alias-state-success-border,#a7f3d0)' : undefined,
432
+ background: active ? 'linear-gradient(180deg, var(--dsw-alias-bg-layer-2,#f9fafb), var(--dsw-alias-bg-layer-1,#ffffff))' : undefined,
433
+ },
434
+ },
435
+ React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 10 } },
436
+ React.createElement('div', { style: { flex: '1 1 auto', minWidth: 0 } },
437
+ React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } },
438
+ React.createElement('span', { style: { fontSize: 16 } }, '🌐'),
439
+ React.createElement('div', { style: s.label }, '公网访问入口'),
440
+ ),
441
+ React.createElement('div', { style: { ...s.muted, marginTop: 4 } },
442
+ entry ? (entry.title + ' · ' + entry.desc) : '尚未配置任何公网隧道,可在下方开启 Cloudflare 隧道或配置自建隧道'
443
+ ),
444
+ ),
445
+ React.createElement(StatusTag, {
446
+ running: active,
447
+ status: (entry && entry.phase && entry.phase !== 'ready') ? entry.phase : undefined,
448
+ }),
449
+ ),
450
+
451
+ React.createElement('div', { style: { ...s.block, display: 'flex', flexDirection: 'column', gap: 10 } },
452
+ // 有地址:大号 URL + 复制
453
+ hasUrl && React.createElement('div', {
454
+ style: {
455
+ padding: '10px 12px',
456
+ background: 'var(--dsw-alias-bg-layer-1,#ffffff)',
457
+ border: '1px solid var(--dsw-alias-border-l2,#e5e7eb)',
458
+ borderRadius: 10,
459
+ display: 'flex', alignItems: 'center', gap: 10,
460
+ },
461
+ },
462
+ React.createElement('code', {
463
+ style: { ...s.code, flex: '1 1 auto', fontSize: 13.5, wordBreak: 'break-all', lineHeight: 1.5 },
464
+ }, entry.url),
465
+ React.createElement('button', {
466
+ style: { ...s.btnGhost, flexShrink: 0, height: 28, padding: '0 12px', fontSize: 12 },
467
+ onClick: () => onCopy && onCopy(entry.url),
468
+ }, copied ? '✓ 已复制' : '复制'),
469
+ ),
470
+ // 状态细节(重连/错误/连接中)——无 URL 时格外重要,让用户知道隧道在自愈而非消失
471
+ entry && entry.stateDetail && React.createElement('div', {
472
+ style: {
473
+ fontSize: 12, lineHeight: 1.5,
474
+ color: entry.phase === 'error' ? 'var(--dsw-alias-state-error-primary,#dc2626)'
475
+ : entry.phase === 'reconnecting' ? 'var(--dsw-alias-state-warn-primary,#d97706)'
476
+ : 'var(--dsw-alias-label-secondary,#6b7280)',
477
+ },
478
+ }, entry.stateDetail),
479
+ // 操作行:running → 关闭/停止重连(+ 重置);!running → 开启
480
+ active && onStop && React.createElement('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap' } },
481
+ React.createElement('button', {
482
+ style: s.btnGhost,
483
+ onClick: onStop,
484
+ }, entry && entry.phase === 'reconnecting' ? '停止重连' : '关闭'),
485
+ onReset && React.createElement('button', {
486
+ style: { ...s.btnGhost, height: 28, padding: '0 12px', fontSize: 12 },
487
+ onClick: onReset,
488
+ title: '关闭并重新开启,更换临时地址',
489
+ }, '🔄 重置链接'),
490
+ ),
491
+ // running 但无 onStop 的入口(如外部登记):只给重置/二维码辅助,无开关
492
+ active && !onStop && onReset && React.createElement('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap' } },
493
+ React.createElement('button', {
494
+ style: { ...s.btnGhost, height: 28, padding: '0 12px', fontSize: 12 },
495
+ onClick: onReset,
496
+ title: '关闭并重新开启,更换临时地址',
497
+ }, '🔄 重置链接'),
498
+ ),
499
+ // 未运行:引导开启(连接中/下载中禁用)
500
+ !active && onStart && React.createElement('button', {
501
+ style: {
502
+ ...s.btnPri, alignSelf: 'flex-start',
503
+ opacity: (entry && entry.configured === false) ? 0.4 : 1,
504
+ background: (entry && entry.phase === 'connecting') ? 'var(--dsw-alias-state-info-primary,#3b82f6)' : undefined,
505
+ },
506
+ onClick: onStart,
507
+ disabled: Boolean((entry && entry.configured === false) || (entry && (entry.phase === 'connecting' || entry.phase === 'downloading'))),
508
+ title: (entry && entry.configured === false) ? '请先在「隧道配置」中保存服务器配置' : '',
509
+ }, (entry && entry.phase === 'connecting') ? '连接中…' : (entry && entry.phase === 'downloading') ? '下载中…' : '开启公网隧道'),
510
+ // 二维码辅助按钮:有地址未运行时也可查看(外部登记等)
511
+ hasUrl && entry.qr && React.createElement('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap' } },
512
+ React.createElement('button', {
513
+ style: { ...s.btnGhost, height: 28, padding: '0 12px', fontSize: 12 },
514
+ onClick: () => setShowQr((v) => !v),
515
+ }, showQr ? '隐藏二维码' : '显示二维码'),
516
+ ),
517
+ showQr && hasUrl && entry.qr && React.createElement('div', { style: { display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 6 } },
518
+ React.createElement('img', { src: entry.qr, alt: 'QR', style: { ...s.qr, margin: 0 } }),
519
+ React.createElement('div', { style: { ...s.muted, fontSize: 11 } }, '请在私密环境下扫码使用'),
520
+ ),
521
+ ),
522
+
523
+ onToggleAutoStart && React.createElement('label', {
524
+ style: {
525
+ display: 'flex', alignItems: 'center', gap: 6, marginTop: 12, paddingTop: 10,
526
+ borderTop: '1px solid var(--dsw-alias-border-l2,#e5e7eb)',
527
+ fontSize: 12, color: 'var(--dsw-alias-label-secondary,#6b7280)', cursor: 'pointer', userSelect: 'none',
528
+ },
529
+ title: 'DSH 启动时自动恢复该隧道的运行状态',
530
+ },
531
+ React.createElement('input', {
532
+ type: 'checkbox',
533
+ checked: Boolean(autoStart),
534
+ onChange: (e) => onToggleAutoStart(e.target.checked),
535
+ }),
536
+ React.createElement('span', null, '随 DSH 启动自动开启'),
537
+ ),
538
+ );
539
+ });
540
+
541
+ // 隧道配置折叠分组:把 CF / 自建 / 外部 三套"不常用"配置收进一处
542
+ const TunnelConfigGroup = React.memo(function TunnelConfigGroup({ children }) {
543
+ const [open, setOpen] = React.useState(false);
544
+ return React.createElement('div', { style: s.card },
545
+ React.createElement('div', {
546
+ style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', cursor: 'pointer', userSelect: 'none', gap: 8 },
547
+ onClick: () => setOpen((v) => !v),
548
+ },
549
+ React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } },
550
+ React.createElement('span', { style: { fontSize: 14 } }, '⚙️'),
551
+ React.createElement('div', { style: s.label }, '隧道配置'),
552
+ React.createElement('span', { style: { ...s.muted, fontSize: 11 } }, 'Token · 固定域名 · 自建服务器 · 外部登记'),
553
+ ),
554
+ React.createElement('span', { style: { fontSize: 12, color: 'var(--dsw-alias-label-tertiary,#9ca3af)', flexShrink: 0 } }, open ? '收起 ▴' : '展开 ▾'),
555
+ ),
556
+ open && React.createElement('div', { style: { marginTop: 4 } }, children),
557
+ );
558
+ });
559
+
419
560
  const CustomTunnelConfigForm = React.memo(function CustomTunnelConfigForm({ serverUrl: initUrl, accessToken: initToken, onSave }) {
420
561
  const [serverUrl, setServerUrl] = React.useState(initUrl ?? '');
421
562
  const [accessToken, setAccessToken] = React.useState(initToken ?? '');
@@ -511,7 +652,7 @@ const TunnelCard = React.memo(function TunnelCard({
511
652
  React.createElement('div', { style: { ...s.muted, marginTop: 2 } }, desc),
512
653
  ),
513
654
  React.createElement('div', { style: { display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 6 } },
514
- React.createElement(StatusTag, { running }),
655
+ React.createElement(StatusTag, { running, status: phase === 'ready' ? undefined : phase }),
515
656
  onToggleAutoStart && React.createElement('label', {
516
657
  style: {
517
658
  display: 'flex',
@@ -537,7 +678,9 @@ const TunnelCard = React.memo(function TunnelCard({
537
678
  phase !== 'idle' && phase !== 'ready' && React.createElement('div', {
538
679
  style: {
539
680
  ...s.block, fontSize: 12,
540
- color: phase === 'error' ? 'var(--dsw-alias-state-error-primary,#dc2626)' : 'var(--dsw-alias-label-secondary,#6b7280)',
681
+ color: phase === 'error' ? 'var(--dsw-alias-state-error-primary,#dc2626)'
682
+ : phase === 'reconnecting' ? 'var(--dsw-alias-state-warn-primary,#d97706)'
683
+ : 'var(--dsw-alias-label-secondary,#6b7280)',
541
684
  },
542
685
  }, state?.detail ?? phase),
543
686
  url && React.createElement(QrBlock, { url, qr, onReset, auth, onNavigateSecurity }),
@@ -550,7 +693,10 @@ const TunnelCard = React.memo(function TunnelCard({
550
693
  disabled: configured === false || phase === 'connecting' || phase === 'downloading',
551
694
  title: configured === false ? '请先保存服务器配置' : '',
552
695
  }, phase === 'connecting' ? '连接中…' : phase === 'downloading' ? '下载中…' : '开启'),
553
- running && onStop && React.createElement('button', { style: s.btnGhost, onClick: onStop }, '关闭'),
696
+ running && onStop && React.createElement('button', {
697
+ style: s.btnGhost,
698
+ onClick: onStop,
699
+ }, phase === 'reconnecting' ? '停止重连' : '关闭'),
554
700
  ),
555
701
  );
556
702
  });
@@ -608,6 +754,12 @@ const CloudflareConfigForm = React.memo(function CloudflareConfigForm({ token, h
608
754
  React.createElement('div', { style: { fontSize: 12, color: 'var(--dsw-alias-label-secondary, #6b7280)', marginBottom: 8, lineHeight: 1.5 } },
609
755
  '在 Cloudflare Zero Trust 控制台创建 Tunnel 即可获取专属 Token 并绑定自己的域名(如 dsh.yourname.com),每次重启 URL 永不变更。不填则使用默认免登录临时随机域名。'
610
756
  ),
757
+ React.createElement('a', {
758
+ href: CLOUDFLARE_TUTORIAL_URL,
759
+ target: '_blank',
760
+ rel: 'noreferrer',
761
+ style: { ...s.btnLink, marginBottom: 10 },
762
+ }, '📖 查看「Cloudflare 固定域名」申请与配置教程'),
611
763
  React.createElement('div', { style: { marginBottom: 8 } },
612
764
  React.createElement('input', {
613
765
  style: s.input,
@@ -2595,6 +2747,20 @@ function BridgePanel({ rpcCall }) {
2595
2747
  const [platforms, setPlatforms] = React.useState(null);
2596
2748
  const [selectedPlatform, setSelectedPlatform] = React.useState('wechat');
2597
2749
 
2750
+ // 隧道页"主入口"地址复制反馈
2751
+ const [copiedUrl, setCopiedUrl] = React.useState('');
2752
+ const copyEntryUrl = React.useCallback((url) => {
2753
+ const done = () => {
2754
+ setCopiedUrl(url);
2755
+ setTimeout(() => setCopiedUrl(''), 2000);
2756
+ };
2757
+ if (navigator.clipboard?.writeText) {
2758
+ navigator.clipboard.writeText(url).then(done).catch(() => done());
2759
+ } else {
2760
+ done();
2761
+ }
2762
+ }, []);
2763
+
2598
2764
  // 远程设备管理权限解锁状态
2599
2765
  const isLocalhost = typeof window === 'undefined' || (
2600
2766
  !window.location.hostname ||
@@ -2851,58 +3017,103 @@ function BridgePanel({ rpcCall }) {
2851
3017
  );
2852
3018
  } else if (activeTab === 'tunnel') {
2853
3019
  const ext = status?.externalTunnel;
2854
- tabContent = React.createElement(React.Fragment, null,
2855
- React.createElement(TunnelCard, {
2856
- title: 'Cloudflare 隧道',
2857
- desc: status?.cloudflared?.tokenConfigured
2858
- ? '固定域名模式(Token 运行 · 重启 URL 保持不变)'
2859
- : '一键获取公网地址(免登录临时随机域名)',
2860
- data: {
2861
- running: status?.cloudflared?.running,
2862
- url: status?.cloudflared?.url,
2863
- qr: status?.cloudflared?.qr,
2864
- state: status?.cloudflared?.state,
2865
- },
2866
- autoStart: status?.cloudflared?.autoStart,
2867
- onToggleAutoStart: onToggleCloudflaredAutoStart,
2868
- auth: status?.auth,
2869
- onNavigateSecurity: navSecurity,
2870
- onStart: onStartCloudflared,
2871
- onStop: onStopCloudflared,
2872
- onReset: status?.cloudflared?.running ? onResetCloudflared : null,
3020
+ const cf = status?.cloudflared;
3021
+
3022
+ // 计算"当前主入口":优先自建隧道(固定地址)> Cloudflare > 外部登记。
3023
+ // running(含重连中)即入列——重连时 url 可能暂时为空,但应让用户看到状态。
3024
+ const cfDesc = cf?.tokenConfigured ? '固定域名模式' : '免登录临时域名';
3025
+ const entries = [
3026
+ ct && ct.running && {
3027
+ key: 'custom', title: '自建隧道', desc: 'VPS 自建 · 固定地址',
3028
+ url: ct.url || null, qr: ct.qr, running: true,
3029
+ phase: ct.state && ct.state.phase, stateDetail: ct.state && ct.state.detail,
3030
+ autoStart: ct.autoStart, onToggleAutoStart: onToggleCustomAutoStart,
3031
+ onStart: onStartCustom, onStop: onStopCustom,
2873
3032
  },
2874
- React.createElement(CloudflareConfigForm, {
2875
- token: status?.cloudflared?.token ?? '',
2876
- hostname: status?.cloudflared?.hostname ?? '',
2877
- onSave: saveCloudflaredConfig,
2878
- }),
2879
- ),
2880
- React.createElement(ExternalTunnelCard, {
2881
- ext,
2882
- onSave: saveExternalTunnel,
3033
+ cf && cf.running && {
3034
+ key: 'cloudflared', title: 'Cloudflare 隧道', desc: cfDesc,
3035
+ url: cf.url || null, qr: cf.qr, running: true,
3036
+ phase: cf.state && cf.state.phase, stateDetail: cf.state && cf.state.detail,
3037
+ autoStart: cf.autoStart, onToggleAutoStart: onToggleCloudflaredAutoStart,
3038
+ onStart: onStartCloudflared, onStop: onStopCloudflared,
3039
+ onReset: onResetCloudflared,
3040
+ },
3041
+ ext && ext.configured && ext.url && {
3042
+ key: 'external', title: '外部已部署隧道', desc: '自行部署登记',
3043
+ url: ext.url, qr: ext.qr, running: true,
3044
+ },
3045
+ ].filter(Boolean);
3046
+ const primary = entries[0] || null;
3047
+ const otherCount = primary ? entries.length - 1 : entries.length;
3048
+
3049
+ tabContent = React.createElement(React.Fragment, null,
3050
+ React.createElement(TunnelEntryCard, {
3051
+ entry: primary,
3052
+ onCopy: copyEntryUrl,
3053
+ copied: Boolean(copiedUrl && primary && copiedUrl === primary.url),
3054
+ autoStart: primary ? primary.autoStart : undefined,
3055
+ onToggleAutoStart: primary ? primary.onToggleAutoStart : undefined,
3056
+ onStart: primary ? primary.onStart : (cf ? onStartCloudflared : null),
3057
+ onStop: primary ? primary.onStop : undefined,
3058
+ onReset: primary ? primary.onReset : undefined,
2883
3059
  }),
2884
- React.createElement(TunnelCard, {
2885
- title: '自建隧道',
2886
- desc: '连接自己部署的隧道服务器,获得固定域名',
2887
- data: {
2888
- configured: ct?.configured,
2889
- running: ct?.running,
2890
- url: ct?.url,
2891
- qr: ct?.qr,
2892
- state: ct?.state,
3060
+ otherCount > 0 && React.createElement('div', {
3061
+ style: { ...s.muted, fontSize: 11, marginBottom: 8, textAlign: 'center' },
3062
+ }, '另有 ' + otherCount + ' 个隧道入口在运行,可在下方「隧道配置」中查看与管理'),
3063
+
3064
+ React.createElement(TunnelConfigGroup, null,
3065
+ React.createElement(TunnelCard, {
3066
+ title: 'Cloudflare 隧道',
3067
+ desc: cf && cf.tokenConfigured
3068
+ ? '固定域名模式(Token 运行 · 重启 URL 保持不变)'
3069
+ : '一键获取公网地址(免登录临时随机域名)',
3070
+ data: {
3071
+ running: cf && cf.running,
3072
+ url: cf && cf.url,
3073
+ qr: cf && cf.qr,
3074
+ state: cf && cf.state,
3075
+ },
3076
+ autoStart: cf && cf.autoStart,
3077
+ onToggleAutoStart: onToggleCloudflaredAutoStart,
3078
+ auth: status && status.auth,
3079
+ onNavigateSecurity: navSecurity,
3080
+ onStart: onStartCloudflared,
3081
+ onStop: onStopCloudflared,
3082
+ onReset: (cf && cf.running) ? onResetCloudflared : null,
2893
3083
  },
2894
- autoStart: ct?.autoStart,
2895
- onToggleAutoStart: onToggleCustomAutoStart,
2896
- auth: status?.auth,
2897
- onNavigateSecurity: navSecurity,
2898
- onStart: onStartCustom,
2899
- onStop: onStopCustom,
2900
- },
2901
- React.createElement(CustomTunnelGuide),
2902
- React.createElement(CustomTunnelConfigForm, {
2903
- serverUrl: ct?.serverUrl ?? '',
2904
- accessToken: ct?.accessToken ?? '',
2905
- onSave: saveConfig,
3084
+ React.createElement(CloudflareConfigForm, {
3085
+ token: (cf && cf.token) || '',
3086
+ hostname: (cf && cf.hostname) || '',
3087
+ onSave: saveCloudflaredConfig,
3088
+ }),
3089
+ ),
3090
+ React.createElement(TunnelCard, {
3091
+ title: '自建隧道',
3092
+ desc: '连接自己部署的隧道服务器,获得固定域名',
3093
+ data: {
3094
+ configured: ct && ct.configured,
3095
+ running: ct && ct.running,
3096
+ url: ct && ct.url,
3097
+ qr: ct && ct.qr,
3098
+ state: ct && ct.state,
3099
+ },
3100
+ autoStart: ct && ct.autoStart,
3101
+ onToggleAutoStart: onToggleCustomAutoStart,
3102
+ auth: status && status.auth,
3103
+ onNavigateSecurity: navSecurity,
3104
+ onStart: onStartCustom,
3105
+ onStop: onStopCustom,
3106
+ },
3107
+ React.createElement(CustomTunnelGuide),
3108
+ React.createElement(CustomTunnelConfigForm, {
3109
+ serverUrl: (ct && ct.serverUrl) || '',
3110
+ accessToken: (ct && ct.accessToken) || '',
3111
+ onSave: saveConfig,
3112
+ }),
3113
+ ),
3114
+ React.createElement(ExternalTunnelCard, {
3115
+ ext,
3116
+ onSave: saveExternalTunnel,
2906
3117
  }),
2907
3118
  ),
2908
3119
  );
@@ -2991,6 +3202,14 @@ function BridgePanel({ rpcCall }) {
2991
3202
  const policy = auth?.adminPolicy ?? 'password_unlock';
2992
3203
  // 系统是否已配置任何密码(访客访问密码 或 独立管理密码)。
2993
3204
  const hasAnyPassword = !!(auth?.hasPassword || auth?.hasAdminPassword);
3205
+ // 解锁密码类型:有独立管理密码 → 用管理密码;否则(只有访问密码/都没有)
3206
+ // 服务端以 adminPasswordHash || passwordHash 兜底,实际校验的是访问密码。
3207
+ // UI 需如实告诉用户该输哪个密码,避免"哪来的管理密码"的困惑。
3208
+ const unlockUsesAdmin = !!auth?.hasAdminPassword;
3209
+ const unlockPwdKind = unlockUsesAdmin ? '管理密码' : '访问密码';
3210
+ const unlockPwdHint = unlockUsesAdmin
3211
+ ? '请输入后台管理密码解锁管理权限。'
3212
+ : '当前未设置独立管理密码,输入您的访问密码即可解锁。';
2994
3213
  // 锁屏条件:远程 + 管理保护开启(adminProtection)+ 未解锁 + 非宽松策略。
2995
3214
  // 不依赖 auth.enabled:即使访问认证关闭,管理保护仍独立生效,上锁后必须显示锁屏。
2996
3215
  // local_only 也锁定(显示"仅限本机管理"专属锁屏)。
@@ -3040,8 +3259,32 @@ function BridgePanel({ rpcCall }) {
3040
3259
  React.createElement('div', { style: { textAlign: 'center', marginBottom: 20 } },
3041
3260
  React.createElement('div', { style: { fontSize: 40, marginBottom: 10 } }, '🔒'),
3042
3261
  React.createElement('div', { style: { ...s.label, fontSize: 16, fontWeight: 600 } }, '管理控制台已锁定'),
3043
- React.createElement('div', { style: { ...s.muted, fontSize: 12, marginTop: 6, lineHeight: 1.5 } },
3044
- '当前设备为远程访问。为保护您的网络与平台配置安全,请输入管理员密码解锁管理权限。'
3262
+ // 醒目提示该输入哪种密码
3263
+ React.createElement('div', { style: { marginTop: 10, display: 'flex', justifyContent: 'center' } },
3264
+ unlockUsesAdmin ? (
3265
+ React.createElement('span', {
3266
+ style: {
3267
+ display: 'inline-flex', alignItems: 'center', gap: 5,
3268
+ padding: '4px 12px', borderRadius: 999, fontSize: 12, fontWeight: 600,
3269
+ background: 'var(--dsw-alias-state-info-bg,#eff6ff)',
3270
+ color: 'var(--dsw-alias-state-info-primary,#2563eb)',
3271
+ border: '1px solid var(--dsw-alias-state-info-border,#bfdbfe)',
3272
+ },
3273
+ }, '🔑 使用管理密码解锁')
3274
+ ) : (
3275
+ React.createElement('span', {
3276
+ style: {
3277
+ display: 'inline-flex', alignItems: 'center', gap: 5,
3278
+ padding: '4px 12px', borderRadius: 999, fontSize: 12, fontWeight: 600,
3279
+ background: 'var(--dsw-alias-state-success-bg,#ecfdf5)',
3280
+ color: 'var(--dsw-alias-state-success-primary,#059669)',
3281
+ border: '1px solid var(--dsw-alias-state-success-border,#a7f3d0)',
3282
+ },
3283
+ }, '🔐 使用访问密码解锁')
3284
+ ),
3285
+ ),
3286
+ React.createElement('div', { style: { ...s.muted, fontSize: 12, marginTop: 10, lineHeight: 1.6 } },
3287
+ '当前设备为远程访问。为保护您的网络与平台配置安全,' + unlockPwdHint
3045
3288
  ),
3046
3289
  ),
3047
3290
  React.createElement('form', {
@@ -3051,7 +3294,7 @@ function BridgePanel({ rpcCall }) {
3051
3294
  React.createElement('input', {
3052
3295
  type: 'password',
3053
3296
  style: s.input,
3054
- placeholder: '输入后台管理密码',
3297
+ placeholder: '输入' + unlockPwdKind,
3055
3298
  value: unlockPassword,
3056
3299
  onChange: (e) => setUnlockPassword(e.target.value),
3057
3300
  autoFocus: true,
@@ -3070,7 +3313,7 @@ function BridgePanel({ rpcCall }) {
3070
3313
  type: 'button',
3071
3314
  style: { ...s.btnLink, fontSize: 12, color: 'var(--dsw-alias-label-secondary,#6b7280)' },
3072
3315
  onClick: () => setShowForgotGuide(v => !v),
3073
- }, '❓ 忘记后台管理密码?'),
3316
+ }, '❓ 忘记' + unlockPwdKind + '?'),
3074
3317
  ),
3075
3318
  showForgotGuide && React.createElement('div', {
3076
3319
  style: {
@@ -3079,8 +3322,8 @@ function BridgePanel({ rpcCall }) {
3079
3322
  color: 'var(--dsw-alias-label-secondary,#4b5563)', textAlign: 'left',
3080
3323
  },
3081
3324
  },
3082
- React.createElement('div', { style: { fontWeight: 600, color: 'var(--dsw-alias-label-primary,currentColor)', marginBottom: 4 } }, '🛟 找回与重置密码指引:'),
3083
- React.createElement('div', null, '1. ', React.createElement('strong', null, '电脑本机直连修改'), ':直接在运行本程序的电脑本机打开本控制台(127.0.0.1 享有物理免锁特权),可随时修改管理密码。'),
3325
+ React.createElement('div', { style: { fontWeight: 600, color: 'var(--dsw-alias-label-primary,currentColor)', marginBottom: 4 } }, '🛟 找回与重置' + unlockPwdKind + '指引:'),
3326
+ React.createElement('div', null, '1. ', React.createElement('strong', null, '电脑本机直连修改'), ':直接在运行本程序的电脑本机打开本控制台(127.0.0.1 享有物理免锁特权),可随时修改或清除密码。'),
3084
3327
  React.createElement('div', { style: { marginTop: 4 } }, '2. ', React.createElement('strong', null, '服务器 / 无头环境'), ':救急重置步骤参见 GitHub README 的「三重容灾保命体系」章节。'),
3085
3328
  ),
3086
3329
  )
@@ -3114,7 +3357,7 @@ function BridgePanel({ rpcCall }) {
3114
3357
  setUnlockErr(err);
3115
3358
  setShowUnlockModal(true);
3116
3359
  },
3117
- }, '🔑 立即输入管理密码解锁'),
3360
+ }, '🔑 立即输入' + unlockPwdKind + '解锁'),
3118
3361
  ),
3119
3362
 
3120
3363
  // 管理员解锁状态提示条
@@ -3190,7 +3433,9 @@ function BridgePanel({ rpcCall }) {
3190
3433
  }, '✕'),
3191
3434
  ),
3192
3435
  React.createElement('div', { style: { fontSize: 13, color: 'var(--dsw-alias-label-secondary,#4b5563)', marginBottom: 16, lineHeight: 1.5 } },
3193
- '当前操作需要后台管理员权限。为保护您的网络配置与机器人平台安全,请输入管理密码解锁:'
3436
+ unlockUsesAdmin
3437
+ ? '当前操作需要后台管理员权限。为保护您的网络配置与机器人平台安全,请输入管理密码解锁:'
3438
+ : '当前操作需要后台管理权限。未设置独立管理密码,输入您的访问密码即可解锁:'
3194
3439
  ),
3195
3440
  React.createElement('form', {
3196
3441
  onSubmit: handleUnlockAdmin,
@@ -3199,7 +3444,7 @@ function BridgePanel({ rpcCall }) {
3199
3444
  React.createElement('input', {
3200
3445
  type: 'password',
3201
3446
  style: s.input,
3202
- placeholder: '请输入后台管理密码',
3447
+ placeholder: '输入' + unlockPwdKind,
3203
3448
  value: unlockPassword,
3204
3449
  onChange: (e) => setUnlockPassword(e.target.value),
3205
3450
  autoFocus: true,