@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/README.en.md +85 -8
- package/README.md +85 -8
- package/client/client.js +832 -57
- package/client/index.js +718 -61
- package/docs/telegram-usage.md +89 -0
- package/lib/auth/login-template.js +378 -0
- package/lib/auth/manager.js +425 -0
- package/lib/bridge-rpc-constants.js +6 -0
- package/lib/bridge-rpc.js +34 -1
- package/lib/feishu/gateway.js +166 -7
- package/lib/feishu/node.js +55 -10
- package/lib/index.js +258 -14
- package/lib/platform/base.js +13 -0
- package/lib/platform/conversation-bridge.js +32 -4
- package/lib/qq/gateway.js +94 -14
- package/lib/qq/node.js +49 -9
- package/lib/telegram/gateway.js +619 -0
- package/lib/telegram/index.js +210 -0
- package/lib/telegram/node.js +342 -0
- package/lib/wechat/gateway.js +54 -0
- package/lib/wechat/node.js +1 -0
- package/package.json +4 -3
package/client/index.js
CHANGED
|
@@ -72,6 +72,9 @@ const Icons = {
|
|
|
72
72
|
tunnel: (props) => React.createElement('svg', { viewBox: '0 0 24 24', width: 16, height: 16, fill: 'currentColor', ...props },
|
|
73
73
|
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' })
|
|
74
74
|
),
|
|
75
|
+
security: (props) => React.createElement('svg', { viewBox: '0 0 24 24', width: 16, height: 16, fill: 'currentColor', ...props },
|
|
76
|
+
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' })
|
|
77
|
+
),
|
|
75
78
|
bot: (props) => React.createElement('svg', { viewBox: '0 0 24 24', width: 16, height: 16, fill: 'currentColor', ...props },
|
|
76
79
|
React.createElement('path', { d: 'M20 9V7c0-1.1-.9-2-2-2h-3c0-1.66-1.34-3-3-3S9 3.34 9 5H6c-1.1 0-2 .9-2 2v2c-1.66 0-3 1.34-3 3s1.34 3 3 3v4c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-4c1.66 0 3-1.34 3-3s-1.34-3-3-3zm-2 10H6V7h12v12zm-9-6c-.83 0-1.5-.67-1.5-1.5S8.17 10 9 10s1.5.67 1.5 1.5S9.83 13 9 13zm6 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5z' })
|
|
77
80
|
),
|
|
@@ -154,7 +157,7 @@ function StatusTag({ running, status }) {
|
|
|
154
157
|
}, text);
|
|
155
158
|
}
|
|
156
159
|
|
|
157
|
-
function QrBlock({ url, qr, onReset }) {
|
|
160
|
+
function QrBlock({ url, qr, onReset, auth, onNavigateSecurity }) {
|
|
158
161
|
const [copied, setCopied] = React.useState(false);
|
|
159
162
|
const [showQr, setShowQr] = React.useState(true);
|
|
160
163
|
|
|
@@ -191,10 +194,31 @@ function QrBlock({ url, qr, onReset }) {
|
|
|
191
194
|
const toggleQr = React.useCallback(() => setShowQr(v => !v), []);
|
|
192
195
|
|
|
193
196
|
return React.createElement('div', { style: { marginTop: 10 } },
|
|
194
|
-
|
|
195
|
-
'
|
|
196
|
-
|
|
197
|
-
|
|
197
|
+
auth?.enabled
|
|
198
|
+
? React.createElement('div', {
|
|
199
|
+
style: {
|
|
200
|
+
display: 'inline-flex', alignItems: 'center', gap: 6, padding: '4px 10px',
|
|
201
|
+
background: 'var(--dsw-alias-state-success-bg,#ecfdf5)',
|
|
202
|
+
border: '1px solid var(--dsw-alias-state-success-primary,#10b981)',
|
|
203
|
+
borderRadius: 8, fontSize: 12, color: 'var(--dsw-alias-state-success-primary,#059669)',
|
|
204
|
+
marginBottom: 8, fontWeight: 500,
|
|
205
|
+
cursor: onNavigateSecurity ? 'pointer' : 'default',
|
|
206
|
+
},
|
|
207
|
+
onClick: onNavigateSecurity,
|
|
208
|
+
title: onNavigateSecurity ? '点击前往「安全认证」配置' : undefined,
|
|
209
|
+
},
|
|
210
|
+
'🛡️ 访问安全认证已生效 · 扫码设备自动完成免密授权',
|
|
211
|
+
onNavigateSecurity && React.createElement('span', { style: { textDecoration: 'underline', fontSize: 11, marginLeft: 4 } }, '设置 ➔')
|
|
212
|
+
)
|
|
213
|
+
: React.createElement('div', {
|
|
214
|
+
style: { ...s.warn, cursor: onNavigateSecurity ? 'pointer' : 'default', display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: 6 },
|
|
215
|
+
onClick: onNavigateSecurity,
|
|
216
|
+
title: onNavigateSecurity ? '点击前往「安全认证」开启访问保护' : undefined,
|
|
217
|
+
},
|
|
218
|
+
React.createElement('span', null, '⚠️ 当前未开启访问认证,建议在「安全认证」开启密码或扫码保护。'),
|
|
219
|
+
onNavigateSecurity && React.createElement('span', { style: { fontWeight: 600, textDecoration: 'underline', fontSize: 12, color: 'var(--dsw-alias-brand-primary,#4f6ef7)' } }, '去开启 ➔')
|
|
220
|
+
),
|
|
221
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, marginTop: 6 } },
|
|
198
222
|
React.createElement('code', { style: { ...s.code, flex: 1 } }, url),
|
|
199
223
|
React.createElement('button', {
|
|
200
224
|
style: { ...s.btnGhost, height: 26, padding: '0 10px', fontSize: 12, flexShrink: 0 },
|
|
@@ -260,7 +284,7 @@ const CustomTunnelConfigForm = React.memo(function CustomTunnelConfigForm({ serv
|
|
|
260
284
|
const handleTokenChange = React.useCallback((e) => { setAccessToken(e.target.value); setSaveSuccess(false); }, []);
|
|
261
285
|
|
|
262
286
|
return React.createElement('div', { style: s.block },
|
|
263
|
-
React.createElement('div', { style: { ...s.muted, marginBottom: 8 } }, '
|
|
287
|
+
React.createElement('div', { style: { ...s.muted, marginBottom: 8 } }, '隧道服务器配置'),
|
|
264
288
|
React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 8 } },
|
|
265
289
|
React.createElement('input', {
|
|
266
290
|
style: s.input,
|
|
@@ -273,12 +297,15 @@ const CustomTunnelConfigForm = React.memo(function CustomTunnelConfigForm({ serv
|
|
|
273
297
|
React.createElement('input', {
|
|
274
298
|
style: s.input,
|
|
275
299
|
type: 'password',
|
|
276
|
-
placeholder: '
|
|
300
|
+
placeholder: '隧道服务端连接令牌(Tunnel Access Token)',
|
|
277
301
|
value: accessToken,
|
|
278
302
|
onChange: handleTokenChange,
|
|
279
303
|
onKeyDown: (e) => { if (e.key === 'Enter' && dirty && !saving) handleSave(); },
|
|
280
304
|
disabled: saving,
|
|
281
305
|
}),
|
|
306
|
+
React.createElement('div', { style: { ...s.muted, fontSize: 11 } },
|
|
307
|
+
'💡 用于与您的 VPS 隧道服务端建立反向通道(与 Web 网页访客访问密码互相独立)。'
|
|
308
|
+
),
|
|
282
309
|
React.createElement('button', {
|
|
283
310
|
style: {
|
|
284
311
|
...s.btnPri,
|
|
@@ -293,7 +320,7 @@ const CustomTunnelConfigForm = React.memo(function CustomTunnelConfigForm({ serv
|
|
|
293
320
|
);
|
|
294
321
|
});
|
|
295
322
|
|
|
296
|
-
const TunnelCard = React.memo(function TunnelCard({ title, desc, data, onStart, onStop, onReset, children }) {
|
|
323
|
+
const TunnelCard = React.memo(function TunnelCard({ title, desc, data, onStart, onStop, onReset, auth, onNavigateSecurity, children }) {
|
|
297
324
|
const { running, configured, url, qr, state } = data ?? {};
|
|
298
325
|
const phase = state?.phase ?? 'idle';
|
|
299
326
|
|
|
@@ -312,7 +339,7 @@ const TunnelCard = React.memo(function TunnelCard({ title, desc, data, onStart,
|
|
|
312
339
|
color: phase === 'error' ? 'var(--dsw-alias-state-error-primary,#dc2626)' : 'var(--dsw-alias-label-secondary,#6b7280)',
|
|
313
340
|
},
|
|
314
341
|
}, state?.detail ?? phase),
|
|
315
|
-
url && React.createElement(QrBlock, { url, qr, onReset }),
|
|
342
|
+
url && React.createElement(QrBlock, { url, qr, onReset, auth, onNavigateSecurity }),
|
|
316
343
|
(onStart || onStop) && React.createElement('div', {
|
|
317
344
|
style: { ...s.block, display: 'flex', gap: 8, flexWrap: 'wrap' },
|
|
318
345
|
},
|
|
@@ -327,6 +354,421 @@ const TunnelCard = React.memo(function TunnelCard({ title, desc, data, onStart,
|
|
|
327
354
|
);
|
|
328
355
|
});
|
|
329
356
|
|
|
357
|
+
// ---- 访问安全认证卡片 ----
|
|
358
|
+
|
|
359
|
+
const AccessAuthCard = React.memo(function AccessAuthCard({ auth, rpcCall, onUpdate }) {
|
|
360
|
+
const [enabled, setEnabled] = React.useState(auth?.enabled ?? false);
|
|
361
|
+
const [mode, setMode] = React.useState(auth?.mode ?? 'token_and_password');
|
|
362
|
+
const [scope, setScope] = React.useState(auth?.scope ?? 'all');
|
|
363
|
+
const [adminPolicy, setAdminPolicy] = React.useState(auth?.adminPolicy ?? 'password_unlock');
|
|
364
|
+
|
|
365
|
+
// 1. 访客访问密码状态
|
|
366
|
+
const [accessPassword, setAccessPassword] = React.useState('');
|
|
367
|
+
const [showAccessPassword, setShowAccessPassword] = React.useState(false);
|
|
368
|
+
const [savingAccess, setSavingAccess] = React.useState(false);
|
|
369
|
+
const [saveAccessSuccess, setSaveAccessSuccess] = React.useState(false);
|
|
370
|
+
const [msgAccess, setMsgAccess] = React.useState(null);
|
|
371
|
+
|
|
372
|
+
// 2. 后台管理密码状态
|
|
373
|
+
const [adminPassword, setAdminPassword] = React.useState('');
|
|
374
|
+
const [showAdminPassword, setShowAdminPassword] = React.useState(false);
|
|
375
|
+
const [savingAdmin, setSavingAdmin] = React.useState(false);
|
|
376
|
+
const [saveAdminSuccess, setSaveAdminSuccess] = React.useState(false);
|
|
377
|
+
const [msgAdmin, setMsgAdmin] = React.useState(null);
|
|
378
|
+
|
|
379
|
+
const [topMsg, setTopMsg] = React.useState(null);
|
|
380
|
+
|
|
381
|
+
React.useEffect(() => {
|
|
382
|
+
if (auth) {
|
|
383
|
+
setEnabled(auth.enabled ?? false);
|
|
384
|
+
setMode(auth.mode ?? 'token_and_password');
|
|
385
|
+
setScope(auth.scope ?? 'all');
|
|
386
|
+
setAdminPolicy(auth.adminPolicy ?? 'password_unlock');
|
|
387
|
+
}
|
|
388
|
+
}, [auth]);
|
|
389
|
+
|
|
390
|
+
const handleToggleEnabled = async () => {
|
|
391
|
+
const next = !enabled;
|
|
392
|
+
setEnabled(next);
|
|
393
|
+
try {
|
|
394
|
+
await rpcCall(BRIDGE_ENDPOINTS.authUpdateConfig, { enabled: next });
|
|
395
|
+
setTopMsg({ ok: true, text: next ? '✓ 访问安全认证已开启(现有登录态已刷新)' : '✓ 访问安全认证已关闭' });
|
|
396
|
+
onUpdate?.();
|
|
397
|
+
} catch (e) {
|
|
398
|
+
setTopMsg({ ok: false, text: e.message || '更新失败' });
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
const handleChangeMode = async (m) => {
|
|
403
|
+
setMode(m);
|
|
404
|
+
try {
|
|
405
|
+
await rpcCall(BRIDGE_ENDPOINTS.authUpdateConfig, { mode: m });
|
|
406
|
+
setTopMsg({ ok: true, text: '✓ 外部验证模式已切换,已刷新全域登录态' });
|
|
407
|
+
onUpdate?.();
|
|
408
|
+
} catch (e) {
|
|
409
|
+
setTopMsg({ ok: false, text: e.message || '更新失败' });
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
const handleChangeScope = async (sc) => {
|
|
414
|
+
setScope(sc);
|
|
415
|
+
try {
|
|
416
|
+
await rpcCall(BRIDGE_ENDPOINTS.authUpdateConfig, { scope: sc });
|
|
417
|
+
setTopMsg({ ok: true, text: '✓ 防护生效范围已更新' });
|
|
418
|
+
onUpdate?.();
|
|
419
|
+
} catch (e) {
|
|
420
|
+
setTopMsg({ ok: false, text: e.message || '更新失败' });
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
const handleChangeAdminPolicy = async (pol) => {
|
|
425
|
+
setAdminPolicy(pol);
|
|
426
|
+
try {
|
|
427
|
+
await rpcCall(BRIDGE_ENDPOINTS.authUpdateConfig, { adminPolicy: pol });
|
|
428
|
+
setTopMsg({ ok: true, text: '✓ 远程管理防篡改策略已更新' });
|
|
429
|
+
onUpdate?.();
|
|
430
|
+
} catch (e) {
|
|
431
|
+
setTopMsg({ ok: false, text: e.message || '更新失败' });
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
// 保存访客访问密码
|
|
436
|
+
const handleSaveAccessPassword = async () => {
|
|
437
|
+
setSavingAccess(true);
|
|
438
|
+
setMsgAccess(null);
|
|
439
|
+
try {
|
|
440
|
+
const res = await rpcCall(BRIDGE_ENDPOINTS.authUpdateConfig, { password: accessPassword });
|
|
441
|
+
if (res?.ok) {
|
|
442
|
+
setSaveAccessSuccess(true);
|
|
443
|
+
setMsgAccess({ ok: true, text: '✓ 访客访问密码已成功保存!原有的历史访客会话已全部安全刷新。' });
|
|
444
|
+
setAccessPassword('');
|
|
445
|
+
setTimeout(() => setSaveAccessSuccess(false), 3500);
|
|
446
|
+
onUpdate?.();
|
|
447
|
+
} else {
|
|
448
|
+
setMsgAccess({ ok: false, text: res?.error?.message || '保存失败' });
|
|
449
|
+
}
|
|
450
|
+
} catch (e) {
|
|
451
|
+
setMsgAccess({ ok: false, text: e.message || '保存失败' });
|
|
452
|
+
} finally {
|
|
453
|
+
setSavingAccess(false);
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
// 保存后台管理密码
|
|
458
|
+
const handleSaveAdminPassword = async () => {
|
|
459
|
+
setSavingAdmin(true);
|
|
460
|
+
setMsgAdmin(null);
|
|
461
|
+
try {
|
|
462
|
+
const res = await rpcCall(BRIDGE_ENDPOINTS.authUpdateConfig, { adminPassword });
|
|
463
|
+
if (res?.ok) {
|
|
464
|
+
setSaveAdminSuccess(true);
|
|
465
|
+
setMsgAdmin({ ok: true, text: '✓ 后台管理密码已成功保存!远程管理解锁状态已重置生效。' });
|
|
466
|
+
setAdminPassword('');
|
|
467
|
+
setTimeout(() => setSaveAdminSuccess(false), 3500);
|
|
468
|
+
onUpdate?.();
|
|
469
|
+
} else {
|
|
470
|
+
setMsgAdmin({ ok: false, text: res?.error?.message || '保存失败' });
|
|
471
|
+
}
|
|
472
|
+
} catch (e) {
|
|
473
|
+
setMsgAdmin({ ok: false, text: e.message || '保存失败' });
|
|
474
|
+
} finally {
|
|
475
|
+
setSavingAdmin(false);
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
const handleRegenerateToken = async () => {
|
|
480
|
+
if (!confirm('重置后,之前包含旧 Token 的二维码和分享链接将立即失效。是否确认重置?')) return;
|
|
481
|
+
try {
|
|
482
|
+
await rpcCall(BRIDGE_ENDPOINTS.authRegenerateToken, {});
|
|
483
|
+
setTopMsg({ ok: true, text: '✓ 安全 Token 已重置,二维码与专属链接已刷新' });
|
|
484
|
+
onUpdate?.();
|
|
485
|
+
} catch (e) {
|
|
486
|
+
setTopMsg({ ok: false, text: e.message || '重置失败' });
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
const scopeLabel = scope === 'all' ? '全部通道 (局域网+公网)' : scope === 'public_only' ? '仅公网隧道' : '仅局域网';
|
|
491
|
+
const modeLabel = mode === 'token_and_password' ? '扫码免密 + 密码' : mode === 'password_only' ? '仅密码登录' : '仅安全 Token';
|
|
492
|
+
const adminLabel = adminPolicy === 'password_unlock' ? '需密码解锁' : adminPolicy === 'local_only' ? '仅限电脑本机管理' : '宽松模式';
|
|
493
|
+
|
|
494
|
+
return React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 14 } },
|
|
495
|
+
// ---- 顶部总控与状态概览卡片 ----
|
|
496
|
+
React.createElement('div', { style: s.card },
|
|
497
|
+
React.createElement('div', {
|
|
498
|
+
style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: 12 },
|
|
499
|
+
},
|
|
500
|
+
React.createElement('div', { style: { flex: '1 1 260px' } },
|
|
501
|
+
React.createElement('div', { style: { ...s.label, fontSize: 15, display: 'flex', alignItems: 'center', gap: 8 } },
|
|
502
|
+
'🔐 全局访问安全防护体系',
|
|
503
|
+
),
|
|
504
|
+
React.createElement('div', { style: { ...s.muted, marginTop: 4 } },
|
|
505
|
+
'集成外部访问门禁拦截与管理后台防篡改控制,双重守护远程会话与网络配置安全',
|
|
506
|
+
),
|
|
507
|
+
),
|
|
508
|
+
React.createElement('button', {
|
|
509
|
+
style: { ...(enabled ? s.btnPri : s.btnGhost), whiteSpace: 'nowrap', flexShrink: 0 },
|
|
510
|
+
onClick: handleToggleEnabled,
|
|
511
|
+
}, enabled ? '✓ 已启用安全防护' : '未开启安全防护'),
|
|
512
|
+
),
|
|
513
|
+
|
|
514
|
+
enabled && React.createElement('div', {
|
|
515
|
+
style: {
|
|
516
|
+
display: 'flex', gap: 8, flexWrap: 'wrap', marginTop: 14, paddingTop: 12,
|
|
517
|
+
borderTop: '1px solid var(--dsw-alias-border-l2,#e5e7eb)',
|
|
518
|
+
},
|
|
519
|
+
},
|
|
520
|
+
React.createElement('div', {
|
|
521
|
+
style: {
|
|
522
|
+
display: 'inline-flex', alignItems: 'center', gap: 5, padding: '4px 10px',
|
|
523
|
+
borderRadius: 16, fontSize: 11, background: 'var(--dsw-alias-bg-layer-2,#f3f4f6)',
|
|
524
|
+
color: 'var(--dsw-alias-label-secondary,#4b5563)',
|
|
525
|
+
},
|
|
526
|
+
}, '🌐 保护范围: ', React.createElement('strong', { style: { color: 'var(--dsw-alias-brand-primary,#4f6ef7)' } }, scopeLabel)),
|
|
527
|
+
React.createElement('div', {
|
|
528
|
+
style: {
|
|
529
|
+
display: 'inline-flex', alignItems: 'center', gap: 5, padding: '4px 10px',
|
|
530
|
+
borderRadius: 16, fontSize: 11, background: 'var(--dsw-alias-bg-layer-2,#f3f4f6)',
|
|
531
|
+
color: 'var(--dsw-alias-label-secondary,#4b5563)',
|
|
532
|
+
},
|
|
533
|
+
}, '🔑 外部验证: ', React.createElement('strong', { style: { color: 'var(--dsw-alias-brand-primary,#4f6ef7)' } }, modeLabel)),
|
|
534
|
+
React.createElement('div', {
|
|
535
|
+
style: {
|
|
536
|
+
display: 'inline-flex', alignItems: 'center', gap: 5, padding: '4px 10px',
|
|
537
|
+
borderRadius: 16, fontSize: 11, background: 'var(--dsw-alias-bg-layer-2,#f3f4f6)',
|
|
538
|
+
color: 'var(--dsw-alias-label-secondary,#4b5563)',
|
|
539
|
+
},
|
|
540
|
+
}, '🔒 后台防篡改: ', React.createElement('strong', { style: { color: 'var(--dsw-alias-state-success-primary,#059669)' } }, adminLabel)),
|
|
541
|
+
),
|
|
542
|
+
|
|
543
|
+
topMsg && React.createElement('div', {
|
|
544
|
+
style: {
|
|
545
|
+
marginTop: 12, padding: '8px 12px', borderRadius: 6, fontSize: 12,
|
|
546
|
+
background: topMsg.ok ? 'var(--dsw-alias-state-success-bg,#ecfdf5)' : 'var(--dsw-alias-state-error-bg,#fef2f2)',
|
|
547
|
+
color: topMsg.ok ? 'var(--dsw-alias-state-success-primary,#059669)' : 'var(--dsw-alias-state-error-primary,#dc2626)',
|
|
548
|
+
},
|
|
549
|
+
}, topMsg.text),
|
|
550
|
+
),
|
|
551
|
+
|
|
552
|
+
enabled && React.createElement(React.Fragment, null,
|
|
553
|
+
// =========================================================================
|
|
554
|
+
// ---- 第一道防线:外部访问门禁(控制谁能进入 Web 界面使用 AI) ----
|
|
555
|
+
// =========================================================================
|
|
556
|
+
React.createElement('div', { style: s.card },
|
|
557
|
+
React.createElement('div', { style: { marginBottom: 14 } },
|
|
558
|
+
React.createElement('div', { style: { ...s.label, fontSize: 14, display: 'flex', alignItems: 'center', gap: 6 } },
|
|
559
|
+
'🛡️ 第一道防线:外部访问门禁(控制谁能使用 AI)',
|
|
560
|
+
),
|
|
561
|
+
React.createElement('div', { style: { ...s.muted, marginTop: 3 } },
|
|
562
|
+
'控制外部设备通过局域网 IP 或公网隧道(Cloudflare/自建隧道)进入 DSH 聊天界面时的身份验证方式',
|
|
563
|
+
),
|
|
564
|
+
),
|
|
565
|
+
|
|
566
|
+
// 验证模式选择
|
|
567
|
+
React.createElement('div', { style: { marginBottom: 16 } },
|
|
568
|
+
React.createElement('label', { style: { ...s.label, display: 'block', marginBottom: 8, fontSize: 12 } }, '验证模式选择'),
|
|
569
|
+
React.createElement('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap' } },
|
|
570
|
+
[
|
|
571
|
+
{ id: 'token_and_password', title: '🟢 扫码免密 + 密码认证 (推荐)', desc: '二维码自带专属 Token 扫码秒进;直接输 IP/公网域名需输密码' },
|
|
572
|
+
{ id: 'password_only', title: '🔑 仅密码 / PIN 码登录', desc: '所有外部访问必须手动输入访问密码方可进入' },
|
|
573
|
+
{ id: 'token_only', title: '🎫 仅安全 Token 免密', desc: '仅持有带安全 Token 的二维码或专属分享链接方可进入' },
|
|
574
|
+
].map(opt => {
|
|
575
|
+
const isSel = mode === opt.id;
|
|
576
|
+
return React.createElement('div', {
|
|
577
|
+
key: opt.id,
|
|
578
|
+
onClick: () => handleChangeMode(opt.id),
|
|
579
|
+
style: {
|
|
580
|
+
flex: '1 1 200px',
|
|
581
|
+
padding: '10px 12px',
|
|
582
|
+
borderRadius: 8,
|
|
583
|
+
border: `1px solid ${isSel ? 'var(--dsw-alias-brand-primary,#4f6ef7)' : 'var(--dsw-alias-border-l2,#e5e7eb)'}`,
|
|
584
|
+
background: isSel ? 'var(--dsw-alias-state-info-bg,#eff6ff)' : 'var(--dsw-alias-bg-layer-2,#f9fafb)',
|
|
585
|
+
cursor: 'pointer',
|
|
586
|
+
transition: 'all 0.15s ease',
|
|
587
|
+
},
|
|
588
|
+
},
|
|
589
|
+
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),
|
|
590
|
+
React.createElement('div', { style: { ...s.muted, fontSize: 11, marginTop: 4 } }, opt.desc),
|
|
591
|
+
);
|
|
592
|
+
})
|
|
593
|
+
),
|
|
594
|
+
),
|
|
595
|
+
|
|
596
|
+
// 防护生效范围
|
|
597
|
+
React.createElement('div', { style: { marginBottom: 16 } },
|
|
598
|
+
React.createElement('label', { style: { ...s.label, display: 'block', marginBottom: 8, fontSize: 12 } }, '防护生效通道'),
|
|
599
|
+
React.createElement('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap' } },
|
|
600
|
+
[
|
|
601
|
+
{ id: 'all', title: '全部通道防护 (推荐)', desc: '局域网 IP 直连与公网隧道全部受安全保护' },
|
|
602
|
+
{ id: 'public_only', title: '仅公网隧道开启防护', desc: '局域网内设备直接免密直连,公网隧道强制验证' },
|
|
603
|
+
{ id: 'lan_only', title: '仅局域网开启防护', desc: '仅局域网直连需验证,公网隧道不开启' },
|
|
604
|
+
].map(opt => {
|
|
605
|
+
const isSel = scope === opt.id;
|
|
606
|
+
return React.createElement('div', {
|
|
607
|
+
key: opt.id,
|
|
608
|
+
onClick: () => handleChangeScope(opt.id),
|
|
609
|
+
style: {
|
|
610
|
+
flex: '1 1 180px',
|
|
611
|
+
padding: '9px 12px',
|
|
612
|
+
borderRadius: 8,
|
|
613
|
+
border: `1px solid ${isSel ? 'var(--dsw-alias-brand-primary,#4f6ef7)' : 'var(--dsw-alias-border-l2,#e5e7eb)'}`,
|
|
614
|
+
background: isSel ? 'var(--dsw-alias-state-info-bg,#eff6ff)' : 'var(--dsw-alias-bg-layer-2,#f9fafb)',
|
|
615
|
+
cursor: 'pointer',
|
|
616
|
+
transition: 'all 0.15s ease',
|
|
617
|
+
},
|
|
618
|
+
},
|
|
619
|
+
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),
|
|
620
|
+
React.createElement('div', { style: { ...s.muted, fontSize: 11, marginTop: 3 } }, opt.desc),
|
|
621
|
+
);
|
|
622
|
+
})
|
|
623
|
+
),
|
|
624
|
+
),
|
|
625
|
+
|
|
626
|
+
// 访客访问密码输入框 (当非 token_only 时展示)
|
|
627
|
+
mode !== 'token_only' && React.createElement('div', { style: { marginBottom: 16 } },
|
|
628
|
+
React.createElement('label', { style: { ...s.label, display: 'block', marginBottom: 6, fontSize: 12 } },
|
|
629
|
+
`设置外部访客访问密码 ${auth?.hasPassword ? '(✓ 已设置访客密码)' : '(⚠️ 尚未设置密码,直接输入 IP 将免密)'}`,
|
|
630
|
+
),
|
|
631
|
+
React.createElement('div', { style: { display: 'flex', gap: 8, alignItems: 'center' } },
|
|
632
|
+
React.createElement('input', {
|
|
633
|
+
type: showAccessPassword ? 'text' : 'password',
|
|
634
|
+
style: { ...s.input, flex: 1 },
|
|
635
|
+
placeholder: auth?.hasPassword ? '输入新密码以修改(留空保存可清除访客密码)' : '设置外部访客访问密码 / PIN 码',
|
|
636
|
+
value: accessPassword,
|
|
637
|
+
onChange: e => setAccessPassword(e.target.value),
|
|
638
|
+
}),
|
|
639
|
+
React.createElement('button', {
|
|
640
|
+
style: { ...s.btnGhost, height: 32, fontSize: 12, whiteSpace: 'nowrap', flexShrink: 0 },
|
|
641
|
+
onClick: () => setShowAccessPassword(v => !v),
|
|
642
|
+
}, showAccessPassword ? '隐藏' : '显示'),
|
|
643
|
+
React.createElement('button', {
|
|
644
|
+
style: {
|
|
645
|
+
...s.btnPri, height: 32, fontSize: 12, whiteSpace: 'nowrap', flexShrink: 0,
|
|
646
|
+
background: saveAccessSuccess ? '#059669' : 'var(--dsw-alias-brand-primary, #4f6ef7)',
|
|
647
|
+
color: '#ffffff',
|
|
648
|
+
cursor: savingAccess ? 'wait' : 'pointer',
|
|
649
|
+
},
|
|
650
|
+
onClick: handleSaveAccessPassword,
|
|
651
|
+
disabled: savingAccess,
|
|
652
|
+
}, savingAccess ? '保存中…' : saveAccessSuccess ? '✓ 已成功保存!' : '保存访问密码'),
|
|
653
|
+
),
|
|
654
|
+
React.createElement('div', { style: { ...s.muted, fontSize: 11, marginTop: 4 } },
|
|
655
|
+
'💡 当外部朋友或同事未通过二维码扫码,而是直接输入 IP 或公网域名访问时,需输入此密码登录。'
|
|
656
|
+
),
|
|
657
|
+
msgAccess && React.createElement('div', {
|
|
658
|
+
style: {
|
|
659
|
+
marginTop: 8, padding: '6px 12px', borderRadius: 6, fontSize: 12,
|
|
660
|
+
background: msgAccess.ok ? 'var(--dsw-alias-state-success-bg,#ecfdf5)' : 'var(--dsw-alias-state-error-bg,#fef2f2)',
|
|
661
|
+
color: msgAccess.ok ? 'var(--dsw-alias-state-success-primary,#059669)' : 'var(--dsw-alias-state-error-primary,#dc2626)',
|
|
662
|
+
},
|
|
663
|
+
}, msgAccess.text),
|
|
664
|
+
),
|
|
665
|
+
|
|
666
|
+
// 免密 Token 管理
|
|
667
|
+
mode !== 'password_only' && React.createElement('div', { style: s.block },
|
|
668
|
+
React.createElement('label', { style: { ...s.label, display: 'block', marginBottom: 6, fontSize: 12 } }, '免密扫码 Token (专属访问凭据)'),
|
|
669
|
+
React.createElement('div', { style: { display: 'flex', gap: 8, alignItems: 'center' } },
|
|
670
|
+
React.createElement('code', { 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)' } },
|
|
671
|
+
auth?.secretToken ? `${auth.secretToken.slice(0, 10)}****************` : '未生成'
|
|
672
|
+
),
|
|
673
|
+
React.createElement('button', {
|
|
674
|
+
style: { ...s.btnGhost, height: 32, fontSize: 12, whiteSpace: 'nowrap', flexShrink: 0 },
|
|
675
|
+
onClick: handleRegenerateToken,
|
|
676
|
+
title: '重新生成 Token,使之前分享的旧二维码和链接立即失效',
|
|
677
|
+
}, '🔄 重置安全 Token'),
|
|
678
|
+
),
|
|
679
|
+
React.createElement('div', { style: { ...s.muted, fontSize: 11, marginTop: 4 } },
|
|
680
|
+
'💡 控制台生成的局域网与公网二维码已自动嵌入此 Token,手机扫码即可免密进入聊天界面(但不赋予后台管理设置权限)。'
|
|
681
|
+
),
|
|
682
|
+
),
|
|
683
|
+
),
|
|
684
|
+
|
|
685
|
+
// =========================================================================
|
|
686
|
+
// ---- 第二道防线:后台管理防篡改(控制谁能修改本插件所有设置) ----
|
|
687
|
+
// =========================================================================
|
|
688
|
+
React.createElement('div', { style: s.card },
|
|
689
|
+
React.createElement('div', { style: { marginBottom: 14 } },
|
|
690
|
+
React.createElement('div', { style: { ...s.label, fontSize: 14, display: 'flex', alignItems: 'center', gap: 6 } },
|
|
691
|
+
'🔒 第二道防线:管理后台防篡改(控制谁能修改本插件所有设置)',
|
|
692
|
+
),
|
|
693
|
+
React.createElement('div', { style: { ...s.muted, marginTop: 3 } },
|
|
694
|
+
'锁定整个插件设置后台(包含局域网、公网隧道、IM 机器人密钥与安全设置),防止他人随意篡改配置',
|
|
695
|
+
),
|
|
696
|
+
),
|
|
697
|
+
|
|
698
|
+
// 管理员密码设置
|
|
699
|
+
React.createElement('div', { style: { marginBottom: 16 } },
|
|
700
|
+
React.createElement('label', { style: { ...s.label, display: 'block', marginBottom: 6, fontSize: 12 } },
|
|
701
|
+
`设置独立管理员密码 ${auth?.hasAdminPassword ? '(✓ 已设置独立管理密码)' : '(未单独设置,默认使用上述访客访问密码)'}`,
|
|
702
|
+
),
|
|
703
|
+
React.createElement('div', { style: { display: 'flex', gap: 8, alignItems: 'center' } },
|
|
704
|
+
React.createElement('input', {
|
|
705
|
+
type: showAdminPassword ? 'text' : 'password',
|
|
706
|
+
style: { ...s.input, flex: 1 },
|
|
707
|
+
placeholder: auth?.hasAdminPassword ? '输入新密码以修改(留空保存可清除独立管理密码)' : '设置后台管理解锁密码(建议与访客密码不同)',
|
|
708
|
+
value: adminPassword,
|
|
709
|
+
onChange: e => setAdminPassword(e.target.value),
|
|
710
|
+
}),
|
|
711
|
+
React.createElement('button', {
|
|
712
|
+
style: { ...s.btnGhost, height: 32, fontSize: 12, whiteSpace: 'nowrap', flexShrink: 0 },
|
|
713
|
+
onClick: () => setShowAdminPassword(v => !v),
|
|
714
|
+
}, showAdminPassword ? '隐藏' : '显示'),
|
|
715
|
+
React.createElement('button', {
|
|
716
|
+
style: {
|
|
717
|
+
...s.btnPri, height: 32, fontSize: 12, whiteSpace: 'nowrap', flexShrink: 0,
|
|
718
|
+
background: saveAdminSuccess ? '#059669' : 'var(--dsw-alias-brand-primary, #4f6ef7)',
|
|
719
|
+
color: '#ffffff',
|
|
720
|
+
cursor: savingAdmin ? 'wait' : 'pointer',
|
|
721
|
+
},
|
|
722
|
+
onClick: handleSaveAdminPassword,
|
|
723
|
+
disabled: savingAdmin,
|
|
724
|
+
}, savingAdmin ? '保存中…' : saveAdminSuccess ? '✓ 已成功保存!' : '保存管理密码'),
|
|
725
|
+
),
|
|
726
|
+
React.createElement('div', { style: { ...s.muted, fontSize: 11, marginTop: 4, color: 'var(--dsw-alias-brand-primary,#4f6ef7)' } },
|
|
727
|
+
'🔑 核心作用:用于远程设备进入设置后台时的解锁验证。设置后,即便把访问密码告知他人,他人也无法进入设置后台改配置。'
|
|
728
|
+
),
|
|
729
|
+
msgAdmin && React.createElement('div', {
|
|
730
|
+
style: {
|
|
731
|
+
marginTop: 8, padding: '6px 12px', borderRadius: 6, fontSize: 12,
|
|
732
|
+
background: msgAdmin.ok ? 'var(--dsw-alias-state-success-bg,#ecfdf5)' : 'var(--dsw-alias-state-error-bg,#fef2f2)',
|
|
733
|
+
color: msgAdmin.ok ? 'var(--dsw-alias-state-success-primary,#059669)' : 'var(--dsw-alias-state-error-primary,#dc2626)',
|
|
734
|
+
},
|
|
735
|
+
}, msgAdmin.text),
|
|
736
|
+
),
|
|
737
|
+
|
|
738
|
+
// 远程管理权限控制策略
|
|
739
|
+
React.createElement('div', { style: s.block },
|
|
740
|
+
React.createElement('label', { style: { ...s.label, display: 'block', marginBottom: 8, fontSize: 12 } }, '远程设备管理权限策略'),
|
|
741
|
+
React.createElement('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap' } },
|
|
742
|
+
[
|
|
743
|
+
{ id: 'password_unlock', title: '🔒 需密码解锁 (推荐)', desc: '远程手机/外网打开本插件设置时默认全局锁定,输入管理密码解锁后方可使用' },
|
|
744
|
+
{ id: 'local_only', title: '🚫 仅限电脑本机管理 (最严格)', desc: '远程设备彻底锁定整个设置后台,仅允许在 127.0.0.1 电脑本机上操作' },
|
|
745
|
+
{ id: 'open', title: '🔓 宽松模式', desc: '任何已通过第一道防线登录的设备均可直接修改所有配置' },
|
|
746
|
+
].map(opt => {
|
|
747
|
+
const isSel = adminPolicy === opt.id;
|
|
748
|
+
return React.createElement('div', {
|
|
749
|
+
key: opt.id,
|
|
750
|
+
onClick: () => handleChangeAdminPolicy(opt.id),
|
|
751
|
+
style: {
|
|
752
|
+
flex: '1 1 180px',
|
|
753
|
+
padding: '10px 12px',
|
|
754
|
+
borderRadius: 8,
|
|
755
|
+
border: `1px solid ${isSel ? 'var(--dsw-alias-brand-primary,#4f6ef7)' : 'var(--dsw-alias-border-l2,#e5e7eb)'}`,
|
|
756
|
+
background: isSel ? 'var(--dsw-alias-state-info-bg,#eff6ff)' : 'var(--dsw-alias-bg-layer-2,#f9fafb)',
|
|
757
|
+
cursor: 'pointer',
|
|
758
|
+
transition: 'all 0.15s ease',
|
|
759
|
+
},
|
|
760
|
+
},
|
|
761
|
+
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),
|
|
762
|
+
React.createElement('div', { style: { ...s.muted, fontSize: 11, marginTop: 4 } }, opt.desc),
|
|
763
|
+
);
|
|
764
|
+
})
|
|
765
|
+
),
|
|
766
|
+
),
|
|
767
|
+
),
|
|
768
|
+
),
|
|
769
|
+
);
|
|
770
|
+
});
|
|
771
|
+
|
|
330
772
|
// ---- 通用 IM 平台卡片 ----
|
|
331
773
|
|
|
332
774
|
function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatusChange }) {
|
|
@@ -343,16 +785,18 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
343
785
|
setCfgDraft({
|
|
344
786
|
digestIntervalSec: String(platform.config.digestIntervalSec ?? 300),
|
|
345
787
|
approvalTimeoutSec: String(platform.config.approvalTimeoutSec ?? 600),
|
|
346
|
-
maxMessageChars: String(platform.config.maxMessageChars ?? 2000),
|
|
788
|
+
maxMessageChars: String(platform.config.maxMessageChars ?? (platformId === 'telegram' ? 4096 : 2000)),
|
|
347
789
|
sendChunkDelayMs: String(platform.config.sendChunkDelayMs ?? 1500),
|
|
348
790
|
appId: platform.config.appId ?? '',
|
|
349
791
|
// Secret 不由后端回传;空值表示沿用已保存密钥
|
|
350
792
|
clientSecret: '',
|
|
351
793
|
appSecret: '',
|
|
352
794
|
domain: platform.config.domain ?? 'feishu',
|
|
795
|
+
botToken: '',
|
|
796
|
+
proxy: platform.config.proxy ?? '',
|
|
353
797
|
});
|
|
354
798
|
}
|
|
355
|
-
}, [platform?.config]);
|
|
799
|
+
}, [platform?.config, platformId]);
|
|
356
800
|
|
|
357
801
|
// 向上传递连接状态(供平台列表卡片绿点使用)
|
|
358
802
|
React.useEffect(() => {
|
|
@@ -424,10 +868,10 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
424
868
|
...d,
|
|
425
869
|
digestIntervalSec: '300',
|
|
426
870
|
approvalTimeoutSec: '600',
|
|
427
|
-
maxMessageChars: '2000',
|
|
871
|
+
maxMessageChars: platformId === 'telegram' ? '4096' : '2000',
|
|
428
872
|
sendChunkDelayMs: '1500',
|
|
429
873
|
}));
|
|
430
|
-
}, []);
|
|
874
|
+
}, [platformId]);
|
|
431
875
|
|
|
432
876
|
// 高级设置保存
|
|
433
877
|
const saveConfig = React.useCallback(async () => {
|
|
@@ -438,7 +882,7 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
438
882
|
maxMessageChars: Number(cfgDraft.maxMessageChars),
|
|
439
883
|
sendChunkDelayMs: Number(cfgDraft.sendChunkDelayMs),
|
|
440
884
|
};
|
|
441
|
-
// QQ / 飞书 平台额外携带凭证
|
|
885
|
+
// QQ / 飞书 / Telegram 平台额外携带凭证
|
|
442
886
|
if (platformId === 'qq') {
|
|
443
887
|
payload.appId = cfgDraft.appId.trim();
|
|
444
888
|
payload.clientSecret = cfgDraft.clientSecret.trim();
|
|
@@ -446,6 +890,9 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
446
890
|
payload.appId = cfgDraft.appId.trim();
|
|
447
891
|
payload.appSecret = cfgDraft.appSecret.trim();
|
|
448
892
|
payload.domain = cfgDraft.domain || 'feishu';
|
|
893
|
+
} else if (platformId === 'telegram') {
|
|
894
|
+
payload.botToken = cfgDraft.botToken.trim();
|
|
895
|
+
payload.proxy = cfgDraft.proxy.trim();
|
|
449
896
|
}
|
|
450
897
|
await act(BRIDGE_ENDPOINTS.platformSetConfig, payload);
|
|
451
898
|
}, [act, cfgDraft, platformId]);
|
|
@@ -461,6 +908,10 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
461
908
|
(platformId === 'feishu' && (
|
|
462
909
|
cfgDraft.appId !== (platform.config.appId ?? '') ||
|
|
463
910
|
cfgDraft.appSecret !== (platform.config.appSecret ?? '')
|
|
911
|
+
)) ||
|
|
912
|
+
(platformId === 'telegram' && (
|
|
913
|
+
cfgDraft.botToken !== '' ||
|
|
914
|
+
cfgDraft.proxy !== (platform.config.proxy ?? '')
|
|
464
915
|
))
|
|
465
916
|
);
|
|
466
917
|
|
|
@@ -523,6 +974,16 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
523
974
|
target: '_blank', rel: 'noopener noreferrer',
|
|
524
975
|
style: s.btnGhost,
|
|
525
976
|
}, '🌐 飞书开放平台'),
|
|
977
|
+
platformId === 'telegram' && React.createElement('a', {
|
|
978
|
+
href: 'https://github.com/wenbin-wb/dsh-bridge/blob/main/docs/telegram-usage.md',
|
|
979
|
+
target: '_blank', rel: 'noopener noreferrer',
|
|
980
|
+
style: s.btnGhost,
|
|
981
|
+
}, '📖 Telegram 使用说明'),
|
|
982
|
+
platformId === 'telegram' && React.createElement('a', {
|
|
983
|
+
href: 'https://t.me/BotFather',
|
|
984
|
+
target: '_blank', rel: 'noopener noreferrer',
|
|
985
|
+
style: s.btnGhost,
|
|
986
|
+
}, '🌐 @BotFather 申请 Bot'),
|
|
526
987
|
React.createElement('button', {
|
|
527
988
|
style: s.btnGhost,
|
|
528
989
|
onClick: () => setShowHelp(v => !v),
|
|
@@ -643,8 +1104,8 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
643
1104
|
title: '清除登录凭证,下次需重新配置',
|
|
644
1105
|
}, '解绑账号'),
|
|
645
1106
|
),
|
|
646
|
-
//
|
|
647
|
-
platformId === 'feishu' && platform.botQr && React.createElement('div', {
|
|
1107
|
+
// 飞书 / Telegram 扫码直达对话引导卡片
|
|
1108
|
+
(platformId === 'feishu' || platformId === 'telegram') && platform.botQr && React.createElement('div', {
|
|
648
1109
|
style: {
|
|
649
1110
|
marginTop: 12,
|
|
650
1111
|
padding: 12,
|
|
@@ -657,11 +1118,11 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
657
1118
|
flexWrap: 'wrap',
|
|
658
1119
|
},
|
|
659
1120
|
},
|
|
660
|
-
React.createElement('img', { src: platform.botQr, alt:
|
|
1121
|
+
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' } }),
|
|
661
1122
|
React.createElement('div', { style: { flex: 1, minWidth: 160 } },
|
|
662
|
-
React.createElement('div', { style: { ...s.label, fontSize: 13, fontWeight: 600 } },
|
|
1123
|
+
React.createElement('div', { style: { ...s.label, fontSize: 13, fontWeight: 600 } }, `📱 手机 ${platformName} 扫码直达对话`),
|
|
663
1124
|
React.createElement('div', { style: { ...s.muted, fontSize: 12, marginTop: 4, lineHeight: 1.5 } },
|
|
664
|
-
|
|
1125
|
+
`用 ${platformName} 扫描左侧二维码,立即打开与 Bot 对话;发送首条消息自动完成白名单授权。`
|
|
665
1126
|
),
|
|
666
1127
|
platform.botLink && React.createElement('div', { style: { display: 'flex', gap: 8, marginTop: 8 } },
|
|
667
1128
|
React.createElement('a', {
|
|
@@ -669,13 +1130,13 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
669
1130
|
target: '_blank',
|
|
670
1131
|
rel: 'noopener noreferrer',
|
|
671
1132
|
style: { ...s.btnGhost, textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 4, padding: '4px 10px', fontSize: 12 },
|
|
672
|
-
},
|
|
1133
|
+
}, `在 ${platformName} 客户端打开 ↗`),
|
|
673
1134
|
),
|
|
674
1135
|
),
|
|
675
1136
|
),
|
|
676
1137
|
),
|
|
677
1138
|
|
|
678
|
-
// 未配置 / 登录中:表单(QQ /
|
|
1139
|
+
// 未配置 / 登录中:表单(QQ / 飞书 / Telegram)或二维码(微信)
|
|
679
1140
|
(!platform?.configured || showQr) && React.createElement('div', { style: s.block },
|
|
680
1141
|
showQr && login.qr
|
|
681
1142
|
? React.createElement('div', null,
|
|
@@ -687,7 +1148,7 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
687
1148
|
),
|
|
688
1149
|
login.error && React.createElement('div', { style: { ...s.muted, marginTop: 4, color: 'var(--dsw-alias-state-warn-primary,#92400e)' } }, login.error),
|
|
689
1150
|
)
|
|
690
|
-
: (platformId === 'qq' || platformId === 'feishu')
|
|
1151
|
+
: (platformId === 'qq' || platformId === 'feishu' || platformId === 'telegram')
|
|
691
1152
|
? React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 10, marginTop: 4 } },
|
|
692
1153
|
platformId === 'feishu' && React.createElement('div', {
|
|
693
1154
|
style: {
|
|
@@ -704,40 +1165,76 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
704
1165
|
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'),
|
|
705
1166
|
React.createElement('span', { style: s.muted }, ' 手机扫码一键自动创建应用并输出凭证;或在下方手动填入凭证。')
|
|
706
1167
|
),
|
|
707
|
-
|
|
708
|
-
React.createElement(
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
1168
|
+
platformId === 'telegram'
|
|
1169
|
+
? React.createElement(React.Fragment, null,
|
|
1170
|
+
React.createElement('div', null,
|
|
1171
|
+
React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, 'Bot Token — Telegram @BotFather 下发的机器人 Token'),
|
|
1172
|
+
React.createElement('div', { style: { display: 'flex', gap: 6, alignItems: 'center' } },
|
|
1173
|
+
React.createElement('input', {
|
|
1174
|
+
style: { ...s.input, flex: 1 },
|
|
1175
|
+
type: showSecret ? 'text' : 'password',
|
|
1176
|
+
placeholder: '请输入 Telegram Bot Token (如 123456789:ABCdef...)',
|
|
1177
|
+
value: cfgDraft?.botToken ?? '',
|
|
1178
|
+
onChange: (e) => setCfgDraft(d => ({ ...d, botToken: e.target.value })),
|
|
1179
|
+
}),
|
|
1180
|
+
React.createElement('button', {
|
|
1181
|
+
style: { ...s.btnGhost, height: 32, padding: '0 10px', fontSize: 13, flexShrink: 0 },
|
|
1182
|
+
onClick: () => setShowSecret(v => !v),
|
|
1183
|
+
type: 'button',
|
|
1184
|
+
title: showSecret ? '隐藏密钥' : '显示明文',
|
|
1185
|
+
}, showSecret ? '🙈 隐藏' : '👁️ 显示'),
|
|
1186
|
+
),
|
|
1187
|
+
),
|
|
1188
|
+
React.createElement('div', null,
|
|
1189
|
+
React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, '网络代理 (可选) — 支持国内 HTTP / HTTPS 代理'),
|
|
1190
|
+
React.createElement('input', {
|
|
1191
|
+
style: { ...s.input, width: '100%' },
|
|
1192
|
+
placeholder: '可选,例如 http://127.0.0.1:7890(为空则直连或读取环境变量)',
|
|
1193
|
+
value: cfgDraft?.proxy ?? '',
|
|
1194
|
+
onChange: (e) => setCfgDraft(d => ({ ...d, proxy: e.target.value })),
|
|
1195
|
+
}),
|
|
1196
|
+
),
|
|
1197
|
+
)
|
|
1198
|
+
: React.createElement(React.Fragment, null,
|
|
1199
|
+
React.createElement('div', null,
|
|
1200
|
+
React.createElement('div', { style: { ...s.muted, marginBottom: 4 } },
|
|
1201
|
+
platformId === 'qq' ? 'AppID — QQ 开放平台机器人应用 ID' : 'App ID — 飞书开放平台自建应用 ID (cli_xxx)'
|
|
1202
|
+
),
|
|
1203
|
+
React.createElement('input', {
|
|
1204
|
+
style: { ...s.input, width: '100%' },
|
|
1205
|
+
placeholder: platformId === 'qq' ? '请输入 AppID' : '请输入 App ID (如 cli_a1b2c3d4...)',
|
|
1206
|
+
value: cfgDraft?.appId ?? '',
|
|
1207
|
+
onChange: (e) => setCfgDraft(d => ({ ...d, appId: e.target.value })),
|
|
1208
|
+
}),
|
|
1209
|
+
),
|
|
1210
|
+
React.createElement('div', null,
|
|
1211
|
+
React.createElement('div', { style: { ...s.muted, marginBottom: 4 } },
|
|
1212
|
+
platformId === 'qq' ? 'ClientSecret — QQ 开放平台机器人密钥' : 'App Secret — 飞书开放平台应用密钥'
|
|
1213
|
+
),
|
|
1214
|
+
React.createElement('div', { style: { display: 'flex', gap: 6, alignItems: 'center' } },
|
|
1215
|
+
React.createElement('input', {
|
|
1216
|
+
style: { ...s.input, flex: 1 },
|
|
1217
|
+
type: showSecret ? 'text' : 'password',
|
|
1218
|
+
placeholder: platformId === 'qq' ? '请输入 ClientSecret' : '请输入 App Secret',
|
|
1219
|
+
value: platformId === 'qq' ? (cfgDraft?.clientSecret ?? '') : (cfgDraft?.appSecret ?? ''),
|
|
1220
|
+
onChange: (e) => setCfgDraft(d => platformId === 'qq' ? ({ ...d, clientSecret: e.target.value }) : ({ ...d, appSecret: e.target.value })),
|
|
1221
|
+
}),
|
|
1222
|
+
React.createElement('button', {
|
|
1223
|
+
style: { ...s.btnGhost, height: 32, padding: '0 10px', fontSize: 13, flexShrink: 0 },
|
|
1224
|
+
onClick: () => setShowSecret(v => !v),
|
|
1225
|
+
type: 'button',
|
|
1226
|
+
title: showSecret ? '隐藏密钥' : '显示明文',
|
|
1227
|
+
}, showSecret ? '🙈 隐藏' : '👁️ 显示'),
|
|
1228
|
+
),
|
|
1229
|
+
),
|
|
1230
|
+
),
|
|
738
1231
|
React.createElement('div', null,
|
|
739
1232
|
React.createElement('a', {
|
|
740
|
-
href: platformId === 'qq'
|
|
1233
|
+
href: platformId === 'qq'
|
|
1234
|
+
? 'https://bot.q.qq.com/wiki/develop/api-v2/'
|
|
1235
|
+
: platformId === 'feishu'
|
|
1236
|
+
? 'https://open.feishu.cn/app'
|
|
1237
|
+
: 'https://t.me/BotFather',
|
|
741
1238
|
target: '_blank', rel: 'noopener noreferrer',
|
|
742
1239
|
style: s.btnLink,
|
|
743
1240
|
}, platformId === 'qq' ? '📖 前往 QQ 开放平台申请机器人' : '📖 前往飞书开放平台创建企业自建应用'),
|
|
@@ -746,7 +1243,9 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
746
1243
|
React.createElement('button', {
|
|
747
1244
|
style: { ...s.btnPri, opacity: busy ? 0.5 : 1 },
|
|
748
1245
|
onClick: saveConfig,
|
|
749
|
-
disabled: busy ||
|
|
1246
|
+
disabled: busy || (platformId === 'telegram'
|
|
1247
|
+
? !cfgDraft?.botToken?.trim()
|
|
1248
|
+
: (!cfgDraft?.appId?.trim() || (platformId === 'qq' ? !cfgDraft?.clientSecret?.trim() : !cfgDraft?.appSecret?.trim()))),
|
|
750
1249
|
}, busy ? '保存中…' : '保存并连接'),
|
|
751
1250
|
login.phase === 'error' && React.createElement('div', { style: { ...s.muted, fontSize: 12 } }, login.error ?? '连接失败'),
|
|
752
1251
|
),
|
|
@@ -1006,9 +1505,10 @@ function VersionBanner({ rpcCall }) {
|
|
|
1006
1505
|
// ---- Tab Bar ----
|
|
1007
1506
|
|
|
1008
1507
|
const TABS = [
|
|
1009
|
-
{ id: 'lan',
|
|
1010
|
-
{ id: 'tunnel',
|
|
1011
|
-
{ id: 'im',
|
|
1508
|
+
{ id: 'lan', label: '局域网', icon: Icons.lan },
|
|
1509
|
+
{ id: 'tunnel', label: '公网隧道', icon: Icons.tunnel },
|
|
1510
|
+
{ id: 'im', label: 'IM 机器人', icon: Icons.bot },
|
|
1511
|
+
{ id: 'security', label: '安全认证', icon: Icons.security },
|
|
1012
1512
|
];
|
|
1013
1513
|
|
|
1014
1514
|
function TabBar({ active, onChange, dots }) {
|
|
@@ -1068,6 +1568,43 @@ function BridgePanel({ rpcCall }) {
|
|
|
1068
1568
|
const [platforms, setPlatforms] = React.useState(null);
|
|
1069
1569
|
const [selectedPlatform, setSelectedPlatform] = React.useState('wechat');
|
|
1070
1570
|
|
|
1571
|
+
// 远程设备管理权限解锁状态
|
|
1572
|
+
const isLocalhost = typeof window === 'undefined' || (
|
|
1573
|
+
!window.location.hostname ||
|
|
1574
|
+
window.location.hostname === '127.0.0.1' ||
|
|
1575
|
+
window.location.hostname === 'localhost' ||
|
|
1576
|
+
window.location.hostname === '::1' ||
|
|
1577
|
+
window.location.hostname === '' ||
|
|
1578
|
+
window.location.protocol === 'file:' ||
|
|
1579
|
+
window.location.protocol === 'vscode-webview:' ||
|
|
1580
|
+
window.location.protocol === 'app:' ||
|
|
1581
|
+
window.location.hostname.endsWith('.local')
|
|
1582
|
+
);
|
|
1583
|
+
const [adminUnlocked, setAdminUnlocked] = React.useState(false);
|
|
1584
|
+
const [unlockPassword, setUnlockPassword] = React.useState('');
|
|
1585
|
+
const [unlockErr, setUnlockErr] = React.useState(null);
|
|
1586
|
+
const [unlocking, setUnlocking] = React.useState(false);
|
|
1587
|
+
const [showForgotGuide, setShowForgotGuide] = React.useState(false);
|
|
1588
|
+
|
|
1589
|
+
const handleUnlockAdmin = React.useCallback(async (e) => {
|
|
1590
|
+
e?.preventDefault?.();
|
|
1591
|
+
setUnlocking(true);
|
|
1592
|
+
setUnlockErr(null);
|
|
1593
|
+
try {
|
|
1594
|
+
const res = await rpcCall(BRIDGE_ENDPOINTS.authAdminUnlock, { password: unlockPassword });
|
|
1595
|
+
if (res?.ok) {
|
|
1596
|
+
setAdminUnlocked(true);
|
|
1597
|
+
setUnlockPassword('');
|
|
1598
|
+
} else {
|
|
1599
|
+
setUnlockErr(res?.error?.message || '管理员密码错误');
|
|
1600
|
+
}
|
|
1601
|
+
} catch (err) {
|
|
1602
|
+
setUnlockErr(err.message || '解锁请求失败');
|
|
1603
|
+
} finally {
|
|
1604
|
+
setUnlocking(false);
|
|
1605
|
+
}
|
|
1606
|
+
}, [rpcCall, unlockPassword]);
|
|
1607
|
+
|
|
1071
1608
|
const load = React.useCallback(async (quiet = false) => {
|
|
1072
1609
|
try {
|
|
1073
1610
|
const r = await rpcCall(BRIDGE_ENDPOINTS.getStatus, {});
|
|
@@ -1123,6 +1660,8 @@ function BridgePanel({ rpcCall }) {
|
|
|
1123
1660
|
act(BRIDGE_ENDPOINTS.saveCustomTunnelConfig, { serverUrl, accessToken })
|
|
1124
1661
|
, [act]);
|
|
1125
1662
|
|
|
1663
|
+
const navSecurity = React.useCallback(() => setActiveTab('security'), []);
|
|
1664
|
+
|
|
1126
1665
|
if (!status && !err) {
|
|
1127
1666
|
return React.createElement('div', {
|
|
1128
1667
|
style: { padding: 32, color: 'var(--dsw-alias-label-tertiary,#9ca3af)', fontSize: 13 },
|
|
@@ -1136,9 +1675,10 @@ function BridgePanel({ rpcCall }) {
|
|
|
1136
1675
|
p.status === 'connected' || p.status === 'starting' || p.status === 'reconnecting'
|
|
1137
1676
|
);
|
|
1138
1677
|
const dots = {
|
|
1139
|
-
lan:
|
|
1140
|
-
tunnel:
|
|
1141
|
-
im:
|
|
1678
|
+
lan: !!(status?.proxy?.running),
|
|
1679
|
+
tunnel: !!(status?.cloudflared?.running || ct?.running),
|
|
1680
|
+
im: !!imConnected,
|
|
1681
|
+
security: !!(status?.auth?.enabled),
|
|
1142
1682
|
};
|
|
1143
1683
|
|
|
1144
1684
|
// Tab 内容
|
|
@@ -1148,6 +1688,8 @@ function BridgePanel({ rpcCall }) {
|
|
|
1148
1688
|
title: '局域网访问',
|
|
1149
1689
|
desc: '同一 Wi-Fi 下的设备可直接扫码访问',
|
|
1150
1690
|
data: { running: status?.proxy?.running, url: status?.lan?.url, qr: status?.lan?.qr },
|
|
1691
|
+
auth: status?.auth,
|
|
1692
|
+
onNavigateSecurity: navSecurity,
|
|
1151
1693
|
});
|
|
1152
1694
|
} else if (activeTab === 'tunnel') {
|
|
1153
1695
|
tabContent = React.createElement(React.Fragment, null,
|
|
@@ -1160,6 +1702,8 @@ function BridgePanel({ rpcCall }) {
|
|
|
1160
1702
|
qr: status?.cloudflared?.qr,
|
|
1161
1703
|
state: status?.cloudflared?.state,
|
|
1162
1704
|
},
|
|
1705
|
+
auth: status?.auth,
|
|
1706
|
+
onNavigateSecurity: navSecurity,
|
|
1163
1707
|
onStart: onStartCloudflared,
|
|
1164
1708
|
onStop: onStopCloudflared,
|
|
1165
1709
|
onReset: status?.cloudflared?.running ? onResetCloudflared : null,
|
|
@@ -1174,6 +1718,8 @@ function BridgePanel({ rpcCall }) {
|
|
|
1174
1718
|
qr: ct?.qr,
|
|
1175
1719
|
state: ct?.state,
|
|
1176
1720
|
},
|
|
1721
|
+
auth: status?.auth,
|
|
1722
|
+
onNavigateSecurity: navSecurity,
|
|
1177
1723
|
onStart: onStartCustom,
|
|
1178
1724
|
onStop: onStopCustom,
|
|
1179
1725
|
},
|
|
@@ -1185,6 +1731,12 @@ function BridgePanel({ rpcCall }) {
|
|
|
1185
1731
|
}),
|
|
1186
1732
|
),
|
|
1187
1733
|
);
|
|
1734
|
+
} else if (activeTab === 'security') {
|
|
1735
|
+
tabContent = React.createElement(AccessAuthCard, {
|
|
1736
|
+
auth: status?.auth,
|
|
1737
|
+
rpcCall,
|
|
1738
|
+
onUpdate: () => load(true),
|
|
1739
|
+
});
|
|
1188
1740
|
} else if (activeTab === 'im') {
|
|
1189
1741
|
// 从 listPlatforms 动态生成平台列表
|
|
1190
1742
|
const IM_PLATFORMS = [
|
|
@@ -1249,11 +1801,116 @@ function BridgePanel({ rpcCall }) {
|
|
|
1249
1801
|
);
|
|
1250
1802
|
}
|
|
1251
1803
|
|
|
1804
|
+
const auth = status?.auth;
|
|
1805
|
+
const policy = auth?.adminPolicy ?? 'password_unlock';
|
|
1806
|
+
const isLocked = !isLocalhost && auth?.enabled && policy !== 'open' && !adminUnlocked;
|
|
1807
|
+
|
|
1808
|
+
// 远程设备被锁定:全局展示锁定页面,阻断所有 Tab 的查看与操作
|
|
1809
|
+
if (isLocked) {
|
|
1810
|
+
return React.createElement('div', { style: { maxWidth: 620 } },
|
|
1811
|
+
policy === 'local_only' ? (
|
|
1812
|
+
React.createElement('div', {
|
|
1813
|
+
style: { ...s.card, textAlign: 'center', padding: '36px 20px', marginTop: 10 },
|
|
1814
|
+
},
|
|
1815
|
+
React.createElement('div', { style: { fontSize: 40, marginBottom: 12 } }, '🛡️'),
|
|
1816
|
+
React.createElement('div', { style: { ...s.label, fontSize: 16, fontWeight: 600, marginBottom: 8 } }, '管理控制台已锁定(仅限电脑本机管理)'),
|
|
1817
|
+
React.createElement('div', { style: { ...s.muted, maxWidth: 420, margin: '0 auto', lineHeight: 1.6, fontSize: 13 } },
|
|
1818
|
+
'当前设备通过远程局域网或公网接入。已开启「仅限电脑本机管理」最高安全策略,远程设备禁止查看与修改任何网络与机器人配置。如需管理请在电脑本机(127.0.0.1)上操作。'
|
|
1819
|
+
),
|
|
1820
|
+
React.createElement('div', { style: { marginTop: 20 } },
|
|
1821
|
+
React.createElement('button', {
|
|
1822
|
+
type: 'button',
|
|
1823
|
+
style: { ...s.btnLink, fontSize: 12, color: 'var(--dsw-alias-label-secondary,#6b7280)' },
|
|
1824
|
+
onClick: () => setShowForgotGuide(v => !v),
|
|
1825
|
+
}, '❓ 远程如何救急解除锁定?'),
|
|
1826
|
+
),
|
|
1827
|
+
showForgotGuide && React.createElement('div', {
|
|
1828
|
+
style: {
|
|
1829
|
+
marginTop: 14, padding: '12px 14px', borderRadius: 8, fontSize: 12, lineHeight: 1.6,
|
|
1830
|
+
background: 'var(--dsw-alias-bg-layer-2,#f3f4f6)', border: '1px solid var(--dsw-alias-border-l2,#e5e7eb)',
|
|
1831
|
+
color: 'var(--dsw-alias-label-secondary,#4b5563)', textAlign: 'left', maxWidth: 420, margin: '14px auto 0',
|
|
1832
|
+
},
|
|
1833
|
+
},
|
|
1834
|
+
React.createElement('div', { style: { fontWeight: 600, color: 'var(--dsw-alias-label-primary,currentColor)', marginBottom: 4 } }, '🛟 救急解除锁定指引:'),
|
|
1835
|
+
React.createElement('div', null, '1. ', React.createElement('strong', null, '电脑本机直连修改'), ':直接在运行本程序的电脑本机打开本控制台(127.0.0.1 享有物理免锁特权),可随时修改策略或清除密码。'),
|
|
1836
|
+
React.createElement('div', { style: { marginTop: 4 } }, '2. ', React.createElement('strong', null, '服务器救急指令'), ':在宿主电脑/服务器终端执行 ', React.createElement('code', { style: s.code }, 'touch ~/.dsh/dsh-bridge/reset-auth'), ' 即可瞬间清空密码恢复初始状态。'),
|
|
1837
|
+
),
|
|
1838
|
+
)
|
|
1839
|
+
) : (
|
|
1840
|
+
React.createElement('div', {
|
|
1841
|
+
style: { ...s.card, maxWidth: 440, margin: '20px auto', padding: '32px 24px' },
|
|
1842
|
+
},
|
|
1843
|
+
React.createElement('div', { style: { textAlign: 'center', marginBottom: 20 } },
|
|
1844
|
+
React.createElement('div', { style: { fontSize: 40, marginBottom: 10 } }, '🔒'),
|
|
1845
|
+
React.createElement('div', { style: { ...s.label, fontSize: 16, fontWeight: 600 } }, '管理控制台已锁定'),
|
|
1846
|
+
React.createElement('div', { style: { ...s.muted, fontSize: 12, marginTop: 6, lineHeight: 1.5 } },
|
|
1847
|
+
'当前设备为远程访问。为保护您的网络与平台配置安全,请输入管理员密码解锁管理权限。'
|
|
1848
|
+
),
|
|
1849
|
+
),
|
|
1850
|
+
React.createElement('form', {
|
|
1851
|
+
onSubmit: handleUnlockAdmin,
|
|
1852
|
+
style: { display: 'flex', flexDirection: 'column', gap: 12 },
|
|
1853
|
+
},
|
|
1854
|
+
React.createElement('input', {
|
|
1855
|
+
type: 'password',
|
|
1856
|
+
style: s.input,
|
|
1857
|
+
placeholder: '输入后台管理密码',
|
|
1858
|
+
value: unlockPassword,
|
|
1859
|
+
onChange: (e) => setUnlockPassword(e.target.value),
|
|
1860
|
+
autoFocus: true,
|
|
1861
|
+
}),
|
|
1862
|
+
unlockErr && React.createElement('div', {
|
|
1863
|
+
style: { fontSize: 12, color: 'var(--dsw-alias-state-error-primary,#dc2626)' },
|
|
1864
|
+
}, unlockErr),
|
|
1865
|
+
React.createElement('button', {
|
|
1866
|
+
type: 'submit',
|
|
1867
|
+
style: { ...s.btnPri, width: '100%', justifyContent: 'center', height: 36, background: '#4f6ef7', color: '#ffffff' },
|
|
1868
|
+
disabled: unlocking,
|
|
1869
|
+
}, unlocking ? '验证中…' : '解锁管理权限'),
|
|
1870
|
+
),
|
|
1871
|
+
React.createElement('div', { style: { marginTop: 16, textAlign: 'center' } },
|
|
1872
|
+
React.createElement('button', {
|
|
1873
|
+
type: 'button',
|
|
1874
|
+
style: { ...s.btnLink, fontSize: 12, color: 'var(--dsw-alias-label-secondary,#6b7280)' },
|
|
1875
|
+
onClick: () => setShowForgotGuide(v => !v),
|
|
1876
|
+
}, '❓ 忘记后台管理密码?'),
|
|
1877
|
+
),
|
|
1878
|
+
showForgotGuide && React.createElement('div', {
|
|
1879
|
+
style: {
|
|
1880
|
+
marginTop: 12, padding: '12px 14px', borderRadius: 8, fontSize: 12, lineHeight: 1.6,
|
|
1881
|
+
background: 'var(--dsw-alias-bg-layer-2,#f3f4f6)', border: '1px solid var(--dsw-alias-border-l2,#e5e7eb)',
|
|
1882
|
+
color: 'var(--dsw-alias-label-secondary,#4b5563)', textAlign: 'left',
|
|
1883
|
+
},
|
|
1884
|
+
},
|
|
1885
|
+
React.createElement('div', { style: { fontWeight: 600, color: 'var(--dsw-alias-label-primary,currentColor)', marginBottom: 4 } }, '🛟 找回与重置密码指引:'),
|
|
1886
|
+
React.createElement('div', null, '1. ', React.createElement('strong', null, '电脑本机直连修改'), ':直接在运行本程序的电脑本机打开本控制台(127.0.0.1 享有物理免锁特权),可随时修改管理密码。'),
|
|
1887
|
+
React.createElement('div', { style: { marginTop: 4 } }, '2. ', React.createElement('strong', null, '服务器救急指令'), ':在宿主电脑终端执行 ', React.createElement('code', { style: s.code }, 'touch ~/.dsh/dsh-bridge/reset-auth'), ' 即可瞬间清空密码恢复初始状态。'),
|
|
1888
|
+
),
|
|
1889
|
+
)
|
|
1890
|
+
)
|
|
1891
|
+
);
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1252
1894
|
return React.createElement('div', { style: { maxWidth: 620 } },
|
|
1253
1895
|
err && React.createElement('div', {
|
|
1254
1896
|
style: { ...s.card, background: 'var(--dsw-alias-state-error-bg,#fef2f2)', color: 'var(--dsw-alias-state-error-primary,#dc2626)', fontSize: 13, marginBottom: 16 },
|
|
1255
1897
|
}, err),
|
|
1256
1898
|
|
|
1899
|
+
!isLocalhost && adminUnlocked && React.createElement('div', {
|
|
1900
|
+
style: {
|
|
1901
|
+
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
|
1902
|
+
padding: '8px 14px', background: 'var(--dsw-alias-state-info-bg,#eff6ff)',
|
|
1903
|
+
border: '1px solid var(--dsw-alias-brand-primary,#4f6ef7)', borderRadius: 8,
|
|
1904
|
+
marginBottom: 14, fontSize: 12, color: 'var(--dsw-alias-brand-primary,#4f6ef7)',
|
|
1905
|
+
},
|
|
1906
|
+
},
|
|
1907
|
+
React.createElement('span', null, '🔓 管理员权限已解锁(当前临时会话有效)'),
|
|
1908
|
+
React.createElement('button', {
|
|
1909
|
+
style: { ...s.btnGhost, height: 24, fontSize: 11, padding: '0 8px' },
|
|
1910
|
+
onClick: () => setAdminUnlocked(false),
|
|
1911
|
+
}, '🔒 重新锁定后台'),
|
|
1912
|
+
),
|
|
1913
|
+
|
|
1257
1914
|
React.createElement(VersionBanner, { rpcCall }),
|
|
1258
1915
|
|
|
1259
1916
|
React.createElement(TabBar, { active: activeTab, onChange: setActiveTab, dots }),
|