From 91f27a59792eedd456d5811f0f1fe1c81bc7dbeb Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Thu, 26 Feb 2026 07:55:03 +0100 Subject: [PATCH 01/17] chore: Switch license to EUPL-1.2 Replace existing license (Apache-2.0/AGPL) with EUPL-1.2 across all metadata files: LICENSE, appinfo/info.xml, composer.json, package.json. Co-Authored-By: Claude Opus 4.6 --- LICENSE | 391 +++++++++++++++++++++++------------------------ appinfo/info.xml | 2 +- composer.json | 2 +- package.json | 2 +- 4 files changed, 193 insertions(+), 204 deletions(-) diff --git a/LICENSE b/LICENSE index 261eeb9e9..6d8cea430 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,190 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +EUROPEAN UNION PUBLIC LICENCE v. 1.2 +EUPL © the European Union 2007, 2016 + +This European Union Public Licence (the ‘EUPL’) applies to the Work (as defined below) which is provided under the +terms of this Licence. Any use of the Work, other than as authorised under this Licence is prohibited (to the extent such +use is covered by a right of the copyright holder of the Work). +The Work is provided under the terms of this Licence when the Licensor (as defined below) has placed the following +notice immediately following the copyright notice for the Work: + Licensed under the EUPL +or has expressed by any other means his willingness to license under the EUPL. + +1.Definitions +In this Licence, the following terms have the following meaning: +— ‘The Licence’:this Licence. +— ‘The Original Work’:the work or software distributed or communicated by the Licensor under this Licence, available +as Source Code and also as Executable Code as the case may be. +— ‘Derivative Works’:the works or software that could be created by the Licensee, based upon the Original Work or +modifications thereof. This Licence does not define the extent of modification or dependence on the Original Work +required in order to classify a work as a Derivative Work; this extent is determined by copyright law applicable in +the country mentioned in Article 15. +— ‘The Work’:the Original Work or its Derivative Works. +— ‘The Source Code’:the human-readable form of the Work which is the most convenient for people to study and +modify. +— ‘The Executable Code’:any code which has generally been compiled and which is meant to be interpreted by +a computer as a program. +— ‘The Licensor’:the natural or legal person that distributes or communicates the Work under the Licence. +— ‘Contributor(s)’:any natural or legal person who modifies the Work under the Licence, or otherwise contributes to +the creation of a Derivative Work. +— ‘The Licensee’ or ‘You’:any natural or legal person who makes any usage of the Work under the terms of the +Licence. +— ‘Distribution’ or ‘Communication’:any act of selling, giving, lending, renting, distributing, communicating, +transmitting, or otherwise making available, online or offline, copies of the Work or providing access to its essential +functionalities at the disposal of any other natural or legal person. + +2.Scope of the rights granted by the Licence +The Licensor hereby grants You a worldwide, royalty-free, non-exclusive, sublicensable licence to do the following, for +the duration of copyright vested in the Original Work: +— use the Work in any circumstance and for all usage, +— reproduce the Work, +— modify the Work, and make Derivative Works based upon the Work, +— communicate to the public, including the right to make available or display the Work or copies thereof to the public +and perform publicly, as the case may be, the Work, +— distribute the Work or copies thereof, +— lend and rent the Work or copies thereof, +— sublicense rights in the Work or copies thereof. +Those rights can be exercised on any media, supports and formats, whether now known or later invented, as far as the +applicable law permits so. +In the countries where moral rights apply, the Licensor waives his right to exercise his moral right to the extent allowed +by law in order to make effective the licence of the economic rights here above listed. +The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to any patents held by the Licensor, to the +extent necessary to make use of the rights granted on the Work under this Licence. + +3.Communication of the Source Code +The Licensor may provide the Work either in its Source Code form, or as Executable Code. If the Work is provided as +Executable Code, the Licensor provides in addition a machine-readable copy of the Source Code of the Work along with +each copy of the Work that the Licensor distributes or indicates, in a notice following the copyright notice attached to +the Work, a repository where the Source Code is easily and freely accessible for as long as the Licensor continues to +distribute or communicate the Work. + +4.Limitations on copyright +Nothing in this Licence is intended to deprive the Licensee of the benefits from any exception or limitation to the +exclusive rights of the rights owners in the Work, of the exhaustion of those rights or of other applicable limitations +thereto. + +5.Obligations of the Licensee +The grant of the rights mentioned above is subject to some restrictions and obligations imposed on the Licensee. Those +obligations are the following: + +Attribution right: The Licensee shall keep intact all copyright, patent or trademarks notices and all notices that refer to +the Licence and to the disclaimer of warranties. The Licensee must include a copy of such notices and a copy of the +Licence with every copy of the Work he/she distributes or communicates. The Licensee must cause any Derivative Work +to carry prominent notices stating that the Work has been modified and the date of modification. + +Copyleft clause: If the Licensee distributes or communicates copies of the Original Works or Derivative Works, this +Distribution or Communication will be done under the terms of this Licence or of a later version of this Licence unless +the Original Work is expressly distributed only under this version of the Licence — for example by communicating +‘EUPL v. 1.2 only’. The Licensee (becoming Licensor) cannot offer or impose any additional terms or conditions on the +Work or Derivative Work that alter or restrict the terms of the Licence. + +Compatibility clause: If the Licensee Distributes or Communicates Derivative Works or copies thereof based upon both +the Work and another work licensed under a Compatible Licence, this Distribution or Communication can be done +under the terms of this Compatible Licence. For the sake of this clause, ‘Compatible Licence’ refers to the licences listed +in the appendix attached to this Licence. Should the Licensee's obligations under the Compatible Licence conflict with +his/her obligations under this Licence, the obligations of the Compatible Licence shall prevail. + +Provision of Source Code: When distributing or communicating copies of the Work, the Licensee will provide +a machine-readable copy of the Source Code or indicate a repository where this Source will be easily and freely available +for as long as the Licensee continues to distribute or communicate the Work. +Legal Protection: This Licence does not grant permission to use the trade names, trademarks, service marks, or names +of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and +reproducing the content of the copyright notice. + +6.Chain of Authorship +The original Licensor warrants that the copyright in the Original Work granted hereunder is owned by him/her or +licensed to him/her and that he/she has the power and authority to grant the Licence. +Each Contributor warrants that the copyright in the modifications he/she brings to the Work are owned by him/her or +licensed to him/her and that he/she has the power and authority to grant the Licence. +Each time You accept the Licence, the original Licensor and subsequent Contributors grant You a licence to their contributions +to the Work, under the terms of this Licence. + +7.Disclaimer of Warranty +The Work is a work in progress, which is continuously improved by numerous Contributors. It is not a finished work +and may therefore contain defects or ‘bugs’ inherent to this type of development. +For the above reason, the Work is provided under the Licence on an ‘as is’ basis and without warranties of any kind +concerning the Work, including without limitation merchantability, fitness for a particular purpose, absence of defects or +errors, accuracy, non-infringement of intellectual property rights other than copyright as stated in Article 6 of this +Licence. +This disclaimer of warranty is an essential part of the Licence and a condition for the grant of any rights to the Work. + +8.Disclaimer of Liability +Except in the cases of wilful misconduct or damages directly caused to natural persons, the Licensor will in no event be +liable for any direct or indirect, material or moral, damages of any kind, arising out of the Licence or of the use of the +Work, including without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, loss +of data or any commercial damage, even if the Licensor has been advised of the possibility of such damage. However, +the Licensor will be liable under statutory product liability laws as far such laws apply to the Work. + +9.Additional agreements +While distributing the Work, You may choose to conclude an additional agreement, defining obligations or services +consistent with this Licence. However, if accepting obligations, You may act only on your own behalf and on your sole +responsibility, not on behalf of the original Licensor or any other Contributor, and only if You agree to indemnify, +defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against such Contributor by +the fact You have accepted any warranty or additional liability. + +10.Acceptance of the Licence +The provisions of this Licence can be accepted by clicking on an icon ‘I agree’ placed under the bottom of a window +displaying the text of this Licence or by affirming consent in any other similar way, in accordance with the rules of +applicable law. Clicking on that icon indicates your clear and irrevocable acceptance of this Licence and all of its terms +and conditions. +Similarly, you irrevocably accept this Licence and all of its terms and conditions by exercising any rights granted to You +by Article 2 of this Licence, such as the use of the Work, the creation by You of a Derivative Work or the Distribution +or Communication by You of the Work or copies thereof. + +11.Information to the public +In case of any Distribution or Communication of the Work by means of electronic communication by You (for example, +by offering to download the Work from a remote location) the distribution channel or media (for example, a website) +must at least provide to the public the information requested by the applicable law regarding the Licensor, the Licence +and the way it may be accessible, concluded, stored and reproduced by the Licensee. + +12.Termination of the Licence +The Licence and the rights granted hereunder will terminate automatically upon any breach by the Licensee of the terms +of the Licence. +Such a termination will not terminate the licences of any person who has received the Work from the Licensee under +the Licence, provided such persons remain in full compliance with the Licence. + +13.Miscellaneous +Without prejudice of Article 9 above, the Licence represents the complete agreement between the Parties as to the +Work. +If any provision of the Licence is invalid or unenforceable under applicable law, this will not affect the validity or +enforceability of the Licence as a whole. Such provision will be construed or reformed so as necessary to make it valid +and enforceable. +The European Commission may publish other linguistic versions or new versions of this Licence or updated versions of +the Appendix, so far this is required and reasonable, without reducing the scope of the rights granted by the Licence. +New versions of the Licence will be published with a unique version number. +All linguistic versions of this Licence, approved by the European Commission, have identical value. Parties can take +advantage of the linguistic version of their choice. + +14.Jurisdiction +Without prejudice to specific agreement between parties, +— any litigation resulting from the interpretation of this License, arising between the European Union institutions, +bodies, offices or agencies, as a Licensor, and any Licensee, will be subject to the jurisdiction of the Court of Justice +of the European Union, as laid down in article 272 of the Treaty on the Functioning of the European Union, +— any litigation arising between other parties and resulting from the interpretation of this License, will be subject to +the exclusive jurisdiction of the competent court where the Licensor resides or conducts its primary business. + +15.Applicable Law +Without prejudice to specific agreement between parties, +— this Licence shall be governed by the law of the European Union Member State where the Licensor has his seat, +resides or has his registered office, +— this licence shall be governed by Belgian law if the Licensor has no seat, residence or registered office inside +a European Union Member State. + + + Appendix + +‘Compatible Licences’ according to Article 5 EUPL are: +— GNU General Public License (GPL) v. 2, v. 3 +— GNU Affero General Public License (AGPL) v. 3 +— Open Software License (OSL) v. 2.1, v. 3.0 +— Eclipse Public License (EPL) v. 1.0 +— CeCILL v. 2.0, v. 2.1 +— Mozilla Public Licence (MPL) v. 2 +— GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3 +— Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for works other than software +— European Union Public Licence (EUPL) v. 1.1, v. 1.2 +— Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong Reciprocity (LiLiQ-R+). + +The European Commission may update this Appendix to later versions of the above licences without producing +a new version of the EUPL, as long as they provide the rights granted in Article 2 of this Licence and protect the +covered Source Code from exclusive appropriation. +All other changes or additions to this Appendix require the production of a new EUPL version. diff --git a/appinfo/info.xml b/appinfo/info.xml index 513db5ab1..4fa83643f 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -14,7 +14,7 @@ The OpenConnector Nextcloud app provides a ESB-framework to work together in an ]]> 0.2.8 - agpl + EUPL-1.2 integration Conduction OpenConnector diff --git a/composer.json b/composer.json index d371f0cc8..18e223bc2 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "conductionnl/openconnector", "description": "Gateway and Service bus funtionality", - "license": "AGPL-3.0-or-later", + "license": "EUPL-1.2", "authors": [ { "name": "Conduction b.v.", diff --git a/package.json b/package.json index d886a92f6..830557c72 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "openconnector", "version": "1.0.0", - "license": "AGPL-3.0-or-later", + "license": "EUPL-1.2", "engines": { "node": "^20.0.0", "npm": "^10.0.0" From f5c2113e4ded791be992e030385d686f6d8f938b Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Mon, 2 Mar 2026 00:24:06 +0100 Subject: [PATCH 02/17] fix: Revert licence to agpl for Nextcloud App Store compatibility The Nextcloud App Store schema does not accept EUPL-1.2 as a valid licence value, causing all release uploads to fail with HTTP 400. Revert to 'agpl' which is in the accepted set. Co-Authored-By: Claude Opus 4.6 --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 4fa83643f..513db5ab1 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -14,7 +14,7 @@ The OpenConnector Nextcloud app provides a ESB-framework to work together in an ]]> 0.2.8 - EUPL-1.2 + agpl integration Conduction OpenConnector From e5aede3915918046d856b3b7de593982644e22e0 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Tue, 3 Mar 2026 13:29:54 +0100 Subject: [PATCH 03/17] refactor: Move website/ to docusaurus/ pattern (docs/ at root) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename website/ → docusaurus/ (preserves git history) - Move website/docs/ → docs/ at repository root - Existing docs/ merged with conflict resolution (legacy suffixes) - Update docusaurus.config.js: path '../docs', editUrl → docusaurus/ - Update documentation.yml: trigger development branch, source-folder docusaurus - Create img/app-store.svg with blue hexagon connection icon - Update logo.svg with blue hexagon app-store icon Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/documentation.yml | 12 +- .../README.md | 0 .../cloudevents.md | 0 .../consumer.md | 0 .../mapping/README.md | 0 .../mapping/mapping.md | 0 .../sources/README.md | 0 docs/administrators-legacy/sources/source.md | 75 ++++ .../sources/xxllnctopublication.md | 0 .../synchronysation/README.md | 0 .../synchronysation/contract.md | 0 .../synchronysation/synchronysation.md | 0 .../vault/README.md | 0 .../vault/applications.md | 0 .../vault/authentication.md | 0 .../docs => docs}/administrators/logging.md | 0 docs/administrators/sources/source.md | 217 ++++++++--- {website/docs => docs}/async-file-fetching.md | 0 {website/docs => docs}/configurations.md | 0 .../diagrams/object-deletion-states.puml | 0 .../diagrams/object-deletion-states.svg | 0 .../diagrams/object-locking-sequence.puml | 0 .../diagrams/object-locking-sequence.svg | 0 .../examples/parallel-timing-example.md | 0 {website/docs => docs}/flow-token.md | 0 {website/docs => docs}/image.png | Bin {website/docs => docs}/intro.md | 0 {website/docs => docs}/origin-id.png | Bin .../docs => docs}/parallel-page-fetching.md | 0 {website/docs => docs}/quality-assurance.md | 0 {website/docs => docs}/rule-tags.png | Bin {website/docs => docs}/rules.md | 0 .../docs => docs}/security-best-practices.md | 0 {website/docs => docs}/sources.md | 0 {website/docs => docs}/sub-objects.png | Bin docs/synchronization-legacy.md | 39 ++ .../docs => docs}/synchronization-timing.md | 0 docs/synchronization.md | 349 ++++++++++++++++-- .../tutorial/endpoint-example.png | Bin {website/docs => docs}/tutorial/endpoints.md | 0 .../tutorial/example-consumer.png | Bin .../docs => docs}/tutorial/installation.md | 0 {website/docs => docs}/tutorial/jobs.md | 0 {website/docs => docs}/tutorial/mappings.md | 0 .../tutorial/postman-example.png | Bin {website/docs => docs}/tutorial/sources.md | 0 .../docs => docs}/user-api-quick-reference.md | 0 {website/docs => docs}/user-api-setup.md | 0 {website/docs => docs}/user-api.md | 0 {website => docusaurus}/.gitignore | 0 {website => docusaurus}/README.md | 0 .../blog/2019-05-28-first-blog-post.md | 0 .../blog/2019-05-29-long-blog-post.md | 0 .../blog/2021-08-01-mdx-blog-post.mdx | 0 .../docusaurus-plushie-banner.jpeg | Bin .../blog/2021-08-26-welcome/index.md | 0 {website => docusaurus}/blog/authors.yml | 0 {website => docusaurus}/blog/tags.yml | 0 {website => docusaurus}/docusaurus.config.js | 3 +- {website => docusaurus}/package-lock.json | 0 {website => docusaurus}/package.json | 0 {website => docusaurus}/sidebars.js | 0 .../src/components/HomepageFeatures/index.js | 0 .../HomepageFeatures/styles.module.css | 0 {website => docusaurus}/src/css/custom.css | 0 {website => docusaurus}/src/pages/index.js | 0 .../src/pages/index.module.css | 0 .../src/pages/markdown-page.md | 0 {website => docusaurus}/static/.nojekyll | 0 .../static/img/docusaurus-social-card.jpg | Bin .../static/img/docusaurus.png | Bin .../static/img/favicon.ico | Bin docusaurus/static/img/logo.svg | 6 + .../static/img/undraw_docusaurus_mountain.svg | 0 .../static/img/undraw_docusaurus_react.svg | 0 .../static/img/undraw_docusaurus_tree.svg | 0 img/app-store.svg | 6 + website/docs/administrators/sources/source.md | 208 ----------- website/docs/synchronization.md | 336 ----------------- website/static/img/logo.svg | 1 - 80 files changed, 632 insertions(+), 620 deletions(-) rename docs/{administrators => administrators-legacy}/README.md (100%) rename docs/{administrators => administrators-legacy}/cloudevents.md (100%) rename docs/{administrators => administrators-legacy}/consumer.md (100%) rename docs/{administrators => administrators-legacy}/mapping/README.md (100%) rename docs/{administrators => administrators-legacy}/mapping/mapping.md (100%) rename docs/{administrators => administrators-legacy}/sources/README.md (100%) create mode 100644 docs/administrators-legacy/sources/source.md rename docs/{administrators => administrators-legacy}/sources/xxllnctopublication.md (100%) rename docs/{administrators => administrators-legacy}/synchronysation/README.md (100%) rename docs/{administrators => administrators-legacy}/synchronysation/contract.md (100%) rename docs/{administrators => administrators-legacy}/synchronysation/synchronysation.md (100%) rename docs/{administrators => administrators-legacy}/vault/README.md (100%) rename docs/{administrators => administrators-legacy}/vault/applications.md (100%) rename docs/{administrators => administrators-legacy}/vault/authentication.md (100%) rename {website/docs => docs}/administrators/logging.md (100%) rename {website/docs => docs}/async-file-fetching.md (100%) rename {website/docs => docs}/configurations.md (100%) rename {website/docs => docs}/diagrams/object-deletion-states.puml (100%) rename {website/docs => docs}/diagrams/object-deletion-states.svg (100%) rename {website/docs => docs}/diagrams/object-locking-sequence.puml (100%) rename {website/docs => docs}/diagrams/object-locking-sequence.svg (100%) rename {website/docs => docs}/examples/parallel-timing-example.md (100%) rename {website/docs => docs}/flow-token.md (100%) rename {website/docs => docs}/image.png (100%) rename {website/docs => docs}/intro.md (100%) rename {website/docs => docs}/origin-id.png (100%) rename {website/docs => docs}/parallel-page-fetching.md (100%) rename {website/docs => docs}/quality-assurance.md (100%) rename {website/docs => docs}/rule-tags.png (100%) rename {website/docs => docs}/rules.md (100%) rename {website/docs => docs}/security-best-practices.md (100%) rename {website/docs => docs}/sources.md (100%) rename {website/docs => docs}/sub-objects.png (100%) create mode 100644 docs/synchronization-legacy.md rename {website/docs => docs}/synchronization-timing.md (100%) rename {website/docs => docs}/tutorial/endpoint-example.png (100%) rename {website/docs => docs}/tutorial/endpoints.md (100%) rename {website/docs => docs}/tutorial/example-consumer.png (100%) rename {website/docs => docs}/tutorial/installation.md (100%) rename {website/docs => docs}/tutorial/jobs.md (100%) rename {website/docs => docs}/tutorial/mappings.md (100%) rename {website/docs => docs}/tutorial/postman-example.png (100%) rename {website/docs => docs}/tutorial/sources.md (100%) rename {website/docs => docs}/user-api-quick-reference.md (100%) rename {website/docs => docs}/user-api-setup.md (100%) rename {website/docs => docs}/user-api.md (100%) rename {website => docusaurus}/.gitignore (100%) rename {website => docusaurus}/README.md (100%) rename {website => docusaurus}/blog/2019-05-28-first-blog-post.md (100%) rename {website => docusaurus}/blog/2019-05-29-long-blog-post.md (100%) rename {website => docusaurus}/blog/2021-08-01-mdx-blog-post.mdx (100%) rename {website => docusaurus}/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg (100%) rename {website => docusaurus}/blog/2021-08-26-welcome/index.md (100%) rename {website => docusaurus}/blog/authors.yml (100%) rename {website => docusaurus}/blog/tags.yml (100%) rename {website => docusaurus}/docusaurus.config.js (98%) rename {website => docusaurus}/package-lock.json (100%) rename {website => docusaurus}/package.json (100%) rename {website => docusaurus}/sidebars.js (100%) rename {website => docusaurus}/src/components/HomepageFeatures/index.js (100%) rename {website => docusaurus}/src/components/HomepageFeatures/styles.module.css (100%) rename {website => docusaurus}/src/css/custom.css (100%) rename {website => docusaurus}/src/pages/index.js (100%) rename {website => docusaurus}/src/pages/index.module.css (100%) rename {website => docusaurus}/src/pages/markdown-page.md (100%) rename {website => docusaurus}/static/.nojekyll (100%) rename {website => docusaurus}/static/img/docusaurus-social-card.jpg (100%) rename {website => docusaurus}/static/img/docusaurus.png (100%) rename {website => docusaurus}/static/img/favicon.ico (100%) create mode 100644 docusaurus/static/img/logo.svg rename {website => docusaurus}/static/img/undraw_docusaurus_mountain.svg (100%) rename {website => docusaurus}/static/img/undraw_docusaurus_react.svg (100%) rename {website => docusaurus}/static/img/undraw_docusaurus_tree.svg (100%) create mode 100644 img/app-store.svg delete mode 100644 website/docs/administrators/sources/source.md delete mode 100644 website/docs/synchronization.md delete mode 100644 website/static/img/logo.svg diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 96ba843b8..f12a13f12 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -2,11 +2,11 @@ name: Documentation on: push: - branches: - - documentation + branches: + - development pull_request: - branches: - - documentation + branches: + - development jobs: deploy: @@ -22,7 +22,7 @@ jobs: - name: List directory structure run: | ls -la - ls -la website/ + ls -la docusaurus/ # Generate SVG files using PlantUML - name: plantuml @@ -38,7 +38,7 @@ jobs: id: deploy uses: redkubes/docusaurus-deploy-action@v1.2 with: - source-folder: website + source-folder: docusaurus git-user: ${{ github.actor }} git-password: ${{ secrets.GITHUB_TOKEN }} deployment-branch: gh-pages diff --git a/docs/administrators/README.md b/docs/administrators-legacy/README.md similarity index 100% rename from docs/administrators/README.md rename to docs/administrators-legacy/README.md diff --git a/docs/administrators/cloudevents.md b/docs/administrators-legacy/cloudevents.md similarity index 100% rename from docs/administrators/cloudevents.md rename to docs/administrators-legacy/cloudevents.md diff --git a/docs/administrators/consumer.md b/docs/administrators-legacy/consumer.md similarity index 100% rename from docs/administrators/consumer.md rename to docs/administrators-legacy/consumer.md diff --git a/docs/administrators/mapping/README.md b/docs/administrators-legacy/mapping/README.md similarity index 100% rename from docs/administrators/mapping/README.md rename to docs/administrators-legacy/mapping/README.md diff --git a/docs/administrators/mapping/mapping.md b/docs/administrators-legacy/mapping/mapping.md similarity index 100% rename from docs/administrators/mapping/mapping.md rename to docs/administrators-legacy/mapping/mapping.md diff --git a/docs/administrators/sources/README.md b/docs/administrators-legacy/sources/README.md similarity index 100% rename from docs/administrators/sources/README.md rename to docs/administrators-legacy/sources/README.md diff --git a/docs/administrators-legacy/sources/source.md b/docs/administrators-legacy/sources/source.md new file mode 100644 index 000000000..75abe4c31 --- /dev/null +++ b/docs/administrators-legacy/sources/source.md @@ -0,0 +1,75 @@ +# Source Configuration + +## Source + +A **Source** represents an external service or system that OpenConnector can interact with. This is typically an API but can also be databases, file servers, or other services. Sources allow the Connector to retrieve, process, and synchronize data. + +### Basic setup + +To configure a source, you need to define its basic properties: + +1. **Name**: A descriptive identifier for the source (e.g., "Noordwijk"). This name will appear in the UI to help you quickly locate the source. +2. **Description**: A brief explanation of the source's purpose or content (optional). +3. **Type**: Defines the type of connection (e.g., API, database, file server). +4. **Location**: The endpoint or base URL of the source (e.g., `https://zaaksysteem.noordwijk.nl/api/v1`). + +![alt text](../../sources/image-6.png) + +**Note**: Ensure the location does not end with a `/`. If included, it will be removed during saving. + +### Authentication + +If the source requires authentication (e.g., an API key, certificate, or OAuth token), these credentials are managed securely in the **Vault**. To authenticate: + +1. Add the required authentication details in the Vault. +2. Link the authentication method to the source. + +Multiple authentication methods can be associated with a single source, offering flexibility for different connection scenarios. + +### Synchronisations + +Synchronizations between sources and the system can be managed in the **Synchronizations** tab for each source. This provides an overview of all configured synchronization tasks related to a source. + +@todo add screenshots + +### Configuration + +Sources are called using the **Call Service**, based on the popular [Guzzle library](https://docs.guzzlephp.org/en/stable/). This enables flexible configuration of connection parameters. You can add custom configurations using a **dot.notation** format. + +**Example Configuration:** + +To set the `Accept` header for API calls: + +* **Key**: `headers.Accept` +* **Value**: `application/json` + +This creates the following request header: + +![the add Configuration modal](../../sources/image-3.png) + +This configuration will lead to the following configuration on the call service: + +``` +headers: + - name: Accept + value: application/json +``` + +Other commonly used options include: + +* **Headers**: Define custom request headers. +* **Query**: Add query parameters to requests. + +But all [guzzle options](https://docs.guzzlephp.org/en/stable/request-options.html) are supported. So for example, you can change the timeout of the call by adding the following configuration to the source: + +`timeout = 10` + +## Logging + +The Call Service logs all requests and responses for each source. Logs can be viewed directly in the **Logs** tab of the source's detail page. + +![alt text](../../sources/image-4.png) + +![alt text](../../sources/image-5.png) + +**Note**: Future updates will allow configuring the log retention period. diff --git a/docs/administrators/sources/xxllnctopublication.md b/docs/administrators-legacy/sources/xxllnctopublication.md similarity index 100% rename from docs/administrators/sources/xxllnctopublication.md rename to docs/administrators-legacy/sources/xxllnctopublication.md diff --git a/docs/administrators/synchronysation/README.md b/docs/administrators-legacy/synchronysation/README.md similarity index 100% rename from docs/administrators/synchronysation/README.md rename to docs/administrators-legacy/synchronysation/README.md diff --git a/docs/administrators/synchronysation/contract.md b/docs/administrators-legacy/synchronysation/contract.md similarity index 100% rename from docs/administrators/synchronysation/contract.md rename to docs/administrators-legacy/synchronysation/contract.md diff --git a/docs/administrators/synchronysation/synchronysation.md b/docs/administrators-legacy/synchronysation/synchronysation.md similarity index 100% rename from docs/administrators/synchronysation/synchronysation.md rename to docs/administrators-legacy/synchronysation/synchronysation.md diff --git a/docs/administrators/vault/README.md b/docs/administrators-legacy/vault/README.md similarity index 100% rename from docs/administrators/vault/README.md rename to docs/administrators-legacy/vault/README.md diff --git a/docs/administrators/vault/applications.md b/docs/administrators-legacy/vault/applications.md similarity index 100% rename from docs/administrators/vault/applications.md rename to docs/administrators-legacy/vault/applications.md diff --git a/docs/administrators/vault/authentication.md b/docs/administrators-legacy/vault/authentication.md similarity index 100% rename from docs/administrators/vault/authentication.md rename to docs/administrators-legacy/vault/authentication.md diff --git a/website/docs/administrators/logging.md b/docs/administrators/logging.md similarity index 100% rename from website/docs/administrators/logging.md rename to docs/administrators/logging.md diff --git a/docs/administrators/sources/source.md b/docs/administrators/sources/source.md index 75abe4c31..7c95db835 100644 --- a/docs/administrators/sources/source.md +++ b/docs/administrators/sources/source.md @@ -1,75 +1,208 @@ -# Source Configuration +# Source -## Source +## Configuration -A **Source** represents an external service or system that OpenConnector can interact with. This is typically an API but can also be databases, file servers, or other services. Sources allow the Connector to retrieve, process, and synchronize data. +Sources can be configured with various options to customize how they connect to external systems. All [Guzzle options](https://docs.guzzlephp.org/en/stable/request-options.html) are supported for HTTP-based sources. -### Basic setup +### Common Configuration Options -To configure a source, you need to define its basic properties: +Some commonly used configuration options include: -1. **Name**: A descriptive identifier for the source (e.g., "Noordwijk"). This name will appear in the UI to help you quickly locate the source. -2. **Description**: A brief explanation of the source's purpose or content (optional). -3. **Type**: Defines the type of connection (e.g., API, database, file server). -4. **Location**: The endpoint or base URL of the source (e.g., `https://zaaksysteem.noordwijk.nl/api/v1`). +**Authentication:** +```json +headers: + - name: Authorization + value: Bearer YOUR_TOKEN_HERE +``` + +**Custom Headers:** +```json +headers: + - name: Accept + value: application/json +``` + +**Query Parameters:** +```json +query: + - name: limit + value: 100 +``` -![alt text](../../sources/image-6.png) +**Timeout Configuration:** +```json +timeout: 10 +``` -**Note**: Ensure the location does not end with a `/`. If included, it will be removed during saving. +### Additional Configuration Parameters -### Authentication +You can set additional configuration parameters for sources: -If the source requires authentication (e.g., an API key, certificate, or OAuth token), these credentials are managed securely in the **Vault**. To authenticate: +- **logBody**: Controls response body logging behavior + - `true` or `1`: Log all response bodies + - `false` or `0`: Log only error response bodies (status codes 400-599) + - Default: `false` (only error responses are logged) -1. Add the required authentication details in the Vault. -2. Link the authentication method to the source. +Example: +```json +{ + "logBody": true, + "timeout": 30 +} +``` -Multiple authentication methods can be associated with a single source, offering flexibility for different connection scenarios. +## Call Logs Management -### Synchronisations +The OpenConnector provides comprehensive call log management for monitoring API interactions with your sources. The **Source Logs** page offers professional-grade tools for analyzing, filtering, and managing your API call history. -Synchronizations between sources and the system can be managed in the **Synchronizations** tab for each source. This provides an overview of all configured synchronization tasks related to a source. +### Accessing Source Logs -@todo add screenshots +You can access source logs in two ways: -### Configuration +1. **From the main navigation**: Go to Sources → Logs +2. **From individual sources**: Click 'View Logs' on any source in the Sources list -Sources are called using the **Call Service**, based on the popular [Guzzle library](https://docs.guzzlephp.org/en/stable/). This enables flexible configuration of connection parameters. You can add custom configurations using a **dot.notation** format. +### Call Logs Interface -**Example Configuration:** +The Source Logs page provides a streamlined, table-based interface optimized for log analysis: -To set the `Accept` header for API calls: +#### Key Features -* **Key**: `headers.Accept` -* **Value**: `application/json` +- **Table-only view**: Focused on readability and data density +- **Advanced filtering**: Comprehensive filtering system via sidebar +- **Export functionality**: Export filtered logs for external analysis +- **Bulk operations**: Select and delete multiple logs efficiently +- **Real-time updates**: Refresh logs with latest data -This creates the following request header: +#### Log Information Displayed -![the add Configuration modal](../../sources/image-3.png) +Each log entry shows: -This configuration will lead to the following configuration on the call service: +- **Status Code**: HTTP response status with color-coded indicators + - 🟢 Green: Success (2xx) + - 🟡 Yellow: Client errors (4xx) + - 🔴 Red: Server errors (5xx) +- **Source**: Name of the source that made the call +- **HTTP Method**: GET, POST, PUT, PATCH, DELETE with color-coded badges +- **Endpoint**: Target URL or endpoint path +- **Response Time**: Request duration with performance indicators + - 🟢 Fast: < 1 second + - 🟡 Medium: 1-3 seconds + - 🔴 Slow: > 3 seconds +- **Created**: Timestamp of the API call -``` -headers: - - name: Accept - value: application/json +### Advanced Filtering + +The sidebar provides powerful filtering capabilities: + +#### Source Filtering +- Filter logs by specific sources +- View logs from all sources or focus on individual sources + +#### Status Code Filtering +- Filter by HTTP response codes: + - Success codes: 200, 201 + - Client errors: 400, 401, 403, 404 + - Server errors: 500, 502, 503 + +#### HTTP Method Filtering +- Filter by request methods: GET, POST, PUT, PATCH, DELETE + +#### Date Range Filtering +- **From Date**: Start of time range +- **To Date**: End of time range +- Custom date/time picker for precise filtering + +#### Advanced Filters +- **Endpoint Filter**: Text-based filtering for specific endpoints +- **Show Only Errors**: Toggle to display only failed requests (4xx, 5xx) +- **Show Slow Requests**: Toggle to display only slow requests (>5 seconds) + +#### Filter Statistics + +The sidebar also provides real-time statistics: + +- **Total Logs**: Overall count of call logs +- **Success Rate**: Percentage of successful calls (2xx status codes) +- **Average Response Time**: Mean response time across all calls +- **Status Distribution**: Breakdown of response codes +- **Most Active Sources**: Sources generating the most calls + +### Log Actions + +#### Individual Log Actions +- **View Details**: Open detailed log information in a modal +- **Copy Data**: Copy complete log data to clipboard (JSON format) +- **Delete**: Remove individual log entries + +#### Bulk Operations +- **Select All**: Select all visible logs for bulk operations +- **Bulk Delete**: Remove multiple selected logs at once +- **Export**: Download filtered logs in CSV format + +### Performance Monitoring + +#### Response Time Analysis +The interface provides visual indicators for performance monitoring: + +- **Fast responses** (< 1s): Green highlighting +- **Medium responses** (1-3s): Yellow highlighting +- **Slow responses** (> 3s): Red highlighting + +#### Error Analysis +- Color-coded status indicators help identify patterns +- Filter by error types to troubleshoot issues +- Export error logs for detailed analysis + +### Export and Integration + +#### Export Formats +- **CSV Export**: Download logs in comma-separated format +- **Filtered Export**: Export only logs matching current filters +- **Complete Data**: Include all log fields and metadata + +#### Integration with Monitoring Tools +The log data can be integrated with external monitoring systems: + +```php +// Example: Export log metrics +$metrics = [ + 'total_calls' => $logCount, + 'success_rate' => $successRate, + 'average_response_time' => $avgResponseTime, + 'error_count' => $errorCount +]; ``` -Other commonly used options include: +### Best Practices -* **Headers**: Define custom request headers. -* **Query**: Add query parameters to requests. +#### Log Management +1. **Regular Cleanup**: Periodically remove old logs to maintain performance +2. **Filter Strategy**: Use specific filters to focus on relevant data +3. **Export Important Data**: Download critical logs before cleanup +4. **Monitor Trends**: Watch for patterns in response times and errors -But all [guzzle options](https://docs.guzzlephp.org/en/stable/request-options.html) are supported. So for example, you can change the timeout of the call by adding the following configuration to the source: +#### Performance Optimization +1. **Response Time Monitoring**: Track slow endpoints for optimization +2. **Error Pattern Analysis**: Identify recurring issues +3. **Source Performance**: Compare performance across different sources +4. **Rate Limit Awareness**: Monitor for rate limiting indicators -`timeout = 10` +#### Troubleshooting +1. **Error Investigation**: Use status code filters to isolate issues +2. **Timeline Analysis**: Use date filters to correlate issues with events +3. **Endpoint Analysis**: Filter by specific endpoints to debug problems +4. **Source Comparison**: Compare behavior across different sources -## Logging +**Note**: The log retention period and cleanup policies can be configured in the system settings. -The Call Service logs all requests and responses for each source. Logs can be viewed directly in the **Logs** tab of the source's detail page. +## Testing Sources -![alt text](../../sources/image-4.png) +Use the test functionality to verify your source configuration before deploying: -![alt text](../../sources/image-5.png) +1. Navigate to your source configuration +2. Click the 'Test Source' button +3. Review the test results for connectivity and authentication +4. Adjust configuration as needed based on test feedback -**Note**: Future updates will allow configuring the log retention period. +Test results will appear in the source logs and can be analyzed using the same filtering and analysis tools. \ No newline at end of file diff --git a/website/docs/async-file-fetching.md b/docs/async-file-fetching.md similarity index 100% rename from website/docs/async-file-fetching.md rename to docs/async-file-fetching.md diff --git a/website/docs/configurations.md b/docs/configurations.md similarity index 100% rename from website/docs/configurations.md rename to docs/configurations.md diff --git a/website/docs/diagrams/object-deletion-states.puml b/docs/diagrams/object-deletion-states.puml similarity index 100% rename from website/docs/diagrams/object-deletion-states.puml rename to docs/diagrams/object-deletion-states.puml diff --git a/website/docs/diagrams/object-deletion-states.svg b/docs/diagrams/object-deletion-states.svg similarity index 100% rename from website/docs/diagrams/object-deletion-states.svg rename to docs/diagrams/object-deletion-states.svg diff --git a/website/docs/diagrams/object-locking-sequence.puml b/docs/diagrams/object-locking-sequence.puml similarity index 100% rename from website/docs/diagrams/object-locking-sequence.puml rename to docs/diagrams/object-locking-sequence.puml diff --git a/website/docs/diagrams/object-locking-sequence.svg b/docs/diagrams/object-locking-sequence.svg similarity index 100% rename from website/docs/diagrams/object-locking-sequence.svg rename to docs/diagrams/object-locking-sequence.svg diff --git a/website/docs/examples/parallel-timing-example.md b/docs/examples/parallel-timing-example.md similarity index 100% rename from website/docs/examples/parallel-timing-example.md rename to docs/examples/parallel-timing-example.md diff --git a/website/docs/flow-token.md b/docs/flow-token.md similarity index 100% rename from website/docs/flow-token.md rename to docs/flow-token.md diff --git a/website/docs/image.png b/docs/image.png similarity index 100% rename from website/docs/image.png rename to docs/image.png diff --git a/website/docs/intro.md b/docs/intro.md similarity index 100% rename from website/docs/intro.md rename to docs/intro.md diff --git a/website/docs/origin-id.png b/docs/origin-id.png similarity index 100% rename from website/docs/origin-id.png rename to docs/origin-id.png diff --git a/website/docs/parallel-page-fetching.md b/docs/parallel-page-fetching.md similarity index 100% rename from website/docs/parallel-page-fetching.md rename to docs/parallel-page-fetching.md diff --git a/website/docs/quality-assurance.md b/docs/quality-assurance.md similarity index 100% rename from website/docs/quality-assurance.md rename to docs/quality-assurance.md diff --git a/website/docs/rule-tags.png b/docs/rule-tags.png similarity index 100% rename from website/docs/rule-tags.png rename to docs/rule-tags.png diff --git a/website/docs/rules.md b/docs/rules.md similarity index 100% rename from website/docs/rules.md rename to docs/rules.md diff --git a/website/docs/security-best-practices.md b/docs/security-best-practices.md similarity index 100% rename from website/docs/security-best-practices.md rename to docs/security-best-practices.md diff --git a/website/docs/sources.md b/docs/sources.md similarity index 100% rename from website/docs/sources.md rename to docs/sources.md diff --git a/website/docs/sub-objects.png b/docs/sub-objects.png similarity index 100% rename from website/docs/sub-objects.png rename to docs/sub-objects.png diff --git a/docs/synchronization-legacy.md b/docs/synchronization-legacy.md new file mode 100644 index 000000000..00e6664ca --- /dev/null +++ b/docs/synchronization-legacy.md @@ -0,0 +1,39 @@ +# Synchronization Actions + +## Concept + +Just as with Endpoints, synchronizations can trigger additional actions (at this moment called rules). +These can change the content of an object, but also trigger additional synchronizations. + +These actions can be created by creating a rule (an action) and adding it by id to the property +actions in the Synchronization. + +## Synchronizing files +In order to fetch a file from an external source and store it in the Nextcloud Filesystem in a way that OpenRegister can +connect it to an object, there are two predefined actions: + +- `fetch_file`: This action downloads the file and substitutes the base encoded content into the variable that contained the file url +- `write_file`: This action takes a file's content in base encoding and the filename (otherwise it will use a default filename), and writes it to the filesystem. + +### Fetch file + +This action should be run on timing `after` (when the object has been stored). +The action takes the following parameters in the `configuration` property: + +- `source` (required): The id of the source where the file can be downloaded +- `filePath` (required): The dot path of the location in the input object that contains the file url or file path. +- `method` (optional): The HTTP method that should be used to fetch the file. Defaults to GET +- `sourceConfiguration` (optional): Additional configuration for the source that only holds for fetching files. + +When properly configured this action will download the file from the given source and substitute the base64 encoded content in the returned object. +It is preferred to run this action in combination with `write_file` immediately after, so the file contents are properly stored in the Nextcloud file system instead of written to a database. + +### Write file + +This action should be run on timing `after`, and if combined with `fetch_file` it should be run in order after `fetch_file`. +The action takes the followin parameters in the `configuration` property: + +- `filePath` (required): The dot path of the location in the input object that contains the base64 encoded content of the file. +- `fileNamePath` (required): The dot path of the location in the input object that contains the filename + +This will write the file to the nextcloud filesystem in the folder that belongs to the written object, and substitutes the file content in the returned data with the path of the object in the Nextcloud File System. diff --git a/website/docs/synchronization-timing.md b/docs/synchronization-timing.md similarity index 100% rename from website/docs/synchronization-timing.md rename to docs/synchronization-timing.md diff --git a/docs/synchronization.md b/docs/synchronization.md index 00e6664ca..1f62751b4 100644 --- a/docs/synchronization.md +++ b/docs/synchronization.md @@ -1,39 +1,336 @@ -# Synchronization Actions +# Synchronization -## Concept +Synchronization is a core feature that enables data transfer between different systems and APIs. Here's how it works: -Just as with Endpoints, synchronizations can trigger additional actions (at this moment called rules). -These can change the content of an object, but also trigger additional synchronizations. +## Overview +- Synchronizations define how data should be synchronized between a source and target system +- Each synchronization has a source configuration (where to get data from) and target configuration (where to send data to) +- The synchronization process handles pagination, data mapping, and maintaining sync state -These actions can be created by creating a rule (an action) and adding it by id to the property -actions in the Synchronization. +## Key Components -## Synchronizing files -In order to fetch a file from an external source and store it in the Nextcloud Filesystem in a way that OpenRegister can -connect it to an object, there are two predefined actions: +### Source Configuration +- Defines where to fetch data from (API endpoint, database etc) +- Specifies how to map source data to target format (incomming data) +- Specifies how to locate objects in the response using resultsPosition: + - `_root`: Uses the entire response body as the objects array + - Dot notation (e.g. `data.items`): Extracts objects from a nested path + - Common keys (`items`, `result`, `results`): Automatically checks these standard locations + - Custom path: Specify any JSON path to locate the objects +- Configures pagination settings (paginationQuery) +- Can include conditions to filter which objects to sync using JSON Logic +- Source type can be set to API or other supported types +- Source ID mapping allows specifying position of IDs in source objects +- Optional endpoint configuration for fetching data +- usesPagination: configure this field if you know this source **does not** uses pagination or next endpoint. Use value "false". If this source uses next endpoint it will auto detect. +- Optional configuration of sub objects. +- (Optional) restrictDeletion: if set to true or 1, restricts the deletion of objects in the synchronization to objects of which the origin id was in the request to the source, to prevent deletion of objects when a specified set of objects is synchronized. -- `fetch_file`: This action downloads the file and substitutes the base encoded content into the variable that contained the file url -- `write_file`: This action takes a file's content in base encoding and the filename (otherwise it will use a default filename), and writes it to the filesystem. +#### Related or sub objects -### Fetch file +If you don't want mapped related- or sub- objects to duplicate and do want sub objects to have their own contracts you need to let the synchronization know. +You can configure this under the source configuration of a synchronization. + +![Sub objects](sub-objects.png) + +The synchronization know knows we need to find and update a existing contract for zaaktype and the zaaktype its own sub object statustype. The contract can find the related object and update that object instead of duplicating objects each time the synchronization has ran. + +Also make sure you first map a `originId` in the mapping of a sub object so the code can find this object and update it instead of duplicate it. + +![Origin id](origin-id.png) + +### Target Configuration +- Defines where to send synchronized data +- Specifies how to map target data to source format (outgoing data) +- Handles create/update/delete operations +- Target type can be Register/Schema or other supported types +- Target ID and schema selection for Register/Schema targets +- Target source mapping for data transformations +- `idInRequestBody` is a config option u can use when the id of the object in the target is not managed by itself but needs to be given with the request. Set the id to the required key in a mapping and configure `idInRequestBody` to that key so we set the targetId of the contract to that id. + +### Synchronization Contracts +- Tracks the sync state for each individual object +- Stores origin ID and target ID mappings +- Maintains hashes to detect changes +- Logs synchronization events and errors + +## Process Flow +1. Fetch all objects from source system with pagination +2. Determining if an object has changed +3. Create/update synchronization contracts for each object +4. Transform data according to mapping rules +5. Write objects to target system (POST/PUT/DELETE) +6. Update contract status and hashes +7. Handle any follow-up synchronizations + +### Determining if an object has changed +The synchronization service prevents unnecessary updates and code execution by checking the following conditions: + +1. The origin hash matches the stored hash in the synchronization contract (indicating the source object hasn't changed) +2. The synchronization configuration hasn't been updated since the last check (synchronization.updated < contract.sourceLastChecked) +3. If a source target mapping exists, verify it hasn't been updated since the last check (mapping.updated < contract.sourceLastChecked) +4. The target ID and hash exist in the synchronization contract (object hasn't been removed from target system) +5. The force parameter is false (if true, the update will proceed regardless of other conditions) + +If all these conditions are met, the synchronization service will skip updating the object since no changes are needed. This optimization prevents unnecessary API calls and processing. + +This procces might be overridden by the user by setting the force option to true. In that case the synchronization service will update the target object regardless of whether it has changed or not. + +## Error Handling +- Rate limiting detection and backoff +- Logging of failed operations +- Contract state tracking for retry attempts + +The synchronization system provides a robust way to keep data in sync across different systems while maintaining state and handling errors gracefully. + +## Form Configuration +The synchronization form allows configuring: + +- Name: Descriptive name for the synchronization +- Description: Optional details about the synchronization +- Conditions: JSON Logic conditions for filtering objects +- Source Configuration: + - Source Type: API or other supported types + - Source ID: Selection of configured source + - Source hash mapping: Hash configuration + - Source target mapping: Data mapping rules + - Position of ID in source object (optional) + - Position of results in source object (optional) + - Custom endpoint for data fetching (optional) +- Target Configuration: + - Target Type: Register/Schema or other types + - Target ID: Selection of target system + - Register and Schema selection for Register/Schema targets + - Target source mapping: Data transformation rules +- Test sync option to validate configuration + +![alt text](image.png) + +## XML + +All XML files are first parsed to a json object before being processed. During this parings all element atributes are added to the `@attributes` property of the element Resulting in a clean json object. Lets take a look at the XML example, and the json result that it will produce. + + + + +```xml + + + GEMMA + De GEMeentelijk Model Architectuur (GEMMA) bevat een blauwdruk van de gemeente en haar informatievoorziening. De GEMMA kan worden gebruikt als basis voor de projectmodellen + + + Publiceren en gebruiken van informatie over datadiensten + Dienstenafnemers moeten in online catalogi kunnen opvragen welke diensten, met welke kenmerken, door dienstenaanbieder worden aangeboden. +Onder andere ontwikkelaars hebben baat bij informatie over beschikbare diensten en de vereisten voor het gebruik van de dienst (bijv. specificatie van een dienst conform de OAS-standaard). + + + Thema-architectuur Common Ground + + + a10869bf-a895-4a66-8f81-a4f96c58cc3e + + + + + Bieden van 'regie op gegevens' aan burgers en bedrijven + Burgers en bedrijven moet zeggenschap worden geboden over hun persoonlijke gegevens. Ze moeten o.a. in staat worden gesteld om te weten welke gegevens de overheid over hen vastlegt en gebruikt, persoonlijke gegevens (digitaal) te delen, eenmalig te verstrekken, de eigen gegevens in te zien, te controleren en waar nodig te (laten) corrigeren,in te zien welke gegevens worden en zijn uitgewisseld. + + + Thema-architectuur Common Ground + + + 6868ab87-f5db-40b7-aba6-17b1ba477d54 + + + + + +``` + +```json +{ + "@attributes": { + "identifier": "id-d47463497ef83403bedb663bff370727" + }, + "name": "GEMMA", + "documentation": "De GEMeentelijk Model Architectuur (GEMMA) bevat een blauwdruk van de gemeente en haar informatievoorziening. De GEMMA kan worden gebruikt als basis voor de projectmodellen", + "elements": { + "element": [ + { + "@attributes": { + "identifier": "id-009fa62f25844aa3a87d252bf2b6bb0c", + "xsi:type": "Capability" + }, + "name": "Publiceren en gebruiken van informatie over datadiensten", + "documentation": "Dienstenafnemers moeten in online catalogi kunnen opvragen welke diensten, met welke kenmerken, door dienstenaanbieder worden aangeboden. \nOnder andere ontwikkelaars hebben baat bij informatie over beschikbare diensten en de vereisten voor het gebruik van de dienst (bijv. specificatie van een dienst conform de OAS-standaard).", + "properties": { + "property": [ + { + "@attributes": { + "propertyDefinitionRef": "propid-1" + }, + "value": "Thema-architectuur Common Ground" + }, + { + "@attributes": { + "propertyDefinitionRef": "propid-2" + }, + "value": "a10869bf-a895-4a66-8f81-a4f96c58cc3e" + } + ] + } + }, + { + "@attributes": { + "identifier": "id-096b6619f4cd4164a0572b04ef082c0f", + "xsi:type": "Capability" + }, + "name": "Bieden van 'regie op gegevens' aan burgers en bedrijven", + "documentation": "Burgers en bedrijven moet zeggenschap worden geboden over hun persoonlijke gegevens. Ze moeten o.a. in staat worden gesteld om te weten welke gegevens de overheid over hen vastlegt en gebruikt, persoonlijke gegevens (digitaal) te delen, eenmalig te verstrekken, de eigen gegevens in te zien, te controleren en waar nodig te (laten) corrigeren,in te zien welke gegevens worden en zijn uitgewisseld.", + "properties": { + "property": [ + { + "@attributes": { + "propertyDefinitionRef": "propid-1" + }, + "value": "Thema-architectuur Common Ground" + }, + { + "@attributes": { + "propertyDefinitionRef": "propid-2" + }, + "value": "6868ab87-f5db-40b7-aba6-17b1ba477d54" + } + ] + } + } + ] + } +} +``` + +So in this case we have a json object with an @attributes property. This property is used to store the identifier of the element. The name property is used to store the name of the element. And the properties property is used to store the properties of the element. + +That means that we can configure the psoition of the `id` in the source mapping as `@attributes.identifier` and the position of result within the source object as `elements.element`. + +Oke that setsup geting the data from the source, but how does the data get to the destination? Wel ugly is the answer, we have to map the data to the destination object. Right now the above code would result in the following object: + +```json +{ + "@attributes": { + "identifier": "id-8576c90ac5104cf8ade8acadf2764432" + }, + "name": "IngeschrevenPersoon", + "documentation": "Een INGEZETENE of NIET-INGEZETENE", + "properties": { + "property": [ + { + "@attributes": { + "propertyDefinitionRef": "propid-6" + }, + "value": "{AF9534B0-7CBD-4061-9276-A7B6698B383A}" + }, + { + "@attributes": { + "propertyDefinitionRef": "propid-8" + }, + "value": "Class" + }, + { + "@attributes": { + "propertyDefinitionRef": "propid-7" + }, + "value": "10122024-112046" + }, + { + "@attributes": { + "propertyDefinitionRef": "propid-9" + }, + "value": "\"ggm-\" properties worden beheerd in het GGM informatiemodel" + }, + { + "@attributes": { + "propertyDefinitionRef": "propid-15" + }, + "value": "Ingezetene, Client, Leerling, Ouder Of Verzorger" + }, + { + "@attributes": { + "propertyDefinitionRef": "propid-2" + }, + "value": "e1371e07-969e-4a34-be81-0a23ffd541dd" + }, + { + "@attributes": { + "propertyDefinitionRef": "propid-10" + }, + "value": "https://gemmaonline.nl/index.php/GEMMA/id-e1371e07-969e-4a34-be81-0a23ffd541dd" + } + ] + }, + "id": "faea7cdb-fe32-42fb-bbd4-98c3744ef333" +} +``` + + +So lets create a mapping for this, like: + +```json +{ + "identifier": "@attributes.identifier", + "name": "name", + "documentation": "documentation", + "properties": "{ {% for property in properties.property %}\"{{property['@attributes']['propertyDefinitionRef']}}\":\"{{property['value']}}\"{% if not loop.last %},{% endif %}{% endfor %} }" +} +``` + +Lets not forget that this will output the properties like + +```json +{ + "properties": "{ \"propid-6\":\"{AF9534B0-7CBD-4061-9276-A7B6698B383A}\",\"propid-8\":\"Class\",\"propid-7\":\"10122024-112046\",\"propid-9\":\""ggm-" properties worden beheerd in het GGM informatiemodel\",\"propid-15\":\"Ingezetene, Client, Leerling, Ouder Of Verzorger\",\"propid-2\":\"e1371e07-969e-4a34-be81-0a23ffd541dd\",\"propid-10\":\"https://gemmaonline.nl/index.php/GEMMA/id-e1371e07-969e-4a34-be81-0a23ffd541dd\" }" +} +``` + +So in the mapping we must add a cast to turn that into a json object. + +```json +{ + "properties": "jsonToArray" +} +``` + +And that will result in the following object: + +```json +{ + "properties": { + "propid-6": "{AF9534B0-7CBD-4061-9276-A7B6698B383A}", +But we want to map the data to the following object: + +```json +{ + "identifier": "id-8576c90ac5104cf8ade8acadf2764432", + "name": "IngeschrevenPersoon", + "documentation": "Een INGEZETENE of NIET-INGEZETENE", + "properties": { + "propid-6": "{AF9534B0-7CBD-4061-9276-A7B6698B383A}", + "propid-8": "Class", + "propid-7": "10122024-112046", + "propid-9": "\"ggm-\" properties worden beheerd in het GGM informatiemodel", + "propid-15": "Ingezetene, Client, Leerling, Ouder Of Verzorger", + "propid-2": "e1371e07-969e-4a34-be81-0a23ffd541dd", + "propid-10": "https://gemmaonline.nl/index.php/GEMMA/id-e1371e07-969e-4a34-be81-0a23ffd541dd" + } +} +``` + +Now of course propeties like `propid-6` are not very readable, so we can rename them to something more readable later using a translation table. -This action should be run on timing `after` (when the object has been stored). -The action takes the following parameters in the `configuration` property: -- `source` (required): The id of the source where the file can be downloaded -- `filePath` (required): The dot path of the location in the input object that contains the file url or file path. -- `method` (optional): The HTTP method that should be used to fetch the file. Defaults to GET -- `sourceConfiguration` (optional): Additional configuration for the source that only holds for fetching files. -When properly configured this action will download the file from the given source and substitute the base64 encoded content in the returned object. -It is preferred to run this action in combination with `write_file` immediately after, so the file contents are properly stored in the Nextcloud file system instead of written to a database. -### Write file -This action should be run on timing `after`, and if combined with `fetch_file` it should be run in order after `fetch_file`. -The action takes the followin parameters in the `configuration` property: -- `filePath` (required): The dot path of the location in the input object that contains the base64 encoded content of the file. -- `fileNamePath` (required): The dot path of the location in the input object that contains the filename -This will write the file to the nextcloud filesystem in the folder that belongs to the written object, and substitutes the file content in the returned data with the path of the object in the Nextcloud File System. diff --git a/website/docs/tutorial/endpoint-example.png b/docs/tutorial/endpoint-example.png similarity index 100% rename from website/docs/tutorial/endpoint-example.png rename to docs/tutorial/endpoint-example.png diff --git a/website/docs/tutorial/endpoints.md b/docs/tutorial/endpoints.md similarity index 100% rename from website/docs/tutorial/endpoints.md rename to docs/tutorial/endpoints.md diff --git a/website/docs/tutorial/example-consumer.png b/docs/tutorial/example-consumer.png similarity index 100% rename from website/docs/tutorial/example-consumer.png rename to docs/tutorial/example-consumer.png diff --git a/website/docs/tutorial/installation.md b/docs/tutorial/installation.md similarity index 100% rename from website/docs/tutorial/installation.md rename to docs/tutorial/installation.md diff --git a/website/docs/tutorial/jobs.md b/docs/tutorial/jobs.md similarity index 100% rename from website/docs/tutorial/jobs.md rename to docs/tutorial/jobs.md diff --git a/website/docs/tutorial/mappings.md b/docs/tutorial/mappings.md similarity index 100% rename from website/docs/tutorial/mappings.md rename to docs/tutorial/mappings.md diff --git a/website/docs/tutorial/postman-example.png b/docs/tutorial/postman-example.png similarity index 100% rename from website/docs/tutorial/postman-example.png rename to docs/tutorial/postman-example.png diff --git a/website/docs/tutorial/sources.md b/docs/tutorial/sources.md similarity index 100% rename from website/docs/tutorial/sources.md rename to docs/tutorial/sources.md diff --git a/website/docs/user-api-quick-reference.md b/docs/user-api-quick-reference.md similarity index 100% rename from website/docs/user-api-quick-reference.md rename to docs/user-api-quick-reference.md diff --git a/website/docs/user-api-setup.md b/docs/user-api-setup.md similarity index 100% rename from website/docs/user-api-setup.md rename to docs/user-api-setup.md diff --git a/website/docs/user-api.md b/docs/user-api.md similarity index 100% rename from website/docs/user-api.md rename to docs/user-api.md diff --git a/website/.gitignore b/docusaurus/.gitignore similarity index 100% rename from website/.gitignore rename to docusaurus/.gitignore diff --git a/website/README.md b/docusaurus/README.md similarity index 100% rename from website/README.md rename to docusaurus/README.md diff --git a/website/blog/2019-05-28-first-blog-post.md b/docusaurus/blog/2019-05-28-first-blog-post.md similarity index 100% rename from website/blog/2019-05-28-first-blog-post.md rename to docusaurus/blog/2019-05-28-first-blog-post.md diff --git a/website/blog/2019-05-29-long-blog-post.md b/docusaurus/blog/2019-05-29-long-blog-post.md similarity index 100% rename from website/blog/2019-05-29-long-blog-post.md rename to docusaurus/blog/2019-05-29-long-blog-post.md diff --git a/website/blog/2021-08-01-mdx-blog-post.mdx b/docusaurus/blog/2021-08-01-mdx-blog-post.mdx similarity index 100% rename from website/blog/2021-08-01-mdx-blog-post.mdx rename to docusaurus/blog/2021-08-01-mdx-blog-post.mdx diff --git a/website/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg b/docusaurus/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg similarity index 100% rename from website/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg rename to docusaurus/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg diff --git a/website/blog/2021-08-26-welcome/index.md b/docusaurus/blog/2021-08-26-welcome/index.md similarity index 100% rename from website/blog/2021-08-26-welcome/index.md rename to docusaurus/blog/2021-08-26-welcome/index.md diff --git a/website/blog/authors.yml b/docusaurus/blog/authors.yml similarity index 100% rename from website/blog/authors.yml rename to docusaurus/blog/authors.yml diff --git a/website/blog/tags.yml b/docusaurus/blog/tags.yml similarity index 100% rename from website/blog/tags.yml rename to docusaurus/blog/tags.yml diff --git a/website/docusaurus.config.js b/docusaurus/docusaurus.config.js similarity index 98% rename from website/docusaurus.config.js rename to docusaurus/docusaurus.config.js index 8e2c89fbc..69bdb1bbb 100644 --- a/website/docusaurus.config.js +++ b/docusaurus/docusaurus.config.js @@ -30,8 +30,9 @@ const config = { /** @type {import('@docusaurus/preset-classic').Options} */ ({ docs: { + path: '../docs', sidebarPath: require.resolve('./sidebars.js'), - editUrl: 'https://github.com/conductionnl/openconnector/tree/main/website/', + editUrl: 'https://github.com/conductionnl/openconnector/tree/main/docusaurus/', }, blog: false, theme: { diff --git a/website/package-lock.json b/docusaurus/package-lock.json similarity index 100% rename from website/package-lock.json rename to docusaurus/package-lock.json diff --git a/website/package.json b/docusaurus/package.json similarity index 100% rename from website/package.json rename to docusaurus/package.json diff --git a/website/sidebars.js b/docusaurus/sidebars.js similarity index 100% rename from website/sidebars.js rename to docusaurus/sidebars.js diff --git a/website/src/components/HomepageFeatures/index.js b/docusaurus/src/components/HomepageFeatures/index.js similarity index 100% rename from website/src/components/HomepageFeatures/index.js rename to docusaurus/src/components/HomepageFeatures/index.js diff --git a/website/src/components/HomepageFeatures/styles.module.css b/docusaurus/src/components/HomepageFeatures/styles.module.css similarity index 100% rename from website/src/components/HomepageFeatures/styles.module.css rename to docusaurus/src/components/HomepageFeatures/styles.module.css diff --git a/website/src/css/custom.css b/docusaurus/src/css/custom.css similarity index 100% rename from website/src/css/custom.css rename to docusaurus/src/css/custom.css diff --git a/website/src/pages/index.js b/docusaurus/src/pages/index.js similarity index 100% rename from website/src/pages/index.js rename to docusaurus/src/pages/index.js diff --git a/website/src/pages/index.module.css b/docusaurus/src/pages/index.module.css similarity index 100% rename from website/src/pages/index.module.css rename to docusaurus/src/pages/index.module.css diff --git a/website/src/pages/markdown-page.md b/docusaurus/src/pages/markdown-page.md similarity index 100% rename from website/src/pages/markdown-page.md rename to docusaurus/src/pages/markdown-page.md diff --git a/website/static/.nojekyll b/docusaurus/static/.nojekyll similarity index 100% rename from website/static/.nojekyll rename to docusaurus/static/.nojekyll diff --git a/website/static/img/docusaurus-social-card.jpg b/docusaurus/static/img/docusaurus-social-card.jpg similarity index 100% rename from website/static/img/docusaurus-social-card.jpg rename to docusaurus/static/img/docusaurus-social-card.jpg diff --git a/website/static/img/docusaurus.png b/docusaurus/static/img/docusaurus.png similarity index 100% rename from website/static/img/docusaurus.png rename to docusaurus/static/img/docusaurus.png diff --git a/website/static/img/favicon.ico b/docusaurus/static/img/favicon.ico similarity index 100% rename from website/static/img/favicon.ico rename to docusaurus/static/img/favicon.ico diff --git a/docusaurus/static/img/logo.svg b/docusaurus/static/img/logo.svg new file mode 100644 index 000000000..ff1e1c167 --- /dev/null +++ b/docusaurus/static/img/logo.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/website/static/img/undraw_docusaurus_mountain.svg b/docusaurus/static/img/undraw_docusaurus_mountain.svg similarity index 100% rename from website/static/img/undraw_docusaurus_mountain.svg rename to docusaurus/static/img/undraw_docusaurus_mountain.svg diff --git a/website/static/img/undraw_docusaurus_react.svg b/docusaurus/static/img/undraw_docusaurus_react.svg similarity index 100% rename from website/static/img/undraw_docusaurus_react.svg rename to docusaurus/static/img/undraw_docusaurus_react.svg diff --git a/website/static/img/undraw_docusaurus_tree.svg b/docusaurus/static/img/undraw_docusaurus_tree.svg similarity index 100% rename from website/static/img/undraw_docusaurus_tree.svg rename to docusaurus/static/img/undraw_docusaurus_tree.svg diff --git a/img/app-store.svg b/img/app-store.svg new file mode 100644 index 000000000..ff1e1c167 --- /dev/null +++ b/img/app-store.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/website/docs/administrators/sources/source.md b/website/docs/administrators/sources/source.md deleted file mode 100644 index 7c95db835..000000000 --- a/website/docs/administrators/sources/source.md +++ /dev/null @@ -1,208 +0,0 @@ -# Source - -## Configuration - -Sources can be configured with various options to customize how they connect to external systems. All [Guzzle options](https://docs.guzzlephp.org/en/stable/request-options.html) are supported for HTTP-based sources. - -### Common Configuration Options - -Some commonly used configuration options include: - -**Authentication:** -```json -headers: - - name: Authorization - value: Bearer YOUR_TOKEN_HERE -``` - -**Custom Headers:** -```json -headers: - - name: Accept - value: application/json -``` - -**Query Parameters:** -```json -query: - - name: limit - value: 100 -``` - -**Timeout Configuration:** -```json -timeout: 10 -``` - -### Additional Configuration Parameters - -You can set additional configuration parameters for sources: - -- **logBody**: Controls response body logging behavior - - `true` or `1`: Log all response bodies - - `false` or `0`: Log only error response bodies (status codes 400-599) - - Default: `false` (only error responses are logged) - -Example: -```json -{ - "logBody": true, - "timeout": 30 -} -``` - -## Call Logs Management - -The OpenConnector provides comprehensive call log management for monitoring API interactions with your sources. The **Source Logs** page offers professional-grade tools for analyzing, filtering, and managing your API call history. - -### Accessing Source Logs - -You can access source logs in two ways: - -1. **From the main navigation**: Go to Sources → Logs -2. **From individual sources**: Click 'View Logs' on any source in the Sources list - -### Call Logs Interface - -The Source Logs page provides a streamlined, table-based interface optimized for log analysis: - -#### Key Features - -- **Table-only view**: Focused on readability and data density -- **Advanced filtering**: Comprehensive filtering system via sidebar -- **Export functionality**: Export filtered logs for external analysis -- **Bulk operations**: Select and delete multiple logs efficiently -- **Real-time updates**: Refresh logs with latest data - -#### Log Information Displayed - -Each log entry shows: - -- **Status Code**: HTTP response status with color-coded indicators - - 🟢 Green: Success (2xx) - - 🟡 Yellow: Client errors (4xx) - - 🔴 Red: Server errors (5xx) -- **Source**: Name of the source that made the call -- **HTTP Method**: GET, POST, PUT, PATCH, DELETE with color-coded badges -- **Endpoint**: Target URL or endpoint path -- **Response Time**: Request duration with performance indicators - - 🟢 Fast: < 1 second - - 🟡 Medium: 1-3 seconds - - 🔴 Slow: > 3 seconds -- **Created**: Timestamp of the API call - -### Advanced Filtering - -The sidebar provides powerful filtering capabilities: - -#### Source Filtering -- Filter logs by specific sources -- View logs from all sources or focus on individual sources - -#### Status Code Filtering -- Filter by HTTP response codes: - - Success codes: 200, 201 - - Client errors: 400, 401, 403, 404 - - Server errors: 500, 502, 503 - -#### HTTP Method Filtering -- Filter by request methods: GET, POST, PUT, PATCH, DELETE - -#### Date Range Filtering -- **From Date**: Start of time range -- **To Date**: End of time range -- Custom date/time picker for precise filtering - -#### Advanced Filters -- **Endpoint Filter**: Text-based filtering for specific endpoints -- **Show Only Errors**: Toggle to display only failed requests (4xx, 5xx) -- **Show Slow Requests**: Toggle to display only slow requests (>5 seconds) - -#### Filter Statistics - -The sidebar also provides real-time statistics: - -- **Total Logs**: Overall count of call logs -- **Success Rate**: Percentage of successful calls (2xx status codes) -- **Average Response Time**: Mean response time across all calls -- **Status Distribution**: Breakdown of response codes -- **Most Active Sources**: Sources generating the most calls - -### Log Actions - -#### Individual Log Actions -- **View Details**: Open detailed log information in a modal -- **Copy Data**: Copy complete log data to clipboard (JSON format) -- **Delete**: Remove individual log entries - -#### Bulk Operations -- **Select All**: Select all visible logs for bulk operations -- **Bulk Delete**: Remove multiple selected logs at once -- **Export**: Download filtered logs in CSV format - -### Performance Monitoring - -#### Response Time Analysis -The interface provides visual indicators for performance monitoring: - -- **Fast responses** (< 1s): Green highlighting -- **Medium responses** (1-3s): Yellow highlighting -- **Slow responses** (> 3s): Red highlighting - -#### Error Analysis -- Color-coded status indicators help identify patterns -- Filter by error types to troubleshoot issues -- Export error logs for detailed analysis - -### Export and Integration - -#### Export Formats -- **CSV Export**: Download logs in comma-separated format -- **Filtered Export**: Export only logs matching current filters -- **Complete Data**: Include all log fields and metadata - -#### Integration with Monitoring Tools -The log data can be integrated with external monitoring systems: - -```php -// Example: Export log metrics -$metrics = [ - 'total_calls' => $logCount, - 'success_rate' => $successRate, - 'average_response_time' => $avgResponseTime, - 'error_count' => $errorCount -]; -``` - -### Best Practices - -#### Log Management -1. **Regular Cleanup**: Periodically remove old logs to maintain performance -2. **Filter Strategy**: Use specific filters to focus on relevant data -3. **Export Important Data**: Download critical logs before cleanup -4. **Monitor Trends**: Watch for patterns in response times and errors - -#### Performance Optimization -1. **Response Time Monitoring**: Track slow endpoints for optimization -2. **Error Pattern Analysis**: Identify recurring issues -3. **Source Performance**: Compare performance across different sources -4. **Rate Limit Awareness**: Monitor for rate limiting indicators - -#### Troubleshooting -1. **Error Investigation**: Use status code filters to isolate issues -2. **Timeline Analysis**: Use date filters to correlate issues with events -3. **Endpoint Analysis**: Filter by specific endpoints to debug problems -4. **Source Comparison**: Compare behavior across different sources - -**Note**: The log retention period and cleanup policies can be configured in the system settings. - -## Testing Sources - -Use the test functionality to verify your source configuration before deploying: - -1. Navigate to your source configuration -2. Click the 'Test Source' button -3. Review the test results for connectivity and authentication -4. Adjust configuration as needed based on test feedback - -Test results will appear in the source logs and can be analyzed using the same filtering and analysis tools. \ No newline at end of file diff --git a/website/docs/synchronization.md b/website/docs/synchronization.md deleted file mode 100644 index 1f62751b4..000000000 --- a/website/docs/synchronization.md +++ /dev/null @@ -1,336 +0,0 @@ -# Synchronization - -Synchronization is a core feature that enables data transfer between different systems and APIs. Here's how it works: - -## Overview -- Synchronizations define how data should be synchronized between a source and target system -- Each synchronization has a source configuration (where to get data from) and target configuration (where to send data to) -- The synchronization process handles pagination, data mapping, and maintaining sync state - -## Key Components - -### Source Configuration -- Defines where to fetch data from (API endpoint, database etc) -- Specifies how to map source data to target format (incomming data) -- Specifies how to locate objects in the response using resultsPosition: - - `_root`: Uses the entire response body as the objects array - - Dot notation (e.g. `data.items`): Extracts objects from a nested path - - Common keys (`items`, `result`, `results`): Automatically checks these standard locations - - Custom path: Specify any JSON path to locate the objects -- Configures pagination settings (paginationQuery) -- Can include conditions to filter which objects to sync using JSON Logic -- Source type can be set to API or other supported types -- Source ID mapping allows specifying position of IDs in source objects -- Optional endpoint configuration for fetching data -- usesPagination: configure this field if you know this source **does not** uses pagination or next endpoint. Use value "false". If this source uses next endpoint it will auto detect. -- Optional configuration of sub objects. -- (Optional) restrictDeletion: if set to true or 1, restricts the deletion of objects in the synchronization to objects of which the origin id was in the request to the source, to prevent deletion of objects when a specified set of objects is synchronized. - -#### Related or sub objects - -If you don't want mapped related- or sub- objects to duplicate and do want sub objects to have their own contracts you need to let the synchronization know. -You can configure this under the source configuration of a synchronization. - -![Sub objects](sub-objects.png) - -The synchronization know knows we need to find and update a existing contract for zaaktype and the zaaktype its own sub object statustype. The contract can find the related object and update that object instead of duplicating objects each time the synchronization has ran. - -Also make sure you first map a `originId` in the mapping of a sub object so the code can find this object and update it instead of duplicate it. - -![Origin id](origin-id.png) - -### Target Configuration -- Defines where to send synchronized data -- Specifies how to map target data to source format (outgoing data) -- Handles create/update/delete operations -- Target type can be Register/Schema or other supported types -- Target ID and schema selection for Register/Schema targets -- Target source mapping for data transformations -- `idInRequestBody` is a config option u can use when the id of the object in the target is not managed by itself but needs to be given with the request. Set the id to the required key in a mapping and configure `idInRequestBody` to that key so we set the targetId of the contract to that id. - -### Synchronization Contracts -- Tracks the sync state for each individual object -- Stores origin ID and target ID mappings -- Maintains hashes to detect changes -- Logs synchronization events and errors - -## Process Flow -1. Fetch all objects from source system with pagination -2. Determining if an object has changed -3. Create/update synchronization contracts for each object -4. Transform data according to mapping rules -5. Write objects to target system (POST/PUT/DELETE) -6. Update contract status and hashes -7. Handle any follow-up synchronizations - -### Determining if an object has changed -The synchronization service prevents unnecessary updates and code execution by checking the following conditions: - -1. The origin hash matches the stored hash in the synchronization contract (indicating the source object hasn't changed) -2. The synchronization configuration hasn't been updated since the last check (synchronization.updated < contract.sourceLastChecked) -3. If a source target mapping exists, verify it hasn't been updated since the last check (mapping.updated < contract.sourceLastChecked) -4. The target ID and hash exist in the synchronization contract (object hasn't been removed from target system) -5. The force parameter is false (if true, the update will proceed regardless of other conditions) - -If all these conditions are met, the synchronization service will skip updating the object since no changes are needed. This optimization prevents unnecessary API calls and processing. - -This procces might be overridden by the user by setting the force option to true. In that case the synchronization service will update the target object regardless of whether it has changed or not. - -## Error Handling -- Rate limiting detection and backoff -- Logging of failed operations -- Contract state tracking for retry attempts - -The synchronization system provides a robust way to keep data in sync across different systems while maintaining state and handling errors gracefully. - -## Form Configuration -The synchronization form allows configuring: - -- Name: Descriptive name for the synchronization -- Description: Optional details about the synchronization -- Conditions: JSON Logic conditions for filtering objects -- Source Configuration: - - Source Type: API or other supported types - - Source ID: Selection of configured source - - Source hash mapping: Hash configuration - - Source target mapping: Data mapping rules - - Position of ID in source object (optional) - - Position of results in source object (optional) - - Custom endpoint for data fetching (optional) -- Target Configuration: - - Target Type: Register/Schema or other types - - Target ID: Selection of target system - - Register and Schema selection for Register/Schema targets - - Target source mapping: Data transformation rules -- Test sync option to validate configuration - -![alt text](image.png) - -## XML - -All XML files are first parsed to a json object before being processed. During this parings all element atributes are added to the `@attributes` property of the element Resulting in a clean json object. Lets take a look at the XML example, and the json result that it will produce. - - - - -```xml - - - GEMMA - De GEMeentelijk Model Architectuur (GEMMA) bevat een blauwdruk van de gemeente en haar informatievoorziening. De GEMMA kan worden gebruikt als basis voor de projectmodellen - - - Publiceren en gebruiken van informatie over datadiensten - Dienstenafnemers moeten in online catalogi kunnen opvragen welke diensten, met welke kenmerken, door dienstenaanbieder worden aangeboden. -Onder andere ontwikkelaars hebben baat bij informatie over beschikbare diensten en de vereisten voor het gebruik van de dienst (bijv. specificatie van een dienst conform de OAS-standaard). - - - Thema-architectuur Common Ground - - - a10869bf-a895-4a66-8f81-a4f96c58cc3e - - - - - Bieden van 'regie op gegevens' aan burgers en bedrijven - Burgers en bedrijven moet zeggenschap worden geboden over hun persoonlijke gegevens. Ze moeten o.a. in staat worden gesteld om te weten welke gegevens de overheid over hen vastlegt en gebruikt, persoonlijke gegevens (digitaal) te delen, eenmalig te verstrekken, de eigen gegevens in te zien, te controleren en waar nodig te (laten) corrigeren,in te zien welke gegevens worden en zijn uitgewisseld. - - - Thema-architectuur Common Ground - - - 6868ab87-f5db-40b7-aba6-17b1ba477d54 - - - - - -``` - -```json -{ - "@attributes": { - "identifier": "id-d47463497ef83403bedb663bff370727" - }, - "name": "GEMMA", - "documentation": "De GEMeentelijk Model Architectuur (GEMMA) bevat een blauwdruk van de gemeente en haar informatievoorziening. De GEMMA kan worden gebruikt als basis voor de projectmodellen", - "elements": { - "element": [ - { - "@attributes": { - "identifier": "id-009fa62f25844aa3a87d252bf2b6bb0c", - "xsi:type": "Capability" - }, - "name": "Publiceren en gebruiken van informatie over datadiensten", - "documentation": "Dienstenafnemers moeten in online catalogi kunnen opvragen welke diensten, met welke kenmerken, door dienstenaanbieder worden aangeboden. \nOnder andere ontwikkelaars hebben baat bij informatie over beschikbare diensten en de vereisten voor het gebruik van de dienst (bijv. specificatie van een dienst conform de OAS-standaard).", - "properties": { - "property": [ - { - "@attributes": { - "propertyDefinitionRef": "propid-1" - }, - "value": "Thema-architectuur Common Ground" - }, - { - "@attributes": { - "propertyDefinitionRef": "propid-2" - }, - "value": "a10869bf-a895-4a66-8f81-a4f96c58cc3e" - } - ] - } - }, - { - "@attributes": { - "identifier": "id-096b6619f4cd4164a0572b04ef082c0f", - "xsi:type": "Capability" - }, - "name": "Bieden van 'regie op gegevens' aan burgers en bedrijven", - "documentation": "Burgers en bedrijven moet zeggenschap worden geboden over hun persoonlijke gegevens. Ze moeten o.a. in staat worden gesteld om te weten welke gegevens de overheid over hen vastlegt en gebruikt, persoonlijke gegevens (digitaal) te delen, eenmalig te verstrekken, de eigen gegevens in te zien, te controleren en waar nodig te (laten) corrigeren,in te zien welke gegevens worden en zijn uitgewisseld.", - "properties": { - "property": [ - { - "@attributes": { - "propertyDefinitionRef": "propid-1" - }, - "value": "Thema-architectuur Common Ground" - }, - { - "@attributes": { - "propertyDefinitionRef": "propid-2" - }, - "value": "6868ab87-f5db-40b7-aba6-17b1ba477d54" - } - ] - } - } - ] - } -} -``` - -So in this case we have a json object with an @attributes property. This property is used to store the identifier of the element. The name property is used to store the name of the element. And the properties property is used to store the properties of the element. - -That means that we can configure the psoition of the `id` in the source mapping as `@attributes.identifier` and the position of result within the source object as `elements.element`. - -Oke that setsup geting the data from the source, but how does the data get to the destination? Wel ugly is the answer, we have to map the data to the destination object. Right now the above code would result in the following object: - -```json -{ - "@attributes": { - "identifier": "id-8576c90ac5104cf8ade8acadf2764432" - }, - "name": "IngeschrevenPersoon", - "documentation": "Een INGEZETENE of NIET-INGEZETENE", - "properties": { - "property": [ - { - "@attributes": { - "propertyDefinitionRef": "propid-6" - }, - "value": "{AF9534B0-7CBD-4061-9276-A7B6698B383A}" - }, - { - "@attributes": { - "propertyDefinitionRef": "propid-8" - }, - "value": "Class" - }, - { - "@attributes": { - "propertyDefinitionRef": "propid-7" - }, - "value": "10122024-112046" - }, - { - "@attributes": { - "propertyDefinitionRef": "propid-9" - }, - "value": "\"ggm-\" properties worden beheerd in het GGM informatiemodel" - }, - { - "@attributes": { - "propertyDefinitionRef": "propid-15" - }, - "value": "Ingezetene, Client, Leerling, Ouder Of Verzorger" - }, - { - "@attributes": { - "propertyDefinitionRef": "propid-2" - }, - "value": "e1371e07-969e-4a34-be81-0a23ffd541dd" - }, - { - "@attributes": { - "propertyDefinitionRef": "propid-10" - }, - "value": "https://gemmaonline.nl/index.php/GEMMA/id-e1371e07-969e-4a34-be81-0a23ffd541dd" - } - ] - }, - "id": "faea7cdb-fe32-42fb-bbd4-98c3744ef333" -} -``` - - -So lets create a mapping for this, like: - -```json -{ - "identifier": "@attributes.identifier", - "name": "name", - "documentation": "documentation", - "properties": "{ {% for property in properties.property %}\"{{property['@attributes']['propertyDefinitionRef']}}\":\"{{property['value']}}\"{% if not loop.last %},{% endif %}{% endfor %} }" -} -``` - -Lets not forget that this will output the properties like - -```json -{ - "properties": "{ \"propid-6\":\"{AF9534B0-7CBD-4061-9276-A7B6698B383A}\",\"propid-8\":\"Class\",\"propid-7\":\"10122024-112046\",\"propid-9\":\""ggm-" properties worden beheerd in het GGM informatiemodel\",\"propid-15\":\"Ingezetene, Client, Leerling, Ouder Of Verzorger\",\"propid-2\":\"e1371e07-969e-4a34-be81-0a23ffd541dd\",\"propid-10\":\"https://gemmaonline.nl/index.php/GEMMA/id-e1371e07-969e-4a34-be81-0a23ffd541dd\" }" -} -``` - -So in the mapping we must add a cast to turn that into a json object. - -```json -{ - "properties": "jsonToArray" -} -``` - -And that will result in the following object: - -```json -{ - "properties": { - "propid-6": "{AF9534B0-7CBD-4061-9276-A7B6698B383A}", -But we want to map the data to the following object: - -```json -{ - "identifier": "id-8576c90ac5104cf8ade8acadf2764432", - "name": "IngeschrevenPersoon", - "documentation": "Een INGEZETENE of NIET-INGEZETENE", - "properties": { - "propid-6": "{AF9534B0-7CBD-4061-9276-A7B6698B383A}", - "propid-8": "Class", - "propid-7": "10122024-112046", - "propid-9": "\"ggm-\" properties worden beheerd in het GGM informatiemodel", - "propid-15": "Ingezetene, Client, Leerling, Ouder Of Verzorger", - "propid-2": "e1371e07-969e-4a34-be81-0a23ffd541dd", - "propid-10": "https://gemmaonline.nl/index.php/GEMMA/id-e1371e07-969e-4a34-be81-0a23ffd541dd" - } -} -``` - -Now of course propeties like `propid-6` are not very readable, so we can rename them to something more readable later using a translation table. - - - - - - - diff --git a/website/static/img/logo.svg b/website/static/img/logo.svg deleted file mode 100644 index 9db6d0d06..000000000 --- a/website/static/img/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From f6d610923f36f1fb43f1f410c1006bb3937e8d42 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Mon, 16 Mar 2026 09:37:53 +0100 Subject: [PATCH 04/17] feat: Add 3 tender-derived OpenSpecs for integration adapters Based on analysis of 74 Dutch government tenders: - stuf-adapter: Bidirectional StUF-BG/ZKN adapter (79% tender demand) - ibabs-notubiz-connector: B&W besluitvorming RIS integration (27% demand) - dso-omgevingsloket: DSO/Omgevingsloket VTH adapter (32% demand) --- openspec/specs/dso-omgevingsloket/spec.md | 156 ++++++++++++++++++ .../specs/ibabs-notubiz-connector/spec.md | 141 ++++++++++++++++ openspec/specs/stuf-adapter/spec.md | 126 ++++++++++++++ 3 files changed, 423 insertions(+) create mode 100644 openspec/specs/dso-omgevingsloket/spec.md create mode 100644 openspec/specs/ibabs-notubiz-connector/spec.md create mode 100644 openspec/specs/stuf-adapter/spec.md diff --git a/openspec/specs/dso-omgevingsloket/spec.md b/openspec/specs/dso-omgevingsloket/spec.md new file mode 100644 index 000000000..dc03f583a --- /dev/null +++ b/openspec/specs/dso-omgevingsloket/spec.md @@ -0,0 +1,156 @@ +--- +status: proposed +--- + +# DSO / Omgevingsloket Adapter + +## Purpose + +Provides integration with the Digitaal Stelsel Omgevingswet (DSO) Landelijke Voorziening for receiving and processing vergunningaanvragen, meldingen, and informatieverzoeken from the Omgevingsloket. Required by 32% of tenders (all VTH-related). The adapter receives DSO-verzoeken via the STAM koppelvlak, parses them into zaak objects in Procest, maps activiteiten to zaaktypen, and supports samenwerking met bevoegd gezag via DSO-SWF (SamenWerkingsFunctionaliteit). Replaces the legacy OLO (Omgevingsloket Online) integration. + +## Requirements + +### DSO-LV Inbound (Receive Verzoeken) + +| ID | Requirement | Priority | Status | +|----|------------|----------|--------| +| DSO-001 | Receive vergunningaanvragen from DSO-LV via the STAM (STAndaard Machtiging) koppelvlak REST API | MUST | Planned | +| DSO-002 | Receive meldingen (activiteiten waarvoor geen vergunning nodig is) from DSO-LV | MUST | Planned | +| DSO-003 | Receive informatieverzoeken and vooroverleg-aanvragen from DSO-LV | SHOULD | Planned | +| DSO-004 | Parse the DSO-verzoek XML/JSON payload into structured data: aanvrager, locatie, activiteiten, bijlagen, projectbeschrijving | MUST | Planned | +| DSO-005 | Download bijlagen (documenten, tekeningen, rapporten) from DSO-LV and store in Nextcloud Files | MUST | Planned | +| DSO-006 | Validate the received verzoek against DSO-LV schema and reject malformed requests with descriptive errors | MUST | Planned | + +### Activiteiten-to-Zaaktype Mapping + +| ID | Requirement | Priority | Status | +|----|------------|----------|--------| +| DSO-010 | Map DSO activiteiten (e.g., bouwen, milieu, kappen, uitrit) to Procest zaaktypen via configurable mapping table | MUST | Planned | +| DSO-011 | Support samenloop: one DSO-verzoek with multiple activiteiten can result in multiple zaak objects or one zaak with multiple deelzaken | MUST | Planned | +| DSO-012 | Default mapping configuration for common Omgevingswet activiteiten is pre-seeded | MUST | Planned | +| DSO-013 | Unmapped activiteiten create a zaak with a generic "Onbekend DSO-activiteit" zaaktype and flag for manual triage | MUST | Planned | +| DSO-014 | Mapping table is editable via the OpenConnector admin UI | SHOULD | Planned | + +### Zaak Creation + +| ID | Requirement | Priority | Status | +|----|------------|----------|--------| +| DSO-020 | Automatically create a zaak in Procest for each received DSO-verzoek | MUST | Planned | +| DSO-021 | Map aanvrager (initiatiefnemer) data to the zaak: BSN/KVK-nummer, naam, adres, contactgegevens | MUST | Planned | +| DSO-022 | Map locatie to the zaak: BAG-adres, kadastrale aanduiding, GML-geometrie (punt of polygoon) | MUST | Planned | +| DSO-023 | Set zaak startdatum to DSO-verzoek indieningsdatum | MUST | Planned | +| DSO-024 | Link downloaded bijlagen to the created zaak | MUST | Planned | +| DSO-025 | Store the original DSO-verzoek reference (verzoekId, bronorganisatie) on the zaak for traceability | MUST | Planned | +| DSO-026 | Extract bouwkosten from DSO-verzoek for legesberekening (if provided by aanvrager) | SHOULD | Planned | + +### DSO-SWF (Samenwerking) + +| ID | Requirement | Priority | Status | +|----|------------|----------|--------| +| DSO-030 | Support samenwerking met bevoegd gezag: when another overheidsorgaan is betrokken bij dezelfde aanvraag, coordinate via DSO-SWF | SHOULD | Planned | +| DSO-031 | Send adviesverzoeken to ketenpartners (provincie, waterschap, omgevingsdienst) via DSO-SWF | SHOULD | Planned | +| DSO-032 | Receive adviezen from ketenpartners and link to the zaak | SHOULD | Planned | +| DSO-033 | Track samenwerkingsstatus per zaak: welke organisaties zijn betrokken, welke adviezen zijn ontvangen | SHOULD | Planned | + +### Status Updates (Outbound to DSO-LV) + +| ID | Requirement | Priority | Status | +|----|------------|----------|--------| +| DSO-040 | Push zaak status updates back to DSO-LV so the aanvrager can track progress via the Omgevingsloket | MUST | Planned | +| DSO-041 | Map Procest zaak statussen to DSO-LV statuscodes: ontvangen, in behandeling, besluit genomen, etc. | MUST | Planned | +| DSO-042 | Push the vergunningbesluit (verleend, geweigerd, buiten behandeling) to DSO-LV | MUST | Planned | +| DSO-043 | Push vergunningdocumenten (beschikking PDF) to DSO-LV for publication | SHOULD | Planned | + +### Authentication & Security + +| ID | Requirement | Priority | Status | +|----|------------|----------|--------| +| DSO-050 | Authenticate with DSO-LV using PKIoverheid certificates (mTLS) | MUST | Planned | +| DSO-051 | Validate incoming DSO-LV webhook signatures to prevent spoofing | MUST | Planned | +| DSO-052 | Support DSO-LV test environment (pre-productie) alongside production for acceptance testing | SHOULD | Planned | +| DSO-053 | Store DSO API credentials and certificates securely in Nextcloud's credential store | MUST | Planned | + +### OpenConnector Integration + +| ID | Requirement | Priority | Status | +|----|------------|----------|--------| +| DSO-060 | Registered as an OpenConnector source type with DSO-LV-specific configuration | MUST | Planned | +| DSO-061 | Connection settings: DSO-LV API URL, PKIoverheid certificates, organisatie OIN, bevoegd-gezag code | MUST | Planned | +| DSO-062 | Health check: validate connectivity and certificate validity against DSO-LV | SHOULD | Planned | +| DSO-063 | n8n workflow integration: DSO-verzoek ontvangst triggers a configurable n8n workflow for intake processing | SHOULD | Planned | + +## Data Model + +### DSO-Verzoek (stored in OpenRegister before zaak creation) + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| verzoekId | string | Yes | DSO-LV unique verzoek identifier | +| bronorganisatie | string | Yes | OIN of the submitting DSO-LV instance | +| type | string (enum) | Yes | `aanvraag`, `melding`, `informatieverzoek`, `vooroverleg` | +| indieningsdatum | datetime | Yes | Date/time of submission in DSO-LV | +| aanvrager | object | Yes | Initiatiefnemer: BSN/KVK, naam, adres, contactgegevens | +| locatie | object | Yes | BAG-adres, kadastrale aanduiding, GML-geometrie | +| activiteiten | array | Yes | List of DSO activiteiten with codes and omschrijvingen | +| bouwkosten | decimal | No | Opgegeven bouwkosten (for legesberekening) | +| bijlagen | array | No | References to downloaded documents in Nextcloud Files | +| zaakId | string (UUID) | No | Created Procest zaak reference (set after processing) | +| status | string (enum) | Yes | `ontvangen`, `verwerkt`, `fout` | + +## Scenarios + +### Receive vergunningaanvraag from Omgevingsloket + +``` +GIVEN the DSO-LV adapter is configured with valid PKIoverheid certificates +AND an initiatiefnemer submits a vergunningaanvraag via het Omgevingsloket +WHEN DSO-LV sends the verzoek to our STAM endpoint +THEN the verzoek payload is parsed and validated +AND bijlagen are downloaded and stored in Nextcloud Files +AND activiteiten are mapped to zaaktypen +AND a zaak is created in Procest with aanvrager, locatie, and activiteiten data +AND a status "ontvangen" is pushed back to DSO-LV +``` + +### Multiple activiteiten with samenloop + +``` +GIVEN a verzoek contains activiteiten "bouwen" and "kappen" +AND "bouwen" maps to zaaktype "Omgevingsvergunning Bouwen" +AND "kappen" maps to zaaktype "Omgevingsvergunning Kappen" +WHEN the adapter processes the verzoek +THEN two deelzaken are created under one hoofdzaak +AND both share the same aanvrager and locatie +AND each deelzaak follows its own behandelproces +``` + +### Push besluit to DSO-LV + +``` +GIVEN a zaak originated from a DSO-verzoek +AND the vergunning is verleend +WHEN the zaak status changes to "Besluit genomen" in Procest +THEN the adapter pushes status "besluit genomen" to DSO-LV +AND the beschikking PDF is uploaded to DSO-LV +AND the aanvrager can view the besluit in het Omgevingsloket +``` + +### Unknown activiteit fallback + +``` +GIVEN a verzoek contains an activiteit not in the mapping table +WHEN the adapter processes the verzoek +THEN a zaak is created with zaaktype "Onbekend DSO-activiteit" +AND the zaak is flagged for manual triage +AND a notification is sent to the VTH-behandelaar +``` + +## Dependencies + +- **OpenConnector**: Source registration and connection management +- **OpenRegister**: Verzoek and mapping table storage +- **Procest**: Zaak creation and lifecycle management +- **Docudesk**: PDF generation for beschikkingen pushed to DSO-LV +- **DSO-LV STAM API**: External service (Kadaster/RWS) +- **PKIoverheid certificates**: For mTLS authentication +- **BAG/BRK services**: For locatie-validatie (via OpenConnector) diff --git a/openspec/specs/ibabs-notubiz-connector/spec.md b/openspec/specs/ibabs-notubiz-connector/spec.md new file mode 100644 index 000000000..c0e237299 --- /dev/null +++ b/openspec/specs/ibabs-notubiz-connector/spec.md @@ -0,0 +1,141 @@ +--- +status: proposed +--- + +# iBabs & NotuBiz Connector + +## Purpose + +Provides bidirectional integration with iBabs and NotuBiz — the two dominant raadsinformatiesystemen (RIS) used by Dutch municipalities for bestuurlijke besluitvorming (B&W/College). Found as a requirement in 20+ tenders: iBabs in 12+ and NotuBiz in 8+. The connector pushes collegevoorstellen and documents from Procest to the RIS for vergaderbehandeling, and receives besluiten and besluitenlijsten back into the zaak. Implements the standard B&W workflow pattern: documenten heen, besluiten terug. + +## Requirements + +### iBabs API Integration + +| ID | Requirement | Priority | Status | +|----|------------|----------|--------| +| RIS-001 | Connect to the iBabs REST API with API key authentication | MUST | Planned | +| RIS-002 | Push a collegevoorstel (advies + bijlagen) to iBabs as a vergaderstuk | MUST | Planned | +| RIS-003 | Create or update an agendapunt in iBabs linked to the collegevoorstel | MUST | Planned | +| RIS-004 | Retrieve besluiten from iBabs after vergaderbehandeling | MUST | Planned | +| RIS-005 | Retrieve the besluitenlijst (PDF/document) from iBabs | MUST | Planned | +| RIS-006 | Support iBabs document upload (PDF, DOCX) with metadata (onderwerp, portefeuillehouder, zaaktype) | MUST | Planned | +| RIS-007 | Support iBabs vergadering retrieval: list upcoming and past vergaderingen with agendapunten | SHOULD | Planned | +| RIS-008 | Map iBabs besluit status (aangenomen, verworpen, aangehouden, doorgeschoven) to Procest zaak status updates | MUST | Planned | + +### NotuBiz API Integration + +| ID | Requirement | Priority | Status | +|----|------------|----------|--------| +| RIS-020 | Connect to the NotuBiz API with OAuth2 or API key authentication | MUST | Planned | +| RIS-021 | Push vergaderstukken (voorstel + bijlagen) to NotuBiz | MUST | Planned | +| RIS-022 | Create or update agendapunten in NotuBiz linked to vergaderstukken | MUST | Planned | +| RIS-023 | Retrieve besluiten and besluitenlijst from NotuBiz after behandeling | MUST | Planned | +| RIS-024 | Support NotuBiz event types: collegevergadering, raadsvergadering, commissievergadering | SHOULD | Planned | +| RIS-025 | Map NotuBiz besluit metadata to Procest zaak properties | MUST | Planned | + +### Bidirectional Sync + +| ID | Requirement | Priority | Status | +|----|------------|----------|--------| +| RIS-030 | Outbound sync: when a zaak reaches status "Ter besluitvorming" in Procest, automatically push voorstel to configured RIS | MUST | Planned | +| RIS-031 | Inbound sync: poll or webhook for besluit updates from RIS, update the source zaak in Procest | MUST | Planned | +| RIS-032 | Conflict detection: if a zaak has been modified in both Procest and the RIS, flag for manual resolution | SHOULD | Planned | +| RIS-033 | Sync history: log all sync operations (push/pull, timestamp, status, document IDs) as OpenRegister objects for audit trail | MUST | Planned | +| RIS-034 | Retry failed syncs with configurable backoff (default: 3 retries, 5/15/60 minute intervals) | SHOULD | Planned | + +### Document Flow + +| ID | Requirement | Priority | Status | +|----|------------|----------|--------| +| RIS-040 | Outbound documents: export from Nextcloud Files, convert to PDF if needed (via Docudesk), push to RIS | MUST | Planned | +| RIS-041 | Inbound documents: download besluit/besluitenlijst from RIS, store in Nextcloud Files, link to zaak | MUST | Planned | +| RIS-042 | Document metadata mapping: onderwerp, datum, portefeuillehouder, zaaktype, geheimhouding | MUST | Planned | +| RIS-043 | Support geheimhouding flag: mark documents as vertrouwelijk in the RIS when the zaak has geheimhouding | SHOULD | Planned | + +### Parafering Support (Ambtelijk Deel) + +| ID | Requirement | Priority | Status | +|----|------------|----------|--------| +| RIS-050 | Track parafering status within Procest before pushing to RIS: steller > adviseur > parafeerder > portefeuillehouder > secretariaat | MUST | Planned | +| RIS-051 | Only push to RIS after all required paraferingen are completed (configurable per zaaktype) | MUST | Planned | +| RIS-052 | Parafering route is configurable: sequential, parallel, or mixed per zaaktype | SHOULD | Planned | +| RIS-053 | Mobile-friendly parafering: API supports paraferen from any device (responsive UI in Procest) | SHOULD | Planned | + +### OpenConnector Integration + +| ID | Requirement | Priority | Status | +|----|------------|----------|--------| +| RIS-060 | Registered as an OpenConnector endpoint type with separate configurations for iBabs and NotuBiz | MUST | Planned | +| RIS-061 | Connection settings: API URL, authentication credentials, organisatie-ID, default vergadertype | MUST | Planned | +| RIS-062 | Health check: validate API connectivity and authentication | SHOULD | Planned | +| RIS-063 | n8n workflow integration: connector can be triggered from n8n nodes for custom B&W-besluitvorming workflows | SHOULD | Planned | + +## Data Model + +### Sync Record (stored in OpenRegister) + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| zaakId | string (UUID) | Yes | Source zaak in Procest | +| risType | string (enum) | Yes | `ibabs` or `notubiz` | +| risDocumentId | string | No | Document/agendapunt ID in the RIS | +| direction | string (enum) | Yes | `outbound` (push) or `inbound` (pull) | +| status | string (enum) | Yes | `pending`, `synced`, `failed`, `conflict` | +| syncedAt | datetime | No | Timestamp of last successful sync | +| errorMessage | string | No | Error details if status is `failed` | +| documents | array | No | List of document references (Nextcloud file ID + RIS doc ID) | + +## Scenarios + +### Push collegevoorstel to iBabs + +``` +GIVEN a zaak "Bestemmingsplan Centrum" has completed parafering in Procest +AND the zaak reaches status "Ter besluitvorming" +WHEN the outbound sync triggers +THEN the voorstel document and bijlagen are exported from Nextcloud +AND pushed to iBabs as vergaderstukken with metadata (onderwerp, portefeuillehouder) +AND an agendapunt is created for the next collegevergadering +AND a sync record is stored with status "synced" +``` + +### Receive besluit from iBabs + +``` +GIVEN a collegevoorstel was pushed to iBabs for zaak "Bestemmingsplan Centrum" +AND the college has behandeld the voorstel +WHEN the inbound sync polls iBabs for updates +THEN the besluit (aangenomen/verworpen) is retrieved +AND the besluitenlijst PDF is downloaded and stored in Nextcloud Files +AND the zaak status in Procest is updated to reflect the besluit +AND the besluit document is linked to the zaak +``` + +### NotuBiz raadsvergadering flow + +``` +GIVEN a collegevoorstel requires raadsbehandeling after collegebesluit +WHEN the connector pushes stukken to NotuBiz for raadsvergadering +THEN vergaderstukken are uploaded with commissie/raad metadata +AND after raadsbehandeling, the raadsbesluit is synced back to Procest +``` + +### Failed sync with retry + +``` +GIVEN a voorstel push to iBabs fails due to API timeout +WHEN the first retry is triggered after 5 minutes +THEN if it succeeds, the sync record is updated to "synced" +AND if all 3 retries fail, the sync record is set to "failed" with error details +AND a notification is sent to the behandelaar +``` + +## Dependencies + +- **OpenConnector**: Endpoint registration and connection management +- **OpenRegister**: Sync record storage and zaak object access +- **Procest**: Zaak lifecycle management and parafering workflow +- **Docudesk**: PDF conversion for outbound documents +- **iBabs REST API**: External service (api.ibabs.eu) +- **NotuBiz API**: External service (api.notubiz.nl) diff --git a/openspec/specs/stuf-adapter/spec.md b/openspec/specs/stuf-adapter/spec.md new file mode 100644 index 000000000..326c110e5 --- /dev/null +++ b/openspec/specs/stuf-adapter/spec.md @@ -0,0 +1,126 @@ +--- +status: proposed +--- + +# StUF Adapter + +## Purpose + +Provides bidirectional translation between modern REST/ZGW APIs and legacy StUF-BG (personen/adressen) and StUF-ZKN (zaken/documenten) SOAP-based interfaces. 79% of Dutch government tenders still require StUF support despite the migration to ZGW APIs. The adapter enables OpenRegister objects to be exposed as StUF services (for legacy consumers) and allows OpenConnector to query legacy StUF sources (for data import). Supports StUF-BG 3.10 and StUF-ZKN 3.10/3.10e. + +## Requirements + +### StUF-BG Inbound (Legacy Consumer Queries OpenRegister) + +| ID | Requirement | Priority | Status | +|----|------------|----------|--------| +| STUF-001 | Expose a SOAP endpoint that accepts StUF-BG 3.10 `npsLv01` (persoon opvragen) requests | MUST | Planned | +| STUF-002 | Map StUF-BG person fields (`bsn`, `geslachtsnaam`, `voorvoegsel`, `voornamen`, `geboortedatum`, `verblijfsadres`) to OpenRegister object properties | MUST | Planned | +| STUF-003 | Expose `npsLa01` (persoon antwoord) response with correctly formed StUF-BG XML | MUST | Planned | +| STUF-004 | Support StUF-BG `adrLv01` (adres opvragen) and `adrLa01` (adres antwoord) for BAG-adressen | SHOULD | Planned | +| STUF-005 | Support `scope` element filtering — return only requested fields in the response | MUST | Planned | +| STUF-006 | Handle StUF `sortering` and `maximumAantal` parameters for result limiting | SHOULD | Planned | + +### StUF-BG Outbound (OpenConnector Queries Legacy Source) + +| ID | Requirement | Priority | Status | +|----|------------|----------|--------| +| STUF-010 | Query external StUF-BG services via SOAP and map responses to OpenRegister objects | MUST | Planned | +| STUF-011 | Support certificate-based mutual TLS authentication (PKIoverheid) for StUF endpoints | MUST | Planned | +| STUF-012 | Support WS-Security (UsernameToken) authentication for StUF endpoints | MUST | Planned | +| STUF-013 | Parse StUF-BG `npsLa01` responses and extract person/address data into flat JSON | MUST | Planned | +| STUF-014 | Handle StUF `Fo01`/`Fo02` fault messages and map to HTTP error responses with diagnostic info | MUST | Planned | + +### StUF-ZKN Inbound (Legacy Consumer Manages Zaken) + +| ID | Requirement | Priority | Status | +|----|------------|----------|--------| +| STUF-020 | Expose a SOAP endpoint that accepts StUF-ZKN 3.10 `zakLk01` (zaak aanmaken/bijwerken) messages | MUST | Planned | +| STUF-021 | Map StUF-ZKN zaak fields (`zaakidentificatie`, `omschrijving`, `startdatum`, `einddatum`, `zaaktype`, `status`) to Procest zaak objects in OpenRegister | MUST | Planned | +| STUF-022 | Support `edcLk01` (document koppelen aan zaak) for document management via StUF-ZKN | SHOULD | Planned | +| STUF-023 | Support `zakLv01` (zaak opvragen) and respond with `zakLa01` including related documenten and statussen | MUST | Planned | +| STUF-024 | Handle `Bv03` (bevestiging) and `Fo03` (foutmelding) asynchronous response patterns | SHOULD | Planned | + +### StUF-ZKN Outbound (OpenConnector Queries Legacy Zaaksysteem) + +| ID | Requirement | Priority | Status | +|----|------------|----------|--------| +| STUF-030 | Query external StUF-ZKN services for zaak data and map to OpenRegister objects | MUST | Planned | +| STUF-031 | Support `genereerZaakIdentificatie` for obtaining zaak IDs from legacy systems | SHOULD | Planned | +| STUF-032 | Support document retrieval via `edcLv01` and store in Nextcloud Files | SHOULD | Planned | + +### SOAP/XML Processing + +| ID | Requirement | Priority | Status | +|----|------------|----------|--------| +| STUF-040 | WSDL files for StUF-BG 3.10 and StUF-ZKN 3.10 are bundled with the adapter | MUST | Planned | +| STUF-041 | XML namespace handling for `StUF`, `BG`, `ZKN`, `xsi`, `gml` namespaces | MUST | Planned | +| STUF-042 | StUF `stuurgegevens` (zender, ontvanger, referentienummer, tijdstip) correctly populated on all messages | MUST | Planned | +| STUF-043 | StUF `noValue` attribute handling: `geenWaarde`, `nietOndersteund`, `waardeOnbekend`, `vastgesteldOnbekend` | MUST | Planned | +| STUF-044 | XML schema validation of outbound messages against StUF XSD schemas | SHOULD | Planned | + +### Field Mapping Configuration + +| ID | Requirement | Priority | Status | +|----|------------|----------|--------| +| STUF-050 | Field mappings between StUF XML paths and OpenRegister object properties are configurable via mapping objects stored in OpenRegister | MUST | Planned | +| STUF-051 | Default mapping configurations for BRP-personen (StUF-BG) and ZGW-zaken (StUF-ZKN) are pre-seeded | MUST | Planned | +| STUF-052 | Custom mappings can be added for municipality-specific StUF extensions | SHOULD | Planned | +| STUF-053 | Mapping supports value transformations: date format conversion (StUF `YYYYMMDD` to ISO 8601), code list lookups, string concatenation | MUST | Planned | + +### OpenConnector Integration + +| ID | Requirement | Priority | Status | +|----|------------|----------|--------| +| STUF-060 | The adapter is registered as an OpenConnector source type, configurable via the connector UI | MUST | Planned | +| STUF-061 | Connection settings: endpoint URL, authentication method (mTLS/WS-Security), certificates, zender/ontvanger codes | MUST | Planned | +| STUF-062 | Health check: validate connectivity and authentication against the StUF endpoint | SHOULD | Planned | + +## Scenarios + +### Query BRP via StUF-BG + +``` +GIVEN an external StUF-BG service is configured in OpenConnector +WHEN a user or workflow requests person data by BSN +THEN OpenConnector sends a StUF-BG npsLv01 SOAP request +AND parses the npsLa01 response +AND returns a JSON object with mapped person fields +``` + +### Legacy system queries zaak via StUF-ZKN + +``` +GIVEN a legacy application sends a StUF-ZKN zakLv01 SOAP request +WHEN the adapter receives the request at the SOAP endpoint +THEN it resolves the zaak from OpenRegister by zaakidentificatie +AND returns a zakLa01 response with zaak data, statussen, and documenten +AND stuurgegevens are correctly populated with the adapter's zender code +``` + +### Create zaak from StUF-ZKN message + +``` +GIVEN a legacy formulierensysteem sends a StUF-ZKN zakLk01 message +WHEN the adapter receives the create-zaak message +THEN it maps the StUF fields to OpenRegister properties +AND creates a zaak object in Procest's register +AND returns a Bv03 bevestiging message +``` + +### Certificate-based authentication + +``` +GIVEN a StUF endpoint requires PKIoverheid mTLS +WHEN the connection is configured with client certificate and key +THEN SOAP requests include the client certificate +AND the server's certificate is validated against the PKIoverheid chain +``` + +## Dependencies + +- **OpenConnector**: Source/endpoint registration and connection management +- **OpenRegister**: Object storage and field mapping configuration +- **PHP SOAP extension**: SOAP client/server functionality +- **PKIoverheid root certificates**: For mTLS validation +- **StUF-BG 3.10 and StUF-ZKN 3.10 XSD schemas**: For XML validation From 45644173b42dcda8482ac702c45729f5936860af Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Mon, 16 Mar 2026 23:14:18 +0100 Subject: [PATCH 05/17] feat: Enrich 3 specs, add prometheus metrics Spec enrichment: - stuf-adapter: partial impl (SOAPService has StUF-ZKN awareness, no inbound SOAP server) - dso-omgevingsloket: not implemented, foundational infrastructure exists - ibabs-notubiz-connector: not implemented, foundational infrastructure exists Implementation: - MetricsController: sources by type, calls by status, sync operations - HealthController: database + sources table checks --- .github/workflows/sbom.yml | 91 + appinfo/routes.php | 7 + composer.json | 7 +- css/dashboardWidgets.css | 8 + docs/GOVERNMENT-FEATURES.md | 132 ++ lib/Controller/HealthController.php | 106 ++ lib/Controller/MetricsController.php | 257 +++ lib/Dashboard/JobQueueWidget.php | 84 + lib/Dashboard/RecentCallsWidget.php | 84 + lib/Dashboard/SourceSyncWidget.php | 84 + openspec/specs/dso-omgevingsloket/spec.md | 75 + .../specs/ibabs-notubiz-connector/spec.md | 74 + openspec/specs/prometheus-metrics/spec.md | 42 + openspec/specs/stuf-adapter/spec.md | 80 + package-lock.json | 1599 ++++++++++++++++- package.json | 1 + src/jobQueueWidget.js | 15 + src/recentCallsWidget.js | 15 + src/sourceSyncWidget.js | 15 + src/views/widgets/JobQueueWidget.vue | 113 ++ src/views/widgets/RecentCallsWidget.vue | 123 ++ src/views/widgets/SourceSyncWidget.vue | 125 ++ 22 files changed, 3092 insertions(+), 45 deletions(-) create mode 100644 .github/workflows/sbom.yml create mode 100644 css/dashboardWidgets.css create mode 100644 docs/GOVERNMENT-FEATURES.md create mode 100644 lib/Controller/HealthController.php create mode 100644 lib/Controller/MetricsController.php create mode 100644 lib/Dashboard/JobQueueWidget.php create mode 100644 lib/Dashboard/RecentCallsWidget.php create mode 100644 lib/Dashboard/SourceSyncWidget.php create mode 100644 openspec/specs/prometheus-metrics/spec.md create mode 100644 src/jobQueueWidget.js create mode 100644 src/recentCallsWidget.js create mode 100644 src/sourceSyncWidget.js create mode 100644 src/views/widgets/JobQueueWidget.vue create mode 100644 src/views/widgets/RecentCallsWidget.vue create mode 100644 src/views/widgets/SourceSyncWidget.vue diff --git a/.github/workflows/sbom.yml b/.github/workflows/sbom.yml new file mode 100644 index 000000000..ac53113d5 --- /dev/null +++ b/.github/workflows/sbom.yml @@ -0,0 +1,91 @@ +name: SBOM + +on: + push: + branches: [main, development, feature/**, bugfix/**, hotfix/**] + pull_request: + branches: [main, development] + +jobs: + sbom: + runs-on: ubuntu-latest + name: "SBOM Generation & Validation" + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref || github.ref_name }} + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.3" + extensions: mbstring, intl, zip, gd, curl, xml, json + tools: composer:v2 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Cache Composer dependencies + uses: actions/cache@v4 + with: + path: vendor + key: ${{ runner.os }}-composer-${{ hashFiles('composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install Composer dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + + - name: Generate PHP SBOM + run: composer CycloneDX:make-sbom --output-format=JSON --output-file=bom-php.cdx.json --spec-version=1.5 --omit=dev --omit=plugin + + - name: Install npm dependencies + run: npm ci + + - name: Generate npm SBOM + run: npx @cyclonedx/cyclonedx-npm --output-file bom-npm.cdx.json --spec-version 1.5 --omit dev + + - name: Merge PHP + npm SBOMs + run: | + jq -s '.[0] * {components: ([.[].components[]?] | unique_by(.purl // .name))}' bom-php.cdx.json bom-npm.cdx.json > sbom.cdx.json + + - name: Install Grype + uses: anchore/scan-action/download-grype@v5 + + - name: CVE scan SBOM + run: grype sbom:sbom.cdx.json --fail-on critical + + - name: Composer audit + run: composer audit --format=json || true + + - name: npm audit + run: npm audit --audit-level=critical + + - name: Commit SBOM + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add sbom.cdx.json + if git diff --cached --quiet; then + echo "No SBOM changes to commit" + else + git commit -m "chore: update SBOM" + git push + fi + + - name: Upload SBOM artifact + uses: actions/upload-artifact@v4 + with: + name: sbom-openconnector + path: sbom.cdx.json + retention-days: 90 + + - name: Attach SBOM to release + if: ${{ startsWith(github.ref, 'refs/tags/') }} + uses: softprops/action-gh-release@v2 + with: + files: sbom.cdx.json diff --git a/appinfo/routes.php b/appinfo/routes.php index f6da0a778..85a60aa24 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -14,6 +14,11 @@ ], 'routes' => [ ['name' => 'dashboard#page', 'url' => '/', 'verb' => 'GET'], + + // Metrics and health + ['name' => 'metrics#index', 'url' => '/api/metrics', 'verb' => 'GET'], + ['name' => 'health#index', 'url' => '/api/health', 'verb' => 'GET'], + ['name' => 'dashboard#index', 'url' => '/api/dashboard', 'verb' => 'GET'], ['name' => 'dashboard#getCallStats', 'url' => '/api/dashboard/callstats', 'verb' => 'GET'], ['name' => 'dashboard#getJobStats', 'url' => '/api/dashboard/jobstats', 'verb' => 'GET'], @@ -116,5 +121,7 @@ ['name' => 'ui#cloudEventsEventsId', 'url' => '/cloud-events/events/{id}', 'verb' => 'GET'], ['name' => 'ui#cloudEventsLogs', 'url' => '/cloud-events/logs', 'verb' => 'GET'], ['name' => 'ui#import', 'url' => '/import', 'verb' => 'GET'], + // SPA catch-all — serves the Vue app for any frontend route (history mode routing) + ['name' => 'dashboard#page', 'url' => '/{path}', 'verb' => 'GET', 'requirements' => ['path' => '.+'], 'defaults' => ['path' => '']], ], ]; diff --git a/composer.json b/composer.json index 18e223bc2..cdc59f326 100644 --- a/composer.json +++ b/composer.json @@ -74,14 +74,17 @@ "phpro/grumphp": "^2.9", "phpunit/phpunit": "^10.5", "roave/security-advisories": "dev-latest", - "squizlabs/php_codesniffer": "^3.9" + "squizlabs/php_codesniffer": "^3.9", + "cyclonedx/cyclonedx-php-composer": "^6.2" }, "config": { "allow-plugins": { "bamarni/composer-bin-plugin": true, "php-http/discovery": true, + "cyclonedx/cyclonedx-php-composer": true, "phpro/grumphp": true, - "dealerdirect/phpcodesniffer-composer-installer": true + "dealerdirect/phpcodesniffer-composer-installer": true, + "cyclonedx/cyclonedx-php-composer": true }, "optimize-autoloader": true, "sort-packages": true, diff --git a/css/dashboardWidgets.css b/css/dashboardWidgets.css new file mode 100644 index 000000000..e60d69541 --- /dev/null +++ b/css/dashboardWidgets.css @@ -0,0 +1,8 @@ +.icon-openconnector-widget { + background-image: url("../img/app-dark.svg"); + filter: var(--background-invert-if-dark); +} + +body.theme--dark .icon-openconnector-widget { + background-image: url("../img/app.svg"); +} diff --git a/docs/GOVERNMENT-FEATURES.md b/docs/GOVERNMENT-FEATURES.md new file mode 100644 index 000000000..c6aeea0da --- /dev/null +++ b/docs/GOVERNMENT-FEATURES.md @@ -0,0 +1,132 @@ +# Open Connector — Overheidsfunctionaliteiten + +> Functiepagina voor Nederlandse overheidsorganisaties. +> Gebruik deze checklist om te toetsen aan uw Programma van Eisen. + +**Product:** Open Connector +**Categorie:** Enterprise Service Bus (ESB) & API Gateway +**Licentie:** AGPL (vrije open source) +**Leverancier:** Conduction B.V. +**Platform:** Nextcloud (self-hosted / on-premise / cloud) + +## Legenda + +| Status | Betekenis | +|--------|-----------| +| Beschikbaar | Functionaliteit is beschikbaar in de huidige versie | +| Gepland | Functionaliteit staat op de roadmap | +| Via platform | Functionaliteit wordt geleverd door Nextcloud | +| Op aanvraag | Beschikbaar als maatwerk | +| N.v.t. | Niet van toepassing voor dit product | + +--- + +## 1. Functionele eisen + +### API Gateway & Service Bus + +| # | Eis | Status | Toelichting | +|---|-----|--------|-------------| +| F-01 | API-aanroepen mappen en vertalen | Beschikbaar | REST-naar-REST, SOAP-naar-REST | +| F-02 | Databronnen synchroniseren | Beschikbaar | Geautomatiseerde bronsynchronisatie | +| F-03 | Cloud Events verzenden en ontvangen | Beschikbaar | Event-driven architectuur | +| F-04 | Geplande taken (cron-jobs) | Beschikbaar | Periodieke synchronisatie en verwerking | +| F-05 | Logbeheer en opschoning | Beschikbaar | Automatische log cleanup | + +### Koppelingen & Integratie + +| # | Eis | Status | Toelichting | +|---|-----|--------|-------------| +| F-06 | StUF-naar-REST vertaling | Beschikbaar | Legacy XML-standaard vertalen | +| F-07 | SOAP-naar-REST vertaling | Beschikbaar | Oude webservices ontsluiten | +| F-08 | Configureerbare endpoints | Beschikbaar | Admin-UI voor koppelingen | +| F-09 | Authenticatie-relay (OAuth, API keys, certificaten) | Beschikbaar | Doorvertaling van authenticatie | +| F-10 | Datavalidatie en -transformatie | Beschikbaar | Mapping en filtering van data | + +--- + +## 2. Technische eisen + +| # | Eis | Status | Toelichting | +|---|-----|--------|-------------| +| T-01 | On-premise / self-hosted | Beschikbaar | Nextcloud-app | +| T-02 | Open source | Beschikbaar | AGPL, GitHub | +| T-03 | RESTful API | Beschikbaar | API voor configuratie en monitoring | +| T-04 | Cron-gebaseerde taken | Beschikbaar | Background jobs via Nextcloud cron | +| T-05 | Database-onafhankelijkheid | Beschikbaar | PostgreSQL, MySQL, SQLite | +| T-06 | Containerisatie (Docker) | Beschikbaar | Docker Compose | +| T-07 | curl-gebaseerd (geen externe dependencies) | Beschikbaar | Alleen PHP curl vereist | + +--- + +## 3. Beveiligingseisen + +| # | Eis | Status | Toelichting | +|---|-----|--------|-------------| +| B-01 | RBAC | Via platform | Nextcloud admin-rechten | +| B-02 | Audit trail / logging | Beschikbaar | Verwerking logs met opschoning | +| B-03 | BIO-compliance | Via platform | Nextcloud BIO | +| B-04 | 2FA | Via platform | Nextcloud 2FA | +| B-05 | SSO / SAML / LDAP | Via platform | Nextcloud SSO | +| B-06 | Versleuteling (rust + transit) | Via platform | Nextcloud encryption + TLS | +| B-07 | Certificaat-authenticatie naar externe systemen | Beschikbaar | PKI/mTLS ondersteuning | + +--- + +## 4. Privacyeisen (AVG/GDPR) + +| # | Eis | Status | Toelichting | +|---|-----|--------|-------------| +| P-01 | Geen permanente dataopslag van doorgevoerde gegevens | Beschikbaar | Connector verwerkt, slaat niet op | +| P-02 | Log-opschoning (configureerbaar) | Beschikbaar | Automatische verwijdering van oude logs | +| P-03 | Data minimalisatie | Beschikbaar | Alleen noodzakelijke velden doorgeven via mapping | + +--- + +## 5. Toegankelijkheidseisen + +| # | Eis | Status | Toelichting | +|---|-----|--------|-------------| +| A-01 | WCAG 2.1 AA (admin-UI) | Beschikbaar | Nextcloud-componenten | +| A-02 | Meertalig (NL/EN) | Beschikbaar | Volledige vertaling | + +--- + +## 6. Integratiestandaarden + +| # | Eis | Status | Toelichting | +|---|-----|--------|-------------| +| I-01 | Common Ground architectuur | Beschikbaar | Laag 3 (integratie) — ESB-functionaliteit | +| I-02 | StUF-koppelvlak | Beschikbaar | Vertaling van StUF XML naar REST | +| I-03 | SOAP-koppelvlak | Beschikbaar | Vertaling van SOAP naar REST | +| I-04 | REST API | Beschikbaar | Standaard REST-koppelingen | +| I-05 | Cloud Events | Beschikbaar | Event-driven integratie standaard | +| I-06 | OAuth 2.0 / OpenID Connect | Beschikbaar | Moderne authenticatie-relay | +| I-07 | API-key authenticatie | Beschikbaar | Eenvoudige API-toegang | +| I-08 | Certificaat-authenticatie (mTLS) | Beschikbaar | PKIoverheid-certificaten | + +--- + +## 7. Beheer en onderhoud + +| # | Eis | Status | Toelichting | +|---|-----|--------|-------------| +| BO-01 | Nextcloud App Store | Beschikbaar | Installatie via App Store | +| BO-02 | Automatische updates | Beschikbaar | Via Nextcloud app-updater | +| BO-03 | Beheerderspaneel | Beschikbaar | Nextcloud admin settings | +| BO-04 | Monitoring | Beschikbaar | Log-inzicht en foutmeldingen | +| BO-05 | Open source community | Beschikbaar | GitHub Issues | +| BO-06 | Professionele ondersteuning (SLA) | Op aanvraag | Via Conduction B.V. | + +--- + +## 8. Onderscheidende kenmerken + +| Kenmerk | Toelichting | +|---------|-------------| +| **StUF-vertaling** | Enige Nextcloud-app die StUF XML kan vertalen naar REST | +| **Nextcloud-native ESB** | Geen apart integratie-platform nodig | +| **Lichtgewicht** | Alleen PHP + curl, geen Java/Spring | +| **Common Ground laag 3** | Past in de Common Ground integratie-architectuur | +| **Event-driven** | Cloud Events voor real-time integratie | +| **Zero-footprint** | Connector verwerkt data door, slaat niets permanent op | diff --git a/lib/Controller/HealthController.php b/lib/Controller/HealthController.php new file mode 100644 index 000000000..5f84c0f3a --- /dev/null +++ b/lib/Controller/HealthController.php @@ -0,0 +1,106 @@ + + * @copyright 2024 Conduction B.V. + * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * @version GIT: + * + * @link https://www.OpenConnector.nl + */ + +declare(strict_types=1); + +namespace OCA\OpenConnector\Controller; + +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\JSONResponse; +use OCP\IDBConnection; +use OCP\IRequest; +use Psr\Log\LoggerInterface; + +/** + * Controller for health check endpoint. + * + * Returns JSON indicating whether the application and its dependencies are healthy. + */ +class HealthController extends Controller +{ + + + /** + * HealthController constructor. + * + * @param string $appName The name of the app + * @param IRequest $request Request object + * @param IDBConnection $db The database connection + * @param LoggerInterface $logger Logger for error handling + */ + public function __construct( + string $appName, + IRequest $request, + private readonly IDBConnection $db, + private readonly LoggerInterface $logger + ) { + parent::__construct($appName, $request); + + }//end __construct() + + + /** + * Return health check status. + * + * @return JSONResponse JSON response with health status and checks. + * + * @NoCSRFRequired + */ + public function index(): JSONResponse + { + $checks = []; + $status = 'ok'; + + // Database check. + try { + $qb = $this->db->getQueryBuilder(); + $qb->select($qb->createFunction('1')); + $result = $qb->executeQuery(); + $result->closeCursor(); + $checks['database'] = 'ok'; + } catch (\Exception $e) { + $checks['database'] = 'error'; + $status = 'error'; + $this->logger->error('Health check: database failed', ['exception' => $e->getMessage()]); + } + + // Source table check. + try { + $qb = $this->db->getQueryBuilder(); + $qb->select($qb->createFunction('COUNT(*) AS cnt')) + ->from('openconnector_sources'); + $result = $qb->executeQuery(); + $result->closeCursor(); + $checks['sources_table'] = 'ok'; + } catch (\Exception $e) { + $checks['sources_table'] = 'error'; + $status = 'degraded'; + $this->logger->warning('Health check: sources table not accessible', ['exception' => $e->getMessage()]); + } + + return new JSONResponse( + [ + 'status' => $status, + 'checks' => $checks, + ] + ); + + }//end index() + + +}//end class diff --git a/lib/Controller/MetricsController.php b/lib/Controller/MetricsController.php new file mode 100644 index 000000000..4c9374d99 --- /dev/null +++ b/lib/Controller/MetricsController.php @@ -0,0 +1,257 @@ + + * @copyright 2024 Conduction B.V. + * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * @version GIT: + * + * @link https://www.OpenConnector.nl + */ + +declare(strict_types=1); + +namespace OCA\OpenConnector\Controller; + +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\TextPlainResponse; +use OCP\IConfig; +use OCP\IDBConnection; +use OCP\IRequest; +use Psr\Log\LoggerInterface; + +/** + * Controller for exposing Prometheus metrics. + * + * Provides a metrics endpoint returning data in Prometheus text exposition format + * for monitoring sources, calls, and synchronizations. + */ +class MetricsController extends Controller +{ + + + /** + * MetricsController constructor. + * + * @param string $appName The name of the app + * @param IRequest $request Request object + * @param IConfig $config The config service + * @param IDBConnection $db The database connection + * @param LoggerInterface $logger Logger for error handling + */ + public function __construct( + string $appName, + IRequest $request, + private readonly IConfig $config, + private readonly IDBConnection $db, + private readonly LoggerInterface $logger + ) { + parent::__construct($appName, $request); + + }//end __construct() + + + /** + * Expose Prometheus metrics. + * + * @return TextPlainResponse Plain text response with Prometheus metrics. + * + * @NoCSRFRequired + */ + public function index(): TextPlainResponse + { + $lines = []; + + $appVersion = $this->config->getAppValue('openconnector', 'installed_version', '0.0.0'); + $phpVersion = PHP_VERSION; + $ncVersion = $this->config->getSystemValueString('version', '0.0.0'); + + // Info gauge. + $lines[] = '# HELP openconnector_info Application information'; + $lines[] = '# TYPE openconnector_info gauge'; + $lines[] = 'openconnector_info{version="'.$appVersion.'",php_version="'.$phpVersion.'",nextcloud_version="'.$ncVersion.'"} 1'; + + // Up gauge. + $lines[] = '# HELP openconnector_up Whether the application is up'; + $lines[] = '# TYPE openconnector_up gauge'; + $lines[] = 'openconnector_up 1'; + + // Sources total by type. + $this->collectSourceMetrics($lines); + + // Calls total by status. + $this->collectCallMetrics($lines); + + // Synchronizations total by status. + $this->collectSyncMetrics($lines); + + $body = implode("\n", $lines)."\n"; + $response = new TextPlainResponse($body); + $response->addHeader('Content-Type', 'text/plain; version=0.0.4; charset=utf-8'); + + return $response; + + }//end index() + + + /** + * Collect source metrics grouped by type. + * + * @param array $lines Reference to the metrics output lines. + * + * @return void + */ + private function collectSourceMetrics(array &$lines): void + { + $lines[] = '# HELP openconnector_sources_total Total sources by type'; + $lines[] = '# TYPE openconnector_sources_total gauge'; + + try { + $qb = $this->db->getQueryBuilder(); + $qb->select('type', $qb->createFunction('COUNT(*) AS cnt')) + ->from('openconnector_sources') + ->groupBy('type'); + + $result = $qb->executeQuery(); + $rows = $result->fetchAll(); + $result->closeCursor(); + + $counts = []; + foreach ($rows as $row) { + $type = ($row['type'] !== null && $row['type'] !== '') ? strtolower($row['type']) : 'rest'; + $counts[$type] = (isset($counts[$type]) === true) ? $counts[$type] + (int) $row['cnt'] : (int) $row['cnt']; + } + + if (empty($counts) === true) { + $lines[] = 'openconnector_sources_total{type="rest"} 0'; + } else { + foreach ($counts as $type => $count) { + $lines[] = 'openconnector_sources_total{type="'.$type.'"} '.$count; + } + } + } catch (\Exception $e) { + $this->logger->warning('Could not count sources for metrics', ['exception' => $e->getMessage()]); + $lines[] = 'openconnector_sources_total{type="rest"} 0'; + }//end try + + }//end collectSourceMetrics() + + + /** + * Collect call log metrics grouped by status code. + * + * @param array $lines Reference to the metrics output lines. + * + * @return void + */ + private function collectCallMetrics(array &$lines): void + { + $lines[] = '# HELP openconnector_calls_total Total API calls by status'; + $lines[] = '# TYPE openconnector_calls_total counter'; + + try { + $qb = $this->db->getQueryBuilder(); + $qb->select('status_code', $qb->createFunction('COUNT(*) AS cnt')) + ->from('openconnector_call_logs') + ->groupBy('status_code'); + + $result = $qb->executeQuery(); + $rows = $result->fetchAll(); + $result->closeCursor(); + + if (empty($rows) === true) { + $lines[] = 'openconnector_calls_total{status="200"} 0'; + } else { + foreach ($rows as $row) { + $statusCode = $row['status_code'] ?? 'unknown'; + $lines[] = 'openconnector_calls_total{status="'.$statusCode.'"} '.(int) $row['cnt']; + } + } + } catch (\Exception $e) { + $this->logger->warning('Could not count calls for metrics', ['exception' => $e->getMessage()]); + $lines[] = 'openconnector_calls_total{status="200"} 0'; + }//end try + + }//end collectCallMetrics() + + + /** + * Collect synchronization metrics grouped by status. + * + * @param array $lines Reference to the metrics output lines. + * + * @return void + */ + private function collectSyncMetrics(array &$lines): void + { + $lines[] = '# HELP openconnector_synchronizations_total Total synchronization runs'; + $lines[] = '# TYPE openconnector_synchronizations_total gauge'; + + try { + $total = $this->countTable('openconnector_synchronizations'); + $lines[] = 'openconnector_synchronizations_total '.$total; + } catch (\Exception $e) { + $this->logger->warning('Could not count synchronizations for metrics', ['exception' => $e->getMessage()]); + $lines[] = 'openconnector_synchronizations_total 0'; + } + + // Sync logs by result for counter metric. + $lines[] = '# HELP openconnector_synchronization_runs_total Total synchronization log entries by result'; + $lines[] = '# TYPE openconnector_synchronization_runs_total counter'; + + try { + $qb = $this->db->getQueryBuilder(); + $qb->select('result', $qb->createFunction('COUNT(*) AS cnt')) + ->from('openconnector_synchronization_logs') + ->groupBy('result'); + + $result = $qb->executeQuery(); + $rows = $result->fetchAll(); + $result->closeCursor(); + + if (empty($rows) === true) { + $lines[] = 'openconnector_synchronization_runs_total{status="success"} 0'; + } else { + foreach ($rows as $row) { + $resultLabel = ($row['result'] !== null && $row['result'] !== '') ? strtolower($row['result']) : 'unknown'; + $lines[] = 'openconnector_synchronization_runs_total{status="'.$resultLabel.'"} '.(int) $row['cnt']; + } + } + } catch (\Exception $e) { + $this->logger->warning('Could not count sync logs for metrics', ['exception' => $e->getMessage()]); + $lines[] = 'openconnector_synchronization_runs_total{status="success"} 0'; + }//end try + + }//end collectSyncMetrics() + + + /** + * Count rows in a given table. + * + * @param string $tableName The table name. + * + * @return int The row count. + */ + private function countTable(string $tableName): int + { + $qb = $this->db->getQueryBuilder(); + $qb->select($qb->createFunction('COUNT(*) AS cnt')) + ->from($tableName); + + $result = $qb->executeQuery(); + $count = (int) $result->fetchOne(); + $result->closeCursor(); + + return $count; + + }//end countTable() + + +}//end class diff --git a/lib/Dashboard/JobQueueWidget.php b/lib/Dashboard/JobQueueWidget.php new file mode 100644 index 000000000..e67540029 --- /dev/null +++ b/lib/Dashboard/JobQueueWidget.php @@ -0,0 +1,84 @@ +l10n->t('Taken wachtrij'); + + }//end getTitle() + + + /** + * @inheritDoc + */ + public function getOrder(): int + { + return 12; + + }//end getOrder() + + + /** + * @inheritDoc + */ + public function getIconClass(): string + { + return 'icon-openconnector-widget'; + + }//end getIconClass() + + + /** + * @inheritDoc + */ + public function getUrl(): ?string + { + return null; + + }//end getUrl() + + + /** + * @inheritDoc + */ + public function load(): void + { + Util::addScript(Application::APP_ID, Application::APP_ID.'-jobQueueWidget'); + Util::addStyle(Application::APP_ID, 'dashboardWidgets'); + + }//end load() + + +}//end class diff --git a/lib/Dashboard/RecentCallsWidget.php b/lib/Dashboard/RecentCallsWidget.php new file mode 100644 index 000000000..f78b7d501 --- /dev/null +++ b/lib/Dashboard/RecentCallsWidget.php @@ -0,0 +1,84 @@ +l10n->t('Recente calls'); + + }//end getTitle() + + + /** + * @inheritDoc + */ + public function getOrder(): int + { + return 11; + + }//end getOrder() + + + /** + * @inheritDoc + */ + public function getIconClass(): string + { + return 'icon-openconnector-widget'; + + }//end getIconClass() + + + /** + * @inheritDoc + */ + public function getUrl(): ?string + { + return null; + + }//end getUrl() + + + /** + * @inheritDoc + */ + public function load(): void + { + Util::addScript(Application::APP_ID, Application::APP_ID.'-recentCallsWidget'); + Util::addStyle(Application::APP_ID, 'dashboardWidgets'); + + }//end load() + + +}//end class diff --git a/lib/Dashboard/SourceSyncWidget.php b/lib/Dashboard/SourceSyncWidget.php new file mode 100644 index 000000000..eb008601b --- /dev/null +++ b/lib/Dashboard/SourceSyncWidget.php @@ -0,0 +1,84 @@ +l10n->t('Bron synchronisatie status'); + + }//end getTitle() + + + /** + * @inheritDoc + */ + public function getOrder(): int + { + return 10; + + }//end getOrder() + + + /** + * @inheritDoc + */ + public function getIconClass(): string + { + return 'icon-openconnector-widget'; + + }//end getIconClass() + + + /** + * @inheritDoc + */ + public function getUrl(): ?string + { + return null; + + }//end getUrl() + + + /** + * @inheritDoc + */ + public function load(): void + { + Util::addScript(Application::APP_ID, Application::APP_ID.'-sourceSyncWidget'); + Util::addStyle(Application::APP_ID, 'dashboardWidgets'); + + }//end load() + + +}//end class diff --git a/openspec/specs/dso-omgevingsloket/spec.md b/openspec/specs/dso-omgevingsloket/spec.md index dc03f583a..51c4d4e1d 100644 --- a/openspec/specs/dso-omgevingsloket/spec.md +++ b/openspec/specs/dso-omgevingsloket/spec.md @@ -154,3 +154,78 @@ AND a notification is sent to the VTH-behandelaar - **DSO-LV STAM API**: External service (Kadaster/RWS) - **PKIoverheid certificates**: For mTLS authentication - **BAG/BRK services**: For locatie-validatie (via OpenConnector) + +### Using Mock Register Data + +The **DSO** mock register provides test data for developing the DSO adapter without requiring access to the DSO-LV production/test environment. + +**Loading the register:** +```bash +# Load DSO register (53 records, register slug: "dso", schemas: "activiteit", "locatie", "omgevingsdocument", "vergunningaanvraag") +docker exec -u www-data nextcloud php occ openregister:load-register /var/www/html/custom_apps/openregister/lib/Settings/dso_register.json +``` + +**Test data for this spec's use cases:** +- **Activiteiten-to-zaaktype mapping (DSO-010)**: 20+ activiteit records (bouwen, kappen, uitrit aanleggen, etc.) -- test mapping configuration +- **Vergunningaanvraag parsing (DSO-004)**: 10+ vergunningaanvraag records with activiteiten, locatie, and aanvrager data +- **Samenloop testing (DSO-011)**: Vergunningaanvragen referencing multiple activiteiten -- test single-zaak vs multi-deelzaak creation + +## Current Implementation Status + +### Implemented +- **None of the DSO-specific requirements are implemented.** There is no DSO adapter, STAM endpoint, activiteiten-mapping, or DSO-SWF integration in the codebase. + +### Partially relevant existing infrastructure +- **SOAP engine** (`lib/Service/SOAPService.php`): A generic SOAP client exists that can call SOAP sources using WSDL, Guzzle HTTP, and the `php-soap` extension. It already handles SOAP 1.1/1.2, cookie management, WSDL caching, and binary data encoding. This could serve as a foundation for DSO-LV STAM SOAP communication. +- **Source entity** (`lib/Db/Source.php`, `src/entities/source/source.types.ts`): Sources support types `json`, `xml`, `soap`, `ftp`, `sftp` with configurable authentication (`apikey`, `jwt`, `username-password`, `oauth`, etc.). A new `dso` source type would need to be added. +- **CallService** (`lib/Service/CallService.php`): Routes SOAP-type sources to the SOAPService (line ~448). Already supports certificate file writing to disk for mTLS connections. +- **SynchronizationService** (`lib/Service/SynchronizationService.php`): Full sync framework with contracts, logging, and mapping between external and internal objects. Could be leveraged for DSO-verzoek sync. +- **AuthenticationService** (`lib/Service/AuthenticationService.php`): Has certificate handling logic that could be extended for PKIoverheid mTLS. + +### Not implemented +- DSO-LV STAM koppelvlak endpoint (inbound REST/SOAP receiver) +- DSO verzoek parsing (XML/JSON payload to structured data) +- Activiteiten-to-zaaktype mapping table and UI +- Samenloop handling (multiple deelzaken from one verzoek) +- DSO-SWF samenwerking (adviesverzoeken, adviezen) +- Status push back to DSO-LV (outbound) +- PKIoverheid certificate validation chain +- DSO-LV webhook signature verification +- DSO-specific source type registration +- Bijlagen download and Nextcloud Files storage +- All zaak creation logic (depends on Procest) + +## Standards & References + +- **DSO-LV STAM koppelvlak**: REST API specification maintained by Kadaster/RWS for the Digitaal Stelsel Omgevingswet. Defines the verzoek intake interface. +- **Omgevingswet (2024)**: The Dutch Environment and Planning Act that replaced the Wabo/Wro, effective January 1, 2024. +- **DSO-SWF**: SamenWerkingsFunctionaliteit — the collaboration API within the DSO-LV for coordinating between bevoegd gezag and ketenpartners. +- **PKIoverheid**: Dutch government PKI for mutual TLS authentication (PKIO Server 2020 certificate chain). +- **BAG (Basisregistratie Adressen en Gebouwen)**: National address registry, used for locatie-validatie. +- **BRK (Basisregistratie Kadaster)**: Cadastral registry for kadastrale aanduidingen. +- **GML (Geography Markup Language)**: OGC standard for geospatial data encoding, used for locatie geometrie. +- **OIN (Organisatie-Identificatienummer)**: Unique identifier for Dutch government organizations. + +## Specificity Assessment + +### Sufficient for implementation +- The data model for DSO-Verzoek is well-defined with clear field types. +- Requirements are granular with individual IDs and clear MUST/SHOULD priorities. +- Scenarios cover the main flows (receive, samenloop, besluit push, unknown activiteit). + +### Missing or ambiguous +- **STAM API version**: The spec doesn't specify which version of the STAM koppelvlak API to target. The DSO has evolved significantly since its 2024 launch. +- **Authentication flow details**: How PKIoverheid certificates are obtained, renewed, and stored is not specified. The CallService already writes certs to disk — how does this integrate? +- **Webhook vs polling**: DSO-001 says "receive" but doesn't clarify whether this is a webhook (DSO pushes to us) or polling (we poll DSO). The STAM interface is typically push-based but the mechanism needs clarification. +- **Status mapping table**: DSO-041 mentions mapping Procest statussen to DSO statuscodes, but the actual mapping values are not defined. +- **Error handling**: DSO-006 mentions "descriptive errors" but doesn't define error response format (HTTP status codes, error schema). +- **Samenloop strategy**: DSO-011 says "multiple zaak objects or one zaak with multiple deelzaken" — which strategy is preferred? This is a significant architectural decision. +- **Procest dependency**: All zaak creation logic depends on Procest, which is itself under development. The interface between this adapter and Procest is undefined. +- **n8n workflow template**: DSO-063 mentions n8n integration but doesn't specify the workflow structure or trigger mechanism. + +### Open questions +1. Which STAM API version and environment (pre-prod/prod) endpoints should be targeted first? +2. Should the adapter support the legacy OLO format during a transition period, or DSO-only? +3. How are PKIoverheid certificates provisioned — uploaded via UI, or configured via Nextcloud admin settings? +4. What is the preferred samenloop strategy: one hoofdzaak with deelzaken, or separate independent zaken? +5. How does the adapter discover which activiteiten mappings exist? Is there a national registry of activiteit codes? diff --git a/openspec/specs/ibabs-notubiz-connector/spec.md b/openspec/specs/ibabs-notubiz-connector/spec.md index c0e237299..f1c219ffe 100644 --- a/openspec/specs/ibabs-notubiz-connector/spec.md +++ b/openspec/specs/ibabs-notubiz-connector/spec.md @@ -139,3 +139,77 @@ AND a notification is sent to the behandelaar - **Docudesk**: PDF conversion for outbound documents - **iBabs REST API**: External service (api.ibabs.eu) - **NotuBiz API**: External service (api.notubiz.nl) + +### Using Mock Register Data + +The **ORI** mock register provides test data for developing the iBabs/NotuBiz connector without requiring access to production RIS systems. + +**Loading the register:** +```bash +# Load ORI register (115 records, register slug: "ori", schemas: "vergadering", "agendapunt", "raadsdocument", "stemming", "raadslid", "fractie") +docker exec -u www-data nextcloud php occ openregister:load-register /var/www/html/custom_apps/openregister/lib/Settings/ori_register.json +``` + +**Test data for this spec's use cases:** +- **Vergadering retrieval (RIS-007)**: 10+ vergaderingen with dates and types (raadsvergadering, commissievergadering) -- test sync back to ORI register +- **Agendapunt creation (RIS-003)**: 30+ agendapunten linked to vergaderingen -- test push/pull of agenda items +- **Besluit mapping (RIS-008)**: Stemmingen with aangenomen/verworpen results -- test besluit status mapping +- **Document handling (RIS-006)**: 15+ raadsdocumenten (moties, amendementen, besluiten) -- test document upload/download sync + +## Current Implementation Status + +### Implemented +- **None of the iBabs/NotuBiz-specific requirements are implemented.** There is no iBabs connector, NotuBiz connector, parafering workflow, or RIS sync mechanism in the codebase. + +### Partially relevant existing infrastructure +- **Source entity** (`lib/Db/Source.php`, `src/entities/source/source.types.ts`): Supports source types `json`, `xml`, `soap`, `ftp`, `sftp` with multiple auth methods including `apikey`, `jwt`, `oauth`. Both iBabs (REST + API key) and NotuBiz (OAuth2/API key) could be configured as `json`-type sources with appropriate auth. +- **CallService** (`lib/Service/CallService.php`): Generic HTTP client that handles REST calls to configured sources. Could be used for iBabs/NotuBiz API calls without modification. +- **SynchronizationService** (`lib/Service/SynchronizationService.php`): Full bidirectional sync framework with contracts, logs, and mapping. Supports sync between external sources and OpenRegister objects. This is directly relevant for RIS-030/031 (bidirectional sync). +- **AuthenticationService** (`lib/Service/AuthenticationService.php`): Handles various auth methods. iBabs API key and NotuBiz OAuth2 should be supportable. +- **EndpointService** (`lib/Service/EndpointService.php`): Manages endpoint configuration and routing. +- **JobService** (`lib/Service/JobService.php`): Background job execution — could be used for polling and retry logic (RIS-031, RIS-034). + +### Not implemented +- iBabs REST API client (document upload, agendapunt creation, besluit retrieval) +- NotuBiz API client (vergaderstuk upload, agendapunt, besluit retrieval) +- Bidirectional sync triggers (status-based outbound push, polling/webhook inbound) +- Sync record storage (the data model described in the spec) +- Conflict detection (RIS-032) +- Retry with configurable backoff (RIS-034) +- Parafering workflow (RIS-050 through RIS-053) — entirely within Procest scope +- Document flow with PDF conversion via Docudesk +- Geheimhouding flag mapping +- RIS-specific source type registration + +## Standards & References + +- **iBabs REST API**: Proprietary API by iBabs BV (now part of Meeting.nl). Documented at developer.ibabs.eu. Uses API key authentication, REST/JSON format. +- **NotuBiz API**: Proprietary API by NotuBiz BV (part of CMSolutions). Supports OAuth2 and API key auth. REST/JSON format. +- **Gemeentelijke besluitvormingsprocessen**: The B&W-besluitvorming workflow is standardized across Dutch municipalities: steller > adviseur > parafeerder > portefeuillehouder > secretariaat > collegevergadering > besluit. +- **GEMMA procesarchitectuur**: The reference architecture for Dutch municipal decision-making processes. +- **Archiefwet**: Dutch archiving law — besluitenlijsten and vergaderstukken must be archived according to selectielijsten. + +## Specificity Assessment + +### Sufficient for implementation +- The sync record data model is well-defined. +- The document flow direction (outbound voorstel, inbound besluit) is clear. +- Scenarios cover the main happy path and error/retry cases. +- Parafering route requirements are specific (sequential, parallel, mixed). + +### Missing or ambiguous +- **iBabs API version**: No specific API version is mentioned. iBabs has multiple API generations. +- **NotuBiz API version**: Similarly unspecified. NotuBiz API access may require a specific contract/license. +- **Webhook vs polling**: RIS-031 says "poll or webhook" but doesn't specify which is preferred or what the polling interval should be. +- **Vergadering selection**: RIS-003 says "create agendapunt" but doesn't specify how the target vergadering is selected (next upcoming? manual selection? configurable default?). +- **Document format requirements**: RIS-006 mentions PDF/DOCX but iBabs may require specific metadata fields or format constraints not documented here. +- **Parafering scope ambiguity**: RIS-050-053 describe parafering within Procest, but the spec is for the OpenConnector adapter. The boundary between Procest and OpenConnector is unclear. +- **Multi-tenant**: Can multiple iBabs/NotuBiz connections be configured simultaneously (e.g., different vergadertypen mapped to different RIS instances)? +- **Besluit status mapping**: RIS-008 lists status values (aangenomen, verworpen, aangehouden, doorgeschoven) but doesn't define the target Procest zaak statussen. + +### Open questions +1. Are iBabs and NotuBiz API access agreements/licenses in place? Both are proprietary APIs with access restrictions. +2. Should parafering logic live in Procest (as a zaak workflow) or in OpenConnector (as a sync prerequisite)? The spec mixes both. +3. What is the polling interval for inbound besluit sync? Is a webhook option available from either RIS? +4. How is the target vergadering selected when pushing a collegevoorstel? Manual or automatic? +5. Is there a test/sandbox environment available for both iBabs and NotuBiz APIs? diff --git a/openspec/specs/prometheus-metrics/spec.md b/openspec/specs/prometheus-metrics/spec.md new file mode 100644 index 000000000..acc782df4 --- /dev/null +++ b/openspec/specs/prometheus-metrics/spec.md @@ -0,0 +1,42 @@ +# Prometheus Metrics Endpoint + +## Purpose +Expose application metrics in Prometheus text exposition format at `GET /api/metrics` for monitoring, alerting, and operational dashboards. + +## Requirements + +### REQ-PROM-001: Metrics Endpoint +- MUST expose `GET /index.php/apps/openconnector/api/metrics` returning `text/plain; version=0.0.4; charset=utf-8` +- MUST require admin authentication (Nextcloud admin or API token) +- MUST return metrics in Prometheus text exposition format + +### REQ-PROM-002: Standard Metrics +Every app MUST expose these standard metrics: +- `openconnector_info` (gauge, labels: version, php_version, nextcloud_version) — always 1 +- `openconnector_up` (gauge) — 1 if app is healthy, 0 if degraded +- `openconnector_requests_total` (counter, labels: method, endpoint, status) — HTTP request count +- `openconnector_request_duration_seconds` (histogram, labels: method, endpoint) — request latency +- `openconnector_errors_total` (counter, labels: type) — error count by type + +### REQ-PROM-003: App-Specific Metrics +- `openconnector_sources_total` (gauge, labels: type) — total sources by type (rest/soap/graphql) +- `openconnector_calls_total` (counter, labels: source, method, status) — API calls made +- `openconnector_call_duration_seconds` (histogram, labels: source) — call latency +- `openconnector_synchronizations_total` (counter, labels: source, status) — sync operations +- `openconnector_sync_objects_total` (counter, labels: source) — objects synced + +### REQ-PROM-004: Health Check +- MUST expose `GET /index.php/apps/openconnector/api/health` returning JSON `{"status": "ok"|"degraded"|"error", "checks": {...}}` +- Checks: database connectivity, required dependencies available, source endpoint reachability + +## Current Implementation Status +- **Not implemented**: No MetricsController, HealthController, or metrics/monitoring code exists in the app. + +## Standards & References +- Prometheus text exposition format: https://prometheus.io/docs/instrumenting/exposition_formats/ +- OpenMetrics specification: https://openmetrics.io/ +- Nextcloud server monitoring patterns +- OpenRegister MetricsService and HeartbeatController as reference implementation + +## Specificity Assessment +Highly specific — metric names, types, and labels are fully defined. Implementation follows a standard pattern that can be shared via a base MetricsService trait/class from OpenRegister. diff --git a/openspec/specs/stuf-adapter/spec.md b/openspec/specs/stuf-adapter/spec.md index 326c110e5..153731af8 100644 --- a/openspec/specs/stuf-adapter/spec.md +++ b/openspec/specs/stuf-adapter/spec.md @@ -124,3 +124,83 @@ AND the server's certificate is validated against the PKIoverheid chain - **PHP SOAP extension**: SOAP client/server functionality - **PKIoverheid root certificates**: For mTLS validation - **StUF-BG 3.10 and StUF-ZKN 3.10 XSD schemas**: For XML validation + +### Using Mock Register Data + +The **BRP** and **BAG** mock registers provide test data for StUF-BG person/address queries without requiring external government endpoints. + +**Loading the registers:** +```bash +# Load BRP register (35 persons, register slug: "brp", schema: "ingeschreven-persoon") +docker exec -u www-data nextcloud php occ openregister:load-register /var/www/html/custom_apps/openregister/lib/Settings/brp_register.json + +# Load BAG register (32 addresses, register slug: "bag", schema: "nummeraanduiding") +docker exec -u www-data nextcloud php occ openregister:load-register /var/www/html/custom_apps/openregister/lib/Settings/bag_register.json +``` + +**Test data for this spec's use cases:** +- **StUF-BG npsLv01/npsLa01**: BSN `999993653` (Suzanne Moulin) -- test person query and response mapping +- **StUF-BG adrLv01/adrLa01**: Use BAG `nummeraanduiding` records -- test address query and response mapping +- **Field mapping validation**: BRP records include all fields from the StUF-BG mapping table (bsn, geslachtsnaam, voorvoegsel, voornamen, geboortedatum, verblijfsadres) + +## Current Implementation Status + +### Implemented (partial) +- **SOAP engine** (`lib/Service/SOAPService.php`): A working generic SOAP client that supports WSDL-driven requests, SOAP 1.1/1.2, cookie jar management, and XML response parsing. This is the outbound foundation (STUF-010/030). +- **edcLk01 handling** (`lib/Service/SOAPService.php`, lines 218-223): There is **specific StUF-ZKN code** — the SOAPService already handles `edcLk01` document messages by detecting `body['edcLk01']['object']['inhoud']` and base64-decoding the document content. This directly relates to STUF-022 (document koppelen). +- **Source type `soap`** (`src/entities/source/source.types.ts`): Sources can be configured as type `soap` with WSDL URL, SOAP version, and authentication. StUF endpoints can be set up as SOAP sources today. +- **CallService SOAP routing** (`lib/Service/CallService.php`, line ~448): When a source has type `soap`, calls are automatically routed to the SOAPService. +- **Certificate handling** (`lib/Service/CallService.php`): Supports writing client certificates and SSL keys to disk for mTLS connections. This is directly relevant for PKIoverheid mTLS (STUF-011). +- **AuthenticationService** (`lib/Service/AuthenticationService.php`): Has certificate and authentication handling that could support WS-Security (STUF-012). + +### Not implemented +- **Inbound SOAP server** (STUF-001, STUF-020, STUF-023): No SOAP server endpoint exists. The current SOAPService is client-only (outbound). Exposing StUF-BG/ZKN endpoints as a SOAP server requires a fundamentally different architecture. +- **StUF-BG field mapping** (STUF-002, STUF-003): No mapping between StUF-BG XML paths (`bsn`, `geslachtsnaam`, etc.) and OpenRegister object properties. +- **StUF-ZKN field mapping** (STUF-021): No mapping between StUF-ZKN zaak fields and Procest/OpenRegister objects. +- **WSDL files bundled** (STUF-040): No StUF-BG or StUF-ZKN WSDL/XSD files are included in the codebase. +- **XML namespace handling** (STUF-041): No StUF-specific namespace management (StUF, BG, ZKN, xsi, gml). +- **Stuurgegevens** (STUF-042): No automatic population of zender/ontvanger/referentienummer/tijdstip. +- **noValue attribute handling** (STUF-043): No support for StUF noValue semantics. +- **Configurable field mapping** (STUF-050-053): No mapping configuration UI or storage in OpenRegister. +- **Scope filtering** (STUF-005): Not implemented. +- **Fault message handling** (STUF-014, STUF-024): No Fo01/Fo02/Fo03 or Bv03 handling. +- **WS-Security UsernameToken** (STUF-012): Not implemented as a specific auth method. + +### Summary +The outbound SOAP client infrastructure is in place and already has one piece of StUF-ZKN awareness (edcLk01 document handling). The inbound SOAP server side is entirely missing and represents the larger implementation effort. + +## Standards & References + +- **StUF-BG 3.10**: Standaard Uitwisseling Formaat - Basisgegevens. SOAP-based standard for person and address data exchange in Dutch government. Maintained by VNG Realisatie. +- **StUF-ZKN 3.10 / 3.10e**: Standaard Uitwisseling Formaat - Zaak-/Documentservices. SOAP-based standard for case and document management exchange. The "e" extension adds extra message types. +- **ZGW APIs (Zaakgericht Werken)**: The modern REST-based successor to StUF-ZKN. This adapter bridges the gap between legacy StUF and modern ZGW. +- **WS-Security**: OASIS standard for SOAP message security. UsernameToken profile is commonly used by Dutch government StUF endpoints. +- **PKIoverheid**: Dutch government PKI for mTLS authentication. Required for most production StUF endpoints. +- **GEMMA**: Reference architecture for Dutch municipalities — defines the role of StUF in the information architecture. +- **BRP (Basisregistratie Personen)**: National person registry, accessed via StUF-BG by municipalities. +- **RGBZ (Referentiemodel Gemeentelijke Basisgegevens Zaken)**: The information model underlying StUF-ZKN. +- **CMIS**: Content Management Interoperability Services — sometimes used alongside StUF-ZKN for document management. + +## Specificity Assessment + +### Sufficient for implementation +- StUF message types are well-known and standardized (npsLv01, npsLa01, zakLk01, etc.). +- The requirement IDs clearly separate inbound/outbound and BG/ZKN concerns. +- The scenarios cover the main integration patterns (query BRP, expose zaken, create zaken, certificate auth). +- The edcLk01 handling already in the code proves the pattern works. + +### Missing or ambiguous +- **SOAP server architecture**: How to expose inbound SOAP endpoints within Nextcloud is a significant architectural question. Nextcloud routes are REST-based. Running a SOAP server may require a separate endpoint or a raw POST handler that processes SOAP XML. +- **StUF version specifics**: The spec says "3.10" but doesn't address version negotiation. Some municipalities run 3.01 or custom extensions. +- **Performance requirements**: No mention of expected throughput, response time SLAs, or concurrent request handling. +- **Mapping storage format**: STUF-050 says "configurable via mapping objects stored in OpenRegister" but doesn't define the mapping object schema (which register, which schema, what fields). +- **Pre-seeded mappings scope**: STUF-051 says "default mapping configurations" but doesn't list which specific fields are included in the default BRP and ZGW mappings. +- **Asynchronous patterns**: STUF-024 mentions Bv03/Fo03 async patterns but doesn't detail the callback mechanism (how does the adapter receive async responses?). +- **Multi-source routing**: Can the adapter expose multiple StUF endpoints for different registers/schemas, or is it one global SOAP endpoint? + +### Open questions +1. How should the inbound SOAP server be hosted within Nextcloud? As a regular route that parses raw SOAP XML, or as a separate PHP SOAP server process? +2. Which StUF-BG and StUF-ZKN WSDL/XSD files should be bundled? Where are the official schema packages obtained? +3. Should the adapter support StUF-BG 3.01 (still in use by some municipalities) alongside 3.10? +4. What is the expected mapping object schema in OpenRegister for field mappings (STUF-050)? +5. How does WS-Security UsernameToken integrate with the existing AuthenticationService — as a new auth type, or as middleware on the SOAP transport? diff --git a/package-lock.json b/package-lock.json index 2eb9e3bb0..02055a3ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,7 @@ "": { "name": "openconnector", "version": "1.0.0", - "license": "AGPL-3.0-or-later", + "license": "EUPL-1.2", "dependencies": { "@codemirror/lang-json": "^6.0.1", "@fortawesome/fontawesome-svg-core": "^6.5.2", @@ -44,6 +44,7 @@ }, "devDependencies": { "@babel/preset-env": "^7.25.3", + "@cyclonedx/cyclonedx-npm": "^4.2.1", "@eslint/config-helpers": "^0.4.2", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "^9.39.1", @@ -2063,6 +2064,194 @@ "postcss-selector-parser": "^6.0.13" } }, + "node_modules/@cyclonedx/cyclonedx-npm": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@cyclonedx/cyclonedx-npm/-/cyclonedx-npm-4.2.1.tgz", + "integrity": "sha512-SOA/96sf0wsgUYCRtFkLFm6WoFhG+q1BxdC84hPSn9J3xWlH1e7OnTPJT+WNUzTqzX1nSm5JhjRX4krozu2X+g==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://owasp.org/donate/?reponame=www-project-cyclonedx&title=OWASP+CycloneDX" + } + ], + "license": "Apache-2.0", + "dependencies": { + "@cyclonedx/cyclonedx-library": "^10.0.0", + "commander": "^14.0.0", + "normalize-package-data": "^7.0.0 || ^8.0.0", + "packageurl-js": "^2.0.1", + "spdx-expression-parse": "^3.0.1 || ^4.0.0", + "xmlbuilder2": "^3.0.2 || ^4.0.3" + }, + "bin": { + "cyclonedx-npm": "bin/cyclonedx-npm-cli.js" + }, + "engines": { + "node": ">=20.18.0", + "npm": ">=9" + }, + "optionalDependencies": { + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "ajv-formats-draft2019": "^1.6.1", + "libxmljs2": "^0.35||^0.37" + } + }, + "node_modules/@cyclonedx/cyclonedx-npm/node_modules/@cyclonedx/cyclonedx-library": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@cyclonedx/cyclonedx-library/-/cyclonedx-library-10.0.0.tgz", + "integrity": "sha512-xDXf2eqzeFHdjamj6oBV3duRSfrlmsJ5+2z9tXp7q5qxJP5Awmjf4ABSutS4qkVHHj7JzKFL/EM0V0Nihc7zPg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://owasp.org/donate/?reponame=www-project-cyclonedx&title=OWASP+CycloneDX" + } + ], + "license": "Apache-2.0", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "ajv-formats-draft2019": "^1.6.1", + "libxmljs2": "^0.35||^0.37", + "packageurl-js": "*", + "spdx-expression-parse": "*", + "xmlbuilder2": "^3.0.2||^4.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + }, + "ajv-formats": { + "optional": true + }, + "ajv-formats-draft2019": { + "optional": true + }, + "libxmljs2": { + "optional": true + }, + "packageurl-js": { + "optional": true + }, + "spdx-expression-parse": { + "optional": true + }, + "xmlbuilder2": { + "optional": true + } + } + }, + "node_modules/@cyclonedx/cyclonedx-npm/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@cyclonedx/cyclonedx-npm/node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/@cyclonedx/cyclonedx-npm/node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@cyclonedx/cyclonedx-npm/node_modules/hosted-git-info": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", + "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@cyclonedx/cyclonedx-npm/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@cyclonedx/cyclonedx-npm/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@cyclonedx/cyclonedx-npm/node_modules/normalize-package-data": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-8.0.0.tgz", + "integrity": "sha512-RWk+PI433eESQ7ounYxIp67CYuVsS1uYSonX3kA6ps/3LWfjVQa/ptEg6Y3T6uAMq1mWpX9PQ+qx+QaHpsc7gQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^9.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@cyclonedx/cyclonedx-npm/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@discoveryjs/json-ext": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", @@ -2465,6 +2654,20 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -4260,6 +4463,73 @@ "node": ">=12.4.0" } }, + "node_modules/@npmcli/agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", + "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/agent/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@npmcli/agent/node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@npmcli/agent/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC", + "optional": true + }, "node_modules/@npmcli/config": { "version": "8.3.4", "resolved": "https://registry.npmjs.org/@npmcli/config/-/config-8.3.4.tgz", @@ -4315,6 +4585,34 @@ "node": ">=10" } }, + "node_modules/@npmcli/fs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", + "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/fs/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@npmcli/git": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.8.tgz", @@ -4700,6 +4998,58 @@ "dev": true, "license": "MIT" }, + "node_modules/@oozcitak/dom": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@oozcitak/dom/-/dom-2.0.2.tgz", + "integrity": "sha512-GjpKhkSYC3Mj4+lfwEyI1dqnsKTgwGy48ytZEhm4A/xnH/8z9M3ZVXKr/YGQi3uCLs1AEBS+x5T2JPiueEDW8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oozcitak/infra": "^2.0.2", + "@oozcitak/url": "^3.0.0", + "@oozcitak/util": "^10.0.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@oozcitak/infra": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@oozcitak/infra/-/infra-2.0.2.tgz", + "integrity": "sha512-2g+E7hoE2dgCz/APPOEK5s3rMhJvNxSMBrP+U+j1OWsIbtSpWxxlUjq1lU8RIsFJNYv7NMlnVsCuHcUzJW+8vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oozcitak/util": "^10.0.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@oozcitak/url": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@oozcitak/url/-/url-3.0.0.tgz", + "integrity": "sha512-ZKfET8Ak1wsLAiLWNfFkZc/BraDccuTJKR6svTYc7sVjbR+Iu0vtXdiDMY4o6jaFl5TW2TlS7jbLl4VovtAJWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oozcitak/infra": "^2.0.2", + "@oozcitak/util": "^10.0.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@oozcitak/util": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-10.0.0.tgz", + "integrity": "sha512-hAX0pT/73190NLqBPPWSdBVGtbY6VOhWYK3qqHqtXQ1gK7kS2yz4+ivsN07hpJ6I3aeMtKP6J6npsEKOAzuTLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0" + } + }, "node_modules/@parcel/watcher": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", @@ -7061,6 +7411,23 @@ } } }, + "node_modules/ajv-formats-draft2019": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ajv-formats-draft2019/-/ajv-formats-draft2019-1.6.1.tgz", + "integrity": "sha512-JQPvavpkWDvIsBp2Z33UkYCtXCSpW4HD3tAZ+oL4iEFOk9obQZffx0yANwECt6vzr6ET+7HN5czRyqXbnq/u0Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "punycode": "^2.1.1", + "schemes": "^1.4.0", + "smtp-address-parser": "^1.0.3", + "uri-js": "^4.4.1" + }, + "peerDependencies": { + "ajv": "*" + } + }, "node_modules/ajv-formats/node_modules/ajv": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", @@ -8102,8 +8469,7 @@ "url": "https://feross.org/support" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/baseline-browser-mapping": { "version": "2.9.11", @@ -8153,25 +8519,80 @@ "file-uri-to-path": "1.0.0" } }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "license": "MIT" - }, - "node_modules/blurhash": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/blurhash/-/blurhash-2.0.5.tgz", - "integrity": "sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w==", - "license": "MIT" - }, - "node_modules/bn.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz", - "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==", + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "dev": true, "license": "MIT", - "peer": true + "optional": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "license": "MIT" + }, + "node_modules/blurhash": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/blurhash/-/blurhash-2.0.5.tgz", + "integrity": "sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w==", + "license": "MIT" + }, + "node_modules/bn.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz", + "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==", + "dev": true, + "license": "MIT", + "peer": true }, "node_modules/body-parser": { "version": "1.20.4", @@ -8626,6 +9047,90 @@ "node": ">= 0.8" } }, + "node_modules/cacache": { + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", + "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "@npmcli/fs": "^4.0.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/cacache/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/cacache/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -8872,6 +9377,17 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "optional": true, + "engines": { + "node": ">=18" + } + }, "node_modules/chrome-trace-event": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", @@ -10017,6 +10533,23 @@ "node": ">=0.10" } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/dedent": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", @@ -10032,6 +10565,17 @@ } } }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -10290,6 +10834,14 @@ "node": ">=8" } }, + "node_modules/discontinuous-range": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", + "integrity": "sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/dns-packet": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", @@ -10600,6 +11152,40 @@ "node": ">= 0.8" } }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.18.4", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", @@ -10626,6 +11212,17 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/envinfo": { "version": "7.21.0", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", @@ -11952,6 +12549,17 @@ "node": ">= 0.8.0" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "license": "(MIT OR WTFPL)", + "optional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/expect": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", @@ -11969,6 +12577,14 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0", + "optional": true + }, "node_modules/express": { "version": "4.22.1", "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", @@ -12521,6 +13137,28 @@ "node": ">= 0.6" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -12708,6 +13346,14 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -13387,6 +14033,14 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true + }, "node_modules/http-deceiver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", @@ -13586,8 +14240,7 @@ "url": "https://feross.org/support" } ], - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/ignore": { "version": "5.3.2", @@ -13767,6 +14420,17 @@ "loose-envify": "^1.0.0" } }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", @@ -16760,6 +17424,24 @@ "node": ">= 0.8.0" } }, + "node_modules/libxmljs2": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/libxmljs2/-/libxmljs2-0.37.0.tgz", + "integrity": "sha512-Xb78V8GZouoZFrq8cCwx7+G3WYOcJG0xb3YUbweSyE4z2EIrQCZMr3Ye/dHn4mESs6YxUMeQeUZm5IXg+iLHog==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "bindings": "~1.5.0", + "nan": "~2.22.2", + "node-gyp": "^11.2.0", + "prebuild-install": "^7.1.3" + }, + "engines": { + "node": ">=22" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -16985,20 +17667,66 @@ "dev": true, "license": "ISC" }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "node_modules/make-fetch-happen": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", + "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", "dev": true, - "license": "BSD-3-Clause", + "license": "ISC", + "optional": true, "dependencies": { - "tmpl": "1.0.5" + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "ssri": "^12.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/map-obj": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", - "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "node_modules/make-fetch-happen/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/make-fetch-happen/node_modules/proc-log": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", + "dev": true, + "license": "ISC", + "optional": true, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", "dev": true, "license": "MIT", "peer": true, @@ -17947,6 +18675,20 @@ "node": ">=6" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -18010,6 +18752,161 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", + "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -18024,6 +18921,22 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/moo": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.3.tgz", + "integrity": "sha512-m2fmM2dDm7GZQsY7KK2cme8agi+AAljILjQnof7p1ZMDe6dQ4bdnSMx0cPppudoeNv5hEFQirN6u+O4fDE0IWA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -18045,6 +18958,14 @@ "multicast-dns": "cli.js" } }, + "node_modules/nan": { + "version": "2.22.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.2.tgz", + "integrity": "sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -18063,6 +18984,14 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/napi-postinstall": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", @@ -18087,6 +19016,38 @@ "dev": true, "license": "MIT" }, + "node_modules/nearley": { + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz", + "integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "commander": "^2.19.0", + "moo": "^0.5.0", + "railroad-diagrams": "^1.0.0", + "randexp": "0.4.6" + }, + "bin": { + "nearley-railroad": "bin/nearley-railroad.js", + "nearley-test": "bin/nearley-test.js", + "nearley-unparse": "bin/nearley-unparse.js", + "nearleyc": "bin/nearleyc.js" + }, + "funding": { + "type": "individual", + "url": "https://nearley.js.org/#give-to-nearley" + } + }, + "node_modules/nearley/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/negotiator": { "version": "0.6.4", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", @@ -18111,6 +19072,34 @@ "license": "MIT", "optional": true }, + "node_modules/node-abi": { + "version": "3.89.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.89.0.tgz", + "integrity": "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-abi/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/node-addon-api": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", @@ -18216,6 +19205,113 @@ "lodash.get": "^4.4.2" } }, + "node_modules/node-gyp": { + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.5.0.tgz", + "integrity": "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^14.0.3", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "tar": "^7.4.3", + "tinyglobby": "^0.2.12", + "which": "^5.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/node-gyp/node_modules/abbrev": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", + "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", + "dev": true, + "license": "ISC", + "optional": true, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/node-gyp/node_modules/nopt": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", + "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "abbrev": "^3.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/node-gyp/node_modules/proc-log": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", + "dev": true, + "license": "ISC", + "optional": true, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/node-gyp/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -18786,6 +19882,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-map": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-queue": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.1.1.tgz", @@ -18860,6 +19970,13 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, + "node_modules/packageurl-js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/packageurl-js/-/packageurl-js-2.0.1.tgz", + "integrity": "sha512-N5ixXjzTy4QDQH0Q9YFjqIWd6zH6936Djpl2m9QNFmDv5Fum8q8BjkpAcHNMzOFE0IwQrFhJWex3AN6kS0OSwg==", + "dev": true, + "license": "MIT" + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -19387,14 +20504,54 @@ "util-deprecate": "^1.0.2" }, "engines": { - "node": ">=4" + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" } }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "license": "MIT" + "node_modules/prebuild-install/node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } }, "node_modules/prelude-ls": { "version": "1.2.1", @@ -19614,6 +20771,18 @@ "license": "MIT", "peer": true }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -19710,6 +20879,29 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/railroad-diagrams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", + "integrity": "sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==", + "dev": true, + "license": "CC0-1.0", + "optional": true + }, + "node_modules/randexp": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", + "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "discontinuous-range": "1.0.0", + "ret": "~0.1.10" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -19760,6 +20952,34 @@ "node": ">= 0.8" } }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "optional": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -20963,6 +22183,17 @@ "node": ">=10" } }, + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.12" + } + }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -21204,7 +22435,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/sass": { @@ -21343,6 +22574,17 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, + "node_modules/schemes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/schemes/-/schemes-1.4.0.tgz", + "integrity": "sha512-ImFy9FbCsQlVgnE3TCWmLPCFnVzx0lHL/l+umHplDqAKd0dzFpnS6lFZIpagBlYhKwzVmlV36ec0Y1XTu8JBAQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "extend": "^3.0.0" + } + }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -21768,6 +23010,55 @@ "dev": true, "license": "ISC" }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -21844,6 +23135,32 @@ "license": "MIT", "peer": true }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/smtp-address-parser": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/smtp-address-parser/-/smtp-address-parser-1.1.0.tgz", + "integrity": "sha512-Gz11jbNU0plrReU9Sj7fmshSBxxJ9ShdD2q4ktHIHo/rpTH6lFyQoYHYKINPJtPe8aHFnsbtW46Ls0tCCBsIZg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "nearley": "^2.20.1" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -21857,6 +23174,49 @@ "websocket-driver": "^0.7.4" } }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/socks-proxy-agent/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 14" + } + }, "node_modules/source-map": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", @@ -21952,7 +23312,6 @@ "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -22027,6 +23386,20 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/ssri": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", + "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/stable-hash": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", @@ -22945,6 +24318,91 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/tar": { + "version": "7.5.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.11.tgz", + "integrity": "sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "optional": true, + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "optional": true, + "engines": { + "node": ">=18" + } + }, "node_modules/terser": { "version": "5.44.1", "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", @@ -23141,7 +24599,6 @@ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" @@ -23159,7 +24616,6 @@ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12.0.0" }, @@ -23178,7 +24634,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -23656,6 +25111,20 @@ "license": "MIT", "peer": true }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -24231,6 +25700,34 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/unique-filename": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", + "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "unique-slug": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/unique-slug": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", + "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/unist-builder": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-4.0.0.tgz", @@ -25948,6 +27445,22 @@ "node": ">=12" } }, + "node_modules/xmlbuilder2": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-4.0.3.tgz", + "integrity": "sha512-bx8Q1STctnNaaDymWnkfQLKofs0mGNN7rLLapJlGuV3VlvegD7Ls4ggMjE3aUSWItCCzU0PEv45lI87iSigiCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oozcitak/dom": "^2.0.2", + "@oozcitak/infra": "^2.0.2", + "@oozcitak/util": "^10.0.0", + "js-yaml": "^4.1.1" + }, + "engines": { + "node": ">=20.0" + } + }, "node_modules/xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", diff --git a/package.json b/package.json index 830557c72..fb31c8538 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ }, "devDependencies": { "@babel/preset-env": "^7.25.3", + "@cyclonedx/cyclonedx-npm": "^4.2.1", "@eslint/config-helpers": "^0.4.2", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "^9.39.1", diff --git a/src/jobQueueWidget.js b/src/jobQueueWidget.js new file mode 100644 index 000000000..85afed1be --- /dev/null +++ b/src/jobQueueWidget.js @@ -0,0 +1,15 @@ +import Vue from 'vue' +import { PiniaVuePlugin } from 'pinia' +import pinia from './pinia.js' +import JobQueueWidget from './views/widgets/JobQueueWidget.vue' + +Vue.use(PiniaVuePlugin) + +OCA.Dashboard.register('openconnector_job_queue_widget', async (el, { widget }) => { + Vue.mixin({ methods: { t, n } }) + const View = Vue.extend(JobQueueWidget) + new View({ + pinia, + propsData: { title: widget.title }, + }).$mount(el) +}) diff --git a/src/recentCallsWidget.js b/src/recentCallsWidget.js new file mode 100644 index 000000000..eda62198f --- /dev/null +++ b/src/recentCallsWidget.js @@ -0,0 +1,15 @@ +import Vue from 'vue' +import { PiniaVuePlugin } from 'pinia' +import pinia from './pinia.js' +import RecentCallsWidget from './views/widgets/RecentCallsWidget.vue' + +Vue.use(PiniaVuePlugin) + +OCA.Dashboard.register('openconnector_recent_calls_widget', async (el, { widget }) => { + Vue.mixin({ methods: { t, n } }) + const View = Vue.extend(RecentCallsWidget) + new View({ + pinia, + propsData: { title: widget.title }, + }).$mount(el) +}) diff --git a/src/sourceSyncWidget.js b/src/sourceSyncWidget.js new file mode 100644 index 000000000..47e444bdd --- /dev/null +++ b/src/sourceSyncWidget.js @@ -0,0 +1,15 @@ +import Vue from 'vue' +import { PiniaVuePlugin } from 'pinia' +import pinia from './pinia.js' +import SourceSyncWidget from './views/widgets/SourceSyncWidget.vue' + +Vue.use(PiniaVuePlugin) + +OCA.Dashboard.register('openconnector_source_sync_widget', async (el, { widget }) => { + Vue.mixin({ methods: { t, n } }) + const View = Vue.extend(SourceSyncWidget) + new View({ + pinia, + propsData: { title: widget.title }, + }).$mount(el) +}) diff --git a/src/views/widgets/JobQueueWidget.vue b/src/views/widgets/JobQueueWidget.vue new file mode 100644 index 000000000..3fe343a6d --- /dev/null +++ b/src/views/widgets/JobQueueWidget.vue @@ -0,0 +1,113 @@ + + + diff --git a/src/views/widgets/RecentCallsWidget.vue b/src/views/widgets/RecentCallsWidget.vue new file mode 100644 index 000000000..ab2c9348a --- /dev/null +++ b/src/views/widgets/RecentCallsWidget.vue @@ -0,0 +1,123 @@ + + + diff --git a/src/views/widgets/SourceSyncWidget.vue b/src/views/widgets/SourceSyncWidget.vue new file mode 100644 index 000000000..2a6302409 --- /dev/null +++ b/src/views/widgets/SourceSyncWidget.vue @@ -0,0 +1,125 @@ + + + From 9f29ee3776087c1c7ce7cbcb9b2f34cf1bfe2a12 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 18 Mar 2026 10:38:45 +0100 Subject: [PATCH 06/17] feat: Add OpenRegister dependency check and enforce scoped CSS Show NcEmptyContent empty state when OpenRegister is not installed, with install button for admins. Add settings store with app initialization. Add ESLint rule enforcing scoped styles in Vue files, move global CSS to src/assets/app.css. --- eslint.config.js | 1 + lib/Controller/SettingsController.php | 74 +++++++++++++++++-- src/App.vue | 100 +++++++++++++++++++++++--- src/assets/app.css | 9 +++ src/main.js | 1 + src/store/modules/settings.js | 55 ++++++++++++++ src/store/store.js | 5 ++ src/views/settings/Settings.vue | 19 ++--- 8 files changed, 239 insertions(+), 25 deletions(-) create mode 100644 src/assets/app.css create mode 100644 src/store/modules/settings.js diff --git a/eslint.config.js b/eslint.config.js index 58c388f07..8d222cd7d 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -55,6 +55,7 @@ module.exports = defineConfig([ 'vue/first-attribute-linebreak': 'off', '@typescript-eslint/no-explicit-any': 'off', 'n/no-missing-import': 'off', + 'vue/enforce-style-attribute': ['error', { allow: ['scoped'] }], }, }, ]) diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php index d2fc259a4..50622b596 100644 --- a/lib/Controller/SettingsController.php +++ b/lib/Controller/SettingsController.php @@ -21,9 +21,13 @@ namespace OCA\OpenConnector\Controller; use OCA\OpenConnector\Service\SettingsService; +use OCP\App\IAppManager; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\JSONResponse; +use OCP\IGroupManager; use OCP\IRequest; +use OCP\IUserSession; +use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; /** @@ -35,25 +39,76 @@ class SettingsController extends Controller { + /** + * The OpenRegister object service. + * + * @var \OCA\OpenRegister\Service\ObjectService|null The OpenRegister object service. + */ + private ?\OCA\OpenRegister\Service\ObjectService $objectService = null; + /** * SettingsController constructor. * - * @param string $appName The name of the app - * @param IRequest $request Request object - * @param SettingsService $settingsService Settings service for business logic - * @param LoggerInterface $logger Logger for error handling + * @param string $appName The name of the app + * @param IRequest $request Request object + * @param ContainerInterface $container The container + * @param IAppManager $appManager The app manager + * @param IGroupManager $groupManager The group manager + * @param SettingsService $settingsService Settings service for business logic + * @param LoggerInterface $logger Logger for error handling + * @param IUserSession $userSession The user session */ public function __construct( string $appName, IRequest $request, + private readonly ContainerInterface $container, + private readonly IAppManager $appManager, + private readonly IGroupManager $groupManager, private readonly SettingsService $settingsService, - private readonly LoggerInterface $logger + private readonly LoggerInterface $logger, + private readonly IUserSession $userSession ) { parent::__construct($appName, $request); }//end __construct() + /** + * Attempts to retrieve the OpenRegister service from the container. + * + * @return \OCA\OpenRegister\Service\ObjectService|null The OpenRegister service if available, null otherwise. + * @throws \RuntimeException If the service is not available. + */ + public function getObjectService(): ?\OCA\OpenRegister\Service\ObjectService + { + if (in_array(needle: 'openregister', haystack: $this->appManager->getInstalledApps()) === true) { + $this->objectService = $this->container->get('OCA\OpenRegister\Service\ObjectService'); + return $this->objectService; + } + + throw new \RuntimeException('OpenRegister service is not available.'); + + }//end getObjectService() + + + /** + * Attempts to retrieve the Configuration service from the container. + * + * @return \OCA\OpenRegister\Service\ConfigurationService|null The Configuration service if available, null otherwise. + * @throws \RuntimeException If the service is not available. + */ + public function getConfigurationService(): ?\OCA\OpenRegister\Service\ConfigurationService + { + if (in_array(needle: 'openregister', haystack: $this->appManager->getInstalledApps()) === true) { + $configurationService = $this->container->get('OCA\OpenRegister\Service\ConfigurationService'); + return $configurationService; + } + + throw new \RuntimeException('Configuration service is not available.'); + + }//end getConfigurationService() + + /** * Get comprehensive database statistics for the settings dashboard. * @@ -115,12 +170,19 @@ public function getSettings(): JSONResponse $settings = $this->settingsService->getSettings(); + $user = $this->userSession->getUser(); + $isAdmin = $user !== null && $this->groupManager->isAdmin($user->getUID()); + $this->logger->debug('Settings retrieved successfully', [ 'hasRetention' => isset($settings['retention']), 'executionTime' => microtime(true) ]); - return new JSONResponse($settings); + return new JSONResponse([ + 'openRegisters' => in_array(needle: 'openregister', haystack: $this->appManager->getInstalledApps()), + 'isAdmin' => $isAdmin, + 'config' => $settings, + ]); } catch (\Exception $e) { $this->logger->error('Failed to retrieve settings', [ 'exception' => $e->getMessage(), diff --git a/src/App.vue b/src/App.vue index e3a2c7e67..b74e8590d 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,32 +1,110 @@ + + diff --git a/src/assets/app.css b/src/assets/app.css new file mode 100644 index 000000000..82104afe5 --- /dev/null +++ b/src/assets/app.css @@ -0,0 +1,9 @@ +/** + * Global (unscoped) styles for OpenConnector. + */ +.open-register-missing { + display: flex !important; + align-items: center !important; + justify-content: center !important; + min-height: 100% !important; +} diff --git a/src/main.js b/src/main.js index 33d1a8f7f..c9caee32f 100644 --- a/src/main.js +++ b/src/main.js @@ -4,6 +4,7 @@ import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip.js' import pinia from './pinia.js' import App from './App.vue' import router from './router/index.js' +import './assets/app.css' Vue.mixin({ methods: { t, n } }) Vue.use(PiniaVuePlugin) diff --git a/src/store/modules/settings.js b/src/store/modules/settings.js new file mode 100644 index 000000000..e1caeed56 --- /dev/null +++ b/src/store/modules/settings.js @@ -0,0 +1,55 @@ +import { defineStore } from 'pinia' + +export const useSettingsStore = defineStore('settings', { + state: () => ({ + config: null, + openRegisters: false, + isAdmin: false, + loading: false, + error: null, + initialized: false, + }), + getters: { + isLoading: (state) => state.loading, + getError: (state) => state.error, + isInitialized: (state) => state.initialized, + getConfig: (state) => state.config, + hasOpenRegisters: (state) => state.openRegisters, + getIsAdmin: (state) => state.isAdmin, + }, + actions: { + async fetchSettings() { + this.loading = true + this.error = null + + try { + const response = await fetch('/index.php/apps/openconnector/api/settings', { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + requesttoken: OC.requestToken, + 'OCS-APIREQUEST': 'true', + }, + }) + + if (!response.ok) { + throw new Error(`Failed to fetch settings: ${response.statusText}`) + } + + const data = await response.json() + this.config = data.config || data + this.openRegisters = data.openRegisters ?? false + this.isAdmin = data.isAdmin ?? false + this.initialized = true + + return this.config + } catch (error) { + this.error = error.message + console.error('Error fetching OpenConnector settings:', error) + return null + } finally { + this.loading = false + } + }, + }, +}) diff --git a/src/store/store.js b/src/store/store.js index 0748707d9..bc2863814 100644 --- a/src/store/store.js +++ b/src/store/store.js @@ -14,6 +14,7 @@ import { useImportExportStore } from './modules/importExport.js' import { useEventStore } from './modules/event.ts' import { useRuleStore } from './modules/rule.ts' import { useContractStore } from './modules/contract.ts' +import { useSettingsStore } from './modules/settings.js' const navigationStore = useNavigationStore(pinia) const searchStore = useSearchStore(pinia) @@ -29,6 +30,7 @@ const importExportStore = useImportExportStore(pinia) const eventStore = useEventStore(pinia) const ruleStore = useRuleStore(pinia) const contractStore = useContractStore(pinia) +const settingsStore = useSettingsStore(pinia) export { // generic @@ -47,4 +49,7 @@ export { eventStore, ruleStore, contractStore, + settingsStore, } + +export { useSettingsStore } diff --git a/src/views/settings/Settings.vue b/src/views/settings/Settings.vue index d547789cc..5d4618f7a 100644 --- a/src/views/settings/Settings.vue +++ b/src/views/settings/Settings.vue @@ -832,18 +832,21 @@ export default defineComponent({ return } + // Unwrap config from dependency-check response envelope + const config = data.config || data + // Version information - this.versionInfo = data.version + this.versionInfo = config.version // Retention settings - if (data.retention) { + if (config.retention) { this.retentionOptions = { - successLogRetention: data.retention.successLogRetention || 2592000000, - callLogRetention: data.retention.callLogRetention || 2592000000, - eventMessageRetention: data.retention.eventMessageRetention || 604800000, - jobLogRetention: data.retention.jobLogRetention || 2592000000, - syncContractLogRetention: data.retention.syncContractLogRetention || 7776000000, - syncLogRetention: data.retention.syncLogRetention || 2592000000, + successLogRetention: config.retention.successLogRetention || 2592000000, + callLogRetention: config.retention.callLogRetention || 2592000000, + eventMessageRetention: config.retention.eventMessageRetention || 604800000, + jobLogRetention: config.retention.jobLogRetention || 2592000000, + syncContractLogRetention: config.retention.syncContractLogRetention || 7776000000, + syncLogRetention: config.retention.syncLogRetention || 2592000000, } } From cf673a3d0a6c61cdf3af32faad536e6b401bd903 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 18 Mar 2026 14:07:39 +0100 Subject: [PATCH 07/17] feat: Add support contact info to app description and README Support: support@conduction.nl SLA: sales@conduction.nl --- README.md | 4 +++- appinfo/info.xml | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0730886e1..e19332edb 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,9 @@ Documentation is available at [https://conduction.nl/openconnector](https://cond ## Support -[Support information](https://conduction.nl/openconnector/support) +For support, contact us at [support@conduction.nl](mailto:support@conduction.nl). + +For a Service Level Agreement (SLA), contact [sales@conduction.nl](mailto:sales@conduction.nl). ## Project Structure diff --git a/appinfo/info.xml b/appinfo/info.xml index 513db5ab1..56ab53f7a 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -12,6 +12,7 @@ The OpenConnector Nextcloud app provides a ESB-framework to work together in an - 📰 Send cloud events - 🆓 Map and translate API calls +**Support:** For support, contact support@conduction.nl. For a Service Level Agreement (SLA), contact sales@conduction.nl. ]]> 0.2.8 agpl From 0967c14e3350483f353b1d4430ed818bdb2af5f5 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 18 Mar 2026 14:17:35 +0100 Subject: [PATCH 08/17] feat: Standardize settings with CnVersionInfoCard, fix section icon Replace inline version info with CnVersionInfoCard component, add support footer (support@conduction.nl, sales@conduction.nl), use app-dark.svg instead of core settings icon. --- lib/Sections/OpenConnectorAdmin.php | 2 +- src/views/settings/Settings.vue | 81 +++++++++-------------------- 2 files changed, 26 insertions(+), 57 deletions(-) diff --git a/lib/Sections/OpenConnectorAdmin.php b/lib/Sections/OpenConnectorAdmin.php index dd8f3f781..cf32e6634 100644 --- a/lib/Sections/OpenConnectorAdmin.php +++ b/lib/Sections/OpenConnectorAdmin.php @@ -15,7 +15,7 @@ public function __construct(IL10N $l, IURLGenerator $urlGenerator) { } public function getIcon(): string { - return $this->urlGenerator->imagePath('core', 'actions/settings-dark.svg'); + return $this->urlGenerator->imagePath('openconnector', 'app-dark.svg'); } public function getID(): string { diff --git a/src/views/settings/Settings.vue b/src/views/settings/Settings.vue index 5d4618f7a..3b2ce5c5a 100644 --- a/src/views/settings/Settings.vue +++ b/src/views/settings/Settings.vue @@ -5,35 +5,30 @@ description="A central place for managing your Open Connector" doc-url="https://docs.openconnector.nl" /> - -
-
-
- Application: {{ versionInfo.appName }} v{{ versionInfo.appVersion }} -
-
- License: EUPL-1.2 -
-
- Author: Conduction B.V. -
- + + + + @@ -641,6 +636,7 @@ diff --git a/src/views/Endpoint/EndpointDetails.vue b/src/views/Endpoint/EndpointDetails.vue index b7359c615..5a6d356fe 100644 --- a/src/views/Endpoint/EndpointDetails.vue +++ b/src/views/Endpoint/EndpointDetails.vue @@ -1,5 +1,4 @@ @@ -285,7 +284,7 @@ export default { } - diff --git a/src/views/Import/ImportIndex.vue b/src/views/Import/ImportIndex.vue index 4591f9f9f..7d4d9deb4 100644 --- a/src/views/Import/ImportIndex.vue +++ b/src/views/Import/ImportIndex.vue @@ -1,5 +1,4 @@ diff --git a/src/views/Job/JobDetails.vue b/src/views/Job/JobDetails.vue index 123b9d200..6da5ea79b 100644 --- a/src/views/Job/JobDetails.vue +++ b/src/views/Job/JobDetails.vue @@ -1,5 +1,4 @@ diff --git a/src/views/Job/JobsList.vue b/src/views/Job/JobsList.vue index 335f362b0..5d2fb9280 100644 --- a/src/views/Job/JobsList.vue +++ b/src/views/Job/JobsList.vue @@ -1,5 +1,4 @@ @@ -153,6 +152,6 @@ export default { } - diff --git a/src/views/Mapping/MappingDetails.vue b/src/views/Mapping/MappingDetails.vue index 708a418d1..bba519dc2 100644 --- a/src/views/Mapping/MappingDetails.vue +++ b/src/views/Mapping/MappingDetails.vue @@ -1,5 +1,4 @@ @@ -318,7 +317,7 @@ export default { } - diff --git a/src/views/Synchronization/SynchronizationDetails.vue b/src/views/Synchronization/SynchronizationDetails.vue index 31e8509ff..102f48943 100644 --- a/src/views/Synchronization/SynchronizationDetails.vue +++ b/src/views/Synchronization/SynchronizationDetails.vue @@ -1,5 +1,4 @@ @@ -424,7 +423,7 @@ export default { } - diff --git a/src/views/Webhook/WebhookDetails.vue b/src/views/Webhook/WebhookDetails.vue index c612a8fe7..688a7f84b 100644 --- a/src/views/Webhook/WebhookDetails.vue +++ b/src/views/Webhook/WebhookDetails.vue @@ -65,6 +65,6 @@ export default { } - diff --git a/src/views/Webhook/WebhooksList.vue b/src/views/Webhook/WebhooksList.vue index b07e82d5a..92fa9773a 100644 --- a/src/views/Webhook/WebhooksList.vue +++ b/src/views/Webhook/WebhooksList.vue @@ -1,5 +1,4 @@ @@ -107,6 +106,6 @@ export default { } - diff --git a/src/views/dashboard/DashboardIndex.vue b/src/views/dashboard/DashboardIndex.vue index 41b529b71..0e94dc819 100644 --- a/src/views/dashboard/DashboardIndex.vue +++ b/src/views/dashboard/DashboardIndex.vue @@ -886,7 +886,7 @@ export default { } - diff --git a/src/views/rule/RuleDetails.vue b/src/views/rule/RuleDetails.vue index a6d767672..d26ccecbe 100644 --- a/src/views/rule/RuleDetails.vue +++ b/src/views/rule/RuleDetails.vue @@ -120,7 +120,7 @@ export default { } - From 011d4e8b22caa1d1cf29781bd76530874876342a Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Thu, 19 Mar 2026 14:43:59 +0100 Subject: [PATCH 16/17] =?UTF-8?q?config:=20Restore=20DisallowInlineIf=20PH?= =?UTF-8?q?PCS=20rule=20=E2=80=94=20use=20early=20returns=20instead=20of?= =?UTF-8?q?=20ternaries?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpcs.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/phpcs.xml b/phpcs.xml index efd303d88..6d4ef55dd 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -31,6 +31,7 @@ + From f4a04b55dcd97290be4020ef55bee2f143b5bc4f Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Thu, 19 Mar 2026 15:10:42 +0100 Subject: [PATCH 17/17] fix: Regenerate composer.lock and resolve remaining ESLint issue - Regenerated composer.lock to sync with composer.json (azjezz/psl URL update) - Added @conduction/nextcloud-vue dependency to package.json (used by Settings.vue CnVersionInfoCard) --- composer.lock | 4 ++-- package-lock.json | 14 ++++++++++++++ package.json | 1 + 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/composer.lock b/composer.lock index 4a4070fe6..598ec7ba7 100644 --- a/composer.lock +++ b/composer.lock @@ -65,12 +65,12 @@ "version": "4.3.0", "source": { "type": "git", - "url": "https://github.com/azjezz/psl.git", + "url": "https://github.com/php-standard-library/php-standard-library.git", "reference": "74c95be0214eb7ea39146ed00ac4eb71b45d787b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/azjezz/psl/zipball/74c95be0214eb7ea39146ed00ac4eb71b45d787b", + "url": "https://api.github.com/repos/php-standard-library/php-standard-library/zipball/74c95be0214eb7ea39146ed00ac4eb71b45d787b", "reference": "74c95be0214eb7ea39146ed00ac4eb71b45d787b", "shasum": "" }, diff --git a/package-lock.json b/package-lock.json index 02055a3ba..e159a10b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "EUPL-1.2", "dependencies": { "@codemirror/lang-json": "^6.0.1", + "@conduction/nextcloud-vue": "^0.1.0-beta.3", "@fortawesome/fontawesome-svg-core": "^6.5.2", "@fortawesome/free-solid-svg-icons": "^6.5.2", "@mdi/js": "^7.4.47", @@ -1970,6 +1971,19 @@ "w3c-keyname": "^2.2.4" } }, + "node_modules/@conduction/nextcloud-vue": { + "version": "0.1.0-beta.3", + "resolved": "https://registry.npmjs.org/@conduction/nextcloud-vue/-/nextcloud-vue-0.1.0-beta.3.tgz", + "integrity": "sha512-+B02z2vUgN8BTZ0ZRd9mtrIVo55KMETzvEsyt7g0AW50hNU44xd9ILBHjKvZlKQclsLWzT36K0+RWIbcC22QVA==", + "license": "EUPL-1.2", + "peerDependencies": { + "@nextcloud/l10n": "^2.0.0 || ^3.0.0", + "@nextcloud/vue": "^8.0.0", + "pinia": "^2.0.0", + "vue": "^2.7.0", + "vue-material-design-icons": "^5.0.0" + } + }, "node_modules/@csstools/css-parser-algorithms": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.7.1.tgz", diff --git a/package.json b/package.json index fb31c8538..aab1c131f 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ ], "dependencies": { "@codemirror/lang-json": "^6.0.1", + "@conduction/nextcloud-vue": "^0.1.0-beta.3", "@fortawesome/fontawesome-svg-core": "^6.5.2", "@fortawesome/free-solid-svg-icons": "^6.5.2", "@mdi/js": "^7.4.47",