@peers-app/peers-ui 0.21.1 → 0.21.3

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 (31) hide show
  1. package/dist/screens/network-viewer/connection-troubleshooter.js +1 -1
  2. package/dist/screens/network-viewer/device-details-modal.js +11 -15
  3. package/dist/screens/network-viewer/group-details-modal.js +60 -17
  4. package/dist/screens/network-viewer/network-viewer-ipc.d.ts +9 -39
  5. package/dist/screens/network-viewer/network-viewer-ipc.js +71 -3
  6. package/dist/screens/network-viewer/network-viewer-ipc.test.d.ts +1 -0
  7. package/dist/screens/network-viewer/network-viewer-ipc.test.js +59 -0
  8. package/dist/screens/network-viewer/network-viewer.js +46 -60
  9. package/dist/screens/network-viewer/remote-logs-panel.js +11 -12
  10. package/dist/tabs-layout/network-status-indicator.d.ts +19 -0
  11. package/dist/tabs-layout/network-status-indicator.js +105 -0
  12. package/dist/tabs-layout/network-status-indicator.test.d.ts +1 -0
  13. package/dist/tabs-layout/network-status-indicator.test.js +81 -0
  14. package/dist/tabs-layout/tabs-layout.js +25 -24
  15. package/dist/tabs-layout/use-network-status.d.ts +8 -0
  16. package/dist/tabs-layout/use-network-status.js +57 -0
  17. package/dist/tabs-layout/use-network-status.test.d.ts +1 -0
  18. package/dist/tabs-layout/use-network-status.test.js +42 -0
  19. package/package.json +3 -3
  20. package/src/screens/network-viewer/connection-troubleshooter.tsx +2 -2
  21. package/src/screens/network-viewer/device-details-modal.tsx +19 -61
  22. package/src/screens/network-viewer/group-details-modal.tsx +211 -130
  23. package/src/screens/network-viewer/network-viewer-ipc.test.ts +70 -0
  24. package/src/screens/network-viewer/network-viewer-ipc.ts +102 -35
  25. package/src/screens/network-viewer/network-viewer.tsx +76 -148
  26. package/src/screens/network-viewer/remote-logs-panel.tsx +14 -16
  27. package/src/tabs-layout/network-status-indicator.test.tsx +94 -0
  28. package/src/tabs-layout/network-status-indicator.tsx +209 -0
  29. package/src/tabs-layout/tabs-layout.tsx +4 -1
  30. package/src/tabs-layout/use-network-status.test.tsx +55 -0
  31. package/src/tabs-layout/use-network-status.ts +68 -0
