@decentnetwork/beagle-ui 0.2.36 → 0.2.38

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.
Files changed (2) hide show
  1. package/dist/beagle-ui.js +94 -14
  2. package/package.json +1 -1
package/dist/beagle-ui.js CHANGED
@@ -1,4 +1,4 @@
1
- export const BEAGLE_UI_VERSION = "0.2.36";
1
+ export const BEAGLE_UI_VERSION = "0.2.38";
2
2
  // src/icons.jsx
3
3
  var ICON_PATHS = {
4
4
  // ---- tab bar (the four must feel like one set) ----
@@ -448,6 +448,12 @@ var dkApi = {
448
448
  cancelSend: (userid, ids) => dkPost("/api/file-cancel", { userid, ids }),
449
449
  retrySend: (userid, ids) => dkPost("/api/file-retry", { userid, ids }),
450
450
  setProfile: (name, description) => dkPost("/api/set-profile", { name, description }),
451
+ // Probe a failed exit once, now (or every failed exit with all=true). Exits
452
+ // that failed three times in a row are never probed again on their own.
453
+ reconnectExit: (ip) => dkPost("/api/exits/reconnect", { ip }),
454
+ reconnectAllExits: () => dkPost("/api/exits/reconnect", { all: true }),
455
+ // Every exit once, now, failed ones included.
456
+ refreshExits: () => dkPost("/api/exits/refresh", {}),
451
457
  // Used by the browser's welcome flow, which also picks an avatar and marks
452
458
  // first run done. Harmless where there is no welcome flow: nothing calls it.
453
459
  setProfileFull: (p) => dkPost("/api/set-profile", p),
@@ -583,22 +589,33 @@ function useProtocolTails(peers, isProtocol, enabled) {
583
589
  }, [peers, isProtocol, enabled]);
584
590
  return fixes;
585
591
  }
586
- function useDaemonData() {
592
+ function useDaemonData(opts) {
593
+ const wantExits = !!(opts && opts.exits);
594
+ const wantExitsRef = React.useRef(wantExits);
595
+ wantExitsRef.current = wantExits;
596
+ const exitsOpenedRef = React.useRef(false);
587
597
  const [snap, setSnap] = React.useState({
588
598
  me: DK_ME_FALLBACK,
589
599
  peers: [],
590
600
  requests: [],
591
601
  exits: [],
592
602
  activeExit: null,
603
+ exitFeature: null,
604
+ routeFallback: null,
593
605
  loaded: false,
594
606
  locked: false
595
607
  });
596
608
  const [threads, setThreads] = React.useState({});
597
609
  const refreshRef = React.useRef(null);
598
610
  const refresh = React.useCallback(async () => {
599
- var _a;
611
+ var _a, _b, _c;
600
612
  try {
601
- const d = await dkGet("/api/desktop");
613
+ let path = "/api/desktop";
614
+ if (wantExitsRef.current) {
615
+ path += exitsOpenedRef.current ? "?exits=1&open=1" : "?exits=1";
616
+ exitsOpenedRef.current = false;
617
+ }
618
+ const d = await dkGet(path);
602
619
  if (d && d.locked) {
603
620
  setSnap((s) => ({ ...s, locked: true, loaded: true }));
604
621
  return;
@@ -616,6 +633,8 @@ function useDaemonData() {
616
633
  requests: d.requests || [],
617
634
  exits: d.exits || [],
618
635
  activeExit: (_a = d.activeExit) != null ? _a : null,
636
+ exitFeature: (_b = d.exitFeature) != null ? _b : null,
637
+ routeFallback: (_c = d.routeFallback) != null ? _c : null,
619
638
  loaded: true
620
639
  });
621
640
  } catch (e) {
@@ -627,6 +646,12 @@ function useDaemonData() {
627
646
  const t = setInterval(refresh, 2500);
628
647
  return () => clearInterval(t);
629
648
  }, [refresh]);
649
+ React.useEffect(() => {
650
+ if (wantExits) {
651
+ exitsOpenedRef.current = true;
652
+ refresh();
653
+ }
654
+ }, [wantExits, refresh]);
630
655
  const inFlight = React.useRef(/* @__PURE__ */ new Set());
631
656
  const cache = React.useRef({});
632
657
  const [earlier, setEarlier] = React.useState({});
@@ -648,10 +673,10 @@ function useDaemonData() {
648
673
  setThreads((t) => Object.assign({}, t, { [peerId]: withDay }));
649
674
  setEarlier((e) => e[peerId] === c.more ? e : Object.assign({}, e, { [peerId]: c.more }));
650
675
  }, []);
651
- const loadThread = React.useCallback(async (peerId, opts) => {
676
+ const loadThread = React.useCallback(async (peerId, opts2) => {
652
677
  if (!peerId)
653
678
  return;
654
- if (opts && opts.reset)
679
+ if (opts2 && opts2.reset)
655
680
  delete cache.current[peerId];
656
681
  if (inFlight.current.has(peerId))
657
682
  return;
@@ -6161,15 +6186,40 @@ function PeerTable({ T, peers, onOpenChat }) {
6161
6186
  borderBottom: i < peers.length - 1 ? "1px solid var(--line)" : "none"
6162
6187
  } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10, minWidth: 0 } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer: p, size: 26, radius: 6 }), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: p.alias ? "var(--ui)" : "var(--mono)", fontSize: 13, fontWeight: 600, color: "var(--text)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" } }, p.alias || shortKey(p.userId, 8, 5)), p.agent && /* @__PURE__ */ React.createElement(Icon, { name: "bot", size: 12, color: "var(--faint)", stroke: 2 })), /* @__PURE__ */ React.createElement(Mono, { size: 12.5, dim: true, copy: p.ip }, p.ip), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(RouteTag, { peer: p })), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: p.wire === "64" ? "var(--warn)" : "var(--dim)" } }, p.wire === "64" ? "old\xB764" : "new\xB7163"), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", justifyContent: "flex-end", gap: 6 } }, /* @__PURE__ */ React.createElement(Btn, { icon: "message", size: "sm", title: T.openChat, onClick: () => onOpenChat(p.id) }), /* @__PURE__ */ React.createElement(Btn, { icon: "signal", size: "sm", title: T.ping })))));
