@wenbin_wb/dsh-bridge 2.10.4 → 2.10.6

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
@@ -1222,14 +1222,14 @@ function StatusTag({ running, status }) {
1222
1222
  bg = "var(--dsw-alias-state-success-bg,#ecfdf5)";
1223
1223
  color = "var(--dsw-alias-state-success-primary,#059669)";
1224
1224
  text = "\u5DF2\u8FDE\u63A5";
1225
- } else if (status === "starting") {
1225
+ } else if (status === "starting" || status === "connecting" || status === "downloading") {
1226
1226
  bg = "var(--dsw-alias-state-info-bg,#eff6ff)";
1227
1227
  color = "var(--dsw-alias-state-info-primary,#3b82f6)";
1228
- text = "\u8FDE\u63A5\u4E2D\u2026";
1228
+ text = status === "downloading" ? "\u4E0B\u8F7D\u4E2D\u2026" : "\u8FDE\u63A5\u4E2D\u2026";
1229
1229
  } else if (status === "reconnecting") {
1230
1230
  bg = "var(--dsw-alias-state-warn-bg,#fffbeb)";
1231
1231
  color = "var(--dsw-alias-state-warn-primary,#d97706)";
1232
- text = "\u91CD\u8FDE\u4E2D\u2026";
1232
+ text = "\u81EA\u52A8\u91CD\u8FDE\u4E2D\u2026";
1233
1233
  } else if (status === "paused") {
1234
1234
  bg = "var(--dsw-alias-state-warn-bg,#fffbeb)";
1235
1235
  color = "var(--dsw-alias-state-warn-primary,#d97706)";
@@ -1445,6 +1445,185 @@ var CustomTunnelGuide = React.memo(function CustomTunnelGuide2() {
1445
1445
  }, "\u67E5\u770B\u81EA\u5EFA\u96A7\u9053\u670D\u52A1\u5668\u642D\u5EFA\u6559\u7A0B")
1446
1446
  );
1447
1447
  });