@@ -75,7 +75,7 @@ function ConnectionTroubleshooter({ onBack }) {
75
75
  setDone(false);
76
76
  setChecks([]);
77
77
  setExpanded(new Set());
78
- const api = (0, network_viewer_ipc_1.getNetworkViewerApi)();
78
+ const api = (0, network_viewer_ipc_1.getNetworkViewerPlatformApi)();
79
79
  if (!api) {
80
80
  setChecks([
81
81
  {
@@ -36,12 +36,8 @@ function DeviceDetailsModal({ deviceId, onClose, onDisconnect }) {
36
36
  const [notFound, setNotFound] = (0, react_1.useState)(false);
37
37
  const loadData = (0, react_1.useCallback)(async () => {
38
38
  try {
39
- const api = (0, network_viewer_ipc_1.getNetworkViewerApi)();
40
- if (!api) {
41
- console.warn("Network Viewer API not available");
42
- return;
43
- }
44
- const deviceData = (await api.getDeviceDetails(deviceId));
39
+ const diagnostics = await (0, network_viewer_ipc_1.getNetworkDiagnosticsConsumer)();
40
+ const { device: deviceData } = await diagnostics.tools.getDeviceDetails({ deviceId });
45
41
  if (!deviceData) {
46
42
  setNotFound(true);
47
43
  }
@@ -63,14 +59,14 @@ function DeviceDetailsModal({ deviceId, onClose, onDisconnect }) {
63
59
  return;
64
60
  }
65
61
  try {
66
- const api = (0, network_viewer_ipc_1.getNetworkViewerApi)();
67
- if (api && deviceId) {
68
- await api.disconnectDevice(deviceId);
69
- if (onDisconnect) {
70
- onDisconnect();
71
- }
72
- onClose();
62
+ const control = await (0, network_viewer_ipc_1.getNetworkControlConsumer)();
63
+ const result = await control.tools.disconnectDevice({ deviceId });
64
+ if (!result.success) {
65
+ alert(result.message);
66
+ return;
73
67
  }
68
+ onDisconnect?.();
69
+ onClose();
74
70
  }
75
71
  catch (error) {
76
72
  console.error("Error disconnecting device:", error);
@@ -88,9 +84,9 @@ function DeviceDetailsModal({ deviceId, onClose, onDisconnect }) {
88
84
  overflowY: "auto",
89
85
  paddingTop: "2rem",
90
86
  paddingBottom: "2rem",
91
- }, onClick: onClose, children: (0, jsx_runtime_1.jsxs)("div", { className: "card", style: { maxWidth: "900px", width: "90%", maxHeight: "90vh" }, onClick: (e) => e.stopPropagation(), children: [(0, jsx_runtime_1.jsxs)("div", { className: "card-header d-flex justify-content-between align-items-center", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h5", { className: "mb-0", children: loading ? "Loading..." : device?.userName || device?.userId || "Device Details" }), device?.userName && ((0, jsx_runtime_1.jsx)("small", { className: "text-muted", children: (0, jsx_runtime_1.jsx)("code", { children: device.userId }) }))] }), (0, jsx_runtime_1.jsxs)("div", { children: [device && ((0, jsx_runtime_1.jsxs)("button", { className: "btn btn-sm btn-danger me-2", onClick: handleDisconnect, children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-x-circle" }), " Disconnect"] })), (0, jsx_runtime_1.jsx)("button", { className: "btn-close", onClick: onClose, "aria-label": "Close" })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "card-body", style: { overflowY: "auto", maxHeight: "calc(90vh - 120px)" }, children: [loading && (0, jsx_runtime_1.jsx)(loading_indicator_1.LoadingIndicator, {}), !loading && (notFound || !device) && ((0, jsx_runtime_1.jsx)("div", { className: "alert alert-warning", children: "Device not found or connection has been closed." })), !loading && device && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "mb-4", children: [(0, jsx_runtime_1.jsx)("h6", { className: "border-bottom pb-2 mb-3", children: "Connection Information" }), (0, jsx_runtime_1.jsxs)("div", { className: "row", children: [device.deviceName && ((0, jsx_runtime_1.jsxs)("div", { className: "col-md-6 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Device Name:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)("span", { children: device.deviceName })] })), (0, jsx_runtime_1.jsxs)("div", { className: "col-md-6 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Device ID:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)("code", { className: "text-muted small", children: device.deviceId })] }), (0, jsx_runtime_1.jsxs)("div", { className: "col-md-6 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Connection State:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)("span", { className: "badge bg-success", children: device.connectionState })] }), (0, jsx_runtime_1.jsxs)("div", { className: "col-md-6 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Trust Level:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)(trust_level_badge_1.TrustLevelBadge, { level: device.trustLevel || peers_sdk_1.TrustLevel.Unknown })] }), (0, jsx_runtime_1.jsxs)("div", { className: "col-md-6 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Path Type:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)("span", { className: `badge bg-${device.pathType === "direct" ? "success" : "info"}`, children: device.pathType })] }), device.throughDeviceIds && device.throughDeviceIds.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "col-md-12 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Path:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)("small", { className: "text-muted", children: device.throughDeviceIds.join(" → ") })] }))] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "mb-4", children: [(0, jsx_runtime_1.jsx)("h6", { className: "border-bottom pb-2 mb-3", children: "Throughput" }), (0, jsx_runtime_1.jsxs)("div", { className: "row", children: [(0, jsx_runtime_1.jsxs)("div", { className: "col-md-6 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Send Rate:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsxs)("span", { className: "text-success", children: ["\u2191 ", formatRate(device.sendRateMBps)] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "col-md-6 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Receive Rate:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsxs)("span", { className: "text-primary", children: ["\u2193 ", formatRate(device.receiveRateMBps)] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "col-md-6 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Total Sent:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)("span", { children: formatBytes(device.bytesSent || 0) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "col-md-6 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Total Received:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)("span", { children: formatBytes(device.bytesReceived || 0) })] })] })] }), device.canReadRemoteLogs && (0, jsx_runtime_1.jsx)(remote_logs_panel_1.RemoteLogsPanel, { deviceId: device.deviceId }), device.sharedGroups.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "mb-4", children: [(0, jsx_runtime_1.jsxs)("h6", { className: "border-bottom pb-2 mb-3", children: ["Shared Groups (", device.sharedGroups.length, ")"] }), (0, jsx_runtime_1.jsx)("div", { className: "table-responsive", children: (0, jsx_runtime_1.jsxs)("table", { className: "table table-sm table-hover", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { children: "Group ID" }), (0, jsx_runtime_1.jsx)("th", { children: "Role" }), (0, jsx_runtime_1.jsx)("th", { children: "Sync Status" })] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: device.sharedGroups.map((groupId) => {
87
+ }, onClick: onClose, children: (0, jsx_runtime_1.jsxs)("div", { className: "card", style: { maxWidth: "900px", width: "90%", maxHeight: "90vh" }, onClick: (e) => e.stopPropagation(), children: [(0, jsx_runtime_1.jsxs)("div", { className: "card-header d-flex justify-content-between align-items-center", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h5", { className: "mb-0", children: loading ? "Loading..." : device?.userName || device?.userId || "Device Details" }), device?.userName && ((0, jsx_runtime_1.jsx)("small", { className: "text-muted", children: (0, jsx_runtime_1.jsx)("code", { children: device.userId }) }))] }), (0, jsx_runtime_1.jsxs)("div", { children: [device && ((0, jsx_runtime_1.jsxs)("button", { className: "btn btn-sm btn-danger me-2", onClick: handleDisconnect, children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-x-circle" }), " Disconnect"] })), (0, jsx_runtime_1.jsx)("button", { className: "btn-close", onClick: onClose, "aria-label": "Close" })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "card-body", style: { overflowY: "auto", maxHeight: "calc(90vh - 120px)" }, children: [loading && (0, jsx_runtime_1.jsx)(loading_indicator_1.LoadingIndicator, {}), !loading && (notFound || !device) && ((0, jsx_runtime_1.jsx)("div", { className: "alert alert-warning", children: "Device not found or connection has been closed." })), !loading && device && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "mb-4", children: [(0, jsx_runtime_1.jsx)("h6", { className: "border-bottom pb-2 mb-3", children: "Connection Information" }), (0, jsx_runtime_1.jsxs)("div", { className: "row", children: [device.deviceName && ((0, jsx_runtime_1.jsxs)("div", { className: "col-md-6 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Device Name:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)("span", { children: device.deviceName })] })), (0, jsx_runtime_1.jsxs)("div", { className: "col-md-6 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Device ID:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)("code", { className: "text-muted small", children: device.deviceId })] }), (0, jsx_runtime_1.jsxs)("div", { className: "col-md-6 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Connection State:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)("span", { className: "badge bg-success", children: device.connectionState })] }), (0, jsx_runtime_1.jsxs)("div", { className: "col-md-6 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "User Trust:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)(trust_level_badge_1.TrustLevelBadge, { level: device.userTrustLevel || peers_sdk_1.TrustLevel.Unknown })] }), (0, jsx_runtime_1.jsxs)("div", { className: "col-md-6 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Device Trust:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)(trust_level_badge_1.TrustLevelBadge, { level: device.deviceTrustLevel || peers_sdk_1.TrustLevel.Unknown })] }), (0, jsx_runtime_1.jsxs)("div", { className: "col-md-6 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Path Type:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)("span", { className: `badge bg-${device.pathType === "direct" ? "success" : "info"}`, children: device.pathType })] }), device.throughDeviceIds && device.throughDeviceIds.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "col-md-12 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Path:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)("small", { className: "text-muted", children: device.throughDeviceIds.join(" → ") })] }))] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "mb-4", children: [(0, jsx_runtime_1.jsx)("h6", { className: "border-bottom pb-2 mb-3", children: "Throughput" }), (0, jsx_runtime_1.jsxs)("div", { className: "row", children: [(0, jsx_runtime_1.jsxs)("div", { className: "col-md-6 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Send Rate:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsxs)("span", { className: "text-success", children: ["\u2191 ", formatRate(device.sendRateMBps)] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "col-md-6 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Receive Rate:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsxs)("span", { className: "text-primary", children: ["\u2193 ", formatRate(device.receiveRateMBps)] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "col-md-6 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Total Sent:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)("span", { children: formatBytes(device.bytesSent || 0) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "col-md-6 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Total Received:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)("span", { children: formatBytes(device.bytesReceived || 0) })] })] })] }), device.canReadRemoteLogs && (0, jsx_runtime_1.jsx)(remote_logs_panel_1.RemoteLogsPanel, { deviceId: device.deviceId }), device.sharedGroups.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "mb-4", children: [(0, jsx_runtime_1.jsxs)("h6", { className: "border-bottom pb-2 mb-3", children: ["Shared Groups (", device.sharedGroups.length, ")"] }), (0, jsx_runtime_1.jsx)("div", { className: "table-responsive", children: (0, jsx_runtime_1.jsxs)("table", { className: "table table-sm table-hover", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { children: "Group ID" }), (0, jsx_runtime_1.jsx)("th", { children: "Role" }), (0, jsx_runtime_1.jsx)("th", { children: "Sync Status" })] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: device.sharedGroups.map((groupId) => {
92
88
  const syncStats = device.syncStatsByGroup[groupId];
93
89
  const role = device.roles[groupId];
94
90
  return ((0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsx)("code", { className: "text-muted small", children: groupId }) }), (0, jsx_runtime_1.jsx)("td", { children: role != null ? String(role) : "N/A" }), (0, jsx_runtime_1.jsx)("td", { children: syncStats ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [syncStats.isPreferred && ((0, jsx_runtime_1.jsx)("span", { className: "badge bg-primary me-2", children: "Preferred" })), (0, jsx_runtime_1.jsx)("small", { className: "text-muted", children: new Date(syncStats.lastSyncTime).toLocaleString() })] })) : ((0, jsx_runtime_1.jsx)("span", { className: "text-muted", children: "-" })) })] }, groupId));