6163
6188
  }
6164
- function ExitCard({ T, region, activeExit, onSetExit }) {
6189
+ function ReconnectBtn({ T, ip, onReconnect, label, icon = "refresh" }) {
6190
+ const [busy, setBusy] = React.useState(false);
6191
+ return /* @__PURE__ */ React.createElement(
6192
+ Btn,
6193
+ {
6194
+ size: "sm",
6195
+ icon,
6196
+ disabled: busy,
6197
+ onClick: async () => {
6198
+ if (busy || !onReconnect)
6199
+ return;
6200
+ setBusy(true);
6201
+ try {
6202
+ await onReconnect(ip);
6203
+ } finally {
6204
+ setBusy(false);
6205
+ }
6206
+ }
6207
+ },
6208
+ busy ? T.exitChecking || "checking\u2026" : label || T.exitReconnect || "Reconnect"
6209
+ );
6210
+ }
6211
+ function ExitCard({ T, region, activeExit, onSetExit, onReconnect, featureOn }) {
6165
6212
  return /* @__PURE__ */ React.createElement("div", { style: { borderRadius: 11, border: "1px solid var(--line)", overflow: "hidden", background: "var(--panel)" } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10, padding: "11px 14px", borderBottom: "1px solid var(--line)", background: "var(--panel-2)" } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, fontWeight: 700, letterSpacing: 0.5, padding: "2px 7px", borderRadius: 5, background: "var(--chip)", color: "var(--dim)" } }, region.flag), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--ui)", fontSize: 13.5, fontWeight: 600, color: "var(--text)" } }, region.label), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), (() => {
6166
6213
  const reach = region.nodes.filter((n) => n.reachable).length;
