@antelopejs/dms 0.9.0 → 0.9.1
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/CHANGELOG.md +16 -0
- package/data/module-store/catalog.json +71 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.js +8 -0
- package/dist/config.js.map +1 -1
- package/dist/implementations/dms/frontend-modules.d.ts +2 -0
- package/dist/implementations/dms/frontend-modules.js +29 -1
- package/dist/implementations/dms/frontend-modules.js.map +1 -1
- package/dist/implementations/dms/frontend-ready.d.ts +8 -0
- package/dist/implementations/dms/frontend-ready.js +34 -0
- package/dist/implementations/dms/frontend-ready.js.map +1 -0
- package/dist/implementations/dms/manifest.d.ts +1 -0
- package/dist/implementations/dms/manifest.js.map +1 -1
- package/dist/implementations/dms/page.d.ts +2 -0
- package/dist/implementations/dms/page.js +16 -0
- package/dist/implementations/dms/page.js.map +1 -1
- package/dist/routes/index.d.ts +1 -0
- package/dist/routes/index.js +1 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/store.d.ts +21 -0
- package/dist/routes/store.js +130 -0
- package/dist/routes/store.js.map +1 -0
- package/dist/store/activity.d.ts +2 -0
- package/dist/store/activity.js +12 -0
- package/dist/store/activity.js.map +1 -0
- package/dist/store/cache.d.ts +8 -0
- package/dist/store/cache.js +29 -0
- package/dist/store/cache.js.map +1 -0
- package/dist/store/catalog.d.ts +4 -0
- package/dist/store/catalog.js +69 -0
- package/dist/store/catalog.js.map +1 -0
- package/dist/store/compatibility.d.ts +3 -0
- package/dist/store/compatibility.js +45 -0
- package/dist/store/compatibility.js.map +1 -0
- package/dist/store/declarations.d.ts +18 -0
- package/dist/store/declarations.js +116 -0
- package/dist/store/declarations.js.map +1 -0
- package/dist/store/frontend-compatibility.d.ts +18 -0
- package/dist/store/frontend-compatibility.js +93 -0
- package/dist/store/frontend-compatibility.js.map +1 -0
- package/dist/store/interfaces.d.ts +6 -0
- package/dist/store/interfaces.js +126 -0
- package/dist/store/interfaces.js.map +1 -0
- package/dist/store/listing.d.ts +2 -0
- package/dist/store/listing.js +180 -0
- package/dist/store/listing.js.map +1 -0
- package/dist/store/operations.d.ts +8 -0
- package/dist/store/operations.js +254 -0
- package/dist/store/operations.js.map +1 -0
- package/dist/store/registry.d.ts +4 -0
- package/dist/store/registry.js +61 -0
- package/dist/store/registry.js.map +1 -0
- package/dist/store/requirements.d.ts +14 -0
- package/dist/store/requirements.js +81 -0
- package/dist/store/requirements.js.map +1 -0
- package/dist/store/runtime.d.ts +11 -0
- package/dist/store/runtime.js +78 -0
- package/dist/store/runtime.js.map +1 -0
- package/dist/store/tarball.d.ts +2 -0
- package/dist/store/tarball.js +80 -0
- package/dist/store/tarball.js.map +1 -0
- package/dist/store/types.d.ts +98 -0
- package/dist/store/types.js +3 -0
- package/dist/store/types.js.map +1 -0
- package/dist/test/unit/store/store.test.d.ts +1 -0
- package/dist/test/unit/store/store.test.js +127 -0
- package/dist/test/unit/store/store.test.js.map +1 -0
- package/dist/validation/store.schema.d.ts +39 -0
- package/dist/validation/store.schema.js +27 -0
- package/dist/validation/store.schema.js.map +1 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreDependencyPicker.vue +136 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreRequirements.vue +98 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreTile.vue +247 -29
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTile.vue +19 -2
- package/frontend-vue/layers/dms-layout/app/build/utils/module-store-catalog.ts +186 -120
- package/frontend-vue/layers/dms-layout/app/build/utils/module-store-install.ts +146 -0
- package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStore.ts +109 -0
- package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +335 -34
- package/frontend-vue/layers/dms-layout/app/types/module-store.ts +134 -0
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +107 -9
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +107 -9
- package/frontend-vue/package.json +3 -3
- package/frontend-vue/pnpm-lock.yaml +16 -22
- package/frontend-vue/tests/module-store-catalog.test.ts +270 -51
- package/frontend-vue/tests/top-list-card-period-badge-ssr.test.ts +212 -0
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import type { Ref, VNode } from "vue";
|
|
2
3
|
import { MONO_CHIP_CLASS } from "#dms-ui/app/build/utils/monoChip";
|
|
3
4
|
import UButton from "@nuxt/ui/components/Button.vue";
|
|
4
5
|
import UIcon from "@nuxt/ui/runtime/vue/components/Icon.vue";
|
|
@@ -13,6 +14,7 @@ import { useKeyboardPlatform } from "#dms-ui/app/composables/global/keyboardPlat
|
|
|
13
14
|
import DmsChipGroup from "#dms-ui/app/build/components/form/ChipGroup.vue";
|
|
14
15
|
import ModuleCatalogTile from "../build/components/pages/modules/ModuleCatalogTile.vue";
|
|
15
16
|
import ModuleStoreTile from "../build/components/pages/modules/ModuleStoreTile.vue";
|
|
17
|
+
import ModuleStoreDependencyPicker from "../build/components/pages/modules/ModuleStoreDependencyPicker.vue";
|
|
16
18
|
import { usePageHeaderActions } from "../composables/layout/usePageHeaderActions";
|
|
17
19
|
import {
|
|
18
20
|
ENDPOINT_MODULES_LISTING,
|
|
@@ -30,9 +32,30 @@ import {
|
|
|
30
32
|
type ModuleSortMode,
|
|
31
33
|
} from "../build/utils/modules-catalog";
|
|
32
34
|
import {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
type ModuleStoreEntry,
|
|
36
|
+
type StoreScope,
|
|
37
|
+
FRONTEND_BUILD_COMMAND,
|
|
38
|
+
filterByScope,
|
|
39
|
+
isAwaitingFrontend,
|
|
40
|
+
needsDependencyChoice,
|
|
41
|
+
sortStoreModules,
|
|
42
|
+
toModuleStoreEntry,
|
|
35
43
|
} from "../build/utils/module-store-catalog";
|
|
44
|
+
import {
|
|
45
|
+
type DependencyDraft,
|
|
46
|
+
areDependencyDraftsValid,
|
|
47
|
+
buildInstallBody,
|
|
48
|
+
createDependencyDrafts,
|
|
49
|
+
} from "../build/utils/module-store-install";
|
|
50
|
+
import { useModuleStore } from "../composables/page/useModuleStore";
|
|
51
|
+
import {
|
|
52
|
+
ConfirmActionError,
|
|
53
|
+
useConfirm,
|
|
54
|
+
} from "#dms-ui/app/composables/confirm";
|
|
55
|
+
import type {
|
|
56
|
+
StoreActionBody,
|
|
57
|
+
StoreOperationKind,
|
|
58
|
+
} from "../types/module-store";
|
|
36
59
|
import DmsSearchInput from "#dms-ui/app/build/components/form/SearchInput.vue";
|
|
37
60
|
|
|
38
61
|
const SKELETON_PLACEHOLDER_COUNT = 6;
|
|
@@ -197,26 +220,49 @@ function clearFilters(): void {
|
|
|
197
220
|
category.value = MODULE_CATEGORY_ALL;
|
|
198
221
|
}
|
|
199
222
|
|
|
200
|
-
// ── Module store
|
|
201
|
-
|
|
202
|
-
|
|
223
|
+
// ── Module store ───────────────────────────────────────────────────
|
|
224
|
+
const moduleStore = useModuleStore();
|
|
225
|
+
const toast = useToast();
|
|
226
|
+
const { confirm } = useConfirm();
|
|
227
|
+
|
|
228
|
+
onMounted(() => {
|
|
229
|
+
if (isOwner.value) void moduleStore.load();
|
|
230
|
+
});
|
|
231
|
+
onBeforeUnmount(() => moduleStore.stop());
|
|
232
|
+
|
|
233
|
+
const storeListing = computed(() => moduleStore.listing.value);
|
|
234
|
+
const isStoreLoading = computed(
|
|
235
|
+
() => storeListing.value === undefined && !moduleStore.loadingError.value,
|
|
236
|
+
);
|
|
203
237
|
const storeModules = computed(() =>
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
new Set((modulesListing.modules.value ?? []).map((entry) => entry.id)),
|
|
238
|
+
sortStoreModules(
|
|
239
|
+
(storeListing.value?.modules ?? []).map(toModuleStoreEntry),
|
|
207
240
|
processI18n,
|
|
208
241
|
),
|
|
209
242
|
);
|
|
243
|
+
const storeScope = ref<StoreScope>("all");
|
|
210
244
|
const storeQuery = ref("");
|
|
211
245
|
const storeCategory = ref<string>(MODULE_CATEGORY_ALL);
|
|
212
246
|
|
|
247
|
+
const storeScopeItems = computed(() =>
|
|
248
|
+
(["all", "available", "installed"] as const).map((scope) => ({
|
|
249
|
+
value: scope,
|
|
250
|
+
label: t(`modules.store.scope.${scope}`, {
|
|
251
|
+
count: filterByScope(storeModules.value, scope).length,
|
|
252
|
+
}),
|
|
253
|
+
})),
|
|
254
|
+
);
|
|
255
|
+
const scopedStoreModules = computed(() =>
|
|
256
|
+
filterByScope(storeModules.value, storeScope.value),
|
|
257
|
+
);
|
|
258
|
+
|
|
213
259
|
const storeCategoryOptions = computed(() => [
|
|
214
260
|
{
|
|
215
261
|
value: MODULE_CATEGORY_ALL,
|
|
216
262
|
label: t("modules.category.all"),
|
|
217
|
-
count:
|
|
263
|
+
count: scopedStoreModules.value.length,
|
|
218
264
|
},
|
|
219
|
-
...moduleCategoryOptions(
|
|
265
|
+
...moduleCategoryOptions(scopedStoreModules.value, processI18n),
|
|
220
266
|
]);
|
|
221
267
|
|
|
222
268
|
watch(storeCategoryOptions, (options) => {
|
|
@@ -226,7 +272,7 @@ watch(storeCategoryOptions, (options) => {
|
|
|
226
272
|
});
|
|
227
273
|
|
|
228
274
|
const visibleStoreModules = computed(() =>
|
|
229
|
-
filterModules(
|
|
275
|
+
filterModules(scopedStoreModules.value, {
|
|
230
276
|
query: storeQuery.value,
|
|
231
277
|
category: storeCategory.value,
|
|
232
278
|
resolve: processI18n,
|
|
@@ -239,6 +285,153 @@ function clearStoreFilters(): void {
|
|
|
239
285
|
storeCategory.value = MODULE_CATEGORY_ALL;
|
|
240
286
|
}
|
|
241
287
|
|
|
288
|
+
/** Store modules whose interface the running frontend does not match yet. */
|
|
289
|
+
const frontendLagging = computed(() =>
|
|
290
|
+
storeModules.value.filter(
|
|
291
|
+
(entry) =>
|
|
292
|
+
isAwaitingFrontend(entry) ||
|
|
293
|
+
(entry.frontend === "unknown" &&
|
|
294
|
+
moduleStore.awaitingReload.value.has(entry.id)),
|
|
295
|
+
),
|
|
296
|
+
);
|
|
297
|
+
const frontendNotice = computed(() => {
|
|
298
|
+
const lagging = frontendLagging.value;
|
|
299
|
+
if (lagging.length === 0) return null;
|
|
300
|
+
const names = lagging.map((entry) => processI18n(entry.title)).join(", ");
|
|
301
|
+
const hasReported = lagging.every((entry) => entry.frontend !== "unknown");
|
|
302
|
+
const key = !hasReported
|
|
303
|
+
? "unknown"
|
|
304
|
+
: storeListing.value?.dev
|
|
305
|
+
? "dev"
|
|
306
|
+
: "production";
|
|
307
|
+
return { key, names };
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Runs `kind` from a dialog: a refusal is worded as the toast words it and
|
|
312
|
+
* shown in the dialog, which stays open.
|
|
313
|
+
*/
|
|
314
|
+
async function runFromDialog(
|
|
315
|
+
kind: StoreOperationKind,
|
|
316
|
+
entry: ModuleStoreEntry,
|
|
317
|
+
body?: StoreActionBody,
|
|
318
|
+
): Promise<void> {
|
|
319
|
+
try {
|
|
320
|
+
await moduleStore.run(kind, entry, body);
|
|
321
|
+
} catch (error) {
|
|
322
|
+
throw new ConfirmActionError({
|
|
323
|
+
title: t("modules.store.action_failed", {
|
|
324
|
+
module: processI18n(entry.title),
|
|
325
|
+
}),
|
|
326
|
+
description: resolveStoreError(error),
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** What the owner last picked in a module's install dialog, for a retry. */
|
|
332
|
+
const lastDependencyDrafts = new Map<string, DependencyDraft[]>();
|
|
333
|
+
|
|
334
|
+
function renderDependencyPicker(
|
|
335
|
+
entry: ModuleStoreEntry,
|
|
336
|
+
drafts: Ref<DependencyDraft[]>,
|
|
337
|
+
showErrors: boolean,
|
|
338
|
+
): VNode {
|
|
339
|
+
return h(ModuleStoreDependencyPicker, {
|
|
340
|
+
missing: entry.missingInterfaces,
|
|
341
|
+
drafts: drafts.value,
|
|
342
|
+
showErrors,
|
|
343
|
+
onUpdate: (index: number, patch: Partial<DependencyDraft>) => {
|
|
344
|
+
drafts.value = drafts.value.map((draft, position) =>
|
|
345
|
+
position === index ? { ...draft, ...patch } : draft,
|
|
346
|
+
);
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Asks which modules implement the interfaces the release misses, then
|
|
353
|
+
* installs them with it. A refusal shows in the dialog, kept open.
|
|
354
|
+
*/
|
|
355
|
+
async function installWithDependencies(entry: ModuleStoreEntry): Promise<void> {
|
|
356
|
+
const title = processI18n(entry.title);
|
|
357
|
+
const drafts = ref(
|
|
358
|
+
createDependencyDrafts(
|
|
359
|
+
entry.missingInterfaces,
|
|
360
|
+
lastDependencyDrafts.get(entry.id),
|
|
361
|
+
),
|
|
362
|
+
);
|
|
363
|
+
const showErrors = ref(false);
|
|
364
|
+
await confirm({
|
|
365
|
+
title: t("modules.store.install_dialog.title", { module: title }),
|
|
366
|
+
description: t("modules.store.install_dialog.description", {
|
|
367
|
+
module: title,
|
|
368
|
+
}),
|
|
369
|
+
confirmLabel: t("modules.store.actions.install"),
|
|
370
|
+
confirmIcon: "i-ph-download-simple",
|
|
371
|
+
icon: "i-ph-puzzle-piece",
|
|
372
|
+
body: () => renderDependencyPicker(entry, drafts, showErrors.value),
|
|
373
|
+
validate: () => {
|
|
374
|
+
showErrors.value = true;
|
|
375
|
+
return areDependencyDraftsValid(drafts.value);
|
|
376
|
+
},
|
|
377
|
+
onConfirm: async () => {
|
|
378
|
+
lastDependencyDrafts.set(entry.id, drafts.value);
|
|
379
|
+
await runFromDialog(
|
|
380
|
+
"install",
|
|
381
|
+
entry,
|
|
382
|
+
buildInstallBody(entry.id, drafts.value),
|
|
383
|
+
);
|
|
384
|
+
},
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
async function runStoreAction(
|
|
389
|
+
kind: StoreOperationKind,
|
|
390
|
+
entry: ModuleStoreEntry,
|
|
391
|
+
): Promise<void> {
|
|
392
|
+
const title = processI18n(entry.title);
|
|
393
|
+
if (needsDependencyChoice(entry, kind)) {
|
|
394
|
+
await installWithDependencies(entry);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
if (kind === "uninstall") {
|
|
398
|
+
await confirm({
|
|
399
|
+
title: t("modules.store.uninstall_confirm.title", { module: title }),
|
|
400
|
+
description: t("modules.store.uninstall_confirm.description", {
|
|
401
|
+
module: title,
|
|
402
|
+
}),
|
|
403
|
+
confirmLabel: t("modules.store.actions.uninstall"),
|
|
404
|
+
color: "error",
|
|
405
|
+
icon: "i-ph-trash",
|
|
406
|
+
onConfirm: () => runFromDialog(kind, entry),
|
|
407
|
+
});
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
try {
|
|
411
|
+
await moduleStore.run(kind, entry);
|
|
412
|
+
} catch (error) {
|
|
413
|
+
toast.add({
|
|
414
|
+
color: "error",
|
|
415
|
+
icon: "i-ph-warning-circle",
|
|
416
|
+
title: t("modules.store.action_failed", { module: title }),
|
|
417
|
+
description: resolveStoreError(error),
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/** What a refused store request carries: the server's error key, if any. */
|
|
423
|
+
interface StoreRequestError {
|
|
424
|
+
data?: unknown;
|
|
425
|
+
message?: string;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function resolveStoreError(error: unknown): string {
|
|
429
|
+
const details = error as StoreRequestError | undefined;
|
|
430
|
+
const key = typeof details?.data === "string" ? details.data : undefined;
|
|
431
|
+
if (!key) return details?.message ?? "";
|
|
432
|
+
return t(`modules.store.errors.${key.replace("error.", "")}`);
|
|
433
|
+
}
|
|
434
|
+
|
|
242
435
|
// ── Actions ────────────────────────────────────────────────────────
|
|
243
436
|
async function retry(): Promise<void> {
|
|
244
437
|
if (isRetrying.value) return;
|
|
@@ -506,49 +699,141 @@ const summaryCards = computed<StatGroupItem[]>(() =>
|
|
|
506
699
|
</div>
|
|
507
700
|
</section>
|
|
508
701
|
|
|
509
|
-
<!-- Module store:
|
|
702
|
+
<!-- Module store: the official modules, installed or not. -->
|
|
510
703
|
<section class="mt-10" aria-labelledby="module-store-title">
|
|
511
704
|
<DmsSectionHeader
|
|
512
705
|
class="mb-3"
|
|
513
|
-
:count="
|
|
706
|
+
:count="isStoreLoading ? undefined : storeModules.length"
|
|
514
707
|
:description="t('modules.store.description')"
|
|
515
708
|
>
|
|
516
709
|
<template #title>
|
|
517
710
|
<span id="module-store-title">{{ t("modules.store.title") }}</span>
|
|
518
711
|
</template>
|
|
519
|
-
<template #
|
|
520
|
-
<
|
|
521
|
-
|
|
522
|
-
:
|
|
712
|
+
<template #trailing>
|
|
713
|
+
<span
|
|
714
|
+
v-if="storeListing?.frontendVersion"
|
|
715
|
+
:class="[
|
|
716
|
+
MONO_CHIP_CLASS,
|
|
717
|
+
'text-dimmed border border-(--ui-border) text-[11px] max-sm:hidden',
|
|
718
|
+
]"
|
|
719
|
+
:title="
|
|
720
|
+
storeListing.frontendVersionSource
|
|
721
|
+
? t(
|
|
722
|
+
`modules.store.frontend_version.${storeListing.frontendVersionSource}`,
|
|
723
|
+
)
|
|
724
|
+
: undefined
|
|
725
|
+
"
|
|
726
|
+
>
|
|
727
|
+
{{
|
|
728
|
+
t("modules.store.frontend_version.label", {
|
|
729
|
+
version: storeListing.frontendVersion,
|
|
730
|
+
})
|
|
731
|
+
}}
|
|
732
|
+
</span>
|
|
733
|
+
<DmsSegmented
|
|
734
|
+
v-model="storeScope"
|
|
735
|
+
:items="storeScopeItems"
|
|
523
736
|
size="sm"
|
|
524
|
-
|
|
737
|
+
:aria-label="t('modules.store.scope.label')"
|
|
525
738
|
/>
|
|
526
739
|
</template>
|
|
527
740
|
</DmsSectionHeader>
|
|
528
741
|
|
|
529
742
|
<DmsBanner
|
|
743
|
+
v-if="storeListing?.configError"
|
|
744
|
+
class="mb-4"
|
|
745
|
+
tone="error"
|
|
746
|
+
size="sm"
|
|
747
|
+
icon="i-ph-file-x"
|
|
748
|
+
:title="t('modules.store.config_error')"
|
|
749
|
+
:description="storeListing.configError"
|
|
750
|
+
/>
|
|
751
|
+
<DmsBanner
|
|
752
|
+
v-else-if="storeListing && !storeListing.enabled"
|
|
530
753
|
class="mb-4"
|
|
531
754
|
tone="info"
|
|
532
755
|
size="sm"
|
|
533
|
-
icon="i-ph-
|
|
534
|
-
:title="t('modules.store.
|
|
535
|
-
:description="t('modules.store.
|
|
756
|
+
icon="i-ph-lock-simple"
|
|
757
|
+
:title="t('modules.store.disabled.title')"
|
|
758
|
+
:description="t('modules.store.disabled.description')"
|
|
536
759
|
/>
|
|
537
760
|
|
|
538
|
-
<!--
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
:
|
|
761
|
+
<!-- The interface lags behind the backend: say what follows. -->
|
|
762
|
+
<DmsBanner
|
|
763
|
+
v-if="frontendNotice"
|
|
764
|
+
class="mb-4"
|
|
765
|
+
:tone="frontendNotice.key === 'dev' ? 'info' : 'warning'"
|
|
766
|
+
size="sm"
|
|
767
|
+
:icon="
|
|
768
|
+
frontendNotice.key === 'dev'
|
|
769
|
+
? 'i-ph-circle-notch'
|
|
770
|
+
: 'i-ph-arrows-clockwise'
|
|
771
|
+
"
|
|
772
|
+
:title="
|
|
773
|
+
t(`modules.store.frontend.${frontendNotice.key}.title`, {
|
|
774
|
+
modules: frontendNotice.names,
|
|
775
|
+
})
|
|
776
|
+
"
|
|
543
777
|
>
|
|
544
|
-
<
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
778
|
+
<template #description>
|
|
779
|
+
<i18n-t
|
|
780
|
+
:keypath="`modules.store.frontend.${frontendNotice.key}.description`"
|
|
781
|
+
scope="global"
|
|
782
|
+
tag="span"
|
|
783
|
+
>
|
|
784
|
+
<template #command>
|
|
785
|
+
<code
|
|
786
|
+
:class="[
|
|
787
|
+
MONO_CHIP_CLASS,
|
|
788
|
+
'bg-elevated text-highlighted text-xs',
|
|
789
|
+
]"
|
|
790
|
+
>
|
|
791
|
+
{{ FRONTEND_BUILD_COMMAND }}
|
|
792
|
+
</code>
|
|
793
|
+
</template>
|
|
794
|
+
</i18n-t>
|
|
795
|
+
</template>
|
|
796
|
+
</DmsBanner>
|
|
550
797
|
|
|
551
|
-
<
|
|
798
|
+
<div v-if="isStoreLoading" :class="TILE_GRID_CLASS" aria-busy="true">
|
|
799
|
+
<DmsCard
|
|
800
|
+
v-for="placeholder in skeletonItems"
|
|
801
|
+
:key="placeholder"
|
|
802
|
+
:padded="false"
|
|
803
|
+
class="flex min-h-[228px] flex-col gap-3 p-4"
|
|
804
|
+
>
|
|
805
|
+
<USkeleton
|
|
806
|
+
:aria-label="t('dms.a11y.loading')"
|
|
807
|
+
class="size-9 rounded-[10px]"
|
|
808
|
+
/>
|
|
809
|
+
<USkeleton
|
|
810
|
+
:aria-label="t('dms.a11y.loading')"
|
|
811
|
+
class="mt-auto h-2.5 w-3/5"
|
|
812
|
+
/>
|
|
813
|
+
</DmsCard>
|
|
814
|
+
</div>
|
|
815
|
+
|
|
816
|
+
<!-- Only when nothing is on screen yet: a follow-up read that fails
|
|
817
|
+
while the frontend restarts keeps the tiles and tries again. -->
|
|
818
|
+
<DmsBanner
|
|
819
|
+
v-else-if="moduleStore.loadingError.value && !storeListing"
|
|
820
|
+
tone="error"
|
|
821
|
+
size="sm"
|
|
822
|
+
icon="i-ph-plugs"
|
|
823
|
+
:title="t('modules.store.load_error')"
|
|
824
|
+
>
|
|
825
|
+
<template #actions>
|
|
826
|
+
<UButton
|
|
827
|
+
color="error"
|
|
828
|
+
size="sm"
|
|
829
|
+
icon="i-ph-arrows-clockwise"
|
|
830
|
+
:label="t('modules.error.retry')"
|
|
831
|
+
@click="moduleStore.load()"
|
|
832
|
+
/>
|
|
833
|
+
</template>
|
|
834
|
+
</DmsBanner>
|
|
835
|
+
|
|
836
|
+
<template v-else>
|
|
552
837
|
<div :class="TOOLBAR_CLASS">
|
|
553
838
|
<DmsSearchInput
|
|
554
839
|
v-model="storeQuery"
|
|
@@ -564,7 +849,18 @@ const summaryCards = computed<StatGroupItem[]>(() =>
|
|
|
564
849
|
/>
|
|
565
850
|
</div>
|
|
566
851
|
|
|
567
|
-
<DmsCard
|
|
852
|
+
<DmsCard
|
|
853
|
+
v-if="scopedStoreModules.length === 0 && storeScope === 'available'"
|
|
854
|
+
:padded="false"
|
|
855
|
+
>
|
|
856
|
+
<DmsEmptyState
|
|
857
|
+
icon="i-ph-check-circle"
|
|
858
|
+
:title="t('modules.store.all_installed.title')"
|
|
859
|
+
:description="t('modules.store.all_installed.description')"
|
|
860
|
+
/>
|
|
861
|
+
</DmsCard>
|
|
862
|
+
|
|
863
|
+
<DmsCard v-else-if="visibleStoreModules.length === 0" :padded="false">
|
|
568
864
|
<DmsEmptyState
|
|
569
865
|
variant="no-result"
|
|
570
866
|
:title="
|
|
@@ -592,6 +888,11 @@ const summaryCards = computed<StatGroupItem[]>(() =>
|
|
|
592
888
|
v-for="entry in visibleStoreModules"
|
|
593
889
|
:key="entry.id"
|
|
594
890
|
:entry="entry"
|
|
891
|
+
:is-locked="
|
|
892
|
+
!storeListing?.enabled ||
|
|
893
|
+
(storeListing?.busy === true && entry.state !== 'busy')
|
|
894
|
+
"
|
|
895
|
+
@action="runStoreAction($event, entry)"
|
|
595
896
|
/>
|
|
596
897
|
</div>
|
|
597
898
|
</template>
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mirror of the backend's module store listing (`GET /dms/store/listing`,
|
|
3
|
+
* `src/store/types.ts` in `@antelopejs/dms`).
|
|
4
|
+
*/
|
|
5
|
+
export type StoreModuleState =
|
|
6
|
+
| "available"
|
|
7
|
+
| "busy"
|
|
8
|
+
| "active"
|
|
9
|
+
| "disabled"
|
|
10
|
+
| "error"
|
|
11
|
+
| "pending";
|
|
12
|
+
|
|
13
|
+
export type StoreFrontendState = "none" | "synced" | "pending" | "unknown";
|
|
14
|
+
|
|
15
|
+
export type StoreOperationKind =
|
|
16
|
+
| "install"
|
|
17
|
+
| "update"
|
|
18
|
+
| "enable"
|
|
19
|
+
| "disable"
|
|
20
|
+
| "uninstall"
|
|
21
|
+
| "apply";
|
|
22
|
+
|
|
23
|
+
export type StoreOperationStep =
|
|
24
|
+
| "resolve"
|
|
25
|
+
| "dependencies"
|
|
26
|
+
| "unload"
|
|
27
|
+
| "load"
|
|
28
|
+
| "save";
|
|
29
|
+
|
|
30
|
+
export interface StoreOperation {
|
|
31
|
+
moduleId: string;
|
|
32
|
+
kind: StoreOperationKind;
|
|
33
|
+
step: StoreOperationStep;
|
|
34
|
+
targetVersion?: string;
|
|
35
|
+
startedAt: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** A module of the AntelopeJS interfaces registry that implements an interface. */
|
|
39
|
+
export interface InterfaceImplementation {
|
|
40
|
+
/** Name the registry gives it, such as `redis`. */
|
|
41
|
+
name: string;
|
|
42
|
+
/** npm package it is installed from. */
|
|
43
|
+
package: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** An interface the release to install needs and no running module implements. */
|
|
47
|
+
export interface MissingInterface {
|
|
48
|
+
interfacePackage: string;
|
|
49
|
+
/** Registry modules that implement it, npm packages only. */
|
|
50
|
+
candidates: InterfaceImplementation[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A frontend layer that would stop loading on another dms-frontend. */
|
|
54
|
+
export interface FrontendBreak {
|
|
55
|
+
name: string;
|
|
56
|
+
/** The dms-frontend range the layer supports. */
|
|
57
|
+
range: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Why a release cannot load on the running dms-frontend, and what moving it would take. */
|
|
61
|
+
export interface FrontendRequirement {
|
|
62
|
+
/** The dms-frontend range the release supports. */
|
|
63
|
+
range: string;
|
|
64
|
+
/** The dms-frontend version that runs. */
|
|
65
|
+
running: string;
|
|
66
|
+
/** Latest published dms-frontend in the range, when there is one. */
|
|
67
|
+
target?: string;
|
|
68
|
+
/** Command that moves the project to `target`. */
|
|
69
|
+
command?: string;
|
|
70
|
+
/** Layers already loaded that `target` would refuse. */
|
|
71
|
+
breaks: FrontendBreak[];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Where the store read the running dms-frontend version. */
|
|
75
|
+
export type FrontendVersionSource = "reported" | "project";
|
|
76
|
+
|
|
77
|
+
export interface StoreListingEntry {
|
|
78
|
+
id: string;
|
|
79
|
+
package: string;
|
|
80
|
+
icon: string;
|
|
81
|
+
/** Store category key, under `modules.store.category`. */
|
|
82
|
+
category: string;
|
|
83
|
+
/** `$i18n.key` */
|
|
84
|
+
title: string;
|
|
85
|
+
/** `$i18n.key` */
|
|
86
|
+
description: string;
|
|
87
|
+
state: StoreModuleState;
|
|
88
|
+
installedVersion?: string;
|
|
89
|
+
/** The version range antelope.config.ts declares. */
|
|
90
|
+
declaredRange?: string;
|
|
91
|
+
latestVersion?: string;
|
|
92
|
+
updateAvailable: boolean;
|
|
93
|
+
compatible: boolean;
|
|
94
|
+
requiredInterfaceRange?: string;
|
|
95
|
+
error?: string;
|
|
96
|
+
operation?: StoreOperation;
|
|
97
|
+
frontend: StoreFrontendState;
|
|
98
|
+
/** The dms-frontend range the release to install declares, when it does. */
|
|
99
|
+
frontendRange?: string;
|
|
100
|
+
/** Set when that range excludes the running dms-frontend. */
|
|
101
|
+
frontendRequirement?: FrontendRequirement;
|
|
102
|
+
/** Interfaces the release to install needs and nothing implements. */
|
|
103
|
+
missingInterfaces: MissingInterface[];
|
|
104
|
+
/** Running modules that need an interface only this one implements. */
|
|
105
|
+
dependents: string[];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface StoreListing {
|
|
109
|
+
enabled: boolean;
|
|
110
|
+
modules: StoreListingEntry[];
|
|
111
|
+
busy: boolean;
|
|
112
|
+
interfaceVersion: string;
|
|
113
|
+
dev: boolean;
|
|
114
|
+
/** Why antelope.config.ts cannot be read or edited, when it cannot. */
|
|
115
|
+
configError?: string;
|
|
116
|
+
/** The dms-frontend version that runs, when the store can tell. */
|
|
117
|
+
frontendVersion?: string;
|
|
118
|
+
frontendVersionSource?: FrontendVersionSource;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** A module the owner picked to implement a missing interface. */
|
|
122
|
+
export interface StoreDependencyChoice {
|
|
123
|
+
interfacePackage: string;
|
|
124
|
+
/** npm package, never a git or local source. */
|
|
125
|
+
package: string;
|
|
126
|
+
/** Config written to its antelope.config.ts entry. */
|
|
127
|
+
config?: Record<string, unknown>;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Body of `POST /dms/store/<kind>`; only an install carries dependencies. */
|
|
131
|
+
export interface StoreActionBody {
|
|
132
|
+
id: string;
|
|
133
|
+
dependencies?: StoreDependencyChoice[];
|
|
134
|
+
}
|