@peers-app/peers-ui 0.26.3 → 0.26.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/components/group-switcher.d.ts +6 -0
- package/dist/components/group-switcher.js +8 -1
- package/dist/components/markdown-with-mentions.d.ts +4 -0
- package/dist/components/markdown-with-mentions.js +6 -1
- package/dist/components/save-button.d.ts +15 -0
- package/dist/components/save-button.js +14 -3
- package/dist/globals.js +34 -8
- package/dist/host-shell/host-shell-consumer.d.ts +21 -0
- package/dist/host-shell/host-shell-consumer.js +50 -0
- package/dist/host-shell/host-shell-consumer.test.d.ts +1 -0
- package/dist/host-shell/host-shell-consumer.test.js +28 -0
- package/dist/mention-configs.js +2 -1
- package/dist/screens/groups/group-list.js +4 -1
- package/dist/screens/packages/package-details.d.ts +1 -0
- package/dist/screens/packages/package-details.js +27 -8
- package/dist/screens/packages/package-helpers.d.ts +3 -23
- package/dist/screens/packages/package-helpers.js +12 -46
- package/dist/screens/packages/package-helpers.test.d.ts +1 -0
- package/dist/screens/packages/package-helpers.test.js +53 -0
- package/dist/screens/packages/package-info.js +3 -2
- package/dist/screens/packages/package-list.d.ts +4 -2
- package/dist/screens/packages/package-list.js +50 -10
- package/dist/screens/packages/package-new-local.js +7 -4
- package/dist/screens/packages/package-runtime-status.d.ts +12 -0
- package/dist/screens/packages/package-runtime-status.js +33 -0
- package/dist/screens/packages/package-runtime-status.test.d.ts +1 -0
- package/dist/screens/packages/package-runtime-status.test.js +50 -0
- package/dist/screens/packages/package-versions.js +8 -55
- package/dist/screens/packages/packages-ipc.d.ts +41 -0
- package/dist/screens/packages/packages-ipc.js +102 -0
- package/dist/screens/packages/packages-ipc.test.d.ts +1 -0
- package/dist/screens/packages/packages-ipc.test.js +43 -0
- package/dist/screens/tools/tool-catalog-ipc.test.js +6 -0
- package/dist/screens/variables/variable-details.d.ts +1 -0
- package/dist/screens/variables/variable-details.js +11 -5
- package/dist/tabs-layout/network-status-indicator.d.ts +4 -1
- package/dist/tabs-layout/network-status-indicator.js +12 -6
- package/dist/tabs-layout/network-status-indicator.test.js +1 -1
- package/dist/ui-router/ui-loader.js +10 -37
- package/package.json +3 -3
- package/src/components/group-switcher.tsx +12 -4
- package/src/components/markdown-with-mentions.tsx +7 -2
- package/src/components/save-button.tsx +24 -3
- package/src/globals.tsx +32 -11
- package/src/host-shell/host-shell-consumer.test.ts +32 -0
- package/src/host-shell/host-shell-consumer.ts +53 -0
- package/src/mention-configs.ts +2 -2
- package/src/screens/groups/group-list.tsx +6 -4
- package/src/screens/packages/package-details.tsx +31 -8
- package/src/screens/packages/package-helpers.test.ts +57 -0
- package/src/screens/packages/package-helpers.ts +13 -55
- package/src/screens/packages/package-info.tsx +3 -3
- package/src/screens/packages/package-list.tsx +51 -16
- package/src/screens/packages/package-new-local.tsx +8 -13
- package/src/screens/packages/package-runtime-status.test.tsx +58 -0
- package/src/screens/packages/package-runtime-status.tsx +68 -0
- package/src/screens/packages/package-versions.tsx +9 -72
- package/src/screens/packages/packages-ipc.test.ts +47 -0
- package/src/screens/packages/packages-ipc.ts +125 -0
- package/src/screens/tools/tool-catalog-ipc.test.ts +7 -1
- package/src/screens/variables/variable-details.tsx +11 -6
- package/src/tabs-layout/network-status-indicator.test.tsx +1 -1
- package/src/tabs-layout/network-status-indicator.tsx +24 -11
- package/src/ui-router/ui-loader.tsx +12 -51
|
@@ -10,9 +10,12 @@ const loading_indicator_1 = require("../../components/loading-indicator");
|
|
|
10
10
|
const tooltip_1 = require("../../components/tooltip");
|
|
11
11
|
const globals_1 = require("../../globals");
|
|
12
12
|
const hooks_1 = require("../../hooks");
|
|
13
|
+
const host_shell_consumer_1 = require("../../host-shell/host-shell-consumer");
|
|
13
14
|
const ui_loader_1 = require("../../ui-router/ui-loader");
|
|
14
15
|
const local_folder_grants_panel_1 = require("./local-folder-grants-panel");
|
|
15
16
|
const package_helpers_1 = require("./package-helpers");
|
|
17
|
+
const package_runtime_status_1 = require("./package-runtime-status");
|
|
18
|
+
const packages_ipc_1 = require("./packages-ipc");
|
|
16
19
|
function tagBadgeClass(tag) {
|
|
17
20
|
if (tag === "dev")
|
|
18
21
|
return "text-bg-danger";
|
|
@@ -31,6 +34,18 @@ const PackageRow = ({ pkg, onUpdated }) => {
|
|
|
31
34
|
const [updating, setUpdating] = (0, react_1.useState)(false);
|
|
32
35
|
const [devicePrefs] = (0, hooks_1.useObservable)((0, peers_sdk_1.packagePrefsVar)(pkg.packageId));
|
|
33
36
|
const effective = (0, peers_sdk_1.getEffectivePackagePrefs)(pkg, devicePrefs);
|
|
37
|
+
// A dev reload rewrites the selected version row in place (same id, new version), so the
|
|
38
|
+
// ids below do not change; recompute when this package's version rows change.
|
|
39
|
+
const [versionsRevision, setVersionsRevision] = (0, react_1.useState)(0);
|
|
40
|
+
(0, react_1.useEffect)(() => {
|
|
41
|
+
const sub = (0, peers_sdk_1.PackageVersions)().dataChanged.subscribe((evt) => {
|
|
42
|
+
if (evt.dataObject?.packageId === pkg.packageId)
|
|
43
|
+
setVersionsRevision((value) => value + 1);
|
|
44
|
+
});
|
|
45
|
+
return () => {
|
|
46
|
+
sub.unsubscribe();
|
|
47
|
+
};
|
|
48
|
+
}, [pkg.packageId]);
|
|
34
49
|
const status = (0, hooks_1.usePromise)(async () => {
|
|
35
50
|
if (!effective.activePackageVersionId)
|
|
36
51
|
return null;
|
|
@@ -65,6 +80,7 @@ const PackageRow = ({ pkg, onUpdated }) => {
|
|
|
65
80
|
pkg.packageId,
|
|
66
81
|
effective.followTags,
|
|
67
82
|
pkg.activePackageVersionId,
|
|
83
|
+
versionsRevision,
|
|
68
84
|
]);
|
|
69
85
|
const isOnDev = status?.activePv.versionTag === "dev";
|
|
70
86
|
const hasGroupRelease = !!status?.groupReleasePv;
|
|
@@ -91,7 +107,10 @@ const PackageRow = ({ pkg, onUpdated }) => {
|
|
|
91
107
|
async function handleUseRelease() {
|
|
92
108
|
setUpdating(true);
|
|
93
109
|
try {
|
|
94
|
-
await (0,
|
|
110
|
+
await (await (0, packages_ipc_1.packagesTools)()).activatePackageVersion({
|
|
111
|
+
packageId: pkg.packageId,
|
|
112
|
+
scope: "device",
|
|
113
|
+
});
|
|
95
114
|
onUpdated();
|
|
96
115
|
}
|
|
97
116
|
catch (err) {
|
|
@@ -106,8 +125,10 @@ const PackageRow = ({ pkg, onUpdated }) => {
|
|
|
106
125
|
return;
|
|
107
126
|
setUpdating(true);
|
|
108
127
|
try {
|
|
109
|
-
await (0,
|
|
110
|
-
|
|
128
|
+
await (await (0, packages_ipc_1.packagesTools)()).activatePackageVersion({
|
|
129
|
+
packageId: pkg.packageId,
|
|
130
|
+
packageVersionId: status.latestDevPv.packageVersionId,
|
|
131
|
+
scope: "device",
|
|
111
132
|
});
|
|
112
133
|
onUpdated();
|
|
113
134
|
}
|
|
@@ -118,10 +139,12 @@ const PackageRow = ({ pkg, onUpdated }) => {
|
|
|
118
139
|
setUpdating(false);
|
|
119
140
|
}
|
|
120
141
|
}
|
|
121
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: "container-fluid pb-4 d-flex align-items-center", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex-grow-1", style: { minWidth: 0 }, children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-box-fill" }), "\u00A0\u00A0", (0, jsx_runtime_1.jsx)("a", { href: `#packages/${pkg.packageId}`, children: pkg.name }), isDeviceOverridden && ((0, jsx_runtime_1.jsx)("span", { className: "text-warning ms-1", title: "This device is using a different version than the group", children: (0, jsx_runtime_1.jsx)("i", { className: "bi bi-pc-display" }) })), status && ((0, jsx_runtime_1.jsxs)("span", { className: "ms-2", children: [(0, jsx_runtime_1.jsxs)("small", { className: "text-muted", children: ["v", status.activePv.version] }), status.activePv.versionTag && ((0, jsx_runtime_1.jsx)("span", { className: `badge ms-1 ${tagBadgeClass(status.activePv.versionTag)}`, style: { fontSize: "0.65em" }, children: status.activePv.versionTag })), hasUpdate ? ((0, jsx_runtime_1.jsxs)("span", { className: `badge ms-1 ${updateBadgeClass(status.newerLevel)}`, style: { fontSize: "0.65em" }, children: ["v", status.newestPv?.version, " available"] })) : isOnDev && hasGroupRelease ? ((0, jsx_runtime_1.jsxs)("span", { className: "badge ms-1 text-bg-secondary", style: { fontSize: "0.65em" }, children: ["release: v", status.groupReleasePv?.version] })) : ((0, jsx_runtime_1.jsx)("span", { className: "badge ms-1 text-bg-success", style: { fontSize: "0.65em" }, children: "up to date" }))] })), (0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { markdownContent: pkg.description, positions: ["bottom", "top", "right", "left"] })] }), hasUpdate && ((0, jsx_runtime_1.jsxs)("button", { className: "btn btn-sm btn-outline-primary ms-2 text-nowrap", disabled: updating, onClick: handleUpdate, children: [updating ? ((0, jsx_runtime_1.jsx)("span", { className: "spinner-border spinner-border-sm me-1" })) : ((0, jsx_runtime_1.jsx)("i", { className: "bi bi-arrow-up-circle me-1" })), "Update"] })), isOnDev && hasGroupRelease && ((0, jsx_runtime_1.jsxs)("button", { className: "btn btn-sm btn-outline-secondary ms-2 text-nowrap", disabled: updating, onClick: handleUseRelease, children: [updating ? ((0, jsx_runtime_1.jsx)("span", { className: "spinner-border spinner-border-sm me-1" })) : ((0, jsx_runtime_1.jsx)("i", { className: "bi bi-box-arrow-in-right me-1" })), "Use Release"] })), hasDevVersion && ((0, jsx_runtime_1.jsxs)("button", { className: "btn btn-sm btn-outline-danger ms-2 text-nowrap", disabled: updating, onClick: handleUseDev, children: [updating ? ((0, jsx_runtime_1.jsx)("span", { className: "spinner-border spinner-border-sm me-1" })) : ((0, jsx_runtime_1.jsx)("i", { className: "bi bi-tools me-1" })), "Use Dev"] }))] }));
|
|
142
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "container-fluid pb-4 d-flex align-items-center", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex-grow-1", style: { minWidth: 0 }, children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-box-fill" }), "\u00A0\u00A0", (0, jsx_runtime_1.jsx)("a", { href: `#packages/${pkg.packageId}`, children: pkg.name }), (0, jsx_runtime_1.jsx)("span", { className: "ms-2", children: (0, jsx_runtime_1.jsx)(package_runtime_status_1.PackageRuntimeBadge, { packageId: pkg.packageId }) }), isDeviceOverridden && ((0, jsx_runtime_1.jsx)("span", { className: "text-warning ms-1", title: "This device is using a different version than the group", children: (0, jsx_runtime_1.jsx)("i", { className: "bi bi-pc-display" }) })), status && ((0, jsx_runtime_1.jsxs)("span", { className: "ms-2", children: [(0, jsx_runtime_1.jsxs)("small", { className: "text-muted", children: ["v", status.activePv.version] }), status.activePv.versionTag && ((0, jsx_runtime_1.jsx)("span", { className: `badge ms-1 ${tagBadgeClass(status.activePv.versionTag)}`, style: { fontSize: "0.65em" }, children: status.activePv.versionTag })), hasUpdate ? ((0, jsx_runtime_1.jsxs)("span", { className: `badge ms-1 ${updateBadgeClass(status.newerLevel)}`, style: { fontSize: "0.65em" }, children: ["v", status.newestPv?.version, " available"] })) : isOnDev && hasGroupRelease ? ((0, jsx_runtime_1.jsxs)("span", { className: "badge ms-1 text-bg-secondary", style: { fontSize: "0.65em" }, children: ["release: v", status.groupReleasePv?.version] })) : ((0, jsx_runtime_1.jsx)("span", { className: "badge ms-1 text-bg-success", style: { fontSize: "0.65em" }, children: "up to date" }))] })), (0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { markdownContent: pkg.description, positions: ["bottom", "top", "right", "left"] })] }), hasUpdate && ((0, jsx_runtime_1.jsxs)("button", { className: "btn btn-sm btn-outline-primary ms-2 text-nowrap", disabled: updating, onClick: handleUpdate, children: [updating ? ((0, jsx_runtime_1.jsx)("span", { className: "spinner-border spinner-border-sm me-1" })) : ((0, jsx_runtime_1.jsx)("i", { className: "bi bi-arrow-up-circle me-1" })), "Update"] })), isOnDev && hasGroupRelease && ((0, jsx_runtime_1.jsxs)("button", { className: "btn btn-sm btn-outline-secondary ms-2 text-nowrap", disabled: updating, onClick: handleUseRelease, children: [updating ? ((0, jsx_runtime_1.jsx)("span", { className: "spinner-border spinner-border-sm me-1" })) : ((0, jsx_runtime_1.jsx)("i", { className: "bi bi-box-arrow-in-right me-1" })), "Use Release"] })), hasDevVersion && ((0, jsx_runtime_1.jsxs)("button", { className: "btn btn-sm btn-outline-danger ms-2 text-nowrap", disabled: updating, onClick: handleUseDev, children: [updating ? ((0, jsx_runtime_1.jsx)("span", { className: "spinner-border spinner-border-sm me-1" })) : ((0, jsx_runtime_1.jsx)("i", { className: "bi bi-tools me-1" })), "Use Dev"] }))] }));
|
|
122
143
|
};
|
|
123
|
-
/**
|
|
124
|
-
|
|
144
|
+
/**
|
|
145
|
+
* Packages screen: search, create/import/install from the search box, local folder grants,
|
|
146
|
+
* and the installed package list.
|
|
147
|
+
*/
|
|
125
148
|
const PackageList = () => {
|
|
126
149
|
const [searchTextObs] = (0, react_1.useState)(() => (0, peers_sdk_1.observable)(""));
|
|
127
150
|
const [searchText] = (0, hooks_1.useObservable)(searchTextObs);
|
|
@@ -201,12 +224,12 @@ const PackageList = () => {
|
|
|
201
224
|
const name = searchText.trim();
|
|
202
225
|
if (!name)
|
|
203
226
|
return;
|
|
204
|
-
if (
|
|
227
|
+
if ((0, peers_sdk_1.isPackageRepositoryUrl)(name)) {
|
|
205
228
|
if (!confirm(`Add remote package: ${name}`))
|
|
206
229
|
return;
|
|
207
230
|
try {
|
|
208
231
|
addingPackage(true);
|
|
209
|
-
const { packageId } = (0,
|
|
232
|
+
const { packageId } = await (await (0, packages_ipc_1.packagesTools)()).clonePackage({ url: name });
|
|
210
233
|
const pkg = await (0, peers_sdk_1.Packages)().get(packageId);
|
|
211
234
|
if (pkg) {
|
|
212
235
|
(0, globals_1.mainContentPath)(`packages/${packageId}`);
|
|
@@ -222,11 +245,28 @@ const PackageList = () => {
|
|
|
222
245
|
}
|
|
223
246
|
return;
|
|
224
247
|
}
|
|
248
|
+
// A package id re-enables that package, or copies it here from another of your contexts.
|
|
249
|
+
if ((0, peers_sdk_1.isid)(name)) {
|
|
250
|
+
if (!confirm(`Install package ${name} into this context`))
|
|
251
|
+
return;
|
|
252
|
+
try {
|
|
253
|
+
addingPackage(true);
|
|
254
|
+
const { packageId } = await (await (0, packages_ipc_1.packagesTools)()).installPackage({ packageId: name });
|
|
255
|
+
(0, globals_1.mainContentPath)(`packages/${packageId}`);
|
|
256
|
+
}
|
|
257
|
+
catch (err) {
|
|
258
|
+
confirm(`Error installing package: ${err instanceof Error ? err.message : String(err)}`);
|
|
259
|
+
}
|
|
260
|
+
finally {
|
|
261
|
+
addingPackage(false);
|
|
262
|
+
}
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
225
265
|
if (!confirm(`Create or import local package: ${searchText}`))
|
|
226
266
|
return;
|
|
227
267
|
try {
|
|
228
268
|
addingPackage(true);
|
|
229
|
-
const { packageId, warning } = (0,
|
|
269
|
+
const { packageId, warning } = await (await (0, packages_ipc_1.packagesTools)()).createPackage({ name });
|
|
230
270
|
const pkg = await (0, peers_sdk_1.Packages)().get(packageId);
|
|
231
271
|
if (pkg) {
|
|
232
272
|
if (warning) {
|
|
@@ -245,7 +285,7 @@ const PackageList = () => {
|
|
|
245
285
|
if (addingPackage()) {
|
|
246
286
|
return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("div", { className: "container-fluid d-flex justify-content-center mt-5", children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-box-seam" }), "\u00A0 Adding Package"] }), (0, jsx_runtime_1.jsx)("div", { className: "container-fluid d-flex justify-content-center mt-2", children: (0, jsx_runtime_1.jsx)("div", { className: "progress", role: "progressbar", "aria-label": "Animated striped example", "aria-valuenow": 100, "aria-valuemin": 0, "aria-valuemax": 100, children: (0, jsx_runtime_1.jsx)("div", { className: "progress-bar progress-bar-striped progress-bar-animated", style: { width: 150 } }) }) })] }));
|
|
247
287
|
}
|
|
248
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: "container-fluid", children: [(0, jsx_runtime_1.jsxs)("div", { className: "input-group mt-3 mb-3", children: [(0, jsx_runtime_1.jsx)(input_1.Input, { value: searchTextObs, className: "form-control", placeholder: "Search, add, or create package", autoFocus: !!(0, globals_1.isDesktop)(), onKeyUp: (evt) => searchSubmit(evt) }), (0, jsx_runtime_1.jsx)("button", { className: "btn btn-outline-secondary dropdown-toggle sm", type: "button", "data-bs-toggle": "dropdown", "aria-expanded": "false", children: (0, jsx_runtime_1.jsx)("i", { className: "bi bi-plus-lg" }) }), (0, jsx_runtime_1.jsxs)("ul", { className: "dropdown-menu dropdown-menu-end", children: [(0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsx)("a", { className: "dropdown-item", href: "#packages/newlocal", children: "New Local Package" }) }), (0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsx)("a", { className: "dropdown-item disabled", href: "#", children: "Add Local Package" }) }), (0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsx)("a", { className: "dropdown-item disabled", href: "#", children: "Add Remote Package" }) }), (0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsx)("hr", { className: "dropdown-divider" }) }), (0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsx)("a", { className: "dropdown-item", onClick: () =>
|
|
288
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "container-fluid", children: [(0, jsx_runtime_1.jsxs)("div", { className: "input-group mt-3 mb-3", children: [(0, jsx_runtime_1.jsx)(input_1.Input, { value: searchTextObs, className: "form-control", placeholder: "Search, add, or create package", autoFocus: !!(0, globals_1.isDesktop)(), onKeyUp: (evt) => searchSubmit(evt) }), (0, jsx_runtime_1.jsx)("button", { className: "btn btn-outline-secondary dropdown-toggle sm", type: "button", "data-bs-toggle": "dropdown", "aria-expanded": "false", children: (0, jsx_runtime_1.jsx)("i", { className: "bi bi-plus-lg" }) }), (0, jsx_runtime_1.jsxs)("ul", { className: "dropdown-menu dropdown-menu-end", children: [(0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsx)("a", { className: "dropdown-item", href: "#packages/newlocal", children: "New Local Package" }) }), (0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsx)("a", { className: "dropdown-item disabled", href: "#", children: "Add Local Package" }) }), (0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsx)("a", { className: "dropdown-item disabled", href: "#", children: "Add Remote Package" }) }), (0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsx)("hr", { className: "dropdown-divider" }) }), (0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsx)("a", { className: "dropdown-item", onClick: () => void (0, host_shell_consumer_1.openOnHost)(peers_sdk_1.packagesRootDir), style: { cursor: "pointer" }, children: "Open Packages Directory" }) })] })] }), (0, jsx_runtime_1.jsx)(local_folder_grants_panel_1.LocalFolderGrantsPanel, {}), (0, jsx_runtime_1.jsx)("div", { className: "peers-list-container", children: (0, jsx_runtime_1.jsx)(lazy_list_1.LazyList, { resetTrigger: `${searchText}_${refreshKey()}`, loadMore: loadMore, scrollThreshold: 0.6, renderItems: (packages) => {
|
|
249
289
|
return packages.map((pkg) => ((0, jsx_runtime_1.jsx)(PackageRow, { pkg: pkg, onUpdated: () => refreshKey(refreshKey() + 1) }, pkg.packageId)));
|
|
250
290
|
}, loadingIndicator: (0, jsx_runtime_1.jsx)("div", { className: "d-flex justify-content-center", style: { height: 200 }, children: (0, jsx_runtime_1.jsx)(loading_indicator_1.LoadingIndicator, {}) }), endOfList: (0, jsx_runtime_1.jsx)("div", { className: "d-flex justify-content-center", style: { height: 200 } }) }) })] }));
|
|
251
291
|
};
|
|
@@ -38,6 +38,8 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
38
38
|
const peers_sdk_1 = require("@peers-app/peers-sdk");
|
|
39
39
|
const react_1 = __importStar(require("react"));
|
|
40
40
|
const globals_1 = require("../../globals");
|
|
41
|
+
const host_shell_consumer_1 = require("../../host-shell/host-shell-consumer");
|
|
42
|
+
const packages_ipc_1 = require("./packages-ipc");
|
|
41
43
|
/** Form for scaffolding a new local package from the template into `~/peers/packages`. */
|
|
42
44
|
const PackageNewLocal = () => {
|
|
43
45
|
const defaultName = "New Package";
|
|
@@ -59,16 +61,17 @@ const PackageNewLocal = () => {
|
|
|
59
61
|
};
|
|
60
62
|
const createLocalPackage = async () => {
|
|
61
63
|
try {
|
|
62
|
-
const { packageId, warning } = (0,
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
const { packageId, warning } = await (await (0, packages_ipc_1.packagesTools)()).createPackage({
|
|
65
|
+
name: packageName,
|
|
66
|
+
location: packageLocation || packageLocationDefault,
|
|
67
|
+
});
|
|
65
68
|
const pkg = await (0, peers_sdk_1.Packages)().get(packageId);
|
|
66
69
|
if (pkg) {
|
|
67
70
|
if (warning) {
|
|
68
71
|
confirm(warning);
|
|
69
72
|
}
|
|
70
73
|
(0, globals_1.mainContentPath)(`packages/${packageId}`);
|
|
71
|
-
|
|
74
|
+
void (0, host_shell_consumer_1.openInEditorOnHost)(packageLocation || packageLocationDefault);
|
|
72
75
|
}
|
|
73
76
|
}
|
|
74
77
|
catch (err) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
interface IPackageRuntimeProps {
|
|
2
|
+
/** Package to show. */
|
|
3
|
+
packageId: string;
|
|
4
|
+
}
|
|
5
|
+
/** Small badge with a package's runtime status on this device; renders nothing until known. */
|
|
6
|
+
export declare const PackageRuntimeBadge: ({ packageId }: IPackageRuntimeProps) => import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
/**
|
|
8
|
+
* Runtime status on this device: badge, running version, and the last load or worker
|
|
9
|
+
* failure.
|
|
10
|
+
*/
|
|
11
|
+
export declare const PackageRuntimeStatus: ({ packageId }: IPackageRuntimeProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PackageRuntimeStatus = exports.PackageRuntimeBadge = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const packages_ipc_1 = require("./packages-ipc");
|
|
6
|
+
/** Small badge with a package's runtime status on this device; renders nothing until known. */
|
|
7
|
+
const PackageRuntimeBadge = ({ packageId }) => {
|
|
8
|
+
const states = (0, packages_ipc_1.usePackageRuntimeStates)();
|
|
9
|
+
return (0, jsx_runtime_1.jsx)(RuntimeBadge, { state: states?.[packageId] });
|
|
10
|
+
};
|
|
11
|
+
exports.PackageRuntimeBadge = PackageRuntimeBadge;
|
|
12
|
+
/**
|
|
13
|
+
* Runtime status on this device: badge, running version, and the last load or worker
|
|
14
|
+
* failure.
|
|
15
|
+
*/
|
|
16
|
+
const PackageRuntimeStatus = ({ packageId }) => {
|
|
17
|
+
const state = (0, packages_ipc_1.usePackageRuntimeStates)()?.[packageId];
|
|
18
|
+
if (!state)
|
|
19
|
+
return null;
|
|
20
|
+
const running = state.running;
|
|
21
|
+
const workerError = state.worker?.lastWorkerError;
|
|
22
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "mb-3", "data-testid": "package-runtime-status", children: [(0, jsx_runtime_1.jsxs)("div", { className: "d-flex align-items-center gap-2", children: [(0, jsx_runtime_1.jsx)(RuntimeBadge, { state: state }), running?.version ? ((0, jsx_runtime_1.jsxs)("span", { className: "text-muted small", children: ["Running v", running.version, running.versionTag ? ` (${running.versionTag})` : "", " on this device"] })) : state.reason ? ((0, jsx_runtime_1.jsx)("span", { className: "text-muted small", children: state.reason })) : null] }), state.status === "failed" && state.lastError ? ((0, jsx_runtime_1.jsx)(RuntimeError, { title: state.lastError.version
|
|
23
|
+
? `Loading v${state.lastError.version} failed`
|
|
24
|
+
: "Loading this package failed", message: state.lastError.message })) : null, workerError ? ((0, jsx_runtime_1.jsx)(RuntimeError, { title: "The package worker failed", message: workerError.message })) : null] }));
|
|
25
|
+
};
|
|
26
|
+
exports.PackageRuntimeStatus = PackageRuntimeStatus;
|
|
27
|
+
const RuntimeBadge = ({ state }) => {
|
|
28
|
+
const badge = (0, packages_ipc_1.packageStatusBadge)(state);
|
|
29
|
+
if (!badge)
|
|
30
|
+
return null;
|
|
31
|
+
return ((0, jsx_runtime_1.jsx)("span", { className: `badge text-bg-${badge.color}`, title: state?.lastError?.message, children: badge.label }));
|
|
32
|
+
};
|
|
33
|
+
const RuntimeError = ({ title, message }) => ((0, jsx_runtime_1.jsxs)("div", { className: "alert alert-danger py-2 px-3 mt-2 mb-0 small", role: "alert", children: [(0, jsx_runtime_1.jsxs)("strong", { children: [title, ":"] }), " ", (0, jsx_runtime_1.jsx)("span", { className: "font-monospace", children: message })] }));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
4
|
+
const react_1 = require("@testing-library/react");
|
|
5
|
+
const package_runtime_status_1 = require("./package-runtime-status");
|
|
6
|
+
let mockStates;
|
|
7
|
+
jest.mock("./packages-ipc", () => ({
|
|
8
|
+
...jest.requireActual("./packages-ipc"),
|
|
9
|
+
usePackageRuntimeStates: () => mockStates,
|
|
10
|
+
}));
|
|
11
|
+
const packageId = "pkg";
|
|
12
|
+
describe("PackageRuntimeStatus", () => {
|
|
13
|
+
it("renders nothing until the host reports state", () => {
|
|
14
|
+
mockStates = undefined;
|
|
15
|
+
expect((0, react_1.render)((0, jsx_runtime_1.jsx)(package_runtime_status_1.PackageRuntimeStatus, { packageId: packageId })).container).toBeEmptyDOMElement();
|
|
16
|
+
});
|
|
17
|
+
it("shows the running version and a failed replacement's error", () => {
|
|
18
|
+
mockStates = {
|
|
19
|
+
[packageId]: {
|
|
20
|
+
packageId,
|
|
21
|
+
status: "failed",
|
|
22
|
+
running: { kind: "isolated", version: "1.0.0", versionTag: "dev" },
|
|
23
|
+
lastError: { phase: "load", message: "manifest has unknown key", version: "1.0.1", at: 1 },
|
|
24
|
+
updatedAt: 1,
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
const view = (0, react_1.render)((0, jsx_runtime_1.jsx)(package_runtime_status_1.PackageRuntimeStatus, { packageId: packageId }));
|
|
28
|
+
expect(view.container).toHaveTextContent("Update failed");
|
|
29
|
+
expect(view.container).toHaveTextContent("Running v1.0.0 (dev) on this device");
|
|
30
|
+
expect(view.getByRole("alert")).toHaveTextContent("Loading v1.0.1 failed: manifest has unknown key");
|
|
31
|
+
});
|
|
32
|
+
it("shows a worker failure and a reason when nothing is running", () => {
|
|
33
|
+
mockStates = {
|
|
34
|
+
[packageId]: {
|
|
35
|
+
packageId,
|
|
36
|
+
status: "unavailable",
|
|
37
|
+
reason: "The selected version's bundle file is not on this device yet",
|
|
38
|
+
worker: { workerRunning: false, lastWorkerError: { message: "boom", at: 1 } },
|
|
39
|
+
updatedAt: 1,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
const view = (0, react_1.render)((0, jsx_runtime_1.jsx)(package_runtime_status_1.PackageRuntimeStatus, { packageId: packageId }));
|
|
43
|
+
expect(view.container).toHaveTextContent("not on this device yet");
|
|
44
|
+
expect(view.getByRole("alert")).toHaveTextContent("The package worker failed: boom");
|
|
45
|
+
});
|
|
46
|
+
it("renders a running badge", () => {
|
|
47
|
+
mockStates = { [packageId]: { packageId, status: "running", updatedAt: 1 } };
|
|
48
|
+
expect((0, react_1.render)((0, jsx_runtime_1.jsx)(package_runtime_status_1.PackageRuntimeBadge, { packageId: packageId })).container).toHaveTextContent("Running");
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -6,6 +6,7 @@ const peers_sdk_1 = require("@peers-app/peers-sdk");
|
|
|
6
6
|
const react_1 = require("react");
|
|
7
7
|
const hooks_1 = require("../../hooks");
|
|
8
8
|
const package_helpers_1 = require("./package-helpers");
|
|
9
|
+
const packages_ipc_1 = require("./packages-ipc");
|
|
9
10
|
function formatDate(iso) {
|
|
10
11
|
try {
|
|
11
12
|
const d = new Date(iso);
|
|
@@ -107,50 +108,16 @@ const PackageVersionsList = (props) => {
|
|
|
107
108
|
}
|
|
108
109
|
async function promoteVersion(pv, newTag) {
|
|
109
110
|
try {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
action: `promoted:${newTag}`,
|
|
114
|
-
by: me.userId,
|
|
115
|
-
at: new Date().toISOString(),
|
|
116
|
-
signature: "",
|
|
117
|
-
});
|
|
118
|
-
const pvHash = (0, peers_sdk_1.computePackageVersionHash)(pv.version, newTag, pv.packageBundleFileHash, pv.routesBundleFileHash, pv.uiBundleFileHash);
|
|
119
|
-
// Auto-sign with package author key if available (server/PWA host — key never
|
|
120
|
-
// exposed to UI). Soft-fail when the key is missing so group admins can still
|
|
121
|
-
// promote, but never keep a signature that was computed for the old tag.
|
|
122
|
-
const authorSig = await peers_sdk_1.rpcServerCalls.signPackageAuthorVersion({
|
|
123
|
-
packageId: pv.packageId,
|
|
111
|
+
// The host re-tags, re-signs, records history, and activates the version when this
|
|
112
|
+
// device was running it and the group follows the new tag.
|
|
113
|
+
const result = await (await (0, packages_ipc_1.packagesTools)()).promotePackageVersion({
|
|
124
114
|
packageVersionId: pv.packageVersionId,
|
|
125
|
-
version: pv.version,
|
|
126
|
-
versionTag: newTag,
|
|
127
|
-
packageBundleFileHash: pv.packageBundleFileHash,
|
|
128
|
-
routesBundleFileHash: pv.routesBundleFileHash,
|
|
129
|
-
uiBundleFileHash: pv.uiBundleFileHash,
|
|
130
|
-
appNavs: pv.appNavs,
|
|
131
|
-
createdBy: pv.createdBy,
|
|
132
|
-
createdAt: pv.createdAt,
|
|
133
|
-
});
|
|
134
|
-
const updated = {
|
|
135
|
-
...pv,
|
|
136
115
|
versionTag: newTag,
|
|
137
|
-
packageVersionHash: pvHash,
|
|
138
|
-
history,
|
|
139
|
-
packageAuthorSignature: authorSig,
|
|
140
|
-
};
|
|
141
|
-
await (0, peers_sdk_1.PackageVersions)().signAndSave(updated, { saveAsSnapshot: true });
|
|
142
|
-
// Promoting the version this device is already running should advance the
|
|
143
|
-
// group active version when the new tag matches group follow policy
|
|
144
|
-
// (e.g. promote active → stable with default follow settings).
|
|
145
|
-
const pkgJs = pkg.toJS();
|
|
146
|
-
const activated = await (0, package_helpers_1.maybeActivateAfterPromote)(pkg.packageId, updated, {
|
|
147
|
-
wasEffectiveActive: pv.packageVersionId === effectiveActiveVersionId,
|
|
148
|
-
groupFollowTags: pkgJs.followVersionTags,
|
|
149
|
-
groupPinned: pkgJs.versionFollowRange === "pinned",
|
|
150
116
|
});
|
|
151
|
-
if (
|
|
117
|
+
if (result.warning)
|
|
118
|
+
alert(result.warning);
|
|
119
|
+
if (result.activated)
|
|
152
120
|
await pkg.load();
|
|
153
|
-
}
|
|
154
121
|
refreshKey(refreshKey() + 1);
|
|
155
122
|
}
|
|
156
123
|
catch (err) {
|
|
@@ -190,24 +157,10 @@ const PackageVersionsList = (props) => {
|
|
|
190
157
|
return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("small", { className: "text-muted", children: [sorted.length, " version", sorted.length !== 1 ? "s" : ""] }), (0, jsx_runtime_1.jsx)("div", { className: "list-group mt-2", children: sorted.map((pv) => ((0, jsx_runtime_1.jsx)(VersionRow, { pv: pv, isActive: pv.packageVersionId === effectiveActiveVersionId, isPinned: isPinned, pinning: pinning, activating: activating, deleting: deleting, userMap: userMap, onActivate: activateVersion, onDelete: deleteVersion, onPin: pinVersion, onPromote: promoteVersion }, pv.packageVersionId))) })] }));
|
|
191
158
|
};
|
|
192
159
|
exports.PackageVersionsList = PackageVersionsList;
|
|
193
|
-
/**
|
|
194
|
-
* Valid promotion targets for each version tag. Promotion only moves forward:
|
|
195
|
-
* dev -> beta -> stable. No demotions allowed.
|
|
196
|
-
*/
|
|
197
|
-
function getPromoteTargets(currentTag) {
|
|
198
|
-
switch (currentTag) {
|
|
199
|
-
case "dev":
|
|
200
|
-
return ["beta", "stable"];
|
|
201
|
-
case "beta":
|
|
202
|
-
return ["stable"];
|
|
203
|
-
default:
|
|
204
|
-
return [];
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
160
|
function VersionRow(props) {
|
|
208
161
|
const { pv, isActive, isPinned, pinning, activating, deleting, userMap } = props;
|
|
209
162
|
const creatorName = userMap.get(pv.createdBy);
|
|
210
163
|
const currentTag = pv.versionTag || "dev";
|
|
211
|
-
const promoteTargets =
|
|
164
|
+
const promoteTargets = (0, peers_sdk_1.packageVersionPromotionTargets)(currentTag);
|
|
212
165
|
return ((0, jsx_runtime_1.jsx)("div", { className: `list-group-item ${isActive ? "list-group-item-success" : ""}`, children: (0, jsx_runtime_1.jsxs)("div", { className: "d-flex align-items-center justify-content-between", children: [(0, jsx_runtime_1.jsxs)("div", { className: "d-flex align-items-center flex-wrap", children: [(0, jsx_runtime_1.jsxs)("strong", { className: "me-2", children: ["v", pv.version] }), pv.versionTag && ((0, jsx_runtime_1.jsx)("span", { className: `badge ${tagBadgeClass(pv.versionTag)} me-2`, children: pv.versionTag })), (0, jsx_runtime_1.jsx)("code", { className: "text-muted small me-2", children: pv.packageVersionHash?.substring(0, 8) }), (0, jsx_runtime_1.jsx)("small", { className: "text-muted me-2", children: formatDate(pv.createdAt) }), creatorName && (0, jsx_runtime_1.jsxs)("small", { className: "text-muted", children: ["by ", creatorName] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "d-flex gap-1 align-items-center", children: [promoteTargets.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "dropdown", children: [(0, jsx_runtime_1.jsx)("button", { className: "btn btn-sm btn-outline-info dropdown-toggle", "data-bs-toggle": "dropdown", "data-bs-popper-config": '{"strategy":"fixed"}', title: "Change version tag", children: "Promote" }), (0, jsx_runtime_1.jsx)("ul", { className: "dropdown-menu dropdown-menu-end", children: promoteTargets.map((tag) => ((0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsxs)("button", { className: "dropdown-item", onClick: () => props.onPromote(pv, tag), children: [currentTag, " \u2192 ", tag] }) }, tag))) })] })), isActive ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("button", { className: "btn btn-sm btn-outline-secondary", disabled: isPinned || pinning, onClick: () => props.onPin(), title: isPinned ? "Already pinned" : "Pin to this version (disable auto-updates)", children: [pinning ? ((0, jsx_runtime_1.jsx)("span", { className: "spinner-border spinner-border-sm" })) : ((0, jsx_runtime_1.jsx)("i", { className: "bi bi-pin-fill" })), isPinned ? " Pinned" : " Pin"] }), (0, jsx_runtime_1.jsx)("span", { className: "badge text-bg-info align-self-center", children: "active" })] })) : ((0, jsx_runtime_1.jsxs)("button", { className: "btn btn-sm btn-outline-primary", disabled: activating === pv.packageVersionId, onClick: () => props.onActivate(pv), children: [activating === pv.packageVersionId ? ((0, jsx_runtime_1.jsx)("span", { className: "spinner-border spinner-border-sm me-1" })) : ((0, jsx_runtime_1.jsx)("i", { className: "bi bi-check2-circle me-1" })), "Activate"] })), !isActive && ((0, jsx_runtime_1.jsx)("button", { className: "btn btn-sm btn-outline-danger", disabled: deleting === pv.packageVersionId, onClick: () => props.onDelete(pv), children: deleting === pv.packageVersionId ? ((0, jsx_runtime_1.jsx)("span", { className: "spinner-border spinner-border-sm" })) : ((0, jsx_runtime_1.jsx)("i", { className: "bi bi-trash" })) }))] })] }) }));
|
|
213
166
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type IHostContractConsumer, type IPackageObservedRuntime, type IPackagesConsumer, type PackageRuntimeStates } from "@peers-app/peers-sdk";
|
|
2
|
+
/**
|
|
3
|
+
* Shared System Packages handle for one data context; stable for `useHostContract`.
|
|
4
|
+
*
|
|
5
|
+
* @param dataContextId - Personal context (user id) or group id.
|
|
6
|
+
*/
|
|
7
|
+
export declare function packagesContractFor(dataContextId: string): IHostContractConsumer<IPackagesConsumer>;
|
|
8
|
+
/**
|
|
9
|
+
* Resolve the System Packages consumer for a data context.
|
|
10
|
+
*
|
|
11
|
+
* @param dataContextId - Context to bind; defaults to the active group, the same context
|
|
12
|
+
* `Packages()` reads.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getPackagesConsumer(dataContextId?: string): Promise<IPackagesConsumer>;
|
|
15
|
+
/**
|
|
16
|
+
* System Packages tools bound to a data context.
|
|
17
|
+
*
|
|
18
|
+
* @param dataContextId - Context to bind; defaults to the active group.
|
|
19
|
+
*/
|
|
20
|
+
export declare function packagesTools(dataContextId?: string): Promise<IPackagesConsumer["tools"]>;
|
|
21
|
+
/**
|
|
22
|
+
* Live runtime state of a context's packages on this device.
|
|
23
|
+
*
|
|
24
|
+
* @param dataContextId - Context to watch; defaults to the active group.
|
|
25
|
+
* @returns States keyed by package id, or `undefined` until the host answers.
|
|
26
|
+
*/
|
|
27
|
+
export declare function usePackageRuntimeStates(dataContextId?: string): PackageRuntimeStates | undefined;
|
|
28
|
+
/** Display label and Bootstrap badge color for an observed package status. */
|
|
29
|
+
export interface IPackageStatusBadge {
|
|
30
|
+
/** Short status label. */
|
|
31
|
+
label: string;
|
|
32
|
+
/** Bootstrap text-bg color suffix (`success`, `danger`, ...). */
|
|
33
|
+
color: "success" | "danger" | "warning" | "secondary";
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Describe a package's observed runtime status for a badge.
|
|
37
|
+
*
|
|
38
|
+
* @param runtime - Observed runtime state, or `undefined` before the host reports one.
|
|
39
|
+
* @returns The badge, or `undefined` when there is nothing to show.
|
|
40
|
+
*/
|
|
41
|
+
export declare function packageStatusBadge(runtime: Pick<IPackageObservedRuntime, "status" | "running" | "worker"> | undefined): IPackageStatusBadge | undefined;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.packagesContractFor = packagesContractFor;
|
|
4
|
+
exports.getPackagesConsumer = getPackagesConsumer;
|
|
5
|
+
exports.packagesTools = packagesTools;
|
|
6
|
+
exports.usePackageRuntimeStates = usePackageRuntimeStates;
|
|
7
|
+
exports.packageStatusBadge = packageStatusBadge;
|
|
8
|
+
const peers_sdk_1 = require("@peers-app/peers-sdk");
|
|
9
|
+
const react_1 = require("react");
|
|
10
|
+
const hooks_1 = require("../../hooks");
|
|
11
|
+
const use_host_contract_1 = require("../../hooks/use-host-contract");
|
|
12
|
+
const handles = new Map();
|
|
13
|
+
/**
|
|
14
|
+
* Shared System Packages handle for one data context; stable for `useHostContract`.
|
|
15
|
+
*
|
|
16
|
+
* @param dataContextId - Personal context (user id) or group id.
|
|
17
|
+
*/
|
|
18
|
+
function packagesContractFor(dataContextId) {
|
|
19
|
+
let handle = handles.get(dataContextId);
|
|
20
|
+
if (!handle) {
|
|
21
|
+
handle = (0, peers_sdk_1.hostContractConsumer)((0, peers_sdk_1.getPackagesContractDefinition)(), {
|
|
22
|
+
dataContextId: async () => dataContextId,
|
|
23
|
+
});
|
|
24
|
+
handles.set(dataContextId, handle);
|
|
25
|
+
}
|
|
26
|
+
return handle;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the System Packages consumer for a data context.
|
|
30
|
+
*
|
|
31
|
+
* @param dataContextId - Context to bind; defaults to the active group, the same context
|
|
32
|
+
* `Packages()` reads.
|
|
33
|
+
*/
|
|
34
|
+
async function getPackagesConsumer(dataContextId) {
|
|
35
|
+
const id = dataContextId ?? (await (0, peers_sdk_1.getDefaultDataContext)()).dataContextId;
|
|
36
|
+
return packagesContractFor(id).get();
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* System Packages tools bound to a data context.
|
|
40
|
+
*
|
|
41
|
+
* @param dataContextId - Context to bind; defaults to the active group.
|
|
42
|
+
*/
|
|
43
|
+
async function packagesTools(dataContextId) {
|
|
44
|
+
return (await getPackagesConsumer(dataContextId)).tools;
|
|
45
|
+
}
|
|
46
|
+
/** A handle that never connects, used until the data context id is known. */
|
|
47
|
+
const pendingHandle = {
|
|
48
|
+
get: () => new Promise(() => undefined),
|
|
49
|
+
dispose: () => undefined,
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Live runtime state of a context's packages on this device.
|
|
53
|
+
*
|
|
54
|
+
* @param dataContextId - Context to watch; defaults to the active group.
|
|
55
|
+
* @returns States keyed by package id, or `undefined` until the host answers.
|
|
56
|
+
*/
|
|
57
|
+
function usePackageRuntimeStates(dataContextId) {
|
|
58
|
+
const [handle, setHandle] = (0, react_1.useState)();
|
|
59
|
+
(0, react_1.useEffect)(() => {
|
|
60
|
+
let cancelled = false;
|
|
61
|
+
(dataContextId
|
|
62
|
+
? Promise.resolve(dataContextId)
|
|
63
|
+
: (0, peers_sdk_1.getDefaultDataContext)().then((dc) => dc.dataContextId)).then((id) => {
|
|
64
|
+
if (!cancelled)
|
|
65
|
+
setHandle(packagesContractFor(id));
|
|
66
|
+
}, () => undefined);
|
|
67
|
+
return () => {
|
|
68
|
+
cancelled = true;
|
|
69
|
+
};
|
|
70
|
+
}, [dataContextId]);
|
|
71
|
+
const { consumer } = (0, use_host_contract_1.useHostContract)(handle ?? pendingHandle);
|
|
72
|
+
const [states] = (0, hooks_1.useObservable)(consumer?.observables.runtimeStates);
|
|
73
|
+
return states;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Describe a package's observed runtime status for a badge.
|
|
77
|
+
*
|
|
78
|
+
* @param runtime - Observed runtime state, or `undefined` before the host reports one.
|
|
79
|
+
* @returns The badge, or `undefined` when there is nothing to show.
|
|
80
|
+
*/
|
|
81
|
+
function packageStatusBadge(runtime) {
|
|
82
|
+
if (!runtime)
|
|
83
|
+
return undefined;
|
|
84
|
+
if (runtime.worker?.lastWorkerError)
|
|
85
|
+
return { label: "Worker failed", color: "danger" };
|
|
86
|
+
switch (runtime.status) {
|
|
87
|
+
case "running":
|
|
88
|
+
return { label: "Running", color: "success" };
|
|
89
|
+
case "failed":
|
|
90
|
+
return runtime.running
|
|
91
|
+
? { label: "Update failed", color: "warning" }
|
|
92
|
+
: { label: "Failed", color: "danger" };
|
|
93
|
+
case "unavailable":
|
|
94
|
+
return { label: "Waiting for files", color: "warning" };
|
|
95
|
+
case "unsupported":
|
|
96
|
+
return { label: "Unsupported here", color: "secondary" };
|
|
97
|
+
case "disabled":
|
|
98
|
+
return { label: "Disabled", color: "secondary" };
|
|
99
|
+
default:
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const hostContractConsumer = jest.fn((_definition, options) => ({
|
|
4
|
+
get: async () => ({ tools: { boundTo: await options.dataContextId() } }),
|
|
5
|
+
dispose: jest.fn(),
|
|
6
|
+
}));
|
|
7
|
+
jest.mock("@peers-app/peers-sdk", () => ({
|
|
8
|
+
getDefaultDataContext: async () => ({ dataContextId: "active-group" }),
|
|
9
|
+
getPackagesContractDefinition: () => ({ name: "System Packages" }),
|
|
10
|
+
hostContractConsumer: (definition, options) => hostContractConsumer(definition, options),
|
|
11
|
+
subscribeHostContractTransport: () => () => undefined,
|
|
12
|
+
}));
|
|
13
|
+
const packages_ipc_1 = require("./packages-ipc");
|
|
14
|
+
test("caches one host consumer per data context and defaults to the active group", async () => {
|
|
15
|
+
expect((0, packages_ipc_1.packagesContractFor)("group-a")).toBe((0, packages_ipc_1.packagesContractFor)("group-a"));
|
|
16
|
+
expect((0, packages_ipc_1.packagesContractFor)("group-b")).not.toBe((0, packages_ipc_1.packagesContractFor)("group-a"));
|
|
17
|
+
expect(await (0, packages_ipc_1.packagesTools)("group-b")).toEqual({ boundTo: "group-b" });
|
|
18
|
+
expect(await (0, packages_ipc_1.packagesTools)()).toEqual({ boundTo: "active-group" });
|
|
19
|
+
expect(hostContractConsumer).toHaveBeenCalledTimes(3);
|
|
20
|
+
});
|
|
21
|
+
test.each([
|
|
22
|
+
[undefined, undefined],
|
|
23
|
+
[{ status: "running" }, { label: "Running", color: "success" }],
|
|
24
|
+
[{ status: "failed" }, { label: "Failed", color: "danger" }],
|
|
25
|
+
[
|
|
26
|
+
{ status: "failed", running: { kind: "isolated", version: "1.0.0" } },
|
|
27
|
+
{ label: "Update failed", color: "warning" },
|
|
28
|
+
],
|
|
29
|
+
[
|
|
30
|
+
{
|
|
31
|
+
status: "running",
|
|
32
|
+
worker: { workerRunning: false, lastWorkerError: { message: "x", at: 1 } },
|
|
33
|
+
},
|
|
34
|
+
{ label: "Worker failed", color: "danger" },
|
|
35
|
+
],
|
|
36
|
+
[{ status: "unavailable" }, { label: "Waiting for files", color: "warning" }],
|
|
37
|
+
[{ status: "unsupported" }, { label: "Unsupported here", color: "secondary" }],
|
|
38
|
+
[{ status: "disabled" }, { label: "Disabled", color: "secondary" }],
|
|
39
|
+
[{ status: "inactive" }, undefined],
|
|
40
|
+
[{ status: "notLoaded" }, undefined],
|
|
41
|
+
])("badge for %p", (runtime, expected) => {
|
|
42
|
+
expect((0, packages_ipc_1.packageStatusBadge)(runtime)).toEqual(expected);
|
|
43
|
+
});
|
|
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const list = jest.fn();
|
|
4
4
|
const get = jest.fn();
|
|
5
5
|
const rows = jest.fn();
|
|
6
|
+
const mainContentPath = jest.fn();
|
|
7
|
+
jest.mock("../../globals", () => ({ mainContentPath }));
|
|
6
8
|
let transport = true;
|
|
7
9
|
jest.mock("@peers-app/peers-sdk", () => ({
|
|
8
10
|
getDefaultDataContext: async () => ({ dataContextId: "group" }),
|
|
@@ -32,6 +34,10 @@ test("tool mentions include only workflow-invocable catalog tools", async () =>
|
|
|
32
34
|
name: "historical",
|
|
33
35
|
});
|
|
34
36
|
});
|
|
37
|
+
test("type mentions navigate directly through the UI route observable", () => {
|
|
38
|
+
mention_configs_1.valueTypeMentionConfig.onClick?.({ kind: "valueType", id: "type-id", name: "Type" });
|
|
39
|
+
expect(mainContentPath).toHaveBeenCalledWith("peer-types/type-id");
|
|
40
|
+
});
|
|
35
41
|
test("browser fallback omits quarantined rows and preserves historical detail", async () => {
|
|
36
42
|
transport = false;
|
|
37
43
|
rows.mockResolvedValue([
|