6167
- const down = region.nodes.length - reach;
6168
- return /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, color: down > 0 ? "var(--warn)" : "var(--faint)" } }, reach, "/", region.nodes.length, " ", T.up, down > 0 ? ` \xB7 ${down} down` : "");
6214
+ const failed = region.nodes.filter((n) => n.status === "failed").length;
6215
+ const down = region.nodes.length - reach - failed;
6216
+ return /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, color: down + failed > 0 ? "var(--warn)" : "var(--faint)" } }, reach, "/", region.nodes.length, " ", T.up, failed > 0 ? ` \xB7 ${failed} ${T.exitFailed || "failed"}` : "", down > 0 ? ` \xB7 ${down} down` : "");
6169
6217
  })()), /* @__PURE__ */ React.createElement("div", null, region.nodes.map((n, i) => {
6170
6218
  const active = n.ip === activeExit;
6171
6219
  const stuck = n.online && !n.reachable;
6172
- return /* @__PURE__ */ React.createElement("div", { key: n.ip, className: "dk-wrap", style: { display: "flex", alignItems: "center", gap: 12, rowGap: 6, padding: "10px 14px", borderBottom: i < region.nodes.length - 1 ? "1px solid var(--line)" : "none", background: active ? "color-mix(in oklab, var(--accent), transparent 92%)" : "transparent" } }, /* @__PURE__ */ React.createElement(StatusDot, { online: n.reachable }), /* @__PURE__ */ React.createElement(Mono, { size: 13, copy: n.ip }, n.ip), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--faint)" } }, n.host), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), n.reachable ? /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: n.ping > 200 ? "var(--warn)" : "var(--dim)" } }, n.ping, "ms") : stuck ? /* @__PURE__ */ React.createElement(Tag, { tone: "warn", title: "Announces online but IP won't pass \u2014 session desync or NAT-blocked" }, "online \xB7 no route") : /* @__PURE__ */ React.createElement(Tag, { tone: "off" }, "offline"), active ? /* @__PURE__ */ React.createElement(Btn, { tone: "ok", icon: "check", size: "sm", onClick: () => onSetExit(null) }, T.routing) : /* @__PURE__ */ React.createElement(Btn, { size: "sm", icon: "route", onClick: () => n.reachable && onSetExit(n.ip), style: { opacity: n.reachable ? 1 : 0.4 } }, T.routeThru));
6220
+ const failed = n.status === "failed";
6221
+ const usable = n.reachable && featureOn !== false;
6222
+ return /* @__PURE__ */ React.createElement("div", { key: n.ip, className: "dk-wrap", style: { display: "flex", alignItems: "center", gap: 12, rowGap: 6, padding: "10px 14px", borderBottom: i < region.nodes.length - 1 ? "1px solid var(--line)" : "none", background: active ? "color-mix(in oklab, var(--accent), transparent 92%)" : "transparent" } }, /* @__PURE__ */ React.createElement(StatusDot, { online: n.reachable }), /* @__PURE__ */ React.createElement(Mono, { size: 13, copy: n.ip }, n.ip), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--faint)" } }, n.host), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), n.reachable ? /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: n.ping > 200 ? "var(--warn)" : "var(--dim)" } }, n.ping, "ms") : failed ? /* @__PURE__ */ React.createElement(Tag, { tone: "danger", title: "Failed 3 checks in a row \u2014 no longer checked; Reconnect to try again" }, T.exitFailed || "failed") : n.status === "unknown" ? /* @__PURE__ */ React.createElement(Tag, { tone: "off" }, T.exitNotChecked || "not checked") : stuck ? /* @__PURE__ */ React.createElement(Tag, { tone: "warn", title: "Announces online but IP won't pass \u2014 session desync or NAT-blocked" }, "online \xB7 no route") : n.status === "down" ? /* @__PURE__ */ React.createElement(Tag, { tone: "warn", title: "No answer \u2014 checked again only when you open this panel or press Refresh; 3 in a row and it is no longer checked" }, T.exitDown || "down", " \xB7 ", n.fails, "/3") : /* @__PURE__ */ React.createElement(Tag, { tone: "off" }, "offline"), failed ? /* @__PURE__ */ React.createElement(ReconnectBtn, { T, ip: n.ip, onReconnect }) : active ? /* @__PURE__ */ React.createElement(Btn, { tone: "ok", icon: "check", size: "sm", onClick: () => onSetExit(null) }, T.routing) : /* @__PURE__ */ React.createElement(Btn, { size: "sm", icon: "route", disabled: !usable, onClick: () => usable && onSetExit(n.ip), style: { opacity: usable ? 1 : 0.4 } }, T.routeThru));
6173
6223
  })));
