@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/client.js CHANGED
@@ -1085,6 +1085,7 @@ var GITHUB_URL = "https://github.com/wenbin-wb/dsh-bridge";
1085
1085
  var RELEASES_URL = "https://github.com/wenbin-wb/dsh-bridge/releases";
1086
1086
  var ISSUES_URL = "https://github.com/wenbin-wb/dsh-bridge/issues/new";
1087
1087
  var TUNNEL_DOCS_URL = "https://github.com/wenbin-wb/dsh-bridge/blob/main/docs/custom-tunnel.md";
1088
+ var CLOUDFLARE_TUTORIAL_URL = "https://github.com/wenbin-wb/dsh-bridge/blob/main/docs/cloudflare-fixed-domain.md";
1088
1089
  function upgradeCommands(latest) {
1089
1090
  const spec = `@wenbin_wb/dsh-bridge@${latest}`;
1090
1091
  return [
@@ -1222,14 +1223,14 @@ function StatusTag({ running, status }) {
1222
1223
  bg = "var(--dsw-alias-state-success-bg,#ecfdf5)";
1223
1224
  color = "var(--dsw-alias-state-success-primary,#059669)";
1224
1225
  text = "\u5DF2\u8FDE\u63A5";
1225
- } else if (status === "starting") {
1226
+ } else if (status === "starting" || status === "connecting" || status === "downloading") {
1226
1227
  bg = "var(--dsw-alias-state-info-bg,#eff6ff)";
1227
1228
  color = "var(--dsw-alias-state-info-primary,#3b82f6)";
1228
- text = "\u8FDE\u63A5\u4E2D\u2026";
1229
+ text = status === "downloading" ? "\u4E0B\u8F7D\u4E2D\u2026" : "\u8FDE\u63A5\u4E2D\u2026";
1229
1230
  } else if (status === "reconnecting") {
1230
1231
  bg = "var(--dsw-alias-state-warn-bg,#fffbeb)";
1231
1232
  color = "var(--dsw-alias-state-warn-primary,#d97706)";
1232
- text = "\u91CD\u8FDE\u4E2D\u2026";
1233
+ text = "\u81EA\u52A8\u91CD\u8FDE\u4E2D\u2026";
1233
1234
  } else if (status === "paused") {
1234
1235
  bg = "var(--dsw-alias-state-warn-bg,#fffbeb)";
1235
1236
  color = "var(--dsw-alias-state-warn-primary,#d97706)";
@@ -1445,6 +1446,185 @@ var CustomTunnelGuide = React.memo(function CustomTunnelGuide2() {
1445
1446
  }, "\u67E5\u770B\u81EA\u5EFA\u96A7\u9053\u670D\u52A1\u5668\u642D\u5EFA\u6559\u7A0B")
1446
1447
  );
1447
1448
  });
