@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.
Files changed (24) hide show
  1. package/dist/screens/network-viewer/network-viewer-section.d.ts +9 -0
  2. package/dist/screens/network-viewer/network-viewer-section.js +11 -0
  3. package/dist/screens/network-viewer/network-viewer.d.ts +6 -0
  4. package/dist/screens/network-viewer/network-viewer.js +19 -1
  5. package/dist/screens/network-viewer/network-viewer.section.test.d.ts +1 -0
  6. package/dist/screens/network-viewer/network-viewer.section.test.js +63 -0
  7. package/dist/screens/network-viewer/sync-recovery.d.ts +8 -0
  8. package/dist/screens/network-viewer/sync-recovery.js +182 -0
  9. package/dist/screens/network-viewer/sync-recovery.test.d.ts +1 -0
  10. package/dist/screens/network-viewer/sync-recovery.test.js +104 -0
  11. package/dist/screens/settings/settings-page.js +13 -3
  12. package/dist/tabs-layout/network-status-indicator.js +39 -11
  13. package/dist/tabs-layout/network-status-indicator.test.js +109 -0
  14. package/dist/tabs-layout/use-network-status.test.js +1 -0
  15. package/package.json +3 -3
  16. package/src/screens/network-viewer/network-viewer-section.ts +12 -0
  17. package/src/screens/network-viewer/network-viewer.section.test.tsx +68 -0
  18. package/src/screens/network-viewer/network-viewer.tsx +27 -0
  19. package/src/screens/network-viewer/sync-recovery.test.tsx +114 -0
  20. package/src/screens/network-viewer/sync-recovery.tsx +461 -0
  21. package/src/screens/settings/settings-page.tsx +15 -5
  22. package/src/tabs-layout/network-status-indicator.test.tsx +131 -0
  23. package/src/tabs-layout/network-status-indicator.tsx +121 -43
  24. 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
- label: `Network error; ${errorCount} sync ${errorCount === 1 ? "error" : "errors"}; ${connectionCount} active ${connectionCount === 1 ? "connection" : "connections"}${connectedSuffix}${disconnectedSuffix}`,
121
- shortLabel: "Error",
122
- colorClass: "text-danger",
123
- mark: "solid",
124
- disconnectedGroupsOverlay: disconnectedOverlay,
125
- connectedGroupsOverlay: groupsOverlay,
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
- label: `Network syncing ${contextsLabel}; ${connectionCount} active ${connectionCount === 1 ? "connection" : "connections"}${connectedSuffix}${disconnectedSuffix}`,
136
- shortLabel: "Syncing",
137
- colorClass: longSync ? "text-primary" : "text-success",
138
- mark: longSync ? "indeterminate" : "pulse",
139
- disconnectedGroupsOverlay: disconnectedOverlay,
140
- connectedGroupsOverlay: groupsOverlay,
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
- label: "Network offline; no active connections",
146
- shortLabel: "Offline",
147
- iconClass: "bi-cloud-slash-fill",
148
- colorClass: "text-secondary",
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
- label: `Network synced; ${connectionCount} active ${connectionCount === 1 ? "connection" : "connections"}${connectedSuffix}${disconnectedSuffix}`,
153
- shortLabel: "Synced",
154
- iconClass: "bi-check-circle-fill",
155
- colorClass: "text-success",
156
- compactIcon: true,
157
- disconnectedGroupsOverlay: disconnectedOverlay,
158
- connectedGroupsOverlay: groupsOverlay,
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
- <dt className="col-7">Connections</dt>
364
- <dd className="col-5 text-end">{status.connectionCount}</dd>
365
- <dt className="col-7">Syncing contexts</dt>
366
- <dd className="col-5 text-end">{status.syncingContextCount}</dd>
367
- <dt className="col-7">Sync errors</dt>
368
- <dd className="col-5 text-end">{status.errorCount}</dd>
369
- <dt className="col-7">Groups connected</dt>
370
- <dd className="col-5 text-end">{status.connectedContextCount}</dd>
371
- <dt className="col-7">Groups offline</dt>
372
- <dd className="col-5 text-end">{status.disconnectedContextCount}</dd>
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="btn btn-sm btn-outline-primary w-100"
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
  />
@@ -25,6 +25,7 @@ function makeStatus(overrides: Partial<INetworkStatusSummary> = {}): INetworkSta
25
25
  errorCount: 0,
26
26
  disconnectedContextCount: 0,
27
27
  connectedContextCount: 0,
28
+ stuckRecoveryCount: 0,
28
29
  errors: [],
29
30
  ...overrides,
30
31
  };