6174
6224
  }
6175
6225
  function LanEnableCard({ backend, onArm, onCancel }) {
@@ -6273,12 +6323,14 @@ function LanEnableCard({ backend, onArm, onCancel }) {
6273
6323
  "\u53D6\u6D88,\u6062\u590D\u6D88\u606F"
6274
6324
  )));
6275
6325
  }
6276
- function NetworkTab({ T, me, peers, exits, activeExit, reqCount, onSetExit, onOpenChat, backend, onArmLan, onCancelLan }) {
6326
+ function NetworkTab({ T, me, peers, exits, activeExit, exitFeature, routeFallback, onReconnectExit, onRefreshExits, reqCount, onSetExit, onOpenChat, backend, onArmLan, onCancelLan }) {
6327
+ const featureOn = !exitFeature || exitFeature.available !== false;
6328
+ const anyFailed = (exits || []).some((r) => (r.nodes || []).some((n) => n.status === "failed"));
6277
6329
  if (backend && backend.switchable && !backend.hasVirtualLan) {
6278
6330
  return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, overflow: "auto", background: "var(--bg)" } }, /* @__PURE__ */ React.createElement("div", { className: "dk-page", style: { maxWidth: 1040, margin: "0 auto", padding: "24px 28px 60px", display: "flex", flexDirection: "column", gap: 26 } }, /* @__PURE__ */ React.createElement(MyNode, { T, me, activeExit, peers, reqCount }), /* @__PURE__ */ React.createElement(LanEnableCard, { backend, onArm: onArmLan, onCancel: onCancelLan })));
6279
6331
  }
6280
6332
  const lanless = !!backend && backend.hasVirtualLan === false;