95
- }) })] }) })] })), device.networkInfo && ((0, jsx_runtime_1.jsxs)("div", { className: "mb-4", children: [(0, jsx_runtime_1.jsx)("h6", { className: "border-bottom pb-2 mb-3", children: "Remote Device Network Info" }), (0, jsx_runtime_1.jsxs)("div", { className: "row mb-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "col-md-3 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "CPU Usage:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsxs)("span", { className: "h5", children: [device.networkInfo.cpuPercent.toFixed(1), "%"] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "col-md-3 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Memory Usage:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsxs)("span", { className: "h5", children: [device.networkInfo.memPercent.toFixed(1), "%"] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "col-md-3 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Connection Slots:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)("span", { className: "h5", children: device.networkInfo.connectionSlotsAvailable })] }), (0, jsx_runtime_1.jsxs)("div", { className: "col-md-3 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Total Connections:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)("span", { className: "h5", children: device.networkInfo.connections.length })] })] }), device.networkInfo.preferredDeviceIds.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Preferred Devices:" }), (0, jsx_runtime_1.jsx)("ul", { className: "list-unstyled mt-2 ms-3", children: device.networkInfo.preferredDeviceIds.map((prefDeviceId) => ((0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsx)("code", { className: "text-muted small", children: prefDeviceId }) }, prefDeviceId))) })] })), device.networkInfo.connections.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("strong", { children: ["Remote Connections (", device.networkInfo.connections.length, "):"] }), (0, jsx_runtime_1.jsx)("div", { className: "table-responsive mt-2", children: (0, jsx_runtime_1.jsxs)("table", { className: "table table-sm table-hover", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { children: "Device ID" }), (0, jsx_runtime_1.jsx)("th", { children: "User ID" })] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: device.networkInfo.connections.map((conn, idx) => ((0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsx)("code", { className: "text-muted small", children: conn.deviceId }) }), (0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsx)("code", { className: "text-muted small", children: conn.userId }) })] }, idx))) })] }) })] }))] }))] }))] })] }) }));
91
+ }) })] }) })] })), device.networkInfo && ((0, jsx_runtime_1.jsxs)("div", { className: "mb-4", children: [(0, jsx_runtime_1.jsx)("h6", { className: "border-bottom pb-2 mb-3", children: "Remote Device Network Info" }), (0, jsx_runtime_1.jsxs)("div", { className: "row mb-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "col-md-3 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "CPU Usage:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsxs)("span", { className: "h5", children: [device.networkInfo.cpuPercent.toFixed(1), "%"] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "col-md-3 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Memory Usage:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsxs)("span", { className: "h5", children: [device.networkInfo.memPercent.toFixed(1), "%"] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "col-md-3 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Connection Slots:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)("span", { className: "h5", children: device.networkInfo.connectionSlotsAvailable })] }), (0, jsx_runtime_1.jsxs)("div", { className: "col-md-3 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Total Connections:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)("span", { className: "h5", children: device.networkInfo.connections.length })] })] }), device.networkInfo.preferredDeviceIds.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Preferred Devices:" }), (0, jsx_runtime_1.jsx)("ul", { className: "list-unstyled mt-2 ms-3", children: device.networkInfo.preferredDeviceIds.map((prefDeviceId) => ((0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsx)("code", { className: "text-muted small", children: prefDeviceId }) }, prefDeviceId))) })] })), device.networkInfo.connections.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("strong", { children: ["Remote Connections (", device.networkInfo.connections.length, "):"] }), (0, jsx_runtime_1.jsx)("div", { className: "table-responsive mt-2", children: (0, jsx_runtime_1.jsxs)("table", { className: "table table-sm table-hover", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsx)("tr", { children: (0, jsx_runtime_1.jsx)("th", { children: "Device ID" }) }) }), (0, jsx_runtime_1.jsx)("tbody", { children: device.networkInfo.connections.map((conn, idx) => ((0, jsx_runtime_1.jsx)("tr", { children: (0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsx)("code", { className: "text-muted small", children: conn.deviceId }) }) }, idx))) })] }) })] }))] }))] }))] })] }) }));
96
92
  }
