adminizer 5.0.0-build.2 → 5.0.0-build.21
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/assets/abnfDiagram-VCTEODGH-CkmsvdDi.js +1 -0
- package/assets/add-CESPH_aI.js +1 -0
- package/assets/add-group-CQ4Xm8Ru.js +1 -0
- package/assets/add-user-B-QNjR4B.js +1 -0
- package/assets/ai-assistant/agent.es.js +28362 -0
- package/assets/app-C5Ju5Zra.css +1 -0
- package/assets/app-Ca5yUSBv.js +138 -0
- package/assets/arc-B-Dt8YNl.js +1 -0
- package/assets/architectureDiagram-5GKGNRK7-Ca58kq-l.js +36 -0
- package/assets/asmcrypto-DZeQH0LC.js +1 -0
- package/assets/blockDiagram-NRAW4CY4-CuqE52IG.js +129 -0
- package/assets/c4Diagram-UCG6FXSJ-Cx8z-0Bw.js +38 -0
- package/assets/catalog-CkSbxvx0.js +6 -0
- package/assets/channel-C9z6Pi_j.js +1 -0
- package/assets/chunk-2Q5K7J3B-CXISVKTC.js +1 -0
- package/assets/chunk-5VM5RSS4-DYGeeVYe.js +15 -0
- package/assets/chunk-F27PBJKO-BeY-dbKj.js +1 -0
- package/assets/chunk-G27WJ6UU-DeX_eH7C.js +231 -0
- package/assets/chunk-JWPE2WC7-DaxI9q0u.js +1 -0
- package/assets/chunk-LCL6LL3I-B7PkaEz_.js +206 -0
- package/assets/chunk-POPQ4Y6H-lwu6ZpsN.js +1 -0
- package/assets/chunk-SVP7TREG-CVFllgl1.js +88 -0
- package/assets/chunk-XXDRQBXY-DDLN0rcg.js +1 -0
- package/assets/classDiagram-DTDB5LWJ-BKtCIC3B.js +1 -0
- package/assets/classDiagram-v2-JRS7N3AN-BKtCIC3B.js +1 -0
- package/assets/controls/ckeditor.es.js +9370 -9306
- package/assets/controls/handsontable.es.js +11971 -11938
- package/assets/controls/jsoneditor.es.js +13104 -13008
- package/assets/controls/leaflet.es.js +1023 -1004
- package/assets/controls/toast-ui.es.js +1843 -1834
- package/assets/cose-bilkent-JH36ORCC-CWRyUkhQ.js +1 -0
- package/assets/cynefin-OW5HDTMX-B1iLDXCC.js +166 -0
- package/assets/cynefinDiagram-5FMLGOSQ-DaXQHr3l.js +62 -0
- package/assets/cytoscape.esm-yzknjiTM.js +321 -0
- package/assets/dagre-3AP2YEHR-DFORa4Hz.js +4 -0
- package/assets/dashboard-Ssa1_Of3.js +3 -0
- package/assets/debounce-B3mXI_sx.js +1 -0
- package/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/assets/diagram-S7CK7UJ4-CgP6KBo0.js +30 -0
- package/assets/diagram-UQ7AKVKN-oYfQe6w0.js +41 -0
- package/assets/diagram-VSXAHHWV-DfCU5wgt.js +3 -0
- package/assets/diagram-VX7I27RA-BITJVrNY.js +24 -0
- package/assets/diagram-Z3DM3KII-D8J9Ssjy.js +24 -0
- package/assets/docs-DHMt_Rq1.js +1 -0
- package/assets/ebnfDiagram-PWID7BFC-qpNmoYgx.js +1 -0
- package/assets/erDiagram-SSCWMZ5O-DDF-Y0zs.js +99 -0
- package/assets/flowDiagram-A5DVABFB-CjKVkR9T.js +168 -0
- package/assets/ganttDiagram-EL5Y4UJY-BXGQ53w6.js +292 -0
- package/assets/gitGraphDiagram-WWUBYQGX-BLMevdLh.js +106 -0
- package/assets/history-tBOo2gd5.js +1 -0
- package/assets/infoDiagram-RXCK75RN-BAnPdnyw.js +2 -0
- package/assets/init-Gi6I4Gst.js +1 -0
- package/assets/init-user-DrNUHa6r.js +1 -0
- package/assets/isObject-DL-BuaBl.js +1 -0
- package/assets/ishikawaDiagram-5VMMS53U-PnL0Z4Li.js +70 -0
- package/assets/journeyDiagram-EYS64GPL-CLcsCSwF.js +139 -0
- package/assets/kanban-definition-3QL26DDD-C2eU3uBE.js +89 -0
- package/assets/katex-HP8lGamR.js +257 -0
- package/assets/layout-BXoYdHdx.js +1 -0
- package/assets/linear-BSzrP0oP.js +1 -0
- package/assets/list-CLg6xeIK.js +4 -0
- package/assets/login-Ce7o3-o_.js +1 -0
- package/assets/manifest.json +834 -58
- package/assets/{media-manager-BDbpSSjH.js → media-manager-D7G1P3fz.js} +3 -3
- package/assets/mermaid.core-aKMJHeEk.js +310 -0
- package/assets/mindmap-definition-FBJOCRG2-Dzbz5ghA.js +96 -0
- package/assets/module-BsXph_pA.js +1 -0
- package/assets/notification-D-Zz1gF_.js +20 -0
- package/assets/ordinal-Cboi1Yqb.js +1 -0
- package/assets/pegDiagram-XKGWAZYB-BTdWkU0Y.js +1 -0
- package/assets/pieDiagram-E7YTZNPT-Dnw8-TB-.js +39 -0
- package/assets/quadrantDiagram-AXDQQJYC-CALzLlg9.js +7 -0
- package/assets/railroadDiagram-O6MQD6OU-CTA1vzcv.js +1 -0
- package/assets/register-CNnBkzej.js +1 -0
- package/assets/requirementDiagram-EFPCY7ZU-UX2CF5HA.js +84 -0
- package/assets/ru-CoNgAwFK.js +1 -0
- package/assets/sankeyDiagram-P5KCCOFB-4aPp3ATV.js +40 -0
- package/assets/sequenceDiagram-WJ2MYXX4--kltkwJw.js +162 -0
- package/assets/sizeCapture-X5ZJPWSS-Ca9-g5QY.js +1 -0
- package/assets/stateDiagram-HBIQ2CUA-Diaf5Q7I.js +1 -0
- package/assets/stateDiagram-v2-4QOOHH4V-C6FlRp7u.js +1 -0
- package/assets/swimlanes-XN3QIQJK-DhqX_myE.js +1 -0
- package/assets/swimlanesDiagram-VK2B7HYN-i_3LTEBk.js +8 -0
- package/assets/timeline-definition-24CTP7MA-CwkJQbhq.js +120 -0
- package/assets/toNumber-DZb-r9sy.js +1 -0
- package/assets/use-appearance-DC-DESKB.js +1 -0
- package/assets/user-filters-list-W47DTBp2.js +1 -0
- package/assets/vennDiagram-4TSXK5OY-BNM3_cVS.js +34 -0
- package/assets/wardleyDiagram-VM6X3IG4-BODb1NP8.js +78 -0
- package/assets/welcome-BeBm6NIi.js +1 -0
- package/assets/with-app-layout-B90DtzYC.js +31 -0
- package/assets/xychartDiagram-S5SC5T6Z-DthqEh3g.js +7 -0
- package/controllers/access-rights/permissionOptions.d.ts +2 -0
- package/controllers/access-rights/permissionOptions.js +23 -0
- package/controllers/add.js +1 -1
- package/controllers/addGroup.js +15 -6
- package/controllers/ai/AiAgentController.d.ts +66 -0
- package/controllers/ai/AiAgentController.js +435 -0
- package/controllers/ai/AiAssistantController.d.ts +0 -4
- package/controllers/ai/AiAssistantController.js +4 -78
- package/controllers/catalog/FrontendCatalogAdapter.d.ts +1 -1
- package/controllers/catalog/FrontentCatalogAdapter.d.ts +1 -1
- package/controllers/docs/index.d.ts +22 -0
- package/controllers/docs/index.js +126 -0
- package/controllers/edit.js +1 -1
- package/controllers/editGroup.js +15 -6
- package/controllers/feedback/feedbackController.d.ts +1 -1
- package/controllers/filter-fields/savedFilters.js +1 -1
- package/controllers/history-actions/HistoryController.d.ts +1 -1
- package/controllers/history-actions/HistoryController.js +6 -6
- package/controllers/list.js +1 -1
- package/controllers/login.js +1 -1
- package/controllers/media-manager/mediaManagerAdapter.d.ts +1 -1
- package/controllers/notifications/NotificationController.js +14 -8
- package/controllers/view.js +2 -2
- package/controllers/widgets/filterInfo.js +1 -1
- package/helpers/accessRightsHelper.d.ts +11 -4
- package/helpers/accessRightsHelper.js +82 -12
- package/helpers/assetVersionHelper.d.ts +14 -0
- package/helpers/assetVersionHelper.js +42 -0
- package/helpers/configHelper.d.ts +3 -3
- package/helpers/configHelper.js +20 -18
- package/helpers/controllerHelper.d.ts +3 -2
- package/helpers/controllerHelper.js +25 -15
- package/helpers/fieldsHelper.d.ts +2 -2
- package/helpers/fieldsHelper.js +3 -3
- package/helpers/filterDatetimeHelper.d.ts +1 -1
- package/helpers/inertiaActionsHelper.d.ts +3 -3
- package/helpers/inertiaActionsHelper.js +4 -5
- package/helpers/inertiaAddHelper.d.ts +5 -5
- package/helpers/inertiaAddHelper.js +7 -6
- package/helpers/inertiaGroupHelper.d.ts +3 -3
- package/helpers/inertiaGroupHelper.js +10 -2
- package/helpers/inertiaListHelper.d.ts +4 -4
- package/helpers/inertiaListHelper.js +7 -8
- package/helpers/inertiaMenuHelper.d.ts +2 -3
- package/helpers/inertiaMenuHelper.js +3 -26
- package/helpers/inertiaUserHelper.d.ts +3 -3
- package/helpers/menuHelper.d.ts +2 -2
- package/helpers/modelResourceHelper.d.ts +9 -0
- package/helpers/modelResourceHelper.js +43 -0
- package/helpers/navigationAccessHelper.d.ts +13 -0
- package/helpers/navigationAccessHelper.js +40 -0
- package/helpers/translationHelper.d.ts +2 -2
- package/index.d.ts +69 -58
- package/index.js +11 -0
- package/interfaces/adminpanelConfig.d.ts +18 -5
- package/interfaces/global.d.ts +6 -6
- package/interfaces/internalModelAccess.d.ts +1 -1
- package/interfaces/listQueryBuilder.d.ts +1 -1
- package/interfaces/types.d.ts +171 -2
- package/lib/Adminizer.d.ts +46 -26
- package/lib/Adminizer.js +37 -1
- package/lib/DataAccessor.d.ts +7 -6
- package/lib/DataAccessor.js +50 -48
- package/lib/MiddlewareManager.d.ts +1 -1
- package/lib/access-rights/AccessRightsHandler.d.ts +2 -2
- package/lib/admin-links/AdminLinkHandler.d.ts +72 -0
- package/lib/admin-links/AdminLinkHandler.js +175 -0
- package/lib/ai-assistant/AbstractAiConversationHistoryService.d.ts +39 -0
- package/lib/ai-assistant/AbstractAiConversationHistoryService.js +98 -0
- package/lib/ai-assistant/AbstractAiModelService.d.ts +140 -3
- package/lib/ai-assistant/AbstractAiModelService.js +154 -1
- package/lib/ai-assistant/AiAgentRunManager.d.ts +35 -0
- package/lib/ai-assistant/AiAgentRunManager.js +109 -0
- package/lib/ai-assistant/AiAssistantAgentSkillHandler.d.ts +58 -0
- package/lib/ai-assistant/AiAssistantAgentSkillHandler.js +87 -0
- package/lib/ai-assistant/AiAssistantHandler.d.ts +16 -5
- package/lib/ai-assistant/AiAssistantHandler.js +35 -1
- package/lib/ai-assistant/AiAssistantUiMethodHandler.d.ts +60 -0
- package/lib/ai-assistant/AiAssistantUiMethodHandler.js +138 -0
- package/lib/ai-assistant/builtinAgentSkills.d.ts +9 -0
- package/lib/ai-assistant/builtinAgentSkills.js +256 -0
- package/lib/ai-assistant/jsonSafe.d.ts +13 -0
- package/lib/ai-assistant/jsonSafe.js +81 -0
- package/lib/app-manager/AdminizerApp.d.ts +40 -11
- package/lib/app-manager/AppManager.d.ts +2 -2
- package/lib/app-manager/AppManager.js +54 -50
- package/lib/app-manager/AssetHandler.d.ts +2 -2
- package/lib/app-manager/ConfigLayerHandler.d.ts +2 -2
- package/lib/app-manager/ControllerHandler.d.ts +2 -2
- package/lib/catalog/AbstractCatalog.d.ts +1 -1
- package/lib/catalog/CatalogHandler.d.ts +1 -1
- package/lib/controls/ControlsHandler.d.ts +1 -1
- package/lib/controls/codeEditor/MonacoEditor.d.ts +1 -1
- package/lib/controls/geojsoneditor/GeoEditor.d.ts +1 -1
- package/lib/controls/jsoneditor/JsonEditor.d.ts +1 -1
- package/lib/controls/markdown/ToastUiEditor.d.ts +1 -1
- package/lib/controls/table/Handsontable.d.ts +1 -1
- package/lib/controls/wysiwyg/CKeditor.d.ts +1 -1
- package/lib/docs/AbstractDocumentation.d.ts +100 -0
- package/lib/docs/AbstractDocumentation.js +108 -0
- package/lib/docs/DocumentationHandler.d.ts +100 -0
- package/lib/docs/DocumentationHandler.js +198 -0
- package/lib/docs/FileDocumentation.d.ts +47 -0
- package/lib/docs/FileDocumentation.js +189 -0
- package/lib/docs/docsAgentSkills.d.ts +10 -0
- package/lib/docs/docsAgentSkills.js +93 -0
- package/lib/feedback/FeedbackHandler.d.ts +2 -2
- package/lib/filters/ConditionValidator.d.ts +1 -1
- package/lib/filters/FilterCustomFieldHandler.d.ts +2 -2
- package/lib/filters/FilterService.d.ts +4 -4
- package/lib/filters/FilterService.js +4 -3
- package/lib/filters/index.d.ts +6 -6
- package/lib/history-actions/AbstractHistoryAdapter.d.ts +6 -6
- package/lib/history-actions/AbstractHistoryAdapter.js +11 -9
- package/lib/history-actions/DefaultHistoryAdapter.d.ts +4 -4
- package/lib/history-actions/HistoryHandler.d.ts +1 -1
- package/lib/list-query-builder/ListQueryBuilder.d.ts +7 -7
- package/lib/media-manager/AbstractMediaManager.d.ts +2 -2
- package/lib/media-manager/MediaManagerHandler.d.ts +1 -1
- package/lib/media-manager/Thumb.d.ts +1 -1
- package/lib/media-manager/helpers/MediaManagerHelper.d.ts +5 -5
- package/lib/model/AbstractModel.d.ts +11 -4
- package/lib/model/InternalModelAccessFactory.d.ts +2 -2
- package/lib/model/ModelHandler.d.ts +50 -5
- package/lib/model/ModelHandler.js +107 -8
- package/lib/model/adapter/sequelize.d.ts +7 -5
- package/lib/model/adapter/sequelize.js +79 -46
- package/lib/model/adapter/typeorm.d.ts +2 -2
- package/lib/model/adapter/typeorm.js +1 -1
- package/lib/model/internalModelRepository.d.ts +2 -2
- package/lib/notifications/AbstractNotificationService.d.ts +4 -4
- package/lib/notifications/GeneralNotificationService.d.ts +2 -2
- package/lib/notifications/GeneralNotificationService.js +6 -5
- package/lib/notifications/NotificationHandler.d.ts +2 -2
- package/lib/notifications/SystemNotificationService.d.ts +2 -2
- package/lib/notifications/SystemNotificationService.js +1 -1
- package/lib/requestProcessor.d.ts +1 -1
- package/lib/ui-i18n/uiTranslationKeys.d.ts +2 -1
- package/lib/ui-i18n/uiTranslationKeys.js +42 -1
- package/lib/widgets/abstractAction.d.ts +1 -1
- package/lib/widgets/abstractCustom.d.ts +2 -2
- package/lib/widgets/abstractInfo.d.ts +1 -1
- package/lib/widgets/abstractLink.d.ts +2 -2
- package/lib/widgets/abstractSwitch.d.ts +1 -1
- package/lib/widgets/abstractWidgetBase.d.ts +1 -1
- package/lib/widgets/widgetHandler.d.ts +9 -9
- package/lib/widgets/widgetHandler.js +5 -1
- package/models/Filter.d.ts +1 -1
- package/models/FilterColumn.d.ts +1 -1
- package/models/Group.d.ts +3 -2
- package/models/HistoryActions.d.ts +1 -1
- package/models/MediaManagerAP.d.ts +2 -2
- package/models/MediaManagerAssociationsAP.d.ts +1 -1
- package/models/MediaManagerMetaAP.d.ts +1 -1
- package/models/User.d.ts +2 -2
- package/models/UserNotification.d.ts +1 -1
- package/package.json +29 -20
- package/policies/authPolicies.js +6 -6
- package/policies/permissionResolvers.d.ts +1 -0
- package/policies/permissionResolvers.js +3 -0
- package/services/FeedService.d.ts +3 -3
- package/system/Router.d.ts +1 -1
- package/system/Router.js +27 -1
- package/system/bindAccessRights.d.ts +1 -1
- package/system/bindAccessRights.js +2 -3
- package/system/bindAuthorization.d.ts +1 -1
- package/system/bindControls.d.ts +1 -1
- package/system/bindCors.d.ts +1 -1
- package/system/bindCustomFilterHandlers.d.ts +1 -1
- package/system/bindDashboardWidgets.d.ts +1 -1
- package/system/bindDev.d.ts +1 -1
- package/system/bindDocs.d.ts +13 -0
- package/system/bindDocs.js +77 -0
- package/system/bindHistory.d.ts +1 -1
- package/system/bindInertia.d.ts +1 -1
- package/system/bindInertia.js +23 -1
- package/system/bindMediaManager.d.ts +1 -1
- package/system/bindNotifications.d.ts +1 -1
- package/system/bindReqFunctions.d.ts +1 -1
- package/system/bindTranslations.d.ts +1 -1
- package/system/buildInternalModelAccess.d.ts +3 -3
- package/system/buildInternalModelAccess.js +5 -2
- package/system/defaults.d.ts +1 -1
- package/system/routePolicies.d.ts +1 -1
- package/system/systemModelContracts.d.ts +1 -1
- package/system/systemModelContracts.js +11 -7
- package/system/validateSystemModels.d.ts +1 -1
- package/system/validateSystemModels.js +29 -9
- package/assets/add-group-BdHFzKng.js +0 -1
- package/assets/add-p6yGCoZ7.js +0 -1
- package/assets/add-user-CDspCLu4.js +0 -1
- package/assets/app-DNXiApCk.css +0 -1
- package/assets/app-layout-DCr_-JY3.js +0 -1
- package/assets/app.js +0 -138
- package/assets/catalog-XN-_p9TB.js +0 -6
- package/assets/dashboard-CHZO2QFY.js +0 -3
- package/assets/debounce-BMmXVQ06.js +0 -1
- package/assets/history-CQd0FojQ.js +0 -1
- package/assets/init-user-8aUohVDq.js +0 -1
- package/assets/list-PHWgTHsg.js +0 -4
- package/assets/login-CO4laf6m.js +0 -1
- package/assets/module-DVyQtIxm.js +0 -1
- package/assets/notification-B87XG8nQ.js +0 -20
- package/assets/register-DBYIMXGQ.js +0 -1
- package/assets/ru-rYKdQbWF.js +0 -1
- package/assets/use-appearance-_EbbjHTh.js +0 -1
- package/assets/user-filters-list-BRzDbPkx.js +0 -1
- package/assets/welcome-DiXmrTgc.js +0 -1
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { User } from '../../models/User.js';
|
|
2
|
+
import type { Adminizer } from '../Adminizer.js';
|
|
3
|
+
import type { AbstractDocumentation, DocContent, DocMeta, DocSearchQuery, DocSearchResult } from './AbstractDocumentation.js';
|
|
4
|
+
export declare const DOCUMENTATION_BASE_TOKEN = "read-documentation";
|
|
5
|
+
export declare const DOCUMENTATION_DEPARTMENT = "documentation";
|
|
6
|
+
/** Contextual table of contents handed to the UI as a shared Inertia prop. */
|
|
7
|
+
export interface DocContextSummary {
|
|
8
|
+
count: number;
|
|
9
|
+
items: Array<{
|
|
10
|
+
id: string;
|
|
11
|
+
title: string;
|
|
12
|
+
section?: string;
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The adminizer-side facade of the documentation subsystem: holds the single
|
|
17
|
+
* active `AbstractDocumentation` implementation and applies access rights to
|
|
18
|
+
* every result before it can reach the UI, the HTTP API or the assistant.
|
|
19
|
+
*
|
|
20
|
+
* Implementations know nothing about users; a document a user may not read
|
|
21
|
+
* simply does not exist for them — it is absent from listings, search results,
|
|
22
|
+
* the keyword map and link resolution.
|
|
23
|
+
*
|
|
24
|
+
* Per-document rights are declarative: the document author references an
|
|
25
|
+
* existing access token in the metadata (a model token like `read-Test-model`
|
|
26
|
+
* via `models`, or any custom token via `accessRightsToken`). The subsystem
|
|
27
|
+
* never creates tokens from documents — a referenced token that nobody
|
|
28
|
+
* registered simply keeps the document admin-only.
|
|
29
|
+
*/
|
|
30
|
+
export declare class DocumentationHandler {
|
|
31
|
+
private readonly adminizer;
|
|
32
|
+
private readonly bind?;
|
|
33
|
+
private service?;
|
|
34
|
+
private owner?;
|
|
35
|
+
private bound;
|
|
36
|
+
/**
|
|
37
|
+
* `bind` is the wiring Adminizer installs (token, navigation, skills, HTTP
|
|
38
|
+
* routes); it runs at most once, the moment the feature has both of its
|
|
39
|
+
* switches: `config.documentation.enabled` and a registered implementation.
|
|
40
|
+
*/
|
|
41
|
+
constructor(adminizer: Adminizer, bind?: () => void);
|
|
42
|
+
/** Base access token; without it the whole subsystem is invisible. */
|
|
43
|
+
get baseToken(): string;
|
|
44
|
+
get enabled(): boolean;
|
|
45
|
+
isRegistered(): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Activates the singleton implementation. Registering a second one is an
|
|
48
|
+
* error: replacing the active service must be an explicit `unregister`
|
|
49
|
+
* followed by a new `register`.
|
|
50
|
+
*
|
|
51
|
+
* With `config.documentation.enabled` set, the first registration also
|
|
52
|
+
* brings the subsystem up — token, navigation, skills and HTTP routes.
|
|
53
|
+
* None of it exists before that, so a missing implementation is
|
|
54
|
+
* indistinguishable from a disabled feature.
|
|
55
|
+
*/
|
|
56
|
+
register(service: AbstractDocumentation, owner?: string): void;
|
|
57
|
+
/**
|
|
58
|
+
* Runs the installed wiring once both switches of the feature hold. Called
|
|
59
|
+
* from `register()` and again from `Adminizer.init` — that second call
|
|
60
|
+
* covers a service registered before the config was read. Re-registering
|
|
61
|
+
* after an `unregister` reuses the wiring instead of mounting it twice.
|
|
62
|
+
*/
|
|
63
|
+
activate(): void;
|
|
64
|
+
unregister(owner?: string): boolean;
|
|
65
|
+
/** The locale documentation is served in when the consumer names none. */
|
|
66
|
+
localeFor(user?: User): string | undefined;
|
|
67
|
+
/** Metadata of every document this user may read. */
|
|
68
|
+
list(user: User, locale?: string): Promise<DocMeta[]>;
|
|
69
|
+
/** One document, or `undefined` when it does not exist for this user. */
|
|
70
|
+
get(user: User, id: string, locale?: string): Promise<DocContent | undefined>;
|
|
71
|
+
search(user: User, query: DocSearchQuery, locale?: string): Promise<DocSearchResult[]>;
|
|
72
|
+
/** Whether the subsystem is up and this user may read documentation at all. */
|
|
73
|
+
canRead(user?: User): Promise<boolean>;
|
|
74
|
+
/**
|
|
75
|
+
* The contextual table of contents of an admin page: metadata only, and
|
|
76
|
+
* `null` when the subsystem is off or the user may not read documentation.
|
|
77
|
+
* Shared with every page as a prop, so it never throws.
|
|
78
|
+
*/
|
|
79
|
+
forContextMeta(user: User, url: string, locale?: string): Promise<DocContextSummary | null>;
|
|
80
|
+
/** Documents bound to a place in the admin panel this user may read. */
|
|
81
|
+
forContext(user: User, ctx: {
|
|
82
|
+
url?: string;
|
|
83
|
+
model?: string;
|
|
84
|
+
}, locale?: string): Promise<DocMeta[]>;
|
|
85
|
+
/**
|
|
86
|
+
* Keyword → document ids over accessible documents only: keywords of a
|
|
87
|
+
* hidden document never reach the map, empty keywords are dropped.
|
|
88
|
+
*/
|
|
89
|
+
keywords(user: User, locale?: string): Promise<Map<string, string[]>>;
|
|
90
|
+
/** Resolves an in-document reference; a target this user may not read stays unresolved. */
|
|
91
|
+
resolveLink(user: User, fromId: string, ref: string): Promise<string | undefined>;
|
|
92
|
+
private requireService;
|
|
93
|
+
private permitted;
|
|
94
|
+
/**
|
|
95
|
+
* The access decision of the subsystem: the base token, then the per-doc
|
|
96
|
+
* token, then — for model-bound documents — read access to at least one of
|
|
97
|
+
* the models, with the same token DataAccessor gates the data itself.
|
|
98
|
+
*/
|
|
99
|
+
private allowed;
|
|
100
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
export const DOCUMENTATION_BASE_TOKEN = 'read-documentation';
|
|
2
|
+
export const DOCUMENTATION_DEPARTMENT = 'documentation';
|
|
3
|
+
/**
|
|
4
|
+
* The adminizer-side facade of the documentation subsystem: holds the single
|
|
5
|
+
* active `AbstractDocumentation` implementation and applies access rights to
|
|
6
|
+
* every result before it can reach the UI, the HTTP API or the assistant.
|
|
7
|
+
*
|
|
8
|
+
* Implementations know nothing about users; a document a user may not read
|
|
9
|
+
* simply does not exist for them — it is absent from listings, search results,
|
|
10
|
+
* the keyword map and link resolution.
|
|
11
|
+
*
|
|
12
|
+
* Per-document rights are declarative: the document author references an
|
|
13
|
+
* existing access token in the metadata (a model token like `read-Test-model`
|
|
14
|
+
* via `models`, or any custom token via `accessRightsToken`). The subsystem
|
|
15
|
+
* never creates tokens from documents — a referenced token that nobody
|
|
16
|
+
* registered simply keeps the document admin-only.
|
|
17
|
+
*/
|
|
18
|
+
export class DocumentationHandler {
|
|
19
|
+
adminizer;
|
|
20
|
+
bind;
|
|
21
|
+
service;
|
|
22
|
+
owner;
|
|
23
|
+
bound = false;
|
|
24
|
+
/**
|
|
25
|
+
* `bind` is the wiring Adminizer installs (token, navigation, skills, HTTP
|
|
26
|
+
* routes); it runs at most once, the moment the feature has both of its
|
|
27
|
+
* switches: `config.documentation.enabled` and a registered implementation.
|
|
28
|
+
*/
|
|
29
|
+
constructor(adminizer, bind) {
|
|
30
|
+
this.adminizer = adminizer;
|
|
31
|
+
this.bind = bind;
|
|
32
|
+
}
|
|
33
|
+
/** Base access token; without it the whole subsystem is invisible. */
|
|
34
|
+
get baseToken() {
|
|
35
|
+
return DOCUMENTATION_BASE_TOKEN;
|
|
36
|
+
}
|
|
37
|
+
get enabled() {
|
|
38
|
+
return this.adminizer.config.documentation?.enabled === true && Boolean(this.service);
|
|
39
|
+
}
|
|
40
|
+
isRegistered() {
|
|
41
|
+
return Boolean(this.service);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Activates the singleton implementation. Registering a second one is an
|
|
45
|
+
* error: replacing the active service must be an explicit `unregister`
|
|
46
|
+
* followed by a new `register`.
|
|
47
|
+
*
|
|
48
|
+
* With `config.documentation.enabled` set, the first registration also
|
|
49
|
+
* brings the subsystem up — token, navigation, skills and HTTP routes.
|
|
50
|
+
* None of it exists before that, so a missing implementation is
|
|
51
|
+
* indistinguishable from a disabled feature.
|
|
52
|
+
*/
|
|
53
|
+
register(service, owner = 'host') {
|
|
54
|
+
if (this.service) {
|
|
55
|
+
throw new Error(`Documentation service is already registered by "${this.owner}"`);
|
|
56
|
+
}
|
|
57
|
+
service.adminizer = this.adminizer;
|
|
58
|
+
this.service = service;
|
|
59
|
+
this.owner = owner;
|
|
60
|
+
this.activate();
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Runs the installed wiring once both switches of the feature hold. Called
|
|
64
|
+
* from `register()` and again from `Adminizer.init` — that second call
|
|
65
|
+
* covers a service registered before the config was read. Re-registering
|
|
66
|
+
* after an `unregister` reuses the wiring instead of mounting it twice.
|
|
67
|
+
*/
|
|
68
|
+
activate() {
|
|
69
|
+
if (this.bound || !this.bind || !this.service)
|
|
70
|
+
return;
|
|
71
|
+
if (this.adminizer.config?.documentation?.enabled !== true)
|
|
72
|
+
return;
|
|
73
|
+
this.bound = true;
|
|
74
|
+
this.bind();
|
|
75
|
+
}
|
|
76
|
+
unregister(owner) {
|
|
77
|
+
if (!this.service || (owner && this.owner !== owner))
|
|
78
|
+
return false;
|
|
79
|
+
this.service = undefined;
|
|
80
|
+
this.owner = undefined;
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
/** The locale documentation is served in when the consumer names none. */
|
|
84
|
+
localeFor(user) {
|
|
85
|
+
if (user?.locale)
|
|
86
|
+
return user.locale;
|
|
87
|
+
const translation = this.adminizer.config.translation;
|
|
88
|
+
return typeof translation === 'object' && translation ? translation.defaultLocale : undefined;
|
|
89
|
+
}
|
|
90
|
+
/** Metadata of every document this user may read. */
|
|
91
|
+
async list(user, locale) {
|
|
92
|
+
const service = this.requireService();
|
|
93
|
+
return this.permitted(user, await service.list(locale));
|
|
94
|
+
}
|
|
95
|
+
/** One document, or `undefined` when it does not exist for this user. */
|
|
96
|
+
async get(user, id, locale) {
|
|
97
|
+
const service = this.requireService();
|
|
98
|
+
const content = await service.get(id, locale);
|
|
99
|
+
if (!content || !(await this.allowed(user, content.meta)))
|
|
100
|
+
return undefined;
|
|
101
|
+
return content;
|
|
102
|
+
}
|
|
103
|
+
async search(user, query, locale) {
|
|
104
|
+
const service = this.requireService();
|
|
105
|
+
const results = await service.search(query, locale);
|
|
106
|
+
const filtered = [];
|
|
107
|
+
for (const result of results) {
|
|
108
|
+
if (await this.allowed(user, result.meta))
|
|
109
|
+
filtered.push(result);
|
|
110
|
+
}
|
|
111
|
+
return filtered;
|
|
112
|
+
}
|
|
113
|
+
/** Whether the subsystem is up and this user may read documentation at all. */
|
|
114
|
+
async canRead(user) {
|
|
115
|
+
if (!this.enabled || !user)
|
|
116
|
+
return false;
|
|
117
|
+
return Boolean(await this.adminizer.accessRightsHelper.hasPermission(this.baseToken, user));
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* The contextual table of contents of an admin page: metadata only, and
|
|
121
|
+
* `null` when the subsystem is off or the user may not read documentation.
|
|
122
|
+
* Shared with every page as a prop, so it never throws.
|
|
123
|
+
*/
|
|
124
|
+
async forContextMeta(user, url, locale) {
|
|
125
|
+
if (!(await this.canRead(user)))
|
|
126
|
+
return null;
|
|
127
|
+
const documents = await this.forContext(user, { url }, locale ?? this.localeFor(user));
|
|
128
|
+
return {
|
|
129
|
+
count: documents.length,
|
|
130
|
+
items: documents.map((meta) => ({ id: meta.id, title: meta.title, section: meta.section })),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/** Documents bound to a place in the admin panel this user may read. */
|
|
134
|
+
async forContext(user, ctx, locale) {
|
|
135
|
+
const service = this.requireService();
|
|
136
|
+
return this.permitted(user, await service.forContext(ctx, locale));
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Keyword → document ids over accessible documents only: keywords of a
|
|
140
|
+
* hidden document never reach the map, empty keywords are dropped.
|
|
141
|
+
*/
|
|
142
|
+
async keywords(user, locale) {
|
|
143
|
+
const service = this.requireService();
|
|
144
|
+
const visible = new Set((await this.list(user, locale)).map((meta) => meta.id));
|
|
145
|
+
const map = new Map();
|
|
146
|
+
for (const [keyword, ids] of await service.keywords(locale)) {
|
|
147
|
+
const permitted = ids.filter((id) => visible.has(id));
|
|
148
|
+
if (permitted.length > 0)
|
|
149
|
+
map.set(keyword, permitted);
|
|
150
|
+
}
|
|
151
|
+
return map;
|
|
152
|
+
}
|
|
153
|
+
/** Resolves an in-document reference; a target this user may not read stays unresolved. */
|
|
154
|
+
async resolveLink(user, fromId, ref) {
|
|
155
|
+
const service = this.requireService();
|
|
156
|
+
const target = await service.resolveLink(fromId, ref);
|
|
157
|
+
if (!target)
|
|
158
|
+
return undefined;
|
|
159
|
+
const metas = await service.list();
|
|
160
|
+
const meta = metas.find((candidate) => candidate.id === target);
|
|
161
|
+
if (!meta || !(await this.allowed(user, meta)))
|
|
162
|
+
return undefined;
|
|
163
|
+
return target;
|
|
164
|
+
}
|
|
165
|
+
requireService() {
|
|
166
|
+
if (!this.service)
|
|
167
|
+
throw new Error('No documentation service is registered');
|
|
168
|
+
return this.service;
|
|
169
|
+
}
|
|
170
|
+
async permitted(user, metas) {
|
|
171
|
+
const filtered = [];
|
|
172
|
+
for (const meta of metas) {
|
|
173
|
+
if (await this.allowed(user, meta))
|
|
174
|
+
filtered.push(meta);
|
|
175
|
+
}
|
|
176
|
+
return filtered;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* The access decision of the subsystem: the base token, then the per-doc
|
|
180
|
+
* token, then — for model-bound documents — read access to at least one of
|
|
181
|
+
* the models, with the same token DataAccessor gates the data itself.
|
|
182
|
+
*/
|
|
183
|
+
async allowed(user, meta) {
|
|
184
|
+
const has = (token) => this.adminizer.accessRightsHelper.hasPermission(token, user);
|
|
185
|
+
if (!(await has(this.baseToken)))
|
|
186
|
+
return false;
|
|
187
|
+
if (meta.accessRightsToken && !(await has(meta.accessRightsToken)))
|
|
188
|
+
return false;
|
|
189
|
+
if (meta.models.length > 0) {
|
|
190
|
+
for (const model of meta.models) {
|
|
191
|
+
if (await has(`read-${model}-model`))
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { AbstractDocumentation, DocContent, DocMeta } from './AbstractDocumentation.js';
|
|
2
|
+
export interface FileDocumentationOptions {
|
|
3
|
+
/** Directory scanned (recursively) for markdown documents. */
|
|
4
|
+
dir: string;
|
|
5
|
+
/** Locale of files without a locale suffix. @default "en" */
|
|
6
|
+
defaultLocale?: string;
|
|
7
|
+
/** Watch the directory and drop the cache on changes. @default false */
|
|
8
|
+
watch?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The reference implementation bundled with adminizer: documents are markdown
|
|
12
|
+
* files with a frontmatter header, translations are sibling files with a
|
|
13
|
+
* locale suffix (`intro.md` + `intro.ru.md` → document `intro` in two
|
|
14
|
+
* locales). Structural metadata (bindings, rights, section) is taken from the
|
|
15
|
+
* default-locale file; title and keywords are translatable per file.
|
|
16
|
+
*
|
|
17
|
+
* Frontmatter is an intentionally small `key: value` subset (quoted strings,
|
|
18
|
+
* `[a, b]` inline lists, `- item` dash lists), not full YAML.
|
|
19
|
+
*/
|
|
20
|
+
export declare class FileDocumentation extends AbstractDocumentation {
|
|
21
|
+
private readonly dir;
|
|
22
|
+
private readonly defaultLocale;
|
|
23
|
+
private cache?;
|
|
24
|
+
private readonly changeCallbacks;
|
|
25
|
+
private watcher?;
|
|
26
|
+
private watchDebounce?;
|
|
27
|
+
constructor(options: FileDocumentationOptions);
|
|
28
|
+
list(locale?: string): Promise<DocMeta[]>;
|
|
29
|
+
get(id: string, locale?: string): Promise<DocContent | undefined>;
|
|
30
|
+
onChange(cb: (ids?: string[]) => void): void;
|
|
31
|
+
/** Drops the cache; the next call rescans the directory. */
|
|
32
|
+
reload(): void;
|
|
33
|
+
stopWatcher(): void;
|
|
34
|
+
private startWatcher;
|
|
35
|
+
private scan;
|
|
36
|
+
private scanDirectory;
|
|
37
|
+
/** `<id>.md` is the default locale, `<id>.<locale>.md` a translation. */
|
|
38
|
+
private parseFile;
|
|
39
|
+
private buildMeta;
|
|
40
|
+
/** Exact locale, else the default locale, else whatever exists. */
|
|
41
|
+
private pickTranslation;
|
|
42
|
+
private splitFrontmatter;
|
|
43
|
+
private parseFrontmatter;
|
|
44
|
+
private stringList;
|
|
45
|
+
private unquote;
|
|
46
|
+
private extractTitle;
|
|
47
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { AbstractDocumentation } from './AbstractDocumentation.js';
|
|
4
|
+
/**
|
|
5
|
+
* The reference implementation bundled with adminizer: documents are markdown
|
|
6
|
+
* files with a frontmatter header, translations are sibling files with a
|
|
7
|
+
* locale suffix (`intro.md` + `intro.ru.md` → document `intro` in two
|
|
8
|
+
* locales). Structural metadata (bindings, rights, section) is taken from the
|
|
9
|
+
* default-locale file; title and keywords are translatable per file.
|
|
10
|
+
*
|
|
11
|
+
* Frontmatter is an intentionally small `key: value` subset (quoted strings,
|
|
12
|
+
* `[a, b]` inline lists, `- item` dash lists), not full YAML.
|
|
13
|
+
*/
|
|
14
|
+
export class FileDocumentation extends AbstractDocumentation {
|
|
15
|
+
dir;
|
|
16
|
+
defaultLocale;
|
|
17
|
+
cache;
|
|
18
|
+
changeCallbacks = [];
|
|
19
|
+
watcher;
|
|
20
|
+
watchDebounce;
|
|
21
|
+
constructor(options) {
|
|
22
|
+
super();
|
|
23
|
+
this.dir = path.resolve(options.dir);
|
|
24
|
+
this.defaultLocale = options.defaultLocale ?? 'en';
|
|
25
|
+
if (options.watch)
|
|
26
|
+
this.startWatcher();
|
|
27
|
+
}
|
|
28
|
+
async list(locale) {
|
|
29
|
+
const documents = await this.scan();
|
|
30
|
+
return [...documents.entries()].map(([id, translations]) => this.buildMeta(id, translations, locale));
|
|
31
|
+
}
|
|
32
|
+
async get(id, locale) {
|
|
33
|
+
const documents = await this.scan();
|
|
34
|
+
const translations = documents.get(id);
|
|
35
|
+
if (!translations)
|
|
36
|
+
return undefined;
|
|
37
|
+
const [servedLocale, file] = this.pickTranslation(translations, locale);
|
|
38
|
+
return {
|
|
39
|
+
meta: this.buildMeta(id, translations, locale),
|
|
40
|
+
markdown: file.markdown,
|
|
41
|
+
locale: servedLocale,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
onChange(cb) {
|
|
45
|
+
this.changeCallbacks.push(cb);
|
|
46
|
+
}
|
|
47
|
+
/** Drops the cache; the next call rescans the directory. */
|
|
48
|
+
reload() {
|
|
49
|
+
this.cache = undefined;
|
|
50
|
+
for (const cb of this.changeCallbacks)
|
|
51
|
+
cb();
|
|
52
|
+
}
|
|
53
|
+
stopWatcher() {
|
|
54
|
+
this.watcher?.close();
|
|
55
|
+
this.watcher = undefined;
|
|
56
|
+
if (this.watchDebounce)
|
|
57
|
+
clearTimeout(this.watchDebounce);
|
|
58
|
+
}
|
|
59
|
+
startWatcher() {
|
|
60
|
+
try {
|
|
61
|
+
this.watcher = fs.watch(this.dir, { recursive: true }, () => {
|
|
62
|
+
if (this.watchDebounce)
|
|
63
|
+
clearTimeout(this.watchDebounce);
|
|
64
|
+
this.watchDebounce = setTimeout(() => this.reload(), 200);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// A missing directory or an unsupported platform degrades to a static implementation.
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
scan() {
|
|
72
|
+
this.cache ??= this.scanDirectory();
|
|
73
|
+
return this.cache;
|
|
74
|
+
}
|
|
75
|
+
async scanDirectory() {
|
|
76
|
+
const documents = new Map();
|
|
77
|
+
let files;
|
|
78
|
+
try {
|
|
79
|
+
files = (await fs.promises.readdir(this.dir, { recursive: true, withFileTypes: true }))
|
|
80
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith('.md'))
|
|
81
|
+
.map((entry) => path.join(entry.parentPath ?? entry.path, entry.name));
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return documents;
|
|
85
|
+
}
|
|
86
|
+
for (const file of files) {
|
|
87
|
+
const parsed = this.parseFile(await fs.promises.readFile(file, 'utf8'), path.basename(file));
|
|
88
|
+
if (!parsed)
|
|
89
|
+
continue;
|
|
90
|
+
const { id, doc } = parsed;
|
|
91
|
+
const translations = documents.get(id) ?? new Map();
|
|
92
|
+
translations.set(doc.locale, doc);
|
|
93
|
+
documents.set(id, translations);
|
|
94
|
+
}
|
|
95
|
+
return documents;
|
|
96
|
+
}
|
|
97
|
+
/** `<id>.md` is the default locale, `<id>.<locale>.md` a translation. */
|
|
98
|
+
parseFile(raw, filename) {
|
|
99
|
+
const base = filename.replace(/\.md$/, '');
|
|
100
|
+
const localeMatch = base.match(/^(.+)\.([a-z]{2}(?:-[A-Za-z]{2})?)$/);
|
|
101
|
+
const fromName = localeMatch ? { id: localeMatch[1], locale: localeMatch[2] } : { id: base, locale: this.defaultLocale };
|
|
102
|
+
const { front, body } = this.splitFrontmatter(raw);
|
|
103
|
+
const id = front.id || fromName.id;
|
|
104
|
+
if (!id)
|
|
105
|
+
return undefined;
|
|
106
|
+
return {
|
|
107
|
+
id,
|
|
108
|
+
doc: {
|
|
109
|
+
locale: front.locale || fromName.locale,
|
|
110
|
+
title: front.title || this.extractTitle(body) || id,
|
|
111
|
+
keywords: this.stringList(front.keywords),
|
|
112
|
+
section: front.section || undefined,
|
|
113
|
+
models: this.stringList(front.models),
|
|
114
|
+
urls: this.stringList(front.urls),
|
|
115
|
+
accessRightsToken: front.accessRightsToken || undefined,
|
|
116
|
+
markdown: body.trim(),
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
buildMeta(id, translations, locale) {
|
|
121
|
+
const [, localized] = this.pickTranslation(translations, locale);
|
|
122
|
+
const structural = translations.get(this.defaultLocale) ?? localized;
|
|
123
|
+
return {
|
|
124
|
+
id,
|
|
125
|
+
title: localized.title,
|
|
126
|
+
section: structural.section,
|
|
127
|
+
keywords: localized.keywords,
|
|
128
|
+
models: structural.models,
|
|
129
|
+
urls: structural.urls,
|
|
130
|
+
accessRightsToken: structural.accessRightsToken,
|
|
131
|
+
locales: [...translations.keys()],
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/** Exact locale, else the default locale, else whatever exists. */
|
|
135
|
+
pickTranslation(translations, locale) {
|
|
136
|
+
for (const wanted of [locale, this.defaultLocale]) {
|
|
137
|
+
const file = wanted ? translations.get(wanted) : undefined;
|
|
138
|
+
if (file)
|
|
139
|
+
return [wanted, file];
|
|
140
|
+
}
|
|
141
|
+
const [fallbackLocale, file] = [...translations.entries()][0];
|
|
142
|
+
return [fallbackLocale, file];
|
|
143
|
+
}
|
|
144
|
+
splitFrontmatter(raw) {
|
|
145
|
+
const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
|
|
146
|
+
if (!match)
|
|
147
|
+
return { front: {}, body: raw };
|
|
148
|
+
return { front: this.parseFrontmatter(match[1]), body: raw.slice(match[0].length) };
|
|
149
|
+
}
|
|
150
|
+
parseFrontmatter(block) {
|
|
151
|
+
const front = {};
|
|
152
|
+
let listKey;
|
|
153
|
+
for (const line of block.split('\n')) {
|
|
154
|
+
const dashItem = line.match(/^\s+-\s+(.*)$/);
|
|
155
|
+
if (dashItem && listKey) {
|
|
156
|
+
front[listKey].push(this.unquote(dashItem[1]));
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
const pair = line.match(/^([A-Za-z][A-Za-z0-9_-]*):\s*(.*)$/);
|
|
160
|
+
if (!pair)
|
|
161
|
+
continue;
|
|
162
|
+
const [, key, value] = pair;
|
|
163
|
+
if (value === '') {
|
|
164
|
+
front[key] = [];
|
|
165
|
+
listKey = key;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
listKey = undefined;
|
|
169
|
+
const inlineList = value.match(/^\[(.*)]$/);
|
|
170
|
+
front[key] = inlineList
|
|
171
|
+
? inlineList[1].split(',').map((item) => this.unquote(item)).filter(Boolean)
|
|
172
|
+
: this.unquote(value);
|
|
173
|
+
}
|
|
174
|
+
return front;
|
|
175
|
+
}
|
|
176
|
+
stringList(value) {
|
|
177
|
+
if (Array.isArray(value))
|
|
178
|
+
return value.map(String).filter(Boolean);
|
|
179
|
+
if (typeof value === 'string' && value)
|
|
180
|
+
return [value];
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
unquote(value) {
|
|
184
|
+
return value.trim().replace(/^(["'])(.*)\1$/, '$2');
|
|
185
|
+
}
|
|
186
|
+
extractTitle(body) {
|
|
187
|
+
return body.match(/^#\s+(.+)$/m)?.[1].trim();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Adminizer } from '../Adminizer.js';
|
|
2
|
+
import type { AiAssistantAgentSkill } from '../ai-assistant/AiAssistantAgentSkillHandler.js';
|
|
3
|
+
/**
|
|
4
|
+
* Documentation skills of the assistant. Every call goes through
|
|
5
|
+
* `DocumentationHandler`, so the agent reads documentation with exactly the
|
|
6
|
+
* permissions of the user it acts for; a document the user may not read does
|
|
7
|
+
* not exist for the agent either. Registered only when the documentation
|
|
8
|
+
* subsystem is enabled.
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildDocumentationAgentSkills(adminizer: Adminizer): AiAssistantAgentSkill[];
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
function describeMeta(meta) {
|
|
2
|
+
return {
|
|
3
|
+
id: meta.id,
|
|
4
|
+
title: meta.title,
|
|
5
|
+
section: meta.section,
|
|
6
|
+
keywords: meta.keywords.length ? meta.keywords : undefined,
|
|
7
|
+
models: meta.models.length ? meta.models : undefined,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Documentation skills of the assistant. Every call goes through
|
|
12
|
+
* `DocumentationHandler`, so the agent reads documentation with exactly the
|
|
13
|
+
* permissions of the user it acts for; a document the user may not read does
|
|
14
|
+
* not exist for the agent either. Registered only when the documentation
|
|
15
|
+
* subsystem is enabled.
|
|
16
|
+
*/
|
|
17
|
+
export function buildDocumentationAgentSkills(adminizer) {
|
|
18
|
+
const handler = () => adminizer.documentationHandler;
|
|
19
|
+
const token = adminizer.documentationHandler.baseToken;
|
|
20
|
+
const listKeywords = {
|
|
21
|
+
id: 'list_documentation_keywords',
|
|
22
|
+
description: 'Map of documentation keywords to the ids of the admin-panel documentation the current user may read.'
|
|
23
|
+
+ ' Use it to discover what topics are covered before searching.',
|
|
24
|
+
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
25
|
+
accessRightsToken: token,
|
|
26
|
+
execute: async (_input, { user }) => {
|
|
27
|
+
const keywords = await handler().keywords(user, handler().localeFor(user));
|
|
28
|
+
return { keywords: Object.fromEntries(keywords) };
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
const search = {
|
|
32
|
+
id: 'search_documentation',
|
|
33
|
+
description: 'Search the admin-panel documentation the current user may read: full-text query, keyword filters,'
|
|
34
|
+
+ ' or scoping to an admin URL or a data model. Returns matching documents with snippets;'
|
|
35
|
+
+ ' read a whole document with read_documentation.',
|
|
36
|
+
inputSchema: {
|
|
37
|
+
type: 'object',
|
|
38
|
+
properties: {
|
|
39
|
+
query: { type: 'string', description: 'Full-text query matched against titles, keywords and content.' },
|
|
40
|
+
keywords: {
|
|
41
|
+
type: 'array', items: { type: 'string' },
|
|
42
|
+
description: 'Keywords the documents must all have, as returned by list_documentation_keywords.',
|
|
43
|
+
},
|
|
44
|
+
url: { type: 'string', description: 'Admin path to scope to, e.g. the page the user is looking at.' },
|
|
45
|
+
model: { type: 'string', description: 'Data model name to scope to.' },
|
|
46
|
+
},
|
|
47
|
+
additionalProperties: false,
|
|
48
|
+
},
|
|
49
|
+
accessRightsToken: token,
|
|
50
|
+
execute: async (input, { user }) => {
|
|
51
|
+
const results = await handler().search(user, {
|
|
52
|
+
query: typeof input.query === 'string' ? input.query : undefined,
|
|
53
|
+
keywords: Array.isArray(input.keywords) ? input.keywords.map(String) : undefined,
|
|
54
|
+
url: typeof input.url === 'string' ? input.url : undefined,
|
|
55
|
+
model: typeof input.model === 'string' ? input.model : undefined,
|
|
56
|
+
}, handler().localeFor(user));
|
|
57
|
+
return {
|
|
58
|
+
results: results.map((result) => ({
|
|
59
|
+
...describeMeta(result.meta),
|
|
60
|
+
snippets: result.snippets.length ? result.snippets : undefined,
|
|
61
|
+
})),
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
const read = {
|
|
66
|
+
id: 'read_documentation',
|
|
67
|
+
description: 'Read one admin-panel documentation document, in the locale of the current user,'
|
|
68
|
+
+ ' by the id returned from search_documentation or list_documentation_keywords.',
|
|
69
|
+
inputSchema: {
|
|
70
|
+
type: 'object',
|
|
71
|
+
properties: {
|
|
72
|
+
id: { type: 'string', description: 'Document id.' },
|
|
73
|
+
},
|
|
74
|
+
required: ['id'],
|
|
75
|
+
additionalProperties: false,
|
|
76
|
+
},
|
|
77
|
+
accessRightsToken: token,
|
|
78
|
+
execute: async (input, { user }) => {
|
|
79
|
+
const id = String(input.id ?? '').trim();
|
|
80
|
+
if (!id)
|
|
81
|
+
throw new Error('id of the document to read is required');
|
|
82
|
+
const content = await handler().get(user, id, handler().localeFor(user));
|
|
83
|
+
if (!content)
|
|
84
|
+
throw new Error(`Document "${id}" is not available.`);
|
|
85
|
+
return {
|
|
86
|
+
...describeMeta(content.meta),
|
|
87
|
+
locale: content.locale,
|
|
88
|
+
markdown: content.markdown,
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
return [listKeywords, search, read];
|
|
93
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Adminizer } from '../Adminizer';
|
|
2
|
-
import { AbstractFeedbackHandler } from './AbstractFeedbackHandler';
|
|
1
|
+
import { Adminizer } from '../Adminizer.js';
|
|
2
|
+
import { AbstractFeedbackHandler } from './AbstractFeedbackHandler.js';
|
|
3
3
|
export declare class FeedbackHandler {
|
|
4
4
|
private handler;
|
|
5
5
|
private adminizer;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from './CustomFilterHandler';
|
|
2
|
-
export { default } from './CustomFilterHandler';
|
|
1
|
+
export * from './CustomFilterHandler.js';
|
|
2
|
+
export { default } from './CustomFilterHandler.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Adminizer } from '../Adminizer';
|
|
2
|
-
import { Filter } from '../../models/Filter';
|
|
3
|
-
import { FilterColumn } from '../../models/FilterColumn';
|
|
4
|
-
import { User } from '../../models/User';
|
|
1
|
+
import { Adminizer } from '../Adminizer.js';
|
|
2
|
+
import { Filter } from '../../models/Filter.js';
|
|
3
|
+
import { FilterColumn } from '../../models/FilterColumn.js';
|
|
4
|
+
import { User } from '../../models/User.js';
|
|
5
5
|
/**
|
|
6
6
|
* FilterService - manages filter operations
|
|
7
7
|
*
|