6281
- return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, overflow: "auto", background: "var(--bg)" } }, /* @__PURE__ */ React.createElement("div", { className: "dk-page", style: { maxWidth: 1040, margin: "0 auto", padding: "24px 28px 60px", display: "flex", flexDirection: "column", gap: 26 } }, /* @__PURE__ */ React.createElement(MyNode, { T, me, activeExit, peers, reqCount }), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 12 } }, /* @__PURE__ */ React.createElement(Section, { label: T.peerRouting, count: peers.length }), /* @__PURE__ */ React.createElement(PeerTable, { T, peers, onOpenChat }), !peers.length && /* @__PURE__ */ React.createElement("div", { style: { padding: "14px 16px", borderRadius: 11, border: "1px dashed var(--line)", fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)" } }, T.noPeersYet || "No friends yet \u2014 add one from Chat and it shows up here with its route.")), lanless ? null : /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 12 } }, /* @__PURE__ */ React.createElement(Section, { label: T.exitNodes, trailing: /* @__PURE__ */ React.createElement("div", { style: { display: "flex", gap: 6 } }, /* @__PURE__ */ React.createElement(Btn, { icon: "plus", size: "sm" }, T.addExit)) }), activeExit && /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10, padding: "11px 14px", borderRadius: 10, background: "color-mix(in oklab, var(--warn), transparent 90%)", border: "1px solid color-mix(in oklab, var(--warn), transparent 70%)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "route", size: 17, color: "var(--warn)", stroke: 2 }), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--text)" } }, T.egressVia), /* @__PURE__ */ React.createElement(Mono, { size: 13, copy: activeExit }, activeExit), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement(Btn, { tone: "danger", icon: "unlink", size: "sm", onClick: () => onSetExit(null) }, T.stopRouting)), /* @__PURE__ */ React.createElement("div", { className: "dk-grid1", style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 } }, exits.map((r) => /* @__PURE__ */ React.createElement(ExitCard, { key: r.region, T, region: r, activeExit, onSetExit }))))));
6333
+ return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, overflow: "auto", background: "var(--bg)" } }, /* @__PURE__ */ React.createElement("div", { className: "dk-page", style: { maxWidth: 1040, margin: "0 auto", padding: "24px 28px 60px", display: "flex", flexDirection: "column", gap: 26 } }, /* @__PURE__ */ React.createElement(MyNode, { T, me, activeExit, peers, reqCount }), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 12 } }, /* @__PURE__ */ React.createElement(Section, { label: T.peerRouting, count: peers.length }), /* @__PURE__ */ React.createElement(PeerTable, { T, peers, onOpenChat }), !peers.length && /* @__PURE__ */ React.createElement("div", { style: { padding: "14px 16px", borderRadius: 11, border: "1px dashed var(--line)", fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)" } }, T.noPeersYet || "No friends yet \u2014 add one from Chat and it shows up here with its route.")), lanless ? null : /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 12 } }, /* @__PURE__ */ React.createElement(Section, { label: T.exitNodes, trailing: /* @__PURE__ */ React.createElement("div", { style: { display: "flex", gap: 6 } }, onRefreshExits && (exits || []).length > 0 && /* @__PURE__ */ React.createElement(ReconnectBtn, { T, ip: null, onReconnect: () => onRefreshExits(), label: T.exitRefresh || "Refresh" }), anyFailed && onReconnectExit && /* @__PURE__ */ React.createElement(ReconnectBtn, { T, ip: null, onReconnect: onReconnectExit, label: T.exitReconnectAll || "Reconnect all" }), /* @__PURE__ */ React.createElement(Btn, { icon: "plus", size: "sm" }, T.addExit)) }), !featureOn && /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 4, padding: "11px 14px", borderRadius: 10, background: "var(--panel-2)", border: "1px dashed var(--line)" } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, fontWeight: 700, color: "var(--text)" } }, T.noExitAvailable || "No exit available \u2014 routing and proxy are off."), anyFailed && /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--dim)" } }, T.noExitHint || "Every exit failed 3 times and is no longer checked automatically. Press Reconnect to try one again.")), routeFallback && routeFallback.from && /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10, padding: "11px 14px", borderRadius: 10, background: "color-mix(in oklab, var(--warn), transparent 90%)", border: "1px solid color-mix(in oklab, var(--warn), transparent 70%)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "route", size: 17, color: "var(--warn)", stroke: 2 }), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--text)" } }, T.routeFellBack || "Routing fell back to direct: every exit it used failed. Reconnect one to route through it again.")), activeExit && /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10, padding: "11px 14px", borderRadius: 10, background: "color-mix(in oklab, var(--warn), transparent 90%)", border: "1px solid color-mix(in oklab, var(--warn), transparent 70%)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "route", size: 17, color: "var(--warn)", stroke: 2 }), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--text)" } }, T.egressVia), /* @__PURE__ */ React.createElement(Mono, { size: 13, copy: activeExit }, activeExit), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement(Btn, { tone: "danger", icon: "unlink", size: "sm", onClick: () => onSetExit(null) }, T.stopRouting)), /* @__PURE__ */ React.createElement("div", { className: "dk-grid1", style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 } }, exits.map((r) => /* @__PURE__ */ React.createElement(ExitCard, { key: r.region, T, region: r, activeExit, onSetExit, onReconnect: onReconnectExit, featureOn }))))));
6282
6334
  }
6283
6335
  Object.assign(window, { NetworkTab });
6284
6336
 