@@ -15,18 +15,18 @@ function GroupDetailsModal({ groupId, onClose }) {
15
15
  const [downloading, setDownloading] = (0, react_1.useState)(false);
16
16
  const [downloadProgress, setDownloadProgress] = (0, react_1.useState)(null);
17
17
  const [downloadResult, setDownloadResult] = (0, react_1.useState)(null);
18
+ const [fullSyncing, setFullSyncing] = (0, react_1.useState)(false);
19
+ const [fullSyncResult, setFullSyncResult] = (0, react_1.useState)(null);
20
+ const [syncingNow, setSyncingNow] = (0, react_1.useState)(false);
21
+ const [syncNowResult, setSyncNowResult] = (0, react_1.useState)(null);
22
+ const platformApi = (0, network_viewer_ipc_1.getNetworkViewerPlatformApi)();
18
23
  const loadData = (0, react_1.useCallback)(async () => {
19
24
  try {
20
- const api = (0, network_viewer_ipc_1.getNetworkViewerApi)();
21
- if (!api) {
22
- console.warn("Network Viewer API not available");
23
- return;
24
- }
25
- const [groupData, syncDataUnknown] = await Promise.all([
26
- api.getGroupDetails(groupId),
27
- api.getGroupSyncStatus(groupId),
25
+ const diagnostics = await (0, network_viewer_ipc_1.getNetworkDiagnosticsConsumer)();
26
+ const [{ group: groupDetails }, { groups }] = await Promise.all([
27
+ diagnostics.tools.getGroupDetails({ groupId }),
28
+ diagnostics.tools.getGroupSyncStatus({ groupId }),
28
29
  ]);
29
- const groupDetails = groupData;
30
30
  if (!groupDetails) {
31
31
  setNotFound(true);
32
32
  }
@@ -34,9 +34,8 @@ function GroupDetailsModal({ groupId, onClose }) {
34
34
  setGroup(groupDetails);
35
35
  setNotFound(false);
36
36
  }
37
- const syncData = syncDataUnknown;
38
- if (syncData && syncData.length > 0) {
39
- setSyncInfo(syncData[0]);
37
+ if (groups.length > 0) {
38
+ setSyncInfo(groups[0]);
40
39
  }
41
40
  }
42
41
  catch (error) {
@@ -52,8 +51,7 @@ function GroupDetailsModal({ groupId, onClose }) {
52
51
  setDownloading(true);
53
52
  setDownloadProgress("Preparing download...");
54
53
  setDownloadResult(null);
55
- const api = (0, network_viewer_ipc_1.getNetworkViewerApi)();
56
- if (!api?.downloadRemoteDatabase) {
54
+ if (!platformApi?.downloadRemoteDatabase) {
57
55
  setDownloadResult({
58
56
  success: false,
59
57
  message: "Download API not available",
@@ -61,7 +59,7 @@ function GroupDetailsModal({ groupId, onClose }) {
61
59
  return;
62
60
  }
63
61
  setDownloadProgress("Downloading database...");
64
- const result = (await api.downloadRemoteDatabase(groupId, deviceId));
62
+ const result = (await platformApi.downloadRemoteDatabase(groupId, deviceId));
65
63
  setDownloadResult(result);
66
64
  if (result.success) {
67
65
  setDownloadProgress(null);
@@ -79,6 +77,51 @@ function GroupDetailsModal({ groupId, onClose }) {
79
77
  setDownloadProgress(null);
80
78
  }
81
79
  };
80
+ const handleSyncNow = async () => {
81
+ setSyncingNow(true);
82
+ setSyncNowResult(null);
83
+ try {
84
+ const control = await (0, network_viewer_ipc_1.getNetworkControlConsumer)();
85
+ const result = await control.tools.syncGroupNow({ groupId });
86
+ setSyncNowResult(result);
87
+ if (result.success) {
88
+ await loadData();
89
+ }
90
+ }
91
+ catch (error) {
92
+ setSyncNowResult({
93
+ success: false,
94
+ message: error instanceof Error ? error.message : String(error),
95
+ });
96
+ }
97
+ finally {
98
+ setSyncingNow(false);
99
+ }
100
+ };
101
+ const handleFullSync = async () => {
102
+ if (!confirm(`Do a full sync for "${group?.groupName || groupId}"? This resets local change tracking for only this context and may transfer substantial data.`)) {
103
+ return;
104
+ }
105
+ setFullSyncing(true);
106
+ setFullSyncResult(null);
107
+ try {
108
+ const control = await (0, network_viewer_ipc_1.getNetworkControlConsumer)();
109
+ const result = await control.tools.resetChangeTracking({ dataContextId: groupId });
110
+ setFullSyncResult(result);
111
+ if (result.success) {
112
+ await loadData();
113
+ }
114
+ }
115
+ catch (error) {
116
+ setFullSyncResult({
117
+ success: false,
118
+ message: error instanceof Error ? error.message : String(error),
119
+ });
120
+ }
121
+ finally {
122
+ setFullSyncing(false);
123
+ }
124
+ };
82
125
  (0, react_1.useEffect)(() => {
83
126
  loadData();
84
127
  }, [loadData]);
@@ -88,7 +131,7 @@ function GroupDetailsModal({ groupId, onClose }) {
88
131
  overflowY: "auto",
89
132
  paddingTop: "2rem",
90
133
  paddingBottom: "2rem",
91
- }, onClick: onClose, children: (0, jsx_runtime_1.jsxs)("div", { className: "card", style: { maxWidth: "900px", width: "90%", maxHeight: "90vh" }, onClick: (e) => e.stopPropagation(), children: [(0, jsx_runtime_1.jsxs)("div", { className: "card-header d-flex justify-content-between align-items-center", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h5", { className: "mb-0", children: loading ? "Loading..." : group?.groupName || "Group Details" }), group && ((0, jsx_runtime_1.jsx)("small", { className: "text-muted", children: (0, jsx_runtime_1.jsx)("code", { children: group.groupId }) }))] }), (0, jsx_runtime_1.jsx)("button", { className: "btn-close", onClick: onClose, "aria-label": "Close" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "card-body", style: { overflowY: "auto", maxHeight: "calc(90vh - 120px)" }, children: [loading && (0, jsx_runtime_1.jsx)(loading_indicator_1.LoadingIndicator, {}), !loading && (notFound || !group) && ((0, jsx_runtime_1.jsx)("div", { className: "alert alert-warning", children: "Group not found." })), !loading && group && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "mb-4", children: [(0, jsx_runtime_1.jsx)("h6", { className: "border-bottom pb-2 mb-3", children: "Group Information" }), (0, jsx_runtime_1.jsxs)("div", { className: "row", children: [(0, jsx_runtime_1.jsxs)("div", { className: "col-md-6 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Group ID:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)("code", { className: "text-muted small", children: group.groupId })] }), (0, jsx_runtime_1.jsxs)("div", { className: "col-md-6 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Group Name:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)("span", { children: group.groupName })] }), (0, jsx_runtime_1.jsxs)("div", { className: "col-md-6 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Founder:" }), (0, jsx_runtime_1.jsx)("br", {}), group.founderUserName ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { children: group.founderUserName }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)("code", { className: "text-muted small", children: group.founderUserId })] })) : ((0, jsx_runtime_1.jsx)("code", { className: "text-muted small", children: group.founderUserId }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "col-md-6 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Total Members:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)("span", { className: "h5", children: group.members.length })] })] })] }), group.members.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "mb-4", children: [(0, jsx_runtime_1.jsxs)("h6", { className: "border-bottom pb-2 mb-3", children: ["Members (", group.members.length, ")"] }), (0, jsx_runtime_1.jsx)("div", { className: "table-responsive", children: (0, jsx_runtime_1.jsxs)("table", { className: "table table-sm table-hover", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { children: "User" }), (0, jsx_runtime_1.jsx)("th", { children: "User ID" }), (0, jsx_runtime_1.jsx)("th", { children: "Role" }), (0, jsx_runtime_1.jsx)("th", { children: "Trust Level" })] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: group.members.map((member) => ((0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { children: member.userName || (0, jsx_runtime_1.jsx)("span", { className: "text-muted", children: "-" }) }), (0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsx)("code", { className: "text-muted small", children: member.userId }) }), (0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsx)("span", { className: "badge bg-primary", children: member.role }) }), (0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsx)(trust_level_badge_1.TrustLevelBadge, { level: member.trustLevel || peers_sdk_1.TrustLevel.Unknown }) })] }, member.userId))) })] }) })] })), (0, jsx_runtime_1.jsxs)("div", { className: "mb-4", children: [(0, jsx_runtime_1.jsxs)("h6", { className: "border-bottom pb-2 mb-3", children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-download me-2" }), "Download Database (Fast Sync)"] }), (0, jsx_runtime_1.jsx)("p", { className: "text-muted small mb-3", children: "Download the entire database from a connected device. This is useful for fast initial sync on a new device. The database will be saved as a backup file." }), syncInfo && syncInfo.connectedDevices.length > 0 ? ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { className: "form-label", children: "Select a connected device:" }), (0, jsx_runtime_1.jsx)("div", { className: "d-flex flex-wrap gap-2", children: syncInfo.connectedDevices.map((device) => ((0, jsx_runtime_1.jsx)("button", { className: "btn btn-outline-primary btn-sm", onClick: () => handleDownloadDatabase(device.deviceId), disabled: downloading, children: downloading ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { className: "spinner-border spinner-border-sm me-1", role: "status", "aria-hidden": "true" }), "Downloading..."] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-cloud-download me-1" }), device.deviceId.substring(0, 8), "...", (0, jsx_runtime_1.jsxs)("span", { className: "badge bg-secondary ms-1", children: [Math.round(device.latencyMs), "ms"] })] })) }, device.deviceId))) })] })) : ((0, jsx_runtime_1.jsxs)("div", { className: "alert alert-info mb-0", children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-info-circle me-2" }), "No devices are currently connected to this group. Connect to a device first to download its database."] })), downloadProgress && ((0, jsx_runtime_1.jsx)("div", { className: "mt-3", children: (0, jsx_runtime_1.jsxs)("div", { className: "d-flex align-items-center", children: [(0, jsx_runtime_1.jsx)("span", { className: "spinner-border spinner-border-sm me-2", role: "status", "aria-hidden": "true" }), (0, jsx_runtime_1.jsx)("span", { children: downloadProgress })] }) })), downloadResult && ((0, jsx_runtime_1.jsx)("div", { className: `alert mt-3 mb-0 ${downloadResult.success ? "alert-success" : "alert-danger"}`, children: downloadResult.success ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-check-circle me-2" }), (0, jsx_runtime_1.jsx)("strong", { children: "Download complete!" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsxs)("small", { children: ["File: ", (0, jsx_runtime_1.jsx)("code", { children: downloadResult.filePath }), (0, jsx_runtime_1.jsx)("br", {}), "Size:", " ", downloadResult.size
134
+ }, onClick: onClose, children: (0, jsx_runtime_1.jsxs)("div", { className: "card", style: { maxWidth: "900px", width: "90%", maxHeight: "90vh" }, onClick: (e) => e.stopPropagation(), children: [(0, jsx_runtime_1.jsxs)("div", { className: "card-header d-flex justify-content-between align-items-center", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h5", { className: "mb-0", children: loading ? "Loading..." : group?.groupName || "Group Details" }), group && ((0, jsx_runtime_1.jsx)("small", { className: "text-muted", children: (0, jsx_runtime_1.jsx)("code", { children: group.groupId }) }))] }), (0, jsx_runtime_1.jsx)("button", { className: "btn-close", onClick: onClose, "aria-label": "Close" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "card-body", style: { overflowY: "auto", maxHeight: "calc(90vh - 120px)" }, children: [loading && (0, jsx_runtime_1.jsx)(loading_indicator_1.LoadingIndicator, {}), !loading && (notFound || !group) && ((0, jsx_runtime_1.jsx)("div", { className: "alert alert-warning", children: "Group not found." })), !loading && group && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "mb-4", children: [(0, jsx_runtime_1.jsx)("h6", { className: "border-bottom pb-2 mb-3", children: "Group Information" }), (0, jsx_runtime_1.jsxs)("div", { className: "row", children: [(0, jsx_runtime_1.jsxs)("div", { className: "col-md-6 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Group ID:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)("code", { className: "text-muted small", children: group.groupId })] }), (0, jsx_runtime_1.jsxs)("div", { className: "col-md-6 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Group Name:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)("span", { children: group.groupName })] }), (0, jsx_runtime_1.jsxs)("div", { className: "col-md-6 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Founder:" }), (0, jsx_runtime_1.jsx)("br", {}), group.founderUserName ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { children: group.founderUserName }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)("code", { className: "text-muted small", children: group.founderUserId })] })) : ((0, jsx_runtime_1.jsx)("code", { className: "text-muted small", children: group.founderUserId }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "col-md-6 mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Total Members:" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)("span", { className: "h5", children: group.members.length })] })] })] }), group.members.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "mb-4", children: [(0, jsx_runtime_1.jsxs)("h6", { className: "border-bottom pb-2 mb-3", children: ["Members (", group.members.length, ")"] }), (0, jsx_runtime_1.jsx)("div", { className: "table-responsive", children: (0, jsx_runtime_1.jsxs)("table", { className: "table table-sm table-hover", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { children: "User" }), (0, jsx_runtime_1.jsx)("th", { children: "User ID" }), (0, jsx_runtime_1.jsx)("th", { children: "Role" }), (0, jsx_runtime_1.jsx)("th", { children: "Trust Level" })] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: group.members.map((member) => ((0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { children: member.userName || (0, jsx_runtime_1.jsx)("span", { className: "text-muted", children: "-" }) }), (0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsx)("code", { className: "text-muted small", children: member.userId }) }), (0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsx)("span", { className: "badge bg-primary", children: member.role }) }), (0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsx)(trust_level_badge_1.TrustLevelBadge, { level: member.trustLevel || peers_sdk_1.TrustLevel.Unknown }) })] }, member.userId))) })] }) })] })), (0, jsx_runtime_1.jsxs)("div", { className: "mb-4", children: [(0, jsx_runtime_1.jsxs)("h6", { className: "border-bottom pb-2 mb-3", children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-arrow-repeat me-2" }), "Synchronization"] }), (0, jsx_runtime_1.jsxs)("p", { className: "text-muted small mb-3", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Sync now" }), " notifies connected peers that this context may have changes. ", (0, jsx_runtime_1.jsx)("strong", { children: "Do full sync" }), " resets change tracking for this context and rebuilds synchronization state; that may transfer substantial data and should be used only to repair suspected drift."] }), (0, jsx_runtime_1.jsxs)("div", { className: "d-flex flex-wrap gap-2", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-outline-primary", onClick: () => void handleSyncNow(), disabled: syncingNow || fullSyncing, children: syncingNow ? "Sending sync notification..." : "Sync now" }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-outline-warning", onClick: () => void handleFullSync(), disabled: fullSyncing || syncingNow, children: fullSyncing ? "Starting full sync..." : "Do full sync" })] }), syncNowResult && ((0, jsx_runtime_1.jsx)("div", { className: `alert mt-3 mb-0 ${syncNowResult.success ? "alert-success" : "alert-danger"}`, role: "status", children: syncNowResult.message })), fullSyncResult && ((0, jsx_runtime_1.jsx)("div", { className: `alert mt-3 mb-0 ${fullSyncResult.success ? "alert-success" : "alert-danger"}`, role: "status", children: fullSyncResult.message }))] }), platformApi?.downloadRemoteDatabase ? ((0, jsx_runtime_1.jsxs)("div", { className: "mb-4", children: [(0, jsx_runtime_1.jsxs)("h6", { className: "border-bottom pb-2 mb-3", children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-download me-2" }), "Download Database (Fast Sync)"] }), (0, jsx_runtime_1.jsx)("p", { className: "text-muted small mb-3", children: "Download the entire database from a connected device. This is useful for fast initial sync on a new device. The database will be saved as a backup file." }), syncInfo && syncInfo.connectedDevices.length > 0 ? ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { className: "form-label", children: "Select a connected device:" }), (0, jsx_runtime_1.jsx)("div", { className: "d-flex flex-wrap gap-2", children: syncInfo.connectedDevices.map((device) => ((0, jsx_runtime_1.jsx)("button", { className: "btn btn-outline-primary btn-sm", onClick: () => handleDownloadDatabase(device.deviceId), disabled: downloading, children: downloading ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { className: "spinner-border spinner-border-sm me-1", role: "status", "aria-hidden": "true" }), "Downloading..."] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-cloud-download me-1" }), device.deviceId.substring(0, 8), "...", (0, jsx_runtime_1.jsxs)("span", { className: "badge bg-secondary ms-1", children: [Math.round(device.latencyMs), "ms"] })] })) }, device.deviceId))) })] })) : ((0, jsx_runtime_1.jsxs)("div", { className: "alert alert-info mb-0", children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-info-circle me-2" }), "No devices are currently connected to this group. Connect to a device first to download its database."] })), downloadProgress && ((0, jsx_runtime_1.jsx)("div", { className: "mt-3", children: (0, jsx_runtime_1.jsxs)("div", { className: "d-flex align-items-center", children: [(0, jsx_runtime_1.jsx)("span", { className: "spinner-border spinner-border-sm me-2", role: "status", "aria-hidden": "true" }), (0, jsx_runtime_1.jsx)("span", { children: downloadProgress })] }) })), downloadResult && ((0, jsx_runtime_1.jsx)("div", { className: `alert mt-3 mb-0 ${downloadResult.success ? "alert-success" : "alert-danger"}`, children: downloadResult.success ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-check-circle me-2" }), (0, jsx_runtime_1.jsx)("strong", { children: "Download complete!" }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsxs)("small", { children: ["File: ", (0, jsx_runtime_1.jsx)("code", { children: downloadResult.filePath }), (0, jsx_runtime_1.jsx)("br", {}), "Size:", " ", downloadResult.size
92
135
  ? `${(downloadResult.size / (1024 * 1024)).toFixed(2)} MB`
93
- : "Unknown"] })] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-exclamation-circle me-2" }), (0, jsx_runtime_1.jsx)("strong", { children: "Download failed:" }), " ", downloadResult.message] })) }))] })] }))] })] }) }));
136
+ : "Unknown"] })] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-exclamation-circle me-2" }), (0, jsx_runtime_1.jsx)("strong", { children: "Download failed:" }), " ", downloadResult.message] })) }))] })) : ((0, jsx_runtime_1.jsx)("div", { className: "alert alert-info mb-4", children: "Raw database download is available only in the Electron desktop app." }))] }))] })] }) }));
94
137
  }