1449
+ var TunnelEntryCard = React.memo(function TunnelEntryCard2({
1450
+ entry,
1451
+ onCopy,
1452
+ copied,
1453
+ autoStart,
1454
+ onToggleAutoStart,
1455
+ onStart,
1456
+ onStop,
1457
+ onReset
1458
+ }) {
1459
+ const [showQr, setShowQr] = React.useState(false);
1460
+ const hasUrl = Boolean(entry && entry.url);
1461
+ const active = Boolean(entry && entry.running);
1462
+ return React.createElement(
1463
+ "div",
1464
+ {
1465
+ style: {
1466
+ ...s.card,
1467
+ borderColor: active ? "var(--dsw-alias-state-success-border,#a7f3d0)" : void 0,
1468
+ background: active ? "linear-gradient(180deg, var(--dsw-alias-bg-layer-2,#f9fafb), var(--dsw-alias-bg-layer-1,#ffffff))" : void 0
1469
+ }
1470
+ },
1471
+ React.createElement(
1472
+ "div",
1473
+ { style: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 10 } },
1474
+ React.createElement(
1475
+ "div",
1476
+ { style: { flex: "1 1 auto", minWidth: 0 } },
1477
+ React.createElement(
1478
+ "div",
1479
+ { style: { display: "flex", alignItems: "center", gap: 8 } },
1480
+ React.createElement("span", { style: { fontSize: 16 } }, "\u{1F310}"),
1481
+ React.createElement("div", { style: s.label }, "\u516C\u7F51\u8BBF\u95EE\u5165\u53E3")
1482
+ ),
1483
+ React.createElement(
1484
+ "div",
1485
+ { style: { ...s.muted, marginTop: 4 } },
1486
+ entry ? entry.title + " \xB7 " + entry.desc : "\u5C1A\u672A\u914D\u7F6E\u4EFB\u4F55\u516C\u7F51\u96A7\u9053\uFF0C\u53EF\u5728\u4E0B\u65B9\u5F00\u542F Cloudflare \u96A7\u9053\u6216\u914D\u7F6E\u81EA\u5EFA\u96A7\u9053"
1487
+ )
1488
+ ),
1489
+ React.createElement(StatusTag, {
1490
+ running: active,
1491
+ status: entry && entry.phase && entry.phase !== "ready" ? entry.phase : void 0
1492
+ })
1493
+ ),
1494
+ React.createElement(
1495
+ "div",
1496
+ { style: { ...s.block, display: "flex", flexDirection: "column", gap: 10 } },
1497
+ // 有地址:大号 URL + 复制
1498
+ hasUrl && React.createElement(
1499
+ "div",
1500
+ {
1501
+ style: {
1502
+ padding: "10px 12px",
1503
+ background: "var(--dsw-alias-bg-layer-1,#ffffff)",
1504
+ border: "1px solid var(--dsw-alias-border-l2,#e5e7eb)",
1505
+ borderRadius: 10,
1506
+ display: "flex",
1507
+ alignItems: "center",
1508
+ gap: 10
1509
+ }
1510
+ },
1511
+ React.createElement("code", {
1512
+ style: { ...s.code, flex: "1 1 auto", fontSize: 13.5, wordBreak: "break-all", lineHeight: 1.5 }
1513
+ }, entry.url),
1514
+ React.createElement("button", {
1515
+ style: { ...s.btnGhost, flexShrink: 0, height: 28, padding: "0 12px", fontSize: 12 },
1516
+ onClick: () => onCopy && onCopy(entry.url)
1517
+ }, copied ? "\u2713 \u5DF2\u590D\u5236" : "\u590D\u5236")
1518
+ ),
1519
+ // 状态细节(重连/错误/连接中)——无 URL 时格外重要,让用户知道隧道在自愈而非消失
1520
+ entry && entry.stateDetail && React.createElement("div", {
1521
+ style: {
1522
+ fontSize: 12,
1523
+ lineHeight: 1.5,
1524
+ color: entry.phase === "error" ? "var(--dsw-alias-state-error-primary,#dc2626)" : entry.phase === "reconnecting" ? "var(--dsw-alias-state-warn-primary,#d97706)" : "var(--dsw-alias-label-secondary,#6b7280)"
1525
+ }
1526
+ }, entry.stateDetail),
1527
+ // 操作行:running → 关闭/停止重连(+ 重置);!running → 开启
1528
+ active && onStop && React.createElement(
1529
+ "div",
1530
+ { style: { display: "flex", gap: 8, flexWrap: "wrap" } },
1531
+ React.createElement("button", {
1532
+ style: s.btnGhost,
1533
+ onClick: onStop
1534
+ }, entry && entry.phase === "reconnecting" ? "\u505C\u6B62\u91CD\u8FDE" : "\u5173\u95ED"),
1535
+ onReset && React.createElement("button", {
1536
+ style: { ...s.btnGhost, height: 28, padding: "0 12px", fontSize: 12 },
1537
+ onClick: onReset,
1538
+ title: "\u5173\u95ED\u5E76\u91CD\u65B0\u5F00\u542F\uFF0C\u66F4\u6362\u4E34\u65F6\u5730\u5740"
1539
+ }, "\u{1F504} \u91CD\u7F6E\u94FE\u63A5")
1540
+ ),
1541
+ // running 但无 onStop 的入口(如外部登记):只给重置/二维码辅助,无开关
1542
+ active && !onStop && onReset && React.createElement(
1543
+ "div",
1544
+ { style: { display: "flex", gap: 8, flexWrap: "wrap" } },
1545
+ React.createElement("button", {
1546
+ style: { ...s.btnGhost, height: 28, padding: "0 12px", fontSize: 12 },
1547
+ onClick: onReset,
1548
+ title: "\u5173\u95ED\u5E76\u91CD\u65B0\u5F00\u542F\uFF0C\u66F4\u6362\u4E34\u65F6\u5730\u5740"
1549
+ }, "\u{1F504} \u91CD\u7F6E\u94FE\u63A5")
1550
+ ),
1551
+ // 未运行:引导开启(连接中/下载中禁用)
1552
+ !active && onStart && React.createElement("button", {
1553
+ style: {
1554
+ ...s.btnPri,
1555
+ alignSelf: "flex-start",
1556
+ opacity: entry && entry.configured === false ? 0.4 : 1,
1557
+ background: entry && entry.phase === "connecting" ? "var(--dsw-alias-state-info-primary,#3b82f6)" : void 0
1558
+ },
1559
+ onClick: onStart,
1560
+ disabled: Boolean(entry && entry.configured === false || entry && (entry.phase === "connecting" || entry.phase === "downloading")),
1561
+ title: entry && entry.configured === false ? "\u8BF7\u5148\u5728\u300C\u96A7\u9053\u914D\u7F6E\u300D\u4E2D\u4FDD\u5B58\u670D\u52A1\u5668\u914D\u7F6E" : ""
1562
+ }, entry && entry.phase === "connecting" ? "\u8FDE\u63A5\u4E2D\u2026" : entry && entry.phase === "downloading" ? "\u4E0B\u8F7D\u4E2D\u2026" : "\u5F00\u542F\u516C\u7F51\u96A7\u9053"),
1563
+ // 二维码辅助按钮:有地址未运行时也可查看(外部登记等)
1564
+ hasUrl && entry.qr && React.createElement(
1565
+ "div",
1566
+ { style: { display: "flex", gap: 8, flexWrap: "wrap" } },
1567
+ React.createElement("button", {
1568
+ style: { ...s.btnGhost, height: 28, padding: "0 12px", fontSize: 12 },
1569
+ onClick: () => setShowQr((v) => !v)
1570
+ }, showQr ? "\u9690\u85CF\u4E8C\u7EF4\u7801" : "\u663E\u793A\u4E8C\u7EF4\u7801")
1571
+ ),
1572
+ showQr && hasUrl && entry.qr && React.createElement(
1573
+ "div",
1574
+ { style: { display: "flex", flexDirection: "column", alignItems: "flex-start", gap: 6 } },
1575
+ React.createElement("img", { src: entry.qr, alt: "QR", style: { ...s.qr, margin: 0 } }),
1576
+ React.createElement("div", { style: { ...s.muted, fontSize: 11 } }, "\u8BF7\u5728\u79C1\u5BC6\u73AF\u5883\u4E0B\u626B\u7801\u4F7F\u7528")
1577
+ )
1578
+ ),
1579
+ onToggleAutoStart && React.createElement(
1580
+ "label",
1581
+ {
1582
+ style: {
1583
+ display: "flex",
1584
+ alignItems: "center",
1585
+ gap: 6,
1586
+ marginTop: 12,
1587
+ paddingTop: 10,
1588
+ borderTop: "1px solid var(--dsw-alias-border-l2,#e5e7eb)",
1589
+ fontSize: 12,
1590
+ color: "var(--dsw-alias-label-secondary,#6b7280)",
1591
+ cursor: "pointer",
1592
+ userSelect: "none"
1593
+ },
1594
+ title: "DSH \u542F\u52A8\u65F6\u81EA\u52A8\u6062\u590D\u8BE5\u96A7\u9053\u7684\u8FD0\u884C\u72B6\u6001"
1595
+ },
1596
+ React.createElement("input", {
1597
+ type: "checkbox",
1598
+ checked: Boolean(autoStart),
1599
+ onChange: (e) => onToggleAutoStart(e.target.checked)
1600
+ }),
1601
+ React.createElement("span", null, "\u968F DSH \u542F\u52A8\u81EA\u52A8\u5F00\u542F")
1602
+ )
1603
+ );
1604
+ });
1605
+ var TunnelConfigGroup = React.memo(function TunnelConfigGroup2({ children }) {
1606
+ const [open, setOpen] = React.useState(false);
1607
+ return React.createElement(
1608
+ "div",
1609
+ { style: s.card },
1610
+ React.createElement(
1611
+ "div",
1612
+ {
1613
+ style: { display: "flex", alignItems: "center", justifyContent: "space-between", cursor: "pointer", userSelect: "none", gap: 8 },
1614
+ onClick: () => setOpen((v) => !v)
1615
+ },
1616
+ React.createElement(
1617
+ "div",
1618
+ { style: { display: "flex", alignItems: "center", gap: 8 } },
1619
+ React.createElement("span", { style: { fontSize: 14 } }, "\u2699\uFE0F"),
1620
+ React.createElement("div", { style: s.label }, "\u96A7\u9053\u914D\u7F6E"),
1621
+ React.createElement("span", { style: { ...s.muted, fontSize: 11 } }, "Token \xB7 \u56FA\u5B9A\u57DF\u540D \xB7 \u81EA\u5EFA\u670D\u52A1\u5668 \xB7 \u5916\u90E8\u767B\u8BB0")
1622
+ ),
1623
+ React.createElement("span", { style: { fontSize: 12, color: "var(--dsw-alias-label-tertiary,#9ca3af)", flexShrink: 0 } }, open ? "\u6536\u8D77 \u25B4" : "\u5C55\u5F00 \u25BE")
1624
+ ),
1625
+ open && React.createElement("div", { style: { marginTop: 4 } }, children)
1626
+ );
1627
+ });
1448
1628
  var CustomTunnelConfigForm = React.memo(function CustomTunnelConfigForm2({ serverUrl: initUrl, accessToken: initToken, onSave }) {
1449
1629
  const [serverUrl, setServerUrl] = React.useState(initUrl ?? "");
1450
1630
  const [accessToken, setAccessToken] = React.useState(initToken ?? "");
@@ -1561,7 +1741,7 @@ var TunnelCard = React.memo(function TunnelCard2({
1561
1741
  React.createElement(
1562
1742
  "div",
1563
1743
  { style: { display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 6 } },
1564
- React.createElement(StatusTag, { running }),
1744
+ React.createElement(StatusTag, { running, status: phase === "ready" ? void 0 : phase }),
1565
1745
  onToggleAutoStart && React.createElement(
1566
1746
  "label",
1567
1747
  {
@@ -1590,7 +1770,7 @@ var TunnelCard = React.memo(function TunnelCard2({
1590
1770
  style: {
1591
1771
  ...s.block,
1592
1772
  fontSize: 12,
1593
- color: phase === "error" ? "var(--dsw-alias-state-error-primary,#dc2626)" : "var(--dsw-alias-label-secondary,#6b7280)"
1773
+ color: phase === "error" ? "var(--dsw-alias-state-error-primary,#dc2626)" : phase === "reconnecting" ? "var(--dsw-alias-state-warn-primary,#d97706)" : "var(--dsw-alias-label-secondary,#6b7280)"
1594
1774
  }
1595
1775
  }, state?.detail ?? phase),
1596
1776
  url && React.createElement(QrBlock, { url, qr, onReset, auth, onNavigateSecurity }),
@@ -1605,7 +1785,10 @@ var TunnelCard = React.memo(function TunnelCard2({
1605
1785
  disabled: configured === false || phase === "connecting" || phase === "downloading",
1606
1786
  title: configured === false ? "\u8BF7\u5148\u4FDD\u5B58\u670D\u52A1\u5668\u914D\u7F6E" : ""
1607
1787
  }, phase === "connecting" ? "\u8FDE\u63A5\u4E2D\u2026" : phase === "downloading" ? "\u4E0B\u8F7D\u4E2D\u2026" : "\u5F00\u542F"),
1608
- running && onStop && React.createElement("button", { style: s.btnGhost, onClick: onStop }, "\u5173\u95ED")
1788
+ running && onStop && React.createElement("button", {
1789
+ style: s.btnGhost,
1790
+ onClick: onStop
1791
+ }, phase === "reconnecting" ? "\u505C\u6B62\u91CD\u8FDE" : "\u5173\u95ED")
1609
1792
  )
1610
1793
  );
1611
1794
  });
@@ -1667,6 +1850,12 @@ var CloudflareConfigForm = React.memo(function CloudflareConfigForm2({ token, ho
1667
1850
  { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary, #6b7280)", marginBottom: 8, lineHeight: 1.5 } },
1668
1851
  "\u5728 Cloudflare Zero Trust \u63A7\u5236\u53F0\u521B\u5EFA Tunnel \u5373\u53EF\u83B7\u53D6\u4E13\u5C5E Token \u5E76\u7ED1\u5B9A\u81EA\u5DF1\u7684\u57DF\u540D\uFF08\u5982 dsh.yourname.com\uFF09\uFF0C\u6BCF\u6B21\u91CD\u542F URL \u6C38\u4E0D\u53D8\u66F4\u3002\u4E0D\u586B\u5219\u4F7F\u7528\u9ED8\u8BA4\u514D\u767B\u5F55\u4E34\u65F6\u968F\u673A\u57DF\u540D\u3002"
1669
1852
  ),
1853
+ React.createElement("a", {
1854
+ href: CLOUDFLARE_TUTORIAL_URL,
1855
+ target: "_blank",
1856
+ rel: "noreferrer",
1857
+ style: { ...s.btnLink, marginBottom: 10 }
1858
+ }, "\u{1F4D6} \u67E5\u770B\u300CCloudflare \u56FA\u5B9A\u57DF\u540D\u300D\u7533\u8BF7\u4E0E\u914D\u7F6E\u6559\u7A0B"),
1670
1859
  React.createElement(
1671
1860
  "div",
1672
1861
  { style: { marginBottom: 8 } },
@@ -3876,6 +4065,18 @@ function BridgePanel({ rpcCall }) {
3876
4065
  const [activeTab, setActiveTab] = React.useState("lan");
3877
4066
  const [platforms, setPlatforms] = React.useState(null);
3878
4067
  const [selectedPlatform, setSelectedPlatform] = React.useState("wechat");
4068
+ const [copiedUrl, setCopiedUrl] = React.useState("");
4069
+ const copyEntryUrl = React.useCallback((url) => {
4070
+ const done = () => {
4071
+ setCopiedUrl(url);
4072
+ setTimeout(() => setCopiedUrl(""), 2e3);
4073
+ };
4074
+ if (navigator.clipboard?.writeText) {
4075
+ navigator.clipboard.writeText(url).then(done).catch(() => done());
4076
+ } else {
4077
+ done();
4078
+ }
4079
+ }, []);
3879
4080
  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"));
3880
4081
  const [adminUnlocked, setAdminUnlocked] = React.useState(false);
3881
4082
  const [unlockPassword, setUnlockPassword] = React.useState("");
@@ -4098,62 +4299,122 @@ function BridgePanel({ rpcCall }) {
4098
4299
  );
4099
4300
  } else if (activeTab === "tunnel") {
4100
4301
  const ext = status?.externalTunnel;
4302
+ const cf = status?.cloudflared;
4303
+ const cfDesc = cf?.tokenConfigured ? "\u56FA\u5B9A\u57DF\u540D\u6A21\u5F0F" : "\u514D\u767B\u5F55\u4E34\u65F6\u57DF\u540D";
4304
+ const entries = [
4305
+ ct && ct.running && {
4306
+ key: "custom",
4307
+ title: "\u81EA\u5EFA\u96A7\u9053",
4308
+ desc: "VPS \u81EA\u5EFA \xB7 \u56FA\u5B9A\u5730\u5740",
4309
+ url: ct.url || null,
4310
+ qr: ct.qr,
4311
+ running: true,
4312
+ phase: ct.state && ct.state.phase,
4313
+ stateDetail: ct.state && ct.state.detail,
4314
+ autoStart: ct.autoStart,
4315
+ onToggleAutoStart: onToggleCustomAutoStart,
4316
+ onStart: onStartCustom,
4317
+ onStop: onStopCustom
4318
+ },
4319
+ cf && cf.running && {
4320
+ key: "cloudflared",
4321
+ title: "Cloudflare \u96A7\u9053",
4322
+ desc: cfDesc,
4323
+ url: cf.url || null,
4324
+ qr: cf.qr,
4325
+ running: true,
4326
+ phase: cf.state && cf.state.phase,
4327
+ stateDetail: cf.state && cf.state.detail,
4328
+ autoStart: cf.autoStart,
4329
+ onToggleAutoStart: onToggleCloudflaredAutoStart,
4330
+ onStart: onStartCloudflared,
4331
+ onStop: onStopCloudflared,
4332
+ onReset: onResetCloudflared
4333
+ },
4334
+ ext && ext.configured && ext.url && {
4335
+ key: "external",
4336
+ title: "\u5916\u90E8\u5DF2\u90E8\u7F72\u96A7\u9053",
4337
+ desc: "\u81EA\u884C\u90E8\u7F72\u767B\u8BB0",
4338
+ url: ext.url,
4339
+ qr: ext.qr,
4340
+ running: true
4341
+ }
4342
+ ].filter(Boolean);
4343
+ const primary = entries[0] || null;
4344
+ const otherCount = primary ? entries.length - 1 : entries.length;
4101
4345
  tabContent = React.createElement(
4102
4346
  React.Fragment,
4103
4347
  null,
4104
- React.createElement(
4105
- TunnelCard,
4106
- {
4107
- title: "Cloudflare \u96A7\u9053",
4108
- desc: status?.cloudflared?.tokenConfigured ? "\u56FA\u5B9A\u57DF\u540D\u6A21\u5F0F\uFF08Token \u8FD0\u884C \xB7 \u91CD\u542F URL \u4FDD\u6301\u4E0D\u53D8\uFF09" : "\u4E00\u952E\u83B7\u53D6\u516C\u7F51\u5730\u5740\uFF08\u514D\u767B\u5F55\u4E34\u65F6\u968F\u673A\u57DF\u540D\uFF09",
4109
- data: {
4110
- running: status?.cloudflared?.running,
4111
- url: status?.cloudflared?.url,
4112
- qr: status?.cloudflared?.qr,
4113
- state: status?.cloudflared?.state
4114
- },
4115
- autoStart: status?.cloudflared?.autoStart,
4116
- onToggleAutoStart: onToggleCloudflaredAutoStart,
4117
- auth: status?.auth,
4118
- onNavigateSecurity: navSecurity,
4119
- onStart: onStartCloudflared,
4120
- onStop: onStopCloudflared,
4121
- onReset: status?.cloudflared?.running ? onResetCloudflared : null
4122
- },
4123
- React.createElement(CloudflareConfigForm, {
4124
- token: status?.cloudflared?.token ?? "",
4125
- hostname: status?.cloudflared?.hostname ?? "",
4126
- onSave: saveCloudflaredConfig
4127
- })
4128
- ),
4129
- React.createElement(ExternalTunnelCard, {
4130
- ext,
4131
- onSave: saveExternalTunnel
4348
+ React.createElement(TunnelEntryCard, {
4349
+ entry: primary,
4350
+ onCopy: copyEntryUrl,
4351
+ copied: Boolean(copiedUrl && primary && copiedUrl === primary.url),
4352
+ autoStart: primary ? primary.autoStart : void 0,
4353
+ onToggleAutoStart: primary ? primary.onToggleAutoStart : void 0,
4354
+ onStart: primary ? primary.onStart : cf ? onStartCloudflared : null,
4355
+ onStop: primary ? primary.onStop : void 0,
4356
+ onReset: primary ? primary.onReset : void 0
4132
4357
  }),
4358
+ otherCount > 0 && React.createElement("div", {
4359
+ style: { ...s.muted, fontSize: 11, marginBottom: 8, textAlign: "center" }
4360
+ }, "\u53E6\u6709 " + otherCount + " \u4E2A\u96A7\u9053\u5165\u53E3\u5728\u8FD0\u884C\uFF0C\u53EF\u5728\u4E0B\u65B9\u300C\u96A7\u9053\u914D\u7F6E\u300D\u4E2D\u67E5\u770B\u4E0E\u7BA1\u7406"),
4133
4361
  React.createElement(
4134
- TunnelCard,
4135
- {
4136
- title: "\u81EA\u5EFA\u96A7\u9053",
4137
- desc: "\u8FDE\u63A5\u81EA\u5DF1\u90E8\u7F72\u7684\u96A7\u9053\u670D\u52A1\u5668\uFF0C\u83B7\u5F97\u56FA\u5B9A\u57DF\u540D",
4138
- data: {
4139
- configured: ct?.configured,
4140
- running: ct?.running,
4141
- url: ct?.url,
4142
- qr: ct?.qr,
4143
- state: ct?.state
4362
+ TunnelConfigGroup,
4363
+ null,
4364
+ React.createElement(
4365
+ TunnelCard,
4366
+ {
4367
+ title: "Cloudflare \u96A7\u9053",
4368
+ desc: cf && cf.tokenConfigured ? "\u56FA\u5B9A\u57DF\u540D\u6A21\u5F0F\uFF08Token \u8FD0\u884C \xB7 \u91CD\u542F URL \u4FDD\u6301\u4E0D\u53D8\uFF09" : "\u4E00\u952E\u83B7\u53D6\u516C\u7F51\u5730\u5740\uFF08\u514D\u767B\u5F55\u4E34\u65F6\u968F\u673A\u57DF\u540D\uFF09",
4369
+ data: {
4370
+ running: cf && cf.running,
4371
+ url: cf && cf.url,
4372
+ qr: cf && cf.qr,
4373
+ state: cf && cf.state
4374
+ },
4375
+ autoStart: cf && cf.autoStart,
4376
+ onToggleAutoStart: onToggleCloudflaredAutoStart,
4377
+ auth: status && status.auth,
4378
+ onNavigateSecurity: navSecurity,
4379
+ onStart: onStartCloudflared,
4380
+ onStop: onStopCloudflared,
4381
+ onReset: cf && cf.running ? onResetCloudflared : null
4144
4382
  },
4145
- autoStart: ct?.autoStart,
4146
- onToggleAutoStart: onToggleCustomAutoStart,
4147
- auth: status?.auth,
4148
- onNavigateSecurity: navSecurity,
4149
- onStart: onStartCustom,
4150
- onStop: onStopCustom
4151
- },
4152
- React.createElement(CustomTunnelGuide),
4153
- React.createElement(CustomTunnelConfigForm, {
4154
- serverUrl: ct?.serverUrl ?? "",
4155
- accessToken: ct?.accessToken ?? "",
4156
- onSave: saveConfig
4383
+ React.createElement(CloudflareConfigForm, {
4384
+ token: cf && cf.token || "",
4385
+ hostname: cf && cf.hostname || "",
4386
+ onSave: saveCloudflaredConfig
4387
+ })
4388
+ ),
4389
+ React.createElement(
4390
+ TunnelCard,
4391
+ {
4392
+ title: "\u81EA\u5EFA\u96A7\u9053",
4393
+ desc: "\u8FDE\u63A5\u81EA\u5DF1\u90E8\u7F72\u7684\u96A7\u9053\u670D\u52A1\u5668\uFF0C\u83B7\u5F97\u56FA\u5B9A\u57DF\u540D",
4394
+ data: {
4395
+ configured: ct && ct.configured,
4396
+ running: ct && ct.running,
4397
+ url: ct && ct.url,
4398
+ qr: ct && ct.qr,
4399
+ state: ct && ct.state
4400
+ },
4401
+ autoStart: ct && ct.autoStart,
4402
+ onToggleAutoStart: onToggleCustomAutoStart,
4403
+ auth: status && status.auth,
4404
+ onNavigateSecurity: navSecurity,
4405
+ onStart: onStartCustom,
4406
+ onStop: onStopCustom
4407
+ },
4408
+ React.createElement(CustomTunnelGuide),
4409
+ React.createElement(CustomTunnelConfigForm, {
4410
+ serverUrl: ct && ct.serverUrl || "",
4411
+ accessToken: ct && ct.accessToken || "",
4412
+ onSave: saveConfig
4413
+ })
4414
+ ),
4415
+ React.createElement(ExternalTunnelCard, {
4416
+ ext,
4417
+ onSave: saveExternalTunnel
4157
4418
  })
4158
4419
  )
4159
4420
  );
@@ -4249,6 +4510,9 @@ function BridgePanel({ rpcCall }) {
4249
4510
  const auth = status?.auth;
4250
4511
  const policy = auth?.adminPolicy ?? "password_unlock";
4251
4512
  const hasAnyPassword = !!(auth?.hasPassword || auth?.hasAdminPassword);
4513
+ const unlockUsesAdmin = !!auth?.hasAdminPassword;
4514
+ const unlockPwdKind = unlockUsesAdmin ? "\u7BA1\u7406\u5BC6\u7801" : "\u8BBF\u95EE\u5BC6\u7801";
4515
+ const unlockPwdHint = unlockUsesAdmin ? "\u8BF7\u8F93\u5165\u540E\u53F0\u7BA1\u7406\u5BC6\u7801\u89E3\u9501\u7BA1\u7406\u6743\u9650\u3002" : "\u5F53\u524D\u672A\u8BBE\u7F6E\u72EC\u7ACB\u7BA1\u7406\u5BC6\u7801\uFF0C\u8F93\u5165\u60A8\u7684\u8BBF\u95EE\u5BC6\u7801\u5373\u53EF\u89E3\u9501\u3002";
4252
4516
  const isLocked = !isLocalhost && auth?.adminProtection !== false && policy !== "open" && !adminUnlocked && (policy === "local_only" || hasAnyPassword);
4253
4517
  if (isLocked) {
4254
4518
  return React.createElement(
@@ -4306,10 +4570,42 @@ function BridgePanel({ rpcCall }) {
4306
4570
  { style: { textAlign: "center", marginBottom: 20 } },
4307
4571
  React.createElement("div", { style: { fontSize: 40, marginBottom: 10 } }, "\u{1F512}"),
4308
4572
  React.createElement("div", { style: { ...s.label, fontSize: 16, fontWeight: 600 } }, "\u7BA1\u7406\u63A7\u5236\u53F0\u5DF2\u9501\u5B9A"),
4573
+ // 醒目提示该输入哪种密码
4574
+ React.createElement(
4575
+ "div",
4576
+ { style: { marginTop: 10, display: "flex", justifyContent: "center" } },
4577
+ unlockUsesAdmin ? React.createElement("span", {
4578
+ style: {
4579
+ display: "inline-flex",
4580
+ alignItems: "center",
4581
+ gap: 5,
4582
+ padding: "4px 12px",
4583
+ borderRadius: 999,
4584
+ fontSize: 12,
4585
+ fontWeight: 600,
4586
+ background: "var(--dsw-alias-state-info-bg,#eff6ff)",
4587
+ color: "var(--dsw-alias-state-info-primary,#2563eb)",
4588
+ border: "1px solid var(--dsw-alias-state-info-border,#bfdbfe)"
4589
+ }
4590
+ }, "\u{1F511} \u4F7F\u7528\u7BA1\u7406\u5BC6\u7801\u89E3\u9501") : React.createElement("span", {
4591
+ style: {
4592
+ display: "inline-flex",
4593
+ alignItems: "center",
4594
+ gap: 5,
4595
+ padding: "4px 12px",
4596
+ borderRadius: 999,
4597
+ fontSize: 12,
4598
+ fontWeight: 600,
4599
+ background: "var(--dsw-alias-state-success-bg,#ecfdf5)",
4600
+ color: "var(--dsw-alias-state-success-primary,#059669)",
4601
+ border: "1px solid var(--dsw-alias-state-success-border,#a7f3d0)"
4602
+ }
4603
+ }, "\u{1F510} \u4F7F\u7528\u8BBF\u95EE\u5BC6\u7801\u89E3\u9501")
4604
+ ),
4309
4605
  React.createElement(
4310
4606
  "div",
4311
- { style: { ...s.muted, fontSize: 12, marginTop: 6, lineHeight: 1.5 } },
4312
- "\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"
4607
+ { style: { ...s.muted, fontSize: 12, marginTop: 10, lineHeight: 1.6 } },
4608
+ "\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" + unlockPwdHint
4313
4609
  )
4314
4610
  ),
4315
4611
  React.createElement(
@@ -4321,7 +4617,7 @@ function BridgePanel({ rpcCall }) {
4321
4617
  React.createElement("input", {
4322
4618
  type: "password",
4323
4619
  style: s.input,
4324
- placeholder: "\u8F93\u5165\u540E\u53F0\u7BA1\u7406\u5BC6\u7801",
4620
+ placeholder: "\u8F93\u5165" + unlockPwdKind,
4325
4621
  value: unlockPassword,
4326
4622
  onChange: (e) => setUnlockPassword(e.target.value),
4327
4623
  autoFocus: true
@@ -4342,7 +4638,7 @@ function BridgePanel({ rpcCall }) {
4342
4638
  type: "button",
4343
4639
  style: { ...s.btnLink, fontSize: 12, color: "var(--dsw-alias-label-secondary,#6b7280)" },
4344
4640
  onClick: () => setShowForgotGuide((v) => !v)
4345
- }, "\u2753 \u5FD8\u8BB0\u540E\u53F0\u7BA1\u7406\u5BC6\u7801\uFF1F")
4641
+ }, "\u2753 \u5FD8\u8BB0" + unlockPwdKind + "\uFF1F")
4346
4642
  ),
4347
4643
  showForgotGuide && React.createElement(
4348
4644
  "div",
@@ -4359,8 +4655,8 @@ function BridgePanel({ rpcCall }) {
4359
4655
  textAlign: "left"
4360
4656
  }
4361
4657
  },
4362
- 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"),
4363
- 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"),
4658
+ React.createElement("div", { style: { fontWeight: 600, color: "var(--dsw-alias-label-primary,currentColor)", marginBottom: 4 } }, "\u{1F6DF} \u627E\u56DE\u4E0E\u91CD\u7F6E" + unlockPwdKind + "\u6307\u5F15\uFF1A"),
4659
+ 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\u6216\u6E05\u9664\u5BC6\u7801\u3002"),
4364
4660
  React.createElement("div", { style: { marginTop: 4 } }, "2. ", React.createElement("strong", null, "\u670D\u52A1\u5668 / \u65E0\u5934\u73AF\u5883"), "\uFF1A\u6551\u6025\u91CD\u7F6E\u6B65\u9AA4\u53C2\u89C1 GitHub README \u7684\u300C\u4E09\u91CD\u5BB9\u707E\u4FDD\u547D\u4F53\u7CFB\u300D\u7AE0\u8282\u3002")
4365
4661
  )
4366
4662
  )
@@ -4395,7 +4691,7 @@ function BridgePanel({ rpcCall }) {
4395
4691
  setUnlockErr(err);
4396
4692
  setShowUnlockModal(true);
4397
4693
  }
4398
- }, "\u{1F511} \u7ACB\u5373\u8F93\u5165\u7BA1\u7406\u5BC6\u7801\u89E3\u9501")
4694
+ }, "\u{1F511} \u7ACB\u5373\u8F93\u5165" + unlockPwdKind + "\u89E3\u9501")
4399
4695
  ),
4400
4696
  // 管理员解锁状态提示条
4401
4697
  !isLocalhost && adminUnlocked && React.createElement(
@@ -4503,7 +4799,7 @@ function BridgePanel({ rpcCall }) {
4503
4799
  React.createElement(
4504
4800
  "div",
4505
4801
  { style: { fontSize: 13, color: "var(--dsw-alias-label-secondary,#4b5563)", marginBottom: 16, lineHeight: 1.5 } },
4506
- "\u5F53\u524D\u64CD\u4F5C\u9700\u8981\u540E\u53F0\u7BA1\u7406\u5458\u6743\u9650\u3002\u4E3A\u4FDD\u62A4\u60A8\u7684\u7F51\u7EDC\u914D\u7F6E\u4E0E\u673A\u5668\u4EBA\u5E73\u53F0\u5B89\u5168\uFF0C\u8BF7\u8F93\u5165\u7BA1\u7406\u5BC6\u7801\u89E3\u9501\uFF1A"
4802
+ unlockUsesAdmin ? "\u5F53\u524D\u64CD\u4F5C\u9700\u8981\u540E\u53F0\u7BA1\u7406\u5458\u6743\u9650\u3002\u4E3A\u4FDD\u62A4\u60A8\u7684\u7F51\u7EDC\u914D\u7F6E\u4E0E\u673A\u5668\u4EBA\u5E73\u53F0\u5B89\u5168\uFF0C\u8BF7\u8F93\u5165\u7BA1\u7406\u5BC6\u7801\u89E3\u9501\uFF1A" : "\u5F53\u524D\u64CD\u4F5C\u9700\u8981\u540E\u53F0\u7BA1\u7406\u6743\u9650\u3002\u672A\u8BBE\u7F6E\u72EC\u7ACB\u7BA1\u7406\u5BC6\u7801\uFF0C\u8F93\u5165\u60A8\u7684\u8BBF\u95EE\u5BC6\u7801\u5373\u53EF\u89E3\u9501\uFF1A"
4507
4803
  ),
4508
4804
  React.createElement(
4509
4805
  "form",
@@ -4514,7 +4810,7 @@ function BridgePanel({ rpcCall }) {
4514
4810
  React.createElement("input", {
4515
4811
  type: "password",
4516
4812
  style: s.input,
4517
- placeholder: "\u8BF7\u8F93\u5165\u540E\u53F0\u7BA1\u7406\u5BC6\u7801",
4813
+ placeholder: "\u8F93\u5165" + unlockPwdKind,
4518
4814
  value: unlockPassword,
4519
4815
  onChange: (e) => setUnlockPassword(e.target.value),
4520
4816
  autoFocus: true