@@ -6696,6 +6748,16 @@ var STR = {
6696
6748
  routeThru: "route",
6697
6749
  egressVia: "all egress routed via",
6698
6750
  stopRouting: "stop",
6751
+ exitFailed: "failed",
6752
+ exitReconnect: "Reconnect",
6753
+ exitReconnectAll: "Reconnect all",
6754
+ exitChecking: "checking\u2026",
6755
+ exitNotChecked: "not checked",
6756
+ exitDown: "down",
6757
+ exitRefresh: "Refresh",
6758
+ noExitAvailable: "No exit available \u2014 routing and proxy are off.",
6759
+ noExitHint: "Every exit failed 3 checks in a row and is no longer checked. Press Reconnect to try one again.",
6760
+ routeFellBack: "Routing fell back to direct: every exit it used failed. Reconnect one to route through it again.",
6699
6761
  identity: "Identity",
6700
6762
  userId: "user id",
6701
6763
  carrierAddr: "carrier address",
@@ -6898,6 +6960,16 @@ var STR = {
6898
6960
  routeThru: "\u8DEF\u7531",
6899
6961
  egressVia: "\u5168\u90E8\u51FA\u53E3\u6D41\u91CF\u7ECF\u7531",
6900
6962
  stopRouting: "\u505C\u6B62",
6963
+ exitFailed: "\u5DF2\u5931\u8D25",
6964
+ exitReconnect: "\u91CD\u65B0\u8FDE\u63A5",
6965
+ exitReconnectAll: "\u5168\u90E8\u91CD\u8FDE",
6966
+ exitChecking: "\u68C0\u6D4B\u4E2D\u2026",
6967
+ exitNotChecked: "\u672A\u68C0\u6D4B",
6968
+ exitDown: "\u4E0D\u901A",
6969
+ exitRefresh: "\u5237\u65B0",
6970
+ noExitAvailable: "\u6CA1\u6709\u53EF\u7528\u7684\u51FA\u53E3 \u2014\u2014 \u8DEF\u7531\u548C\u4EE3\u7406\u5DF2\u5173\u95ED\u3002",
6971
+ noExitHint: "\u6240\u6709\u51FA\u53E3\u90FD\u8FDE\u7EED 3 \u6B21\u68C0\u6D4B\u5931\u8D25,\u5DF2\u4E0D\u518D\u68C0\u6D4B\u3002\u70B9\u300C\u91CD\u65B0\u8FDE\u63A5\u300D\u518D\u8BD5\u4E00\u6B21\u3002",
6972
+ routeFellBack: "\u8DEF\u7531\u5DF2\u6539\u56DE\u76F4\u8FDE:\u5B83\u4F7F\u7528\u7684\u51FA\u53E3\u5168\u90E8\u5931\u8D25\u3002\u91CD\u65B0\u8FDE\u63A5\u5176\u4E2D\u4E00\u4E2A\u5373\u53EF\u6062\u590D\u3002",
6901
6973
  identity: "\u8EAB\u4EFD",
6902
6974
  userId: "\u7528\u6237 ID",
6903
6975
  carrierAddr: "Carrier \u5730\u5740",
@@ -7192,7 +7264,7 @@ function DkApp() {
7192
7264
  return hasTab(tabs, want) || DK_PEOPLE_TABS.includes(want) && showPeople ? want : firstTab;
7193
7265
  });
7194
7266
  const [activeId, setActiveId] = React.useState(null);
7195
- const data = useDaemonData();
7267
+ const data = useDaemonData({ exits: tab === "network" });
7196
7268
  const me = data.me;
7197
7269
  const [upd, setUpd] = React.useState(null);
7198
7270
  React.useEffect(() => {
@@ -7555,6 +7627,14 @@ function DkApp() {
7555
7627
  const onEdit = (name, description) => dkApi.setProfile(name, description).then(data.refresh);
7556
7628
  const onSetExit = () => {
7557
7629
  };
7630
+ const onReconnectExit = (ip) => (ip ? dkApi.reconnectExit(ip) : dkApi.reconnectAllExits()).then((r) => {
7631
+ data.refresh();
7632
+ return r;
7633
+ });
7634
+ const onRefreshExits = () => dkApi.refreshExits().then((r) => {
7635
+ data.refresh();
7636
+ return r;
7637
+ });
7558
7638
  const onOpenChat = (id) => {
7559
7639
  setTab("chat");
7560
7640
  openConversation(id);
@@ -7634,7 +7714,7 @@ function DkApp() {
7634
7714
  fontWeight: 600,
7635
7715
  background: tab === n.id ? "var(--accent)" : "transparent",
7636
7716
  color: tab === n.id ? "#fff" : "var(--dim)"
7637
- } }, /* @__PURE__ */ React.createElement(Icon, { name: n.icon, size: 14, stroke: 2, color: tab === n.id ? "#fff" : "var(--dim)" }), n.label))), tab === "chat" && /* @__PURE__ */ React.createElement(ChatTab, { T, lang: t.lang, phone, isProtocol, protocolFix, onBack: closeConversation, peers, requests, activeId, thread: activeId === ASSISTANT_ID ? assistant.thread : data.threads[activeId], onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread: () => activeId && data.loadThread(activeId), hasEarlier: !!(activeId && activeId !== ASSISTANT_ID && data.earlier[activeId]), onAssistantRemove: activeId === ASSISTANT_ID ? assistant.removeFriends : null, onAssistantAdopt: activeId === ASSISTANT_ID ? assistant.adoptServicePeers : null, onLoadEarlier: () => activeId && data.loadEarlier(activeId), prefillAddr: pendingAddr, onPrefillConsumed: () => setPendingAddr(""), muted: mutedIds, onToggleMute }), tab === "here" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "bridge", peers, meId: me.userId, onAdd, onOpenChat }), tab === "recommended" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "recommended", peers, meId: me.userId, onAdd, onOpenChat }), tab === "registered" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "registered", peers, meId: me.userId, onAdd, onOpenChat }), tab === "network" && /* @__PURE__ */ React.createElement(NetworkTab, { T, me, peers, exits, activeExit, reqCount: requests.length, onSetExit, onOpenChat, backend, onArmLan: armLan, onCancelLan: cancelLan }), tab === "apps" && /* @__PURE__ */ React.createElement(AppsTab, { T, t, me, peers, phone, setTab }), tabs.map((x) => x.kind === "host" && tab === x.id ? /* @__PURE__ */ React.createElement(HostTabPane, { key: x.id, tab: x, props: { T, t, me, peers, phone, setTab }, lang: t.lang }) : null), tab === "profile" && /* @__PURE__ */ React.createElement(
7717
+ } }, /* @__PURE__ */ React.createElement(Icon, { name: n.icon, size: 14, stroke: 2, color: tab === n.id ? "#fff" : "var(--dim)" }), n.label))), tab === "chat" && /* @__PURE__ */ React.createElement(ChatTab, { T, lang: t.lang, phone, isProtocol, protocolFix, onBack: closeConversation, peers, requests, activeId, thread: activeId === ASSISTANT_ID ? assistant.thread : data.threads[activeId], onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread: () => activeId && data.loadThread(activeId), hasEarlier: !!(activeId && activeId !== ASSISTANT_ID && data.earlier[activeId]), onAssistantRemove: activeId === ASSISTANT_ID ? assistant.removeFriends : null, onAssistantAdopt: activeId === ASSISTANT_ID ? assistant.adoptServicePeers : null, onLoadEarlier: () => activeId && data.loadEarlier(activeId), prefillAddr: pendingAddr, onPrefillConsumed: () => setPendingAddr(""), muted: mutedIds, onToggleMute }), tab === "here" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "bridge", peers, meId: me.userId, onAdd, onOpenChat }), tab === "recommended" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "recommended", peers, meId: me.userId, onAdd, onOpenChat }), tab === "registered" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "registered", peers, meId: me.userId, onAdd, onOpenChat }), tab === "network" && /* @__PURE__ */ React.createElement(NetworkTab, { T, me, peers, exits, activeExit, exitFeature: data.exitFeature, routeFallback: data.routeFallback, onReconnectExit, onRefreshExits, reqCount: requests.length, onSetExit, onOpenChat, backend, onArmLan: armLan, onCancelLan: cancelLan }), tab === "apps" && /* @__PURE__ */ React.createElement(AppsTab, { T, t, me, peers, phone, setTab }), tabs.map((x) => x.kind === "host" && tab === x.id ? /* @__PURE__ */ React.createElement(HostTabPane, { key: x.id, tab: x, props: { T, t, me, peers, phone, setTab }, lang: t.lang }) : null), tab === "profile" && /* @__PURE__ */ React.createElement(
7638
7718
  ProfileTab,
7639
7719
  {
7640
7720
  T,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/beagle-ui",
3
- "version": "0.2.36",
3
+ "version": "0.2.38",
4
4
  "description": "The Beagle chat UI, minus the machine it runs on — shared by the desktop install and the browser build.",
5
5
  "license": "MIT",
6
6
  "type": "module",