@wenbin_wb/dsh-bridge 2.10.5 → 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
  });
@@ -3876,6 +4058,18 @@ function BridgePanel({ rpcCall }) {
3876
4058
  const [activeTab, setActiveTab] = React.useState("lan");
3877
4059
  const [platforms, setPlatforms] = React.useState(null);
3878
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
+ }, []);
3879
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"));
3880
4074
  const [adminUnlocked, setAdminUnlocked] = React.useState(false);
3881
4075
  const [unlockPassword, setUnlockPassword] = React.useState("");
@@ -4098,62 +4292,122 @@ function BridgePanel({ rpcCall }) {
4098
4292
  );
4099
4293
  } else if (activeTab === "tunnel") {
4100
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;
4101
4338
  tabContent = React.createElement(
4102
4339
  React.Fragment,
4103
4340
  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
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
4132
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"),
4133
4354
  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
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
4144
4375
  },
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
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
4157
4411
  })
4158
4412
  )
4159
4413
  );
@@ -4249,6 +4503,9 @@ function BridgePanel({ rpcCall }) {
4249
4503
  const auth = status?.auth;
4250
4504
  const policy = auth?.adminPolicy ?? "password_unlock";
4251
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";
4252
4509
  const isLocked = !isLocalhost && auth?.adminProtection !== false && policy !== "open" && !adminUnlocked && (policy === "local_only" || hasAnyPassword);
4253
4510
  if (isLocked) {
4254
4511
  return React.createElement(
@@ -4306,10 +4563,42 @@ function BridgePanel({ rpcCall }) {
4306
4563
  { style: { textAlign: "center", marginBottom: 20 } },
4307
4564
  React.createElement("div", { style: { fontSize: 40, marginBottom: 10 } }, "\u{1F512}"),
4308
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
+ ),
4309
4598
  React.createElement(
4310
4599
  "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"
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
4313
4602
  )
4314
4603
  ),
4315
4604
  React.createElement(
@@ -4321,7 +4610,7 @@ function BridgePanel({ rpcCall }) {
4321
4610
  React.createElement("input", {
4322
4611
  type: "password",
4323
4612
  style: s.input,
4324
- placeholder: "\u8F93\u5165\u540E\u53F0\u7BA1\u7406\u5BC6\u7801",
4613
+ placeholder: "\u8F93\u5165" + unlockPwdKind,
4325
4614
  value: unlockPassword,
4326
4615
  onChange: (e) => setUnlockPassword(e.target.value),
4327
4616
  autoFocus: true
@@ -4342,7 +4631,7 @@ function BridgePanel({ rpcCall }) {
4342
4631
  type: "button",
4343
4632
  style: { ...s.btnLink, fontSize: 12, color: "var(--dsw-alias-label-secondary,#6b7280)" },
4344
4633
  onClick: () => setShowForgotGuide((v) => !v)
4345
- }, "\u2753 \u5FD8\u8BB0\u540E\u53F0\u7BA1\u7406\u5BC6\u7801\uFF1F")
4634
+ }, "\u2753 \u5FD8\u8BB0" + unlockPwdKind + "\uFF1F")
4346
4635
  ),
4347
4636
  showForgotGuide && React.createElement(
4348
4637
  "div",
@@ -4359,8 +4648,8 @@ function BridgePanel({ rpcCall }) {
4359
4648
  textAlign: "left"
4360
4649
  }
4361
4650
  },
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"),
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"),
4364
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")
4365
4654
  )
4366
4655
  )
@@ -4395,7 +4684,7 @@ function BridgePanel({ rpcCall }) {
4395
4684
  setUnlockErr(err);
4396
4685
  setShowUnlockModal(true);
4397
4686
  }
4398
- }, "\u{1F511} \u7ACB\u5373\u8F93\u5165\u7BA1\u7406\u5BC6\u7801\u89E3\u9501")
4687
+ }, "\u{1F511} \u7ACB\u5373\u8F93\u5165" + unlockPwdKind + "\u89E3\u9501")
4399
4688
  ),
4400
4689
  // 管理员解锁状态提示条
4401
4690
  !isLocalhost && adminUnlocked && React.createElement(
@@ -4503,7 +4792,7 @@ function BridgePanel({ rpcCall }) {
4503
4792
  React.createElement(
4504
4793
  "div",
4505
4794
  { 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"
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"
4507
4796
  ),
4508
4797
  React.createElement(
4509
4798
  "form",
@@ -4514,7 +4803,7 @@ function BridgePanel({ rpcCall }) {
4514
4803
  React.createElement("input", {
4515
4804
  type: "password",
4516
4805
  style: s.input,
4517
- placeholder: "\u8BF7\u8F93\u5165\u540E\u53F0\u7BA1\u7406\u5BC6\u7801",
4806
+ placeholder: "\u8F93\u5165" + unlockPwdKind,
4518
4807
  value: unlockPassword,
4519
4808
  onChange: (e) => setUnlockPassword(e.target.value),
4520
4809
  autoFocus: true