@@ -1,42 +1,12 @@
1
- import type { IConsoleLog } from "@peers-app/peers-sdk";
2
- /** Filters and bounds accepted by the desktop remote-log diagnostic endpoint. */
3
- export type RemoteLogsQuery = {
4
- sinceTimestamp?: number;
5
- level?: IConsoleLog["level"];
6
- process?: string;
7
- search?: string;
8
- pageSize?: number;
9
- timeoutMs?: number;
10
- };
11
- /** Result of reading a directly connected, provider-authorized device's console-log table. */
12
- export type RemoteLogsResult = {
13
- deviceId: string;
14
- truncated: boolean;
15
- logs: IConsoleLog[];
16
- queriedAt: number;
17
- };
18
- /** Electron preload APIs used by the Network Viewer screen. */
19
- export type NetworkViewerElectronApi = {
20
- getOverview: () => Promise<unknown>;
21
- getConnections: () => Promise<unknown>;
22
- /** No-arg: all groups; with id: single group. */
23
- getGroupSyncStatus: (groupId?: string) => Promise<unknown>;
24
- getDisconnectedDevices: () => Promise<unknown>;
25
- refreshNetworkInfo: () => Promise<void>;
26
- disconnectDevice: (deviceId: string) => Promise<void>;
27
- connectToDevice: (deviceId: string) => Promise<{
28
- success: boolean;
29
- message?: string;
30
- }>;
31
- deleteDevice: (deviceId: string) => Promise<{
32
- success: boolean;
33
- message?: string;
34
- }>;
35
- getDeviceDetails: (deviceId: string) => Promise<unknown>;
36
- getGroupDetails: (groupId: string) => Promise<unknown>;
37
- getRemoteLogs: (deviceId: string, query?: RemoteLogsQuery) => Promise<RemoteLogsResult>;
1
+ import { type INetworkControlConsumer, type INetworkDiagnosticsConsumer } from "@peers-app/peers-sdk";
2
+ /** Electron-only capabilities intentionally excluded from portable network contracts. */
3
+ export type NetworkViewerPlatformApi = {
38
4
  downloadRemoteDatabase?: (groupId: string, deviceId: string) => Promise<unknown>;
39
5
  runDiagnostics: () => Promise<unknown>;
40
6
  };
41
- /** Return the context-isolated desktop diagnostics bridge when running in Electron. */
42
- export declare function getNetworkViewerApi(): NetworkViewerElectronApi | undefined;
7
+ /** Return a typed diagnostics consumer bound to the current local host transport. */
8
+ export declare function getNetworkDiagnosticsConsumer(): Promise<INetworkDiagnosticsConsumer>;
9
+ /** Return a typed control consumer bound to the current local host transport. */
10
+ export declare function getNetworkControlConsumer(): Promise<INetworkControlConsumer>;
11
+ /** Return the context-isolated desktop-only capability bridge, when available. */
12
+ export declare function getNetworkViewerPlatformApi(): NetworkViewerPlatformApi | undefined;
@@ -1,7 +1,75 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getNetworkViewerApi = getNetworkViewerApi;
4
- /** Return the context-isolated desktop diagnostics bridge when running in Electron. */
5
- function getNetworkViewerApi() {
3
+ exports.getNetworkDiagnosticsConsumer = getNetworkDiagnosticsConsumer;
4
+ exports.getNetworkControlConsumer = getNetworkControlConsumer;
5
+ exports.getNetworkViewerPlatformApi = getNetworkViewerPlatformApi;
6
+ const peers_sdk_1 = require("@peers-app/peers-sdk");
7
+ /** Default wait for the host transport before treating diagnostics as unavailable. */
8
+ const HOST_TRANSPORT_TIMEOUT_MS = 10_000;
9
+ let cachedConsumers;
10
+ let pendingConsumers;
11
+ function disposeCachedConsumers() {
12
+ cachedConsumers?.diagnostics.dispose();
13
+ cachedConsumers?.control.dispose();
14
+ cachedConsumers = undefined;
15
+ }
16
+ (0, peers_sdk_1.subscribeHostContractTransport)((transport) => {
17
+ if (cachedConsumers && cachedConsumers.transport !== transport) {
18
+ disposeCachedConsumers();
19
+ }
20
+ }, { emitCurrent: false });
21
+ async function loadConsumers(transport) {
22
+ disposeCachedConsumers();
23
+ const diagnostics = (0, peers_sdk_1.createContractConsumer)((0, peers_sdk_1.getNetworkDiagnosticsContractDefinition)(), transport);
24
+ const control = (0, peers_sdk_1.createContractConsumer)((0, peers_sdk_1.getNetworkControlContractDefinition)(), transport);
25
+ try {
26
+ await diagnostics.loadingPromise;
27
+ if ((0, peers_sdk_1.getHostContractTransport)() !== transport) {
28
+ diagnostics.dispose();
29
+ control.dispose();
30
+ // Clear before recursing so getConsumers can start a fresh in-flight memo for the
31
+ // current transport, and so this promise's finally does not wipe that newer memo.
32
+ pendingConsumers = undefined;
33
+ return getConsumers();
34
+ }
35
+ cachedConsumers = { transport, diagnostics, control };
36
+ return cachedConsumers;
37
+ }
38
+ catch (error) {
39
+ diagnostics.dispose();
40
+ control.dispose();
41
+ throw error;
42
+ }
43
+ }
44
+ async function getConsumers() {
45
+ const transport = (0, peers_sdk_1.getHostContractTransport)() ??
46
+ (await (0, peers_sdk_1.waitForHostContractTransport)({ timeoutMs: HOST_TRANSPORT_TIMEOUT_MS }));
47
+ if (cachedConsumers?.transport === transport) {
48
+ return cachedConsumers;
49
+ }
50
+ if (pendingConsumers) {
51
+ return pendingConsumers;
52
+ }
53
+ const loadPromise = loadConsumers(transport);
54
+ pendingConsumers = loadPromise;
55
+ try {
56
+ return await loadPromise;
57
+ }
58
+ finally {
59
+ if (pendingConsumers === loadPromise) {
60
+ pendingConsumers = undefined;
61
+ }
62
+ }
63
+ }
64
+ /** Return a typed diagnostics consumer bound to the current local host transport. */
65
+ async function getNetworkDiagnosticsConsumer() {
66
+ return (await getConsumers()).diagnostics;
67
+ }
68
+ /** Return a typed control consumer bound to the current local host transport. */
69
+ async function getNetworkControlConsumer() {
70
+ return (await getConsumers()).control;
71
+ }
72
+ /** Return the context-isolated desktop-only capability bridge, when available. */
73
+ function getNetworkViewerPlatformApi() {
6
74
  return window.electronAPI?.networkViewer;
7
75
  }
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const peers_sdk_1 = require("@peers-app/peers-sdk");
4
+ const network_viewer_ipc_1 = require("./network-viewer-ipc");
5
+ function setupHost(label) {
6
+ const [consumerTransport, providerTransport] = (0, peers_sdk_1.inProcessTransportPair)();
7
+ providerTransport.on(peers_sdk_1.CONTRACT_REQUEST_CHANNEL, (call) => {
8
+ if (call.member === "observable" && call.name === "status" && call.operation === "get") {
9
+ return {
10
+ state: "offline",
11
+ connectionCount: 0,
12
+ syncingContextCount: 0,
13
+ errorCount: 0,
14
+ errors: [],
15
+ };
16
+ }
17
+ if (call.member === "tool" && call.name === "getOverview") {
18
+ return { overview: { localDeviceId: label } };
19
+ }
20
+ if (call.member === "tool" && call.name === "refreshNetworkInfo") {
21
+ return { success: true, message: label };
22
+ }
23
+ return undefined;
24
+ });
25
+ return {
26
+ dispose: (0, peers_sdk_1.registerHostContractTransport)(consumerTransport),
27
+ };
28
+ }
29
+ describe("Network Viewer host consumers", () => {
30
+ it("caches consumers for one transport and recreates them after replacement", async () => {
31
+ const firstHost = setupHost((0, peers_sdk_1.newid)());
32
+ const firstDiagnostics = await (0, network_viewer_ipc_1.getNetworkDiagnosticsConsumer)();
33
+ expect(await (0, network_viewer_ipc_1.getNetworkDiagnosticsConsumer)()).toBe(firstDiagnostics);
34
+ const secondLabel = (0, peers_sdk_1.newid)();
35
+ const secondHost = setupHost(secondLabel);
36
+ const secondDiagnostics = await (0, network_viewer_ipc_1.getNetworkDiagnosticsConsumer)();
37
+ const control = await (0, network_viewer_ipc_1.getNetworkControlConsumer)();
38
+ expect(secondDiagnostics).not.toBe(firstDiagnostics);
39
+ await expect(firstDiagnostics.tools.getOverview()).rejects.toThrow("disposed");
40
+ await expect(control.tools.refreshNetworkInfo()).resolves.toEqual({
41
+ success: true,
42
+ message: secondLabel,
43
+ });
44
+ secondHost.dispose();
45
+ firstHost.dispose();
46
+ });
47
+ it("shares one consumer pair across concurrent callers", async () => {
48
+ const host = setupHost((0, peers_sdk_1.newid)());
49
+ const [first, second, third] = await Promise.all([
50
+ (0, network_viewer_ipc_1.getNetworkDiagnosticsConsumer)(),
51
+ (0, network_viewer_ipc_1.getNetworkDiagnosticsConsumer)(),
52
+ (0, network_viewer_ipc_1.getNetworkControlConsumer)(),
53
+ ]);
54
+ expect(second).toBe(first);
55
+ const cachedControl = await (0, network_viewer_ipc_1.getNetworkControlConsumer)();
56
+ expect(third).toBe(cachedControl);
57
+ host.dispose();
58
+ });
59
+ });