@peers-app/peers-ui 0.25.8 → 0.25.9
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/network-viewer-section.d.ts +9 -0
- package/dist/screens/network-viewer/network-viewer-section.js +11 -0
- package/dist/screens/network-viewer/network-viewer.d.ts +6 -0
- package/dist/screens/network-viewer/network-viewer.js +19 -1
- package/dist/screens/network-viewer/network-viewer.section.test.d.ts +1 -0
- package/dist/screens/network-viewer/network-viewer.section.test.js +63 -0
- package/dist/screens/network-viewer/sync-recovery.d.ts +8 -0
- package/dist/screens/network-viewer/sync-recovery.js +182 -0
- package/dist/screens/network-viewer/sync-recovery.test.d.ts +1 -0
- package/dist/screens/network-viewer/sync-recovery.test.js +104 -0
- package/dist/screens/settings/settings-page.js +13 -3
- package/dist/tabs-layout/network-status-indicator.js +39 -11
- package/dist/tabs-layout/network-status-indicator.test.js +109 -0
- package/dist/tabs-layout/use-network-status.test.js +1 -0
- package/package.json +3 -3
- package/src/screens/network-viewer/network-viewer-section.ts +12 -0
- package/src/screens/network-viewer/network-viewer.section.test.tsx +68 -0
- package/src/screens/network-viewer/network-viewer.tsx +27 -0
- package/src/screens/network-viewer/sync-recovery.test.tsx +114 -0
- package/src/screens/network-viewer/sync-recovery.tsx +461 -0
- package/src/screens/settings/settings-page.tsx +15 -5
- package/src/tabs-layout/network-status-indicator.test.tsx +131 -0
- package/src/tabs-layout/network-status-indicator.tsx +121 -43
- package/src/tabs-layout/use-network-status.test.tsx +1 -0
|
@@ -3,6 +3,7 @@ import moment from "moment";
|
|
|
3
3
|
import { useEffect, useId, useRef, useState } from "react";
|
|
4
4
|
import { IDENTITY_ACCEPT_PATH, IDENTITY_ACCOUNT_PATH } from "../screens/identity/identity-routes";
|
|
5
5
|
import { getNetworkDiagnosticsConsumer } from "../screens/network-viewer/network-viewer-ipc";
|
|
6
|
+
import { networkViewerSection } from "../screens/network-viewer/network-viewer-section";
|
|
6
7
|
import { goToTabPath } from "./tabs-state";
|
|
7
8
|
import { type INetworkStatusViewState, useNetworkStatus } from "./use-network-status";
|
|
8
9
|
|
|
@@ -59,6 +60,24 @@ function connectedLabelSuffix(connectedContextCount: number): string {
|
|
|
59
60
|
return `; ${connectedContextCount} ${connectedContextCount === 1 ? "group" : "groups"} connected`;
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
function withStuckAttention(
|
|
64
|
+
presentation: INetworkStatusPresentation,
|
|
65
|
+
stuckRecoveryCount: number,
|
|
66
|
+
): INetworkStatusPresentation {
|
|
67
|
+
if (stuckRecoveryCount <= 0) {
|
|
68
|
+
return presentation;
|
|
69
|
+
}
|
|
70
|
+
const attention =
|
|
71
|
+
stuckRecoveryCount === 1
|
|
72
|
+
? "1 record needs attention"
|
|
73
|
+
: `${stuckRecoveryCount} records need attention`;
|
|
74
|
+
const labeled = { ...presentation, label: `${presentation.label}; ${attention}` };
|
|
75
|
+
if (presentation.colorClass === "text-danger") {
|
|
76
|
+
return labeled;
|
|
77
|
+
}
|
|
78
|
+
return { ...labeled, colorClass: "text-warning" };
|
|
79
|
+
}
|
|
80
|
+
|
|
62
81
|
function connectedGroupsOverlay(
|
|
63
82
|
connectionCount: number,
|
|
64
83
|
connectedContextCount: number,
|
|
@@ -104,6 +123,7 @@ export function getNetworkStatusPresentation(
|
|
|
104
123
|
disconnectedContextCount,
|
|
105
124
|
errorCount,
|
|
106
125
|
state,
|
|
126
|
+
stuckRecoveryCount,
|
|
107
127
|
syncingContextCount,
|
|
108
128
|
} = viewState.status;
|
|
109
129
|
const disconnectedOverlay =
|
|
@@ -116,14 +136,17 @@ export function getNetworkStatusPresentation(
|
|
|
116
136
|
const groupsOverlay = connectedGroupsOverlay(connectionCount, connectedContextCount);
|
|
117
137
|
const connectedSuffix = groupsOverlay ? connectedLabelSuffix(connectedContextCount) : "";
|
|
118
138
|
if (state === "error") {
|
|
119
|
-
return
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
139
|
+
return withStuckAttention(
|
|
140
|
+
{
|
|
141
|
+
label: `Network error; ${errorCount} sync ${errorCount === 1 ? "error" : "errors"}; ${connectionCount} active ${connectionCount === 1 ? "connection" : "connections"}${connectedSuffix}${disconnectedSuffix}`,
|
|
142
|
+
shortLabel: "Error",
|
|
143
|
+
colorClass: "text-danger",
|
|
144
|
+
mark: "solid",
|
|
145
|
+
disconnectedGroupsOverlay: disconnectedOverlay,
|
|
146
|
+
connectedGroupsOverlay: groupsOverlay,
|
|
147
|
+
},
|
|
148
|
+
stuckRecoveryCount,
|
|
149
|
+
);
|
|
127
150
|
}
|
|
128
151
|
if (state === "syncing" || opts?.holdSyncingVisual) {
|
|
129
152
|
const longSync = state === "syncing" && opts?.syncingLongerThanGreenMs === true;
|
|
@@ -131,32 +154,41 @@ export function getNetworkStatusPresentation(
|
|
|
131
154
|
state === "syncing"
|
|
132
155
|
? `${syncingContextCount} ${syncingContextCount === 1 ? "context" : "contexts"}`
|
|
133
156
|
: "recent activity";
|
|
134
|
-
return
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
157
|
+
return withStuckAttention(
|
|
158
|
+
{
|
|
159
|
+
label: `Network syncing ${contextsLabel}; ${connectionCount} active ${connectionCount === 1 ? "connection" : "connections"}${connectedSuffix}${disconnectedSuffix}`,
|
|
160
|
+
shortLabel: "Syncing",
|
|
161
|
+
colorClass: longSync ? "text-primary" : "text-success",
|
|
162
|
+
mark: longSync ? "indeterminate" : "pulse",
|
|
163
|
+
disconnectedGroupsOverlay: disconnectedOverlay,
|
|
164
|
+
connectedGroupsOverlay: groupsOverlay,
|
|
165
|
+
},
|
|
166
|
+
stuckRecoveryCount,
|
|
167
|
+
);
|
|
142
168
|
}
|
|
143
169
|
if (state === "offline") {
|
|
144
|
-
return
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
170
|
+
return withStuckAttention(
|
|
171
|
+
{
|
|
172
|
+
label: "Network offline; no active connections",
|
|
173
|
+
shortLabel: "Offline",
|
|
174
|
+
iconClass: "bi-cloud-slash-fill",
|
|
175
|
+
colorClass: "text-secondary",
|
|
176
|
+
},
|
|
177
|
+
stuckRecoveryCount,
|
|
178
|
+
);
|
|
150
179
|
}
|
|
151
|
-
return
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
180
|
+
return withStuckAttention(
|
|
181
|
+
{
|
|
182
|
+
label: `Network synced; ${connectionCount} active ${connectionCount === 1 ? "connection" : "connections"}${connectedSuffix}${disconnectedSuffix}`,
|
|
183
|
+
shortLabel: "Synced",
|
|
184
|
+
iconClass: "bi-check-circle-fill",
|
|
185
|
+
colorClass: "text-success",
|
|
186
|
+
compactIcon: true,
|
|
187
|
+
disconnectedGroupsOverlay: disconnectedOverlay,
|
|
188
|
+
connectedGroupsOverlay: groupsOverlay,
|
|
189
|
+
},
|
|
190
|
+
stuckRecoveryCount,
|
|
191
|
+
);
|
|
160
192
|
}
|
|
161
193
|
|
|
162
194
|
function statusMarkBgClass(colorClass: string): string {
|
|
@@ -166,6 +198,9 @@ function statusMarkBgClass(colorClass: string): string {
|
|
|
166
198
|
if (colorClass === "text-primary") {
|
|
167
199
|
return "bg-primary";
|
|
168
200
|
}
|
|
201
|
+
if (colorClass === "text-warning") {
|
|
202
|
+
return "bg-warning";
|
|
203
|
+
}
|
|
169
204
|
return "bg-success";
|
|
170
205
|
}
|
|
171
206
|
|
|
@@ -307,6 +342,24 @@ function ConnectedDevicesList({ loadState }: { loadState: ConnectionsLoadState }
|
|
|
307
342
|
);
|
|
308
343
|
}
|
|
309
344
|
|
|
345
|
+
function StatusCountRow({
|
|
346
|
+
label,
|
|
347
|
+
value,
|
|
348
|
+
className,
|
|
349
|
+
}: {
|
|
350
|
+
label: string;
|
|
351
|
+
value: number;
|
|
352
|
+
className?: string;
|
|
353
|
+
}) {
|
|
354
|
+
const tone = className ? ` ${className}` : "";
|
|
355
|
+
return (
|
|
356
|
+
<>
|
|
357
|
+
<dt className={`col-7${tone}`}>{label}</dt>
|
|
358
|
+
<dd className={`col-5 text-end${tone}`}>{value}</dd>
|
|
359
|
+
</>
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
310
363
|
function NetworkStatusDetails({
|
|
311
364
|
detailsId,
|
|
312
365
|
status,
|
|
@@ -315,6 +368,7 @@ function NetworkStatusDetails({
|
|
|
315
368
|
onUseInvite,
|
|
316
369
|
onOpenIdentity,
|
|
317
370
|
onOpenViewer,
|
|
371
|
+
onOpenRecovery,
|
|
318
372
|
}: {
|
|
319
373
|
detailsId: string;
|
|
320
374
|
status?: INetworkStatusSummary;
|
|
@@ -323,6 +377,7 @@ function NetworkStatusDetails({
|
|
|
323
377
|
onUseInvite: () => void;
|
|
324
378
|
onOpenIdentity: () => void;
|
|
325
379
|
onOpenViewer: () => void;
|
|
380
|
+
onOpenRecovery: () => void;
|
|
326
381
|
}) {
|
|
327
382
|
return (
|
|
328
383
|
<div
|
|
@@ -360,16 +415,21 @@ function NetworkStatusDetails({
|
|
|
360
415
|
) : (
|
|
361
416
|
<>
|
|
362
417
|
<dl className="row small mb-2">
|
|
363
|
-
<
|
|
364
|
-
<
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
418
|
+
<StatusCountRow label="Connections" value={status.connectionCount} />
|
|
419
|
+
<StatusCountRow label="Groups connected" value={status.connectedContextCount} />
|
|
420
|
+
{status.syncingContextCount > 0 && (
|
|
421
|
+
<StatusCountRow label="Syncing contexts" value={status.syncingContextCount} />
|
|
422
|
+
)}
|
|
423
|
+
{status.disconnectedContextCount > 0 && (
|
|
424
|
+
<StatusCountRow
|
|
425
|
+
label="Groups offline"
|
|
426
|
+
value={status.disconnectedContextCount}
|
|
427
|
+
className="text-warning"
|
|
428
|
+
/>
|
|
429
|
+
)}
|
|
430
|
+
{status.errorCount > 0 && (
|
|
431
|
+
<StatusCountRow label="Sync errors" value={status.errorCount} />
|
|
432
|
+
)}
|
|
373
433
|
{status.lastSuccessfulSyncAt != null && (
|
|
374
434
|
<>
|
|
375
435
|
<dt className="col-7">Last sync</dt>
|
|
@@ -388,19 +448,31 @@ function NetworkStatusDetails({
|
|
|
388
448
|
{status.errors.length > 1 && ` (+${status.errors.length - 1} more)`}
|
|
389
449
|
</div>
|
|
390
450
|
)}
|
|
451
|
+
{status.stuckRecoveryCount > 0 && (
|
|
452
|
+
<div className="alert alert-warning py-2 px-2 small mb-2">
|
|
453
|
+
{status.stuckRecoveryCount === 1
|
|
454
|
+
? "1 record needs attention"
|
|
455
|
+
: `${status.stuckRecoveryCount} records need attention`}
|
|
456
|
+
</div>
|
|
457
|
+
)}
|
|
391
458
|
<div className="border-top pt-2 mt-1 mb-2">
|
|
392
|
-
<div className="small text-muted mb-1">Devices</div>
|
|
459
|
+
<div className="small text-muted mb-1">Connected Devices</div>
|
|
393
460
|
<ConnectedDevicesList loadState={connectionsLoadState} />
|
|
394
461
|
</div>
|
|
395
462
|
</>
|
|
396
463
|
)}
|
|
397
464
|
<button
|
|
398
465
|
type="button"
|
|
399
|
-
className=
|
|
466
|
+
className={`btn btn-sm btn-outline-primary w-100${status && status.stuckRecoveryCount > 0 ? " mb-2" : ""}`}
|
|
400
467
|
onClick={onOpenViewer}
|
|
401
468
|
>
|
|
402
469
|
Open Network Viewer
|
|
403
470
|
</button>
|
|
471
|
+
{status && status.stuckRecoveryCount > 0 && (
|
|
472
|
+
<button type="button" className="btn btn-sm btn-warning w-100" onClick={onOpenRecovery}>
|
|
473
|
+
Sync recovery
|
|
474
|
+
</button>
|
|
475
|
+
)}
|
|
404
476
|
</div>
|
|
405
477
|
</div>
|
|
406
478
|
);
|
|
@@ -592,6 +664,12 @@ export function NetworkStatusIndicator({ compact = false }: { compact?: boolean
|
|
|
592
664
|
}}
|
|
593
665
|
onOpenViewer={() => {
|
|
594
666
|
setOpen(false);
|
|
667
|
+
networkViewerSection("main");
|
|
668
|
+
goToTabPath("network-viewer");
|
|
669
|
+
}}
|
|
670
|
+
onOpenRecovery={() => {
|
|
671
|
+
setOpen(false);
|
|
672
|
+
networkViewerSection("sync-recovery");
|
|
595
673
|
goToTabPath("network-viewer");
|
|
596
674
|
}}
|
|
597
675
|
/>
|