@antelopejs/dms 0.8.3 → 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 +36 -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-backend-proxies.d.ts +3 -0
- package/dist/implementations/dms/frontend-backend-proxies.js +39 -0
- package/dist/implementations/dms/frontend-backend-proxies.js.map +1 -0
- package/dist/implementations/dms/frontend-bootstrap.d.ts +1 -0
- package/dist/implementations/dms/frontend-bootstrap.js +13 -0
- package/dist/implementations/dms/frontend-bootstrap.js.map +1 -1
- package/dist/implementations/dms/frontend-modules.d.ts +6 -2
- package/dist/implementations/dms/frontend-modules.js +37 -8
- 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 +7 -0
- package/dist/implementations/dms/manifest.js +13 -0
- package/dist/implementations/dms/manifest.js.map +1 -1
- package/dist/implementations/dms/menu-categories.d.ts +16 -0
- package/dist/implementations/dms/menu-categories.js +30 -0
- package/dist/implementations/dms/menu-categories.js.map +1 -0
- package/dist/implementations/dms/nav-badges.d.ts +0 -8
- package/dist/implementations/dms/nav-badges.js +0 -16
- package/dist/implementations/dms/nav-badges.js.map +1 -1
- package/dist/implementations/dms/page.d.ts +13 -5
- package/dist/implementations/dms/page.js +72 -41
- package/dist/implementations/dms/page.js.map +1 -1
- package/dist/implementations/dms-html-render/index.js +5 -2
- package/dist/implementations/dms-html-render/index.js.map +1 -1
- package/dist/implementations/dms-notifications/index.d.ts +4 -3
- package/dist/implementations/dms-notifications/index.js +14 -6
- package/dist/implementations/dms-notifications/index.js.map +1 -1
- package/dist/pages/settings/settings-index.js +3 -2
- package/dist/pages/settings/settings-index.js.map +1 -1
- package/dist/pages/settings/users/invites.js +2 -2
- package/dist/pages/settings/users/invites.js.map +1 -1
- package/dist/pages/settings/users/member-change-notifications.js +17 -6
- package/dist/pages/settings/users/member-change-notifications.js.map +1 -1
- package/dist/pages/settings/users/member-invite-form.d.ts +3 -2
- package/dist/pages/settings/users/member-invite-form.js +7 -4
- package/dist/pages/settings/users/member-invite-form.js.map +1 -1
- package/dist/pages/settings/users/member-roles-field.js +2 -1
- package/dist/pages/settings/users/member-roles-field.js.map +1 -1
- package/dist/pages/settings/users/members.js +4 -1
- package/dist/pages/settings/users/members.js.map +1 -1
- package/dist/pages/settings/users/notifications.d.ts +6 -0
- package/dist/pages/settings/users/notifications.js +23 -15
- package/dist/pages/settings/users/notifications.js.map +1 -1
- package/dist/pages/settings/users/profile.js +1 -1
- package/dist/pages/settings/users/profile.js.map +1 -1
- package/dist/pages/settings/users/region-form.d.ts +1 -1
- package/dist/pages/settings/users/region-form.js +2 -1
- package/dist/pages/settings/users/region-form.js.map +1 -1
- package/dist/pages/settings/users/role-editor-store.d.ts +3 -1
- package/dist/pages/settings/users/role-editor-store.js +17 -4
- package/dist/pages/settings/users/role-editor-store.js.map +1 -1
- package/dist/pages/settings/users/roles.js +1 -0
- package/dist/pages/settings/users/roles.js.map +1 -1
- package/dist/realtime/registry.d.ts +1 -0
- package/dist/realtime/registry.js +5 -0
- package/dist/realtime/registry.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/realtime.d.ts +5 -1
- package/dist/routes/realtime.js +16 -9
- package/dist/routes/realtime.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/integration/account-deletion.test.js +2 -2
- package/dist/test/integration/account-deletion.test.js.map +1 -1
- package/dist/test/integration/auth/auth-key-rotation.test.js +2 -1
- package/dist/test/integration/auth/auth-key-rotation.test.js.map +1 -1
- package/dist/test/integration/auth/current-password.test.js +2 -1
- package/dist/test/integration/auth/current-password.test.js.map +1 -1
- package/dist/test/integration/auth/session-revocation.test.js +2 -1
- package/dist/test/integration/auth/session-revocation.test.js.map +1 -1
- package/dist/test/integration/auth/sign-in-country.test.js +2 -1
- package/dist/test/integration/auth/sign-in-country.test.js.map +1 -1
- package/dist/test/integration/auth/two-factor-codes.test.js +2 -1
- package/dist/test/integration/auth/two-factor-codes.test.js.map +1 -1
- package/dist/test/integration/auth/two-factor-password.test.js +3 -2
- package/dist/test/integration/auth/two-factor-password.test.js.map +1 -1
- package/dist/test/integration/email-change.test.js +2 -1
- package/dist/test/integration/email-change.test.js.map +1 -1
- package/dist/test/integration/files/component-files.test.js +2 -2
- package/dist/test/integration/files/component-files.test.js.map +1 -1
- package/dist/test/integration/member-settings-access.test.js +8 -7
- package/dist/test/integration/member-settings-access.test.js.map +1 -1
- package/dist/test/integration/menu-access-events.test.d.ts +1 -0
- package/dist/test/integration/menu-access-events.test.js +148 -0
- package/dist/test/integration/menu-access-events.test.js.map +1 -0
- package/dist/test/integration/notification-inbox.test.js +4 -1
- package/dist/test/integration/notification-inbox.test.js.map +1 -1
- package/dist/test/integration/notification-locked-subjects.test.js +3 -2
- package/dist/test/integration/notification-locked-subjects.test.js.map +1 -1
- package/dist/test/integration/notification-unread-badge.test.js +51 -5
- package/dist/test/integration/notification-unread-badge.test.js.map +1 -1
- package/dist/test/integration/page-layout-route-guard.test.d.ts +1 -0
- package/dist/test/integration/page-layout-route-guard.test.js +56 -0
- package/dist/test/integration/page-layout-route-guard.test.js.map +1 -0
- package/dist/test/integration/permission-warnings.test.js +3 -2
- package/dist/test/integration/permission-warnings.test.js.map +1 -1
- package/dist/test/integration/removed-member-notifications.test.js +3 -2
- package/dist/test/integration/removed-member-notifications.test.js.map +1 -1
- package/dist/test/integration/role-delete-reassign.test.js +2 -1
- package/dist/test/integration/role-delete-reassign.test.js.map +1 -1
- package/dist/test/integration/role-permission-ancestors.test.js +2 -1
- package/dist/test/integration/role-permission-ancestors.test.js.map +1 -1
- package/dist/test/unit/frontend-bootstrap.test.js +13 -0
- package/dist/test/unit/frontend-bootstrap.test.js.map +1 -1
- package/dist/test/unit/implementations/dms/category-slug-registry.test.js +14 -5
- package/dist/test/unit/implementations/dms/category-slug-registry.test.js.map +1 -1
- package/dist/test/unit/implementations/dms/frontend-backend-proxies.test.d.ts +1 -0
- package/dist/test/unit/implementations/dms/frontend-backend-proxies.test.js +66 -0
- package/dist/test/unit/implementations/dms/frontend-backend-proxies.test.js.map +1 -0
- package/dist/test/unit/implementations/dms/manifest-strip.test.js +26 -0
- package/dist/test/unit/implementations/dms/manifest-strip.test.js.map +1 -1
- package/dist/test/unit/implementations/dms/nav-badges.test.js +23 -9
- package/dist/test/unit/implementations/dms/nav-badges.test.js.map +1 -1
- package/dist/test/unit/implementations/dms-html-render/generate-html.test.js +22 -4
- package/dist/test/unit/implementations/dms-html-render/generate-html.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms/page-layout-query.test.js +12 -0
- package/dist/test/unit/interfaces/dms/page-layout-query.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms/page-teardown.test.js +1 -1
- package/dist/test/unit/interfaces/dms/page-teardown.test.js.map +1 -1
- package/dist/test/unit/pages/settings/member-roles-field.test.js +1 -1
- package/dist/test/unit/pages/settings/member-roles-field.test.js.map +1 -1
- package/dist/test/unit/pages/settings/role-delete-confirm.test.js.map +1 -1
- package/dist/test/unit/realtime/menu-topics.test.d.ts +1 -0
- package/dist/test/unit/realtime/menu-topics.test.js +26 -0
- package/dist/test/unit/realtime/menu-topics.test.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/deferred-plugins.ts +3 -4
- package/frontend-vue/dms.email.ts +5 -1
- package/frontend-vue/dms.frontend.ts +15 -5
- package/frontend-vue/dms.server.ts +74 -0
- package/frontend-vue/layers/dms-auth/app/composables/useLogout.ts +1 -1
- package/frontend-vue/layers/dms-auth/app/composables/usePostLoginRedirect.ts +16 -3
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/2fa.vue +3 -1
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/backup.vue +3 -1
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/forgot-validation.vue +1 -1
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/oauth-complete.vue +26 -3
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/recover.vue +1 -1
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/signup.vue +22 -25
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/validate.vue +1 -1
- package/frontend-vue/layers/dms-auth/app/middleware/auth.ts +5 -7
- package/frontend-vue/layers/dms-auth/app/plugins/permissions-preload.ts +17 -0
- package/frontend-vue/layers/dms-auth/server/accounts.ts +135 -0
- package/frontend-vue/layers/dms-auth/server/actions.ts +251 -0
- package/frontend-vue/layers/dms-auth/server/oauth.ts +260 -0
- package/frontend-vue/layers/dms-auth/server/session.ts +158 -0
- package/frontend-vue/layers/dms-core/app/build/utils/asyncDataScope.ts +47 -0
- package/frontend-vue/layers/dms-core/app/build/utils/permission-preview.ts +6 -2
- package/frontend-vue/layers/dms-core/app/composables/auth/useApiData.ts +148 -0
- package/frontend-vue/layers/dms-core/app/composables/auth/useAuthFetch.ts +7 -20
- package/frontend-vue/layers/dms-core/app/composables/auth/useCurrentUser.ts +2 -2
- package/frontend-vue/layers/dms-core/app/composables/auth/useMultiAccount.ts +18 -5
- package/frontend-vue/layers/dms-core/app/composables/auth/usePermissions.ts +35 -6
- package/frontend-vue/layers/dms-core/app/composables/auth/useSessionRecovery.ts +1 -1
- package/frontend-vue/layers/dms-core/app/composables/period/compareResolvers.ts +20 -14
- package/frontend-vue/layers/dms-core/app/composables/period/periodClock.ts +121 -0
- package/frontend-vue/layers/dms-core/app/composables/period/periodRenderNow.ts +42 -0
- package/frontend-vue/layers/dms-core/app/composables/period/periodTimeZone.ts +31 -0
- package/frontend-vue/layers/dms-core/app/composables/period/presetResolvers.ts +48 -19
- package/frontend-vue/layers/dms-core/app/composables/period/usePeriod.ts +33 -4
- package/frontend-vue/layers/dms-core/app/composables/period/usePeriodScope.ts +78 -25
- package/frontend-vue/layers/dms-core/app/composables/realtime/usePageRealtime.ts +3 -0
- package/frontend-vue/layers/dms-core/app/composables/realtime/useUserRealtime.ts +36 -6
- package/frontend-vue/layers/dms-core/app/composables/user/useUserRegionalPreferences.ts +1 -1
- package/frontend-vue/layers/dms-core/app/plugins/async-data-scope.ts +26 -0
- package/frontend-vue/layers/dms-core/app/utils/pageRoutes.ts +22 -0
- package/frontend-vue/layers/dms-core/app/utils/regional.ts +36 -3
- package/frontend-vue/layers/dms-core/app/utils/trailingDeduper.ts +10 -1
- package/frontend-vue/layers/dms-core/shared/types/app-config.ts +48 -4
- package/frontend-vue/layers/dms-layout/app/app.config.ts +42 -23
- package/frontend-vue/layers/dms-layout/app/assets/css/main.css +72 -20
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardFrame.vue +9 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +0 -3
- package/frontend-vue/layers/dms-layout/app/build/components/notification/NotificationPopover.vue +9 -3
- 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/components/pages/settings/members/MemberRolePicker.vue +13 -12
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxDisplay.vue +17 -7
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationPreferencesForm.vue +0 -1
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfilePersonalInfo.vue +36 -33
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfileSecuritySummary.vue +1 -2
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-types.ts +5 -4
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecuritySessions.vue +35 -28
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityStatus.vue +0 -1
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityTwoFactor.vue +1 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/dev-reload/useDevReloadHolder.ts +7 -6
- package/frontend-vue/layers/dms-layout/app/build/composables/layout/sidePanelState.ts +1 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/navigation/useVisitNavBadges.ts +26 -0
- package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationCatalog.ts +25 -23
- package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationPreferences.ts +44 -23
- package/frontend-vue/layers/dms-layout/app/build/composables/security/useSessionHandoff.ts +1 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/preferencesSummary.ts +1 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useAccountData.ts +3 -2
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useProfileAvatar.ts +1 -18
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useProfilePreferences.ts +19 -23
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/security/useSecurityFormat.ts +6 -3
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/security/useSecurityOverview.ts +13 -18
- 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/build/utils/modules-catalog.ts +8 -6
- package/frontend-vue/layers/dms-layout/app/build/utils/publicErrorMessage.ts +19 -0
- package/frontend-vue/layers/dms-layout/app/components/navigation/CollapsedMenuItem.vue +0 -11
- package/frontend-vue/layers/dms-layout/app/components/navigation/NavigationMenu.vue +3 -13
- package/frontend-vue/layers/dms-layout/app/composables/general/useSystemState.ts +7 -11
- package/frontend-vue/layers/dms-layout/app/composables/notification/useNotifications.ts +103 -22
- package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStore.ts +109 -0
- package/frontend-vue/layers/dms-layout/app/composables/page/useModulesListing.ts +25 -102
- package/frontend-vue/layers/dms-layout/app/composables/page/usePageComponentPreload.ts +0 -10
- package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +177 -96
- package/frontend-vue/layers/dms-layout/app/composables/profile/useUserLanguage.ts +3 -2
- package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +341 -62
- package/frontend-vue/layers/dms-layout/app/emails/components/EmailLayout.vue +31 -11
- package/frontend-vue/layers/dms-layout/app/error.vue +5 -2
- package/frontend-vue/layers/dms-layout/app/middleware/module-routing.global.ts +1 -1
- package/frontend-vue/layers/dms-layout/app/middleware/page-leave-guard.global.ts +2 -2
- package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +12 -9
- package/frontend-vue/layers/dms-layout/app/plugins/dms-menu-sync.client.ts +31 -3
- package/frontend-vue/layers/dms-layout/app/plugins/favicon.ts +58 -0
- package/frontend-vue/layers/dms-layout/app/plugins/notification-stream.client.ts +15 -7
- package/frontend-vue/layers/dms-layout/app/plugins/profile-sync.client.ts +5 -1
- package/frontend-vue/layers/dms-layout/app/plugins/regional-preferences.ts +10 -4
- package/frontend-vue/layers/dms-layout/app/plugins/time-zone-cookie.client.ts +12 -0
- package/frontend-vue/layers/dms-layout/app/types/module-store.ts +134 -0
- package/frontend-vue/layers/dms-layout/app/types/page.ts +13 -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/layers/dms-layout/server/html-render.ts +58 -0
- package/frontend-vue/layers/dms-layout/server/render-token.ts +95 -0
- package/frontend-vue/layers/dms-onboarding/app/composables/onboarding/useOnboarding.ts +25 -21
- package/frontend-vue/layers/dms-onboarding/app/middleware/onboarding.global.ts +1 -1
- package/frontend-vue/layers/dms-onboarding/app/plugins/onboarding-preload.ts +9 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +4 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/files/useFileMetadata.ts +162 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formRecordLoad.ts +23 -4
- package/frontend-vue/layers/dms-ui/app/build/composables/live/usePageDataRefresh.ts +101 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/navigation/useNavBadges.ts +33 -6
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodDisplay.ts +19 -16
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodQuery.ts +40 -20
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableFooter.ts +6 -8
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableListData.ts +42 -14
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +19 -8
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViews.ts +1 -1
- package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +5 -2
- package/frontend-vue/layers/dms-ui/app/components/flow-canvas/FlowCanvas.vue +9 -9
- package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +20 -18
- package/frontend-vue/layers/dms-ui/app/components/form/components/Cascader.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/File.vue +26 -132
- package/frontend-vue/layers/dms-ui/app/components/form/components/Image.vue +40 -105
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +2 -0
- package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +4 -4
- package/frontend-vue/layers/dms-ui/app/components/period/PeriodSelector.vue +9 -13
- package/frontend-vue/layers/dms-ui/app/components/table-view/CascaderPath.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/table-view/FilePreview.vue +12 -24
- package/frontend-vue/layers/dms-ui/app/components/table-view/ImagePreview.vue +17 -29
- package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +7 -3
- package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +20 -8
- package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +8 -11
- package/frontend-vue/layers/dms-ui/app/composables/chart/useChartFetch.ts +146 -182
- package/frontend-vue/layers/dms-ui/app/composables/live/usePageLiveRefresh.ts +8 -2
- package/frontend-vue/layers/dms-ui/app/composables/useFileReadUrls.ts +11 -99
- package/frontend-vue/layers/dms-ui/app/utils/blockRefresh.ts +32 -3
- package/frontend-vue/package.json +3 -3
- package/frontend-vue/pnpm-lock.yaml +16 -22
- package/frontend-vue/server/private-options.ts +27 -0
- package/frontend-vue/tests/async-data-scope.test.ts +121 -0
- package/frontend-vue/tests/auth-middleware-error.test.ts +3 -3
- package/frontend-vue/tests/banner-block-fetch.test.ts +3 -0
- package/frontend-vue/tests/block-fetch-options-forwarding.test.ts +3 -0
- package/frontend-vue/tests/block-items-period-realtime.test.ts +3 -0
- package/frontend-vue/tests/block-not-found.test.ts +16 -2
- package/frontend-vue/tests/block-page-refresh.test.ts +3 -0
- package/frontend-vue/tests/block-route-params.test.ts +3 -0
- package/frontend-vue/tests/branding-defaults.test.ts +34 -0
- package/frontend-vue/tests/builtin-emails.test.ts +23 -2
- package/frontend-vue/tests/chart-live-pause.test.ts +4 -1
- package/frontend-vue/tests/chart-value-propagation.test.ts +4 -1
- package/frontend-vue/tests/composed-text-blocks.test.ts +3 -0
- package/frontend-vue/tests/email-layout.test.ts +66 -4
- package/frontend-vue/tests/error-page-message.test.ts +25 -2
- package/frontend-vue/tests/favicon.test.ts +131 -0
- package/frontend-vue/tests/file-fields-ssr.test.ts +145 -0
- package/frontend-vue/tests/file-read-urls.test.ts +145 -34
- package/frontend-vue/tests/file-unavailable.test.ts +5 -1
- package/frontend-vue/tests/fixtures/dataEngine.ts +44 -0
- package/frontend-vue/tests/form-load-error.test.ts +55 -1
- package/frontend-vue/tests/helpers/api-data-runtime.ts +77 -0
- package/frontend-vue/tests/image-hydration-ids.test.ts +21 -16
- package/frontend-vue/tests/image-reference-preservation.test.ts +59 -53
- package/frontend-vue/tests/input-number-adjust.test.ts +14 -0
- package/frontend-vue/tests/key-value-list-copy.test.ts +3 -0
- package/frontend-vue/tests/member-role-picker.test.ts +4 -2
- package/frontend-vue/tests/menu-sync-reconnect.test.ts +47 -0
- package/frontend-vue/tests/metadata-retry.test.ts +5 -1
- package/frontend-vue/tests/module-routing.test.ts +2 -5
- package/frontend-vue/tests/module-store-catalog.test.ts +270 -51
- package/frontend-vue/tests/modules-catalog.test.ts +17 -4
- package/frontend-vue/tests/modules-listing-transfer.test.ts +95 -0
- package/frontend-vue/tests/multi-account-validate.test.ts +23 -1
- package/frontend-vue/tests/nav-badge-tones.test.ts +66 -3
- package/frontend-vue/tests/notification-counts-from-writes.test.ts +223 -0
- package/frontend-vue/tests/notification-counts-transfer.test.ts +88 -0
- package/frontend-vue/tests/notification-inbox-display.test.ts +57 -5
- package/frontend-vue/tests/notification-popover-errors.test.ts +21 -1
- package/frontend-vue/tests/oauth-complete-handoff.test.ts +209 -0
- package/frontend-vue/tests/onboarding-middleware.test.ts +1 -1
- package/frontend-vue/tests/onboarding-transfer.test.ts +106 -0
- package/frontend-vue/tests/page-data-refresh.test.ts +141 -0
- package/frontend-vue/tests/page-fill-height.test.ts +16 -3
- package/frontend-vue/tests/page-no-visible-blocks.test.ts +12 -2
- package/frontend-vue/tests/page-realtime-remount.test.ts +1 -1
- package/frontend-vue/tests/page-record-label.test.ts +0 -7
- package/frontend-vue/tests/page-validation-redirect.test.ts +12 -2
- package/frontend-vue/tests/period-hydration-now.test.ts +185 -0
- package/frontend-vue/tests/period-live-pause.test.ts +3 -0
- package/frontend-vue/tests/period-reader-zone.test.ts +137 -0
- package/frontend-vue/tests/period-scope-first-fetch.test.ts +17 -1
- package/frontend-vue/tests/period-ssr.test.ts +195 -0
- package/frontend-vue/tests/permission-preview-partial.test.ts +0 -2
- package/frontend-vue/tests/permissions-transfer.test.ts +194 -0
- package/frontend-vue/tests/post-login-redirect.test.ts +10 -5
- package/frontend-vue/tests/profile-personal-info-ssr.test.ts +150 -0
- package/frontend-vue/tests/profile-preferences-summary.test.ts +15 -11
- package/frontend-vue/tests/realtime-reconnect.test.ts +22 -3
- package/frontend-vue/tests/roles-name-errors.test.ts +1 -1
- package/frontend-vue/tests/row-side-panel.test.ts +1 -1
- package/frontend-vue/tests/security-sessions-load-error.test.ts +3 -1
- package/frontend-vue/tests/server-routes-vs-pages.test.ts +26 -0
- package/frontend-vue/tests/server-routes.test.ts +582 -0
- package/frontend-vue/tests/session-handoff.test.ts +1 -1
- package/frontend-vue/tests/settings-reads-ssr.test.ts +232 -0
- package/frontend-vue/tests/side-panels.test.ts +8 -1
- package/frontend-vue/tests/sign-in-exit-history.test.ts +219 -0
- package/frontend-vue/tests/signup-invitation-link.test.ts +56 -8
- package/frontend-vue/tests/site-layout-categories.test.ts +42 -0
- package/frontend-vue/tests/site-layout-hydration.test.ts +3 -3
- package/frontend-vue/tests/site-layout-rendered.test.ts +103 -0
- package/frontend-vue/tests/site-layout-resync.test.ts +87 -0
- package/frontend-vue/tests/ssr-guard-preload.test.ts +188 -0
- package/frontend-vue/tests/table-cell-tones.test.ts +36 -1
- package/frontend-vue/tests/table-data-ssr.test.ts +195 -0
- package/frontend-vue/tests/table-list-data-requests.test.ts +154 -0
- package/frontend-vue/tests/table-retry-loading.test.ts +37 -0
- package/frontend-vue/tests/table-source-navigation.test.ts +0 -1
- package/frontend-vue/tests/table-source-period-scope.test.ts +0 -1
- package/frontend-vue/tests/table-view-views.test.ts +1 -1
- package/frontend-vue/tests/top-list-card-actions.test.ts +3 -0
- package/frontend-vue/tests/top-list-card-period-badge-ssr.test.ts +212 -0
- package/frontend-vue/tests/top-list-card-period-badge.test.ts +79 -0
- package/frontend-vue/tests/top-list-card-value-suffix.test.ts +3 -0
- package/frontend-vue/tests/trailing-deduper.test.ts +22 -0
- package/frontend-vue/tests/use-api-data-update.test.ts +119 -0
- package/frontend-vue/tests/use-api-data.test.ts +410 -0
- package/frontend-vue/tests/user-language.test.ts +8 -5
- package/frontend-vue/tests/widget-label-i18n.test.ts +4 -1
- package/frontend-vue/vitest.config.ts +6 -0
- package/package.json +2 -2
- package/skills/dms/SKILL.md +2 -1
- package/skills/dms-module/SKILL.md +1 -1
- package/skills/dms-pages/SKILL.md +4 -1
- package/frontend-vue/layers/dms-core/app/build/composables/page/usePageRecordLabel.ts +0 -9
- package/frontend-vue/layers/dms-layout/app/composables/page/usePrefetch.ts +0 -21
- package/frontend-vue/layers/dms-ui/app/build/composables/form/useControlError.ts +0 -10
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useServerRenderedAsyncData.ts +0 -23
- package/frontend-vue/tests/control-error-path.test.ts +0 -11
- package/frontend-vue/tests/modules-listing-per-request.test.ts +0 -104
- package/frontend-vue/tests/page-layout-background-refresh.test.ts +0 -321
|
@@ -739,18 +739,85 @@
|
|
|
739
739
|
},
|
|
740
740
|
"store": {
|
|
741
741
|
"title": "Store de modules",
|
|
742
|
-
"description": "Les modules AntelopeJS
|
|
743
|
-
"
|
|
744
|
-
|
|
745
|
-
"
|
|
746
|
-
"
|
|
742
|
+
"description": "Les modules officiels AntelopeJS : installez-les, mettez-les à jour, mettez-les en pause ou retirez-les sans toucher au code du projet.",
|
|
743
|
+
"scope": {
|
|
744
|
+
"label": "Afficher",
|
|
745
|
+
"all": "Tous · {count}",
|
|
746
|
+
"available": "Disponibles · {count}",
|
|
747
|
+
"installed": "Installés · {count}"
|
|
747
748
|
},
|
|
749
|
+
"state": {
|
|
750
|
+
"available": "Disponible",
|
|
751
|
+
"busy": "En cours",
|
|
752
|
+
"active": "Actif",
|
|
753
|
+
"disabled": "En pause",
|
|
754
|
+
"error": "Échec",
|
|
755
|
+
"pending": "Non appliqué",
|
|
756
|
+
"update_available": "Mise à jour"
|
|
757
|
+
},
|
|
758
|
+
"actions": {
|
|
759
|
+
"install": "Installer",
|
|
760
|
+
"update": "Mettre à jour",
|
|
761
|
+
"enable": "Reprendre",
|
|
762
|
+
"disable": "Mettre en pause",
|
|
763
|
+
"uninstall": "Désinstaller",
|
|
764
|
+
"apply": "Appliquer",
|
|
765
|
+
"manage": "Gérer"
|
|
766
|
+
},
|
|
767
|
+
"steps": {
|
|
768
|
+
"resolve": "Trouver la dernière version",
|
|
769
|
+
"unload": "Arrêter la version en cours",
|
|
770
|
+
"load": "Télécharger et démarrer",
|
|
771
|
+
"save": "Écrire antelope.config.ts",
|
|
772
|
+
"dependencies": "Installer les modules requis"
|
|
773
|
+
},
|
|
774
|
+
"progress": {
|
|
775
|
+
"install": "Installation",
|
|
776
|
+
"update": "Mise à jour",
|
|
777
|
+
"enable": "Reprise",
|
|
778
|
+
"disable": "Mise en pause",
|
|
779
|
+
"uninstall": "Désinstallation",
|
|
780
|
+
"apply": "Application"
|
|
781
|
+
},
|
|
782
|
+
"more_actions": "Autres actions",
|
|
783
|
+
"incompatible": "Demande {'@'}antelopejs/interface-dms {range} : mettez d’abord le DMS à jour",
|
|
784
|
+
"no_release": "Le registre npm ne propose aucune version lisible depuis cette plateforme",
|
|
785
|
+
"locked": "Un autre module est en cours de modification",
|
|
786
|
+
"pending_hint": "antelope.config.ts et la plateforme ne concordent pas : Appliquer les remet d’accord.",
|
|
787
|
+
"uninstall_confirm": {
|
|
788
|
+
"title": "Désinstaller {module} ?",
|
|
789
|
+
"description": "{module} s’arrête et quitte le menu. Ses données (collections, fichiers, réglages) restent en place et reviennent si vous le réinstallez."
|
|
790
|
+
},
|
|
791
|
+
"action_failed": "{module} n’a pas pu être modifié",
|
|
792
|
+
"errors": {
|
|
793
|
+
"store_busy": "Un autre module est en cours de modification. Réessayez une fois terminé.",
|
|
794
|
+
"store_module_unknown": "Ce module n’est pas dans le catalogue.",
|
|
795
|
+
"store_disabled": "Le store est en lecture seule sur cette plateforme.",
|
|
796
|
+
"store_invalid_state": "Le module a changé entre-temps. La liste est de nouveau à jour.",
|
|
797
|
+
"unauthorized": "Seuls les propriétaires de la plateforme gèrent les modules.",
|
|
798
|
+
"store_module_required": "D’autres modules en cours d’exécution ont besoin de celui-ci. Retirez-les d’abord."
|
|
799
|
+
},
|
|
800
|
+
"frontend": {
|
|
801
|
+
"dev": {
|
|
802
|
+
"title": "Rechargement de l’interface pour {modules}",
|
|
803
|
+
"description": "Le frontend de dev prend le changement en compte et la page se recharge toute seule."
|
|
804
|
+
},
|
|
805
|
+
"production": {
|
|
806
|
+
"title": "{modules} : reconstruisez le frontend",
|
|
807
|
+
"description": "Le module tourne côté backend. Lancez {command} puis redémarrez le frontend pour afficher ses pages."
|
|
808
|
+
},
|
|
809
|
+
"unknown": {
|
|
810
|
+
"title": "{modules} : redémarrez le frontend",
|
|
811
|
+
"description": "Le module tourne côté backend, mais aucun serveur frontend n’a indiqué ce qu’il sert. Redémarrez-le ({command} en production) pour afficher les pages du module."
|
|
812
|
+
}
|
|
813
|
+
},
|
|
814
|
+
"disabled": {
|
|
815
|
+
"title": "Le store est en lecture seule ici",
|
|
816
|
+
"description": "Les modules s’installent depuis le développement, qui les écrit dans antelope.config.ts ; cette plateforme charge ce que déclare ce fichier. Passez store.enabled à true dans la configuration du DMS pour les modifier d’ici."
|
|
817
|
+
},
|
|
818
|
+
"load_error": "Le store de modules n’a pas pu être chargé",
|
|
748
819
|
"search_placeholder": "Rechercher dans le store",
|
|
749
820
|
"category_label": "Catégorie du store",
|
|
750
|
-
"available_soon": "Bientôt disponible",
|
|
751
|
-
"install": "Installer",
|
|
752
|
-
"install_soon": "Bientôt disponible",
|
|
753
|
-
"command_label": "Commande d’installation",
|
|
754
821
|
"no_result": {
|
|
755
822
|
"title": "Aucun module du store ne correspond à « {query} »",
|
|
756
823
|
"title_filtered": "Aucun module du store ne correspond à ces filtres"
|
|
@@ -813,6 +880,37 @@
|
|
|
813
880
|
"title": "SaaS",
|
|
814
881
|
"description": "Offres, espaces de travail, facturation Stripe et cycle de vie client."
|
|
815
882
|
}
|
|
883
|
+
},
|
|
884
|
+
"config_error": "antelope.config.ts ne peut pas être modifié d’ici",
|
|
885
|
+
"frontend_requirement": {
|
|
886
|
+
"needs": "Demande dms-frontend {range}, ce projet utilise {running}.",
|
|
887
|
+
"run": "Pour passer le projet à dms-frontend {target}, exécutez :",
|
|
888
|
+
"breaks": "dms-frontend {target} refuserait ces couches frontend installées :",
|
|
889
|
+
"supports": "accepte {range}",
|
|
890
|
+
"no_target": "Aucune version publiée de dms-frontend ne correspond à cette plage."
|
|
891
|
+
},
|
|
892
|
+
"requires": "Requiert : {interfaces}",
|
|
893
|
+
"requires_first": "Demande {interfaces}, qu’aucun module en cours d’exécution n’implémente : installez d’abord un module qui le fait.",
|
|
894
|
+
"used_by": "Utilisé par : {modules}. Retirez-les d’abord.",
|
|
895
|
+
"frontend_version": {
|
|
896
|
+
"label": "dms-frontend {version}",
|
|
897
|
+
"reported": "Indiquée par le serveur frontend en cours d’exécution",
|
|
898
|
+
"project": "Lue dans les dépendances du projet : aucun serveur frontend n’a indiqué sa version"
|
|
899
|
+
},
|
|
900
|
+
"install_dialog": {
|
|
901
|
+
"title": "Installer {module}",
|
|
902
|
+
"description": "{module} demande des interfaces qu’aucun module en cours d’exécution n’implémente. Choisissez un module pour chacune : il est installé en premier.",
|
|
903
|
+
"other": "Autre paquet npm",
|
|
904
|
+
"package_label": "Paquet npm",
|
|
905
|
+
"package_placeholder": "{'@'}scope/paquet",
|
|
906
|
+
"config_label": "Configuration (JSON)",
|
|
907
|
+
"config_hint": "Facultatif",
|
|
908
|
+
"note": "Ces modules sont ajoutés à antelope.config.ts comme entrées distinctes, où leur configuration reste modifiable par la suite.",
|
|
909
|
+
"errors": {
|
|
910
|
+
"package_required": "Saisissez un nom de paquet npm.",
|
|
911
|
+
"package_invalid": "Ce n’est pas un nom de paquet npm : les sources git et locales ne sont pas acceptées.",
|
|
912
|
+
"config_invalid": "Doit être un objet JSON."
|
|
913
|
+
}
|
|
816
914
|
}
|
|
817
915
|
},
|
|
818
916
|
"sort": {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { DmsServerEvent } from "#dms/server";
|
|
2
|
+
import { privateSecret } from "../../../server/private-options";
|
|
3
|
+
import { validRenderToken } from "./render-token";
|
|
4
|
+
|
|
5
|
+
const SERVICE_TOKEN_HEADER = "x-dms-service-token";
|
|
6
|
+
|
|
7
|
+
interface RenderInput {
|
|
8
|
+
templateName?: unknown;
|
|
9
|
+
props?: unknown;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function failure(status: number, message: string): Response {
|
|
13
|
+
return Response.json({ message }, { status });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function renderSecret(event: DmsServerEvent): string | undefined {
|
|
17
|
+
return privateSecret(
|
|
18
|
+
event,
|
|
19
|
+
"htmlRender",
|
|
20
|
+
"serviceSecret",
|
|
21
|
+
"DMS_HTML_RENDER_SECRET",
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* `POST /api/html/render`: renders one of the application's email templates
|
|
27
|
+
* for the DMS backend (`GenerateHtml`), which authenticates with a
|
|
28
|
+
* single-use service token rather than a browser session.
|
|
29
|
+
*/
|
|
30
|
+
export async function renderHtml(event: DmsServerEvent): Promise<Response> {
|
|
31
|
+
if (
|
|
32
|
+
!validRenderToken(
|
|
33
|
+
event.request.headers.get(SERVICE_TOKEN_HEADER),
|
|
34
|
+
renderSecret(event),
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
return failure(401, "Invalid service token");
|
|
38
|
+
const input = await event.readJson<RenderInput>();
|
|
39
|
+
if (
|
|
40
|
+
typeof input.templateName !== "string" ||
|
|
41
|
+
input.templateName.trim() === "" ||
|
|
42
|
+
typeof input.props !== "object"
|
|
43
|
+
)
|
|
44
|
+
return failure(400, "Invalid request body");
|
|
45
|
+
try {
|
|
46
|
+
const html = await event.renderEmail(
|
|
47
|
+
input.templateName,
|
|
48
|
+
(input.props ?? {}) as Record<string, unknown>,
|
|
49
|
+
{ locale: event.request.headers.get("x-content-language") ?? undefined },
|
|
50
|
+
);
|
|
51
|
+
return Response.json({ html });
|
|
52
|
+
} catch {
|
|
53
|
+
return failure(
|
|
54
|
+
404,
|
|
55
|
+
`Template "${input.templateName}" not found or render failed`,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
// The service token that guards the HTML render route, minted by the DMS
|
|
4
|
+
// backend (`service-jwt.ts`) for its own render requests: its signature, its
|
|
5
|
+
// claims and its single use.
|
|
6
|
+
|
|
7
|
+
const RENDER_TOKEN_LIFETIME_SECONDS = 5 * 60;
|
|
8
|
+
const RENDER_REPLAY_LIMIT = 10_000;
|
|
9
|
+
|
|
10
|
+
interface RenderTokenClaims {
|
|
11
|
+
namespace?: unknown;
|
|
12
|
+
purpose?: unknown;
|
|
13
|
+
aud?: unknown;
|
|
14
|
+
jti?: unknown;
|
|
15
|
+
iat?: unknown;
|
|
16
|
+
exp?: unknown;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** jti -> exp, for tokens already spent. Pruned on every accepted token. */
|
|
20
|
+
const usedRenderTokens = new Map<string, number>();
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Constant-time check of the token's HMAC. The length comparison guards
|
|
24
|
+
* timingSafeEqual, which throws on mismatched lengths; it is not itself a
|
|
25
|
+
* timing leak, since the length of a base64url HMAC is fixed.
|
|
26
|
+
*/
|
|
27
|
+
function signatureValid(parts: string[], secret: string): boolean {
|
|
28
|
+
const expected = createHmac("sha256", secret)
|
|
29
|
+
.update(`${parts[0]}.${parts[1]}`)
|
|
30
|
+
.digest("base64url");
|
|
31
|
+
if (expected.length !== parts[2].length) return false;
|
|
32
|
+
return timingSafeEqual(Buffer.from(expected), Buffer.from(parts[2]));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The claims this service accepts. `exp - iat` is bounded so a token minted
|
|
37
|
+
* with a far-future expiry cannot outlive the intended window.
|
|
38
|
+
*/
|
|
39
|
+
function claimsValid(
|
|
40
|
+
payload: RenderTokenClaims,
|
|
41
|
+
now: number,
|
|
42
|
+
): payload is RenderTokenClaims & { jti: string; exp: number } {
|
|
43
|
+
return (
|
|
44
|
+
payload.namespace === "html-render-service" &&
|
|
45
|
+
payload.purpose === "html-render" &&
|
|
46
|
+
payload.aud === "dms-frontend" &&
|
|
47
|
+
typeof payload.jti === "string" &&
|
|
48
|
+
payload.jti.length >= 16 &&
|
|
49
|
+
Number.isSafeInteger(payload.iat) &&
|
|
50
|
+
Number.isSafeInteger(payload.exp) &&
|
|
51
|
+
(payload.exp as number) > now &&
|
|
52
|
+
(payload.exp as number) - (payload.iat as number) <=
|
|
53
|
+
RENDER_TOKEN_LIFETIME_SECONDS &&
|
|
54
|
+
(payload.iat as number) <= now
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Records a token as spent, dropping expired entries first and evicting the
|
|
60
|
+
* oldest if the map is at its cap, so a flood of tokens cannot grow it without
|
|
61
|
+
* bound.
|
|
62
|
+
*/
|
|
63
|
+
function rememberToken(jti: string, exp: number, now: number) {
|
|
64
|
+
for (const [spent, expiresAt] of usedRenderTokens)
|
|
65
|
+
if (expiresAt <= now) usedRenderTokens.delete(spent);
|
|
66
|
+
while (usedRenderTokens.size >= RENDER_REPLAY_LIMIT)
|
|
67
|
+
usedRenderTokens.delete(usedRenderTokens.keys().next().value as string);
|
|
68
|
+
usedRenderTokens.set(jti, exp);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Whether the token is well-formed, signed with `secret`, unexpired and not
|
|
73
|
+
* already spent. A token accepted here is immediately marked as spent.
|
|
74
|
+
*/
|
|
75
|
+
export function validRenderToken(
|
|
76
|
+
token: string | null,
|
|
77
|
+
secret: string | undefined,
|
|
78
|
+
): boolean {
|
|
79
|
+
if (!token || !secret) return false;
|
|
80
|
+
const parts = token.split(".");
|
|
81
|
+
if (parts.length !== 3) return false;
|
|
82
|
+
if (!signatureValid(parts, secret)) return false;
|
|
83
|
+
try {
|
|
84
|
+
const payload = JSON.parse(
|
|
85
|
+
Buffer.from(parts[1], "base64url").toString("utf8"),
|
|
86
|
+
) as RenderTokenClaims;
|
|
87
|
+
const now = Math.floor(Date.now() / 1000);
|
|
88
|
+
if (!claimsValid(payload, now)) return false;
|
|
89
|
+
if (usedRenderTokens.has(payload.jti)) return false;
|
|
90
|
+
rememberToken(payload.jti, payload.exp, now);
|
|
91
|
+
return true;
|
|
92
|
+
} catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -6,34 +6,38 @@ interface OnboardingInfoResponse {
|
|
|
6
6
|
has_onboarded: boolean;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Transferred: the global middleware reads it while a server renders the
|
|
11
|
+
* page, and the browser's run of the middleware then finds it instead of
|
|
12
|
+
* asking again. It holds nothing private.
|
|
13
|
+
*/
|
|
14
|
+
function useOnboardingCache() {
|
|
15
|
+
return useDmsState<OnboardingInfo | null>("dms-onboarding-info", () => null, {
|
|
16
|
+
transfer: true,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
9
20
|
export const useOnboarding = async (): Promise<OnboardingInfo> => {
|
|
10
|
-
const cache =
|
|
11
|
-
"dms-onboarding-info",
|
|
12
|
-
() => null,
|
|
13
|
-
);
|
|
21
|
+
const cache = useOnboardingCache();
|
|
14
22
|
|
|
15
|
-
|
|
23
|
+
const known = cache.value;
|
|
24
|
+
if (known) return known;
|
|
16
25
|
|
|
17
26
|
const { $authFetch } = useAuthFetch();
|
|
18
27
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
cache.value
|
|
29
|
-
return info;
|
|
28
|
+
// One request per app however many callers ask before it answers: the
|
|
29
|
+
// preload started with the app and the `onboarding` middleware share it.
|
|
30
|
+
await useTrailingDeduper("onboarding").join(async () => {
|
|
31
|
+
const response = await $authFetch<OnboardingInfoResponse>(
|
|
32
|
+
"/api/onboarding/informations",
|
|
33
|
+
{ method: "GET" },
|
|
34
|
+
);
|
|
35
|
+
cache.value = { hasOnboarded: response.has_onboarded };
|
|
36
|
+
});
|
|
37
|
+
return cache.value as OnboardingInfo;
|
|
30
38
|
};
|
|
31
39
|
|
|
32
40
|
/** Release the cached onboarding guard after the backend creates the admin. */
|
|
33
41
|
export const setOnboardingComplete = () => {
|
|
34
|
-
|
|
35
|
-
"dms-onboarding-info",
|
|
36
|
-
() => null,
|
|
37
|
-
);
|
|
38
|
-
cache.value = { hasOnboarded: true };
|
|
42
|
+
useOnboardingCache().value = { hasOnboarded: true };
|
|
39
43
|
};
|
|
@@ -5,7 +5,7 @@ const LOGIN_PATH = "/auth";
|
|
|
5
5
|
// login page for anyone else rather than a homepage whose 401 would bounce
|
|
6
6
|
// them there anyway.
|
|
7
7
|
function completedOnboardingDestination(): string {
|
|
8
|
-
const { loggedIn } =
|
|
8
|
+
const { loggedIn } = useDmsUserSession();
|
|
9
9
|
return loggedIn.value ? useHomepage() : LOGIN_PATH;
|
|
10
10
|
}
|
|
11
11
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asks for the onboarding status as the app starts rather than when the
|
|
3
|
+
* `onboarding` middleware does, once the plugins and the page have set up:
|
|
4
|
+
* the request runs alongside the others the render makes, and the middleware
|
|
5
|
+
* joins it. A failure stays the middleware's to handle.
|
|
6
|
+
*/
|
|
7
|
+
export default defineDmsPlugin(() => {
|
|
8
|
+
useOnboarding().catch(() => undefined);
|
|
9
|
+
});
|
|
@@ -742,7 +742,9 @@ interface TextOptions {
|
|
|
742
742
|
/**
|
|
743
743
|
* `string`: the value as written, in the tone the row gives (`toneField`) when
|
|
744
744
|
* it gives one; a row without one keeps the bare value, as a string column
|
|
745
|
-
* always drew it.
|
|
745
|
+
* always drew it. An empty one is `null`: a cell rendering `""` as its root
|
|
746
|
+
* leaves no node in the server's HTML, which the browser hydrates as a
|
|
747
|
+
* mismatch, where `null` renders a placeholder both sides agree on.
|
|
746
748
|
*/
|
|
747
749
|
function renderText(
|
|
748
750
|
value: unknown,
|
|
@@ -750,6 +752,7 @@ function renderText(
|
|
|
750
752
|
options?: unknown,
|
|
751
753
|
row?: Row,
|
|
752
754
|
): unknown {
|
|
755
|
+
if (value === "") return null;
|
|
753
756
|
const tone = readRowTone(
|
|
754
757
|
row,
|
|
755
758
|
(options as TextOptions | undefined)?.toneField,
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import {
|
|
2
|
+
effectScope,
|
|
3
|
+
getCurrentInstance,
|
|
4
|
+
inject,
|
|
5
|
+
onScopeDispose,
|
|
6
|
+
shallowReactive,
|
|
7
|
+
watch,
|
|
8
|
+
} from "vue";
|
|
9
|
+
import { FORM_CONTENT_LANGUAGE_KEY } from "../../../composables/form/types/content-language";
|
|
10
|
+
import {
|
|
11
|
+
isDefinitiveMetadataFailure,
|
|
12
|
+
resolveMetadataRetryDelay,
|
|
13
|
+
} from "../../../utils/metadataRetry";
|
|
14
|
+
|
|
15
|
+
/** What `/api/files/metadata` answers for a stored file. */
|
|
16
|
+
export interface FileMetadata {
|
|
17
|
+
resourceKey: string;
|
|
18
|
+
filename: string;
|
|
19
|
+
size: number;
|
|
20
|
+
mimetype: string;
|
|
21
|
+
url: string;
|
|
22
|
+
/** When `url` stops working, in milliseconds since the epoch. */
|
|
23
|
+
expiresAt?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const METADATA_URL = "/api/files/metadata";
|
|
27
|
+
// A read URL is read again this long before it expires.
|
|
28
|
+
const URL_REFRESH_LEAD_MS = 5000;
|
|
29
|
+
|
|
30
|
+
const isExpired = (metadata: FileMetadata): boolean =>
|
|
31
|
+
metadata.expiresAt !== undefined && metadata.expiresAt <= Date.now();
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The metadata of stored files (name, size, type, read URL), one read per
|
|
35
|
+
* file key, shared by every component showing that file.
|
|
36
|
+
*
|
|
37
|
+
* A key resolved during a component's setup (directly, or from an immediate
|
|
38
|
+
* watcher) is read with the page: a server render waits for it and hands it
|
|
39
|
+
* to the browser, which does not ask again. A key resolved later (a new
|
|
40
|
+
* value, an upload) is read in the browser. While a component shows a file,
|
|
41
|
+
* its URL is read again before it expires, and a failed read that may heal
|
|
42
|
+
* (network, 5xx, rate limiting) is retried with a growing delay; a refused or
|
|
43
|
+
* missing file (another 4xx) is not.
|
|
44
|
+
*
|
|
45
|
+
* @param storage The storage the keys live in (the field's `storage`)
|
|
46
|
+
*/
|
|
47
|
+
export function useFileMetadata(storage?: string) {
|
|
48
|
+
const contentLanguage = inject(FORM_CONTENT_LANGUAGE_KEY, undefined);
|
|
49
|
+
// Reads resolved past the setup run with the application's context.
|
|
50
|
+
const app = getCurrentInstance()?.appContext.app;
|
|
51
|
+
const scope = effectScope();
|
|
52
|
+
const reads = shallowReactive(new Map<string, ApiDataResult<FileMetadata>>());
|
|
53
|
+
const timers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
54
|
+
const failures = new Map<string, number>();
|
|
55
|
+
|
|
56
|
+
function schedule(key: string, delay: number, read: () => Promise<void>) {
|
|
57
|
+
clearTimeout(timers.get(key));
|
|
58
|
+
timers.set(
|
|
59
|
+
key,
|
|
60
|
+
setTimeout(() => {
|
|
61
|
+
timers.delete(key);
|
|
62
|
+
void read();
|
|
63
|
+
}, delay),
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The next read of `key` its last answer calls for, in the browser. */
|
|
68
|
+
function follow(key: string, result: ApiDataResult<FileMetadata>): void {
|
|
69
|
+
const refresh = () => result.refresh({ dedupe: "defer" });
|
|
70
|
+
watch(
|
|
71
|
+
[result.data, result.error, result.status],
|
|
72
|
+
([metadata, error, status]) => {
|
|
73
|
+
if (status === "pending" || status === "idle") return;
|
|
74
|
+
clearTimeout(timers.get(key));
|
|
75
|
+
if (error) {
|
|
76
|
+
const attempts = (failures.get(key) ?? 0) + 1;
|
|
77
|
+
failures.set(key, attempts);
|
|
78
|
+
const delay = resolveMetadataRetryDelay(error, attempts);
|
|
79
|
+
if (delay !== null) schedule(key, delay, refresh);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
failures.delete(key);
|
|
83
|
+
if (metadata?.expiresAt === undefined) return;
|
|
84
|
+
const remaining = Math.max(metadata.expiresAt - Date.now(), 0);
|
|
85
|
+
const lead = Math.min(URL_REFRESH_LEAD_MS, remaining / 2);
|
|
86
|
+
schedule(key, remaining - lead, refresh);
|
|
87
|
+
},
|
|
88
|
+
{ immediate: true },
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function readOf(key: string): ApiDataResult<FileMetadata> {
|
|
93
|
+
const known = reads.get(key);
|
|
94
|
+
if (known) return known;
|
|
95
|
+
const read = () =>
|
|
96
|
+
useApiData<FileMetadata>(
|
|
97
|
+
`dms-file-metadata:${storage ?? ""}:${contentLanguage ?? ""}:${key}`,
|
|
98
|
+
({ $authFetch, signal }) =>
|
|
99
|
+
$authFetch<FileMetadata>(METADATA_URL, {
|
|
100
|
+
query: { resourceKey: key, storage },
|
|
101
|
+
headers: contentLanguage
|
|
102
|
+
? { [CONTENT_LANGUAGE_HEADER]: contentLanguage }
|
|
103
|
+
: undefined,
|
|
104
|
+
signal,
|
|
105
|
+
}),
|
|
106
|
+
);
|
|
107
|
+
const result = scope.run(() => {
|
|
108
|
+
const started =
|
|
109
|
+
getCurrentInstance() || !app ? read() : app.runWithContext(read);
|
|
110
|
+
if (!import.meta.env.SSR) follow(key, started);
|
|
111
|
+
return started;
|
|
112
|
+
})!;
|
|
113
|
+
reads.set(key, result);
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Reads `key`'s metadata unless it is known; `force` reads it again (to
|
|
119
|
+
* open a file with a fresh URL).
|
|
120
|
+
*/
|
|
121
|
+
async function resolve(key: string, force = false): Promise<void> {
|
|
122
|
+
const result = readOf(key);
|
|
123
|
+
if (force) await result.refresh({ dedupe: "defer" });
|
|
124
|
+
else await result;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** The last metadata read for `key`, its URL possibly expired. */
|
|
128
|
+
const lastRead = (key: string): FileMetadata | undefined =>
|
|
129
|
+
reads.get(key)?.data.value ?? undefined;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Whether `key` cannot be shown: its last read failed and the server
|
|
133
|
+
* refused it for good, or nothing usable is left (never read, or expired).
|
|
134
|
+
*/
|
|
135
|
+
function isUnavailable(key: string): boolean {
|
|
136
|
+
const result = reads.get(key);
|
|
137
|
+
// A failure stays until the next answer: a retry keeps the file marked.
|
|
138
|
+
if (!result?.error.value) return false;
|
|
139
|
+
const known = result.data.value;
|
|
140
|
+
return (
|
|
141
|
+
!known ||
|
|
142
|
+
isExpired(known) ||
|
|
143
|
+
isDefinitiveMetadataFailure(result.error.value)
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** The metadata to show for `key`: read, not expired, still available. */
|
|
148
|
+
function metadata(key: string): FileMetadata | undefined {
|
|
149
|
+
const known = lastRead(key);
|
|
150
|
+
return known && !isExpired(known) && !isUnavailable(key)
|
|
151
|
+
? known
|
|
152
|
+
: undefined;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
onScopeDispose(() => {
|
|
156
|
+
scope.stop();
|
|
157
|
+
timers.forEach(clearTimeout);
|
|
158
|
+
timers.clear();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
return { resolve, metadata, lastRead, isUnavailable };
|
|
162
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Ref, ref } from "vue";
|
|
1
|
+
import { type Ref, ref, watch } from "vue";
|
|
2
2
|
import { apiErrorStatus } from "#dms-core/app/composables/useFieldErrors";
|
|
3
3
|
import { HTTP_FORBIDDEN } from "#dms-core/app/utils/http-status";
|
|
4
4
|
|
|
@@ -27,9 +27,7 @@ export function missingRecordFailure(
|
|
|
27
27
|
address: FormRecordAddress,
|
|
28
28
|
): FormLoadFailure | null {
|
|
29
29
|
const isMissing =
|
|
30
|
-
!!address.requiresRecord &&
|
|
31
|
-
!!address.fetchUrl &&
|
|
32
|
-
!address.resolvedFetchUrl;
|
|
30
|
+
!!address.requiresRecord && !!address.fetchUrl && !address.resolvedFetchUrl;
|
|
33
31
|
return isMissing ? "missing" : null;
|
|
34
32
|
}
|
|
35
33
|
|
|
@@ -56,15 +54,31 @@ export function formLoadFailure(error: unknown): FormLoadFailure | null {
|
|
|
56
54
|
return apiErrorStatus(error) === HTTP_FORBIDDEN ? "forbidden" : "error";
|
|
57
55
|
}
|
|
58
56
|
|
|
57
|
+
/** How a form takes the answers of its record's later reads. */
|
|
58
|
+
export interface FormRecordLoadOptions {
|
|
59
|
+
/**
|
|
60
|
+
* Whether the user holds edits a later read must not replace: the read is
|
|
61
|
+
* then left for the next opening (a save reads the record again).
|
|
62
|
+
*/
|
|
63
|
+
isEditing?: () => boolean;
|
|
64
|
+
/** Called once a later read was applied: its values are the saved ones. */
|
|
65
|
+
onReloaded?: () => void;
|
|
66
|
+
}
|
|
67
|
+
|
|
59
68
|
/**
|
|
60
69
|
* Follow a form's record load: `apply` receives the record once it loaded,
|
|
61
70
|
* and only then. A failed load is never applied, not even the values a
|
|
62
71
|
* previous opening left in the cache: a form showing them, or its defaults,
|
|
63
72
|
* would read as the record and save over it.
|
|
73
|
+
*
|
|
74
|
+
* The record is read again in the background each time the form mounts on a
|
|
75
|
+
* cached one, and when the page refreshes its data: the form opens on the
|
|
76
|
+
* cached values, then takes the new ones, unless the user started editing.
|
|
64
77
|
*/
|
|
65
78
|
export function trackFormRecordLoad<T>(
|
|
66
79
|
load: FormLoadSource<T>,
|
|
67
80
|
apply: (payload: T) => void,
|
|
81
|
+
options: FormRecordLoadOptions = {},
|
|
68
82
|
): FormRecordLoad {
|
|
69
83
|
const failure = ref<FormLoadFailure | null>(null);
|
|
70
84
|
const retrying = ref(false);
|
|
@@ -74,6 +88,11 @@ export function trackFormRecordLoad<T>(
|
|
|
74
88
|
if (!failure.value && payload) apply(payload);
|
|
75
89
|
};
|
|
76
90
|
settle();
|
|
91
|
+
watch([load.data, load.error], () => {
|
|
92
|
+
if (options.isEditing?.()) return;
|
|
93
|
+
settle();
|
|
94
|
+
if (!failure.value) options.onReloaded?.();
|
|
95
|
+
});
|
|
77
96
|
return {
|
|
78
97
|
failure,
|
|
79
98
|
retrying,
|