@peers-app/peers-ui 0.21.2 → 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.
- package/dist/screens/network-viewer/connection-troubleshooter.js +1 -1
- package/dist/screens/network-viewer/device-details-modal.js +11 -15
- package/dist/screens/network-viewer/group-details-modal.js +60 -17
- package/dist/screens/network-viewer/network-viewer-ipc.d.ts +9 -39
- package/dist/screens/network-viewer/network-viewer-ipc.js +71 -3
- package/dist/screens/network-viewer/network-viewer-ipc.test.d.ts +1 -0
- package/dist/screens/network-viewer/network-viewer-ipc.test.js +59 -0
- package/dist/screens/network-viewer/network-viewer.js +46 -60
- package/dist/screens/network-viewer/remote-logs-panel.js +11 -12
- package/dist/tabs-layout/network-status-indicator.d.ts +19 -0
- package/dist/tabs-layout/network-status-indicator.js +105 -0
- package/dist/tabs-layout/network-status-indicator.test.d.ts +1 -0
- package/dist/tabs-layout/network-status-indicator.test.js +81 -0
- package/dist/tabs-layout/tabs-layout.js +25 -24
- package/dist/tabs-layout/use-network-status.d.ts +8 -0
- package/dist/tabs-layout/use-network-status.js +57 -0
- package/dist/tabs-layout/use-network-status.test.d.ts +1 -0
- package/dist/tabs-layout/use-network-status.test.js +42 -0
- package/package.json +3 -3
- package/src/screens/network-viewer/connection-troubleshooter.tsx +2 -2
- package/src/screens/network-viewer/device-details-modal.tsx +19 -61
- package/src/screens/network-viewer/group-details-modal.tsx +211 -130
- package/src/screens/network-viewer/network-viewer-ipc.test.ts +70 -0
- package/src/screens/network-viewer/network-viewer-ipc.ts +102 -35
- package/src/screens/network-viewer/network-viewer.tsx +76 -148
- package/src/screens/network-viewer/remote-logs-panel.tsx +14 -16
- package/src/tabs-layout/network-status-indicator.test.tsx +94 -0
- package/src/tabs-layout/network-status-indicator.tsx +209 -0
- package/src/tabs-layout/tabs-layout.tsx +4 -1
- package/src/tabs-layout/use-network-status.test.tsx +55 -0
- package/src/tabs-layout/use-network-status.ts +68 -0
|
@@ -6,7 +6,6 @@ const peers_sdk_1 = require("@peers-app/peers-sdk");
|
|
|
6
6
|
const react_1 = require("react");
|
|
7
7
|
const loading_indicator_1 = require("../../components/loading-indicator");
|
|
8
8
|
const trust_level_badge_1 = require("../../components/trust-level-badge");
|
|
9
|
-
const globals_1 = require("../../globals");
|
|
10
9
|
const ui_loader_1 = require("../../ui-router/ui-loader");
|
|
11
10
|
const connection_troubleshooter_1 = require("./connection-troubleshooter");
|
|
12
11
|
const device_details_modal_1 = require("./device-details-modal");
|
|
@@ -47,18 +46,12 @@ function NetworkViewerList() {
|
|
|
47
46
|
const [showTroubleshooter, setShowTroubleshooter] = (0, react_1.useState)(false);
|
|
48
47
|
const loadData = (0, react_1.useCallback)(async () => {
|
|
49
48
|
try {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
const [overviewData, connectionsData, groupSyncData, disconnectedData] = await Promise.all([
|
|
58
|
-
api.getOverview(),
|
|
59
|
-
api.getConnections(),
|
|
60
|
-
api.getGroupSyncStatus(),
|
|
61
|
-
api.getDisconnectedDevices(),
|
|
49
|
+
const diagnostics = await (0, network_viewer_ipc_1.getNetworkDiagnosticsConsumer)();
|
|
50
|
+
const [{ overview: overviewData }, { connections: connectionsData }, { groups: groupSyncData }, { devices: disconnectedData },] = await Promise.all([
|
|
51
|
+
diagnostics.tools.getOverview(),
|
|
52
|
+
diagnostics.tools.getConnections(),
|
|
53
|
+
diagnostics.tools.getGroupSyncStatus(),
|
|
54
|
+
diagnostics.tools.getDisconnectedDevices(),
|
|
62
55
|
]);
|
|
63
56
|
setOverview(overviewData);
|
|
64
57
|
setConnections(connectionsData);
|
|
@@ -75,11 +68,14 @@ function NetworkViewerList() {
|
|
|
75
68
|
const handleRefresh = async () => {
|
|
76
69
|
setRefreshing(true);
|
|
77
70
|
try {
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
71
|
+
const control = await (0, network_viewer_ipc_1.getNetworkControlConsumer)();
|
|
72
|
+
const result = await control.tools.refreshNetworkInfo();
|
|
73
|
+
if (result.success) {
|
|
81
74
|
await loadData();
|
|
82
75
|
}
|
|
76
|
+
else {
|
|
77
|
+
alert(result.message);
|
|
78
|
+
}
|
|
83
79
|
}
|
|
84
80
|
catch (error) {
|
|
85
81
|
console.error("Error refreshing network info:", error);
|
|
@@ -93,11 +89,14 @@ function NetworkViewerList() {
|
|
|
93
89
|
return;
|
|
94
90
|
}
|
|
95
91
|
try {
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
92
|
+
const control = await (0, network_viewer_ipc_1.getNetworkControlConsumer)();
|
|
93
|
+
const result = await control.tools.disconnectDevice({ deviceId });
|
|
94
|
+
if (result.success) {
|
|
99
95
|
await loadData();
|
|
100
96
|
}
|
|
97
|
+
else {
|
|
98
|
+
alert(result.message);
|
|
99
|
+
}
|
|
101
100
|
}
|
|
102
101
|
catch (error) {
|
|
103
102
|
console.error("Error disconnecting device:", error);
|
|
@@ -108,17 +107,14 @@ function NetworkViewerList() {
|
|
|
108
107
|
};
|
|
109
108
|
const handleConnectToDevice = async (deviceId) => {
|
|
110
109
|
try {
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
else {
|
|
120
|
-
alert(`Failed to connect: ${result.message}`);
|
|
121
|
-
}
|
|
110
|
+
const control = await (0, network_viewer_ipc_1.getNetworkControlConsumer)();
|
|
111
|
+
setConnecting((prev) => new Set(prev).add(deviceId));
|
|
112
|
+
const result = await control.tools.connectToDevice({ deviceId });
|
|
113
|
+
if (result.success) {
|
|
114
|
+
await loadData();
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
alert(`Failed to connect: ${result.message}`);
|
|
122
118
|
}
|
|
123
119
|
}
|
|
124
120
|
catch (error) {
|
|
@@ -135,16 +131,13 @@ function NetworkViewerList() {
|
|
|
135
131
|
};
|
|
136
132
|
const handleDeleteDevice = async (deviceId) => {
|
|
137
133
|
try {
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
else {
|
|
146
|
-
alert(`Failed to delete device: ${result.message}`);
|
|
147
|
-
}
|
|
134
|
+
const control = await (0, network_viewer_ipc_1.getNetworkControlConsumer)();
|
|
135
|
+
const result = await control.tools.deleteDevice({ deviceId });
|
|
136
|
+
if (result.success) {
|
|
137
|
+
await loadData();
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
alert(`Failed to delete device: ${result.message}`);
|
|
148
141
|
}
|
|
149
142
|
}
|
|
150
143
|
catch (error) {
|
|
@@ -161,41 +154,32 @@ function NetworkViewerList() {
|
|
|
161
154
|
if (loading) {
|
|
162
155
|
return (0, jsx_runtime_1.jsx)(loading_indicator_1.LoadingIndicator, {});
|
|
163
156
|
}
|
|
164
|
-
if (!(0, globals_1.isDesktop)()) {
|
|
165
|
-
return ((0, jsx_runtime_1.jsx)("div", { className: "container-fluid p-4", children: (0, jsx_runtime_1.jsx)("div", { className: "alert alert-info", children: "Network Viewer is only available in the desktop application." }) }));
|
|
166
|
-
}
|
|
167
157
|
if (!overview) {
|
|
168
158
|
return ((0, jsx_runtime_1.jsx)("div", { className: "container-fluid p-4", children: (0, jsx_runtime_1.jsx)("div", { className: "alert alert-warning", children: "Unable to load network information." }) }));
|
|
169
159
|
}
|
|
170
160
|
if (showTroubleshooter) {
|
|
171
161
|
return (0, jsx_runtime_1.jsx)(connection_troubleshooter_1.ConnectionTroubleshooter, { onBack: () => setShowTroubleshooter(false) });
|
|
172
162
|
}
|
|
173
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: "container-fluid p-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "d-flex justify-content-between align-items-center mb-4", children: [(0, jsx_runtime_1.jsx)("h2", { children: "Network Viewer" }), (0, jsx_runtime_1.jsxs)("div", { className: "d-flex gap-2", children: [(0, jsx_runtime_1.jsxs)("button", { className: "btn btn-sm btn-outline-warning", onClick: () => setShowTroubleshooter(true), title: "Run connection diagnostics", children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-stethoscope me-1" }), "Troubleshoot"] }), (0, jsx_runtime_1.jsxs)("button", { className: "btn btn-sm btn-primary", onClick: handleRefresh, disabled: refreshing, children: [(0, jsx_runtime_1.jsx)("i", { className: `bi bi-arrow-clockwise ${refreshing ? "spin" : ""}` }), refreshing ? " Refreshing..." : " Refresh"] })] })] }), (0, jsx_runtime_1.jsx)("div", { className: "card mb-2", children: (0, jsx_runtime_1.jsxs)("div", { className: "card-body", children: [(0, jsx_runtime_1.jsx)("h5", { className: "card-title", children: "Local Device" }), (0, jsx_runtime_1.jsxs)("div", { className: "row", children: [(0, jsx_runtime_1.jsxs)("div", { className: "col-md-6", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Device ID:" }), " ", (0, jsx_runtime_1.jsx)("code", { className: "text-muted small", children: overview.localDeviceId })] }), (0, jsx_runtime_1.jsxs)("div", { className: "col-md-6", children: [(0, jsx_runtime_1.jsx)("strong", { children: "User ID:" }), " ", (0, jsx_runtime_1.jsx)("code", { className: "text-muted small", children: overview.localUserId })] })] })] }) }), (0, jsx_runtime_1.jsxs)("div", { className: "row mb-4", children: [(0, jsx_runtime_1.jsx)("div", { className: "col-md-4", children: (0, jsx_runtime_1.jsx)("div", { className: "card", style: { height: "100%" }, children: (0, jsx_runtime_1.jsx)("div", { className: "card-body p-2", children: (0, jsx_runtime_1.jsx)(usage_graph_1.UsageGraph, { label: "Connections", fetchUsage: async () => {
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const data = (await api.getOverview());
|
|
178
|
-
return data.totalConnections;
|
|
163
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "container-fluid p-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "d-flex justify-content-between align-items-center mb-4", children: [(0, jsx_runtime_1.jsx)("h2", { children: "Network Viewer" }), (0, jsx_runtime_1.jsxs)("div", { className: "d-flex gap-2", children: [(0, network_viewer_ipc_1.getNetworkViewerPlatformApi)()?.runDiagnostics && ((0, jsx_runtime_1.jsxs)("button", { className: "btn btn-sm btn-outline-warning", onClick: () => setShowTroubleshooter(true), title: "Run connection diagnostics", children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-stethoscope me-1" }), "Troubleshoot"] })), (0, jsx_runtime_1.jsxs)("button", { className: "btn btn-sm btn-primary", onClick: handleRefresh, disabled: refreshing, children: [(0, jsx_runtime_1.jsx)("i", { className: `bi bi-arrow-clockwise ${refreshing ? "spin" : ""}` }), refreshing ? " Refreshing..." : " Refresh"] })] })] }), (0, jsx_runtime_1.jsx)("div", { className: "card mb-2", children: (0, jsx_runtime_1.jsxs)("div", { className: "card-body", children: [(0, jsx_runtime_1.jsx)("h5", { className: "card-title", children: "Local Device" }), (0, jsx_runtime_1.jsxs)("div", { className: "row", children: [(0, jsx_runtime_1.jsxs)("div", { className: "col-md-6", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Device ID:" }), " ", (0, jsx_runtime_1.jsx)("code", { className: "text-muted small", children: overview.localDeviceId })] }), (0, jsx_runtime_1.jsxs)("div", { className: "col-md-6", children: [(0, jsx_runtime_1.jsx)("strong", { children: "User ID:" }), " ", (0, jsx_runtime_1.jsx)("code", { className: "text-muted small", children: overview.localUserId })] })] })] }) }), (0, jsx_runtime_1.jsxs)("div", { className: "row mb-4", children: [(0, jsx_runtime_1.jsx)("div", { className: "col-md-4", children: (0, jsx_runtime_1.jsx)("div", { className: "card", style: { height: "100%" }, children: (0, jsx_runtime_1.jsx)("div", { className: "card-body p-2", children: (0, jsx_runtime_1.jsx)(usage_graph_1.UsageGraph, { label: "Connections", fetchUsage: async () => {
|
|
164
|
+
const diagnostics = await (0, network_viewer_ipc_1.getNetworkDiagnosticsConsumer)();
|
|
165
|
+
const { overview: currentOverview } = await diagnostics.tools.getOverview();
|
|
166
|
+
return currentOverview.totalConnections;
|
|
179
167
|
}, durationMinutes: 5, updateInterval: 5000, color: "rgba(111, 66, 193, 1)", colorZones: [
|
|
180
168
|
{ maxValue: 1, color: "rgba(220, 53, 69, 1)" },
|
|
181
169
|
{ maxValue: 2, color: "rgba(255, 193, 7, 1)" },
|
|
182
170
|
{ maxValue: overview?.maxConnections || 30, color: "rgba(111, 66, 193, 1)" },
|
|
183
171
|
], minValue: 0, maxValue: overview?.maxConnections || 30, formatOverlayText: (value) => `Connections: ${Math.round(value)}` }) }) }) }), (0, jsx_runtime_1.jsx)("div", { className: "col-md-4", children: (0, jsx_runtime_1.jsx)("div", { className: "card", style: { height: "100%" }, children: (0, jsx_runtime_1.jsx)("div", { className: "card-body p-2", children: (0, jsx_runtime_1.jsx)(usage_graph_1.UsageGraph, { label: "CPU", fetchUsage: async () => {
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const data = (await api.getOverview());
|
|
188
|
-
return data.cpuPercent;
|
|
172
|
+
const diagnostics = await (0, network_viewer_ipc_1.getNetworkDiagnosticsConsumer)();
|
|
173
|
+
const { overview: currentOverview } = await diagnostics.tools.getOverview();
|
|
174
|
+
return currentOverview.cpuPercent;
|
|
189
175
|
}, durationMinutes: 5, updateInterval: 5000, color: "rgba(13, 110, 253, 1)", colorZones: [
|
|
190
176
|
{ maxValue: 60, color: "rgba(13, 110, 253, 1)" },
|
|
191
177
|
{ maxValue: 80, color: "rgba(255, 193, 7, 1)" },
|
|
192
178
|
{ maxValue: Infinity, color: "rgba(220, 53, 69, 1)" },
|
|
193
179
|
] }) }) }) }), (0, jsx_runtime_1.jsx)("div", { className: "col-md-4", children: (0, jsx_runtime_1.jsx)("div", { className: "card", style: { height: "100%" }, children: (0, jsx_runtime_1.jsx)("div", { className: "card-body p-2", children: (0, jsx_runtime_1.jsx)(usage_graph_1.UsageGraph, { label: "Memory", fetchUsage: async () => {
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const data = (await api.getOverview());
|
|
198
|
-
return data.memPercent;
|
|
180
|
+
const diagnostics = await (0, network_viewer_ipc_1.getNetworkDiagnosticsConsumer)();
|
|
181
|
+
const { overview: currentOverview } = await diagnostics.tools.getOverview();
|
|
182
|
+
return currentOverview.memPercent;
|
|
199
183
|
}, durationMinutes: 5, updateInterval: 5000, color: "rgba(25, 135, 84, 1)", colorZones: [
|
|
200
184
|
{ maxValue: 60, color: "rgba(25, 135, 84, 1)" },
|
|
201
185
|
{ maxValue: 80, color: "rgba(255, 193, 7, 1)" },
|
|
@@ -218,7 +202,9 @@ function NetworkViewerList() {
|
|
|
218
202
|
? "success"
|
|
219
203
|
: device.errorRate < 0.2
|
|
220
204
|
? "warning"
|
|
221
|
-
: "danger"}`, children: [(device.errorRate * 100).toFixed(1), "% errors"] }), (0, jsx_runtime_1.jsxs)("span", { className: "badge bg-info", children: ["Last:
|
|
205
|
+
: "danger"}`, children: [(device.errorRate * 100).toFixed(1), "% errors"] }), (0, jsx_runtime_1.jsxs)("span", { className: "badge bg-info", children: ["Last:", " ", device.timestampLastApplied
|
|
206
|
+
? new Date(device.timestampLastApplied).toLocaleString()
|
|
207
|
+
: "Never"] })] }), idx < group.connectedDevices.length - 1 && ((0, jsx_runtime_1.jsx)("hr", { className: "my-2" }))] }, device.deviceId))) })] })) : ((0, jsx_runtime_1.jsx)("span", { className: "text-muted", children: "No connected devices" })) }), (0, jsx_runtime_1.jsx)("td", { children: group.preferredDeviceIds.length > 0 ? ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("span", { className: "badge bg-primary mb-2", children: [group.preferredDeviceIds.length, " preferred"] }), (0, jsx_runtime_1.jsx)("div", { children: group.preferredDeviceIds.map((deviceId) => ((0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)("small", { children: (0, jsx_runtime_1.jsx)("code", { className: "text-muted", children: deviceId }) }) }, deviceId))) })] })) : ((0, jsx_runtime_1.jsx)("span", { className: "text-muted", children: "None" })) }), (0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsx)("small", { children: group.lastSyncTimestamp > 0
|
|
222
208
|
? new Date(group.lastSyncTimestamp).toLocaleString()
|
|
223
209
|
: "Never" }) }), (0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsx)("button", { className: "btn btn-sm btn-outline-primary", onClick: () => setSelectedGroupId(group.groupId), title: "View Group Details", children: group.totalMembers }) })] }, group.groupId))) })] }) }))] }) }), (0, jsx_runtime_1.jsx)("div", { className: "card", children: (0, jsx_runtime_1.jsxs)("div", { className: "card-body", children: [(0, jsx_runtime_1.jsx)("h5", { className: "card-title", children: "Disconnected Devices" }), disconnectedDevices.length === 0 ? ((0, jsx_runtime_1.jsx)("p", { className: "text-muted", children: "No known disconnected devices" })) : ((0, jsx_runtime_1.jsx)("div", { className: "table-responsive", children: (0, jsx_runtime_1.jsxs)("table", { className: "table 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: "Trust Level" }), (0, jsx_runtime_1.jsx)("th", { children: "Device ID" }), (0, jsx_runtime_1.jsx)("th", { children: "Device Name" }), (0, jsx_runtime_1.jsx)("th", { children: "First Seen" }), (0, jsx_runtime_1.jsx)("th", { children: "Last Seen" }), (0, jsx_runtime_1.jsx)("th", { children: "Actions" })] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: disconnectedDevices
|
|
224
210
|
.sort((a, b) => new Date(b.lastSeen).getTime() - new Date(a.lastSeen).getTime())
|
|
@@ -17,11 +17,6 @@ function RemoteLogsPanel({ deviceId }) {
|
|
|
17
17
|
const [search, setSearch] = (0, react_1.useState)("");
|
|
18
18
|
const [minutes, setMinutes] = (0, react_1.useState)("60");
|
|
19
19
|
const loadLogs = (0, react_1.useCallback)(async () => {
|
|
20
|
-
const api = (0, network_viewer_ipc_1.getNetworkViewerApi)();
|
|
21
|
-
if (!api) {
|
|
22
|
-
setError("Network Viewer API is unavailable");
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
20
|
setLoading(true);
|
|
26
21
|
setError(undefined);
|
|
27
22
|
try {
|
|
@@ -29,13 +24,17 @@ function RemoteLogsPanel({ deviceId }) {
|
|
|
29
24
|
const sinceTimestamp = Number.isFinite(minutesNumber) && minutesNumber > 0
|
|
30
25
|
? Date.now() - minutesNumber * 60_000
|
|
31
26
|
: undefined;
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
27
|
+
const diagnostics = await (0, network_viewer_ipc_1.getNetworkDiagnosticsConsumer)();
|
|
28
|
+
const response = await diagnostics.tools.queryRemoteLogs({
|
|
29
|
+
deviceId,
|
|
30
|
+
query: {
|
|
31
|
+
sinceTimestamp,
|
|
32
|
+
level: level ? level : undefined,
|
|
33
|
+
process: processName || undefined,
|
|
34
|
+
search: search || undefined,
|
|
35
|
+
pageSize: 100,
|
|
36
|
+
timeoutMs: 10_000,
|
|
37
|
+
},
|
|
39
38
|
});
|
|
40
39
|
setResult(response);
|
|
41
40
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type INetworkStatusViewState } from "./use-network-status";
|
|
2
|
+
interface INetworkStatusPresentation {
|
|
3
|
+
label: string;
|
|
4
|
+
shortLabel: string;
|
|
5
|
+
iconClass: string;
|
|
6
|
+
colorClass: string;
|
|
7
|
+
spinning?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/** Convert host availability and network state into accessible indicator text and styling. */
|
|
10
|
+
export declare function getNetworkStatusPresentation(viewState: INetworkStatusViewState): INetworkStatusPresentation;
|
|
11
|
+
/** Accessible reactive network status button and details popover. */
|
|
12
|
+
export declare function NetworkStatusIndicator({ compact }: {
|
|
13
|
+
compact?: boolean;
|
|
14
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
/** Fixed tabs-strip area for built-in shell indicators. */
|
|
16
|
+
export declare function TabsStripIndicators({ compact }: {
|
|
17
|
+
compact?: boolean;
|
|
18
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getNetworkStatusPresentation = getNetworkStatusPresentation;
|
|
4
|
+
exports.NetworkStatusIndicator = NetworkStatusIndicator;
|
|
5
|
+
exports.TabsStripIndicators = TabsStripIndicators;
|
|
6
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
7
|
+
const react_1 = require("react");
|
|
8
|
+
const tabs_state_1 = require("./tabs-state");
|
|
9
|
+
const use_network_status_1 = require("./use-network-status");
|
|
10
|
+
/** Convert host availability and network state into accessible indicator text and styling. */
|
|
11
|
+
function getNetworkStatusPresentation(viewState) {
|
|
12
|
+
if (viewState.availability === "loading") {
|
|
13
|
+
return {
|
|
14
|
+
label: "Network status loading",
|
|
15
|
+
shortLabel: "Loading",
|
|
16
|
+
iconClass: "bi-arrow-repeat",
|
|
17
|
+
colorClass: "text-secondary",
|
|
18
|
+
spinning: true,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
if (viewState.availability === "unavailable" || !viewState.status) {
|
|
22
|
+
return {
|
|
23
|
+
label: "Network status unavailable",
|
|
24
|
+
shortLabel: "Unavailable",
|
|
25
|
+
iconClass: "bi-question-circle",
|
|
26
|
+
colorClass: "text-secondary",
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const { connectionCount, errorCount, state, syncingContextCount } = viewState.status;
|
|
30
|
+
if (state === "error") {
|
|
31
|
+
return {
|
|
32
|
+
label: `Network error; ${errorCount} sync ${errorCount === 1 ? "error" : "errors"}; ${connectionCount} active ${connectionCount === 1 ? "connection" : "connections"}`,
|
|
33
|
+
shortLabel: "Error",
|
|
34
|
+
iconClass: "bi-exclamation-triangle-fill",
|
|
35
|
+
colorClass: "text-danger",
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
if (state === "syncing") {
|
|
39
|
+
return {
|
|
40
|
+
label: `Network syncing ${syncingContextCount} ${syncingContextCount === 1 ? "context" : "contexts"}; ${connectionCount} active ${connectionCount === 1 ? "connection" : "connections"}`,
|
|
41
|
+
shortLabel: "Syncing",
|
|
42
|
+
iconClass: "bi-arrow-repeat",
|
|
43
|
+
colorClass: "text-primary",
|
|
44
|
+
spinning: true,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
if (state === "offline") {
|
|
48
|
+
return {
|
|
49
|
+
label: "Network offline; no active connections",
|
|
50
|
+
shortLabel: "Offline",
|
|
51
|
+
iconClass: "bi-cloud-slash-fill",
|
|
52
|
+
colorClass: "text-secondary",
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
label: `Network synced; ${connectionCount} active ${connectionCount === 1 ? "connection" : "connections"}`,
|
|
57
|
+
shortLabel: "Synced",
|
|
58
|
+
iconClass: "bi-check-circle-fill",
|
|
59
|
+
colorClass: "text-success",
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function NetworkStatusDetails({ detailsId, status, unavailable, onOpenViewer, }) {
|
|
63
|
+
return ((0, jsx_runtime_1.jsx)("div", { id: detailsId, role: "dialog", "aria-label": "Network status details", className: "card shadow position-absolute end-0 mt-1", style: { top: "100%", width: "280px", zIndex: 1100 }, children: (0, jsx_runtime_1.jsxs)("div", { className: "card-body p-3", children: [(0, jsx_runtime_1.jsx)("h6", { className: "card-title mb-2", children: "Network status" }), unavailable || !status ? ((0, jsx_runtime_1.jsx)("p", { className: "small text-muted mb-3", children: "Network diagnostics are temporarily unavailable. The local host may still be starting or reconnecting." })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("dl", { className: "row small mb-2", children: [(0, jsx_runtime_1.jsx)("dt", { className: "col-7", children: "Connections" }), (0, jsx_runtime_1.jsx)("dd", { className: "col-5 text-end", children: status.connectionCount }), (0, jsx_runtime_1.jsx)("dt", { className: "col-7", children: "Syncing contexts" }), (0, jsx_runtime_1.jsx)("dd", { className: "col-5 text-end", children: status.syncingContextCount }), (0, jsx_runtime_1.jsx)("dt", { className: "col-7", children: "Sync errors" }), (0, jsx_runtime_1.jsx)("dd", { className: "col-5 text-end", children: status.errorCount })] }), status.lastSuccessfulSyncAt && ((0, jsx_runtime_1.jsxs)("p", { className: "small text-muted mb-2", children: ["Last successful sync: ", new Date(status.lastSuccessfulSyncAt).toLocaleString()] })), status.errors.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "alert alert-danger py-2 px-2 small mb-2", children: [status.errors[0].message, status.errors.length > 1 && ` (+${status.errors.length - 1} more)`] }))] })), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-primary w-100", onClick: onOpenViewer, children: "Open Network Viewer" })] }) }));
|
|
64
|
+
}
|
|
65
|
+
/** Accessible reactive network status button and details popover. */
|
|
66
|
+
function NetworkStatusIndicator({ compact = false }) {
|
|
67
|
+
const viewState = (0, use_network_status_1.useNetworkStatus)();
|
|
68
|
+
const presentation = getNetworkStatusPresentation(viewState);
|
|
69
|
+
const [open, setOpen] = (0, react_1.useState)(false);
|
|
70
|
+
const containerRef = (0, react_1.useRef)(null);
|
|
71
|
+
const detailsId = (0, react_1.useId)();
|
|
72
|
+
(0, react_1.useEffect)(() => {
|
|
73
|
+
if (!open) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const closeOutside = (event) => {
|
|
77
|
+
if (!containerRef.current?.contains(event.target)) {
|
|
78
|
+
setOpen(false);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const closeOnEscape = (event) => {
|
|
82
|
+
if (event.key === "Escape") {
|
|
83
|
+
setOpen(false);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
document.addEventListener("mousedown", closeOutside);
|
|
87
|
+
document.addEventListener("keydown", closeOnEscape);
|
|
88
|
+
return () => {
|
|
89
|
+
document.removeEventListener("mousedown", closeOutside);
|
|
90
|
+
document.removeEventListener("keydown", closeOnEscape);
|
|
91
|
+
};
|
|
92
|
+
}, [open]);
|
|
93
|
+
return ((0, jsx_runtime_1.jsxs)("div", { ref: containerRef, className: "position-relative d-flex align-items-center", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: `btn btn-sm d-flex align-items-center gap-1 px-2 ${presentation.colorClass}`, style: {
|
|
94
|
+
border: "none",
|
|
95
|
+
background: "transparent",
|
|
96
|
+
minWidth: compact ? "30px" : undefined,
|
|
97
|
+
}, "aria-label": presentation.label, "aria-expanded": open, "aria-controls": detailsId, title: presentation.label, onClick: () => setOpen((current) => !current), children: [(0, jsx_runtime_1.jsx)("i", { className: `bi ${presentation.iconClass} ${presentation.spinning ? "spin" : ""}`, "aria-hidden": "true" }), !compact && ((0, jsx_runtime_1.jsxs)("span", { className: "small", children: [viewState.status?.connectionCount ?? 0, (0, jsx_runtime_1.jsx)("span", { className: "visually-hidden", children: " connections" })] }))] }), open && ((0, jsx_runtime_1.jsx)(NetworkStatusDetails, { detailsId: detailsId, status: viewState.status, unavailable: viewState.availability !== "ready", onOpenViewer: () => {
|
|
98
|
+
setOpen(false);
|
|
99
|
+
(0, tabs_state_1.goToTabPath)("network-viewer");
|
|
100
|
+
} }))] }));
|
|
101
|
+
}
|
|
102
|
+
/** Fixed tabs-strip area for built-in shell indicators. */
|
|
103
|
+
function TabsStripIndicators({ compact = false }) {
|
|
104
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: "d-flex align-items-center flex-shrink-0", role: "group", "aria-label": "Application status", children: (0, jsx_runtime_1.jsx)(NetworkStatusIndicator, { compact: compact }) }));
|
|
105
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
4
|
+
const react_1 = require("@testing-library/react");
|
|
5
|
+
const network_status_indicator_1 = require("./network-status-indicator");
|
|
6
|
+
const goToTabPath = jest.fn();
|
|
7
|
+
let mockViewState = { availability: "loading" };
|
|
8
|
+
jest.mock("./tabs-state", () => ({
|
|
9
|
+
goToTabPath: (...args) => goToTabPath(...args),
|
|
10
|
+
}));
|
|
11
|
+
jest.mock("./use-network-status", () => ({
|
|
12
|
+
useNetworkStatus: () => mockViewState,
|
|
13
|
+
}));
|
|
14
|
+
function status(overrides) {
|
|
15
|
+
return {
|
|
16
|
+
state: "synced",
|
|
17
|
+
connectionCount: 2,
|
|
18
|
+
syncingContextCount: 0,
|
|
19
|
+
errorCount: 0,
|
|
20
|
+
errors: [],
|
|
21
|
+
...overrides,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
describe("NetworkStatusIndicator", () => {
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
mockViewState = { availability: "loading" };
|
|
27
|
+
jest.clearAllMocks();
|
|
28
|
+
});
|
|
29
|
+
it.each([
|
|
30
|
+
[{ availability: "loading" }, /Network status loading/],
|
|
31
|
+
[{ availability: "unavailable" }, /Network status unavailable/],
|
|
32
|
+
[
|
|
33
|
+
{ availability: "ready", status: status({ state: "offline", connectionCount: 0 }) },
|
|
34
|
+
/Network offline/,
|
|
35
|
+
],
|
|
36
|
+
[
|
|
37
|
+
{
|
|
38
|
+
availability: "ready",
|
|
39
|
+
status: status({ state: "syncing", syncingContextCount: 1 }),
|
|
40
|
+
},
|
|
41
|
+
/Network syncing/,
|
|
42
|
+
],
|
|
43
|
+
[{ availability: "ready", status: status({ state: "synced" }) }, /Network synced/],
|
|
44
|
+
[
|
|
45
|
+
{
|
|
46
|
+
availability: "ready",
|
|
47
|
+
status: status({
|
|
48
|
+
state: "error",
|
|
49
|
+
errorCount: 1,
|
|
50
|
+
errors: [{ dataContextId: "context", message: "failed", occurredAt: 1 }],
|
|
51
|
+
}),
|
|
52
|
+
},
|
|
53
|
+
/Network error/,
|
|
54
|
+
],
|
|
55
|
+
])("renders an accessible state for %#", (viewState, accessibleName) => {
|
|
56
|
+
mockViewState = viewState;
|
|
57
|
+
const view = (0, react_1.render)((0, jsx_runtime_1.jsx)(network_status_indicator_1.NetworkStatusIndicator, {}));
|
|
58
|
+
expect(view.getByRole("button", { name: accessibleName })).toBeTruthy();
|
|
59
|
+
});
|
|
60
|
+
it("updates on rerender and opens details before navigating to Network Viewer", () => {
|
|
61
|
+
const view = (0, react_1.render)((0, jsx_runtime_1.jsx)(network_status_indicator_1.NetworkStatusIndicator, {}));
|
|
62
|
+
expect(view.getByRole("button", { name: /loading/ })).toBeTruthy();
|
|
63
|
+
mockViewState = {
|
|
64
|
+
availability: "ready",
|
|
65
|
+
status: status({
|
|
66
|
+
state: "error",
|
|
67
|
+
connectionCount: 3,
|
|
68
|
+
errorCount: 1,
|
|
69
|
+
errors: [{ dataContextId: "context", message: "sync failed", occurredAt: 1 }],
|
|
70
|
+
}),
|
|
71
|
+
};
|
|
72
|
+
view.rerender((0, jsx_runtime_1.jsx)(network_status_indicator_1.NetworkStatusIndicator, {}));
|
|
73
|
+
react_1.fireEvent.click(view.getByRole("button", { name: /Network error/ }));
|
|
74
|
+
expect(view.getByRole("dialog", { name: "Network status details" })).toHaveTextContent("sync failed");
|
|
75
|
+
react_1.fireEvent.keyDown(document, { key: "Escape" });
|
|
76
|
+
expect(view.queryByRole("dialog", { name: "Network status details" })).toBeNull();
|
|
77
|
+
react_1.fireEvent.click(view.getByRole("button", { name: /Network error/ }));
|
|
78
|
+
react_1.fireEvent.click(view.getByRole("button", { name: "Open Network Viewer" }));
|
|
79
|
+
expect(goToTabPath).toHaveBeenCalledWith("network-viewer");
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -48,6 +48,7 @@ const color_mode_dropdown_1 = require("../screens/settings/color-mode-dropdown")
|
|
|
48
48
|
const system_apps_1 = require("../system-apps");
|
|
49
49
|
const routes_loader_1 = require("../ui-router/routes-loader");
|
|
50
50
|
const ui_loader_1 = require("../ui-router/ui-loader");
|
|
51
|
+
const network_status_indicator_1 = require("./network-status-indicator");
|
|
51
52
|
const tabs_state_1 = require("./tabs-state");
|
|
52
53
|
/**
|
|
53
54
|
* Wraps a single tab's content so a crash in one tab doesn't take down the whole app.
|
|
@@ -167,7 +168,7 @@ function TabsLayoutInternal({ fillContainer = false } = {}) {
|
|
|
167
168
|
}, children: (0, jsx_runtime_1.jsx)("i", { className: "bi-search", style: { fontSize: "14px" } }) }), (0, jsx_runtime_1.jsx)("div", { className: "d-flex flex-grow-1 overflow-auto", style: {
|
|
168
169
|
scrollbarWidth: "none",
|
|
169
170
|
msOverflowStyle: "none",
|
|
170
|
-
}, children: tabs.map((tab) => ((0, jsx_runtime_1.jsx)(TabHeader, { tab: tab, isActive: activeTab === tab.tabId, onSwitch: switchTab, onClose: closeTab, colorMode: _colorMode }, tab.tabId))) })] })) }), (0, jsx_runtime_1.jsx)("div", { className: "flex-grow-1 overflow-hidden", children: tabs.map((tab) => ((0, jsx_runtime_1.jsx)("div", { className: `h-100 ${activeTab === tab.tabId ? "d-block" : "d-none"}`, style: { display: activeTab === tab.tabId ? "block" : "none" }, children: (0, jsx_runtime_1.jsx)(TabErrorBoundary, { tabTitle: tab.title, tabId: tab.tabId, children: (0, jsx_runtime_1.jsx)(TabContent, { tab: tab, isMobile: isMobile, isActive: activeTab === tab.tabId }) }) }, tab.tabId))) })] }, currentlyActiveGroupId));
|
|
171
|
+
}, children: tabs.map((tab) => ((0, jsx_runtime_1.jsx)(TabHeader, { tab: tab, isActive: activeTab === tab.tabId, onSwitch: switchTab, onClose: closeTab, colorMode: _colorMode }, tab.tabId))) }), (0, jsx_runtime_1.jsx)(network_status_indicator_1.TabsStripIndicators, {})] })) }), (0, jsx_runtime_1.jsx)("div", { className: "flex-grow-1 overflow-hidden", children: tabs.map((tab) => ((0, jsx_runtime_1.jsx)("div", { className: `h-100 ${activeTab === tab.tabId ? "d-block" : "d-none"}`, style: { display: activeTab === tab.tabId ? "block" : "none" }, children: (0, jsx_runtime_1.jsx)(TabErrorBoundary, { tabTitle: tab.title, tabId: tab.tabId, children: (0, jsx_runtime_1.jsx)(TabContent, { tab: tab, isMobile: isMobile, isActive: activeTab === tab.tabId }) }) }, tab.tabId))) })] }, currentlyActiveGroupId));
|
|
171
172
|
}
|
|
172
173
|
function MobileTabsHeader({ tabs, activeTab, onSwitch, onClose, colorMode, }) {
|
|
173
174
|
const [showMenuDropdown, setShowMenuDropdown] = react_1.default.useState(false);
|
|
@@ -178,29 +179,29 @@ function MobileTabsHeader({ tabs, activeTab, onSwitch, onClose, colorMode, }) {
|
|
|
178
179
|
transform: "translateX(-50%)",
|
|
179
180
|
pointerEvents: "none",
|
|
180
181
|
maxWidth: "calc(100% - 140px)",
|
|
181
|
-
}, children: [currentTab?.iconClassName && currentTab.packageId !== "launcher" && ((0, jsx_runtime_1.jsx)("i", { className: `${currentTab.iconClassName} me-2` })), (0, jsx_runtime_1.jsx)("span", { className: "fw-medium text-truncate small", children: currentTab?.title || "Apps" })] }), (0, jsx_runtime_1.
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
182
|
+
}, children: [currentTab?.iconClassName && currentTab.packageId !== "launcher" && ((0, jsx_runtime_1.jsx)("i", { className: `${currentTab.iconClassName} me-2` })), (0, jsx_runtime_1.jsx)("span", { className: "fw-medium text-truncate small", children: currentTab?.title || "Apps" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "d-flex align-items-center gap-1", children: [(0, jsx_runtime_1.jsx)(network_status_indicator_1.TabsStripIndicators, { compact: true }), (0, jsx_runtime_1.jsxs)("div", { className: "dropdown", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: `btn btn-sm ${colorMode === "light" ? "btn-outline-dark" : "btn-outline-light"}`, onClick: () => setShowMenuDropdown(!showMenuDropdown), style: { minWidth: "36px" }, children: [(0, jsx_runtime_1.jsx)("i", { className: "bi-list" }), nonLauncherTabs.length > 0 && (0, jsx_runtime_1.jsx)("span", { className: "ms-1", children: nonLauncherTabs.length })] }), showMenuDropdown && ((0, jsx_runtime_1.jsxs)("div", { className: `dropdown-menu show position-absolute ${colorMode === "light" ? "" : "dropdown-menu-dark"}`, style: {
|
|
183
|
+
right: 0,
|
|
184
|
+
top: "100%",
|
|
185
|
+
zIndex: 1000,
|
|
186
|
+
minWidth: "280px",
|
|
187
|
+
maxHeight: "calc(100vh - 60px)",
|
|
188
|
+
overflowY: "auto",
|
|
189
|
+
}, children: [(0, jsx_runtime_1.jsxs)("div", { className: `dropdown-item d-flex align-items-center ${activeTab === "launcher" ? "active" : ""}`, style: { cursor: "pointer", padding: "14px 18px", fontSize: "17px" }, onClick: () => {
|
|
190
|
+
onSwitch("launcher");
|
|
191
|
+
setShowMenuDropdown(false);
|
|
192
|
+
}, children: [(0, jsx_runtime_1.jsx)("i", { className: "bi-grid-3x3-gap me-3", style: { fontSize: "20px" } }), (0, jsx_runtime_1.jsx)("span", { children: "Apps" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dropdown-item d-flex align-items-center", style: { cursor: "pointer", padding: "14px 18px", fontSize: "17px" }, onClick: () => {
|
|
193
|
+
(0, command_palette_1.openCommandPalette)();
|
|
194
|
+
setShowMenuDropdown(false);
|
|
195
|
+
}, children: [(0, jsx_runtime_1.jsx)("i", { className: "bi-search me-3", style: { fontSize: "20px" } }), (0, jsx_runtime_1.jsx)("span", { children: "Search" })] }), nonLauncherTabs.length > 0 && ((0, jsx_runtime_1.jsx)("div", { className: "dropdown-divider", style: { margin: "8px 0" } })), nonLauncherTabs
|
|
196
|
+
.slice()
|
|
197
|
+
.reverse()
|
|
198
|
+
.map((tab) => ((0, jsx_runtime_1.jsxs)("div", { className: `dropdown-item d-flex align-items-center justify-content-between ${activeTab === tab.tabId ? "active" : ""}`, style: { cursor: "pointer", padding: "14px 18px", fontSize: "17px" }, onClick: () => {
|
|
199
|
+
onSwitch(tab.tabId);
|
|
200
|
+
setShowMenuDropdown(false);
|
|
201
|
+
}, children: [(0, jsx_runtime_1.jsxs)("div", { className: "d-flex align-items-center", children: [tab.iconClassName && ((0, jsx_runtime_1.jsx)("i", { className: `${tab.iconClassName} me-3`, style: { fontSize: "20px" } })), (0, jsx_runtime_1.jsx)("span", { children: tab.title })] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm p-0 ms-3", style: { width: "32px", height: "32px", fontSize: "20px" }, onClick: (e) => {
|
|
202
|
+
e.stopPropagation();
|
|
203
|
+
onClose(tab.tabId);
|
|
204
|
+
}, children: (0, jsx_runtime_1.jsx)("i", { className: "bi-x" }) })] }, tab.tabId)))] }))] })] }), showMenuDropdown && ((0, jsx_runtime_1.jsx)("div", { className: "position-fixed w-100 h-100", style: { top: 0, left: 0, zIndex: 999 }, onClick: () => setShowMenuDropdown(false) }))] }));
|
|
204
205
|
}
|
|
205
206
|
function TabHeader({ tab, isActive, onSwitch, onClose, colorMode }) {
|
|
206
207
|
const activeClass = isActive
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type INetworkStatusSummary } from "@peers-app/peers-sdk";
|
|
2
|
+
/** Host availability plus the latest reactive network status snapshot. */
|
|
3
|
+
export interface INetworkStatusViewState {
|
|
4
|
+
availability: "loading" | "ready" | "unavailable";
|
|
5
|
+
status?: INetworkStatusSummary;
|
|
6
|
+
}
|
|
7
|
+
/** Subscribe to the current host's System Network Diagnostics status observable. */
|
|
8
|
+
export declare function useNetworkStatus(): INetworkStatusViewState;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useNetworkStatus = useNetworkStatus;
|
|
4
|
+
const peers_sdk_1 = require("@peers-app/peers-sdk");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const network_viewer_ipc_1 = require("../screens/network-viewer/network-viewer-ipc");
|
|
7
|
+
/** Subscribe to the current host's System Network Diagnostics status observable. */
|
|
8
|
+
function useNetworkStatus() {
|
|
9
|
+
const [viewState, setViewState] = (0, react_1.useState)({
|
|
10
|
+
availability: "loading",
|
|
11
|
+
});
|
|
12
|
+
(0, react_1.useEffect)(() => {
|
|
13
|
+
let active = true;
|
|
14
|
+
let generation = 0;
|
|
15
|
+
let statusSubscription;
|
|
16
|
+
let hadTransport = false;
|
|
17
|
+
const unsubscribeTransport = (0, peers_sdk_1.subscribeHostContractTransport)((transport) => {
|
|
18
|
+
generation += 1;
|
|
19
|
+
const currentGeneration = generation;
|
|
20
|
+
statusSubscription?.dispose();
|
|
21
|
+
statusSubscription = undefined;
|
|
22
|
+
if (!transport) {
|
|
23
|
+
setViewState({ availability: hadTransport ? "unavailable" : "loading" });
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
hadTransport = true;
|
|
27
|
+
setViewState({ availability: "loading" });
|
|
28
|
+
void (0, network_viewer_ipc_1.getNetworkDiagnosticsConsumer)()
|
|
29
|
+
.then((diagnostics) => {
|
|
30
|
+
if (!active || generation !== currentGeneration) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
setViewState({
|
|
34
|
+
availability: "ready",
|
|
35
|
+
status: diagnostics.observables.status(),
|
|
36
|
+
});
|
|
37
|
+
statusSubscription = diagnostics.observables.status.subscribe((status) => {
|
|
38
|
+
if (active && generation === currentGeneration) {
|
|
39
|
+
setViewState({ availability: "ready", status });
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
})
|
|
43
|
+
.catch(() => {
|
|
44
|
+
if (active && generation === currentGeneration) {
|
|
45
|
+
setViewState({ availability: "unavailable" });
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
return () => {
|
|
50
|
+
active = false;
|
|
51
|
+
generation += 1;
|
|
52
|
+
statusSubscription?.dispose();
|
|
53
|
+
unsubscribeTransport();
|
|
54
|
+
};
|
|
55
|
+
}, []);
|
|
56
|
+
return viewState;
|
|
57
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|