@peers-app/peers-ui 0.27.3 → 0.27.5
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/group-details-modal.d.ts +4 -0
- package/dist/screens/network-viewer/group-details-modal.js +6 -46
- package/dist/screens/network-viewer/network-viewer-ipc.d.ts +3 -2
- package/dist/screens/network-viewer/network-viewer-ipc.js +6 -0
- package/dist/screens/network-viewer/sync-recovery.js +103 -1
- package/dist/screens/variables/variable-details.js +2 -1
- package/dist/tabs-layout/network-status-indicator.js +26 -12
- package/dist/tabs-layout/use-network-status.d.ts +6 -1
- package/dist/tabs-layout/use-network-status.js +27 -4
- package/package.json +9 -3
- package/.claude/settings.local.json +0 -34
- package/.github/README.md +0 -52
- package/.github/workflows/delete-all-artifacts.yml +0 -49
- package/.github/workflows/publish.yml +0 -51
- package/babel.config.js +0 -7
- package/biome.json +0 -198
- package/dist/app-updates/app-updates-consumer.test.d.ts +0 -1
- package/dist/app-updates/app-updates-consumer.test.js +0 -56
- package/dist/app-updates/app-updates-settings.test.d.ts +0 -1
- package/dist/app-updates/app-updates-settings.test.js +0 -93
- package/dist/app-updates/remote-app-update-panel.test.d.ts +0 -1
- package/dist/app-updates/remote-app-update-panel.test.js +0 -165
- package/dist/components/access-level-field.test.d.ts +0 -1
- package/dist/components/access-level-field.test.js +0 -28
- package/dist/components/message-logs/tool-access-approval-card.test.d.ts +0 -1
- package/dist/components/message-logs/tool-access-approval-card.test.js +0 -102
- package/dist/components/responsive-section-nav.test.d.ts +0 -1
- package/dist/components/responsive-section-nav.test.js +0 -45
- package/dist/globals-hash.test.d.ts +0 -1
- package/dist/globals-hash.test.js +0 -16
- package/dist/hooks/use-observable-state.test.d.ts +0 -1
- package/dist/hooks/use-observable-state.test.js +0 -64
- package/dist/hooks/use-observable.test.d.ts +0 -1
- package/dist/hooks/use-observable.test.js +0 -191
- package/dist/hooks/use-rows.test.d.ts +0 -1
- package/dist/hooks/use-rows.test.js +0 -175
- package/dist/hooks/use-tool.test.d.ts +0 -1
- package/dist/hooks/use-tool.test.js +0 -190
- package/dist/host-shell/host-shell-consumer.test.d.ts +0 -1
- package/dist/host-shell/host-shell-consumer.test.js +0 -28
- package/dist/root-layout/root-layout-host.test.d.ts +0 -1
- package/dist/root-layout/root-layout-host.test.js +0 -86
- package/dist/screens/account/account-screen.test.d.ts +0 -1
- package/dist/screens/account/account-screen.test.js +0 -51
- package/dist/screens/assistants/assistant-config.test.d.ts +0 -1
- package/dist/screens/assistants/assistant-config.test.js +0 -57
- package/dist/screens/contacts/contact-list.test.d.ts +0 -1
- package/dist/screens/contacts/contact-list.test.js +0 -48
- package/dist/screens/device-pairing/device-pairing.test.d.ts +0 -1
- package/dist/screens/device-pairing/device-pairing.test.js +0 -119
- package/dist/screens/devices/devices-list.test.d.ts +0 -1
- package/dist/screens/devices/devices-list.test.js +0 -53
- package/dist/screens/groups/group-list.test.d.ts +0 -1
- package/dist/screens/groups/group-list.test.js +0 -38
- package/dist/screens/identity/identity-routes.test.d.ts +0 -1
- package/dist/screens/identity/identity-routes.test.js +0 -51
- package/dist/screens/identity/identity-screen.test.d.ts +0 -1
- package/dist/screens/identity/identity-screen.test.js +0 -103
- package/dist/screens/invites/accept-invite.test.d.ts +0 -1
- package/dist/screens/invites/accept-invite.test.js +0 -103
- package/dist/screens/invites/activity-view.test.d.ts +0 -1
- package/dist/screens/invites/activity-view.test.js +0 -108
- package/dist/screens/invites/invite-classify.test.d.ts +0 -1
- package/dist/screens/invites/invite-classify.test.js +0 -115
- package/dist/screens/network-viewer/network-viewer-ipc.test.d.ts +0 -1
- package/dist/screens/network-viewer/network-viewer-ipc.test.js +0 -61
- package/dist/screens/network-viewer/network-viewer.section.test.d.ts +0 -1
- package/dist/screens/network-viewer/network-viewer.section.test.js +0 -63
- package/dist/screens/network-viewer/sync-recovery.test.d.ts +0 -1
- package/dist/screens/network-viewer/sync-recovery.test.js +0 -104
- package/dist/screens/packages/package-helpers.test.d.ts +0 -1
- package/dist/screens/packages/package-helpers.test.js +0 -53
- package/dist/screens/packages/package-runtime-status.test.d.ts +0 -1
- package/dist/screens/packages/package-runtime-status.test.js +0 -50
- package/dist/screens/packages/packages-ipc.test.d.ts +0 -1
- package/dist/screens/packages/packages-ipc.test.js +0 -43
- package/dist/screens/settings/settings-page.test.d.ts +0 -1
- package/dist/screens/settings/settings-page.test.js +0 -19
- package/dist/screens/tools/tool-access-ipc.test.d.ts +0 -1
- package/dist/screens/tools/tool-access-ipc.test.js +0 -40
- package/dist/screens/tools/tool-catalog-ipc.test.d.ts +0 -1
- package/dist/screens/tools/tool-catalog-ipc.test.js +0 -52
- package/dist/tabs-layout/network-status-indicator.test.d.ts +0 -1
- package/dist/tabs-layout/network-status-indicator.test.js +0 -665
- package/dist/tabs-layout/tabs-state.test.d.ts +0 -1
- package/dist/tabs-layout/tabs-state.test.js +0 -110
- package/dist/tabs-layout/use-network-status.test.d.ts +0 -1
- package/dist/tabs-layout/use-network-status.test.js +0 -45
- package/dist/ui-router/ui-loader.test.d.ts +0 -1
- package/dist/ui-router/ui-loader.test.js +0 -91
- package/jest.config.js +0 -25
- package/release-guard.js +0 -10
- package/src/app-updates/app-update-controls.tsx +0 -73
- package/src/app-updates/app-updates-consumer.test.ts +0 -83
- package/src/app-updates/app-updates-consumer.ts +0 -121
- package/src/app-updates/app-updates-settings.test.tsx +0 -109
- package/src/app-updates/app-updates-settings.tsx +0 -136
- package/src/app-updates/remote-app-update-panel.test.tsx +0 -205
- package/src/app-updates/remote-app-update-panel.tsx +0 -210
- package/src/app-updates/use-app-update-status.ts +0 -16
- package/src/command-palette/command-palette-ui.tsx +0 -564
- package/src/command-palette/command-palette.ts +0 -242
- package/src/components/access-level-field.test.tsx +0 -32
- package/src/components/access-level-field.tsx +0 -68
- package/src/components/checkbox.tsx +0 -25
- package/src/components/group-switcher.tsx +0 -526
- package/src/components/input-date.tsx +0 -29
- package/src/components/input-number.tsx +0 -83
- package/src/components/input.tsx +0 -25
- package/src/components/inverse-lazy-list.tsx +0 -107
- package/src/components/io-schema-values.tsx +0 -103
- package/src/components/io-schema.tsx +0 -259
- package/src/components/lazy-list.tsx +0 -115
- package/src/components/list-screen.tsx +0 -126
- package/src/components/loading-indicator.tsx +0 -7
- package/src/components/markdown-editor/autolink-plugin.tsx +0 -29
- package/src/components/markdown-editor/editor-inline.tsx +0 -9
- package/src/components/markdown-editor/editor.tsx +0 -158
- package/src/components/markdown-editor/markdown-plugin.tsx +0 -202
- package/src/components/markdown-editor/mention-node.ts +0 -204
- package/src/components/markdown-editor/mentions-plugin.tsx +0 -354
- package/src/components/markdown-editor/move-line-plugin.tsx +0 -186
- package/src/components/markdown-editor/select-line-boundary-plugin.tsx +0 -95
- package/src/components/markdown-editor/theme.ts +0 -46
- package/src/components/markdown-editor/toolbar.tsx +0 -269
- package/src/components/markdown-with-mentions.tsx +0 -198
- package/src/components/message-logs/message-logs.tsx +0 -523
- package/src/components/message-logs/tool-access-approval-card.test.tsx +0 -127
- package/src/components/message-logs/tool-access-approval-card.tsx +0 -178
- package/src/components/messages/avatar.tsx +0 -171
- package/src/components/messages/channel-message-list.tsx +0 -189
- package/src/components/messages/channel-view.tsx +0 -92
- package/src/components/messages/message-compose.tsx +0 -192
- package/src/components/messages/message-display.tsx +0 -231
- package/src/components/messages/thread-message-list.tsx +0 -135
- package/src/components/qr-scanner.tsx +0 -180
- package/src/components/responsive-section-nav.test.tsx +0 -88
- package/src/components/responsive-section-nav.tsx +0 -92
- package/src/components/router.tsx +0 -175
- package/src/components/save-button.tsx +0 -134
- package/src/components/sortable-list.tsx +0 -130
- package/src/components/tabs.tsx +0 -76
- package/src/components/tooltip.tsx +0 -43
- package/src/components/trust-level-badge.tsx +0 -44
- package/src/components/trust-level-dropdown.tsx +0 -136
- package/src/components/typeahead.tsx +0 -357
- package/src/globals-hash.test.ts +0 -19
- package/src/globals.tsx +0 -226
- package/src/hooks/index.ts +0 -8
- package/src/hooks/use-host-contract.ts +0 -75
- package/src/hooks/use-observable-state.test.tsx +0 -101
- package/src/hooks/use-observable-state.ts +0 -29
- package/src/hooks/use-observable.test.tsx +0 -258
- package/src/hooks/use-observable.ts +0 -67
- package/src/hooks/use-on-screen.ts +0 -24
- package/src/hooks/use-promise.ts +0 -34
- package/src/hooks/use-rows.test.tsx +0 -196
- package/src/hooks/use-rows.ts +0 -120
- package/src/hooks/use-subscription.ts +0 -24
- package/src/hooks/use-tool.test.tsx +0 -225
- package/src/hooks/use-tool.ts +0 -116
- package/src/host-shell/host-shell-consumer.test.ts +0 -32
- package/src/host-shell/host-shell-consumer.ts +0 -53
- package/src/index.tsx +0 -42
- package/src/mention-configs.ts +0 -185
- package/src/operator-console/operator-console-state.ts +0 -119
- package/src/operator-console/operator-console.tsx +0 -233
- package/src/peers-services-registration.ts +0 -6
- package/src/root-layout/global-overlays.ts +0 -50
- package/src/root-layout/layout-picker.tsx +0 -47
- package/src/root-layout/layouts/full-screen-layout.tsx +0 -144
- package/src/root-layout/layouts/tabs-with-console-layout.tsx +0 -260
- package/src/root-layout/root-layout-host.test.tsx +0 -108
- package/src/root-layout/root-layout-host.tsx +0 -147
- package/src/root-layout/root-layout-registry.ts +0 -51
- package/src/root-layout/root-layout-state.ts +0 -55
- package/src/root-layout/root-layout.ts +0 -32
- package/src/screens/account/account-screen.test.tsx +0 -65
- package/src/screens/account/account-screen.tsx +0 -942
- package/src/screens/assistants/assistant-config.test.tsx +0 -54
- package/src/screens/assistants/assistant-config.tsx +0 -121
- package/src/screens/assistants/assistant-details.tsx +0 -89
- package/src/screens/assistants/assistant-info.tsx +0 -66
- package/src/screens/assistants/assistant-list.tsx +0 -145
- package/src/screens/assistants/assistant-tools.tsx +0 -82
- package/src/screens/console-logs/console-logs-list.tsx +0 -374
- package/src/screens/console-logs/log-display.tsx +0 -137
- package/src/screens/console-logs/log-filters.tsx +0 -64
- package/src/screens/console-logs/mobile-log-card.tsx +0 -161
- package/src/screens/console-logs/resizable-table-header.tsx +0 -110
- package/src/screens/contacts/contact-details.tsx +0 -123
- package/src/screens/contacts/contact-list.test.tsx +0 -59
- package/src/screens/contacts/contact-list.tsx +0 -286
- package/src/screens/contacts/index.ts +0 -2
- package/src/screens/data-explorer/data-explorer.tsx +0 -633
- package/src/screens/data-explorer/index.ts +0 -6
- package/src/screens/data-explorer/query-executor.tsx +0 -520
- package/src/screens/device-pairing/device-pairing.test.tsx +0 -143
- package/src/screens/device-pairing/device-pairing.tsx +0 -461
- package/src/screens/device-pairing/identity-ipc.ts +0 -25
- package/src/screens/device-pairing/index.ts +0 -2
- package/src/screens/device-pairing/use-device-pairing-state.ts +0 -15
- package/src/screens/devices/devices-list.test.tsx +0 -68
- package/src/screens/devices/devices-list.tsx +0 -365
- package/src/screens/devices/index.ts +0 -1
- package/src/screens/groups/group-details.tsx +0 -160
- package/src/screens/groups/group-invite-panel.tsx +0 -311
- package/src/screens/groups/group-list.test.tsx +0 -44
- package/src/screens/groups/group-list.tsx +0 -317
- package/src/screens/groups/group-members.tsx +0 -343
- package/src/screens/groups/index.ts +0 -3
- package/src/screens/identity/identity-routes.test.ts +0 -70
- package/src/screens/identity/identity-routes.ts +0 -301
- package/src/screens/identity/identity-screen.test.tsx +0 -138
- package/src/screens/identity/identity-screen.tsx +0 -282
- package/src/screens/identity/index.ts +0 -2
- package/src/screens/identity/invite-contact-panel.tsx +0 -45
- package/src/screens/identity/profile-invite.tsx +0 -93
- package/src/screens/invites/accept-invite.test.tsx +0 -115
- package/src/screens/invites/accept-invite.tsx +0 -318
- package/src/screens/invites/activity-view.test.tsx +0 -127
- package/src/screens/invites/activity-view.tsx +0 -162
- package/src/screens/invites/index.ts +0 -6
- package/src/screens/invites/invite-capture.ts +0 -104
- package/src/screens/invites/invite-classify.test.ts +0 -129
- package/src/screens/invites/invite-share.tsx +0 -104
- package/src/screens/invites/invites-ipc.ts +0 -25
- package/src/screens/invites/pending-invites-list.tsx +0 -256
- package/src/screens/network-viewer/connection-troubleshooter.tsx +0 -395
- package/src/screens/network-viewer/cpu-usage-graph.tsx +0 -294
- package/src/screens/network-viewer/device-details-modal.tsx +0 -339
- package/src/screens/network-viewer/group-details-modal.tsx +0 -421
- package/src/screens/network-viewer/index.ts +0 -8
- package/src/screens/network-viewer/network-viewer-ipc.test.ts +0 -72
- package/src/screens/network-viewer/network-viewer-ipc.ts +0 -39
- package/src/screens/network-viewer/network-viewer-section.ts +0 -12
- package/src/screens/network-viewer/network-viewer.section.test.tsx +0 -68
- package/src/screens/network-viewer/network-viewer.tsx +0 -708
- package/src/screens/network-viewer/remote-logs-panel.tsx +0 -190
- package/src/screens/network-viewer/sync-recovery.test.tsx +0 -114
- package/src/screens/network-viewer/sync-recovery.tsx +0 -461
- package/src/screens/network-viewer/usage-graph.tsx +0 -455
- package/src/screens/packages/local-folder-grants-panel.tsx +0 -290
- package/src/screens/packages/local-folders-ipc.ts +0 -28
- package/src/screens/packages/package-details.tsx +0 -166
- package/src/screens/packages/package-helpers.test.ts +0 -57
- package/src/screens/packages/package-helpers.ts +0 -137
- package/src/screens/packages/package-info.tsx +0 -354
- package/src/screens/packages/package-list.tsx +0 -519
- package/src/screens/packages/package-network-panel.tsx +0 -82
- package/src/screens/packages/package-new-local.tsx +0 -100
- package/src/screens/packages/package-runtime-status.test.tsx +0 -58
- package/src/screens/packages/package-runtime-status.tsx +0 -68
- package/src/screens/packages/package-versions.tsx +0 -298
- package/src/screens/packages/packages-ipc.test.ts +0 -47
- package/src/screens/packages/packages-ipc.ts +0 -125
- package/src/screens/peer-types/peer-type-details.tsx +0 -172
- package/src/screens/peer-types/peer-type-list.tsx +0 -64
- package/src/screens/search/global-search.tsx +0 -406
- package/src/screens/settings/color-mode-dropdown.tsx +0 -82
- package/src/screens/settings/settings-page.test.tsx +0 -24
- package/src/screens/settings/settings-page.tsx +0 -426
- package/src/screens/setup-user.tsx +0 -355
- package/src/screens/tools/tool-access-ipc.test.ts +0 -55
- package/src/screens/tools/tool-access-ipc.ts +0 -27
- package/src/screens/tools/tool-catalog-ipc.test.ts +0 -52
- package/src/screens/tools/tool-catalog-ipc.ts +0 -51
- package/src/screens/tools/tool-details.tsx +0 -52
- package/src/screens/tools/tool-info.tsx +0 -45
- package/src/screens/tools/tool-list.tsx +0 -106
- package/src/screens/tools/tool-schema.tsx +0 -44
- package/src/screens/tools/tool-test-details.tsx +0 -89
- package/src/screens/tools/tool-test-list.tsx +0 -73
- package/src/screens/variables/variable-details.tsx +0 -289
- package/src/screens/variables/variable-list.tsx +0 -110
- package/src/screens/welcome-modal.tsx +0 -294
- package/src/screens/workflows/workflow-details.tsx +0 -115
- package/src/screens/workflows/workflow-info.tsx +0 -22
- package/src/screens/workflows/workflow-instructions.tsx +0 -134
- package/src/screens/workflows/workflow-list.tsx +0 -117
- package/src/setupTests.ts +0 -38
- package/src/system-apps/assistants.app.ts +0 -7
- package/src/system-apps/console-logs.app.ts +0 -7
- package/src/system-apps/data-explorer.app.ts +0 -8
- package/src/system-apps/identity.app.ts +0 -9
- package/src/system-apps/index.ts +0 -77
- package/src/system-apps/mobile-settings.app.ts +0 -7
- package/src/system-apps/network-viewer.app.ts +0 -8
- package/src/system-apps/packages.app.ts +0 -7
- package/src/system-apps/search.app.ts +0 -8
- package/src/system-apps/settings.app.ts +0 -7
- package/src/system-apps/threads.app.ts +0 -7
- package/src/system-apps/tools.app.ts +0 -7
- package/src/system-apps/types.app.ts +0 -7
- package/src/system-apps/variables.app.ts +0 -7
- package/src/system-apps/workflows.app.ts +0 -7
- package/src/tabs-layout/network-status-indicator.test.tsx +0 -799
- package/src/tabs-layout/network-status-indicator.tsx +0 -756
- package/src/tabs-layout/tabs-layout.tsx +0 -857
- package/src/tabs-layout/tabs-state.test.ts +0 -152
- package/src/tabs-layout/tabs-state.ts +0 -587
- package/src/tabs-layout/use-network-status.test.tsx +0 -58
- package/src/tabs-layout/use-network-status.ts +0 -68
- package/src/ui-defaults/index.ts +0 -2
- package/src/ui-defaults/list-screen.tsx +0 -94
- package/src/ui-defaults/markdown-field.tsx +0 -87
- package/src/ui-router/routes-loader.ts +0 -193
- package/src/ui-router/ui-loader.test.tsx +0 -108
- package/src/ui-router/ui-loader.tsx +0 -606
- package/src/utils.ts +0 -95
- package/tsconfig.json +0 -18
|
@@ -2,5 +2,9 @@ interface IGroupDetailsModalProps {
|
|
|
2
2
|
groupId: string;
|
|
3
3
|
onClose: () => void;
|
|
4
4
|
}
|
|
5
|
+
/**
|
|
6
|
+
* Network Viewer dialog for one personal or group context: its details and members, and the
|
|
7
|
+
* Sync now and Do full sync actions.
|
|
8
|
+
*/
|
|
5
9
|
export declare function GroupDetailsModal({ groupId, onClose }: IGroupDetailsModalProps): import("react/jsx-runtime").JSX.Element;
|
|
6
10
|
export {};
|
|
@@ -7,26 +7,22 @@ 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
9
|
const network_viewer_ipc_1 = require("./network-viewer-ipc");
|
|
10
|
+
/**
|
|
11
|
+
* Network Viewer dialog for one personal or group context: its details and members, and the
|
|
12
|
+
* Sync now and Do full sync actions.
|
|
13
|
+
*/
|
|
10
14
|
function GroupDetailsModal({ groupId, onClose }) {
|
|
11
15
|
const [group, setGroup] = (0, react_1.useState)(null);
|
|
12
|
-
const [syncInfo, setSyncInfo] = (0, react_1.useState)(null);
|
|
13
16
|
const [loading, setLoading] = (0, react_1.useState)(true);
|
|
14
17
|
const [notFound, setNotFound] = (0, react_1.useState)(false);
|
|
15
|
-
const [downloading, setDownloading] = (0, react_1.useState)(false);
|
|
16
|
-
const [downloadProgress, setDownloadProgress] = (0, react_1.useState)(null);
|
|
17
|
-
const [downloadResult, setDownloadResult] = (0, react_1.useState)(null);
|
|
18
18
|
const [fullSyncing, setFullSyncing] = (0, react_1.useState)(false);
|
|
19
19
|
const [fullSyncResult, setFullSyncResult] = (0, react_1.useState)(null);
|
|
20
20
|
const [syncingNow, setSyncingNow] = (0, react_1.useState)(false);
|
|
21
21
|
const [syncNowResult, setSyncNowResult] = (0, react_1.useState)(null);
|
|
22
|
-
const platformApi = (0, network_viewer_ipc_1.getNetworkViewerPlatformApi)();
|
|
23
22
|
const loadData = (0, react_1.useCallback)(async () => {
|
|
24
23
|
try {
|
|
25
24
|
const diagnostics = await (0, network_viewer_ipc_1.getNetworkDiagnosticsConsumer)();
|
|
26
|
-
const
|
|
27
|
-
diagnostics.tools.getGroupDetails({ groupId }),
|
|
28
|
-
diagnostics.tools.getGroupSyncStatus({ groupId }),
|
|
29
|
-
]);
|
|
25
|
+
const { group: groupDetails } = await diagnostics.tools.getGroupDetails({ groupId });
|
|
30
26
|
if (!groupDetails) {
|
|
31
27
|
setNotFound(true);
|
|
32
28
|
}
|
|
@@ -34,9 +30,6 @@ function GroupDetailsModal({ groupId, onClose }) {
|
|
|
34
30
|
setGroup(groupDetails);
|
|
35
31
|
setNotFound(false);
|
|
36
32
|
}
|
|
37
|
-
if (groups.length > 0) {
|
|
38
|
-
setSyncInfo(groups[0]);
|
|
39
|
-
}
|
|
40
33
|
}
|
|
41
34
|
catch (error) {
|
|
42
35
|
console.error("Error loading group details:", error);
|
|
@@ -46,37 +39,6 @@ function GroupDetailsModal({ groupId, onClose }) {
|
|
|
46
39
|
setLoading(false);
|
|
47
40
|
}
|
|
48
41
|
}, [groupId]);
|
|
49
|
-
const handleDownloadDatabase = async (deviceId) => {
|
|
50
|
-
try {
|
|
51
|
-
setDownloading(true);
|
|
52
|
-
setDownloadProgress("Preparing download...");
|
|
53
|
-
setDownloadResult(null);
|
|
54
|
-
if (!platformApi?.downloadRemoteDatabase) {
|
|
55
|
-
setDownloadResult({
|
|
56
|
-
success: false,
|
|
57
|
-
message: "Download API not available",
|
|
58
|
-
});
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
setDownloadProgress("Downloading database...");
|
|
62
|
-
const result = (await platformApi.downloadRemoteDatabase(groupId, deviceId));
|
|
63
|
-
setDownloadResult(result);
|
|
64
|
-
if (result.success) {
|
|
65
|
-
setDownloadProgress(null);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
catch (error) {
|
|
69
|
-
console.error("Error downloading database:", error);
|
|
70
|
-
setDownloadResult({
|
|
71
|
-
success: false,
|
|
72
|
-
message: error instanceof Error ? error.message : "Download failed",
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
finally {
|
|
76
|
-
setDownloading(false);
|
|
77
|
-
setDownloadProgress(null);
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
42
|
const handleSyncNow = async () => {
|
|
81
43
|
setSyncingNow(true);
|
|
82
44
|
setSyncNowResult(null);
|
|
@@ -131,7 +93,5 @@ function GroupDetailsModal({ groupId, onClose }) {
|
|
|
131
93
|
overflowY: "auto",
|
|
132
94
|
paddingTop: "2rem",
|
|
133
95
|
paddingBottom: "2rem",
|
|
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 }))] })
|
|
135
|
-
? `${(downloadResult.size / (1024 * 1024)).toFixed(2)} MB`
|
|
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." }))] }))] })] }) }));
|
|
96
|
+
}, 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 }))] })] }))] })] }) }));
|
|
137
97
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { type INetworkControlConsumer, type INetworkDiagnosticsConsumer } from "@peers-app/peers-sdk";
|
|
1
|
+
import { type INetworkControlConsumer, type INetworkDiagnosticsConsumer, type ISyncCheckConsumer } from "@peers-app/peers-sdk";
|
|
2
2
|
/** Electron-only capabilities intentionally excluded from portable network contracts. */
|
|
3
3
|
export type NetworkViewerPlatformApi = {
|
|
4
|
-
downloadRemoteDatabase?: (groupId: string, deviceId: string) => Promise<unknown>;
|
|
5
4
|
runDiagnostics: () => Promise<unknown>;
|
|
6
5
|
};
|
|
7
6
|
/** Return a typed diagnostics consumer bound to the current local host transport. */
|
|
8
7
|
export declare function getNetworkDiagnosticsConsumer(): Promise<INetworkDiagnosticsConsumer>;
|
|
9
8
|
/** Return a typed control consumer bound to the current local host transport. */
|
|
10
9
|
export declare function getNetworkControlConsumer(): Promise<INetworkControlConsumer>;
|
|
10
|
+
/** Return a typed System Sync Check consumer bound to the current local host transport. */
|
|
11
|
+
export declare function getSyncCheckConsumer(): Promise<ISyncCheckConsumer>;
|
|
11
12
|
/** Return the context-isolated desktop-only capability bridge, when available. */
|
|
12
13
|
export declare function getNetworkViewerPlatformApi(): NetworkViewerPlatformApi | undefined;
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getNetworkDiagnosticsConsumer = getNetworkDiagnosticsConsumer;
|
|
4
4
|
exports.getNetworkControlConsumer = getNetworkControlConsumer;
|
|
5
|
+
exports.getSyncCheckConsumer = getSyncCheckConsumer;
|
|
5
6
|
exports.getNetworkViewerPlatformApi = getNetworkViewerPlatformApi;
|
|
6
7
|
const peers_sdk_1 = require("@peers-app/peers-sdk");
|
|
7
8
|
const diagnostics = (0, peers_sdk_1.hostContractConsumer)((0, peers_sdk_1.getNetworkDiagnosticsContractDefinition)());
|
|
8
9
|
const control = (0, peers_sdk_1.hostContractConsumer)((0, peers_sdk_1.getNetworkControlContractDefinition)());
|
|
10
|
+
const syncCheck = (0, peers_sdk_1.hostContractConsumer)((0, peers_sdk_1.getSyncCheckContractDefinition)());
|
|
9
11
|
/** Return a typed diagnostics consumer bound to the current local host transport. */
|
|
10
12
|
async function getNetworkDiagnosticsConsumer() {
|
|
11
13
|
return diagnostics.get();
|
|
@@ -14,6 +16,10 @@ async function getNetworkDiagnosticsConsumer() {
|
|
|
14
16
|
async function getNetworkControlConsumer() {
|
|
15
17
|
return control.get();
|
|
16
18
|
}
|
|
19
|
+
/** Return a typed System Sync Check consumer bound to the current local host transport. */
|
|
20
|
+
async function getSyncCheckConsumer() {
|
|
21
|
+
return syncCheck.get();
|
|
22
|
+
}
|
|
17
23
|
/** Return the context-isolated desktop-only capability bridge, when available. */
|
|
18
24
|
function getNetworkViewerPlatformApi() {
|
|
19
25
|
return window.electronAPI?.networkViewer;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SyncRecoveryScreen = SyncRecoveryScreen;
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const peers_sdk_1 = require("@peers-app/peers-sdk");
|
|
5
6
|
const react_1 = require("react");
|
|
6
7
|
const react_tiny_popover_1 = require("react-tiny-popover");
|
|
7
8
|
const loading_indicator_1 = require("../../components/loading-indicator");
|
|
@@ -76,6 +77,107 @@ function RecoveryRows({ entry, busy, onRun, }) {
|
|
|
76
77
|
: "This device has no row to keep", onClick: () => onRun(entry, "keepLocal"), children: "Keep this device's version" }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-outline-danger", disabled: busy, onClick: () => onRun(entry, "deleteEverywhere"), children: "Delete everywhere" }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-outline-secondary", disabled: busy, title: "Removes the entry on this device only. Other devices keep theirs.", onClick: () => onRun(entry, "ignoreLocal"), children: "Ignore on this device" })] }) })] }), (errorOpen || recordOpen) && ((0, jsx_runtime_1.jsx)("tr", { children: (0, jsx_runtime_1.jsxs)("td", { colSpan: 7, className: "bg-body-tertiary", children: [errorOpen && ((0, jsx_runtime_1.jsx)("pre", { className: "small mb-0 font-monospace", style: detailsBlockStyle, children: formatError(entry.latestError) })), recordOpen && ((0, jsx_runtime_1.jsxs)("div", { className: errorOpen ? "mt-3" : undefined, children: [entry.changeCreatedAt != null && ((0, jsx_runtime_1.jsxs)("p", { className: "small mb-2", children: ["Change written ", formatWhen(entry.changeCreatedAt)] })), entry.conflictingRecordId && ((0, jsx_runtime_1.jsxs)("p", { className: "small mb-2", children: ["Another record holds the same unique value:", " ", (0, jsx_runtime_1.jsx)("code", { children: entry.conflictingRecordId })] })), entry.tombstoneRecordId &&
|
|
77
78
|
entry.tombstoneRecordId === entry.conflictingRecordId && ((0, jsx_runtime_1.jsxs)("p", { className: "small mb-2", children: ["Keep deletes ", (0, jsx_runtime_1.jsx)("code", { children: entry.tombstoneRecordId }), " on every device."] })), entry.tombstoneRecordId && entry.tombstoneRecordId === entry.recordId && ((0, jsx_runtime_1.jsx)("p", { className: "small mb-2", children: "Keep snapshots the other record and tombstones this queued id." })), entry.keptRecordPreview && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("p", { className: "small mb-1", children: "This device's version" }), (0, jsx_runtime_1.jsx)("pre", { className: "small mb-2 font-monospace", style: detailsBlockStyle, children: entry.keptRecordPreview })] })), entry.queuedRecordPreview && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("p", { className: "small mb-1", children: "Queued change" }), (0, jsx_runtime_1.jsx)("pre", { className: "small mb-0 font-monospace", style: detailsBlockStyle, children: entry.queuedRecordPreview })] })), !entry.keptRecordPreview && !entry.queuedRecordPreview && entry.recordPreview && ((0, jsx_runtime_1.jsx)("pre", { className: "small mb-0 font-monospace", style: detailsBlockStyle, children: entry.recordPreview })), !entry.keptRecordPreview && !entry.queuedRecordPreview && !entry.recordPreview && ((0, jsx_runtime_1.jsx)("p", { className: "small text-muted mb-0", children: "This device has no stored copy of the record. The latest error may still quote the rejected value." }))] }))] }) }))] }));
|
|
78
79
|
}
|
|
80
|
+
function MissingChangeRows({ gap }) {
|
|
81
|
+
const [open, setOpen] = (0, react_1.useState)(false);
|
|
82
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-link btn-sm p-0", "aria-label": open ? "Hide missing changes" : "Show missing changes", onClick: () => setOpen((current) => !current), children: open ? "Hide changes" : "Show changes" }), open && ((0, jsx_runtime_1.jsxs)("table", { className: "table table-sm small mb-0 mt-1", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { children: "Table" }), (0, jsx_runtime_1.jsx)("th", { children: "Record" }), (0, jsx_runtime_1.jsx)("th", { children: "Change" }), (0, jsx_runtime_1.jsx)("th", { children: "Written" })] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: gap.missing.map((change) => ((0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { children: change.tableName }), (0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsx)("code", { children: change.recordId }) }), (0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsxs)("code", { title: change.changeId, children: [change.op, " ", change.path] }) }), (0, jsx_runtime_1.jsx)("td", { className: "text-nowrap", children: formatWhen(change.createdAt) })] }, change.changeId))) })] })), open && gap.missingCount > gap.missing.length && ((0, jsx_runtime_1.jsxs)("div", { className: "small text-muted", children: ["Showing ", gap.missing.length, " of ", gap.missingCount, "."] }))] }));
|
|
83
|
+
}
|
|
84
|
+
/** What the daily sync gap check found missing on this device, with Check now and Repair. */
|
|
85
|
+
function MissingChangesSection() {
|
|
86
|
+
const [gaps, setGaps] = (0, react_1.useState)([]);
|
|
87
|
+
const [availability, setAvailability] = (0, react_1.useState)("loading");
|
|
88
|
+
const loadGeneration = (0, react_1.useRef)(0);
|
|
89
|
+
const [busyKey, setBusyKey] = (0, react_1.useState)(null);
|
|
90
|
+
const [result, setResult] = (0, react_1.useState)(null);
|
|
91
|
+
const load = (0, react_1.useCallback)(async () => {
|
|
92
|
+
const generation = ++loadGeneration.current;
|
|
93
|
+
try {
|
|
94
|
+
const syncCheck = await (0, network_viewer_ipc_1.getSyncCheckConsumer)();
|
|
95
|
+
const { gaps } = await syncCheck.tools.getSyncGaps();
|
|
96
|
+
if (generation !== loadGeneration.current)
|
|
97
|
+
return;
|
|
98
|
+
setGaps(gaps);
|
|
99
|
+
setAvailability("ready");
|
|
100
|
+
}
|
|
101
|
+
catch (loadError) {
|
|
102
|
+
if (generation !== loadGeneration.current)
|
|
103
|
+
return;
|
|
104
|
+
setAvailability("unavailable");
|
|
105
|
+
setGaps([]);
|
|
106
|
+
setResult({
|
|
107
|
+
success: false,
|
|
108
|
+
message: loadError instanceof Error ? loadError.message : String(loadError),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}, []);
|
|
112
|
+
(0, react_1.useEffect)(() => {
|
|
113
|
+
let active = true;
|
|
114
|
+
let generation = 0;
|
|
115
|
+
let subscription;
|
|
116
|
+
const connect = () => {
|
|
117
|
+
const currentGeneration = ++generation;
|
|
118
|
+
subscription?.dispose();
|
|
119
|
+
subscription = undefined;
|
|
120
|
+
setAvailability("loading");
|
|
121
|
+
setGaps([]);
|
|
122
|
+
setResult(null);
|
|
123
|
+
void load();
|
|
124
|
+
void (0, network_viewer_ipc_1.getSyncCheckConsumer)()
|
|
125
|
+
.then((syncCheck) => {
|
|
126
|
+
if (active && generation === currentGeneration) {
|
|
127
|
+
subscription = syncCheck.observables?.status.subscribe(() => void load());
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
.catch(() => {
|
|
131
|
+
// `load` shows the unavailable state, including hosts without System Sync Check.
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
connect();
|
|
135
|
+
const unsubscribe = (0, peers_sdk_1.subscribeHostContractTransport)((transport) => {
|
|
136
|
+
if (transport)
|
|
137
|
+
connect();
|
|
138
|
+
else {
|
|
139
|
+
generation++;
|
|
140
|
+
loadGeneration.current++;
|
|
141
|
+
subscription?.dispose();
|
|
142
|
+
subscription = undefined;
|
|
143
|
+
setAvailability("unavailable");
|
|
144
|
+
setGaps([]);
|
|
145
|
+
}
|
|
146
|
+
}, { emitCurrent: false });
|
|
147
|
+
return () => {
|
|
148
|
+
active = false;
|
|
149
|
+
loadGeneration.current++;
|
|
150
|
+
subscription?.dispose();
|
|
151
|
+
unsubscribe();
|
|
152
|
+
};
|
|
153
|
+
}, [load]);
|
|
154
|
+
const run = async (key, action) => {
|
|
155
|
+
setBusyKey(key);
|
|
156
|
+
setResult(null);
|
|
157
|
+
try {
|
|
158
|
+
setResult({ success: true, message: (await action()).message });
|
|
159
|
+
}
|
|
160
|
+
catch (actionError) {
|
|
161
|
+
setResult({
|
|
162
|
+
success: false,
|
|
163
|
+
message: actionError instanceof Error ? actionError.message : String(actionError),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
setBusyKey(null);
|
|
168
|
+
await load();
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "card mb-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "card-header d-flex justify-content-between align-items-center", children: [(0, jsx_runtime_1.jsxs)("span", { children: ["Missing changes", (0, jsx_runtime_1.jsxs)(InfoTip, { label: "About missing changes", children: ["Once a day this device compares its changes with each connected device in every group. Listed here are changes another device has that this device should have received but doesn't. ", (0, jsx_runtime_1.jsx)("strong", { children: "Repair" }), " fetches them from that device and applies them the same way sync does. If changes keep going missing, that points to a sync problem worth reporting."] })] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-outline-primary", disabled: busyKey !== null || availability !== "ready", onClick: () => void run("check", async () => (await (0, network_viewer_ipc_1.getSyncCheckConsumer)()).tools.checkSyncGaps()), children: busyKey === "check" ? "Checking…" : "Check now" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "card-body py-2", children: [result && ((0, jsx_runtime_1.jsx)("div", { className: `alert ${result.success ? "alert-info" : "alert-danger"} py-1 px-2 small mb-2`, children: result.message })), availability !== "ready" ? ((0, jsx_runtime_1.jsx)("div", { className: "small text-muted", children: availability === "loading"
|
|
172
|
+
? "Loading missing change check…"
|
|
173
|
+
: "Missing change check is unavailable on this host." })) : gaps.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "small text-muted", children: "No missing changes found. Checks run once a day." })) : (gaps.map((gap) => {
|
|
174
|
+
const key = `${gap.dataContextId}:${gap.deviceId}`;
|
|
175
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "border-bottom py-2", children: [(0, jsx_runtime_1.jsxs)("div", { className: "d-flex justify-content-between align-items-start gap-2", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("strong", { children: gap.dataContextName }), ": ", gap.missingCount, gap.truncated ? "+" : "", " ", gap.missingCount === 1 ? "change" : "changes", " on", " ", (0, jsx_runtime_1.jsx)("span", { title: gap.deviceId, children: gap.deviceName || gap.deviceId }), " missing here", (0, jsx_runtime_1.jsxs)("div", { className: "small text-muted", children: ["Found ", formatWhen(gap.checkedAt)] })] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-warning text-nowrap", disabled: busyKey !== null, onClick: () => void run(key, async () => (await (0, network_viewer_ipc_1.getSyncCheckConsumer)()).tools.repairSyncGaps({
|
|
176
|
+
dataContextId: gap.dataContextId,
|
|
177
|
+
deviceId: gap.deviceId,
|
|
178
|
+
})), children: busyKey === key ? "Repairing…" : "Repair" })] }), (0, jsx_runtime_1.jsx)(MissingChangeRows, { gap: gap })] }, key));
|
|
179
|
+
}))] })] }));
|
|
180
|
+
}
|
|
79
181
|
/** Lists recovery-queue entries and the actions that resolve them. */
|
|
80
182
|
function SyncRecoveryScreen({ onBack }) {
|
|
81
183
|
const [entries, setEntries] = (0, react_1.useState)([]);
|
|
@@ -178,5 +280,5 @@ function SyncRecoveryScreen({ onBack }) {
|
|
|
178
280
|
group.push(entry);
|
|
179
281
|
groups.set(entry.dataContextId, group);
|
|
180
282
|
}
|
|
181
|
-
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-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "d-flex align-items-center gap-3", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "btn btn-sm btn-outline-secondary", onClick: onBack, children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-arrow-left me-1" }), "Back"] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h2", { className: "mb-1", children: "Sync recovery" }), (0, jsx_runtime_1.jsx)("p", { className: "text-muted small mb-0", children: "These records have change history, but this device could not write the row. Parked entries are not asked of other devices until you retry them." })] })] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-primary", onClick: () => void load(), children: "Refresh" })] }), error && (0, jsx_runtime_1.jsx)("div", { className: "alert alert-danger py-2 small", children: error }), entries.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "alert alert-success", children: "No records are waiting in the recovery queue." })) : ([...groups.entries()].map(([dataContextId, groupEntries]) => ((0, jsx_runtime_1.jsxs)("div", { className: "card mb-3", children: [(0, jsx_runtime_1.jsx)("div", { className: "card-header", children: groupEntries[0]?.dataContextName || dataContextId }), (0, jsx_runtime_1.jsx)("div", { className: "table-responsive", children: (0, jsx_runtime_1.jsxs)("table", { className: "table table-sm align-middle mb-0", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { children: "Table" }), (0, jsx_runtime_1.jsx)("th", { children: "Record" }), (0, jsx_runtime_1.jsx)("th", { children: "First failed" }), (0, jsx_runtime_1.jsx)("th", { children: "Attempts" }), (0, jsx_runtime_1.jsx)("th", { children: "Status" }), (0, jsx_runtime_1.jsx)("th", { children: "Latest error" }), (0, jsx_runtime_1.jsxs)("th", { children: ["Actions", (0, jsx_runtime_1.jsx)(InfoTip, { label: "About these actions", children: (0, jsx_runtime_1.jsxs)("ul", { className: "mb-0 ps-3", children: [(0, jsx_runtime_1.jsxs)("li", { children: [(0, jsx_runtime_1.jsx)("strong", { children: "Retry" }), " writes the record again from this device's history, then asks another device once. If a schema or unique-key entry is still queued after that, it is parked again. Use it after an upgrade that might accept a row this device rejected before."] }), (0, jsx_runtime_1.jsxs)("li", { children: [(0, jsx_runtime_1.jsx)("strong", { children: "Keep this device's version" }), " snapshots the row this device will keep. That is the queued row when it is already stored here, and otherwise the other row that holds the same unique value. When both rows are stored, the other record is deleted everywhere. When only the other row is stored, that row is kept and this queued id is tombstoned. Disabled when this device has no row to keep."] }), (0, jsx_runtime_1.jsxs)("li", { children: [(0, jsx_runtime_1.jsx)("strong", { children: "Delete everywhere" }), " deletes this record on every device, including devices that already have it."] }), (0, jsx_runtime_1.jsxs)("li", { children: [(0, jsx_runtime_1.jsx)("strong", { children: "Ignore on this device" }), " removes the entry here only. Other devices keep their own copy of the problem."] })] }) })] })] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: groupEntries.map((entry) => ((0, jsx_runtime_1.jsx)(RecoveryRows, { entry: entry, busy: Boolean(busyKey?.startsWith(`${entry.dataContextId}:${entry.syncRecoveryId}:`)), onRun: (item, action) => void run(item, action) }, entry.syncRecoveryId))) })] }) })] }, dataContextId))))] }));
|
|
283
|
+
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-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "d-flex align-items-center gap-3", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "btn btn-sm btn-outline-secondary", onClick: onBack, children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-arrow-left me-1" }), "Back"] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h2", { className: "mb-1", children: "Sync recovery" }), (0, jsx_runtime_1.jsx)("p", { className: "text-muted small mb-0", children: "These records have change history, but this device could not write the row. Parked entries are not asked of other devices until you retry them." })] })] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-primary", onClick: () => void load(), children: "Refresh" })] }), error && (0, jsx_runtime_1.jsx)("div", { className: "alert alert-danger py-2 small", children: error }), (0, jsx_runtime_1.jsx)(MissingChangesSection, {}), entries.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "alert alert-success", children: "No records are waiting in the recovery queue." })) : ([...groups.entries()].map(([dataContextId, groupEntries]) => ((0, jsx_runtime_1.jsxs)("div", { className: "card mb-3", children: [(0, jsx_runtime_1.jsx)("div", { className: "card-header", children: groupEntries[0]?.dataContextName || dataContextId }), (0, jsx_runtime_1.jsx)("div", { className: "table-responsive", children: (0, jsx_runtime_1.jsxs)("table", { className: "table table-sm align-middle mb-0", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { children: "Table" }), (0, jsx_runtime_1.jsx)("th", { children: "Record" }), (0, jsx_runtime_1.jsx)("th", { children: "First failed" }), (0, jsx_runtime_1.jsx)("th", { children: "Attempts" }), (0, jsx_runtime_1.jsx)("th", { children: "Status" }), (0, jsx_runtime_1.jsx)("th", { children: "Latest error" }), (0, jsx_runtime_1.jsxs)("th", { children: ["Actions", (0, jsx_runtime_1.jsx)(InfoTip, { label: "About these actions", children: (0, jsx_runtime_1.jsxs)("ul", { className: "mb-0 ps-3", children: [(0, jsx_runtime_1.jsxs)("li", { children: [(0, jsx_runtime_1.jsx)("strong", { children: "Retry" }), " writes the record again from this device's history, then asks another device once. If a schema or unique-key entry is still queued after that, it is parked again. Use it after an upgrade that might accept a row this device rejected before."] }), (0, jsx_runtime_1.jsxs)("li", { children: [(0, jsx_runtime_1.jsx)("strong", { children: "Keep this device's version" }), " snapshots the row this device will keep. That is the queued row when it is already stored here, and otherwise the other row that holds the same unique value. When both rows are stored, the other record is deleted everywhere. When only the other row is stored, that row is kept and this queued id is tombstoned. Disabled when this device has no row to keep."] }), (0, jsx_runtime_1.jsxs)("li", { children: [(0, jsx_runtime_1.jsx)("strong", { children: "Delete everywhere" }), " deletes this record on every device, including devices that already have it."] }), (0, jsx_runtime_1.jsxs)("li", { children: [(0, jsx_runtime_1.jsx)("strong", { children: "Ignore on this device" }), " removes the entry here only. Other devices keep their own copy of the problem."] })] }) })] })] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: groupEntries.map((entry) => ((0, jsx_runtime_1.jsx)(RecoveryRows, { entry: entry, busy: Boolean(busyKey?.startsWith(`${entry.dataContextId}:${entry.syncRecoveryId}:`)), onRun: (item, action) => void run(item, action) }, entry.syncRecoveryId))) })] }) })] }, dataContextId))))] }));
|
|
182
284
|
}
|
|
@@ -99,7 +99,8 @@ const CopyValueButton = (props) => {
|
|
|
99
99
|
async function onCopy() {
|
|
100
100
|
try {
|
|
101
101
|
const userContext = await (0, peers_sdk_1.getUserContext)();
|
|
102
|
-
|
|
102
|
+
// Personal can be "" here; leave it out so the provider uses the personal context.
|
|
103
|
+
const groupId = userContext.currentlyActiveGroupId() || undefined;
|
|
103
104
|
const tools = await (0, host_shell_consumer_1.hostShellTools)();
|
|
104
105
|
const result = await tools.copyVariableToClipboard({
|
|
105
106
|
persistentVarId: pvar.persistentVarId,
|
|
@@ -37,13 +37,25 @@ function disconnectedLabelSuffix(disconnectedContextCount) {
|
|
|
37
37
|
function connectedLabelSuffix(connectedContextCount) {
|
|
38
38
|
return `; ${connectedContextCount} ${connectedContextCount === 1 ? "group" : "groups"} connected`;
|
|
39
39
|
}
|
|
40
|
-
function
|
|
41
|
-
|
|
40
|
+
function missingChangesLabel(missingChangeCount) {
|
|
41
|
+
return missingChangeCount === 1
|
|
42
|
+
? "1 change missing from another device"
|
|
43
|
+
: `${missingChangeCount} changes missing from another device`;
|
|
44
|
+
}
|
|
45
|
+
function withStuckAttention(presentation, stuckRecoveryCount, missingChangeCount) {
|
|
46
|
+
if (stuckRecoveryCount <= 0 && missingChangeCount <= 0) {
|
|
42
47
|
return presentation;
|
|
43
48
|
}
|
|
44
|
-
const attention =
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
const attention = [
|
|
50
|
+
stuckRecoveryCount === 1
|
|
51
|
+
? "1 record needs attention"
|
|
52
|
+
: stuckRecoveryCount > 1
|
|
53
|
+
? `${stuckRecoveryCount} records need attention`
|
|
54
|
+
: "",
|
|
55
|
+
missingChangeCount > 0 ? missingChangesLabel(missingChangeCount) : "",
|
|
56
|
+
]
|
|
57
|
+
.filter(Boolean)
|
|
58
|
+
.join("; ");
|
|
47
59
|
const labeled = { ...presentation, label: `${presentation.label}; ${attention}` };
|
|
48
60
|
if (presentation.colorClass === "text-danger") {
|
|
49
61
|
return labeled;
|
|
@@ -79,6 +91,7 @@ function getNetworkStatusPresentation(viewState, opts) {
|
|
|
79
91
|
};
|
|
80
92
|
}
|
|
81
93
|
const { connectedContextCount, connectionCount, disconnectedContextCount, errorCount, state, stuckRecoveryCount, syncingContextCount, } = viewState.status;
|
|
94
|
+
const missingChangeCount = viewState.missingChangeCount ?? 0;
|
|
82
95
|
const disconnectedOverlay = disconnectedContextCount > 0 && state !== "offline"
|
|
83
96
|
? { count: disconnectedContextCount }
|
|
84
97
|
: undefined;
|
|
@@ -95,7 +108,7 @@ function getNetworkStatusPresentation(viewState, opts) {
|
|
|
95
108
|
mark: "solid",
|
|
96
109
|
disconnectedGroupsOverlay: disconnectedOverlay,
|
|
97
110
|
connectedGroupsOverlay: groupsOverlay,
|
|
98
|
-
}, stuckRecoveryCount);
|
|
111
|
+
}, stuckRecoveryCount, missingChangeCount);
|
|
99
112
|
}
|
|
100
113
|
if (state === "syncing" || opts?.holdSyncingVisual) {
|
|
101
114
|
const longSync = state === "syncing" && opts?.syncingLongerThanGreenMs === true;
|
|
@@ -109,7 +122,7 @@ function getNetworkStatusPresentation(viewState, opts) {
|
|
|
109
122
|
mark: longSync ? "indeterminate" : "pulse",
|
|
110
123
|
disconnectedGroupsOverlay: disconnectedOverlay,
|
|
111
124
|
connectedGroupsOverlay: groupsOverlay,
|
|
112
|
-
}, stuckRecoveryCount);
|
|
125
|
+
}, stuckRecoveryCount, missingChangeCount);
|
|
113
126
|
}
|
|
114
127
|
if (state === "offline") {
|
|
115
128
|
return withStuckAttention({
|
|
@@ -117,7 +130,7 @@ function getNetworkStatusPresentation(viewState, opts) {
|
|
|
117
130
|
shortLabel: "Offline",
|
|
118
131
|
iconClass: "bi-cloud-slash-fill",
|
|
119
132
|
colorClass: "text-secondary",
|
|
120
|
-
}, stuckRecoveryCount);
|
|
133
|
+
}, stuckRecoveryCount, missingChangeCount);
|
|
121
134
|
}
|
|
122
135
|
return withStuckAttention({
|
|
123
136
|
label: `Network synced; ${connectionCount} active ${connectionCount === 1 ? "connection" : "connections"}${connectedSuffix}${disconnectedSuffix}`,
|
|
@@ -127,7 +140,7 @@ function getNetworkStatusPresentation(viewState, opts) {
|
|
|
127
140
|
compactIcon: true,
|
|
128
141
|
disconnectedGroupsOverlay: disconnectedOverlay,
|
|
129
142
|
connectedGroupsOverlay: groupsOverlay,
|
|
130
|
-
}, stuckRecoveryCount);
|
|
143
|
+
}, stuckRecoveryCount, missingChangeCount);
|
|
131
144
|
}
|
|
132
145
|
/**
|
|
133
146
|
* Add the app-update badge and label to a network status presentation. Applies in every
|
|
@@ -233,10 +246,11 @@ function StatusCountRow({ label, value, className, }) {
|
|
|
233
246
|
const tone = className ? ` ${className}` : "";
|
|
234
247
|
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("dt", { className: `col-7${tone}`, children: label }), (0, jsx_runtime_1.jsx)("dd", { className: `col-5 text-end${tone}`, children: value })] }));
|
|
235
248
|
}
|
|
236
|
-
function NetworkStatusDetails({ detailsId, status, appUpdate, unavailable, connectionsLoadState, onUseInvite, onOpenIdentity, onOpenViewer, onOpenRecovery, }) {
|
|
249
|
+
function NetworkStatusDetails({ detailsId, status, missingChangeCount, appUpdate, unavailable, connectionsLoadState, onUseInvite, onOpenIdentity, onOpenViewer, onOpenRecovery, }) {
|
|
250
|
+
const needsRecovery = !!status && (status.stuckRecoveryCount > 0 || missingChangeCount > 0);
|
|
237
251
|
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: "320px", zIndex: 1100 }, children: (0, jsx_runtime_1.jsxs)("div", { className: "card-body p-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "d-flex gap-2 mb-3", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "btn btn-sm btn-outline-primary flex-fill", onClick: onUseInvite, children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-qr-code me-1", "aria-hidden": "true" }), "Use Invite"] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "btn btn-sm btn-outline-primary flex-fill", onClick: onOpenIdentity, children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-person-badge me-1", "aria-hidden": "true" }), "Identity"] })] }), (0, jsx_runtime_1.jsx)(app_update_controls_1.AppUpdatePopoverSection, { status: appUpdate }), (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)(StatusCountRow, { label: "Connections", value: status.connectionCount }), (0, jsx_runtime_1.jsx)(StatusCountRow, { label: "Groups connected", value: status.connectedContextCount }), status.syncingContextCount > 0 && ((0, jsx_runtime_1.jsx)(StatusCountRow, { label: "Syncing contexts", value: status.syncingContextCount })), status.disconnectedContextCount > 0 && ((0, jsx_runtime_1.jsx)(StatusCountRow, { label: "Groups offline", value: status.disconnectedContextCount, className: "text-warning" })), status.errorCount > 0 && ((0, jsx_runtime_1.jsx)(StatusCountRow, { label: "Sync errors", value: status.errorCount })), status.lastSuccessfulSyncAt != null && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("dt", { className: "col-7", children: "Last sync" }), (0, jsx_runtime_1.jsx)("dd", { className: "col-5 text-end text-truncate", title: new Date(status.lastSuccessfulSyncAt).toLocaleString(), children: formatRelativeSyncTime(status.lastSuccessfulSyncAt) })] }))] }), 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)`] })), status.stuckRecoveryCount > 0 && ((0, jsx_runtime_1.jsx)("div", { className: "alert alert-warning py-2 px-2 small mb-2", children: status.stuckRecoveryCount === 1
|
|
238
252
|
? "1 record needs attention"
|
|
239
|
-
: `${status.stuckRecoveryCount} records need attention` })), (0, jsx_runtime_1.jsxs)("div", { className: "border-top pt-2 mt-1 mb-2", children: [(0, jsx_runtime_1.jsx)("div", { className: "small text-muted mb-1", children: "Connected Devices" }), (0, jsx_runtime_1.jsx)(ConnectedDevicesList, { loadState: connectionsLoadState })] })] })), (0, jsx_runtime_1.jsx)("button", { type: "button", className: `btn btn-sm btn-outline-primary w-100${
|
|
253
|
+
: `${status.stuckRecoveryCount} records need attention` })), missingChangeCount > 0 && ((0, jsx_runtime_1.jsx)("div", { className: "alert alert-warning py-2 px-2 small mb-2", children: missingChangesLabel(missingChangeCount) })), (0, jsx_runtime_1.jsxs)("div", { className: "border-top pt-2 mt-1 mb-2", children: [(0, jsx_runtime_1.jsx)("div", { className: "small text-muted mb-1", children: "Connected Devices" }), (0, jsx_runtime_1.jsx)(ConnectedDevicesList, { loadState: connectionsLoadState })] })] })), (0, jsx_runtime_1.jsx)("button", { type: "button", className: `btn btn-sm btn-outline-primary w-100${needsRecovery ? " mb-2" : ""}`, onClick: onOpenViewer, children: "Open Network Viewer" }), needsRecovery && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-warning w-100", onClick: onOpenRecovery, children: "Sync recovery" }))] }) }));
|
|
240
254
|
}
|
|
241
255
|
/** Accessible reactive network status button and details popover. */
|
|
242
256
|
function NetworkStatusIndicator({ compact = false }) {
|
|
@@ -363,7 +377,7 @@ function NetworkStatusIndicator({ compact = false }) {
|
|
|
363
377
|
.network-status-update-badge.bi::before {
|
|
364
378
|
vertical-align: 0;
|
|
365
379
|
}
|
|
366
|
-
` })] }))] }), !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, appUpdate: appUpdate, unavailable: viewState.availability !== "ready", connectionsLoadState: connectionsLoadState, onUseInvite: () => {
|
|
380
|
+
` })] }))] }), !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, missingChangeCount: viewState.missingChangeCount ?? 0, appUpdate: appUpdate, unavailable: viewState.availability !== "ready", connectionsLoadState: connectionsLoadState, onUseInvite: () => {
|
|
367
381
|
setOpen(false);
|
|
368
382
|
(0, tabs_state_1.goToTabPath)(identity_routes_1.IDENTITY_ACCEPT_PATH);
|
|
369
383
|
}, onOpenIdentity: () => {
|
|
@@ -3,6 +3,11 @@ import { type INetworkStatusSummary } from "@peers-app/peers-sdk";
|
|
|
3
3
|
export interface INetworkStatusViewState {
|
|
4
4
|
availability: "loading" | "ready" | "unavailable";
|
|
5
5
|
status?: INetworkStatusSummary;
|
|
6
|
+
/** Changes the daily sync gap check found on another device but not on this one. */
|
|
7
|
+
missingChangeCount?: number;
|
|
6
8
|
}
|
|
7
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Subscribe to the current host's System Network Diagnostics status observable, and to System
|
|
11
|
+
* Sync Check's missing-change count.
|
|
12
|
+
*/
|
|
8
13
|
export declare function useNetworkStatus(): INetworkStatusViewState;
|
|
@@ -4,7 +4,10 @@ exports.useNetworkStatus = useNetworkStatus;
|
|
|
4
4
|
const peers_sdk_1 = require("@peers-app/peers-sdk");
|
|
5
5
|
const react_1 = require("react");
|
|
6
6
|
const network_viewer_ipc_1 = require("../screens/network-viewer/network-viewer-ipc");
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Subscribe to the current host's System Network Diagnostics status observable, and to System
|
|
9
|
+
* Sync Check's missing-change count.
|
|
10
|
+
*/
|
|
8
11
|
function useNetworkStatus() {
|
|
9
12
|
const [viewState, setViewState] = (0, react_1.useState)({
|
|
10
13
|
availability: "loading",
|
|
@@ -13,12 +16,15 @@ function useNetworkStatus() {
|
|
|
13
16
|
let active = true;
|
|
14
17
|
let generation = 0;
|
|
15
18
|
let statusSubscription;
|
|
19
|
+
let syncCheckSubscription;
|
|
16
20
|
let hadTransport = false;
|
|
17
21
|
const unsubscribeTransport = (0, peers_sdk_1.subscribeHostContractTransport)((transport) => {
|
|
18
22
|
generation += 1;
|
|
19
23
|
const currentGeneration = generation;
|
|
20
24
|
statusSubscription?.dispose();
|
|
21
25
|
statusSubscription = undefined;
|
|
26
|
+
syncCheckSubscription?.dispose();
|
|
27
|
+
syncCheckSubscription = undefined;
|
|
22
28
|
if (!transport) {
|
|
23
29
|
setViewState({ availability: hadTransport ? "unavailable" : "loading" });
|
|
24
30
|
return;
|
|
@@ -30,13 +36,14 @@ function useNetworkStatus() {
|
|
|
30
36
|
if (!active || generation !== currentGeneration) {
|
|
31
37
|
return;
|
|
32
38
|
}
|
|
33
|
-
setViewState({
|
|
39
|
+
setViewState((current) => ({
|
|
40
|
+
...current,
|
|
34
41
|
availability: "ready",
|
|
35
42
|
status: diagnostics.observables.status(),
|
|
36
|
-
});
|
|
43
|
+
}));
|
|
37
44
|
statusSubscription = diagnostics.observables.status.subscribe((status) => {
|
|
38
45
|
if (active && generation === currentGeneration) {
|
|
39
|
-
setViewState({ availability: "ready", status });
|
|
46
|
+
setViewState((current) => ({ ...current, availability: "ready", status }));
|
|
40
47
|
}
|
|
41
48
|
});
|
|
42
49
|
})
|
|
@@ -45,11 +52,27 @@ function useNetworkStatus() {
|
|
|
45
52
|
setViewState({ availability: "unavailable" });
|
|
46
53
|
}
|
|
47
54
|
});
|
|
55
|
+
// A missing count only adds a warning; without it the indicator still works.
|
|
56
|
+
void (0, network_viewer_ipc_1.getSyncCheckConsumer)()
|
|
57
|
+
.then((syncCheck) => {
|
|
58
|
+
if (!active || generation !== currentGeneration) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const update = ({ missingChangeCount }) => {
|
|
62
|
+
if (active && generation === currentGeneration) {
|
|
63
|
+
setViewState((current) => ({ ...current, missingChangeCount }));
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
update(syncCheck.observables.status());
|
|
67
|
+
syncCheckSubscription = syncCheck.observables.status.subscribe(update);
|
|
68
|
+
})
|
|
69
|
+
.catch(() => { });
|
|
48
70
|
});
|
|
49
71
|
return () => {
|
|
50
72
|
active = false;
|
|
51
73
|
generation += 1;
|
|
52
74
|
statusSubscription?.dispose();
|
|
75
|
+
syncCheckSubscription?.dispose();
|
|
53
76
|
unsubscribeTransport();
|
|
54
77
|
};
|
|
55
78
|
}, []);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peers-app/peers-ui",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.5",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/peers-app/peers-ui.git"
|
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"main": "dist/index.js",
|
|
11
11
|
"types": "dist/index.d.ts",
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"!dist/**/*.test.*",
|
|
15
|
+
"!dist/**/__tests__",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
12
18
|
"publishConfig": {
|
|
13
19
|
"access": "public"
|
|
14
20
|
},
|
|
@@ -30,7 +36,7 @@
|
|
|
30
36
|
"lint:fix": "biome check --write ."
|
|
31
37
|
},
|
|
32
38
|
"peerDependencies": {
|
|
33
|
-
"@peers-app/peers-sdk": "^0.27.
|
|
39
|
+
"@peers-app/peers-sdk": "^0.27.5",
|
|
34
40
|
"bootstrap": "^5.3.3",
|
|
35
41
|
"react": "^18.0.0",
|
|
36
42
|
"react-dom": "^18.0.0"
|
|
@@ -41,7 +47,7 @@
|
|
|
41
47
|
"@babel/preset-react": "^7.24.1",
|
|
42
48
|
"@babel/preset-typescript": "^7.27.1",
|
|
43
49
|
"@biomejs/biome": "2.3.11",
|
|
44
|
-
"@peers-app/peers-sdk": "0.27.
|
|
50
|
+
"@peers-app/peers-sdk": "0.27.5",
|
|
45
51
|
"@testing-library/dom": "^10.4.0",
|
|
46
52
|
"@testing-library/jest-dom": "^6.6.3",
|
|
47
53
|
"@testing-library/react": "^16.3.0",
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"Bash(mkdir:*)",
|
|
5
|
-
"Bash(yarn add:*)",
|
|
6
|
-
"Bash(yarn test:*)",
|
|
7
|
-
"Bash(npm test)",
|
|
8
|
-
"Bash(yarn build)",
|
|
9
|
-
"Bash(yarn jest:*)",
|
|
10
|
-
"Bash(mv:*)",
|
|
11
|
-
"Bash(npm run build:*)",
|
|
12
|
-
"Bash(cd:*)",
|
|
13
|
-
"Bash(npm test:*)",
|
|
14
|
-
"Bash(npm run test:*)",
|
|
15
|
-
"Bash(yarn tsc:*)",
|
|
16
|
-
"WebFetch(domain:developer.mozilla.org)",
|
|
17
|
-
"Read(/Users/mark.archer/peers-device/src/**)",
|
|
18
|
-
"Read(/Users/mark.archer/peers-packages/tasks/**)",
|
|
19
|
-
"Read(/Users/mark.archer/peers-packages/tasks/**)",
|
|
20
|
-
"Read(/Users/mark.archer/peers-packages/tasks/**)",
|
|
21
|
-
"Read(/Users/mark.archer/peers-packages/tasks/**)",
|
|
22
|
-
"Read(/Users/mark.archer/peers-packages/tasks/src/**)",
|
|
23
|
-
"Read(/Users/mark.archer/peers-packages/tasks/dist/**)",
|
|
24
|
-
"Read(/Users/mark.archer/peers-packages/tasks/dist/**)",
|
|
25
|
-
"Read(/Users/mark.archer/peers-packages/tasks/dist/**)",
|
|
26
|
-
"Read(/Users/mark.archer/peers-packages/tasks/src/**)",
|
|
27
|
-
"Read(/Users/mark.archer/peers-packages/tasks/src/**)"
|
|
28
|
-
],
|
|
29
|
-
"deny": [],
|
|
30
|
-
"additionalDirectories": [
|
|
31
|
-
"/Users/mark.archer/peers-packages/tasks/dist"
|
|
32
|
-
]
|
|
33
|
-
}
|
|
34
|
-
}
|