@peers-app/peers-ui 0.27.4 → 0.28.0
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/components/message-logs/tool-access-approval-card.js +2 -1
- package/dist/globals.d.ts +1 -0
- package/dist/screens/groups/group-members.js +3 -2
- 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 +0 -1
- package/dist/screens/network-viewer/sync-recovery.js +6 -0
- package/dist/ui-router/routes-loader.d.ts +2 -0
- package/dist/ui-router/routes-loader.js +15 -2
- package/dist/ui-router/ui-loader.js +11 -5
- package/package.json +3 -3
|
@@ -21,6 +21,7 @@ function ToolAccessApprovalCard(props) {
|
|
|
21
21
|
const userContext = await (0, peers_sdk_1.getUserContext)();
|
|
22
22
|
return (0, peers_sdk_1.getCurrentUserAccessLevel)(userContext.getDataContext(approval.dataContextId));
|
|
23
23
|
}, (0, peers_sdk_1.recordAccessLevel)({ accessLevel: 100 }), [approval.dataContextId]);
|
|
24
|
+
const isGroup = (0, hooks_1.usePromise)(async () => !!(await (0, peers_sdk_1.getUserContext)()).getDataContext(approval.dataContextId).groupId, true, [approval.dataContextId]);
|
|
24
25
|
const required = approval.requiredAccessLevel;
|
|
25
26
|
const current = userLevel ?? 100;
|
|
26
27
|
const pending = approval.status === peers_sdk_1.ToolAccessApprovalStatus.pending;
|
|
@@ -53,7 +54,7 @@ function ToolAccessApprovalCard(props) {
|
|
|
53
54
|
setBusy(false);
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
|
-
return ((0, jsx_runtime_1.jsx)("div", { className: "card my-2 bg-body-secondary border-secondary-subtle", "data-testid": "tool-access-approval-card", children: (0, jsx_runtime_1.jsx)("div", { className: "card-body py-2 px-3", children: (0, jsx_runtime_1.jsxs)("div", { className: "d-flex align-items-start gap-2", children: [(0, jsx_runtime_1.jsx)("i", { className: `bi ${statusStyle.icon} ${statusStyle.iconClass} mt-1`, "aria-hidden": "true" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex-grow-1 min-w-0", children: [(0, jsx_runtime_1.jsx)("div", { className: "fw-semibold text-body", children: pending ? "Approval required" : `Access ${approval.status}` }), (0, jsx_runtime_1.jsxs)("div", { className: "small text-body", children: [(0, jsx_runtime_1.jsx)("code", { className: "text-body", children: approval.toolName }), " needs", " ", (0, peers_sdk_1.getAccessLevelLabel)(required), " (", required, "). You have ", (0, peers_sdk_1.getAccessLevelLabel)(current), " ", "(", current, ")."] }), argsPreview && ((0, jsx_runtime_1.jsx)("pre", { className: "small mb-2 mt-2 p-2 bg-body-tertiary border rounded text-body", style: { whiteSpace: "pre-wrap" }, children: argsPreview })), (0, jsx_runtime_1.jsxs)("div", { className: "text-body-secondary small mt-1", children: ["Expires ", approval.expiresAt.toLocaleTimeString(), expired ? " (expired)" : ""] }), approval.resultSummary && ((0, jsx_runtime_1.jsx)("div", { className: "small text-body mt-1", children: approval.resultSummary })), error && (0, jsx_runtime_1.jsx)("div", { className: "text-danger small mt-1", children: error }), pending && !expired && ((0, jsx_runtime_1.jsxs)("div", { className: "d-flex flex-wrap gap-1 mt-2", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-primary", disabled: busy, onClick: () => decide("once"), children: "Allow once" }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "btn btn-sm btn-outline-secondary", disabled: busy, onClick: () => decide("thread"), children: ["Allow in thread (", GRANT_TTL_MINUTES, " min)"] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "btn btn-sm btn-outline-secondary", disabled: busy, onClick: () => decide("context"), children: ["Allow in this context (", GRANT_TTL_MINUTES, " min)"] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-outline-danger", disabled: busy, onClick: () => decide(undefined, true), children: "Deny" })] }))] })] }) }) }));
|
|
57
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: "card my-2 bg-body-secondary border-secondary-subtle", "data-testid": "tool-access-approval-card", children: (0, jsx_runtime_1.jsx)("div", { className: "card-body py-2 px-3", children: (0, jsx_runtime_1.jsxs)("div", { className: "d-flex align-items-start gap-2", children: [(0, jsx_runtime_1.jsx)("i", { className: `bi ${statusStyle.icon} ${statusStyle.iconClass} mt-1`, "aria-hidden": "true" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex-grow-1 min-w-0", children: [(0, jsx_runtime_1.jsx)("div", { className: "fw-semibold text-body", children: pending ? "Approval required" : `Access ${approval.status}` }), (0, jsx_runtime_1.jsxs)("div", { className: "small text-body", children: [(0, jsx_runtime_1.jsx)("code", { className: "text-body", children: approval.toolName }), " needs", " ", (0, peers_sdk_1.getAccessLevelLabel)(required), " (", required, "). You have ", (0, peers_sdk_1.getAccessLevelLabel)(current), " ", "(", current, ")."] }), argsPreview && ((0, jsx_runtime_1.jsx)("pre", { className: "small mb-2 mt-2 p-2 bg-body-tertiary border rounded text-body", style: { whiteSpace: "pre-wrap" }, children: argsPreview })), (0, jsx_runtime_1.jsxs)("div", { className: "text-body-secondary small mt-1", children: ["Expires ", approval.expiresAt.toLocaleTimeString(), expired ? " (expired)" : ""] }), approval.resultSummary && ((0, jsx_runtime_1.jsx)("div", { className: "small text-body mt-1", children: approval.resultSummary })), error && (0, jsx_runtime_1.jsx)("div", { className: "text-danger small mt-1", children: error }), pending && !expired && ((0, jsx_runtime_1.jsxs)("div", { className: "d-flex flex-wrap gap-1 mt-2", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-primary", disabled: busy, onClick: () => decide("once"), children: "Allow once" }), !isGroup && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "btn btn-sm btn-outline-secondary", disabled: busy, onClick: () => decide("thread"), children: ["Allow in thread (", GRANT_TTL_MINUTES, " min)"] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "btn btn-sm btn-outline-secondary", disabled: busy, onClick: () => decide("context"), children: ["Allow in this context (", GRANT_TTL_MINUTES, " min)"] })] })), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-outline-danger", disabled: busy, onClick: () => decide(undefined, true), children: "Deny" })] }))] })] }) }) }));
|
|
57
58
|
}
|
|
58
59
|
function meaningfulArgsPreview(preview) {
|
|
59
60
|
const trimmed = preview?.trim();
|
package/dist/globals.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export declare const groups: import("@peers-app/peers-sdk").Observable<{
|
|
|
30
30
|
founderUserId: string;
|
|
31
31
|
disabled?: boolean | undefined;
|
|
32
32
|
iconClassName?: string | undefined;
|
|
33
|
+
signedAt?: number | undefined;
|
|
33
34
|
publicRole?: import("@peers-app/peers-sdk").GroupMemberRole | undefined;
|
|
34
35
|
}[]>;
|
|
35
36
|
/**
|
|
@@ -110,8 +110,9 @@ const GroupMembersUI = (props) => {
|
|
|
110
110
|
try {
|
|
111
111
|
const member = await groupMembersTable.get(memberId);
|
|
112
112
|
if (member) {
|
|
113
|
-
member
|
|
114
|
-
|
|
113
|
+
// Without `publicKey`, signing binds the grant to the member's key as this device knows it.
|
|
114
|
+
const { publicKey: _granted, ...rest } = member;
|
|
115
|
+
await groupMembersTable.signAndSave({ ...rest, role: newRole });
|
|
115
116
|
setRefreshKey((prev) => prev + 1);
|
|
116
117
|
}
|
|
117
118
|
}
|
|
@@ -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,7 +1,6 @@
|
|
|
1
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. */
|
|
@@ -23,6 +23,9 @@ function statusLabel(entry) {
|
|
|
23
23
|
if (entry.parkedReason === "unresolvable") {
|
|
24
24
|
return "Parked, needs a decision";
|
|
25
25
|
}
|
|
26
|
+
if (entry.parkedReason === "authorization") {
|
|
27
|
+
return "Parked, not authorized";
|
|
28
|
+
}
|
|
26
29
|
return "Parked";
|
|
27
30
|
}
|
|
28
31
|
function statusExplanation(entry) {
|
|
@@ -38,6 +41,9 @@ function statusExplanation(entry) {
|
|
|
38
41
|
if (entry.parkedReason === "unresolvable") {
|
|
39
42
|
return "An automatic fix could not be applied safely — the other record was missing, more than one row matched, or the write failed. The rows were left as they are. Choose an action when you know which version should remain.";
|
|
40
43
|
}
|
|
44
|
+
if (entry.parkedReason === "authorization") {
|
|
45
|
+
return "The change isn't signed by someone allowed to make it — for example, a membership or package release that needs an Admin. This device kept its current row. It checks again on its own whenever the group's members or roles change, so a change that was only waiting for its signer's membership to arrive is written then.";
|
|
46
|
+
}
|
|
41
47
|
return "This entry is waiting and is not being sent to other devices. Retry tries it again. The other actions clear it.";
|
|
42
48
|
}
|
|
43
49
|
/** Show a validation payload as indented JSON when the error ends with one. */
|
|
@@ -4,5 +4,7 @@ import "../ui-defaults";
|
|
|
4
4
|
export type IPackageWithNavs = IPackage & {
|
|
5
5
|
appNavs?: IAppNav[];
|
|
6
6
|
};
|
|
7
|
+
/** Packages with authorized navigation metadata available in the current context. */
|
|
7
8
|
export declare const allPackages: import("@peers-app/peers-sdk").Observable<IPackageWithNavs[]>;
|
|
9
|
+
/** Load package routes and subscribe to changes in the current context. */
|
|
8
10
|
export declare const loadAllRoutes: () => Promise<true | undefined>;
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.loadAllRoutes = exports.allPackages = void 0;
|
|
4
4
|
const peers_sdk_1 = require("@peers-app/peers-sdk");
|
|
5
5
|
require("../ui-defaults");
|
|
6
|
+
/** Packages with authorized navigation metadata available in the current context. */
|
|
6
7
|
exports.allPackages = (0, peers_sdk_1.observable)([]);
|
|
7
8
|
let allRoutesLoaded = false;
|
|
8
9
|
const prefsSubscribed = new Set();
|
|
@@ -46,7 +47,10 @@ async function enrichWithPvAppNavs(pkg) {
|
|
|
46
47
|
if (!pvId)
|
|
47
48
|
return pkg;
|
|
48
49
|
try {
|
|
49
|
-
const
|
|
50
|
+
const dataContext = await (0, peers_sdk_1.getDefaultDataContext)();
|
|
51
|
+
const pv = await (0, peers_sdk_1.PackageVersions)(dataContext).get(pvId);
|
|
52
|
+
if (pv)
|
|
53
|
+
await (0, peers_sdk_1.assertPackageVersionRunnable)(pkg, pv, dataContext);
|
|
50
54
|
if (pv?.appNavs) {
|
|
51
55
|
return { ...pkg, appNavs: pv.appNavs };
|
|
52
56
|
}
|
|
@@ -56,6 +60,7 @@ async function enrichWithPvAppNavs(pkg) {
|
|
|
56
60
|
}
|
|
57
61
|
return pkg;
|
|
58
62
|
}
|
|
63
|
+
/** Load package routes and subscribe to changes in the current context. */
|
|
59
64
|
const loadAllRoutes = async () => {
|
|
60
65
|
if (allRoutesLoaded)
|
|
61
66
|
return;
|
|
@@ -125,9 +130,15 @@ function loadRoutesBundle(pkg, forceRefresh) {
|
|
|
125
130
|
/* use group default */
|
|
126
131
|
}
|
|
127
132
|
let routesBundleFileId;
|
|
133
|
+
let selected;
|
|
128
134
|
if (pvId) {
|
|
129
135
|
try {
|
|
130
|
-
const
|
|
136
|
+
const dataContext = await (0, peers_sdk_1.getDefaultDataContext)();
|
|
137
|
+
const pv = await (0, peers_sdk_1.PackageVersions)(dataContext).get(pvId);
|
|
138
|
+
if (pv) {
|
|
139
|
+
await (0, peers_sdk_1.assertPackageVersionRunnable)(pkg, pv, dataContext);
|
|
140
|
+
selected = { pv, dataContext };
|
|
141
|
+
}
|
|
131
142
|
routesBundleFileId = pv?.routesBundleFileId;
|
|
132
143
|
}
|
|
133
144
|
catch {
|
|
@@ -139,6 +150,8 @@ function loadRoutesBundle(pkg, forceRefresh) {
|
|
|
139
150
|
bundleCode = await peers_sdk_1.rpcServerCalls.getFileContents(routesBundleFileId);
|
|
140
151
|
}
|
|
141
152
|
if (bundleCode) {
|
|
153
|
+
if (selected?.dataContext.groupId)
|
|
154
|
+
(0, peers_sdk_1.assertPackageBundleIntegrity)(selected.pv, "routes", bundleCode);
|
|
142
155
|
const exportRoutes = (peerRoutes) => {
|
|
143
156
|
const rw = window;
|
|
144
157
|
peerRoutes.routes.forEach((route) => {
|
|
@@ -182,7 +182,7 @@ const UILoader = (args) => {
|
|
|
182
182
|
return (0, jsx_runtime_1.jsx)(UIAsyncLoader, { peersUIId: peersUIId, props: props }, peersUIId);
|
|
183
183
|
};
|
|
184
184
|
const uiLoadingPromises = {};
|
|
185
|
-
async function
|
|
185
|
+
async function resolveUiBundleVersion(pkg) {
|
|
186
186
|
let pvId = pkg.activePackageVersionId;
|
|
187
187
|
try {
|
|
188
188
|
const pvar = (0, peers_sdk_1.packagePrefsVar)(pkg.packageId);
|
|
@@ -195,8 +195,11 @@ async function resolveUiBundleFileId(pkg) {
|
|
|
195
195
|
if (!pvId)
|
|
196
196
|
return undefined;
|
|
197
197
|
try {
|
|
198
|
-
const
|
|
199
|
-
|
|
198
|
+
const dataContext = await (0, peers_sdk_1.getDefaultDataContext)();
|
|
199
|
+
const pv = await (0, peers_sdk_1.PackageVersions)(dataContext).get(pvId);
|
|
200
|
+
if (pv)
|
|
201
|
+
await (0, peers_sdk_1.assertPackageVersionRunnable)(pkg, pv, dataContext);
|
|
202
|
+
return pv ? { pv, dataContext } : undefined;
|
|
200
203
|
}
|
|
201
204
|
catch {
|
|
202
205
|
return undefined;
|
|
@@ -208,12 +211,15 @@ function loadUIBundle(pkg, forceRefresh) {
|
|
|
208
211
|
const sTime = Date.now();
|
|
209
212
|
console.log(`loading ui bundle for ${pkg.name}`);
|
|
210
213
|
importPromise = (async () => {
|
|
211
|
-
const
|
|
212
|
-
|
|
214
|
+
const selected = await resolveUiBundleVersion(pkg);
|
|
215
|
+
const uiBundleFileId = selected?.pv.uiBundleFileId;
|
|
216
|
+
if (!selected || !uiBundleFileId) {
|
|
213
217
|
return;
|
|
214
218
|
}
|
|
215
219
|
const bundleCode = await peers_sdk_1.rpcServerCalls.getFileContents(uiBundleFileId);
|
|
216
220
|
if (bundleCode) {
|
|
221
|
+
if (selected.dataContext.groupId)
|
|
222
|
+
(0, peers_sdk_1.assertPackageBundleIntegrity)(selected.pv, "ui", bundleCode);
|
|
217
223
|
const exportUIs = (peerUIs) => {
|
|
218
224
|
peerUIs?.uis?.forEach((ui) => {
|
|
219
225
|
peersUIs[ui.peersUIId] = ui;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peers-app/peers-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/peers-app/peers-ui.git"
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"lint:fix": "biome check --write ."
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
|
-
"@peers-app/peers-sdk": "^0.
|
|
39
|
+
"@peers-app/peers-sdk": "^0.28.0",
|
|
40
40
|
"bootstrap": "^5.3.3",
|
|
41
41
|
"react": "^18.0.0",
|
|
42
42
|
"react-dom": "^18.0.0"
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"@babel/preset-react": "^7.24.1",
|
|
48
48
|
"@babel/preset-typescript": "^7.27.1",
|
|
49
49
|
"@biomejs/biome": "2.3.11",
|
|
50
|
-
"@peers-app/peers-sdk": "0.
|
|
50
|
+
"@peers-app/peers-sdk": "0.28.0",
|
|
51
51
|
"@testing-library/dom": "^10.4.0",
|
|
52
52
|
"@testing-library/jest-dom": "^6.6.3",
|
|
53
53
|
"@testing-library/react": "^16.3.0",
|