@antelopejs/interface-dms 0.10.3 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/README.md +21 -10
- package/dist/attachments.d.ts +15 -2
- package/dist/attachments.js +14 -1
- package/dist/attachments.js.map +1 -1
- package/dist/auth/db/models/sessions.model.d.ts +7 -5
- package/dist/auth/db/models/sessions.model.js +2 -9
- package/dist/auth/db/models/sessions.model.js.map +1 -1
- package/dist/auth/db/models/userExternalIdentities.model.d.ts +7 -5
- package/dist/auth/db/models/users.model.d.ts +7 -5
- package/dist/auth/db/tables/users.table.d.ts +4 -2
- package/dist/auth/db/tables/users.table.js.map +1 -1
- package/dist/auth/index.d.ts +4 -4
- package/dist/auth/internal/jwt-auth-decorator.d.ts +1 -1
- package/dist/auth/internal/jwt-auth-decorator.js +6 -3
- package/dist/auth/internal/jwt-auth-decorator.js.map +1 -1
- package/dist/auth/internal/request-authenticators.d.ts +12 -0
- package/dist/auth/internal/request-authenticators.js +53 -10
- package/dist/auth/internal/request-authenticators.js.map +1 -1
- package/dist/base/activity-feed.d.ts +34 -32
- package/dist/base/activity-feed.js +2 -0
- package/dist/base/activity-feed.js.map +1 -1
- package/dist/base/banner.d.ts +12 -10
- package/dist/base/banner.js +2 -0
- package/dist/base/banner.js.map +1 -1
- package/dist/base/block-registry/internal/describe.js +3 -9
- package/dist/base/block-registry/internal/describe.js.map +1 -1
- package/dist/base/block-registry/internal/ui.js +1 -1
- package/dist/base/block-registry/internal/ui.js.map +1 -1
- package/dist/base/card.d.ts +11 -9
- package/dist/base/card.js +2 -0
- package/dist/base/card.js.map +1 -1
- package/dist/base/chart-card.d.ts +4 -2
- package/dist/base/chart-card.js +2 -0
- package/dist/base/chart-card.js.map +1 -1
- package/dist/base/chart-schemas.d.ts +148 -148
- package/dist/base/chart.d.ts +65 -0
- package/dist/base/chart.js +65 -0
- package/dist/base/chart.js.map +1 -1
- package/dist/base/code-block.d.ts +5 -3
- package/dist/base/code-block.js +2 -0
- package/dist/base/code-block.js.map +1 -1
- package/dist/base/custom.d.ts +5 -0
- package/dist/base/custom.js +5 -0
- package/dist/base/custom.js.map +1 -1
- package/dist/base/data-types/default/address.d.ts +5 -0
- package/dist/base/data-types/default/address.js +5 -0
- package/dist/base/data-types/default/address.js.map +1 -1
- package/dist/base/data-types/default/choices.d.ts +20 -0
- package/dist/base/data-types/default/choices.js +20 -0
- package/dist/base/data-types/default/choices.js.map +1 -1
- package/dist/base/data-types/default/files.d.ts +22 -5
- package/dist/base/data-types/default/files.js +20 -0
- package/dist/base/data-types/default/files.js.map +1 -1
- package/dist/base/data-types/default/numbers.d.ts +15 -0
- package/dist/base/data-types/default/numbers.js +25 -9
- package/dist/base/data-types/default/numbers.js.map +1 -1
- package/dist/base/data-types/default/relations.d.ts +10 -2
- package/dist/base/data-types/default/relations.js +9 -6
- package/dist/base/data-types/default/relations.js.map +1 -1
- package/dist/base/data-types/default/strings.d.ts +25 -0
- package/dist/base/data-types/default/strings.js +25 -0
- package/dist/base/data-types/default/strings.js.map +1 -1
- package/dist/base/data-types/default-types.d.ts +1 -1
- package/dist/base/data-types/field-types.d.ts +10 -0
- package/dist/base/data-types/field-types.js +10 -0
- package/dist/base/data-types/field-types.js.map +1 -1
- package/dist/base/data-types/internal/relation-endpoints.js +1 -0
- package/dist/base/data-types/internal/relation-endpoints.js.map +1 -1
- package/dist/base/data-types/status-type.d.ts +5 -0
- package/dist/base/data-types/status-type.js +5 -0
- package/dist/base/data-types/status-type.js.map +1 -1
- package/dist/base/display.d.ts +6 -6
- package/dist/base/empty-state.d.ts +15 -13
- package/dist/base/empty-state.js +2 -0
- package/dist/base/empty-state.js.map +1 -1
- package/dist/base/form-block-schema.d.ts +50 -50
- package/dist/base/form-schema.d.ts +2 -0
- package/dist/base/form-schema.js +2 -0
- package/dist/base/form-schema.js.map +1 -1
- package/dist/base/grid.d.ts +4 -0
- package/dist/base/grid.js +4 -0
- package/dist/base/grid.js.map +1 -1
- package/dist/base/guard.d.ts +30 -28
- package/dist/base/guard.js +2 -0
- package/dist/base/guard.js.map +1 -1
- package/dist/base/helpers/archive-mode-helpers.d.ts +1 -1
- package/dist/base/helpers/file-refs.js +15 -15
- package/dist/base/helpers/file-refs.js.map +1 -1
- package/dist/base/internal/form-schema-validation.js +7 -1
- package/dist/base/internal/form-schema-validation.js.map +1 -1
- package/dist/base/internal/realtime-topics.js.map +1 -1
- package/dist/base/internal/searchable.d.ts +2 -1
- package/dist/base/internal/searchable.js +1 -3
- package/dist/base/internal/searchable.js.map +1 -1
- package/dist/base/key-value-list.d.ts +21 -19
- package/dist/base/key-value-list.js +2 -0
- package/dist/base/key-value-list.js.map +1 -1
- package/dist/base/kpi-card.d.ts +4 -2
- package/dist/base/kpi-card.js +2 -0
- package/dist/base/kpi-card.js.map +1 -1
- package/dist/base/live-toggle.d.ts +2 -0
- package/dist/base/live-toggle.js +2 -0
- package/dist/base/live-toggle.js.map +1 -1
- package/dist/base/meter.d.ts +23 -21
- package/dist/base/meter.js +2 -0
- package/dist/base/meter.js.map +1 -1
- package/dist/base/nav-card-grid.d.ts +24 -19
- package/dist/base/nav-card-grid.js +5 -0
- package/dist/base/nav-card-grid.js.map +1 -1
- package/dist/base/page-toolbar.d.ts +2 -0
- package/dist/base/page-toolbar.js +2 -0
- package/dist/base/page-toolbar.js.map +1 -1
- package/dist/base/period-selector.d.ts +3 -1
- package/dist/base/period-selector.js +2 -0
- package/dist/base/period-selector.js.map +1 -1
- package/dist/base/placeholder.d.ts +2 -0
- package/dist/base/placeholder.js +2 -0
- package/dist/base/placeholder.js.map +1 -1
- package/dist/base/resource-form-schema.d.ts +2 -2
- package/dist/base/searchable.d.ts +3 -2
- package/dist/base/searchable.js +2 -2
- package/dist/base/searchable.js.map +1 -1
- package/dist/base/section.d.ts +6 -2
- package/dist/base/section.js +4 -0
- package/dist/base/section.js.map +1 -1
- package/dist/base/stack.d.ts +6 -0
- package/dist/base/stack.js +6 -0
- package/dist/base/stack.js.map +1 -1
- package/dist/base/stat-group.d.ts +16 -14
- package/dist/base/stat-group.js +2 -0
- package/dist/base/stat-group.js.map +1 -1
- package/dist/base/tab.d.ts +10 -8
- package/dist/base/tab.js +2 -0
- package/dist/base/tab.js.map +1 -1
- package/dist/base/table-view/column-display.d.ts +1 -1
- package/dist/base/table-view/column-display.js.map +1 -1
- package/dist/base/table-view/data-functions.d.ts +10 -4
- package/dist/base/table-view/data-functions.js.map +1 -1
- package/dist/base/table-view/factory.d.ts +5 -0
- package/dist/base/table-view/factory.js +8 -4
- package/dist/base/table-view/factory.js.map +1 -1
- package/dist/base/table-view/internal/auth.d.ts +17 -3
- package/dist/base/table-view/internal/auth.js +26 -5
- package/dist/base/table-view/internal/auth.js.map +1 -1
- package/dist/base/table-view/internal/column-conditions.d.ts +8 -1
- package/dist/base/table-view/internal/column-conditions.js +25 -13
- package/dist/base/table-view/internal/column-conditions.js.map +1 -1
- package/dist/base/table-view/internal/column-display.d.ts +1 -2
- package/dist/base/table-view/internal/column-display.js +3 -9
- package/dist/base/table-view/internal/column-display.js.map +1 -1
- package/dist/base/table-view/internal/factory-helpers.d.ts +1 -1
- package/dist/base/table-view/internal/factory-helpers.js +17 -14
- package/dist/base/table-view/internal/factory-helpers.js.map +1 -1
- package/dist/base/table-view/internal/factory-lifecycle-created.d.ts +2 -3
- package/dist/base/table-view/internal/factory-lifecycle-created.js +5 -5
- package/dist/base/table-view/internal/factory-lifecycle-created.js.map +1 -1
- package/dist/base/table-view/internal/factory-lifecycle-filter.d.ts +2 -2
- package/dist/base/table-view/internal/factory-private-actions.d.ts +2 -2
- package/dist/base/table-view/internal/files.d.ts +2 -1
- package/dist/base/table-view/internal/files.js +14 -5
- package/dist/base/table-view/internal/files.js.map +1 -1
- package/dist/base/table-view/internal/footer.d.ts +3 -3
- package/dist/base/table-view/internal/footer.js +16 -15
- package/dist/base/table-view/internal/footer.js.map +1 -1
- package/dist/base/table-view/internal/guards.d.ts +27 -5
- package/dist/base/table-view/internal/guards.js +127 -49
- package/dist/base/table-view/internal/guards.js.map +1 -1
- package/dist/base/table-view/internal/meta-helpers.d.ts +1 -1
- package/dist/base/table-view/internal/meta-helpers.js +3 -4
- package/dist/base/table-view/internal/meta-helpers.js.map +1 -1
- package/dist/base/table-view/internal/meta.js +2 -3
- package/dist/base/table-view/internal/meta.js.map +1 -1
- package/dist/base/table-view/internal/option-contract.d.ts +1 -1
- package/dist/base/table-view/internal/option-footer.d.ts +2 -2
- package/dist/base/table-view/internal/option-row-actions.d.ts +1 -1
- package/dist/base/table-view/internal/realtime.d.ts +52 -11
- package/dist/base/table-view/internal/realtime.js +23 -16
- package/dist/base/table-view/internal/realtime.js.map +1 -1
- package/dist/base/table-view/internal/resource-form.d.ts +1 -1
- package/dist/base/table-view/internal/resource-form.js +1 -1
- package/dist/base/table-view/internal/resource-form.js.map +1 -1
- package/dist/base/table-view/internal/row-actions-schema.d.ts +12 -12
- package/dist/base/table-view/internal/row-rule-shape.d.ts +11 -5
- package/dist/base/table-view/internal/row-rule-shape.js +60 -30
- package/dist/base/table-view/internal/row-rule-shape.js.map +1 -1
- package/dist/base/table-view/internal/row-rules.d.ts +28 -1
- package/dist/base/table-view/internal/row-rules.js +8 -19
- package/dist/base/table-view/internal/row-rules.js.map +1 -1
- package/dist/base/table-view/internal/tabs.d.ts +1 -1
- package/dist/base/table-view/internal/tabs.js +1 -4
- package/dist/base/table-view/internal/tabs.js.map +1 -1
- package/dist/base/table-view/internal/validation.d.ts +2 -2
- package/dist/base/table-view/internal/validation.js +7 -7
- package/dist/base/table-view/internal/validation.js.map +1 -1
- package/dist/base/table-view/meta.d.ts +7 -6
- package/dist/base/table-view/meta.js +12 -7
- package/dist/base/table-view/meta.js.map +1 -1
- package/dist/base/table-view/resource-form.d.ts +3 -1
- package/dist/base/table-view/resource-form.js +2 -0
- package/dist/base/table-view/resource-form.js.map +1 -1
- package/dist/base/table-view/routes.d.ts +73 -21
- package/dist/base/table-view/routes.js +13 -10
- package/dist/base/table-view/routes.js.map +1 -1
- package/dist/base/table-view/schema.d.ts +152 -152
- package/dist/base/table-view/source.js +4 -1
- package/dist/base/table-view/source.js.map +1 -1
- package/dist/base/table-view.d.ts +1 -1
- package/dist/base/table-view.js.map +1 -1
- package/dist/base/top-list-card.d.ts +35 -33
- package/dist/base/top-list-card.js +2 -0
- package/dist/base/top-list-card.js.map +1 -1
- package/dist/base/types/error.js +5 -2
- package/dist/base/types/error.js.map +1 -1
- package/dist/base/types/guards.d.ts +4 -4
- package/dist/base/types/json.d.ts +1 -10
- package/dist/base/types/row-action.d.ts +7 -7
- package/dist/component-options/confirm-dialog.d.ts +2 -0
- package/dist/component-options/confirm-dialog.js.map +1 -1
- package/dist/component-options/data-type.d.ts +2 -2
- package/dist/component-options/data-type.js +7 -7
- package/dist/component-options/data-type.js.map +1 -1
- package/dist/component-options/row-action.d.ts +14 -9
- package/dist/component-slots.d.ts +11 -2
- package/dist/component-slots.js +1 -0
- package/dist/component-slots.js.map +1 -1
- package/dist/component.d.ts +7 -8
- package/dist/component.js +17 -25
- package/dist/component.js.map +1 -1
- package/dist/data-controllers/members.d.ts +11 -7
- package/dist/data-controllers/members.js +1 -1
- package/dist/data-controllers/members.js.map +1 -1
- package/dist/data-controllers/roles.d.ts +32 -10
- package/dist/db/internal/exportJobs.model.d.ts +7 -5
- package/dist/db/internal/inviteResolutions.model.d.ts +7 -5
- package/dist/db/internal/tenantLifecycle.model.d.ts +7 -5
- package/dist/db/models/roles.model.d.ts +7 -5
- package/dist/db/models/tenantMembers.model.d.ts +7 -5
- package/dist/db/models/tenants.model.d.ts +7 -5
- package/dist/db/models/user_invites.model.d.ts +7 -5
- package/dist/db/tables/tenants.table.d.ts +9 -0
- package/dist/db/tables/tenants.table.js +4 -0
- package/dist/db/tables/tenants.table.js.map +1 -1
- package/dist/guards.d.ts +1 -1
- package/dist/hooks.d.ts +10 -9
- package/dist/hooks.js +42 -33
- package/dist/hooks.js.map +1 -1
- package/dist/internal/attachments.d.ts +2 -2
- package/dist/internal/component-builder.js +2 -1
- package/dist/internal/component-builder.js.map +1 -1
- package/dist/internal/component-serialization.d.ts +2 -4
- package/dist/internal/component-serialization.js +4 -3
- package/dist/internal/component-serialization.js.map +1 -1
- package/dist/internal/component-slots.d.ts +19 -2
- package/dist/internal/component-slots.js +35 -12
- package/dist/internal/component-slots.js.map +1 -1
- package/dist/internal/component-watch-filter.d.ts +11 -0
- package/dist/internal/component-watch-filter.js +22 -0
- package/dist/internal/component-watch-filter.js.map +1 -0
- package/dist/internal/component-watches.js +0 -1
- package/dist/internal/component-watches.js.map +1 -1
- package/dist/internal/own-entry.d.ts +7 -0
- package/dist/internal/own-entry.js +13 -0
- package/dist/internal/own-entry.js.map +1 -0
- package/dist/invite-extension-payloads.d.ts +14 -2
- package/dist/invite-extensions/internal/delivery.js +1 -2
- package/dist/invite-extensions/internal/delivery.js.map +1 -1
- package/dist/invite-extensions/internal/edit.d.ts +2 -1
- package/dist/invite-extensions/internal/edit.js +8 -9
- package/dist/invite-extensions/internal/edit.js.map +1 -1
- package/dist/invite-extensions/internal/form-slot.js +8 -15
- package/dist/invite-extensions/internal/form-slot.js.map +1 -1
- package/dist/invite-extensions/internal/payload-slices.d.ts +33 -3
- package/dist/invite-extensions/internal/payload-slices.js +84 -19
- package/dist/invite-extensions/internal/payload-slices.js.map +1 -1
- package/dist/invite-extensions/internal/types.d.ts +10 -1
- package/dist/invite-resolution.js +6 -4
- package/dist/invite-resolution.js.map +1 -1
- package/dist/json.d.ts +10 -0
- package/dist/json.js +3 -0
- package/dist/json.js.map +1 -0
- package/dist/layout-banners.d.ts +5 -2
- package/dist/layout-banners.js.map +1 -1
- package/dist/page/controllers.d.ts +2 -2
- package/dist/page/internal/extension-validation.js +3 -2
- package/dist/page/internal/extension-validation.js.map +1 -1
- package/dist/page/internal/metadata-layout.js +1 -1
- package/dist/page/internal/metadata-layout.js.map +1 -1
- package/dist/page/internal/metadata-publish.js +3 -2
- package/dist/page/internal/metadata-publish.js.map +1 -1
- package/dist/page/internal/metadata-registration.js +5 -4
- package/dist/page/internal/metadata-registration.js.map +1 -1
- package/dist/page/internal/metadata-upload-registration.d.ts +3 -1
- package/dist/page/internal/metadata-upload-registration.js +9 -0
- package/dist/page/internal/metadata-upload-registration.js.map +1 -1
- package/dist/page/internal/metadata-uploads.d.ts +18 -5
- package/dist/page/internal/metadata-uploads.js +7 -8
- package/dist/page/internal/metadata-uploads.js.map +1 -1
- package/dist/page/internal/page-record.d.ts +2 -3
- package/dist/page/internal/page-record.js +19 -14
- package/dist/page/internal/page-record.js.map +1 -1
- package/dist/page/layout-filter.js +17 -6
- package/dist/page/layout-filter.js.map +1 -1
- package/dist/page/metadata.js +5 -7
- package/dist/page/metadata.js.map +1 -1
- package/dist/request-tenant.d.ts +10 -4
- package/dist/request-tenant.js +16 -34
- package/dist/request-tenant.js.map +1 -1
- package/dist/tenant-export.d.ts +2 -1
- package/dist/tenant-lifecycle.d.ts +13 -4
- package/dist/tenant-lifecycle.js +51 -18
- package/dist/tenant-lifecycle.js.map +1 -1
- package/dist/types.d.ts +20 -0
- package/dist/uploads.js +42 -59
- package/dist/uploads.js.map +1 -1
- package/dist/utils/fire-and-forget.js.map +1 -1
- package/dist/utils/internal/decorated-class.d.ts +14 -0
- package/dist/utils/internal/decorated-class.js +37 -0
- package/dist/utils/internal/decorated-class.js.map +1 -0
- package/dist/utils/internal/type-check.d.ts +9 -0
- package/dist/utils/internal/type-check.js +13 -5
- package/dist/utils/internal/type-check.js.map +1 -1
- package/dist/utils/internal/value-parser.d.ts +1 -1
- package/dist/utils/internal/value-parser.js +22 -9
- package/dist/utils/internal/value-parser.js.map +1 -1
- package/dist/watch-actions.d.ts +1 -1
- package/docs/1.core/1.introduction.md +82 -77
- package/docs/1.core/10.layout-banners.md +57 -45
- package/docs/1.core/11.modules-and-dynamic-menus.md +178 -0
- package/docs/1.core/12.frontend-modules-and-request-helpers.md +155 -0
- package/docs/1.core/13.tenant-data-export.md +120 -0
- package/docs/1.core/2.pages-and-modules.md +140 -367
- package/docs/1.core/3.components.md +21 -22
- package/docs/1.core/4.permissions.md +92 -71
- package/docs/1.core/5.tenancy.md +112 -86
- package/docs/1.core/6.invites.md +90 -65
- package/docs/1.core/7.hooks.md +96 -166
- package/docs/1.core/8.realtime.md +44 -45
- package/docs/1.core/9.quick-actions.md +37 -30
- package/docs/2.auth/1.introduction.md +33 -35
- package/docs/2.auth/2.authentication.md +60 -38
- package/docs/2.auth/3.tokens-and-sessions.md +56 -55
- package/docs/2.auth/4.users.md +82 -52
- package/docs/2.auth/5.external-identities.md +25 -26
- package/docs/3.base/1.introduction.md +64 -60
- package/docs/3.base/10.display-blocks.md +15 -16
- package/docs/3.base/11.page-layouts.md +273 -0
- package/docs/3.base/12.table-row-actions.md +122 -0
- package/docs/3.base/13.table-filters-and-forms.md +104 -0
- package/docs/3.base/14.table-events-and-helpers.md +114 -0
- package/docs/3.base/15.table-sources-and-gates.md +24 -0
- package/docs/3.base/2.types.md +44 -41
- package/docs/3.base/3.forms.md +27 -28
- package/docs/3.base/4.tables.md +34 -367
- package/docs/3.base/5.charts-and-widgets.md +28 -29
- package/docs/3.base/6.layout.md +21 -270
- package/docs/3.base/8.data-types.md +39 -40
- package/docs/3.base/9.helpers.md +36 -38
- package/docs/4.notifications/1.introduction.md +22 -26
- package/docs/4.notifications/2.categories-and-subjects.md +58 -55
- package/docs/4.notifications/3.sending.md +72 -68
- package/docs/5.html-render/1.introduction.md +119 -81
- package/docs/5.html-render/2.built-in-emails.md +71 -51
- package/docs/index.md +18 -29
- package/docs/tsconfig.json +4 -0
- package/package.json +12 -12
- package/docs/images/emails-admin-invite.png +0 -0
- package/docs/images/emails-change-notice.png +0 -0
- package/docs/images/emails-change-verification.png +0 -0
- package/docs/images/emails-digest-logo-dark.png +0 -0
- package/docs/images/emails-digest-logo-light.png +0 -0
- package/docs/images/emails-digest.png +0 -0
- package/docs/images/emails-export-partial.png +0 -0
- package/docs/images/emails-export-ready.png +0 -0
- package/docs/images/emails-reset-password.png +0 -0
- package/docs/images/emails-two-factor.png +0 -0
- package/docs/images/emails-user-validation.png +0 -0
|
@@ -1,25 +1,20 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "Pages and
|
|
3
|
-
description: "Pages are controller classes registered into a navigation tree of
|
|
2
|
+
title: "Pages and modules"
|
|
3
|
+
description: "Pages are controller classes registered into a navigation tree of categories, with their own routes, layouts, access rules and extensions."
|
|
4
|
+
mode: reference
|
|
4
5
|
---
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
A page is a controller class. `PageController(id, options, layout?)` builds a base class bound to a URL, `@RegisterPage()` records it, and the components declared as static fields on the class become the page's serialized layout. Categories group pages into the sidebar tree. Modules, which own a branch of that tree, and dynamic menu entries are described in [Modules and dynamic menus](/docs/interfaces/core/modules-and-dynamic-menus).
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
To build a first screen, follow [Pages & components](/docs/building/pages-and-components). That guide also shows [page extension placement](/docs/building/pages-and-components#extending-another-modules-page) in the running playground. Everything on this page lives in `@antelopejs/interface-dms/page`.
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
Registration happens at import time. The `@RegisterPage()` decorator runs when the file loads, so the order in which you import page files sets the order in which pages, categories and their permissions register.
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
## Declare a page
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
`PageController(id, options, layout?)` returns a class that extends `Controller(pageRoutePath(fullSlug))`, so its routes live under `PAGE_ROUTE_PREFIX` (`/dms/pages`). Extend it, decorate it with `@RegisterPage()`, and declare components as static fields.
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
## Declare a Page
|
|
19
|
-
|
|
20
|
-
`PageController(id, options, layout?)` returns a class extending `Controller(fullSlug)`. Extend it, decorate with `@RegisterPage()`, and declare components as static fields.
|
|
21
|
-
|
|
22
|
-
```typescript
|
|
17
|
+
```typescript fragment
|
|
23
18
|
import {
|
|
24
19
|
Category,
|
|
25
20
|
PageController,
|
|
@@ -46,44 +41,58 @@ export class InvoicesPage extends PageController("invoices", {
|
|
|
46
41
|
}
|
|
47
42
|
```
|
|
48
43
|
|
|
49
|
-
`PageController`
|
|
44
|
+
`PageController` throws when its options declare neither `category` nor `module`. The returned class extends `Controller`, so route decorators from `@antelopejs/interface-api` work on it directly, and a route declared on the page class is served under the page's slug. [Identifiers a page carries](#identifiers-a-page-carries) gives the exact path.
|
|
50
45
|
|
|
51
|
-
The third argument is the page layout, a `ComponentInfo`
|
|
46
|
+
The third argument is the page layout, a `ComponentInfo` from a layout builder such as `DefaultLayout()` from `@antelopejs/interface-dms/base`. Without it, the page uses its own `layout` option, then the `layout` of the nearest category or parent page that declares one, then `DefaultLayout()`.
|
|
47
|
+
|
|
48
|
+
`@RegisterPage()` reads the class's own static properties and records every `Component` under its property name. It skips a property whose name starts with `_`, so a class can keep a helper value without turning it into a layout entry.
|
|
52
49
|
|
|
53
50
|
### `MenuOptions`
|
|
54
51
|
|
|
55
52
|
| Option | Type | Description |
|
|
56
53
|
| ------ | ---- | ----------- |
|
|
57
54
|
| `displayName` | `string` | Label shown in the sidebar. Required. |
|
|
58
|
-
| `category` | `CategoryInfo \| ControllerClass` | Parent category
|
|
59
|
-
| `module` | `string` | Id of a module registered with `RegisterModule`. Attaches the
|
|
55
|
+
| `category` | `CategoryInfo \| ControllerClass` | Parent category: a `CategoryInfo` from `Category`, `RootCategory` or `RegisterModule`, or a controller class from `PageController` or `RootPageController`. |
|
|
56
|
+
| `module` | `string` | Id of a module registered with `RegisterModule`. Attaches the entry to that module's tree. |
|
|
60
57
|
| `urlSlug` | `string` | URL segment for this entry. Defaults to `id`. |
|
|
61
58
|
| `description` | `string` | Longer description carried in the page metadata. |
|
|
62
59
|
| `order` | `number` | Sort order among siblings. |
|
|
63
60
|
| `icon` | `string` | Icon name. |
|
|
64
|
-
| `hidden` | `boolean` |
|
|
65
|
-
| `parent` | `ControllerClass` | The page this one is a detail of
|
|
66
|
-
| `permission` | `Partial<Permission> \| Action` |
|
|
67
|
-
| `publicAccess` | `boolean` |
|
|
68
|
-
| `authOnly` | `boolean` |
|
|
69
|
-
| `memberAccess` | `boolean \| "self"` |
|
|
61
|
+
| `hidden` | `boolean` | Keeps the entry out of the menu. A page or category under a hidden category is hidden too. |
|
|
62
|
+
| `parent` | `ControllerClass` | The page this one is a detail of. The breadcrumb shows it before this page ("Operations / Projects / <record>"), with its route parameters filled from this page's parameters of the same name. Resolved at registration and served as `parentSlug`. Read on pages only. |
|
|
63
|
+
| `permission` | `Partial<Permission> \| Action` | Overrides the derived page permission, or binds the page to an existing component `Action`. See [Permissions](/docs/interfaces/core/permissions). |
|
|
64
|
+
| `publicAccess` | `boolean` | Serves the page without authentication. A page inherits it from its category. |
|
|
65
|
+
| `authOnly` | `boolean` | Requires a signed-in user and no permission. A page inherits it from its category. |
|
|
66
|
+
| `memberAccess` | `boolean \| "self"` | Opens the entry, its components and their actions to every signed-in member without a role grant. Their permissions register as `defaultGranted`, so guards still check them, the tenant access gate still applies, and the roles editor does not list them. `true` passes to what is declared under the entry. `"self"` opens the entry alone. `settingsCategory` declares `"self"`, so every member opens the settings overview while the workspace pages under it stay gated. `MemberAccess` names this type. |
|
|
70
67
|
| `type` | `MenuItemType` | `"label"` or `"link"`. |
|
|
71
68
|
| `validation` | `PageValidation` | Preconditions for opening the page. |
|
|
72
69
|
| `setupId` | `string` | Id of a page setup function to run while the page is mounted. |
|
|
73
|
-
| `noComponentPermissions` | `boolean` |
|
|
70
|
+
| `noComponentPermissions` | `boolean` | Registers no permission for the page's components. |
|
|
74
71
|
| `query` | `MenuItemQuery` | Query parameters carried by the entry's menu link. |
|
|
75
72
|
| `variant` | `MenuItemVariant` | Semantic look of the entry. |
|
|
76
|
-
| `status` | `MenuItemStatus` | Semantic state dot
|
|
77
|
-
| `badge` | `string` | Short count or label at the end of the entry (`3`, `new`)
|
|
78
|
-
| `layout` | `ComponentInfo` | Default layout of the entry and of every page and
|
|
79
|
-
| `bypassTenantAccessGate` | `boolean` |
|
|
80
|
-
| `live` | `PageLiveOptions` | Gives the page a Live
|
|
73
|
+
| `status` | `MenuItemStatus` | Semantic state dot at the end of the entry. |
|
|
74
|
+
| `badge` | `string` | Short count or label at the end of the entry (`3`, `new`). It takes the place of `status` and of a counted navigation badge. |
|
|
75
|
+
| `layout` | `ComponentInfo` | Default layout of the entry and of every page and category under it that sets none. The nearest declaring ancestor wins, and a page's layout argument always overrides it. |
|
|
76
|
+
| `bypassTenantAccessGate` | `boolean` | Keeps the page reachable while a tenant access gate denies the tenant. The pages of a flagged category inherit it. |
|
|
77
|
+
| `live` | `PageLiveOptions` | Gives the page a Live and Pause state over the realtime of its blocks. Read on pages only. |
|
|
81
78
|
|
|
82
79
|
`MenuItemType` is `"label" | "link"`.
|
|
83
80
|
|
|
84
|
-
`PageValidation` has three optional fields:
|
|
81
|
+
`PageValidation` has three optional fields:
|
|
82
|
+
|
|
83
|
+
- `requiredQueryParams`, a `string[]` of query parameters the page needs, each with a non-empty value.
|
|
84
|
+
- `customFunctionId`, the id of a validation function the frontend registers.
|
|
85
|
+
- `redirectTo`, the dashboard path a page that fails its validation sends the user to instead of answering "Page not found". The path may use the `{{params.X}}` and `{{query.X}}` tokens of the page URL. [Page setup and validation](/docs/building/pages-and-components#page-setup-and-validation) shows it in use.
|
|
86
|
+
|
|
87
|
+
`PageLiveOptions` has three optional fields. [Live pages](/docs/building/live-pages) explains the behaviour they control.
|
|
88
|
+
|
|
89
|
+
- `maxRate`, the shortest interval in seconds between two refetches of one block, and the polling interval while the realtime stream is down.
|
|
90
|
+
- `slowRateAfter`, a `PageLiveSlowRate` of `{ period: PageLivePeriod; maxRate: number }`. Once the period the page shows spans `period`, its `maxRate` replaces the page's. `PageLivePeriod` is a whole number followed by `m`, `h` or `d`, such as `"7d"`.
|
|
91
|
+
- `rememberKey`, a key that shares the remembered Live or Pause choice between the pages that declare it.
|
|
85
92
|
|
|
86
|
-
|
|
93
|
+
A rate that is not a positive number, a period that does not read like `30m`, `24h` or `7d`, or an empty `rememberKey` throws at declaration.
|
|
94
|
+
|
|
95
|
+
### Menu entry appearance
|
|
87
96
|
|
|
88
97
|
Three types describe how an entry looks, so a consumer never ships raw classes:
|
|
89
98
|
|
|
@@ -93,11 +102,13 @@ type MenuItemStatus = "success" | "warning" | "error" | "neutral";
|
|
|
93
102
|
type MenuItemQuery = Record<string, string>;
|
|
94
103
|
```
|
|
95
104
|
|
|
96
|
-
`accent` renders the entry in the theme's primary color, for a call
|
|
105
|
+
`accent` renders the entry in the theme's primary color, for a call to action such as "New project". `status` maps to the theme's semantic colors.
|
|
97
106
|
|
|
98
|
-
`query` appends parameters to the entry's link,
|
|
107
|
+
`query` appends parameters to the entry's link, so one page can serve many entries. Declare the parameter in `validation.requiredQueryParams`, then point several entries at the page:
|
|
99
108
|
|
|
100
109
|
```typescript
|
|
110
|
+
import { PageController, RegisterPage, pagesCategory } from "@antelopejs/interface-dms/page";
|
|
111
|
+
|
|
101
112
|
@RegisterPage()
|
|
102
113
|
export class ProjectPage extends PageController("project", {
|
|
103
114
|
displayName: "Project",
|
|
@@ -107,15 +118,15 @@ export class ProjectPage extends PageController("project", {
|
|
|
107
118
|
}) {}
|
|
108
119
|
```
|
|
109
120
|
|
|
110
|
-
###
|
|
121
|
+
### Reach a page behind a tenant gate
|
|
111
122
|
|
|
112
|
-
`bypassTenantAccessGate` is the page-level mirror of the guard option of the same name ([Permissions](/docs/interfaces/core/permissions)). It covers page visibility, menu
|
|
123
|
+
`bypassTenantAccessGate` is the page-level mirror of the guard option of the same name ([Permissions](/docs/interfaces/core/permissions#authtenantowner-and-authtenantmember)). It covers the page's visibility, its menu entry and its own layout route, and a flagged category passes it to its pages. Permission checks still apply. `authOnly`, by contrast, drops them.
|
|
113
124
|
|
|
114
|
-
|
|
125
|
+
The flag does not cover the data routes that feed the page. A data API belongs to no single page, so it opts out through its own guard, or through `TableViewOptions.bypassTenantAccessGate` for a table view. Reserve the flag for recovery surfaces, such as billing or a payment portal, that a blocked tenant must reach to unblock itself.
|
|
115
126
|
|
|
116
|
-
## Group
|
|
127
|
+
## Group pages with categories
|
|
117
128
|
|
|
118
|
-
`Category(id, options)` returns a `CategoryInfo` you pass as another entry's `category`. Categories nest, and
|
|
129
|
+
`Category(id, options)` returns a `CategoryInfo` that you pass as another entry's `category`. Categories nest, and each one throws unless it declares a `category` or a `module`.
|
|
119
130
|
|
|
120
131
|
```typescript
|
|
121
132
|
import {
|
|
@@ -135,11 +146,11 @@ export const contacts = Category("contacts", {
|
|
|
135
146
|
});
|
|
136
147
|
```
|
|
137
148
|
|
|
138
|
-
Creating a category registers a permission keyed to its `fullId`,
|
|
149
|
+
Creating a category registers a permission keyed to its `fullId`. The permission is `defaultGranted` when the category declares `publicAccess` or `memberAccess`, and its `permission` option overrides the derived fields. A category inside a module marks its id module-scoped instead, and a category whose `permission` is an `Action` registers none. [Permissions](/docs/interfaces/core/permissions) covers both.
|
|
139
150
|
|
|
140
|
-
### Root
|
|
151
|
+
### Root categories
|
|
141
152
|
|
|
142
|
-
`RootCategory(id, options)` creates a top-level group next to the built-in ones. Its options are `MenuOptions` without `category` and `module
|
|
153
|
+
`RootCategory(id, options)` creates a top-level group next to the built-in ones. Its options are `MenuOptions` without `category` and `module`, and `type` defaults to `"label"`. The ids `pages`, `modules` and `settings` are reserved and throw.
|
|
143
154
|
|
|
144
155
|
```typescript
|
|
145
156
|
import { RootCategory } from "@antelopejs/interface-dms/page";
|
|
@@ -151,187 +162,48 @@ export const catalogCategory = RootCategory("catalog", {
|
|
|
151
162
|
});
|
|
152
163
|
```
|
|
153
164
|
|
|
154
|
-
The interface exports
|
|
165
|
+
The interface exports these built-in roots:
|
|
155
166
|
|
|
156
|
-
| Export | Kind | URL slug |
|
|
157
|
-
| ------ | ---- | -------- |
|
|
158
|
-
| `pagesCategory` | `CategoryInfo` | `/` |
|
|
159
|
-
| `modulesCategory` | controller class (`RootPageController`) | `/modules` |
|
|
160
|
-
| `settingsCategory` | controller class (`RootPageController`) | `/settings` |
|
|
167
|
+
| Export | Kind | URL slug | `order` |
|
|
168
|
+
| ------ | ---- | -------- | ------- |
|
|
169
|
+
| `pagesCategory` | `CategoryInfo` | `/` | `1` |
|
|
170
|
+
| `modulesCategory` | controller class (`RootPageController`) | `/modules` | `2` |
|
|
171
|
+
| `settingsCategory` | controller class (`RootPageController`) | `/settings` | `3` |
|
|
161
172
|
|
|
162
|
-
`settingsCategory` declares `layout: SettingsLayout()`, so every page under it renders in the settings frame unless it passes
|
|
173
|
+
`settingsCategory` declares `layout: SettingsLayout()`, so every page under it renders in the settings frame unless it passes its own layout. `workspaceSettingsCategory` is `Category("workspace", …)` under `settingsCategory`, with the full id `settings.workspace` and the URL `/settings/workspace`. It holds the workspace settings, such as members, invitations and roles, and a project adds its own there ([Settings pages](/docs/building/settings-pages)).
|
|
163
174
|
|
|
164
|
-
`modulesCategory` is the module root
|
|
175
|
+
`modulesCategory` is the module root that every module registered with `RegisterModule` attaches under. A page or category that declares a `module` cannot resolve to a category under `settingsCategory`. That combination throws.
|
|
165
176
|
|
|
166
177
|
### `RootPageController`
|
|
167
178
|
|
|
168
|
-
`RootPageController(id, options, layout?)` builds a page controller at the root of the tree. Its options are `MenuOptions` without `category` and `module`, plus an optional `isModuleRoot` boolean.
|
|
169
|
-
|
|
170
|
-
## Identifiers a Page Carries
|
|
171
|
-
|
|
172
|
-
`Category`, `PageController` and `RootPageController` compute two identifiers from the entry's own id and its parent chain:
|
|
173
|
-
|
|
174
|
-
- `fullId` — the dot-joined chain of ids, starting at the root category (`pages.crm.contacts`). This is the page's permission id and the key realtime topics are registered under.
|
|
175
|
-
- `fullSlug` — the joined URL slugs (`/crm/contacts`).
|
|
176
|
-
|
|
177
|
-
`CategoryInfo` extends `MenuOptions` with `category` redeclared as a required `CategoryInfo | ControllerClass | undefined` holding the resolved parent, and adds `id`, `fullId`, `fullSlug` and an optional `isModuleRoot`. `PageInfo` extends `CategoryInfo` and adds `layoutUrl`, the URL the page's component tree is served from — `/dms/pages`, the page's `fullSlug`, then `/pagelayout`.
|
|
178
|
-
|
|
179
|
-
A page controller (`PageController`, `RootPageController`) serves its routes under `PAGE_ROUTE_PREFIX` (`/dms/pages`), not at the page's own path: the frontend server answers that path with the page and proxies only the backend prefixes declared to it, `/api/**` and `/dms/**` for the DMS. `@Get("/sessions")` on the page `/settings/user/security` is `GET /dms/pages/settings/user/security/sessions`, which `pageRoutePath("/settings/user/security", "/sessions")` spells out. A plain `Controller` a module mounts for its frontend sits under `/api/<module>/` instead, or under a prefix it declares (`backendProxies`, below).
|
|
180
|
-
|
|
181
|
-
`GetPermissionId(target)` returns the permission id recorded for a page controller class or a `Component`, or `undefined` when the target was never registered. A child component resolves to its full positional path. When the same component instance is mounted more than once, every position registers a permission but the instance resolves to its first registered position, because the instance alone cannot identify which mount the caller means.
|
|
182
|
-
|
|
183
|
-
```typescript
|
|
184
|
-
import { GetPermissionId } from "@antelopejs/interface-dms/page";
|
|
185
|
-
|
|
186
|
-
const pagePermission = GetPermissionId(InvoicesPage); // "pages.billing.invoices"
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
## Declare a Module
|
|
190
|
-
|
|
191
|
-
A module is a self-contained feature area with its own root in the sidebar. `RegisterModule(info)` creates that root under `modulesCategory` and returns its `CategoryInfo`.
|
|
192
|
-
|
|
193
|
-
```typescript
|
|
194
|
-
import { RegisterModule } from "@antelopejs/interface-dms/page";
|
|
195
|
-
|
|
196
|
-
RegisterModule({
|
|
197
|
-
id: "automation",
|
|
198
|
-
title: "$dms_automation.module.title",
|
|
199
|
-
description: "$dms_automation.module.description",
|
|
200
|
-
icon: "i-ph-flow-arrow",
|
|
201
|
-
defaultCategory: { displayName: "$dms_automation.module.pages", order: 0 },
|
|
202
|
-
});
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
Call `RegisterModule` before any page that references the module id: a page resolves its module synchronously inside the `PageController` call of its class declaration, before `@RegisterPage()` runs, and an unknown module id throws. Registering the same id twice throws as well.
|
|
206
|
-
|
|
207
|
-
### `ModuleInfo`
|
|
208
|
-
|
|
209
|
-
| Option | Type | Description |
|
|
210
|
-
| ------ | ---- | ----------- |
|
|
211
|
-
| `id` | `string` | Module id. Also the URL segment under `/modules`. |
|
|
212
|
-
| `title` | `string` | Display name of the module root. |
|
|
213
|
-
| `description` | `string` | Description carried on the module root. |
|
|
214
|
-
| `icon` | `string` | Icon name. |
|
|
215
|
-
| `landingPage` | `string` | Id of the page the module opens on; `/modules/<id>` redirects to it, query and hash kept. Defaults to the first page by `order`. |
|
|
216
|
-
| `catalogCategory` | `string` | Category the module is filed under in the module catalog (`"Content"`, `"Developer"`…), may be a `$` i18n key. The Modules page and the store both file the module under it; without one, a store module takes the category of the store catalog, and any other goes under "Other". Unrelated to menu categories (`MenuOptions.category`, `defaultCategory`). Formerly `category`. |
|
|
217
|
-
| `defaultCategory` | `{ displayName?, icon?, order?, urlSlug? }` | Groups the module's pages that declare `module` without an explicit `category`. Without it, those pages attach directly to the module root. |
|
|
218
|
-
|
|
219
|
-
The generated default category uses `urlSlug` `"pages"` unless you override it, and falls back to the label `"$menu.section.pages"` when `displayName` is empty or omitted.
|
|
220
|
-
|
|
221
|
-
### Attaching Pages to a Module
|
|
222
|
-
|
|
223
|
-
Set `module` on the page options. `category` stays available to group pages inside the module:
|
|
224
|
-
|
|
225
|
-
```typescript
|
|
226
|
-
import {
|
|
227
|
-
PageController,
|
|
228
|
-
RegisterPage,
|
|
229
|
-
} from "@antelopejs/interface-dms/page";
|
|
230
|
-
import "./module"; // ensures RegisterModule ran first
|
|
231
|
-
|
|
232
|
-
@RegisterPage()
|
|
233
|
-
export class OverviewPage extends PageController("overview", {
|
|
234
|
-
displayName: "$dms_automation.overview.title",
|
|
235
|
-
icon: "i-ph-gauge",
|
|
236
|
-
module: "automation",
|
|
237
|
-
order: 0,
|
|
238
|
-
}) {}
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
When a page declares both `module` and an explicit `category`, the category must be a descendant of that module's root; otherwise registration throws.
|
|
242
|
-
|
|
243
|
-
### Module URLs and Detection
|
|
244
|
-
|
|
245
|
-
`MODULE_URL_PREFIX` is `"/modules"`. Module pages resolve under `/modules/<moduleId>/...` because the module root's `urlSlug` is the module id and its parent is `modulesCategory`. Their ids follow the same chain: the module root's `fullId` is `modules.<moduleId>`, and a page in it is `modules.<moduleId>.<pageId>`.
|
|
246
|
-
|
|
247
|
-
`isInsideModule(entry)` answers whether an entry belongs to a module. It accepts anything carrying `module`, `category` and `isModuleRoot` — a `CategoryInfo` or `PageInfo` qualifies — and walks the category chain looking for a module root.
|
|
248
|
-
|
|
249
|
-
```typescript
|
|
250
|
-
import { isInsideModule } from "@antelopejs/interface-dms/page";
|
|
251
|
-
|
|
252
|
-
if (isInsideModule(pageInfo)) {
|
|
253
|
-
// module pages are gated to platform owners
|
|
254
|
-
}
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
Permissions belonging to a module entry are marked module-scoped instead of being registered in the grantable permission tree. [Permissions](/docs/interfaces/core/permissions) covers what that implies.
|
|
258
|
-
|
|
259
|
-
## Contribute Menu Entries at Request Time
|
|
260
|
-
|
|
261
|
-
Everything above declares the menu at import time. A dynamic menu provider adds entries resolved per request instead — one entry per project, per workspace, per whatever the tenant happens to own.
|
|
262
|
-
|
|
263
|
-
```typescript
|
|
264
|
-
import { RegisterDynamicMenuProvider } from "@antelopejs/interface-dms/page";
|
|
265
|
-
|
|
266
|
-
RegisterDynamicMenuProvider("pages.projects", async (user, tenantId) => {
|
|
267
|
-
const projects = await listProjects(tenantId);
|
|
268
|
-
return projects.map((project, index) => ({
|
|
269
|
-
id: project.slug,
|
|
270
|
-
displayName: project.name,
|
|
271
|
-
fullSlug: "/projects/:projectId",
|
|
272
|
-
params: { projectId: project.id },
|
|
273
|
-
icon: "i-ph-folder",
|
|
274
|
-
order: index,
|
|
275
|
-
}));
|
|
276
|
-
});
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
```typescript
|
|
280
|
-
function RegisterDynamicMenuProvider(
|
|
281
|
-
categoryFullId: string,
|
|
282
|
-
resolver: DynamicMenuResolver,
|
|
283
|
-
): () => void;
|
|
284
|
-
|
|
285
|
-
type DynamicMenuResolver = (
|
|
286
|
-
user: User | undefined,
|
|
287
|
-
tenantId: string,
|
|
288
|
-
permissions: Set<string>,
|
|
289
|
-
) => MaybePromise<DynamicMenuItem[]>;
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
A registration is a `DynamicMenuProviderInfo` — the `categoryFullId` and the `resolver`, paired. The resolver runs during `/sitelayout`, and its entries are grafted onto that request's copy of the tree only — the global navigation tree is never touched, so one tenant's entries can never reach another's site layout. Dynamic and static children of the category sort together by `order`.
|
|
179
|
+
`RootPageController(id, options, layout?)` builds a page controller at the root of the tree. Its options are `MenuOptions` without `category` and `module`, plus an optional `isModuleRoot` boolean. It requires no parent, so it suits a top-level landing screen.
|
|
293
180
|
|
|
294
|
-
|
|
181
|
+
## Identifiers a page carries
|
|
295
182
|
|
|
296
|
-
|
|
183
|
+
`Category`, `PageController` and `RootPageController` compute two identifiers from the entry's id and its parent chain:
|
|
297
184
|
|
|
298
|
-
|
|
185
|
+
- `fullId` is the chain of ids joined by dots, from the root category (`pages.crm.contacts`). It is the page's permission id and the key its realtime topics register under.
|
|
186
|
+
- `fullSlug` is the chain of URL slugs (`/crm/contacts`).
|
|
299
187
|
|
|
300
|
-
|
|
188
|
+
`CategoryInfo` extends `MenuOptions` without `parent`. It redeclares `category` as a required `CategoryInfo | ControllerClass | undefined` that holds the resolved parent, and adds `id`, `fullId`, `fullSlug`, an optional `isModuleRoot`, and an optional `urlTransparent`. `urlTransparent` is `true` when the category adds no URL segment of its own, as `urlSlug: "/"` produces. Such a category is a section heading, not a route, and any number of them may sit under one parent.
|
|
301
189
|
|
|
302
|
-
`
|
|
303
|
-
|
|
304
|
-
| Field | Type | Description |
|
|
305
|
-
| ----- | ---- | ----------- |
|
|
306
|
-
| `id` | `string` | Unique within the provider's category, and becomes the entry's `fullId` suffix. Dots separate `fullId` levels, so an id containing one is skipped with a warning. Required. |
|
|
307
|
-
| `displayName` | `string` | Entry label. Required. |
|
|
308
|
-
| `fullSlug` | `string` | Full slug of the page the entry links to, `:name` segments included. Required. |
|
|
309
|
-
| `params` | `MenuItemParams` | Values for the `:name` segments of `fullSlug`. |
|
|
310
|
-
| `query` | `MenuItemQuery` | Query parameters narrowing that page. |
|
|
311
|
-
| `description` | `string` | Longer description. |
|
|
312
|
-
| `icon` | `string` | Icon name. |
|
|
313
|
-
| `order` | `number` | Sort order among the category's children, dynamic and static alike. |
|
|
314
|
-
| `variant` | `MenuItemVariant` | Semantic look. |
|
|
315
|
-
| `status` | `MenuItemStatus` | Semantic state dot. |
|
|
316
|
-
| `permissionId` | `string` | Permission required to see the entry, as a raw id. Unset means always visible. |
|
|
190
|
+
`PageInfo` extends `CategoryInfo` with `layoutUrl` and an optional `parentSlug`. `layoutUrl` is the URL the page's component tree is served from: `/dms/pages`, then the page's `fullSlug`, then `/pagelayout`. `parentSlug` is the full slug of the page declared as `parent`.
|
|
317
191
|
|
|
318
|
-
|
|
192
|
+
A page controller serves its routes under `PAGE_ROUTE_PREFIX` (`/dms/pages`), not at the page's own path. The frontend server answers the page's own path with the page, and proxies only the backend prefixes declared to it, which are `/api/**` and `/dms/**` for the DMS. `@Get("/sessions")` on the page `/settings/user/security` is `GET /dms/pages/settings/user/security/sessions`, which `pageRoutePath("/settings/user/security", "/sessions")` returns. A plain `Controller` that a module mounts for its frontend sits under `/api/<module>/` instead, or under a prefix it declares in [`backendProxies`](/docs/interfaces/core/frontend-modules-and-request-helpers#addfrontendmoduleoptions).
|
|
319
193
|
|
|
320
|
-
`
|
|
194
|
+
`GetPermissionId(target)` returns the permission id recorded for a page controller class, a `Component`, or a child position, or `undefined` when the target never registered. A child component resolves to its full positional path. When one component instance is mounted more than once, every position registers a permission, but the instance alone resolves to its first registered position. `GetComponentPermissionIds(component)` returns the ids of every live position of a reusable component.
|
|
321
195
|
|
|
322
|
-
|
|
196
|
+
```typescript fragment
|
|
197
|
+
import { GetPermissionId } from "@antelopejs/interface-dms/page";
|
|
323
198
|
|
|
324
|
-
|
|
325
|
-
const NotifyMenuChanged: (tenantId?: string) => void;
|
|
199
|
+
const pagePermission = GetPermissionId(InvoicesPage);
|
|
326
200
|
```
|
|
327
201
|
|
|
328
|
-
|
|
202
|
+
For the page declared above, `pagePermission` is `"pages.billing.invoices"`. `GetRegisteredPageIds()` returns the ids of every registered page, sorted.
|
|
329
203
|
|
|
330
|
-
|
|
204
|
+
## Extend a page from another module
|
|
331
205
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
`@RegisterPageExtension(targetPageId)` injects components into a page owned by someone else. The decorated class is not a page: every static component field on it becomes a component of the target page, keyed by its field name.
|
|
206
|
+
`@RegisterPageExtension(targetPageId)` injects components into a page that another module owns. The decorated class is not a page. Every static component field on it becomes a component of the target page, keyed by its field name.
|
|
335
207
|
|
|
336
208
|
```typescript
|
|
337
209
|
import { RegisterPageExtension } from "@antelopejs/interface-dms/page";
|
|
@@ -345,17 +217,22 @@ export class SeatQuotaExtension {
|
|
|
345
217
|
}
|
|
346
218
|
```
|
|
347
219
|
|
|
348
|
-
The target is named by its page id
|
|
220
|
+
The target is named by its page id, the `fullId` it registered under, which is also its permission id. The DMS members page is `settings.workspace.members`. The extending module imports nothing from the owning module, so it depends on `@antelopejs/interface-dms` alone. `GetRegisteredPageIds()` lists the ids, and `GetPermissionId(ThePageClass)` returns one inside the owning module.
|
|
349
221
|
|
|
350
|
-
|
|
222
|
+
The field name is the component key, so an injected component's permission is `<target fullId>.<field name>` in the target page's own permission tree, like a component the target declares itself. The target's fields and those of every extension share that namespace.
|
|
351
223
|
|
|
352
|
-
|
|
224
|
+
`.before(anchor)` and `.after(anchor)` on the component set its placement. The anchor is a component key of the target page: `"table"` for a static field, or `"content.tasks"` for a nested position. Without an anchor, the component goes after the page's own components. When several components land at the same spot, `.order(n)` decides, and equal orders fall back to the extension class name and then to declaration order, never to module start order. [Components](/docs/interfaces/core/components) covers those builder methods.
|
|
353
225
|
|
|
354
|
-
The injection applies
|
|
226
|
+
The injection applies once the target page finishes registering, whichever module starts first, so anchors resolve against the page's complete component set. It is removed when the extending module stops. The components are prepared as the extending module, each time the target page registers. What they register, such as permissions, native upload fields, form sub-pages and realtime topics, belongs to the extending module. It survives a reload of the DMS or of the target's module and goes away with the extending module.
|
|
355
227
|
|
|
356
|
-
Nothing about the target can be checked when the decorator runs, so
|
|
228
|
+
Nothing about the target can be checked when the decorator runs, so the checks happen when the injection applies:
|
|
357
229
|
|
|
358
|
-
|
|
230
|
+
- An empty target id throws at the decorator.
|
|
231
|
+
- An extension class with no component field logs a warning and injects nothing.
|
|
232
|
+
- A page id that no module registers keeps the extension pending, with a warning once every module has started.
|
|
233
|
+
- A key that the page or another extension already uses, or an anchor that the page does not declare, is reported at error level with the extension name, the target id and the key. That extension alone is skipped.
|
|
234
|
+
|
|
235
|
+
```typescript fragment
|
|
359
236
|
interface PageExtensionInfo {
|
|
360
237
|
extensionName: string;
|
|
361
238
|
targetFullId: string;
|
|
@@ -366,46 +243,48 @@ interface PageExtensionComponent {
|
|
|
366
243
|
key: string;
|
|
367
244
|
component: Component;
|
|
368
245
|
side: PlacementSide;
|
|
369
|
-
|
|
246
|
+
anchorPath?: readonly string[];
|
|
370
247
|
order: number;
|
|
371
248
|
}
|
|
372
249
|
```
|
|
373
250
|
|
|
374
|
-
`PageExtensionInfo` is the registration handle
|
|
251
|
+
`PageExtensionInfo` is the registration handle that is unregistered when the extending module stops. `key` is the static field name, and also the last segment of the component's permission id on the target page. `anchorPath` is the anchor split into its dotted segments.
|
|
375
252
|
|
|
376
|
-
## Page
|
|
253
|
+
## Page layouts
|
|
377
254
|
|
|
378
255
|
A page layout is the serialized description of the page's components:
|
|
379
256
|
|
|
380
|
-
```typescript
|
|
257
|
+
```typescript fragment
|
|
381
258
|
interface PageLayout<T = unknown> {
|
|
382
259
|
components: Record<string, ComponentInfoSerialized<T>>;
|
|
383
260
|
layout?: ComponentInfo<T>;
|
|
261
|
+
allComponentsHidden?: true;
|
|
384
262
|
}
|
|
385
263
|
```
|
|
386
264
|
|
|
387
|
-
`components` is keyed by the static field name on the page class
|
|
265
|
+
`components` is keyed by the static field name on the page class, and each value is the serialized component ([Components](/docs/interfaces/core/components)). `layout` is the layout component. `allComponentsHidden` is set when the page declares components and the caller may see none of them, so the browser can say so instead of drawing a blank page.
|
|
388
266
|
|
|
389
267
|
Registering a page installs a `PageLayoutHandler` for its `fullSlug`:
|
|
390
268
|
|
|
391
|
-
```typescript
|
|
269
|
+
```typescript fragment
|
|
392
270
|
interface PageLayoutRequest {
|
|
393
271
|
user: User | undefined;
|
|
394
272
|
memberModel: TenantMemberModel;
|
|
395
273
|
roleModel: RoleModel;
|
|
396
274
|
tenantId: string;
|
|
397
|
-
/** The page URL's query, for header-button availability. */
|
|
398
275
|
query?: Readonly<Record<string, string>>;
|
|
399
|
-
/** The page URL's route parameters (`:id`), likewise. */
|
|
400
276
|
params?: Readonly<Record<string, string>>;
|
|
401
|
-
/** Passed to the permissions resolvers: `{ preview }` when a role preview reads the layout. */
|
|
402
277
|
resolverContext?: PermissionsResolverContext;
|
|
403
278
|
}
|
|
404
279
|
|
|
405
280
|
type PageLayoutHandler = (request: PageLayoutRequest) => Promise<PageLayout>;
|
|
406
281
|
```
|
|
407
282
|
|
|
408
|
-
|
|
283
|
+
`query` and `params` are the page URL's query and route parameters, handed to header-button availability. `resolverContext` goes to the permissions resolvers, with `preview` set when the layout is served for a role preview.
|
|
284
|
+
|
|
285
|
+
The handler drops the components the caller has no permission for and applies each component's own filter. A page with `publicAccess`, `authOnly` or `noComponentPermissions` skips the permission step and returns every component. Native upload tokens are bound to the exact page and component mount at page registration, not by this handler, and upload and metadata requests still check live authorization ([File storage](/docs/components/file-storage)).
|
|
286
|
+
|
|
287
|
+
`GetPageLayoutBySlug(slug)` returns the handler, or `undefined` for an unknown slug:
|
|
409
288
|
|
|
410
289
|
```typescript
|
|
411
290
|
import { GetModel } from "@antelopejs/interface-database-decorators";
|
|
@@ -434,40 +313,43 @@ async function renderLayout(
|
|
|
434
313
|
}
|
|
435
314
|
```
|
|
436
315
|
|
|
437
|
-
`ClearPageLayoutBySlug(slug)` drops the handler
|
|
316
|
+
`ClearPageLayoutBySlug(slug)` drops the handler. A page unregisters through it, because `/dms/pagelayout` reaches the handler by slug. A handler left behind would keep serving the layout of a page already gone from the registry, the navigation tree and its own route.
|
|
438
317
|
|
|
439
318
|
### `PageMetadata`
|
|
440
319
|
|
|
441
|
-
`PageMetadata` is the
|
|
320
|
+
`PageMetadata` is the record the decorator fills in for each controller. `GetMetadata(controllerClass, PageMetadata)` returns it. It holds `pageInfo`, `layout` and `components`, and exposes:
|
|
442
321
|
|
|
443
322
|
| Member | Description |
|
|
444
323
|
| ------ | ----------- |
|
|
445
|
-
| `SetInfo(id, fullSlug, menuOptions, pageLayout?)` |
|
|
446
|
-
| `SetComponent(id, component)` |
|
|
447
|
-
| `inherit(parent)` |
|
|
448
|
-
| `Register()` |
|
|
449
|
-
| `
|
|
324
|
+
| `SetInfo(id, fullSlug, menuOptions, pageLayout?)` | Computes and stores the `PageInfo` and the layout. |
|
|
325
|
+
| `SetComponent(id, component)` | Attaches a component under a key. |
|
|
326
|
+
| `inherit(parent)` | Merges another `PageMetadata`'s info, layout and components into this one. |
|
|
327
|
+
| `Register()` | Registers the page: permissions, layout handler, `/pagelayout` route and auth decorators. |
|
|
328
|
+
| `ComponentPermissionIds(component)` | Returns the permission ids of every position the component holds on this page. |
|
|
329
|
+
| `target` | The controller class the record belongs to. |
|
|
330
|
+
|
|
331
|
+
`@RegisterPage()` calls `SetComponent` for each static component and then `Register()`. Use `PageMetadata` directly only to generate pages programmatically.
|
|
450
332
|
|
|
451
|
-
|
|
333
|
+
Registration also runs each component's `onCreated` callback with this record. It registers a permission for each component and each component action, unless the page sets `noComponentPermissions` or `authOnly`, or sits inside a module ([Permissions](/docs/interfaces/core/permissions)).
|
|
452
334
|
|
|
453
|
-
|
|
335
|
+
A declaration that the page cannot honour throws `PageDeclarationConflictError` and the page fails to register. `FormPageRouteConflictError` extends it for two page-mode table views that register the same form sub-page.
|
|
454
336
|
|
|
455
|
-
## Page
|
|
337
|
+
## Page access
|
|
456
338
|
|
|
457
|
-
Registering a page installs an auth decorator on the controller, chosen from the options:
|
|
339
|
+
Registering a page installs an auth decorator on the controller's `user` property, chosen from the options:
|
|
458
340
|
|
|
459
341
|
| Options | Decorator installed |
|
|
460
342
|
| ------- | ------------------- |
|
|
461
|
-
| `publicAccess: true` |
|
|
462
|
-
| `authOnly: true` | `AuthUser` from `@antelopejs/interface-dms/auth
|
|
463
|
-
| page
|
|
464
|
-
| default | `AuthUserWithPermission(pageClass)`
|
|
343
|
+
| `publicAccess: true` | None. The page is served to anonymous callers. |
|
|
344
|
+
| `authOnly: true` | `AuthUser` from `@antelopejs/interface-dms/auth`. A signed-in user, no permission check. |
|
|
345
|
+
| page whose `fullSlug` starts with `/modules/` | `AuthOwnerOnly` from `@antelopejs/interface-dms/auth`. Module pages are for platform owners only. |
|
|
346
|
+
| default | `AuthUserWithPermission(pageClass, { bypassTenantAccessGate })` with the page's own permission ([Permissions](/docs/interfaces/core/permissions#authuserwithpermission)). |
|
|
465
347
|
|
|
466
|
-
On a page that is neither `publicAccess` nor `authOnly`, registration also binds `roleModel`, `memberModel` and `tenantId` properties on the controller, scoped to the request's tenant ([Tenancy](/docs/interfaces/core/tenancy)). Routes
|
|
348
|
+
On a page that is neither `publicAccess` nor `authOnly`, registration also binds `roleModel`, `memberModel` and `tenantId` properties on the controller, scoped to the request's tenant ([Tenancy](/docs/interfaces/core/tenancy)). Routes declared on the page class inherit that gating, so a route on a default page already requires the page's permission.
|
|
467
349
|
|
|
468
|
-
|
|
350
|
+
Declare an injected property on the class to read it in a handler. The provider runs on every request, whether or not the handler reads it:
|
|
469
351
|
|
|
470
|
-
```typescript
|
|
352
|
+
```typescript fragment
|
|
471
353
|
import { Get } from "@antelopejs/interface-api";
|
|
472
354
|
import {
|
|
473
355
|
PageController,
|
|
@@ -490,11 +372,11 @@ export class MyContactsPage extends PageController("mine", {
|
|
|
490
372
|
}
|
|
491
373
|
```
|
|
492
374
|
|
|
493
|
-
## Page
|
|
375
|
+
## Page setup functions
|
|
494
376
|
|
|
495
|
-
`setupId` names a function that runs while the page is mounted. The backend only
|
|
377
|
+
`setupId` names a function that runs while the page is mounted. The backend carries only the id. The frontend registers the function, runs it when the page mounts, and passes it a `PageSetupContext`.
|
|
496
378
|
|
|
497
|
-
```typescript
|
|
379
|
+
```typescript fragment
|
|
498
380
|
type PageSetupFunction = (
|
|
499
381
|
context: PageSetupContext,
|
|
500
382
|
) => MaybePromise<PageSetupCleanup | void>;
|
|
@@ -502,14 +384,14 @@ type PageSetupFunction = (
|
|
|
502
384
|
|
|
503
385
|
| Member of `PageSetupContext` | Description |
|
|
504
386
|
| ---------------------------- | ----------- |
|
|
505
|
-
| `pageInfo` | The `PageInfo` of the page
|
|
387
|
+
| `pageInfo` | The `PageInfo` of the mounted page. |
|
|
506
388
|
| `permissions` | `Set<string>` of the caller's effective permission ids. |
|
|
507
|
-
| `on(component, event, handler)` |
|
|
508
|
-
| `emit(component, event, data?)` |
|
|
389
|
+
| `on(component, event, handler)` | Subscribes to a component event. Returns an unsubscribe function. |
|
|
390
|
+
| `emit(component, event, data?)` | Dispatches an event on the shared event bus. |
|
|
509
391
|
|
|
510
|
-
`PageSetupCleanup` is `() => void
|
|
392
|
+
`PageSetupCleanup` is `() => void`. The setup function returns it to release what it allocated, and the frontend calls it when the page unmounts. `MaybePromise<T>` is `T | Promise<T>`, re-exported from `@antelopejs/interface-dms/types`.
|
|
511
393
|
|
|
512
|
-
```typescript
|
|
394
|
+
```typescript fragment
|
|
513
395
|
import {
|
|
514
396
|
PageController,
|
|
515
397
|
RegisterPage,
|
|
@@ -524,27 +406,11 @@ export class LifecyclePage extends PageController("lifecycle", {
|
|
|
524
406
|
}) {}
|
|
525
407
|
```
|
|
526
408
|
|
|
527
|
-
##
|
|
528
|
-
|
|
529
|
-
`AddFrontendModule(config)` registers a frontend module your module ships. Call it from your module's `construct()`.
|
|
409
|
+
## Frontend modules and request helpers
|
|
530
410
|
|
|
531
|
-
|
|
532
|
-
import path from "node:path";
|
|
533
|
-
import { AddFrontendModule } from "@antelopejs/interface-dms/page";
|
|
534
|
-
|
|
535
|
-
export async function construct(): Promise<void> {
|
|
536
|
-
await AddFrontendModule({
|
|
537
|
-
name: "@acme/dms-billing",
|
|
538
|
-
sourcePath: path.join(__dirname, "../frontend-vue"),
|
|
539
|
-
renderer: { name: "vue", version: "3" },
|
|
540
|
-
configKey: "dmsBilling",
|
|
541
|
-
priority: 0,
|
|
542
|
-
options: { prefix: "Billing" },
|
|
543
|
-
});
|
|
544
|
-
}
|
|
545
|
-
```
|
|
411
|
+
[Frontend modules and request helpers](/docs/interfaces/core/frontend-modules-and-request-helpers) describes the functions a module's backend calls outside page declarations.
|
|
546
412
|
|
|
547
|
-
The frontend reads the project, not the backend's registrations
|
|
413
|
+
The frontend reads the project, not the backend's registrations. `ajs dms dev`, `ajs dms build` and `ajs dms start` call the module's `frontendModule` function, which the module's `package.json` names as `dmsFrontend` (`"dmsFrontend": "dist/frontend-module.js"`). Keep the registration in that pure function of the module's config, and pass its result to `AddFrontendModule`. A module that only calls `AddFrontendModule` is left out of the frontend, in development and in production.
|
|
548
414
|
|
|
549
415
|
```typescript
|
|
550
416
|
import path from "node:path";
|
|
@@ -567,113 +433,20 @@ export const frontendModule: FrontendModuleFunction<BillingConfig> = (
|
|
|
567
433
|
|
|
568
434
|
### `AddFrontendModuleOptions`
|
|
569
435
|
|
|
570
|
-
|
|
571
|
-
| ------ | ---- | ----------- |
|
|
572
|
-
| `name` | `string` | Logical frontend module name. Registry identity also includes renderer name and major version. Required. |
|
|
573
|
-
| `sourcePath` | `string` | Absolute path to the frontend module directory. Required. |
|
|
574
|
-
| `renderer` | `FrontendRendererMetadata` | Framework the module is written for, as a name and a version compared by major only. Use `{ name: "vue", version: "3" }` for the Inertia/Vue frontend. The loader releases the module runs on are declared in the `package.json` of `sourcePath` instead ([Frontend layer](/docs/building/frontend-layer#declaring-the-loader-releases-a-module-supports)). |
|
|
575
|
-
| `options` | `Record<string, any>` | Values handed to the layer's configuration. Defaults to `{}`. |
|
|
576
|
-
| `privateOptions` | `Record<string, any>` | A second set of values handed to the layer, kept separate from `options`. Defaults to `{}`. |
|
|
577
|
-
| `priority` | `number` | Ordering among the registered layers. Defaults to `0`. |
|
|
578
|
-
| `configKey` | `string` | Configuration key the layer is published under. |
|
|
579
|
-
| `authEstablishEndpoints` | `string[]` | Backend routes (`/api/…`) the frontend server may open a session from on the module's behalf (its `/auth/establish` route). |
|
|
580
|
-
| `publicEndpoints` | `FrontendPublicEndpoint[]` | Backend routes the module serves to callers outside the dashboard — a provider's webhook. See below. |
|
|
581
|
-
| `backendProxies` | `FrontendBackendProxy[]` | Backend prefixes outside `/api/**` and `/dms/**` the browser reaches through the frontend server (`{ path: "/media/**", auth: "none" }`); `auth` is `"session"` (the default, the session's token added server-side) or `"none"`. A path is `/x`, `/x/*` or `/x/**`, its first segment literal; a malformed entry fails the registration. |
|
|
582
|
-
|
|
583
|
-
#### Public endpoints
|
|
584
|
-
|
|
585
|
-
The frontend server's proxy asks every state-changing request for a same-site `Origin` and forwards it with the signed-in user's session, relaying only a few headers. A webhook a mail or payment provider calls through the dashboard's public URL fails both checks. Declare such a route public:
|
|
586
|
-
|
|
587
|
-
```typescript
|
|
588
|
-
await AddFrontendModule({
|
|
589
|
-
name: "@acme/dms-mailing",
|
|
590
|
-
sourcePath: path.join(__dirname, "../frontend-vue"),
|
|
591
|
-
renderer: { name: "vue", version: "3" },
|
|
592
|
-
publicEndpoints: [
|
|
593
|
-
{
|
|
594
|
-
path: "/api/mailing/events/*/*",
|
|
595
|
-
methods: ["POST"],
|
|
596
|
-
headers: ["x-mailing-webhook-secret"],
|
|
597
|
-
},
|
|
598
|
-
],
|
|
599
|
-
});
|
|
600
|
-
```
|
|
601
|
-
|
|
602
|
-
A request to a declared route needs no `Origin`, carries the declared headers, and is **never sent the session**, even from a signed-in browser: a cross-site form posting to it acts as nobody, and the route authenticates its caller itself (a shared secret, a signature). `path` is absolute under `/api/`, its first segment literal (the module's own prefix, never `*`), each later `*` matching exactly one segment; `methods` defaults to `["POST"]`; `headers` names the module's own `x-` headers, those a proxy or the DMS gives a meaning to (`x-forwarded-*`, `x-real-ip`, `x-dms-*`, `x-inertia*`, method overrides) being refused. A malformed entry fails the registration. The declarations reach the frontend server through the manifest it is built from (an authenticated build only), and its `DMS_PUBLIC_ENDPOINTS` adds routes no module declares. It needs `@antelopejs/dms-frontend` with public endpoint support; an older one ignores the field.
|
|
603
|
-
|
|
604
|
-
Within one logical name, renderer, and major version, the highest-priority
|
|
605
|
-
registration wins. Equal-priority registrations use the latest registration.
|
|
606
|
-
The manifest and archive endpoints select one renderer with the paired
|
|
607
|
-
`renderer` and `rendererVersion` query parameters. The version is matched by
|
|
608
|
-
major version; unsupported selections return 404 rather than mixing renderer
|
|
609
|
-
sources. Omitting both parameters remains an alias for Vue 3 clients.
|
|
610
|
-
|
|
611
|
-
## Stamp Upload Tokens
|
|
612
|
-
|
|
613
|
-
`StampUploadFieldTokens(options)` takes a component's serialized options and returns them with a signed upload token stamped into every file and image field — the component's own fields, and the forms it embeds (a relation field's inline add form, a table view's `new` / `edit` / `view` forms) — binding each field to the storage and path it declares so a client cannot presign an upload elsewhere. The builders that carry upload fields attach it through `transformOptions`, and page registration stamps the authoritative declaration, so pages you declare with `@RegisterPage()` are covered already. Call it yourself only when you serialize and serve components outside that path: a token produced outside a registered page stays unbound and cannot authorize an upload.
|
|
614
|
-
|
|
615
|
-
```typescript
|
|
616
|
-
import { StampUploadFieldTokens } from "@antelopejs/interface-dms/uploads";
|
|
617
|
-
|
|
618
|
-
const components = await StampUploadFieldTokens(layout.components);
|
|
619
|
-
```
|
|
620
|
-
|
|
621
|
-
Options holding no upload field come back untouched; otherwise the return value is a deep clone, since serialization runs again on a hot reload and each pass must start from the untransformed declaration.
|
|
622
|
-
|
|
623
|
-
## Public URLs
|
|
624
|
-
|
|
625
|
-
`GetApiBaseUrl()` and `GetClientBaseUrl()` answer the public origins of the DMS API and of the dashboard, from the DMS configuration (`apiBaseUrl`, `clientBaseUrl`); under `ajs dms dev` they follow the dev server's origins. Build an absolute link on them rather than on the request's headers: behind a reverse proxy that forwards no `X-Forwarded-Host`, the request only knows the internal host.
|
|
626
|
-
|
|
627
|
-
```typescript
|
|
628
|
-
import { GetApiBaseUrl } from "@antelopejs/interface-dms/api-base-url";
|
|
629
|
-
import { GetClientBaseUrl } from "@antelopejs/interface-dms/client-base-url";
|
|
630
|
-
|
|
631
|
-
const apiBase = await GetApiBaseUrl(); // "https://api.example.com"
|
|
632
|
-
const downloadUrl = `${apiBase}/i18n/site/fr.json`;
|
|
633
|
-
const inviteUrl = `${await GetClientBaseUrl()}/auth/signup`;
|
|
634
|
-
```
|
|
635
|
-
|
|
636
|
-
Both return `string | undefined`: `undefined` only when the configuration clears the value.
|
|
637
|
-
|
|
638
|
-
## Client address
|
|
639
|
-
|
|
640
|
-
`getClientIp(context)` answers the address a request came from, resolved the way the DMS resolves the one it records on a session: behind the `auth.trustedProxies` reverse proxies of its configuration ([Client address behind proxies](/docs/building/configuration#client-address-behind-proxies)), each trusted hop vouching for the `x-forwarded-for` entry it appended and nothing left of it, and an IPv4-mapped address (`::ffff:10.0.0.1`) written as plain IPv4. Key a rate limit or an anonymous visitor on it rather than on `x-forwarded-for` read by hand: the operator declares the proxies once, for the DMS and every module.
|
|
641
|
-
|
|
642
|
-
```typescript
|
|
643
|
-
import { Context, Controller, Post, type RequestContext } from "@antelopejs/interface-api";
|
|
644
|
-
import { getClientIp } from "@antelopejs/interface-dms/client-ip";
|
|
645
|
-
|
|
646
|
-
export class CollectController extends Controller("/api/collect") {
|
|
647
|
-
@Context()
|
|
648
|
-
declare requestContext: RequestContext;
|
|
649
|
-
|
|
650
|
-
@Post("/")
|
|
651
|
-
async collect(): Promise<void> {
|
|
652
|
-
const address = await getClientIp(this.requestContext); // "109.132.199.8"
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
```
|
|
656
|
-
|
|
657
|
-
It answers an empty string when the request carries no address. `ResolveClientIp({ socketAddress, forwardedFor })` is the same resolution on values a module already holds, a request it queued for later say.
|
|
436
|
+
The options of `AddFrontendModule`, which registers the frontend a module ships, are listed in [`AddFrontendModuleOptions`](/docs/interfaces/core/frontend-modules-and-request-helpers#addfrontendmoduleoptions).
|
|
658
437
|
|
|
659
|
-
|
|
438
|
+
### Public URLs
|
|
660
439
|
|
|
661
|
-
`
|
|
440
|
+
`GetApiBaseUrl()` and `GetClientBaseUrl()` are described in [Public URLs](/docs/interfaces/core/frontend-modules-and-request-helpers#public-urls).
|
|
662
441
|
|
|
663
|
-
|
|
442
|
+
### Client address
|
|
664
443
|
|
|
665
|
-
|
|
666
|
-
| --- | --- | --- |
|
|
667
|
-
| `context` | `RequestContext` | The authorized server request. |
|
|
668
|
-
| `componentIds` | `string[]` | Trusted component identities, not ids supplied by the client. |
|
|
669
|
-
| `fields` | `AttachmentField[]` | Server-declared attachment fields. |
|
|
670
|
-
| `submitted` | `Record<string, unknown>` | The submitted values. |
|
|
671
|
-
| `before` | `Record<string, unknown>` | Previously persisted values, loaded by the server. |
|
|
444
|
+
`getClientIp(context)` and `ResolveClientIp(source)` are described in [Client address](/docs/interfaces/core/frontend-modules-and-request-helpers#client-address).
|
|
672
445
|
|
|
673
|
-
|
|
446
|
+
### `SaveComponentFiles`
|
|
674
447
|
|
|
675
|
-
|
|
448
|
+
`SaveComponentFiles` is described in [`SaveComponentFiles`](/docs/interfaces/core/frontend-modules-and-request-helpers#savecomponentfiles).
|
|
676
449
|
|
|
677
|
-
## Next
|
|
450
|
+
## Next steps
|
|
678
451
|
|
|
679
|
-
[Components](/docs/interfaces/core/components) covers the builders that produce the values you assign to those static fields.
|
|
452
|
+
[Components](/docs/interfaces/core/components) covers the builders that produce the values you assign to those static fields. [Modules and dynamic menus](/docs/interfaces/core/modules-and-dynamic-menus) covers module roots and per-request menu entries.
|