1448
+ var TunnelEntryCard = React.memo(function TunnelEntryCard2({
1449
+ entry,
1450
+ onCopy,
1451
+ copied,
1452
+ autoStart,
1453
+ onToggleAutoStart,
1454
+ onStart,
1455
+ onStop,
1456
+ onReset
1457
+ }) {
1458
+ const [showQr, setShowQr] = React.useState(false);
1459
+ const hasUrl = Boolean(entry && entry.url);
1460
+ const active = Boolean(entry && entry.running);
1461
+ return React.createElement(
1462
+ "div",
1463
+ {
1464
+ style: {
1465
+ ...s.card,
1466
+ borderColor: active ? "var(--dsw-alias-state-success-border,#a7f3d0)" : void 0,
1467
+ background: active ? "linear-gradient(180deg, var(--dsw-alias-bg-layer-2,#f9fafb), var(--dsw-alias-bg-layer-1,#ffffff))" : void 0
1468
+ }
1469
+ },
1470
+ React.createElement(
1471
+ "div",
1472
+ { style: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 10 } },
1473
+ React.createElement(
1474
+ "div",
1475
+ { style: { flex: "1 1 auto", minWidth: 0 } },
1476
+ React.createElement(
1477
+ "div",
1478
+ { style: { display: "flex", alignItems: "center", gap: 8 } },
1479
+ React.createElement("span", { style: { fontSize: 16 } }, "\u{1F310}"),
1480
+ React.createElement("div", { style: s.label }, "\u516C\u7F51\u8BBF\u95EE\u5165\u53E3")
1481
+ ),
1482
+ React.createElement(
1483
+ "div",
1484
+ { style: { ...s.muted, marginTop: 4 } },
1485
+ 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"
1486
+ )
1487
+ ),
1488
+ React.createElement(StatusTag, {
1489
+ running: active,
1490
+ status: entry && entry.phase && entry.phase !== "ready" ? entry.phase : void 0
1491
+ })
1492
+ ),
1493
+ React.createElement(
1494
+ "div",
1495
+ { style: { ...s.block, display: "flex", flexDirection: "column", gap: 10 } },
1496
+ // 有地址:大号 URL + 复制
1497
+ hasUrl && React.createElement(
1498
+ "div",
1499
+ {
1500
+ style: {
1501
+ padding: "10px 12px",
1502
+ background: "var(--dsw-alias-bg-layer-1,#ffffff)",
1503
+ border: "1px solid var(--dsw-alias-border-l2,#e5e7eb)",
1504
+ borderRadius: 10,
1505
+ display: "flex",
1506
+ alignItems: "center",
1507
+ gap: 10
1508
+ }
1509
+ },
1510
+ React.createElement("code", {
1511
+ style: { ...s.code, flex: "1 1 auto", fontSize: 13.5, wordBreak: "break-all", lineHeight: 1.5 }
1512
+ }, entry.url),
1513
+ React.createElement("button", {
1514
+ style: { ...s.btnGhost, flexShrink: 0, height: 28, padding: "0 12px", fontSize: 12 },
1515
+ onClick: () => onCopy && onCopy(entry.url)
1516
+ }, copied ? "\u2713 \u5DF2\u590D\u5236" : "\u590D\u5236")
1517
+ ),
1518
+ // 状态细节(重连/错误/连接中)——无 URL 时格外重要,让用户知道隧道在自愈而非消失
1519
+ entry && entry.stateDetail && React.createElement("div", {
1520
+ style: {
1521
+ fontSize: 12,
1522
+ lineHeight: 1.5,
1523
+ 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)"
1524
+ }
1525
+ }, entry.stateDetail),
1526
+ // 操作行:running → 关闭/停止重连(+ 重置);!running → 开启
1527
+ active && onStop && React.createElement(
1528
+ "div",
1529
+ { style: { display: "flex", gap: 8, flexWrap: "wrap" } },
1530
+ React.createElement("button", {
1531
+ style: s.btnGhost,
1532
+ onClick: onStop
1533
+ }, entry && entry.phase === "reconnecting" ? "\u505C\u6B62\u91CD\u8FDE" : "\u5173\u95ED"),
1534
+ onReset && React.createElement("button", {
1535
+ style: { ...s.btnGhost, height: 28, padding: "0 12px", fontSize: 12 },
1536
+ onClick: onReset,
1537
+ title: "\u5173\u95ED\u5E76\u91CD\u65B0\u5F00\u542F\uFF0C\u66F4\u6362\u4E34\u65F6\u5730\u5740"
1538
+ }, "\u{1F504} \u91CD\u7F6E\u94FE\u63A5")
1539
+ ),
1540
+ // running 但无 onStop 的入口(如外部登记):只给重置/二维码辅助,无开关
1541
+ active && !onStop && onReset && React.createElement(
1542
+ "div",
1543
+ { style: { display: "flex", gap: 8, flexWrap: "wrap" } },
1544
+ React.createElement("button", {
1545
+ style: { ...s.btnGhost, height: 28, padding: "0 12px", fontSize: 12 },
1546
+ onClick: onReset,
1547
+ title: "\u5173\u95ED\u5E76\u91CD\u65B0\u5F00\u542F\uFF0C\u66F4\u6362\u4E34\u65F6\u5730\u5740"
1548
+ }, "\u{1F504} \u91CD\u7F6E\u94FE\u63A5")
1549
+ ),
1550
+ // 未运行:引导开启(连接中/下载中禁用)
1551
+ !active && onStart && React.createElement("button", {
1552
+ style: {
1553
+ ...s.btnPri,
1554
+ alignSelf: "flex-start",
1555
+ opacity: entry && entry.configured === false ? 0.4 : 1,
1556
+ background: entry && entry.phase === "connecting" ? "var(--dsw-alias-state-info-primary,#3b82f6)" : void 0
1557
+ },
1558
+ onClick: onStart,
1559
+ disabled: Boolean(entry && entry.configured === false || entry && (entry.phase === "connecting" || entry.phase === "downloading")),
1560
+ title: entry && entry.configured === false ? "\u8BF7\u5148\u5728\u300C\u96A7\u9053\u914D\u7F6E\u300D\u4E2D\u4FDD\u5B58\u670D\u52A1\u5668\u914D\u7F6E" : ""
1561
+ }, entry && entry.phase === "connecting" ? "\u8FDE\u63A5\u4E2D\u2026" : entry && entry.phase === "downloading" ? "\u4E0B\u8F7D\u4E2D\u2026" : "\u5F00\u542F\u516C\u7F51\u96A7\u9053"),
1562
+ // 二维码辅助按钮:有地址未运行时也可查看(外部登记等)
1563
+ hasUrl && entry.qr && React.createElement(
1564
+ "div",
1565
+ { style: { display: "flex", gap: 8, flexWrap: "wrap" } },
1566
+ React.createElement("button", {
1567
+ style: { ...s.btnGhost, height: 28, padding: "0 12px", fontSize: 12 },
1568
+ onClick: () => setShowQr((v) => !v)
1569
+ }, showQr ? "\u9690\u85CF\u4E8C\u7EF4\u7801" : "\u663E\u793A\u4E8C\u7EF4\u7801")
1570
+ ),
1571
+ showQr && hasUrl && entry.qr && React.createElement(
1572
+ "div",
1573
+ { style: { display: "flex", flexDirection: "column", alignItems: "flex-start", gap: 6 } },
1574
+ React.createElement("img", { src: entry.qr, alt: "QR", style: { ...s.qr, margin: 0 } }),
1575
+ React.createElement("div", { style: { ...s.muted, fontSize: 11 } }, "\u8BF7\u5728\u79C1\u5BC6\u73AF\u5883\u4E0B\u626B\u7801\u4F7F\u7528")
1576
+ )
1577
+ ),
1578
+ onToggleAutoStart && React.createElement(
1579
+ "label",
1580
+ {
1581
+ style: {
1582
+ display: "flex",
1583
+ alignItems: "center",
1584
+ gap: 6,
1585
+ marginTop: 12,
1586
+ paddingTop: 10,
1587
+ borderTop: "1px solid var(--dsw-alias-border-l2,#e5e7eb)",
1588
+ fontSize: 12,
1589
+ color: "var(--dsw-alias-label-secondary,#6b7280)",
1590
+ cursor: "pointer",
1591
+ userSelect: "none"
1592
+ },
1593
+ title: "DSH \u542F\u52A8\u65F6\u81EA\u52A8\u6062\u590D\u8BE5\u96A7\u9053\u7684\u8FD0\u884C\u72B6\u6001"
1594
+ },
1595
+ React.createElement("input", {
1596
+ type: "checkbox",
1597
+ checked: Boolean(autoStart),
1598
+ onChange: (e) => onToggleAutoStart(e.target.checked)
1599
+ }),
1600
+ React.createElement("span", null, "\u968F DSH \u542F\u52A8\u81EA\u52A8\u5F00\u542F")
1601
+ )
1602
+ );
1603
+ });
1604
+ var TunnelConfigGroup = React.memo(function TunnelConfigGroup2({ children }) {
1605
+ const [open, setOpen] = React.useState(false);
1606
+ return React.createElement(
1607
+ "div",
1608
+ { style: s.card },
1609
+ React.createElement(
1610
+ "div",
1611
+ {
1612
+ style: { display: "flex", alignItems: "center", justifyContent: "space-between", cursor: "pointer", userSelect: "none", gap: 8 },
1613
+ onClick: () => setOpen((v) => !v)
1614
+ },
1615
+ React.createElement(
1616
+ "div",
1617
+ { style: { display: "flex", alignItems: "center", gap: 8 } },
1618
+ React.createElement("span", { style: { fontSize: 14 } }, "\u2699\uFE0F"),
1619
+ React.createElement("div", { style: s.label }, "\u96A7\u9053\u914D\u7F6E"),
1620
+ 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")
1621
+ ),
1622
+ React.createElement("span", { style: { fontSize: 12, color: "var(--dsw-alias-label-tertiary,#9ca3af)", flexShrink: 0 } }, open ? "\u6536\u8D77 \u25B4" : "\u5C55\u5F00 \u25BE")
1623
+ ),
1624
+ open && React.createElement("div", { style: { marginTop: 4 } }, children)
1625
+ );
1626
+ });
1448
1627
  var CustomTunnelConfigForm = React.memo(function CustomTunnelConfigForm2({ serverUrl: initUrl, accessToken: initToken, onSave }) {
1449
1628
  const [serverUrl, setServerUrl] = React.useState(initUrl ?? "");
1450
1629
  const [accessToken, setAccessToken] = React.useState(initToken ?? "");
@@ -1561,7 +1740,7 @@ var TunnelCard = React.memo(function TunnelCard2({
1561
1740
  React.createElement(
1562
1741
  "div",
1563
1742
  { style: { display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 6 } },
1564
- React.createElement(StatusTag, { running }),
1743
+ React.createElement(StatusTag, { running, status: phase === "ready" ? void 0 : phase }),
1565
1744
  onToggleAutoStart && React.createElement(
1566
1745
  "label",
1567
1746
  {
@@ -1590,7 +1769,7 @@ var TunnelCard = React.memo(function TunnelCard2({
1590
1769
  style: {
1591
1770
  ...s.block,
1592
1771
  fontSize: 12,
1593
- color: phase === "error" ? "var(--dsw-alias-state-error-primary,#dc2626)" : "var(--dsw-alias-label-secondary,#6b7280)"
1772
+ 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
1773
  }
1595
1774
  }, state?.detail ?? phase),
1596
1775
  url && React.createElement(QrBlock, { url, qr, onReset, auth, onNavigateSecurity }),
@@ -1605,7 +1784,10 @@ var TunnelCard = React.memo(function TunnelCard2({
1605
1784
  disabled: configured === false || phase === "connecting" || phase === "downloading",
1606
1785
  title: configured === false ? "\u8BF7\u5148\u4FDD\u5B58\u670D\u52A1\u5668\u914D\u7F6E" : ""
1607
1786
  }, 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")
1787
+ running && onStop && React.createElement("button", {
1788
+ style: s.btnGhost,
1789
+ onClick: onStop
1790
+ }, phase === "reconnecting" ? "\u505C\u6B62\u91CD\u8FDE" : "\u5173\u95ED")
1609
1791
  )
1610
1792
  );
1611
1793
  });
@@ -1820,6 +2002,11 @@ var AccessAuthCard = React.memo(function AccessAuthCard2({ auth, rpcCall, onUpda
1820
2002
  const next = !enabled;
1821
2003
  const noPasswordYet = !auth?.hasPassword && !auth?.hasAdminPassword;
1822
2004
  if (next && noPasswordYet && mode !== "token_only") {
2005
+ if (mode === "password_only") {
2006
+ window.alert("\u300C\u4EC5\u5BC6\u7801 / PIN \u7801\u767B\u5F55\u300D\u6A21\u5F0F\u5FC5\u987B\u5148\u8BBE\u7F6E\u8BBF\u95EE\u5BC6\u7801\u624D\u80FD\u5F00\u542F\u5B89\u5168\u9632\u62A4\u3002\n\n\u8BF7\u5148\u5728\u4E0B\u65B9\u300C\u8BBE\u7F6E\u5916\u90E8\u8BBF\u5BA2\u8BBF\u95EE\u5BC6\u7801 / PIN \u7801\u300D\u8F93\u5165\u5BC6\u7801\u5E76\u70B9\u51FB\u300C\u4FDD\u5B58\u8BBF\u95EE\u5BC6\u7801\u300D\uFF0C\u7136\u540E\u518D\u5F00\u542F\u3002");
2007
+ setTopMsg({ ok: false, text: "\u8BF7\u5148\u8BBE\u7F6E\u8BBF\u5BA2\u8BBF\u95EE\u5BC6\u7801\uFF0C\u518D\u5F00\u542F\u5B89\u5168\u9632\u62A4" });
2008
+ return;
2009
+ }
1823
2010
  const go = window.confirm(
1824
2011
  '\u26A0\uFE0F \u60A8\u5C1A\u672A\u8BBE\u7F6E\u4EFB\u4F55\u8BBF\u95EE\u5BC6\u7801\u6216\u7BA1\u7406\u5BC6\u7801\u3002\n\n\u5F00\u542F\u5B89\u5168\u9632\u62A4\u540E\uFF0C\u4EFB\u4F55\u77E5\u9053\u5C40\u57DF\u7F51 IP / \u96A7\u9053\u5730\u5740\u7684\u8BBF\u5BA2\u4ECD\u53EF\u76F4\u63A5\u8FDB\u5165\uFF08\u5F53\u524D\u76F8\u5F53\u4E8E"\u514D\u5BC6\u5F00\u653E"\u72B6\u6001\uFF09\u3002\n\n\u662F\u5426\u4ECD\u8981\u5F00\u542F\uFF1F\u5EFA\u8BAE\u5148\u5173\u95ED\uFF0C\u5728\u4E0B\u65B9\u300C\u8BBE\u7F6E\u5916\u90E8\u8BBF\u5BA2\u8BBF\u95EE\u5BC6\u7801\u300D\u5904\u8BBE\u7F6E\u5BC6\u7801\u540E\u518D\u5F00\u542F\u3002'
1825
2012
  );
@@ -1851,6 +2038,11 @@ var AccessAuthCard = React.memo(function AccessAuthCard2({ auth, rpcCall, onUpda
1851
2038
  }
1852
2039
  };
1853
2040
  const handleChangeMode = async (m) => {
2041
+ if (m === "password_only" && !auth?.hasPassword && !auth?.hasAdminPassword) {
2042
+ window.alert("\u300C\u4EC5\u5BC6\u7801 / PIN \u7801\u767B\u5F55\u300D\u9700\u8981\u5148\u8BBE\u7F6E\u8BBF\u95EE\u5BC6\u7801\u3002\n\n\u8BF7\u5728\u4E0B\u65B9\u300C\u8BBE\u7F6E\u5916\u90E8\u8BBF\u5BA2\u8BBF\u95EE\u5BC6\u7801 / PIN \u7801\u300D\u5904\u8F93\u5165\u5BC6\u7801\u5E76\u70B9\u51FB\u300C\u4FDD\u5B58\u8BBF\u95EE\u5BC6\u7801\u300D\uFF0C\u7136\u540E\u518D\u5207\u6362\u5230\u6B64\u6A21\u5F0F\u6216\u5F00\u542F\u5B89\u5168\u9632\u62A4\u3002");
2043
+ setTopMsg({ ok: false, text: "\u8BF7\u5148\u5728\u4E0B\u65B9\u8BBE\u7F6E\u8BBF\u5BA2\u8BBF\u95EE\u5BC6\u7801\uFF0C\u518D\u5207\u6362\u4E3A\u300C\u4EC5\u5BC6\u7801\u767B\u5F55\u300D\u6A21\u5F0F" });
2044
+ return;
2045
+ }
1854
2046
  const prev = mode;
1855
2047
  setMode(m);
1856
2048
  try {
@@ -2064,7 +2256,10 @@ var AccessAuthCard = React.memo(function AccessAuthCard2({ auth, rpcCall, onUpda
2064
2256
  // =========================================================================
2065
2257
  // ---- 第一道防线:外部访问门禁(控制谁能进入 Web 界面使用 AI) ----
2066
2258
  // =========================================================================
2067
- enabled && React.createElement(
2259
+ // 第一道防线卡片:已开启防护,或【尚未设置任何密码】时始终显示——
2260
+ // 未设密码时必须给出密码输入框,否则用户开启防护(尤其 password_only)后
2261
+ // 会因无密码被锁在登录墙外且找不到设密码入口(自我锁死,v2.10.5 修复)。
2262
+ (enabled || !auth?.hasPassword) && React.createElement(
2068
2263
  "div",
2069
2264
  { style: s.card },
2070
2265
  React.createElement(
@@ -3863,6 +4058,18 @@ function BridgePanel({ rpcCall }) {
3863
4058
  const [activeTab, setActiveTab] = React.useState("lan");
3864
4059
  const [platforms, setPlatforms] = React.useState(null);
3865
4060
  const [selectedPlatform, setSelectedPlatform] = React.useState("wechat");
4061
+ const [copiedUrl, setCopiedUrl] = React.useState("");
4062
+ const copyEntryUrl = React.useCallback((url) => {
4063
+ const done = () => {
4064
+ setCopiedUrl(url);
4065
+ setTimeout(() => setCopiedUrl(""), 2e3);
4066
+ };
4067
+ if (navigator.clipboard?.writeText) {
4068
+ navigator.clipboard.writeText(url).then(done).catch(() => done());
4069
+ } else {
4070
+ done();
4071
+ }
4072
+ }, []);
3866
4073
  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"));
3867
4074
  const [adminUnlocked, setAdminUnlocked] = React.useState(false);
3868
4075
  const [unlockPassword, setUnlockPassword] = React.useState("");
@@ -4085,62 +4292,122 @@ function BridgePanel({ rpcCall }) {
4085
4292
  );
4086
4293
  } else if (activeTab === "tunnel") {
4087
4294
  const ext = status?.externalTunnel;
4295
+ const cf = status?.cloudflared;
4296
+ const cfDesc = cf?.tokenConfigured ? "\u56FA\u5B9A\u57DF\u540D\u6A21\u5F0F" : "\u514D\u767B\u5F55\u4E34\u65F6\u57DF\u540D";
4297
+ const entries = [
4298
+ ct && ct.running && {
4299
+ key: "custom",
4300
+ title: "\u81EA\u5EFA\u96A7\u9053",
4301
+ desc: "VPS \u81EA\u5EFA \xB7 \u56FA\u5B9A\u5730\u5740",
4302
+ url: ct.url || null,
4303
+ qr: ct.qr,
4304
+ running: true,
4305
+ phase: ct.state && ct.state.phase,
4306
+ stateDetail: ct.state && ct.state.detail,
4307
+ autoStart: ct.autoStart,
4308
+ onToggleAutoStart: onToggleCustomAutoStart,
4309
+ onStart: onStartCustom,
4310
+ onStop: onStopCustom
4311
+ },
4312
+ cf && cf.running && {
4313
+ key: "cloudflared",
4314
+ title: "Cloudflare \u96A7\u9053",
4315
+ desc: cfDesc,
4316
+ url: cf.url || null,
4317
+ qr: cf.qr,
4318
+ running: true,
4319
+ phase: cf.state && cf.state.phase,
4320
+ stateDetail: cf.state && cf.state.detail,
4321
+ autoStart: cf.autoStart,
4322
+ onToggleAutoStart: onToggleCloudflaredAutoStart,
4323
+ onStart: onStartCloudflared,
4324
+ onStop: onStopCloudflared,
4325
+ onReset: onResetCloudflared
4326
+ },
4327
+ ext && ext.configured && ext.url && {
4328
+ key: "external",
4329
+ title: "\u5916\u90E8\u5DF2\u90E8\u7F72\u96A7\u9053",
4330
+ desc: "\u81EA\u884C\u90E8\u7F72\u767B\u8BB0",
4331
+ url: ext.url,
4332
+ qr: ext.qr,
4333
+ running: true
4334
+ }
4335
+ ].filter(Boolean);
4336
+ const primary = entries[0] || null;
4337
+ const otherCount = primary ? entries.length - 1 : entries.length;
4088
4338
  tabContent = React.createElement(
4089
4339
  React.Fragment,
4090
4340
  null,
4091
- React.createElement(
4092
- TunnelCard,
4093
- {
4094
- title: "Cloudflare \u96A7\u9053",
4095
- 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",
4096
- data: {
4097
- running: status?.cloudflared?.running,
4098
- url: status?.cloudflared?.url,
4099
- qr: status?.cloudflared?.qr,
4100
- state: status?.cloudflared?.state
4101
- },
4102
- autoStart: status?.cloudflared?.autoStart,
4103
- onToggleAutoStart: onToggleCloudflaredAutoStart,
4104
- auth: status?.auth,
4105
- onNavigateSecurity: navSecurity,
4106
- onStart: onStartCloudflared,
4107
- onStop: onStopCloudflared,
4108
- onReset: status?.cloudflared?.running ? onResetCloudflared : null
4109
- },
4110
- React.createElement(CloudflareConfigForm, {
4111
- token: status?.cloudflared?.token ?? "",
4112
- hostname: status?.cloudflared?.hostname ?? "",
4113
- onSave: saveCloudflaredConfig
4114
- })
4115
- ),
4116
- React.createElement(ExternalTunnelCard, {
4117
- ext,
4118
- onSave: saveExternalTunnel
4341
+ React.createElement(TunnelEntryCard, {
4342
+ entry: primary,
4343
+ onCopy: copyEntryUrl,
4344
+ copied: Boolean(copiedUrl && primary && copiedUrl === primary.url),
4345
+ autoStart: primary ? primary.autoStart : void 0,
4346
+ onToggleAutoStart: primary ? primary.onToggleAutoStart : void 0,
4347
+ onStart: primary ? primary.onStart : cf ? onStartCloudflared : null,
4348
+ onStop: primary ? primary.onStop : void 0,
4349
+ onReset: primary ? primary.onReset : void 0
4119
4350
  }),
4351
+ otherCount > 0 && React.createElement("div", {
4352
+ style: { ...s.muted, fontSize: 11, marginBottom: 8, textAlign: "center" }
4353
+ }, "\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"),
4120
4354
  React.createElement(
4121
- TunnelCard,
4122
- {
4123
- title: "\u81EA\u5EFA\u96A7\u9053",
4124
- desc: "\u8FDE\u63A5\u81EA\u5DF1\u90E8\u7F72\u7684\u96A7\u9053\u670D\u52A1\u5668\uFF0C\u83B7\u5F97\u56FA\u5B9A\u57DF\u540D",
4125
- data: {
4126
- configured: ct?.configured,
4127
- running: ct?.running,
4128
- url: ct?.url,
4129
- qr: ct?.qr,
4130
- state: ct?.state
4355
+ TunnelConfigGroup,
4356
+ null,
4357
+ React.createElement(
4358
+ TunnelCard,
4359
+ {
4360
+ title: "Cloudflare \u96A7\u9053",
4361
+ 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",
4362
+ data: {
4363
+ running: cf && cf.running,
4364
+ url: cf && cf.url,
4365
+ qr: cf && cf.qr,
4366
+ state: cf && cf.state
4367
+ },
4368
+ autoStart: cf && cf.autoStart,
4369
+ onToggleAutoStart: onToggleCloudflaredAutoStart,
4370
+ auth: status && status.auth,
4371
+ onNavigateSecurity: navSecurity,
4372
+ onStart: onStartCloudflared,
4373
+ onStop: onStopCloudflared,
4374
+ onReset: cf && cf.running ? onResetCloudflared : null
4131
4375
  },
4132
- autoStart: ct?.autoStart,
4133
- onToggleAutoStart: onToggleCustomAutoStart,
4134
- auth: status?.auth,
4135
- onNavigateSecurity: navSecurity,
4136
- onStart: onStartCustom,
4137
- onStop: onStopCustom
4138
- },
4139
- React.createElement(CustomTunnelGuide),
4140
- React.createElement(CustomTunnelConfigForm, {
4141
- serverUrl: ct?.serverUrl ?? "",
4142
- accessToken: ct?.accessToken ?? "",
4143
- onSave: saveConfig
4376
+ React.createElement(CloudflareConfigForm, {
4377
+ token: cf && cf.token || "",
4378
+ hostname: cf && cf.hostname || "",
4379
+ onSave: saveCloudflaredConfig
4380
+ })
4381
+ ),
4382
+ React.createElement(
4383
+ TunnelCard,
4384
+ {
4385
+ title: "\u81EA\u5EFA\u96A7\u9053",
4386
+ desc: "\u8FDE\u63A5\u81EA\u5DF1\u90E8\u7F72\u7684\u96A7\u9053\u670D\u52A1\u5668\uFF0C\u83B7\u5F97\u56FA\u5B9A\u57DF\u540D",
4387
+ data: {
4388
+ configured: ct && ct.configured,
4389
+ running: ct && ct.running,
4390
+ url: ct && ct.url,
4391
+ qr: ct && ct.qr,
4392
+ state: ct && ct.state
4393
+ },
4394
+ autoStart: ct && ct.autoStart,
4395
+ onToggleAutoStart: onToggleCustomAutoStart,
4396
+ auth: status && status.auth,
4397
+ onNavigateSecurity: navSecurity,
4398
+ onStart: onStartCustom,
4399
+ onStop: onStopCustom
4400
+ },
4401
+ React.createElement(CustomTunnelGuide),
4402
+ React.createElement(CustomTunnelConfigForm, {
4403
+ serverUrl: ct && ct.serverUrl || "",
4404
+ accessToken: ct && ct.accessToken || "",
4405
+ onSave: saveConfig
4406
+ })
4407
+ ),
4408
+ React.createElement(ExternalTunnelCard, {
4409
+ ext,
4410
+ onSave: saveExternalTunnel
4144
4411
  })
4145
4412
  )
4146
4413
  );
@@ -4236,6 +4503,9 @@ function BridgePanel({ rpcCall }) {
4236
4503
  const auth = status?.auth;
4237
4504
  const policy = auth?.adminPolicy ?? "password_unlock";
4238
4505
  const hasAnyPassword = !!(auth?.hasPassword || auth?.hasAdminPassword);
4506
+ const unlockUsesAdmin = !!auth?.hasAdminPassword;
4507
+ const unlockPwdKind = unlockUsesAdmin ? "\u7BA1\u7406\u5BC6\u7801" : "\u8BBF\u95EE\u5BC6\u7801";
4508
+ 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";
4239
4509
  const isLocked = !isLocalhost && auth?.adminProtection !== false && policy !== "open" && !adminUnlocked && (policy === "local_only" || hasAnyPassword);
4240
4510
  if (isLocked) {
4241
4511
  return React.createElement(
@@ -4293,10 +4563,42 @@ function BridgePanel({ rpcCall }) {
4293
4563
  { style: { textAlign: "center", marginBottom: 20 } },
4294
4564
  React.createElement("div", { style: { fontSize: 40, marginBottom: 10 } }, "\u{1F512}"),
4295
4565
  React.createElement("div", { style: { ...s.label, fontSize: 16, fontWeight: 600 } }, "\u7BA1\u7406\u63A7\u5236\u53F0\u5DF2\u9501\u5B9A"),
4566
+ // 醒目提示该输入哪种密码
4567
+ React.createElement(
4568
+ "div",
4569
+ { style: { marginTop: 10, display: "flex", justifyContent: "center" } },
4570
+ unlockUsesAdmin ? React.createElement("span", {
4571
+ style: {
4572
+ display: "inline-flex",
4573
+ alignItems: "center",
4574
+ gap: 5,
4575
+ padding: "4px 12px",
4576
+ borderRadius: 999,
4577
+ fontSize: 12,
4578
+ fontWeight: 600,
4579
+ background: "var(--dsw-alias-state-info-bg,#eff6ff)",
4580
+ color: "var(--dsw-alias-state-info-primary,#2563eb)",
4581
+ border: "1px solid var(--dsw-alias-state-info-border,#bfdbfe)"
4582
+ }
4583
+ }, "\u{1F511} \u4F7F\u7528\u7BA1\u7406\u5BC6\u7801\u89E3\u9501") : React.createElement("span", {
4584
+ style: {
4585
+ display: "inline-flex",
4586
+ alignItems: "center",
4587
+ gap: 5,
4588
+ padding: "4px 12px",
4589
+ borderRadius: 999,
4590
+ fontSize: 12,
4591
+ fontWeight: 600,
4592
+ background: "var(--dsw-alias-state-success-bg,#ecfdf5)",
4593
+ color: "var(--dsw-alias-state-success-primary,#059669)",
4594
+ border: "1px solid var(--dsw-alias-state-success-border,#a7f3d0)"
4595
+ }
4596
+ }, "\u{1F510} \u4F7F\u7528\u8BBF\u95EE\u5BC6\u7801\u89E3\u9501")
4597
+ ),
4296
4598
  React.createElement(
4297
4599
  "div",
4298
- { style: { ...s.muted, fontSize: 12, marginTop: 6, lineHeight: 1.5 } },
4299
- "\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"
4600
+ { style: { ...s.muted, fontSize: 12, marginTop: 10, lineHeight: 1.6 } },
4601
+ "\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
4300
4602
  )
4301
4603
  ),
4302
4604
  React.createElement(
@@ -4308,7 +4610,7 @@ function BridgePanel({ rpcCall }) {
4308
4610
  React.createElement("input", {
4309
4611
  type: "password",
4310
4612
  style: s.input,
4311
- placeholder: "\u8F93\u5165\u540E\u53F0\u7BA1\u7406\u5BC6\u7801",
4613
+ placeholder: "\u8F93\u5165" + unlockPwdKind,
4312
4614
  value: unlockPassword,
4313
4615
  onChange: (e) => setUnlockPassword(e.target.value),
4314
4616
  autoFocus: true
@@ -4329,7 +4631,7 @@ function BridgePanel({ rpcCall }) {
4329
4631
  type: "button",
4330
4632
  style: { ...s.btnLink, fontSize: 12, color: "var(--dsw-alias-label-secondary,#6b7280)" },
4331
4633
  onClick: () => setShowForgotGuide((v) => !v)
4332
- }, "\u2753 \u5FD8\u8BB0\u540E\u53F0\u7BA1\u7406\u5BC6\u7801\uFF1F")
4634
+ }, "\u2753 \u5FD8\u8BB0" + unlockPwdKind + "\uFF1F")
4333
4635
  ),
4334
4636
  showForgotGuide && React.createElement(
4335
4637
  "div",
@@ -4346,8 +4648,8 @@ function BridgePanel({ rpcCall }) {
4346
4648
  textAlign: "left"
4347
4649
  }
4348
4650
  },
4349
- 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"),
4350
- 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"),
4651
+ 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"),
4652
+ 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"),
4351
4653
  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")
4352
4654
  )
4353
4655
  )
@@ -4382,7 +4684,7 @@ function BridgePanel({ rpcCall }) {
4382
4684
  setUnlockErr(err);
4383
4685
  setShowUnlockModal(true);
4384
4686
  }
4385
- }, "\u{1F511} \u7ACB\u5373\u8F93\u5165\u7BA1\u7406\u5BC6\u7801\u89E3\u9501")
4687
+ }, "\u{1F511} \u7ACB\u5373\u8F93\u5165" + unlockPwdKind + "\u89E3\u9501")
4386
4688
  ),
4387
4689
  // 管理员解锁状态提示条
4388
4690
  !isLocalhost && adminUnlocked && React.createElement(
@@ -4490,7 +4792,7 @@ function BridgePanel({ rpcCall }) {
4490
4792
  React.createElement(
4491
4793
  "div",
4492
4794
  { style: { fontSize: 13, color: "var(--dsw-alias-label-secondary,#4b5563)", marginBottom: 16, lineHeight: 1.5 } },
4493
- "\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"
4795
+ 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"
4494
4796
  ),
4495
4797
  React.createElement(
4496
4798
  "form",
@@ -4501,7 +4803,7 @@ function BridgePanel({ rpcCall }) {
4501
4803
  React.createElement("input", {
4502
4804
  type: "password",
4503
4805
  style: s.input,
4504
- placeholder: "\u8BF7\u8F93\u5165\u540E\u53F0\u7BA1\u7406\u5BC6\u7801",
4806
+ placeholder: "\u8F93\u5165" + unlockPwdKind,
4505
4807
  value: unlockPassword,
4506
4808
  onChange: (e) => setUnlockPassword(e.target.value),
4507
4809
  autoFocus: true