@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.
- 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
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CONTRACT_REQUEST_CHANNEL,
|
|
3
|
+
inProcessTransportPair,
|
|
4
|
+
newid,
|
|
5
|
+
registerHostContractTransport,
|
|
6
|
+
} from "@peers-app/peers-sdk";
|
|
7
|
+
import { getNetworkControlConsumer, getNetworkDiagnosticsConsumer } from "./network-viewer-ipc";
|
|
8
|
+
|
|
9
|
+
function setupHost(label: string) {
|
|
10
|
+
const [consumerTransport, providerTransport] = inProcessTransportPair();
|
|
11
|
+
providerTransport.on(CONTRACT_REQUEST_CHANNEL, (call) => {
|
|
12
|
+
if (call.member === "observable" && call.name === "status" && call.operation === "get") {
|
|
13
|
+
return {
|
|
14
|
+
state: "offline",
|
|
15
|
+
connectionCount: 0,
|
|
16
|
+
syncingContextCount: 0,
|
|
17
|
+
errorCount: 0,
|
|
18
|
+
errors: [],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
if (call.member === "tool" && call.name === "getOverview") {
|
|
22
|
+
return { overview: { localDeviceId: label } };
|
|
23
|
+
}
|
|
24
|
+
if (call.member === "tool" && call.name === "refreshNetworkInfo") {
|
|
25
|
+
return { success: true, message: label };
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
});
|
|
29
|
+
return {
|
|
30
|
+
dispose: registerHostContractTransport(consumerTransport),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe("Network Viewer host consumers", () => {
|
|
35
|
+
it("caches consumers for one transport and recreates them after replacement", async () => {
|
|
36
|
+
const firstHost = setupHost(newid());
|
|
37
|
+
const firstDiagnostics = await getNetworkDiagnosticsConsumer();
|
|
38
|
+
expect(await getNetworkDiagnosticsConsumer()).toBe(firstDiagnostics);
|
|
39
|
+
|
|
40
|
+
const secondLabel = newid();
|
|
41
|
+
const secondHost = setupHost(secondLabel);
|
|
42
|
+
const secondDiagnostics = await getNetworkDiagnosticsConsumer();
|
|
43
|
+
const control = await getNetworkControlConsumer();
|
|
44
|
+
|
|
45
|
+
expect(secondDiagnostics).not.toBe(firstDiagnostics);
|
|
46
|
+
await expect(firstDiagnostics.tools.getOverview()).rejects.toThrow("disposed");
|
|
47
|
+
await expect(control.tools.refreshNetworkInfo()).resolves.toEqual({
|
|
48
|
+
success: true,
|
|
49
|
+
message: secondLabel,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
secondHost.dispose();
|
|
53
|
+
firstHost.dispose();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("shares one consumer pair across concurrent callers", async () => {
|
|
57
|
+
const host = setupHost(newid());
|
|
58
|
+
const [first, second, third] = await Promise.all([
|
|
59
|
+
getNetworkDiagnosticsConsumer(),
|
|
60
|
+
getNetworkDiagnosticsConsumer(),
|
|
61
|
+
getNetworkControlConsumer(),
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
expect(second).toBe(first);
|
|
65
|
+
const cachedControl = await getNetworkControlConsumer();
|
|
66
|
+
expect(third).toBe(cachedControl);
|
|
67
|
+
|
|
68
|
+
host.dispose();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -1,46 +1,113 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
};
|
|
1
|
+
import {
|
|
2
|
+
createContractConsumer,
|
|
3
|
+
getHostContractTransport,
|
|
4
|
+
getNetworkControlContractDefinition,
|
|
5
|
+
getNetworkDiagnosticsContractDefinition,
|
|
6
|
+
type IContractTransport,
|
|
7
|
+
type INetworkControlConsumer,
|
|
8
|
+
type INetworkDiagnosticsConsumer,
|
|
9
|
+
subscribeHostContractTransport,
|
|
10
|
+
waitForHostContractTransport,
|
|
11
|
+
} from "@peers-app/peers-sdk";
|
|
12
12
|
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
deviceId: string;
|
|
16
|
-
truncated: boolean;
|
|
17
|
-
logs: IConsoleLog[];
|
|
18
|
-
queriedAt: number;
|
|
19
|
-
};
|
|
13
|
+
/** Default wait for the host transport before treating diagnostics as unavailable. */
|
|
14
|
+
const HOST_TRANSPORT_TIMEOUT_MS = 10_000;
|
|
20
15
|
|
|
21
|
-
/** Electron
|
|
22
|
-
export type
|
|
23
|
-
getOverview: () => Promise<unknown>;
|
|
24
|
-
getConnections: () => Promise<unknown>;
|
|
25
|
-
/** No-arg: all groups; with id: single group. */
|
|
26
|
-
getGroupSyncStatus: (groupId?: string) => Promise<unknown>;
|
|
27
|
-
getDisconnectedDevices: () => Promise<unknown>;
|
|
28
|
-
refreshNetworkInfo: () => Promise<void>;
|
|
29
|
-
disconnectDevice: (deviceId: string) => Promise<void>;
|
|
30
|
-
connectToDevice: (deviceId: string) => Promise<{ success: boolean; message?: string }>;
|
|
31
|
-
deleteDevice: (deviceId: string) => Promise<{ success: boolean; message?: string }>;
|
|
32
|
-
getDeviceDetails: (deviceId: string) => Promise<unknown>;
|
|
33
|
-
getGroupDetails: (groupId: string) => Promise<unknown>;
|
|
34
|
-
getRemoteLogs: (deviceId: string, query?: RemoteLogsQuery) => Promise<RemoteLogsResult>;
|
|
16
|
+
/** Electron-only capabilities intentionally excluded from portable network contracts. */
|
|
17
|
+
export type NetworkViewerPlatformApi = {
|
|
35
18
|
downloadRemoteDatabase?: (groupId: string, deviceId: string) => Promise<unknown>;
|
|
36
19
|
runDiagnostics: () => Promise<unknown>;
|
|
37
20
|
};
|
|
38
21
|
|
|
39
22
|
type WindowWithNetworkViewer = Window & {
|
|
40
|
-
electronAPI?: { networkViewer?:
|
|
23
|
+
electronAPI?: { networkViewer?: NetworkViewerPlatformApi };
|
|
41
24
|
};
|
|
42
25
|
|
|
43
|
-
|
|
44
|
-
|
|
26
|
+
interface INetworkViewerConsumers {
|
|
27
|
+
transport: IContractTransport;
|
|
28
|
+
diagnostics: INetworkDiagnosticsConsumer;
|
|
29
|
+
control: INetworkControlConsumer;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let cachedConsumers: INetworkViewerConsumers | undefined;
|
|
33
|
+
let pendingConsumers: Promise<INetworkViewerConsumers> | undefined;
|
|
34
|
+
|
|
35
|
+
function disposeCachedConsumers(): void {
|
|
36
|
+
cachedConsumers?.diagnostics.dispose();
|
|
37
|
+
cachedConsumers?.control.dispose();
|
|
38
|
+
cachedConsumers = undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
subscribeHostContractTransport(
|
|
42
|
+
(transport) => {
|
|
43
|
+
if (cachedConsumers && cachedConsumers.transport !== transport) {
|
|
44
|
+
disposeCachedConsumers();
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{ emitCurrent: false },
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
async function loadConsumers(transport: IContractTransport): Promise<INetworkViewerConsumers> {
|
|
51
|
+
disposeCachedConsumers();
|
|
52
|
+
const diagnostics = createContractConsumer(
|
|
53
|
+
getNetworkDiagnosticsContractDefinition(),
|
|
54
|
+
transport,
|
|
55
|
+
) as unknown as INetworkDiagnosticsConsumer;
|
|
56
|
+
const control = createContractConsumer(
|
|
57
|
+
getNetworkControlContractDefinition(),
|
|
58
|
+
transport,
|
|
59
|
+
) as unknown as INetworkControlConsumer;
|
|
60
|
+
try {
|
|
61
|
+
await diagnostics.loadingPromise;
|
|
62
|
+
if (getHostContractTransport() !== transport) {
|
|
63
|
+
diagnostics.dispose();
|
|
64
|
+
control.dispose();
|
|
65
|
+
// Clear before recursing so getConsumers can start a fresh in-flight memo for the
|
|
66
|
+
// current transport, and so this promise's finally does not wipe that newer memo.
|
|
67
|
+
pendingConsumers = undefined;
|
|
68
|
+
return getConsumers();
|
|
69
|
+
}
|
|
70
|
+
cachedConsumers = { transport, diagnostics, control };
|
|
71
|
+
return cachedConsumers;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
diagnostics.dispose();
|
|
74
|
+
control.dispose();
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function getConsumers(): Promise<INetworkViewerConsumers> {
|
|
80
|
+
const transport =
|
|
81
|
+
getHostContractTransport() ??
|
|
82
|
+
(await waitForHostContractTransport({ timeoutMs: HOST_TRANSPORT_TIMEOUT_MS }));
|
|
83
|
+
if (cachedConsumers?.transport === transport) {
|
|
84
|
+
return cachedConsumers;
|
|
85
|
+
}
|
|
86
|
+
if (pendingConsumers) {
|
|
87
|
+
return pendingConsumers;
|
|
88
|
+
}
|
|
89
|
+
const loadPromise = loadConsumers(transport);
|
|
90
|
+
pendingConsumers = loadPromise;
|
|
91
|
+
try {
|
|
92
|
+
return await loadPromise;
|
|
93
|
+
} finally {
|
|
94
|
+
if (pendingConsumers === loadPromise) {
|
|
95
|
+
pendingConsumers = undefined;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Return a typed diagnostics consumer bound to the current local host transport. */
|
|
101
|
+
export async function getNetworkDiagnosticsConsumer(): Promise<INetworkDiagnosticsConsumer> {
|
|
102
|
+
return (await getConsumers()).diagnostics;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Return a typed control consumer bound to the current local host transport. */
|
|
106
|
+
export async function getNetworkControlConsumer(): Promise<INetworkControlConsumer> {
|
|
107
|
+
return (await getConsumers()).control;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Return the context-isolated desktop-only capability bridge, when available. */
|
|
111
|
+
export function getNetworkViewerPlatformApi(): NetworkViewerPlatformApi | undefined {
|
|
45
112
|
return (window as WindowWithNetworkViewer).electronAPI?.networkViewer;
|
|
46
113
|
}
|
|
@@ -1,13 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type INetworkConnectionInfo,
|
|
3
|
+
type INetworkDisconnectedDevice,
|
|
4
|
+
type INetworkGroupSyncInfo,
|
|
5
|
+
type INetworkOverview,
|
|
6
|
+
TrustLevel,
|
|
7
|
+
} from "@peers-app/peers-sdk";
|
|
2
8
|
import { useCallback, useEffect, useState } from "react";
|
|
3
9
|
import { LoadingIndicator } from "../../components/loading-indicator";
|
|
4
10
|
import { TrustLevelBadge } from "../../components/trust-level-badge";
|
|
5
|
-
import { isDesktop } from "../../globals";
|
|
6
11
|
import { registerInternalPeersUI } from "../../ui-router/ui-loader";
|
|
7
12
|
import { ConnectionTroubleshooter } from "./connection-troubleshooter";
|
|
8
13
|
import { DeviceDetailsModal } from "./device-details-modal";
|
|
9
14
|
import { GroupDetailsModal } from "./group-details-modal";
|
|
10
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
getNetworkControlConsumer,
|
|
17
|
+
getNetworkDiagnosticsConsumer,
|
|
18
|
+
getNetworkViewerPlatformApi,
|
|
19
|
+
} from "./network-viewer-ipc";
|
|
11
20
|
import { UsageGraph } from "./usage-graph";
|
|
12
21
|
|
|
13
22
|
/** Format bytes to human-readable string (KB, MB, GB) */
|
|
@@ -29,81 +38,11 @@ function formatRate(mbps: number | undefined): string {
|
|
|
29
38
|
return `${mbps.toFixed(2)} MB/s`;
|
|
30
39
|
}
|
|
31
40
|
|
|
32
|
-
// TypeScript type definitions (matching backend)
|
|
33
|
-
interface INetworkOverview {
|
|
34
|
-
localDeviceId: string;
|
|
35
|
-
localUserId: string;
|
|
36
|
-
totalConnections: number;
|
|
37
|
-
availableSlots: number;
|
|
38
|
-
maxConnections: number;
|
|
39
|
-
cpuPercent: number;
|
|
40
|
-
memPercent: number;
|
|
41
|
-
groups: IGroupSummary[];
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
interface IGroupSummary {
|
|
45
|
-
groupId: string;
|
|
46
|
-
groupName: string;
|
|
47
|
-
connectedDevices: number;
|
|
48
|
-
totalMembers: number;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
interface IConnectionInfo {
|
|
52
|
-
deviceId: string;
|
|
53
|
-
userId: string;
|
|
54
|
-
userName?: string;
|
|
55
|
-
deviceName?: string;
|
|
56
|
-
userTrustLevel: number;
|
|
57
|
-
deviceTrustLevel: number;
|
|
58
|
-
connectionState: "connected" | "syncing" | "idle";
|
|
59
|
-
sharedGroups: string[];
|
|
60
|
-
roles: Record<string, unknown>;
|
|
61
|
-
pathType: "direct" | "indirect" | "unknown";
|
|
62
|
-
throughDeviceIds?: string[];
|
|
63
|
-
latencyMs: number;
|
|
64
|
-
errorRate: number;
|
|
65
|
-
isServer: boolean;
|
|
66
|
-
connectionAddress?: string;
|
|
67
|
-
// Throughput stats
|
|
68
|
-
bytesSent?: number;
|
|
69
|
-
bytesReceived?: number;
|
|
70
|
-
sendRateMBps?: number;
|
|
71
|
-
receiveRateMBps?: number;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
interface IDeviceConnection {
|
|
75
|
-
deviceId: string;
|
|
76
|
-
latencyMs: number;
|
|
77
|
-
errorRate: number;
|
|
78
|
-
timestampLastApplied: number;
|
|
79
|
-
closed?: boolean;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
interface IGroupSyncInfo {
|
|
83
|
-
groupId: string;
|
|
84
|
-
groupName: string;
|
|
85
|
-
connectedDevices: IDeviceConnection[];
|
|
86
|
-
preferredDeviceIds: string[];
|
|
87
|
-
lastSyncTimestamp: number;
|
|
88
|
-
totalMembers: number;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
interface IDisconnectedDevice {
|
|
92
|
-
deviceId: string;
|
|
93
|
-
userId: string;
|
|
94
|
-
userName?: string;
|
|
95
|
-
trustLevel: number;
|
|
96
|
-
firstSeen: Date;
|
|
97
|
-
lastSeen: Date;
|
|
98
|
-
name?: string;
|
|
99
|
-
serverUrl?: string;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
41
|
export function NetworkViewerList() {
|
|
103
42
|
const [overview, setOverview] = useState<INetworkOverview | null>(null);
|
|
104
|
-
const [connections, setConnections] = useState<
|
|
105
|
-
const [groupSyncInfo, setGroupSyncInfo] = useState<
|
|
106
|
-
const [disconnectedDevices, setDisconnectedDevices] = useState<
|
|
43
|
+
const [connections, setConnections] = useState<INetworkConnectionInfo[]>([]);
|
|
44
|
+
const [groupSyncInfo, setGroupSyncInfo] = useState<INetworkGroupSyncInfo[]>([]);
|
|
45
|
+
const [disconnectedDevices, setDisconnectedDevices] = useState<INetworkDisconnectedDevice[]>([]);
|
|
107
46
|
const [loading, setLoading] = useState(true);
|
|
108
47
|
const [refreshing, setRefreshing] = useState(false);
|
|
109
48
|
const [selectedDeviceId, setSelectedDeviceId] = useState<string | null>(null);
|
|
@@ -113,24 +52,23 @@ export function NetworkViewerList() {
|
|
|
113
52
|
|
|
114
53
|
const loadData = useCallback(async () => {
|
|
115
54
|
try {
|
|
116
|
-
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
api.getDisconnectedDevices(),
|
|
55
|
+
const diagnostics = await getNetworkDiagnosticsConsumer();
|
|
56
|
+
const [
|
|
57
|
+
{ overview: overviewData },
|
|
58
|
+
{ connections: connectionsData },
|
|
59
|
+
{ groups: groupSyncData },
|
|
60
|
+
{ devices: disconnectedData },
|
|
61
|
+
] = await Promise.all([
|
|
62
|
+
diagnostics.tools.getOverview(),
|
|
63
|
+
diagnostics.tools.getConnections(),
|
|
64
|
+
diagnostics.tools.getGroupSyncStatus(),
|
|
65
|
+
diagnostics.tools.getDisconnectedDevices(),
|
|
128
66
|
]);
|
|
129
67
|
|
|
130
|
-
setOverview(overviewData
|
|
131
|
-
setConnections(connectionsData
|
|
132
|
-
setGroupSyncInfo(groupSyncData
|
|
133
|
-
setDisconnectedDevices(disconnectedData
|
|
68
|
+
setOverview(overviewData);
|
|
69
|
+
setConnections(connectionsData);
|
|
70
|
+
setGroupSyncInfo(groupSyncData);
|
|
71
|
+
setDisconnectedDevices(disconnectedData);
|
|
134
72
|
} catch (error) {
|
|
135
73
|
console.error("Error loading network data:", error);
|
|
136
74
|
} finally {
|
|
@@ -141,10 +79,12 @@ export function NetworkViewerList() {
|
|
|
141
79
|
const handleRefresh = async () => {
|
|
142
80
|
setRefreshing(true);
|
|
143
81
|
try {
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
82
|
+
const control = await getNetworkControlConsumer();
|
|
83
|
+
const result = await control.tools.refreshNetworkInfo();
|
|
84
|
+
if (result.success) {
|
|
147
85
|
await loadData();
|
|
86
|
+
} else {
|
|
87
|
+
alert(result.message);
|
|
148
88
|
}
|
|
149
89
|
} catch (error) {
|
|
150
90
|
console.error("Error refreshing network info:", error);
|
|
@@ -159,10 +99,12 @@ export function NetworkViewerList() {
|
|
|
159
99
|
}
|
|
160
100
|
|
|
161
101
|
try {
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
102
|
+
const control = await getNetworkControlConsumer();
|
|
103
|
+
const result = await control.tools.disconnectDevice({ deviceId });
|
|
104
|
+
if (result.success) {
|
|
165
105
|
await loadData();
|
|
106
|
+
} else {
|
|
107
|
+
alert(result.message);
|
|
166
108
|
}
|
|
167
109
|
} catch (error) {
|
|
168
110
|
console.error("Error disconnecting device:", error);
|
|
@@ -175,16 +117,13 @@ export function NetworkViewerList() {
|
|
|
175
117
|
|
|
176
118
|
const handleConnectToDevice = async (deviceId: string) => {
|
|
177
119
|
try {
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
} else {
|
|
186
|
-
alert(`Failed to connect: ${result.message}`);
|
|
187
|
-
}
|
|
120
|
+
const control = await getNetworkControlConsumer();
|
|
121
|
+
setConnecting((prev) => new Set(prev).add(deviceId));
|
|
122
|
+
const result = await control.tools.connectToDevice({ deviceId });
|
|
123
|
+
if (result.success) {
|
|
124
|
+
await loadData();
|
|
125
|
+
} else {
|
|
126
|
+
alert(`Failed to connect: ${result.message}`);
|
|
188
127
|
}
|
|
189
128
|
} catch (error) {
|
|
190
129
|
console.error("Error connecting to device:", error);
|
|
@@ -200,15 +139,12 @@ export function NetworkViewerList() {
|
|
|
200
139
|
|
|
201
140
|
const handleDeleteDevice = async (deviceId: string) => {
|
|
202
141
|
try {
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
} else {
|
|
210
|
-
alert(`Failed to delete device: ${result.message}`);
|
|
211
|
-
}
|
|
142
|
+
const control = await getNetworkControlConsumer();
|
|
143
|
+
const result = await control.tools.deleteDevice({ deviceId });
|
|
144
|
+
if (result.success) {
|
|
145
|
+
await loadData();
|
|
146
|
+
} else {
|
|
147
|
+
alert(`Failed to delete device: ${result.message}`);
|
|
212
148
|
}
|
|
213
149
|
} catch (error) {
|
|
214
150
|
console.error("Error deleting device:", error);
|
|
@@ -227,16 +163,6 @@ export function NetworkViewerList() {
|
|
|
227
163
|
return <LoadingIndicator />;
|
|
228
164
|
}
|
|
229
165
|
|
|
230
|
-
if (!isDesktop()) {
|
|
231
|
-
return (
|
|
232
|
-
<div className="container-fluid p-4">
|
|
233
|
-
<div className="alert alert-info">
|
|
234
|
-
Network Viewer is only available in the desktop application.
|
|
235
|
-
</div>
|
|
236
|
-
</div>
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
166
|
if (!overview) {
|
|
241
167
|
return (
|
|
242
168
|
<div className="container-fluid p-4">
|
|
@@ -254,14 +180,16 @@ export function NetworkViewerList() {
|
|
|
254
180
|
<div className="d-flex justify-content-between align-items-center mb-4">
|
|
255
181
|
<h2>Network Viewer</h2>
|
|
256
182
|
<div className="d-flex gap-2">
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
183
|
+
{getNetworkViewerPlatformApi()?.runDiagnostics && (
|
|
184
|
+
<button
|
|
185
|
+
className="btn btn-sm btn-outline-warning"
|
|
186
|
+
onClick={() => setShowTroubleshooter(true)}
|
|
187
|
+
title="Run connection diagnostics"
|
|
188
|
+
>
|
|
189
|
+
<i className="bi bi-stethoscope me-1"></i>
|
|
190
|
+
Troubleshoot
|
|
191
|
+
</button>
|
|
192
|
+
)}
|
|
265
193
|
<button className="btn btn-sm btn-primary" onClick={handleRefresh} disabled={refreshing}>
|
|
266
194
|
<i className={`bi bi-arrow-clockwise ${refreshing ? "spin" : ""}`}></i>
|
|
267
195
|
{refreshing ? " Refreshing..." : " Refresh"}
|
|
@@ -294,10 +222,9 @@ export function NetworkViewerList() {
|
|
|
294
222
|
<UsageGraph
|
|
295
223
|
label="Connections"
|
|
296
224
|
fetchUsage={async () => {
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
return data.totalConnections;
|
|
225
|
+
const diagnostics = await getNetworkDiagnosticsConsumer();
|
|
226
|
+
const { overview: currentOverview } = await diagnostics.tools.getOverview();
|
|
227
|
+
return currentOverview.totalConnections;
|
|
301
228
|
}}
|
|
302
229
|
durationMinutes={5}
|
|
303
230
|
updateInterval={5000}
|
|
@@ -321,10 +248,9 @@ export function NetworkViewerList() {
|
|
|
321
248
|
<UsageGraph
|
|
322
249
|
label="CPU"
|
|
323
250
|
fetchUsage={async () => {
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
return data.cpuPercent;
|
|
251
|
+
const diagnostics = await getNetworkDiagnosticsConsumer();
|
|
252
|
+
const { overview: currentOverview } = await diagnostics.tools.getOverview();
|
|
253
|
+
return currentOverview.cpuPercent;
|
|
328
254
|
}}
|
|
329
255
|
durationMinutes={5}
|
|
330
256
|
updateInterval={5000}
|
|
@@ -344,10 +270,9 @@ export function NetworkViewerList() {
|
|
|
344
270
|
<UsageGraph
|
|
345
271
|
label="Memory"
|
|
346
272
|
fetchUsage={async () => {
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
return data.memPercent;
|
|
273
|
+
const diagnostics = await getNetworkDiagnosticsConsumer();
|
|
274
|
+
const { overview: currentOverview } = await diagnostics.tools.getOverview();
|
|
275
|
+
return currentOverview.memPercent;
|
|
351
276
|
}}
|
|
352
277
|
durationMinutes={5}
|
|
353
278
|
updateInterval={5000}
|
|
@@ -578,7 +503,10 @@ export function NetworkViewerList() {
|
|
|
578
503
|
{(device.errorRate * 100).toFixed(1)}% errors
|
|
579
504
|
</span>
|
|
580
505
|
<span className="badge bg-info">
|
|
581
|
-
Last:
|
|
506
|
+
Last:{" "}
|
|
507
|
+
{device.timestampLastApplied
|
|
508
|
+
? new Date(device.timestampLastApplied).toLocaleString()
|
|
509
|
+
: "Never"}
|
|
582
510
|
</span>
|
|
583
511
|
</div>
|
|
584
512
|
{idx < group.connectedDevices.length - 1 && (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { IConsoleLog } from "@peers-app/peers-sdk";
|
|
1
|
+
import type { IConsoleLog, INetworkRemoteLogsResult } from "@peers-app/peers-sdk";
|
|
2
2
|
import { useCallback, useState } from "react";
|
|
3
|
-
import {
|
|
3
|
+
import { getNetworkDiagnosticsConsumer } from "./network-viewer-ipc";
|
|
4
4
|
|
|
5
5
|
interface RemoteLogsPanelProps {
|
|
6
6
|
deviceId: string;
|
|
@@ -34,7 +34,7 @@ function RemoteLogRow({ log }: { log: IConsoleLog }) {
|
|
|
34
34
|
|
|
35
35
|
/** Read-only System Logs contract panel for a directly connected, provider-authorized device. */
|
|
36
36
|
export function RemoteLogsPanel({ deviceId }: RemoteLogsPanelProps) {
|
|
37
|
-
const [result, setResult] = useState<
|
|
37
|
+
const [result, setResult] = useState<INetworkRemoteLogsResult | null>(null);
|
|
38
38
|
const [loading, setLoading] = useState(false);
|
|
39
39
|
const [error, setError] = useState<string>();
|
|
40
40
|
const [processName, setProcessName] = useState("");
|
|
@@ -43,12 +43,6 @@ export function RemoteLogsPanel({ deviceId }: RemoteLogsPanelProps) {
|
|
|
43
43
|
const [minutes, setMinutes] = useState("60");
|
|
44
44
|
|
|
45
45
|
const loadLogs = useCallback(async () => {
|
|
46
|
-
const api = getNetworkViewerApi();
|
|
47
|
-
if (!api) {
|
|
48
|
-
setError("Network Viewer API is unavailable");
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
46
|
setLoading(true);
|
|
53
47
|
setError(undefined);
|
|
54
48
|
try {
|
|
@@ -57,13 +51,17 @@ export function RemoteLogsPanel({ deviceId }: RemoteLogsPanelProps) {
|
|
|
57
51
|
Number.isFinite(minutesNumber) && minutesNumber > 0
|
|
58
52
|
? Date.now() - minutesNumber * 60_000
|
|
59
53
|
: undefined;
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
54
|
+
const diagnostics = await getNetworkDiagnosticsConsumer();
|
|
55
|
+
const response = await diagnostics.tools.queryRemoteLogs({
|
|
56
|
+
deviceId,
|
|
57
|
+
query: {
|
|
58
|
+
sinceTimestamp,
|
|
59
|
+
level: level ? (level as IConsoleLog["level"]) : undefined,
|
|
60
|
+
process: processName || undefined,
|
|
61
|
+
search: search || undefined,
|
|
62
|
+
pageSize: 100,
|
|
63
|
+
timeoutMs: 10_000,
|
|
64
|
+
},
|
|
67
65
|
});
|
|
68
66
|
setResult(response);
|
|
69
67
|
} catch (cause) {
|