From 46b8e536a850a5f3a1d3c424e95a5bbbe7816956 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 13:43:28 -0700 Subject: [PATCH 01/11] Chore(deps): Bump the frontend-angular-dependencies group (#7825) Bumps the frontend-angular-dependencies group in /src-ui with 21 updates: | Package | From | To | | --- | --- | --- | | [@angular/cdk](https://github.com/angular/components) | `18.2.2` | `18.2.6` | | [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `18.2.2` | `18.2.6` | | [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `18.2.2` | `18.2.6` | | [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `18.2.2` | `18.2.6` | | [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `18.2.2` | `18.2.6` | | [@angular/localize](https://github.com/angular/angular) | `18.2.2` | `18.2.6` | | [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `18.2.2` | `18.2.6` | | [@angular/platform-browser-dynamic](https://github.com/angular/angular/tree/HEAD/packages/platform-browser-dynamic) | `18.2.2` | `18.2.6` | | [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `18.2.2` | `18.2.6` | | [@ng-select/ng-select](https://github.com/ng-select/ng-select) | `13.7.0` | `13.9.0` | | [ng2-pdf-viewer](https://github.com/VadimDez/ng2-pdf-viewer) | `10.3.0` | `10.3.1` | | [@angular-devkit/build-angular](https://github.com/angular/angular-cli) | `18.2.2` | `18.2.6` | | [@angular-devkit/core](https://github.com/angular/angular-cli) | `18.2.2` | `18.2.6` | | [@angular-devkit/schematics](https://github.com/angular/angular-cli) | `18.2.2` | `18.2.6` | | [@angular-eslint/builder](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/builder) | `18.3.0` | `18.3.1` | | [@angular-eslint/eslint-plugin](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/eslint-plugin) | `18.3.0` | `18.3.1` | | [@angular-eslint/eslint-plugin-template](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/eslint-plugin-template) | `18.3.0` | `18.3.1` | | [@angular-eslint/schematics](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/schematics) | `18.3.0` | `18.3.1` | | [@angular-eslint/template-parser](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/template-parser) | `18.3.0` | `18.3.1` | | [@angular/cli](https://github.com/angular/angular-cli) | `18.2.2` | `18.2.6` | | [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli) | `18.2.2` | `18.2.6` | Updates `@angular/cdk` from 18.2.2 to 18.2.6 - [Release notes](https://github.com/angular/components/releases) - [Changelog](https://github.com/angular/components/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/components/compare/18.2.2...18.2.6) Updates `@angular/common` from 18.2.2 to 18.2.6 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/18.2.6/packages/common) Updates `@angular/compiler` from 18.2.2 to 18.2.6 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/18.2.6/packages/compiler) Updates `@angular/core` from 18.2.2 to 18.2.6 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/18.2.6/packages/core) Updates `@angular/forms` from 18.2.2 to 18.2.6 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/18.2.6/packages/forms) Updates `@angular/localize` from 18.2.2 to 18.2.6 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/compare/18.2.2...18.2.6) Updates `@angular/platform-browser` from 18.2.2 to 18.2.6 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/18.2.6/packages/platform-browser) Updates `@angular/platform-browser-dynamic` from 18.2.2 to 18.2.6 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/18.2.6/packages/platform-browser-dynamic) Updates `@angular/router` from 18.2.2 to 18.2.6 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/18.2.6/packages/router) Updates `@ng-select/ng-select` from 13.7.0 to 13.9.0 - [Release notes](https://github.com/ng-select/ng-select/releases) - [Changelog](https://github.com/ng-select/ng-select/blob/master/CHANGELOG.md) - [Commits](https://github.com/ng-select/ng-select/compare/v13.7.0...v13.9.0) Updates `ng2-pdf-viewer` from 10.3.0 to 10.3.1 - [Release notes](https://github.com/VadimDez/ng2-pdf-viewer/releases) - [Changelog](https://github.com/VadimDez/ng2-pdf-viewer/blob/master/CHANGELOG.md) - [Commits](https://github.com/VadimDez/ng2-pdf-viewer/compare/10.3.0...10.3.1) Updates `@angular-devkit/build-angular` from 18.2.2 to 18.2.6 - [Release notes](https://github.com/angular/angular-cli/releases) - [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular-cli/compare/18.2.2...18.2.6) Updates `@angular-devkit/core` from 18.2.2 to 18.2.6 - [Release notes](https://github.com/angular/angular-cli/releases) - [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular-cli/compare/18.2.2...18.2.6) Updates `@angular-devkit/schematics` from 18.2.2 to 18.2.6 - [Release notes](https://github.com/angular/angular-cli/releases) - [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular-cli/compare/18.2.2...18.2.6) Updates `@angular-eslint/builder` from 18.3.0 to 18.3.1 - [Release notes](https://github.com/angular-eslint/angular-eslint/releases) - [Changelog](https://github.com/angular-eslint/angular-eslint/blob/main/packages/builder/CHANGELOG.md) - [Commits](https://github.com/angular-eslint/angular-eslint/commits/v18.3.1/packages/builder) Updates `@angular-eslint/eslint-plugin` from 18.3.0 to 18.3.1 - [Release notes](https://github.com/angular-eslint/angular-eslint/releases) - [Changelog](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/angular-eslint/angular-eslint/commits/v18.3.1/packages/eslint-plugin) Updates `@angular-eslint/eslint-plugin-template` from 18.3.0 to 18.3.1 - [Release notes](https://github.com/angular-eslint/angular-eslint/releases) - [Changelog](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/CHANGELOG.md) - [Commits](https://github.com/angular-eslint/angular-eslint/commits/v18.3.1/packages/eslint-plugin-template) Updates `@angular-eslint/schematics` from 18.3.0 to 18.3.1 - [Release notes](https://github.com/angular-eslint/angular-eslint/releases) - [Changelog](https://github.com/angular-eslint/angular-eslint/blob/main/packages/schematics/CHANGELOG.md) - [Commits](https://github.com/angular-eslint/angular-eslint/commits/v18.3.1/packages/schematics) Updates `@angular-eslint/template-parser` from 18.3.0 to 18.3.1 - [Release notes](https://github.com/angular-eslint/angular-eslint/releases) - [Changelog](https://github.com/angular-eslint/angular-eslint/blob/main/packages/template-parser/CHANGELOG.md) - [Commits](https://github.com/angular-eslint/angular-eslint/commits/v18.3.1/packages/template-parser) Updates `@angular/cli` from 18.2.2 to 18.2.6 - [Release notes](https://github.com/angular/angular-cli/releases) - [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular-cli/compare/18.2.2...18.2.6) Updates `@angular/compiler-cli` from 18.2.2 to 18.2.6 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/18.2.6/packages/compiler-cli) --- updated-dependencies: - dependency-name: "@angular/cdk" dependency-type: direct:production update-type: version-update:semver-patch dependency-group: frontend-angular-dependencies - dependency-name: "@angular/common" dependency-type: direct:production update-type: version-update:semver-patch dependency-group: frontend-angular-dependencies - dependency-name: "@angular/compiler" dependency-type: direct:production update-type: version-update:semver-patch dependency-group: frontend-angular-dependencies - dependency-name: "@angular/core" dependency-type: direct:production update-type: version-update:semver-patch dependency-group: frontend-angular-dependencies - dependency-name: "@angular/forms" dependency-type: direct:production update-type: version-update:semver-patch dependency-group: frontend-angular-dependencies - dependency-name: "@angular/localize" dependency-type: direct:production update-type: version-update:semver-patch dependency-group: frontend-angular-dependencies - dependency-name: "@angular/platform-browser" dependency-type: direct:production update-type: version-update:semver-patch dependency-group: frontend-angular-dependencies - dependency-name: "@angular/platform-browser-dynamic" dependency-type: direct:production update-type: version-update:semver-patch dependency-group: frontend-angular-dependencies - dependency-name: "@angular/router" dependency-type: direct:production update-type: version-update:semver-patch dependency-group: frontend-angular-dependencies - dependency-name: "@ng-select/ng-select" dependency-type: direct:production update-type: version-update:semver-minor dependency-group: frontend-angular-dependencies - dependency-name: ng2-pdf-viewer dependency-type: direct:production update-type: version-update:semver-patch dependency-group: frontend-angular-dependencies - dependency-name: "@angular-devkit/build-angular" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: frontend-angular-dependencies - dependency-name: "@angular-devkit/core" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: frontend-angular-dependencies - dependency-name: "@angular-devkit/schematics" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: frontend-angular-dependencies - dependency-name: "@angular-eslint/builder" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: frontend-angular-dependencies - dependency-name: "@angular-eslint/eslint-plugin" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: frontend-angular-dependencies - dependency-name: "@angular-eslint/eslint-plugin-template" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: frontend-angular-dependencies - dependency-name: "@angular-eslint/schematics" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: frontend-angular-dependencies - dependency-name: "@angular-eslint/template-parser" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: frontend-angular-dependencies - dependency-name: "@angular/cli" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: frontend-angular-dependencies - dependency-name: "@angular/compiler-cli" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: frontend-angular-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src-ui/package-lock.json | 1787 +++++++++++++++++--------------------- src-ui/package.json | 38 +- 2 files changed, 801 insertions(+), 1024 deletions(-) diff --git a/src-ui/package-lock.json b/src-ui/package-lock.json index 21251cca3..7871bd71e 100644 --- a/src-ui/package-lock.json +++ b/src-ui/package-lock.json @@ -9,23 +9,23 @@ "version": "0.0.0", "hasInstallScript": true, "dependencies": { - "@angular/cdk": "^18.2.2", - "@angular/common": "~18.2.2", - "@angular/compiler": "~18.2.2", - "@angular/core": "~18.2.2", - "@angular/forms": "~18.2.2", - "@angular/localize": "~18.2.2", - "@angular/platform-browser": "~18.2.2", - "@angular/platform-browser-dynamic": "~18.2.2", - "@angular/router": "~18.2.2", + "@angular/cdk": "^18.2.6", + "@angular/common": "~18.2.6", + "@angular/compiler": "~18.2.6", + "@angular/core": "~18.2.6", + "@angular/forms": "~18.2.6", + "@angular/localize": "~18.2.6", + "@angular/platform-browser": "~18.2.6", + "@angular/platform-browser-dynamic": "~18.2.6", + "@angular/router": "~18.2.6", "@ng-bootstrap/ng-bootstrap": "^17.0.1", - "@ng-select/ng-select": "^13.7.0", + "@ng-select/ng-select": "^13.9.0", "@ngneat/dirty-check-forms": "^3.0.3", "@popperjs/core": "^2.11.8", "bootstrap": "^5.3.3", "file-saver": "^2.0.5", "mime-names": "^1.0.0", - "ng2-pdf-viewer": "^10.3.0", + "ng2-pdf-viewer": "^10.3.1", "ngx-bootstrap-icons": "^1.9.3", "ngx-color": "^9.0.0", "ngx-cookie-service": "^18.0.0", @@ -40,14 +40,14 @@ "@angular-builders/custom-webpack": "^18.0.0", "@angular-builders/jest": "^18.0.0", "@angular-devkit/build-angular": "^18.2.2", - "@angular-devkit/core": "^18.2.2", - "@angular-devkit/schematics": "^18.2.2", - "@angular-eslint/builder": "18.3.0", - "@angular-eslint/eslint-plugin": "18.3.0", - "@angular-eslint/eslint-plugin-template": "18.3.0", - "@angular-eslint/schematics": "18.3.0", - "@angular-eslint/template-parser": "18.3.0", - "@angular/cli": "~18.2.2", + "@angular-devkit/core": "^18.2.6", + "@angular-devkit/schematics": "^18.2.6", + "@angular-eslint/builder": "18.3.1", + "@angular-eslint/eslint-plugin": "18.3.1", + "@angular-eslint/eslint-plugin-template": "18.3.1", + "@angular-eslint/schematics": "18.3.1", + "@angular-eslint/template-parser": "18.3.1", + "@angular/cli": "~18.2.6", "@angular/compiler-cli": "~18.2.2", "@codecov/webpack-plugin": "^1.0.1", "@playwright/test": "^1.46.1", @@ -234,12 +234,12 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.1802.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.2.tgz", - "integrity": "sha512-LPRl9jhcf0NgshaL6RoUy1uL/cAyNt7oxctoZ9EHUu8eh5E9W/jZGhVowjOLpirwqYhmEzKJJIeS49Ssqs3RQg==", + "version": "0.1802.6", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.6.tgz", + "integrity": "sha512-oF7cPFdTLxeuvXkK/opSdIxZ1E4LrBbmuytQ/nCoAGOaKBWdqvwagRZ6jVhaI0Gwu48rkcV7Zhesg/ESNnROdw==", "dev": true, "dependencies": { - "@angular-devkit/core": "18.2.2", + "@angular-devkit/core": "18.2.6", "rxjs": "7.8.1" }, "engines": { @@ -249,16 +249,16 @@ } }, "node_modules/@angular-devkit/build-angular": { - "version": "18.2.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-18.2.2.tgz", - "integrity": "sha512-7HEnTN2T1jnjuItXKcApOsoYGgfou4+POju3ZbwIQukDZ3B2COskvQkVTxqPNrQ0ZjT2mxZYoVlmGW9M+7N25g==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-18.2.6.tgz", + "integrity": "sha512-u12cJZttgs5j7gICHWSmcaTCu0EFXEzKqI8nkYCwq2MtuJlAXiMQSXYuEP9OU3Go4vMAPtQh2kShyOWCX5b4EQ==", "dev": true, "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.1802.2", - "@angular-devkit/build-webpack": "0.1802.2", - "@angular-devkit/core": "18.2.2", - "@angular/build": "18.2.2", + "@angular-devkit/architect": "0.1802.6", + "@angular-devkit/build-webpack": "0.1802.6", + "@angular-devkit/core": "18.2.6", + "@angular/build": "18.2.6", "@babel/core": "7.25.2", "@babel/generator": "7.25.0", "@babel/helper-annotate-as-pure": "7.24.7", @@ -269,7 +269,7 @@ "@babel/preset-env": "7.25.3", "@babel/runtime": "7.25.0", "@discoveryjs/json-ext": "0.6.1", - "@ngtools/webpack": "18.2.2", + "@ngtools/webpack": "18.2.6", "@vitejs/plugin-basic-ssl": "1.1.0", "ansi-colors": "4.1.3", "autoprefixer": "10.4.20", @@ -309,10 +309,10 @@ "terser": "5.31.6", "tree-kill": "1.2.2", "tslib": "2.6.3", - "vite": "5.4.0", + "vite": "5.4.6", "watchpack": "2.4.1", "webpack": "5.94.0", - "webpack-dev-middleware": "7.3.0", + "webpack-dev-middleware": "7.4.2", "webpack-dev-server": "5.0.4", "webpack-merge": "6.0.1", "webpack-subresource-integrity": "5.1.0" @@ -376,6 +376,89 @@ } } }, + "node_modules/@angular-devkit/build-angular/node_modules/@angular/build": { + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-18.2.6.tgz", + "integrity": "sha512-TQzX6Mi7uXFvmz7+OVl4Za7WawYPcx+B5Ewm6IY/DdMyB9P/Z4tbKb1LO+ynWUXYwm7avXo6XQQ4m5ArDY5F/A==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "2.3.0", + "@angular-devkit/architect": "0.1802.6", + "@babel/core": "7.25.2", + "@babel/helper-annotate-as-pure": "7.24.7", + "@babel/helper-split-export-declaration": "7.24.7", + "@babel/plugin-syntax-import-attributes": "7.24.7", + "@inquirer/confirm": "3.1.22", + "@vitejs/plugin-basic-ssl": "1.1.0", + "browserslist": "^4.23.0", + "critters": "0.0.24", + "esbuild": "0.23.0", + "fast-glob": "3.3.2", + "https-proxy-agent": "7.0.5", + "listr2": "8.2.4", + "lmdb": "3.0.13", + "magic-string": "0.30.11", + "mrmime": "2.0.0", + "parse5-html-rewriting-stream": "7.0.0", + "picomatch": "4.0.2", + "piscina": "4.6.1", + "rollup": "4.22.4", + "sass": "1.77.6", + "semver": "7.6.3", + "vite": "5.4.6", + "watchpack": "2.4.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "@angular/compiler-cli": "^18.0.0", + "@angular/localize": "^18.0.0", + "@angular/platform-server": "^18.0.0", + "@angular/service-worker": "^18.0.0", + "less": "^4.2.0", + "postcss": "^8.4.0", + "tailwindcss": "^2.0.0 || ^3.0.0", + "typescript": ">=5.4 <5.6" + }, + "peerDependenciesMeta": { + "@angular/localize": { + "optional": true + }, + "@angular/platform-server": { + "optional": true + }, + "@angular/service-worker": { + "optional": true + }, + "less": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tailwindcss": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", + "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/@esbuild/aix-ppc64": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.0.tgz", @@ -744,17 +827,219 @@ "node": ">=18" } }, - "node_modules/@angular-devkit/build-angular/node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz", + "integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==", + "cpu": [ + "arm" + ], "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-android-arm64": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz", + "integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz", + "integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-darwin-x64": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz", + "integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz", + "integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz", + "integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz", + "integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz", + "integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz", + "integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz", + "integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz", + "integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz", + "integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz", + "integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz", + "integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz", + "integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz", + "integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true }, "node_modules/@angular-devkit/build-angular/node_modules/esbuild": { "version": "0.23.0", @@ -762,7 +1047,6 @@ "integrity": "sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==", "dev": true, "hasInstallScript": true, - "optional": true, "bin": { "esbuild": "bin/esbuild" }, @@ -796,21 +1080,6 @@ "@esbuild/win32-x64": "0.23.0" } }, - "node_modules/@angular-devkit/build-angular/node_modules/is-wsl": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", - "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", - "dev": true, - "dependencies": { - "is-inside-container": "^1.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@angular-devkit/build-angular/node_modules/istanbul-lib-instrument": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", @@ -827,22 +1096,39 @@ "node": ">=10" } }, - "node_modules/@angular-devkit/build-angular/node_modules/open": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", - "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", + "node_modules/@angular-devkit/build-angular/node_modules/rollup": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz", + "integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==", "dev": true, "dependencies": { - "default-browser": "^5.2.1", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "is-wsl": "^3.1.0" + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" }, "engines": { - "node": ">=18" + "node": ">=18.0.0", + "npm": ">=8.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.22.4", + "@rollup/rollup-android-arm64": "4.22.4", + "@rollup/rollup-darwin-arm64": "4.22.4", + "@rollup/rollup-darwin-x64": "4.22.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.22.4", + "@rollup/rollup-linux-arm-musleabihf": "4.22.4", + "@rollup/rollup-linux-arm64-gnu": "4.22.4", + "@rollup/rollup-linux-arm64-musl": "4.22.4", + "@rollup/rollup-linux-powerpc64le-gnu": "4.22.4", + "@rollup/rollup-linux-riscv64-gnu": "4.22.4", + "@rollup/rollup-linux-s390x-gnu": "4.22.4", + "@rollup/rollup-linux-x64-gnu": "4.22.4", + "@rollup/rollup-linux-x64-musl": "4.22.4", + "@rollup/rollup-win32-arm64-msvc": "4.22.4", + "@rollup/rollup-win32-ia32-msvc": "4.22.4", + "@rollup/rollup-win32-x64-msvc": "4.22.4", + "fsevents": "~2.3.2" } }, "node_modules/@angular-devkit/build-angular/node_modules/tslib": { @@ -852,12 +1138,12 @@ "dev": true }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.1802.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1802.2.tgz", - "integrity": "sha512-Pj+YmKh0nJOKl6QAsqYh3SqfuVJrFqjyp5WrG9BgfsMD9GCMD+5teMHNYJlp+vG/C8e7VdZp4rqOon8K9Xn4Mw==", + "version": "0.1802.6", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1802.6.tgz", + "integrity": "sha512-JMLcXFaitJplwZMKkqhbYirINCRD6eOPZuIGaIOVynXYGWgvJkLT9t5C2wm9HqSLtp1K7NcYG2Y7PtTVR4krnQ==", "dev": true, "dependencies": { - "@angular-devkit/architect": "0.1802.2", + "@angular-devkit/architect": "0.1802.6", "rxjs": "7.8.1" }, "engines": { @@ -871,9 +1157,9 @@ } }, "node_modules/@angular-devkit/core": { - "version": "18.2.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.2.tgz", - "integrity": "sha512-Zz0tGptI/QQnUBDdp+1G5wGwQWMjpfe2oO+UohkrDVgFS71yVj4VDnOy51kMTxBvzw+36evTgthPpmzqPIfxBw==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.6.tgz", + "integrity": "sha512-la4CFvs5PcRWSkQ/H7TB5cPZirFVA9GoWk5LzIk8si6VjWBJRm8b3keKJoC9LlNeABRUIR5z0ocYkyQQUhdMfg==", "dev": true, "dependencies": { "ajv": "8.17.1", @@ -915,12 +1201,12 @@ } }, "node_modules/@angular-devkit/schematics": { - "version": "18.2.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.2.2.tgz", - "integrity": "sha512-PU6+3nX+gQ3gofR7BGwXuvNUNeeV2raURaZjlPfGpBqjyTBxukMV71QsTTWptAZT4WibCWkTFp6X1gvsOGbjMg==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.2.6.tgz", + "integrity": "sha512-uIttrQ2cQ2PWAFFVPeCoNR8xvs7tPJ2i8gzqsIwYdge107xDC6u9CqfgmBqPDSFpWj+IiC2Jwcm8Z4HYKU4+7A==", "dev": true, "dependencies": { - "@angular-devkit/core": "18.2.2", + "@angular-devkit/core": "18.2.6", "jsonc-parser": "3.3.1", "magic-string": "0.30.11", "ora": "5.4.1", @@ -933,9 +1219,9 @@ } }, "node_modules/@angular-eslint/builder": { - "version": "18.3.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-18.3.0.tgz", - "integrity": "sha512-httEQyqyBw3+0CRtAa7muFxHrauRfkEfk/jmrh5fn2Eiu+I53hAqFPgrwVi1V6AP/kj2zbAiWhd5xM3pMJdoRQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-18.3.1.tgz", + "integrity": "sha512-cPc7Ye9zDs5M4i+feL6vob+mh7yX5vxvOS5KQIhneUrp5e9D+IGuNFMmBLlOPpmklSc9XJBtuvI5Zjuh4z1ETw==", "dev": true, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", @@ -943,19 +1229,19 @@ } }, "node_modules/@angular-eslint/bundled-angular-compiler": { - "version": "18.3.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-18.3.0.tgz", - "integrity": "sha512-v/59FxUKnMzymVce99gV43huxoqXWMb85aKvzlNvLN+ScDu6ZE4YMiTQNpfapVL2lkxhs0uwB3jH17EYd5TcsA==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-18.3.1.tgz", + "integrity": "sha512-sikmkjfsXPpPTku1aQkQ1MNNEKGBgGGRvUN/WeNS9dhCJ4dxU3O7dZctt1aQWj+W3nbuUtDiimAWF5fZHGFE2Q==", "dev": true }, "node_modules/@angular-eslint/eslint-plugin": { - "version": "18.3.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-18.3.0.tgz", - "integrity": "sha512-Vl7gfPMXxvtHTjYdlzR161aj5xrqW6T57wd8ToQ7Gqzm0qHGfY6kE4SQobUa2LCYckTNSlv+zXe48C4ah/dSjw==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-18.3.1.tgz", + "integrity": "sha512-MP4Nm+SHboF8KdnN0KpPEGAaTTzDLPm3+S/4W3Mg8onqWCyadyd4mActh9mK/pvCj8TVlb/SW1zeTtdMYhwonw==", "dev": true, "dependencies": { - "@angular-eslint/bundled-angular-compiler": "18.3.0", - "@angular-eslint/utils": "18.3.0" + "@angular-eslint/bundled-angular-compiler": "18.3.1", + "@angular-eslint/utils": "18.3.1" }, "peerDependencies": { "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", @@ -964,13 +1250,13 @@ } }, "node_modules/@angular-eslint/eslint-plugin-template": { - "version": "18.3.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-18.3.0.tgz", - "integrity": "sha512-ddR/qwYbUeq9IpyVKrPbfZyRBTy6V8uc5I0JcBKttQ4CZ4joXhqsVgWFsI+JAMi8E66uNj1VC7NuKCOjDINv2Q==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-18.3.1.tgz", + "integrity": "sha512-hBJ3+f7VSidvrtYaXH7Vp0sWvblA9jLK2c6uQzhYGWdEDUcTg7g7VI9ThW39WvMbHqkyzNE4PPOynK69cBEDGg==", "dev": true, "dependencies": { - "@angular-eslint/bundled-angular-compiler": "18.3.0", - "@angular-eslint/utils": "18.3.0", + "@angular-eslint/bundled-angular-compiler": "18.3.1", + "@angular-eslint/utils": "18.3.1", "aria-query": "5.3.0", "axobject-query": "4.1.0" }, @@ -980,42 +1266,14 @@ "typescript": "*" } }, - "node_modules/@angular-eslint/eslint-plugin-template/node_modules/@angular-eslint/utils": { - "version": "18.3.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-18.3.0.tgz", - "integrity": "sha512-sCrkHkpxBJZLuCikdboZoawCfc2UgbJv+T14tu2uQCv+Vwzeadnu04vkeY2vTkA8GeBdBij/G9/N/nvwmwVw3g==", - "dev": true, - "dependencies": { - "@angular-eslint/bundled-angular-compiler": "18.3.0" - }, - "peerDependencies": { - "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": "*" - } - }, - "node_modules/@angular-eslint/eslint-plugin/node_modules/@angular-eslint/utils": { - "version": "18.3.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-18.3.0.tgz", - "integrity": "sha512-sCrkHkpxBJZLuCikdboZoawCfc2UgbJv+T14tu2uQCv+Vwzeadnu04vkeY2vTkA8GeBdBij/G9/N/nvwmwVw3g==", - "dev": true, - "dependencies": { - "@angular-eslint/bundled-angular-compiler": "18.3.0" - }, - "peerDependencies": { - "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": "*" - } - }, "node_modules/@angular-eslint/schematics": { - "version": "18.3.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-18.3.0.tgz", - "integrity": "sha512-rQ4DEWwf3f5n096GAK6JvXD0SRzRJ52WRaIyKg8MMkk6qvUDfZI8seOkcbjDtZoIe6Ds7DfqSfJgNVte75qvPQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-18.3.1.tgz", + "integrity": "sha512-BTsQHDu7LjvXannJTb5BqMPCFIHRNN94eRyb60VfjJxB/ZFtsbAQDFFOi5lEZsRsd4mBeUMuL9mW4IMcPtUQ9Q==", "dev": true, "dependencies": { - "@angular-eslint/eslint-plugin": "18.3.0", - "@angular-eslint/eslint-plugin-template": "18.3.0", + "@angular-eslint/eslint-plugin": "18.3.1", + "@angular-eslint/eslint-plugin-template": "18.3.1", "ignore": "5.3.2", "semver": "7.6.3", "strip-json-comments": "3.1.1" @@ -1026,12 +1284,12 @@ } }, "node_modules/@angular-eslint/template-parser": { - "version": "18.3.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-18.3.0.tgz", - "integrity": "sha512-1mUquqcnugI4qsoxcYZKZ6WMi6RPelDcJZg2YqGyuaIuhWmi3ZqJZLErSSpjP60+TbYZu7wM8Kchqa1bwJtEaQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-18.3.1.tgz", + "integrity": "sha512-JUUkfWH1G+u/Uk85ZYvJSt/qwN/Ko+jlXFtzBEcknJZsTWTwBcp36v77gPZe5FmKSziJZpyPUd+7Kiy6tuSCTw==", "dev": true, "dependencies": { - "@angular-eslint/bundled-angular-compiler": "18.3.0", + "@angular-eslint/bundled-angular-compiler": "18.3.1", "eslint-scope": "^8.0.2" }, "peerDependencies": { @@ -1039,485 +1297,24 @@ "typescript": "*" } }, - "node_modules/@angular/build": { - "version": "18.2.2", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-18.2.2.tgz", - "integrity": "sha512-okaDdTMXnDhvnnnih6rPQnexL6htfEAPr19bB1Ci9d31gEjVuKZCjlcw2sPZ6BUyilwC9nZlCI5vbH1Ljf6mzA==", + "node_modules/@angular-eslint/utils": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-18.3.1.tgz", + "integrity": "sha512-sd9niZI7h9H2FQ7OLiQsLFBhjhRQTASh+Q0+4+hyjv9idbSHBJli8Gsi2fqj9zhtMKpAZFTrWzuLUpubJ9UYbA==", "dev": true, "dependencies": { - "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.1802.2", - "@babel/core": "7.25.2", - "@babel/helper-annotate-as-pure": "7.24.7", - "@babel/helper-split-export-declaration": "7.24.7", - "@babel/plugin-syntax-import-attributes": "7.24.7", - "@inquirer/confirm": "3.1.22", - "@vitejs/plugin-basic-ssl": "1.1.0", - "browserslist": "^4.23.0", - "critters": "0.0.24", - "esbuild": "0.23.0", - "fast-glob": "3.3.2", - "https-proxy-agent": "7.0.5", - "listr2": "8.2.4", - "lmdb": "3.0.13", - "magic-string": "0.30.11", - "mrmime": "2.0.0", - "parse5-html-rewriting-stream": "7.0.0", - "picomatch": "4.0.2", - "piscina": "4.6.1", - "rollup": "4.20.0", - "sass": "1.77.6", - "semver": "7.6.3", - "vite": "5.4.0", - "watchpack": "2.4.1" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" + "@angular-eslint/bundled-angular-compiler": "18.3.1" }, "peerDependencies": { - "@angular/compiler-cli": "^18.0.0", - "@angular/localize": "^18.0.0", - "@angular/platform-server": "^18.0.0", - "@angular/service-worker": "^18.0.0", - "less": "^4.2.0", - "postcss": "^8.4.0", - "tailwindcss": "^2.0.0 || ^3.0.0", - "typescript": ">=5.4 <5.6" - }, - "peerDependenciesMeta": { - "@angular/localize": { - "optional": true - }, - "@angular/platform-server": { - "optional": true - }, - "@angular/service-worker": { - "optional": true - }, - "less": { - "optional": true - }, - "postcss": { - "optional": true - }, - "tailwindcss": { - "optional": true - } - } - }, - "node_modules/@angular/build/node_modules/@esbuild/aix-ppc64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.0.tgz", - "integrity": "sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/android-arm": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.0.tgz", - "integrity": "sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/android-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.0.tgz", - "integrity": "sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/android-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.0.tgz", - "integrity": "sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/darwin-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz", - "integrity": "sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/darwin-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.0.tgz", - "integrity": "sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/freebsd-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.0.tgz", - "integrity": "sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/freebsd-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.0.tgz", - "integrity": "sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/linux-arm": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.0.tgz", - "integrity": "sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/linux-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.0.tgz", - "integrity": "sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/linux-ia32": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.0.tgz", - "integrity": "sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/linux-loong64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.0.tgz", - "integrity": "sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/linux-mips64el": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.0.tgz", - "integrity": "sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/linux-ppc64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.0.tgz", - "integrity": "sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/linux-riscv64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.0.tgz", - "integrity": "sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/linux-s390x": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.0.tgz", - "integrity": "sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/linux-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.0.tgz", - "integrity": "sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/netbsd-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.0.tgz", - "integrity": "sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/openbsd-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.0.tgz", - "integrity": "sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/sunos-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.0.tgz", - "integrity": "sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/win32-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.0.tgz", - "integrity": "sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/win32-ia32": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.0.tgz", - "integrity": "sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/win32-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.0.tgz", - "integrity": "sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/esbuild": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.0.tgz", - "integrity": "sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.23.0", - "@esbuild/android-arm": "0.23.0", - "@esbuild/android-arm64": "0.23.0", - "@esbuild/android-x64": "0.23.0", - "@esbuild/darwin-arm64": "0.23.0", - "@esbuild/darwin-x64": "0.23.0", - "@esbuild/freebsd-arm64": "0.23.0", - "@esbuild/freebsd-x64": "0.23.0", - "@esbuild/linux-arm": "0.23.0", - "@esbuild/linux-arm64": "0.23.0", - "@esbuild/linux-ia32": "0.23.0", - "@esbuild/linux-loong64": "0.23.0", - "@esbuild/linux-mips64el": "0.23.0", - "@esbuild/linux-ppc64": "0.23.0", - "@esbuild/linux-riscv64": "0.23.0", - "@esbuild/linux-s390x": "0.23.0", - "@esbuild/linux-x64": "0.23.0", - "@esbuild/netbsd-x64": "0.23.0", - "@esbuild/openbsd-arm64": "0.23.0", - "@esbuild/openbsd-x64": "0.23.0", - "@esbuild/sunos-x64": "0.23.0", - "@esbuild/win32-arm64": "0.23.0", - "@esbuild/win32-ia32": "0.23.0", - "@esbuild/win32-x64": "0.23.0" + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" } }, "node_modules/@angular/cdk": { - "version": "18.2.2", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.2.tgz", - "integrity": "sha512-+u7ZcMA24WO03vDzlBJJWq+okZLFDeW9JrtHzrdiT09FDt4sdUp+7PddXaZcRHIXjJL+CaCLQ6slaqPNEufqgg==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.6.tgz", + "integrity": "sha512-Gfq/iv4zhlKYpdQkDaBRwxI71NHNUHM1Cs1XhnZ0/oFct5HXvSv1RHRGTKqBJLLACaAPzZKXJ/UglLoyO5CNiQ==", "dependencies": { "tslib": "^2.3.0" }, @@ -1531,17 +1328,17 @@ } }, "node_modules/@angular/cli": { - "version": "18.2.2", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-18.2.2.tgz", - "integrity": "sha512-HVVaMxnbID0q+V3KE+JqzGbPHcBUFo1RKhBZ/jxY7USZNzgtyYbRc0IYqPWNdr99UT5QefTJrjVazJo1nqQZvQ==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-18.2.6.tgz", + "integrity": "sha512-tdXsnV/w+Rgu8q0zFsLU5L9ImTVqrTol1vppHaQkJ/vuoHy+s8ZEbBqhVrO/ffosNb2xseUybGYvqMS4zkNQjg==", "dev": true, "dependencies": { - "@angular-devkit/architect": "0.1802.2", - "@angular-devkit/core": "18.2.2", - "@angular-devkit/schematics": "18.2.2", + "@angular-devkit/architect": "0.1802.6", + "@angular-devkit/core": "18.2.6", + "@angular-devkit/schematics": "18.2.6", "@inquirer/prompts": "5.3.8", "@listr2/prompt-adapter-inquirer": "2.0.15", - "@schematics/angular": "18.2.2", + "@schematics/angular": "18.2.6", "@yarnpkg/lockfile": "1.1.0", "ini": "4.1.3", "jsonc-parser": "3.3.1", @@ -1564,9 +1361,9 @@ } }, "node_modules/@angular/common": { - "version": "18.2.2", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-18.2.2.tgz", - "integrity": "sha512-AQe4xnnNNch/sXRnV82C8FmhijxPATKfPGojC2qbAG2o6VkWKgt5Lbj0O8WxvSIOS5Syedv+O2kLY/JMGWHNtw==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-18.2.6.tgz", + "integrity": "sha512-89793ow+wrI1c7C6kyMbnweLNIZHzXthosxAEjipRZGBrqBYjvTtkE45Fl+5yBa3JO7bAhyGkUnEoyvWtZIAEA==", "dependencies": { "tslib": "^2.3.0" }, @@ -1574,14 +1371,14 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/core": "18.2.2", + "@angular/core": "18.2.6", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/compiler": { - "version": "18.2.2", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-18.2.2.tgz", - "integrity": "sha512-gmVNCXZiv/CIk2eKRLnH19N9VsPuE2s3Oxm0MNi003zk1cLy7D4YEm4fSrjKXtPY8MMpRXiu5f63W94hLwWEVw==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-18.2.6.tgz", + "integrity": "sha512-3tX2/Qw+bZ8XzKitviH8jzNGyY0uohhehhBB57OJOCc+yr4ojy/7SYFnun1lSsRnDztdCE461641X4iQLCQ94w==", "dependencies": { "tslib": "^2.3.0" }, @@ -1589,7 +1386,7 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/core": "18.2.2" + "@angular/core": "18.2.6" }, "peerDependenciesMeta": { "@angular/core": { @@ -1598,9 +1395,9 @@ } }, "node_modules/@angular/compiler-cli": { - "version": "18.2.2", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-18.2.2.tgz", - "integrity": "sha512-fF7lDrTA12YGqVjF4LyMi4hm58cv9G6CWmzSlvun0nMYCwrbRNnakZsj19dOfiIqqu4MwHaF4w3PTmUSxkMuiw==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-18.2.6.tgz", + "integrity": "sha512-b5x9STfjNiNM/S0D+CnqRP9UOxPtSz1+RlCH5WdOMiW/p8j5p6dBix8YYgTe6Wg3OD7eItD2pnFQKgF/dWiopA==", "dependencies": { "@babel/core": "7.25.2", "@jridgewell/sourcemap-codec": "^1.4.14", @@ -1620,14 +1417,14 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/compiler": "18.2.2", + "@angular/compiler": "18.2.6", "typescript": ">=5.4 <5.6" } }, "node_modules/@angular/core": { - "version": "18.2.2", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-18.2.2.tgz", - "integrity": "sha512-Rx6XajL0Ydj9hXUSPDvL2Q/kMzWtbiE3VxZFJnkE+fLQiWvr0GncB+NTb/nQ6QlPQ0ly60DvuI3KLcGDuFtGVA==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-18.2.6.tgz", + "integrity": "sha512-PjFad2j4YBwLVTw+0Te8CJCa/tV0W8caTHG8aOjj3ObdL6ihGI+FKnwerLc9RVzDFd14BOO4C6/+LbOQAh3Ltw==", "dependencies": { "tslib": "^2.3.0" }, @@ -1640,9 +1437,9 @@ } }, "node_modules/@angular/forms": { - "version": "18.2.2", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-18.2.2.tgz", - "integrity": "sha512-K8cv0w6o7+ocQfUrdSA3XaKrYfa1+2TlmtyxPHjEd2mCu2R+Yqo5RqJ3P8keFewJ1+bSLhz6xnn6mumwl0RnUQ==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-18.2.6.tgz", + "integrity": "sha512-quGkUqTxlBaLB8C/RnpfFG57fdmNF5RQ+368N89Ma++2lpIsVAHaGZZn4yOyo3wNYaM2jBxNqaYxOzZNUl5Tig==", "dependencies": { "tslib": "^2.3.0" }, @@ -1650,16 +1447,16 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "18.2.2", - "@angular/core": "18.2.2", - "@angular/platform-browser": "18.2.2", + "@angular/common": "18.2.6", + "@angular/core": "18.2.6", + "@angular/platform-browser": "18.2.6", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/localize": { - "version": "18.2.2", - "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-18.2.2.tgz", - "integrity": "sha512-grWQ3CVbizOWCthGpyIlNNnZCpF/xpWYa6tIsPzKOXLCyqFQ7vOEtSludNN1nsUmMlZQt76+wA17Fx0qcNx0EA==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-18.2.6.tgz", + "integrity": "sha512-4NZwh5EAyXItmwv6hqilV+JyN8DT+d+S1rW+M1IwJqC9asCDfpFqipKpuQF81LQKeLH0mn/phNfVbnJCLP0Tkw==", "dependencies": { "@babel/core": "7.25.2", "@types/babel__core": "7.20.5", @@ -1675,14 +1472,14 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/compiler": "18.2.2", - "@angular/compiler-cli": "18.2.2" + "@angular/compiler": "18.2.6", + "@angular/compiler-cli": "18.2.6" } }, "node_modules/@angular/platform-browser": { - "version": "18.2.2", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.2.2.tgz", - "integrity": "sha512-Bfvl8elCFxyJ9vlwamr4X5sVMcp/tSwBal2coyl0WR+/PH2PAAtf+/WMYxIN90yZmPiJx6RZWUSJRlHOFiFp3A==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.2.6.tgz", + "integrity": "sha512-RA8UMiYNLga+QMwpKcDw1357gYPfPyY/rmLeezMak//BbsENFYQOJ4Z6DBOBNiPlHxmBsUJMGaKdlpQhfCROyQ==", "dependencies": { "tslib": "^2.3.0" }, @@ -1690,9 +1487,9 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/animations": "18.2.2", - "@angular/common": "18.2.2", - "@angular/core": "18.2.2" + "@angular/animations": "18.2.6", + "@angular/common": "18.2.6", + "@angular/core": "18.2.6" }, "peerDependenciesMeta": { "@angular/animations": { @@ -1701,9 +1498,9 @@ } }, "node_modules/@angular/platform-browser-dynamic": { - "version": "18.2.2", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-18.2.2.tgz", - "integrity": "sha512-UM/+1nY4iIj1v4lxAmV3XRHPAh/4qfNKScCLq8tJGot64rPCbtCl0Rl8rFFGqxAFvTErVDaJycUgWNZSfVl/hw==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-18.2.6.tgz", + "integrity": "sha512-kGBU3FNc+DF9r33hwHZqiWoZgQbCDdEIucU0NCLCIg0Hw6/Q9Hr2ndjxQI+WynCPg0JeBn34jpouvpeJer3YDQ==", "dependencies": { "tslib": "^2.3.0" }, @@ -1711,16 +1508,16 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "18.2.2", - "@angular/compiler": "18.2.2", - "@angular/core": "18.2.2", - "@angular/platform-browser": "18.2.2" + "@angular/common": "18.2.6", + "@angular/compiler": "18.2.6", + "@angular/core": "18.2.6", + "@angular/platform-browser": "18.2.6" } }, "node_modules/@angular/router": { - "version": "18.2.2", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-18.2.2.tgz", - "integrity": "sha512-tBHwuNtZNjzYAoVdveTI1ke/ZnQjKhc7gqDk9HCH2JUpdQhGbTvCKwDM51ktJpPMPcZlA263lQyy7VIyvdtK0A==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-18.2.6.tgz", + "integrity": "sha512-t57Sqja8unHhZlPr+4CWnQacuox2M4p2pMHps+31wt337qH6mKf4jqDmK0dE/MFdRyKjT2a2E/2NwtxXxcWNuw==", "dependencies": { "tslib": "^2.3.0" }, @@ -1728,9 +1525,9 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "18.2.2", - "@angular/core": "18.2.2", - "@angular/platform-browser": "18.2.2", + "@angular/common": "18.2.6", + "@angular/core": "18.2.6", + "@angular/platform-browser": "18.2.6", "rxjs": "^6.5.3 || ^7.4.0" } }, @@ -2324,12 +2121,12 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", - "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.6.tgz", + "integrity": "sha512-sXaDXaJN9SNLymBdlWFA+bjzBhFD617ZaFiY13dGt7TVslVvVgA6fkZOP7Ki3IGElC45lwHdOTrCtKZGVAWeLQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -5620,9 +5417,9 @@ } }, "node_modules/@ng-select/ng-select": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/@ng-select/ng-select/-/ng-select-13.7.0.tgz", - "integrity": "sha512-GMNu3bLYxWAbgy9pXZ4RgnWp/cxRcrWRQdxLLyg8p9gMCLpim1p4TXR8laXJKK25MKG/LEaWgs+90yCVOoWgZA==", + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/@ng-select/ng-select/-/ng-select-13.9.0.tgz", + "integrity": "sha512-FqoNEvWZSfUWjirpUjsfESpgzZ3sEqSKcIbUirBmviKnYMbyUZbtEChjox+ha2vtHLpdA+/mDoYT5fL8Z/0qJA==", "dependencies": { "tslib": "^2.3.1" }, @@ -5652,9 +5449,9 @@ } }, "node_modules/@ngtools/webpack": { - "version": "18.2.2", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.2.tgz", - "integrity": "sha512-YhADmc+lVjLt3kze07A+yLry2yzcghdclu+7D3EDfa6fG2Pk33HK3MY2I0Z0BO+Ivoq7cV7yxm+naR+Od0Y5ng==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.6.tgz", + "integrity": "sha512-7HwOPE1EOgcHnpt4brSiT8G2CcXB50G0+CbCBaKGy4LYCG3Y3mrlzF5Fup9HvMJ6Tzqd62RqzpKKYBiGUT7hxg==", "dev": true, "engines": { "node": "^18.19.1 || ^20.11.1 || >=22.0.0", @@ -6192,9 +5989,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz", - "integrity": "sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.23.0.tgz", + "integrity": "sha512-8OR+Ok3SGEMsAZispLx8jruuXw0HVF16k+ub2eNXKHDmdxL4cf9NlNpAzhlOhNyXzKDEJuFeq0nZm+XlNb1IFw==", "cpu": [ "arm" ], @@ -6205,9 +6002,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.20.0.tgz", - "integrity": "sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.23.0.tgz", + "integrity": "sha512-rEFtX1nP8gqmLmPZsXRMoLVNB5JBwOzIAk/XAcEPuKrPa2nPJ+DuGGpfQUR0XjRm8KjHfTZLpWbKXkA5BoFL3w==", "cpu": [ "arm64" ], @@ -6218,9 +6015,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.20.0.tgz", - "integrity": "sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.23.0.tgz", + "integrity": "sha512-ZbqlMkJRMMPeapfaU4drYHns7Q5MIxjM/QeOO62qQZGPh9XWziap+NF9fsqPHT0KzEL6HaPspC7sOwpgyA3J9g==", "cpu": [ "arm64" ], @@ -6231,9 +6028,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.20.0.tgz", - "integrity": "sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.23.0.tgz", + "integrity": "sha512-PfmgQp78xx5rBCgn2oYPQ1rQTtOaQCna0kRaBlc5w7RlA3TDGGo7m3XaptgitUZ54US9915i7KeVPHoy3/W8tA==", "cpu": [ "x64" ], @@ -6244,9 +6041,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.20.0.tgz", - "integrity": "sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.23.0.tgz", + "integrity": "sha512-WAeZfAAPus56eQgBioezXRRzArAjWJGjNo/M+BHZygUcs9EePIuGI1Wfc6U/Ki+tMW17FFGvhCfYnfcKPh18SA==", "cpu": [ "arm" ], @@ -6257,9 +6054,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.20.0.tgz", - "integrity": "sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.23.0.tgz", + "integrity": "sha512-v7PGcp1O5XKZxKX8phTXtmJDVpE20Ub1eF6w9iMmI3qrrPak6yR9/5eeq7ziLMrMTjppkkskXyxnmm00HdtXjA==", "cpu": [ "arm" ], @@ -6270,9 +6067,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.20.0.tgz", - "integrity": "sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.23.0.tgz", + "integrity": "sha512-nAbWsDZ9UkU6xQiXEyXBNHAKbzSAi95H3gTStJq9UGiS1v+YVXwRHcQOQEF/3CHuhX5BVhShKoeOf6Q/1M+Zhg==", "cpu": [ "arm64" ], @@ -6283,9 +6080,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.20.0.tgz", - "integrity": "sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.23.0.tgz", + "integrity": "sha512-5QT/Di5FbGNPaVw8hHO1wETunwkPuZBIu6W+5GNArlKHD9fkMHy7vS8zGHJk38oObXfWdsuLMogD4sBySLJ54g==", "cpu": [ "arm64" ], @@ -6296,9 +6093,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.20.0.tgz", - "integrity": "sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.23.0.tgz", + "integrity": "sha512-Sefl6vPyn5axzCsO13r1sHLcmPuiSOrKIImnq34CBurntcJ+lkQgAaTt/9JkgGmaZJ+OkaHmAJl4Bfd0DmdtOQ==", "cpu": [ "ppc64" ], @@ -6309,9 +6106,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.20.0.tgz", - "integrity": "sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.23.0.tgz", + "integrity": "sha512-o4QI2KU/QbP7ZExMse6ULotdV3oJUYMrdx3rBZCgUF3ur3gJPfe8Fuasn6tia16c5kZBBw0aTmaUygad6VB/hQ==", "cpu": [ "riscv64" ], @@ -6322,9 +6119,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.20.0.tgz", - "integrity": "sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.23.0.tgz", + "integrity": "sha512-+bxqx+V/D4FGrpXzPGKp/SEZIZ8cIW3K7wOtcJAoCrmXvzRtmdUhYNbgd+RztLzfDEfA2WtKj5F4tcbNPuqgeg==", "cpu": [ "s390x" ], @@ -6335,9 +6132,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.20.0.tgz", - "integrity": "sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.23.0.tgz", + "integrity": "sha512-I/eXsdVoCKtSgK9OwyQKPAfricWKUMNCwJKtatRYMmDo5N859tbO3UsBw5kT3dU1n6ZcM1JDzPRSGhAUkxfLxw==", "cpu": [ "x64" ], @@ -6348,9 +6145,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.20.0.tgz", - "integrity": "sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.23.0.tgz", + "integrity": "sha512-4ZoDZy5ShLbbe1KPSafbFh1vbl0asTVfkABC7eWqIs01+66ncM82YJxV2VtV3YVJTqq2P8HMx3DCoRSWB/N3rw==", "cpu": [ "x64" ], @@ -6361,9 +6158,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.20.0.tgz", - "integrity": "sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.23.0.tgz", + "integrity": "sha512-+5Ky8dhft4STaOEbZu3/NU4QIyYssKO+r1cD3FzuusA0vO5gso15on7qGzKdNXnc1gOrsgCqZjRw1w+zL4y4hQ==", "cpu": [ "arm64" ], @@ -6374,9 +6171,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.20.0.tgz", - "integrity": "sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.23.0.tgz", + "integrity": "sha512-0SPJk4cPZQhq9qA1UhIRumSE3+JJIBBjtlGl5PNC///BoaByckNZd53rOYD0glpTkYFBQSt7AkMeLVPfx65+BQ==", "cpu": [ "ia32" ], @@ -6387,9 +6184,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.20.0.tgz", - "integrity": "sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.23.0.tgz", + "integrity": "sha512-lqCK5GQC8fNo0+JvTSxcG7YB1UKYp8yrNLhsArlvPWN+16ovSZgoehlVHg6X0sSWPUkpjRBR5TuR12ZugowZ4g==", "cpu": [ "x64" ], @@ -6400,13 +6197,13 @@ ] }, "node_modules/@schematics/angular": { - "version": "18.2.2", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-18.2.2.tgz", - "integrity": "sha512-0uPA1kQ38RnbNrzMlveX/QAqQIDu2INl5IYd3EUbJZRfYSp1VVyOSyuIBJ+1iUl5Y5VUa2uylaVZXhFdKWprXw==", + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-18.2.6.tgz", + "integrity": "sha512-Y988EoOEQDLEyHu3414T6AeVUyx21AexBHQNbUNQkK8cxlxyB6m1eH1cx6vFgLRFUTsLVv+C6Ln/ICNTfLcG4A==", "dev": true, "dependencies": { - "@angular-devkit/core": "18.2.2", - "@angular-devkit/schematics": "18.2.2", + "@angular-devkit/core": "18.2.6", + "@angular-devkit/schematics": "18.2.6", "jsonc-parser": "3.3.1" }, "engines": { @@ -6680,9 +6477,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true }, "node_modules/@types/express": { @@ -6698,9 +6495,21 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.19.5", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz", - "integrity": "sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.0.tgz", + "integrity": "sha512-AbXMTZGt40T+KON9/Fdxx0B2WK5hsgxcfXJLr5bFpZ7b4JCex2WyQPTEKdXqfHiY5nKKBScZ7yCoO6Pvgxfvnw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/express/node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", "dev": true, "dependencies": { "@types/node": "*", @@ -6725,9 +6534,9 @@ "dev": true }, "node_modules/@types/http-proxy": { - "version": "1.17.14", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", - "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", + "version": "1.17.15", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.15.tgz", + "integrity": "sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==", "dev": true, "dependencies": { "@types/node": "*" @@ -6818,9 +6627,9 @@ } }, "node_modules/@types/qs": { - "version": "6.9.15", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", - "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==", + "version": "6.9.16", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz", + "integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==", "dev": true }, "node_modules/@types/range-parser": { @@ -7825,13 +7634,13 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz", - "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==", + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", + "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", "dev": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.1", - "core-js-compat": "^3.36.1" + "@babel/helper-define-polyfill-provider": "^0.6.2", + "core-js-compat": "^3.38.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -8003,21 +7812,6 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/bonjour-service": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", @@ -8837,57 +8631,13 @@ "node": ">=10.13.0" } }, - "node_modules/copy-webpack-plugin/node_modules/globby": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", - "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", - "dev": true, - "dependencies": { - "@sindresorhus/merge-streams": "^2.1.0", - "fast-glob": "^3.3.2", - "ignore": "^5.2.4", - "path-type": "^5.0.0", - "slash": "^5.1.0", - "unicorn-magic": "^0.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/copy-webpack-plugin/node_modules/path-type": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", - "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/copy-webpack-plugin/node_modules/slash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", - "dev": true, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/core-js-compat": { - "version": "3.37.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.1.tgz", - "integrity": "sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==", + "version": "3.38.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz", + "integrity": "sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==", "dev": true, "dependencies": { - "browserslist": "^4.23.0" + "browserslist": "^4.23.3" }, "funding": { "type": "opencollective", @@ -9385,6 +9135,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -9601,9 +9363,9 @@ } }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "dev": true, "engines": { "node": ">= 0.8" @@ -10260,9 +10022,9 @@ "dev": true }, "node_modules/express": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.20.0.tgz", - "integrity": "sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "dev": true, "dependencies": { "accepts": "~1.3.8", @@ -10277,7 +10039,7 @@ "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", "merge-descriptors": "1.0.3", @@ -10286,11 +10048,11 @@ "parseurl": "~1.3.3", "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "0.19.0", - "serve-static": "1.16.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -10310,15 +10072,6 @@ "ms": "2.0.0" } }, - "node_modules/express/node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -10436,13 +10189,13 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dev": true, "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -10535,9 +10288,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", "dev": true, "funding": [ { @@ -10800,6 +10553,38 @@ "node": ">=4" } }, + "node_modules/globby": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", + "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", + "dev": true, + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -13621,9 +13406,9 @@ } }, "node_modules/launch-editor": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.8.1.tgz", - "integrity": "sha512-elBx2l/tp9z99X5H/qev8uyDywVh0VXAwEbjk8kJhnc5grOFkGh7aW6q55me9xnYbss261XtnUrysZ+XvGbhQA==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.9.1.tgz", + "integrity": "sha512-Gcnl4Bd+hRO9P9icCP/RVVT2o8SFlPXofuCxvA2SaZuH45whSvf5p8x5oih5ftLiVhEI4sp5xDY+R+b3zJBh5w==", "dev": true, "dependencies": { "picocolors": "^1.0.0", @@ -13882,6 +13667,12 @@ "@lmdb/lmdb-win32-x64": "3.0.13" } }, + "node_modules/lmdb/node_modules/node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "dev": true + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -14327,9 +14118,9 @@ } }, "node_modules/memfs": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.11.1.tgz", - "integrity": "sha512-LZcMTBAgqUUKNXZagcZxvXXfgF1bHX7Y7nQ0QyEiNbRJgE29GhgPd8Yna1VQcLlPiHt/5RFJMWYN9Uv/VPNvjQ==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.12.0.tgz", + "integrity": "sha512-74wDsex5tQDSClVkeK1vtxqYCAgCoXxx+K4NSHzgU/muYVYByFqa+0RnrPO9NM6naWm1+G9JmZ0p6QHhXmeYfA==", "dev": true, "dependencies": { "@jsonjoy.com/json-pack": "^1.0.3", @@ -14841,9 +14632,9 @@ "dev": true }, "node_modules/ng2-pdf-viewer": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/ng2-pdf-viewer/-/ng2-pdf-viewer-10.3.0.tgz", - "integrity": "sha512-zU51lVcsmCy1Nytw94r2ABHfdBKlJWc+Zllk7Fct3pT3b7Q8UbMiZ8IbA4d5iXoe2/iznsS2YXGzMn0/vPHcXA==", + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/ng2-pdf-viewer/-/ng2-pdf-viewer-10.3.1.tgz", + "integrity": "sha512-Tdeu74Go1qzBMmpVbiKA96bgtHCeh+Qnq7ErVLHsV/9TPoCO6cRBsGW/7ojdH1S92MnnM6/iQGLY3EnMYh1wOg==", "dependencies": { "pdfjs-dist": "^4.5.136", "tslib": "^2.3.0" @@ -14950,19 +14741,13 @@ "node-gyp-build": "^4.2.2" } }, - "node_modules/nice-napi/node_modules/node-addon-api": { + "node_modules/node-addon-api": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", "dev": true, "optional": true }, - "node_modules/node-addon-api": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", - "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", - "dev": true - }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -15418,6 +15203,39 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/open": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", + "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", + "dev": true, + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open/node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -15541,9 +15359,9 @@ } }, "node_modules/ordered-binary": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.5.1.tgz", - "integrity": "sha512-5VyHfHY3cd0iza71JepYG50My+YUbrFtGoUz2ooEydPyPM7Aai/JW098juLr+RG6+rDJuzNNTsEQu2DZa1A41A==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.5.2.tgz", + "integrity": "sha512-JTo+4+4Fw7FreyAvlSLjb1BBVaxEQAacmjD3jjuyPZclpbEghTvQZbXBb2qPd2LeIMxiHwXBZUcpmG2Gl/mDEA==", "dev": true }, "node_modules/os-tmpdir": { @@ -15648,9 +15466,9 @@ } }, "node_modules/package-json-from-dist": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", - "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true }, "node_modules/pacote": { @@ -15992,6 +15810,18 @@ "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", "dev": true }, + "node_modules/path-type": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/path2d": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/path2d/-/path2d-0.2.1.tgz", @@ -16014,9 +15844,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==" }, "node_modules/picomatch": { "version": "4.0.2", @@ -16144,9 +15974,9 @@ } }, "node_modules/pkg-dir/node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", + "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", "dev": true, "engines": { "node": ">=12.20" @@ -16324,9 +16154,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.1.tgz", - "integrity": "sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "dev": true, "dependencies": { "cssesc": "^3.0.0", @@ -16485,12 +16315,12 @@ ] }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dev": true, "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -16620,9 +16450,9 @@ "dev": true }, "node_modules/regenerate-unicode-properties": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", - "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", "dev": true, "dependencies": { "regenerate": "^1.4.2" @@ -16632,9 +16462,9 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==", + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "dev": true }, "node_modules/regenerator-transform": { @@ -16647,9 +16477,9 @@ } }, "node_modules/regex-parser": { - "version": "2.2.11", - "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", - "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.3.0.tgz", + "integrity": "sha512-TVILVSz2jY5D47F4mA4MppkBrafEaiUWJO/TcZHEIuI13AqoZMkK1WMA4Om1YkYbTx+9Ki1/tSUXbceyr9saRg==", "dev": true }, "node_modules/regexpu-core": { @@ -16852,12 +16682,12 @@ } }, "node_modules/rollup": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.20.0.tgz", - "integrity": "sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.23.0.tgz", + "integrity": "sha512-vXB4IT9/KLDrS2WRXmY22sVB2wTsTwkpxjB8Q3mnakTENcYw3FRmfdYDy/acNmls+lHmDazgrRjK/yQ6hQAtwA==", "dev": true, "dependencies": { - "@types/estree": "1.0.5" + "@types/estree": "1.0.6" }, "bin": { "rollup": "dist/bin/rollup" @@ -16867,22 +16697,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.20.0", - "@rollup/rollup-android-arm64": "4.20.0", - "@rollup/rollup-darwin-arm64": "4.20.0", - "@rollup/rollup-darwin-x64": "4.20.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.20.0", - "@rollup/rollup-linux-arm-musleabihf": "4.20.0", - "@rollup/rollup-linux-arm64-gnu": "4.20.0", - "@rollup/rollup-linux-arm64-musl": "4.20.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.20.0", - "@rollup/rollup-linux-riscv64-gnu": "4.20.0", - "@rollup/rollup-linux-s390x-gnu": "4.20.0", - "@rollup/rollup-linux-x64-gnu": "4.20.0", - "@rollup/rollup-linux-x64-musl": "4.20.0", - "@rollup/rollup-win32-arm64-msvc": "4.20.0", - "@rollup/rollup-win32-ia32-msvc": "4.20.0", - "@rollup/rollup-win32-x64-msvc": "4.20.0", + "@rollup/rollup-android-arm-eabi": "4.23.0", + "@rollup/rollup-android-arm64": "4.23.0", + "@rollup/rollup-darwin-arm64": "4.23.0", + "@rollup/rollup-darwin-x64": "4.23.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.23.0", + "@rollup/rollup-linux-arm-musleabihf": "4.23.0", + "@rollup/rollup-linux-arm64-gnu": "4.23.0", + "@rollup/rollup-linux-arm64-musl": "4.23.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.23.0", + "@rollup/rollup-linux-riscv64-gnu": "4.23.0", + "@rollup/rollup-linux-s390x-gnu": "4.23.0", + "@rollup/rollup-linux-x64-gnu": "4.23.0", + "@rollup/rollup-linux-x64-musl": "4.23.0", + "@rollup/rollup-win32-arm64-msvc": "4.23.0", + "@rollup/rollup-win32-ia32-msvc": "4.23.0", + "@rollup/rollup-win32-x64-msvc": "4.23.0", "fsevents": "~2.3.2" } }, @@ -17012,9 +16842,9 @@ } }, "node_modules/sax": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", - "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", "dev": true, "optional": true }, @@ -17118,6 +16948,15 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -17212,60 +17051,15 @@ } }, "node_modules/serve-static": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.0.tgz", - "integrity": "sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dev": true, "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-static/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-static/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/serve-static/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/serve-static/node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" @@ -17537,9 +17331,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -18435,9 +18229,9 @@ "dev": true }, "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", "dev": true, "engines": { "node": ">=4" @@ -18457,9 +18251,9 @@ } }, "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", "dev": true, "engines": { "node": ">=4" @@ -18687,14 +18481,14 @@ } }, "node_modules/vite": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.0.tgz", - "integrity": "sha512-5xokfMX0PIiwCMCMb9ZJcMyh5wbBun0zUzKib+L65vAZ8GY9ePZMXxFrHbr/Kyll2+LSCY7xtERPpxkBDKngwg==", + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz", + "integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==", "dev": true, "dependencies": { "esbuild": "^0.21.3", - "postcss": "^8.4.40", - "rollup": "^4.13.0" + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "bin": { "vite": "bin/vite.js" @@ -18745,6 +18539,34 @@ } } }, + "node_modules/vite/node_modules/postcss": { + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/w3c-xmlserializer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", @@ -18859,9 +18681,9 @@ } }, "node_modules/webpack-dev-middleware": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.3.0.tgz", - "integrity": "sha512-xD2qnNew+F6KwOGZR7kWdbIou/ud7cVqLEXeK1q0nHcNsX/u7ul/fSdlOTX4ntSL5FNFy7ZJJXbf0piF591JYw==", + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz", + "integrity": "sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==", "dev": true, "dependencies": { "colorette": "^2.0.10", @@ -18955,18 +18777,6 @@ "balanced-match": "^1.0.0" } }, - "node_modules/webpack-dev-server/node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/webpack-dev-server/node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -19011,21 +18821,6 @@ } } }, - "node_modules/webpack-dev-server/node_modules/is-wsl": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", - "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", - "dev": true, - "dependencies": { - "is-inside-container": "^1.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/webpack-dev-server/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -19041,24 +18836,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/webpack-dev-server/node_modules/open": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", - "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", - "dev": true, - "dependencies": { - "default-browser": "^5.2.1", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "is-wsl": "^3.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/webpack-dev-server/node_modules/rimraf": { "version": "5.0.10", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", diff --git a/src-ui/package.json b/src-ui/package.json index ef0bf17bc..a12b21b5d 100644 --- a/src-ui/package.json +++ b/src-ui/package.json @@ -11,23 +11,23 @@ }, "private": true, "dependencies": { - "@angular/cdk": "^18.2.2", - "@angular/common": "~18.2.2", - "@angular/compiler": "~18.2.2", - "@angular/core": "~18.2.2", - "@angular/forms": "~18.2.2", - "@angular/localize": "~18.2.2", - "@angular/platform-browser": "~18.2.2", - "@angular/platform-browser-dynamic": "~18.2.2", - "@angular/router": "~18.2.2", + "@angular/cdk": "^18.2.6", + "@angular/common": "~18.2.6", + "@angular/compiler": "~18.2.6", + "@angular/core": "~18.2.6", + "@angular/forms": "~18.2.6", + "@angular/localize": "~18.2.6", + "@angular/platform-browser": "~18.2.6", + "@angular/platform-browser-dynamic": "~18.2.6", + "@angular/router": "~18.2.6", "@ng-bootstrap/ng-bootstrap": "^17.0.1", - "@ng-select/ng-select": "^13.7.0", + "@ng-select/ng-select": "^13.9.0", "@ngneat/dirty-check-forms": "^3.0.3", "@popperjs/core": "^2.11.8", "bootstrap": "^5.3.3", "file-saver": "^2.0.5", "mime-names": "^1.0.0", - "ng2-pdf-viewer": "^10.3.0", + "ng2-pdf-viewer": "^10.3.1", "ngx-bootstrap-icons": "^1.9.3", "ngx-color": "^9.0.0", "ngx-cookie-service": "^18.0.0", @@ -42,14 +42,14 @@ "@angular-builders/custom-webpack": "^18.0.0", "@angular-builders/jest": "^18.0.0", "@angular-devkit/build-angular": "^18.2.2", - "@angular-devkit/core": "^18.2.2", - "@angular-devkit/schematics": "^18.2.2", - "@angular-eslint/builder": "18.3.0", - "@angular-eslint/eslint-plugin": "18.3.0", - "@angular-eslint/eslint-plugin-template": "18.3.0", - "@angular-eslint/schematics": "18.3.0", - "@angular-eslint/template-parser": "18.3.0", - "@angular/cli": "~18.2.2", + "@angular-devkit/core": "^18.2.6", + "@angular-devkit/schematics": "^18.2.6", + "@angular-eslint/builder": "18.3.1", + "@angular-eslint/eslint-plugin": "18.3.1", + "@angular-eslint/eslint-plugin-template": "18.3.1", + "@angular-eslint/schematics": "18.3.1", + "@angular-eslint/template-parser": "18.3.1", + "@angular/cli": "~18.2.6", "@angular/compiler-cli": "~18.2.2", "@codecov/webpack-plugin": "^1.0.1", "@playwright/test": "^1.46.1", From a7424a7bfe57294afa91b429d663b19834a72338 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 13:59:11 -0700 Subject: [PATCH 02/11] Chore(deps-dev): Bump @playwright/test from 1.46.1 to 1.47.2 in /src-ui (#7828) Bumps [@playwright/test](https://github.com/microsoft/playwright) from 1.46.1 to 1.47.2. - [Release notes](https://github.com/microsoft/playwright/releases) - [Commits](https://github.com/microsoft/playwright/compare/v1.46.1...v1.47.2) --- updated-dependencies: - dependency-name: "@playwright/test" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src-ui/package-lock.json | 24 ++++++++++++------------ src-ui/package.json | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src-ui/package-lock.json b/src-ui/package-lock.json index 7871bd71e..862748b7f 100644 --- a/src-ui/package-lock.json +++ b/src-ui/package-lock.json @@ -50,7 +50,7 @@ "@angular/cli": "~18.2.6", "@angular/compiler-cli": "~18.2.2", "@codecov/webpack-plugin": "^1.0.1", - "@playwright/test": "^1.46.1", + "@playwright/test": "^1.47.2", "@types/jest": "^29.5.12", "@types/node": "^22.0.2", "@typescript-eslint/eslint-plugin": "^8.3.0", @@ -5965,12 +5965,12 @@ } }, "node_modules/@playwright/test": { - "version": "1.46.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.46.1.tgz", - "integrity": "sha512-Fq6SwLujA/DOIvNC2EL/SojJnkKf/rAwJ//APpJJHRyMi1PdKrY3Az+4XNQ51N4RTbItbIByQ0jgd1tayq1aeA==", + "version": "1.47.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.47.2.tgz", + "integrity": "sha512-jTXRsoSPONAs8Za9QEQdyjFn+0ZQFjCiIztAIF6bi1HqhBzG9Ma7g1WotyiGqFSBRZjIEqMdT8RUlbk1QVhzCQ==", "dev": true, "dependencies": { - "playwright": "1.46.1" + "playwright": "1.47.2" }, "bin": { "playwright": "cli.js" @@ -15986,12 +15986,12 @@ } }, "node_modules/playwright": { - "version": "1.46.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.46.1.tgz", - "integrity": "sha512-oPcr1yqoXLCkgKtD5eNUPLiN40rYEM39odNpIb6VE6S7/15gJmA1NzVv6zJYusV0e7tzvkU/utBFNa/Kpxmwng==", + "version": "1.47.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.47.2.tgz", + "integrity": "sha512-nx1cLMmQWqmA3UsnjaaokyoUpdVaaDhJhMoxX2qj3McpjnsqFHs516QAKYhqHAgOP+oCFTEOCOAaD1RgD/RQfA==", "dev": true, "dependencies": { - "playwright-core": "1.46.1" + "playwright-core": "1.47.2" }, "bin": { "playwright": "cli.js" @@ -16004,9 +16004,9 @@ } }, "node_modules/playwright-core": { - "version": "1.46.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.46.1.tgz", - "integrity": "sha512-h9LqIQaAv+CYvWzsZ+h3RsrqCStkBHlgo6/TJlFst3cOTlLghBQlJwPOZKQJTKNaD3QIB7aAVQ+gfWbN3NXB7A==", + "version": "1.47.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.47.2.tgz", + "integrity": "sha512-3JvMfF+9LJfe16l7AbSmU555PaTl2tPyQsVInqm3id16pdDfvZ8TTZ/pyzmkbDrZTQefyzU7AIHlZqQnxpqHVQ==", "dev": true, "bin": { "playwright-core": "cli.js" diff --git a/src-ui/package.json b/src-ui/package.json index a12b21b5d..be2e37cc8 100644 --- a/src-ui/package.json +++ b/src-ui/package.json @@ -52,7 +52,7 @@ "@angular/cli": "~18.2.6", "@angular/compiler-cli": "~18.2.2", "@codecov/webpack-plugin": "^1.0.1", - "@playwright/test": "^1.46.1", + "@playwright/test": "^1.47.2", "@types/jest": "^29.5.12", "@types/node": "^22.0.2", "@typescript-eslint/eslint-plugin": "^8.3.0", From 85e57ede9b094ef98ead1469dcf12df8206abb73 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 21:10:16 +0000 Subject: [PATCH 03/11] Chore(deps-dev): Bump the frontend-jest-dependencies group (#7826) Bumps the frontend-jest-dependencies group in /src-ui with 2 updates: [@types/jest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jest) and [jest-preset-angular](https://github.com/thymikee/jest-preset-angular). Updates `@types/jest` from 29.5.12 to 29.5.13 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jest) Updates `jest-preset-angular` from 14.2.2 to 14.2.4 - [Release notes](https://github.com/thymikee/jest-preset-angular/releases) - [Changelog](https://github.com/thymikee/jest-preset-angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/thymikee/jest-preset-angular/compare/v14.2.2...v14.2.4) --- updated-dependencies: - dependency-name: "@types/jest" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: frontend-jest-dependencies - dependency-name: jest-preset-angular dependency-type: direct:development update-type: version-update:semver-patch dependency-group: frontend-jest-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src-ui/package-lock.json | 16 ++++++++-------- src-ui/package.json | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src-ui/package-lock.json b/src-ui/package-lock.json index 862748b7f..96843936c 100644 --- a/src-ui/package-lock.json +++ b/src-ui/package-lock.json @@ -51,7 +51,7 @@ "@angular/compiler-cli": "~18.2.2", "@codecov/webpack-plugin": "^1.0.1", "@playwright/test": "^1.47.2", - "@types/jest": "^29.5.12", + "@types/jest": "^29.5.13", "@types/node": "^22.0.2", "@typescript-eslint/eslint-plugin": "^8.3.0", "@typescript-eslint/parser": "^8.3.0", @@ -59,7 +59,7 @@ "eslint": "^9.9.1", "jest": "29.7.0", "jest-environment-jsdom": "^29.7.0", - "jest-preset-angular": "^14.2.2", + "jest-preset-angular": "^14.2.4", "jest-websocket-mock": "^2.5.0", "patch-package": "^8.0.0", "ts-node": "~10.9.1", @@ -6567,9 +6567,9 @@ } }, "node_modules/@types/jest": { - "version": "29.5.12", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", - "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", + "version": "29.5.13", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.13.tgz", + "integrity": "sha512-wd+MVEZCHt23V0/L642O5APvspWply/rGY5BcW4SUETo2UzPU3Z26qr8jC2qxpimI2jjx9h7+2cj2FwIr01bXg==", "dev": true, "dependencies": { "expect": "^29.0.0", @@ -12357,9 +12357,9 @@ } }, "node_modules/jest-preset-angular": { - "version": "14.2.2", - "resolved": "https://registry.npmjs.org/jest-preset-angular/-/jest-preset-angular-14.2.2.tgz", - "integrity": "sha512-vdpv1DV4yJMMoBRbTdwRA16Es0UU+8CuOHsV2vfUL0LOy69anvq2RUawh07EyTWSVxko838jOC146jlnCkWOOw==", + "version": "14.2.4", + "resolved": "https://registry.npmjs.org/jest-preset-angular/-/jest-preset-angular-14.2.4.tgz", + "integrity": "sha512-xyhkaiBdn3keBgxxkcbqZu/my3ADU9NcDrz6DaMuGRaxz/bf6ZC1qxZ1eQuz5V1WuA3/rD64VA3Kke8P6E9qNg==", "dev": true, "dependencies": { "bs-logger": "^0.2.6", diff --git a/src-ui/package.json b/src-ui/package.json index be2e37cc8..5470daa04 100644 --- a/src-ui/package.json +++ b/src-ui/package.json @@ -53,7 +53,7 @@ "@angular/compiler-cli": "~18.2.2", "@codecov/webpack-plugin": "^1.0.1", "@playwright/test": "^1.47.2", - "@types/jest": "^29.5.12", + "@types/jest": "^29.5.13", "@types/node": "^22.0.2", "@typescript-eslint/eslint-plugin": "^8.3.0", "@typescript-eslint/parser": "^8.3.0", @@ -61,7 +61,7 @@ "eslint": "^9.9.1", "jest": "29.7.0", "jest-environment-jsdom": "^29.7.0", - "jest-preset-angular": "^14.2.2", + "jest-preset-angular": "^14.2.4", "jest-websocket-mock": "^2.5.0", "patch-package": "^8.0.0", "ts-node": "~10.9.1", From 77bebc861dc1b0d2668cad63bb19a8bbf0d0d5f8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 21:20:53 +0000 Subject: [PATCH 04/11] Chore(deps-dev): Bump the frontend-eslint-dependencies group (#7827) Bumps the frontend-eslint-dependencies group in /src-ui with 4 updates: [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin), [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser), [@typescript-eslint/utils](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/utils) and [eslint](https://github.com/eslint/eslint). Updates `@typescript-eslint/eslint-plugin` from 8.3.0 to 8.8.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.8.0/packages/eslint-plugin) Updates `@typescript-eslint/parser` from 8.3.0 to 8.8.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.8.0/packages/parser) Updates `@typescript-eslint/utils` from 8.3.0 to 8.8.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/utils/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.8.0/packages/utils) Updates `eslint` from 9.9.1 to 9.11.1 - [Release notes](https://github.com/eslint/eslint/releases) - [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md) - [Commits](https://github.com/eslint/eslint/compare/v9.9.1...v9.11.1) --- updated-dependencies: - dependency-name: "@typescript-eslint/eslint-plugin" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: frontend-eslint-dependencies - dependency-name: "@typescript-eslint/parser" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: frontend-eslint-dependencies - dependency-name: "@typescript-eslint/utils" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: frontend-eslint-dependencies - dependency-name: eslint dependency-type: direct:development update-type: version-update:semver-minor dependency-group: frontend-eslint-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src-ui/package-lock.json | 130 +++++++++++++++++++++++---------------- src-ui/package.json | 6 +- 2 files changed, 80 insertions(+), 56 deletions(-) diff --git a/src-ui/package-lock.json b/src-ui/package-lock.json index 96843936c..686f338ab 100644 --- a/src-ui/package-lock.json +++ b/src-ui/package-lock.json @@ -53,10 +53,10 @@ "@playwright/test": "^1.47.2", "@types/jest": "^29.5.13", "@types/node": "^22.0.2", - "@typescript-eslint/eslint-plugin": "^8.3.0", - "@typescript-eslint/parser": "^8.3.0", + "@typescript-eslint/eslint-plugin": "^8.8.0", + "@typescript-eslint/parser": "^8.8.0", "@typescript-eslint/utils": "^8.0.0", - "eslint": "^9.9.1", + "eslint": "^9.11.1", "jest": "29.7.0", "jest-environment-jsdom": "^29.7.0", "jest-preset-angular": "^14.2.4", @@ -3894,6 +3894,15 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/core": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.6.0.tgz", + "integrity": "sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/eslintrc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", @@ -3970,9 +3979,9 @@ "dev": true }, "node_modules/@eslint/js": { - "version": "9.9.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.9.1.tgz", - "integrity": "sha512-xIDQRsfg5hNBqHz04H1R3scSVwmI+KUbqjsQKHKQ1DAUSaUjYPReZZmS/5PNiKu1fUvzDd6H7DEDKACSEhu+TQ==", + "version": "9.11.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.11.1.tgz", + "integrity": "sha512-/qu+TWz8WwPWc7/HcIJKi+c+MOm46GdVaSlTTQcaqaL53+GsoA6MxWp5PtTx48qbSP7ylM1Kn7nhvkugfJvRSA==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3987,6 +3996,18 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz", + "integrity": "sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==", + "dev": true, + "dependencies": { + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@fastify/busboy": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", @@ -6726,16 +6747,16 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.3.0.tgz", - "integrity": "sha512-FLAIn63G5KH+adZosDYiutqkOkYEx0nvcwNNfJAf+c7Ae/H35qWwTYvPZUKFj5AS+WfHG/WJJfWnDnyNUlp8UA==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.0.tgz", + "integrity": "sha512-wORFWjU30B2WJ/aXBfOm1LX9v9nyt9D3jsSOxC3cCaTQGCW5k4jNpmjFv3U7p/7s4yvdjHzwtv2Sd2dOyhjS0A==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.3.0", - "@typescript-eslint/type-utils": "8.3.0", - "@typescript-eslint/utils": "8.3.0", - "@typescript-eslint/visitor-keys": "8.3.0", + "@typescript-eslint/scope-manager": "8.8.0", + "@typescript-eslint/type-utils": "8.8.0", + "@typescript-eslint/utils": "8.8.0", + "@typescript-eslint/visitor-keys": "8.8.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -6759,15 +6780,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.3.0.tgz", - "integrity": "sha512-h53RhVyLu6AtpUzVCYLPhZGL5jzTD9fZL+SYf/+hYOx2bDkyQXztXSc4tbvKYHzfMXExMLiL9CWqJmVz6+78IQ==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.8.0.tgz", + "integrity": "sha512-uEFUsgR+tl8GmzmLjRqz+VrDv4eoaMqMXW7ruXfgThaAShO9JTciKpEsB+TvnfFfbg5IpujgMXVV36gOJRLtZg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.3.0", - "@typescript-eslint/types": "8.3.0", - "@typescript-eslint/typescript-estree": "8.3.0", - "@typescript-eslint/visitor-keys": "8.3.0", + "@typescript-eslint/scope-manager": "8.8.0", + "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/typescript-estree": "8.8.0", + "@typescript-eslint/visitor-keys": "8.8.0", "debug": "^4.3.4" }, "engines": { @@ -6787,13 +6808,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.3.0.tgz", - "integrity": "sha512-mz2X8WcN2nVu5Hodku+IR8GgCOl4C0G/Z1ruaWN4dgec64kDBabuXyPAr+/RgJtumv8EEkqIzf3X2U5DUKB2eg==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.0.tgz", + "integrity": "sha512-EL8eaGC6gx3jDd8GwEFEV091210U97J0jeEHrAYvIYosmEGet4wJ+g0SYmLu+oRiAwbSA5AVrt6DxLHfdd+bUg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.3.0", - "@typescript-eslint/visitor-keys": "8.3.0" + "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/visitor-keys": "8.8.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6804,13 +6825,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.3.0.tgz", - "integrity": "sha512-wrV6qh//nLbfXZQoj32EXKmwHf4b7L+xXLrP3FZ0GOUU72gSvLjeWUl5J5Ue5IwRxIV1TfF73j/eaBapxx99Lg==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.8.0.tgz", + "integrity": "sha512-IKwJSS7bCqyCeG4NVGxnOP6lLT9Okc3Zj8hLO96bpMkJab+10HIfJbMouLrlpyOr3yrQ1cA413YPFiGd1mW9/Q==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "8.3.0", - "@typescript-eslint/utils": "8.3.0", + "@typescript-eslint/typescript-estree": "8.8.0", + "@typescript-eslint/utils": "8.8.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -6828,9 +6849,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.3.0.tgz", - "integrity": "sha512-y6sSEeK+facMaAyixM36dQ5NVXTnKWunfD1Ft4xraYqxP0lC0POJmIaL/mw72CUMqjY9qfyVfXafMeaUj0noWw==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.0.tgz", + "integrity": "sha512-QJwc50hRCgBd/k12sTykOJbESe1RrzmX6COk8Y525C9l7oweZ+1lw9JiU56im7Amm8swlz00DRIlxMYLizr2Vw==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6841,13 +6862,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.3.0.tgz", - "integrity": "sha512-Mq7FTHl0R36EmWlCJWojIC1qn/ZWo2YiWYc1XVtasJ7FIgjo0MVv9rZWXEE7IK2CGrtwe1dVOxWwqXUdNgfRCA==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.0.tgz", + "integrity": "sha512-ZaMJwc/0ckLz5DaAZ+pNLmHv8AMVGtfWxZe/x2JVEkD5LnmhWiQMMcYT7IY7gkdJuzJ9P14fRy28lUrlDSWYdw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.3.0", - "@typescript-eslint/visitor-keys": "8.3.0", + "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/visitor-keys": "8.8.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -6893,15 +6914,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.3.0.tgz", - "integrity": "sha512-F77WwqxIi/qGkIGOGXNBLV7nykwfjLsdauRB/DOFPdv6LTF3BHHkBpq81/b5iMPSF055oO2BiivDJV4ChvNtXA==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.8.0.tgz", + "integrity": "sha512-QE2MgfOTem00qrlPgyByaCHay9yb1+9BjnMFnSFkUKQfu7adBXDTnCAivURnuPPAG/qiB+kzKkZKmKfaMT0zVg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.3.0", - "@typescript-eslint/types": "8.3.0", - "@typescript-eslint/typescript-estree": "8.3.0" + "@typescript-eslint/scope-manager": "8.8.0", + "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/typescript-estree": "8.8.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6915,12 +6936,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.3.0.tgz", - "integrity": "sha512-RmZwrTbQ9QveF15m/Cl28n0LXD6ea2CjkhH5rQ55ewz3H24w+AMCJHPVYaZ8/0HoG8Z3cLLFFycRXxeO2tz9FA==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.0.tgz", + "integrity": "sha512-8mq51Lx6Hpmd7HnA2fcHQo3YgfX1qbccxQOgZcb4tvasu//zXRaA1j5ZRFeCw/VRAdFi4mRM9DnZw0Nu0Q2d1g==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.3.0", + "@typescript-eslint/types": "8.8.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -9599,19 +9620,23 @@ } }, "node_modules/eslint": { - "version": "9.9.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.9.1.tgz", - "integrity": "sha512-dHvhrbfr4xFQ9/dq+jcVneZMyRYLjggWjk6RVsIiHsP8Rz6yZ8LvZ//iU4TrZF+SXWG+JkNF2OyiZRvzgRDqMg==", + "version": "9.11.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.11.1.tgz", + "integrity": "sha512-MobhYKIoAO1s1e4VUrgx1l1Sk2JBR/Gqjjgw8+mfgoLE2xwsHur4gdfTxyTgShrhvdVFTaJSgMiQBl1jv/AWxg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.11.0", "@eslint/config-array": "^0.18.0", + "@eslint/core": "^0.6.0", "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.9.1", + "@eslint/js": "9.11.1", + "@eslint/plugin-kit": "^0.2.0", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.3.0", "@nodelib/fs.walk": "^1.2.8", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -9631,7 +9656,6 @@ "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", diff --git a/src-ui/package.json b/src-ui/package.json index 5470daa04..ce81fd266 100644 --- a/src-ui/package.json +++ b/src-ui/package.json @@ -55,10 +55,10 @@ "@playwright/test": "^1.47.2", "@types/jest": "^29.5.13", "@types/node": "^22.0.2", - "@typescript-eslint/eslint-plugin": "^8.3.0", - "@typescript-eslint/parser": "^8.3.0", + "@typescript-eslint/eslint-plugin": "^8.8.0", + "@typescript-eslint/parser": "^8.8.0", "@typescript-eslint/utils": "^8.0.0", - "eslint": "^9.9.1", + "eslint": "^9.11.1", "jest": "29.7.0", "jest-environment-jsdom": "^29.7.0", "jest-preset-angular": "^14.2.4", From 59f726b2a2415be4ef1d2e53476d25ab586dc8e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 21:29:28 +0000 Subject: [PATCH 05/11] Chore(deps-dev): Bump @types/node from 22.5.2 to 22.7.4 in /src-ui (#7829) Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.5.2 to 22.7.4. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src-ui/package-lock.json | 8 ++++---- src-ui/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src-ui/package-lock.json b/src-ui/package-lock.json index 686f338ab..74806061a 100644 --- a/src-ui/package-lock.json +++ b/src-ui/package-lock.json @@ -52,7 +52,7 @@ "@codecov/webpack-plugin": "^1.0.1", "@playwright/test": "^1.47.2", "@types/jest": "^29.5.13", - "@types/node": "^22.0.2", + "@types/node": "^22.7.4", "@typescript-eslint/eslint-plugin": "^8.8.0", "@typescript-eslint/parser": "^8.8.0", "@typescript-eslint/utils": "^8.0.0", @@ -6630,9 +6630,9 @@ } }, "node_modules/@types/node": { - "version": "22.5.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.2.tgz", - "integrity": "sha512-acJsPTEqYqulZS/Yp/S3GgeE6GZ0qYODUR8aVr/DkhHQ8l9nd4j5x1/ZJy9/gHrRlFMqkO6i0I3E27Alu4jjPg==", + "version": "22.7.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz", + "integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==", "dev": true, "dependencies": { "undici-types": "~6.19.2" diff --git a/src-ui/package.json b/src-ui/package.json index ce81fd266..488afd273 100644 --- a/src-ui/package.json +++ b/src-ui/package.json @@ -54,7 +54,7 @@ "@codecov/webpack-plugin": "^1.0.1", "@playwright/test": "^1.47.2", "@types/jest": "^29.5.13", - "@types/node": "^22.0.2", + "@types/node": "^22.7.4", "@typescript-eslint/eslint-plugin": "^8.8.0", "@typescript-eslint/parser": "^8.8.0", "@typescript-eslint/utils": "^8.0.0", From 374a1ceb05ec4def098572355edadcbecdbb5b11 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 21:38:03 +0000 Subject: [PATCH 06/11] Chore(deps-dev): Bump @codecov/webpack-plugin in /src-ui (#7830) Bumps @codecov/webpack-plugin from 1.0.1 to 1.2.0. --- updated-dependencies: - dependency-name: "@codecov/webpack-plugin" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src-ui/package-lock.json | 79 +++++++++++----------------------------- src-ui/package.json | 2 +- 2 files changed, 23 insertions(+), 58 deletions(-) diff --git a/src-ui/package-lock.json b/src-ui/package-lock.json index 74806061a..4aa06ddf0 100644 --- a/src-ui/package-lock.json +++ b/src-ui/package-lock.json @@ -49,7 +49,7 @@ "@angular-eslint/template-parser": "18.3.1", "@angular/cli": "~18.2.6", "@angular/compiler-cli": "~18.2.2", - "@codecov/webpack-plugin": "^1.0.1", + "@codecov/webpack-plugin": "^1.2.0", "@playwright/test": "^1.47.2", "@types/jest": "^29.5.13", "@types/node": "^22.7.4", @@ -80,7 +80,6 @@ "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.1.tgz", "integrity": "sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g==", "dev": true, - "license": "MIT", "dependencies": { "@actions/http-client": "^2.0.1", "uuid": "^8.3.2" @@ -91,7 +90,6 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "dev": true, - "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } @@ -101,7 +99,6 @@ "resolved": "https://registry.npmjs.org/@actions/github/-/github-6.0.0.tgz", "integrity": "sha512-alScpSVnYmjNEXboZjarjukQEzgCRmjMv6Xj47fsdnqGS73bjJNDpiiXmp8jr0UZLdUB6d9jW63IcmddUP+l0g==", "dev": true, - "license": "MIT", "dependencies": { "@actions/http-client": "^2.2.0", "@octokit/core": "^5.0.1", @@ -114,7 +111,6 @@ "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz", "integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==", "dev": true, - "license": "MIT", "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" @@ -3323,11 +3319,10 @@ "dev": true }, "node_modules/@codecov/bundler-plugin-core": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@codecov/bundler-plugin-core/-/bundler-plugin-core-1.0.1.tgz", - "integrity": "sha512-Uo150Qb2s/mMqqfZMdh6rC1+Cp+bCij5DAB6LqWNI6J9dGbimeNvpU1+jdQ6vlMJOiz5w5jAOhtgZvFNrc8jUw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@codecov/bundler-plugin-core/-/bundler-plugin-core-1.2.0.tgz", + "integrity": "sha512-ublUP5V0tW6oDnaJ1UBWvEmVAkvMmPNEwWkpF+WwJSCBWNLvWrkSwG84S3Gt5Xbnh17xEyAxXBmNzF+mXVXBgw==", "dev": true, - "license": "MIT", "dependencies": { "@actions/core": "^1.10.1", "@actions/github": "^6.0.0", @@ -3345,7 +3340,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -3361,7 +3355,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -3378,7 +3371,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -3390,15 +3382,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@codecov/bundler-plugin-core/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -3408,7 +3398,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -3417,13 +3406,12 @@ } }, "node_modules/@codecov/webpack-plugin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@codecov/webpack-plugin/-/webpack-plugin-1.0.1.tgz", - "integrity": "sha512-e6VpcP3adF5ig2OXjb/mrdZ4o8gluKc/IvTAAZfhjX4CWIsnuyRQqFobKyC9nKMRWpIGCsuxdmamyQSrfwXIUw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@codecov/webpack-plugin/-/webpack-plugin-1.2.0.tgz", + "integrity": "sha512-yawRyKgC8tXj/C/UoTJ+kRMePfhkYJtJey+xmywRqmVmvbxQGnYFkg+Dzix/HxKt4iGpSwKT4p8Glz2MNQ7gTQ==", "dev": true, - "license": "MIT", "dependencies": { - "@codecov/bundler-plugin-core": "^1.0.1", + "@codecov/bundler-plugin-core": "^1.2.0", "unplugin": "^1.10.1" }, "engines": { @@ -4013,7 +4001,6 @@ "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", "dev": true, - "license": "MIT", "engines": { "node": ">=14" } @@ -5808,7 +5795,6 @@ "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", "dev": true, - "license": "MIT", "engines": { "node": ">= 18" } @@ -5818,7 +5804,6 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.0.tgz", "integrity": "sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==", "dev": true, - "license": "MIT", "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -5837,7 +5822,6 @@ "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.5.tgz", "integrity": "sha512-ekqR4/+PCLkEBF6qgj8WqJfvDq65RH85OAgrtnVp1mSxaXF03u2xW/hUdweGS5654IlC0wkNYC18Z50tSYTAFw==", "dev": true, - "license": "MIT", "dependencies": { "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" @@ -5851,7 +5835,6 @@ "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.0.tgz", "integrity": "sha512-r+oZUH7aMFui1ypZnAvZmn0KSqAUgE1/tUXIWaqUCa1758ts/Jio84GZuzsvUkme98kv0WFY8//n0J1Z+vsIsQ==", "dev": true, - "license": "MIT", "dependencies": { "@octokit/request": "^8.3.0", "@octokit/types": "^13.0.0", @@ -5865,15 +5848,13 @@ "version": "22.2.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@octokit/plugin-paginate-rest": { "version": "9.2.1", "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.1.tgz", "integrity": "sha512-wfGhE/TAkXZRLjksFXuDZdmGnJQHvtU/joFQdweXUgzo1XwvBCD4o4+75NtFfjfLK5IwLf9vHTfSiU3sLRYpRw==", "dev": true, - "license": "MIT", "dependencies": { "@octokit/types": "^12.6.0" }, @@ -5888,15 +5869,13 @@ "version": "20.0.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { "version": "12.6.0", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", "dev": true, - "license": "MIT", "dependencies": { "@octokit/openapi-types": "^20.0.0" } @@ -5906,7 +5885,6 @@ "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.1.tgz", "integrity": "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==", "dev": true, - "license": "MIT", "dependencies": { "@octokit/types": "^12.6.0" }, @@ -5921,15 +5899,13 @@ "version": "20.0.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { "version": "12.6.0", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", "dev": true, - "license": "MIT", "dependencies": { "@octokit/openapi-types": "^20.0.0" } @@ -5939,7 +5915,6 @@ "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.0.tgz", "integrity": "sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw==", "dev": true, - "license": "MIT", "dependencies": { "@octokit/endpoint": "^9.0.1", "@octokit/request-error": "^5.1.0", @@ -5955,7 +5930,6 @@ "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.0.tgz", "integrity": "sha512-GETXfE05J0+7H2STzekpKObFe765O5dlAKUTLNGeH+x47z7JjXHfsHKo5z21D/o/IOZTUEI6nyWyR+bZVP/n5Q==", "dev": true, - "license": "MIT", "dependencies": { "@octokit/types": "^13.1.0", "deprecation": "^2.0.0", @@ -5966,11 +5940,10 @@ } }, "node_modules/@octokit/types": { - "version": "13.5.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.5.0.tgz", - "integrity": "sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==", + "version": "13.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.0.tgz", + "integrity": "sha512-CrooV/vKCXqwLa+osmHLIMUb87brpgUqlqkPGc6iE2wCkUvTrHiXFMhAKoDDaAAYJrtKtrFTgSQTg5nObBEaew==", "dev": true, - "license": "MIT", "dependencies": { "@octokit/openapi-types": "^22.2.0" } @@ -7754,8 +7727,7 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", - "dev": true, - "license": "Apache-2.0" + "dev": true }, "node_modules/big.js": { "version": "5.2.2", @@ -9196,8 +9168,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/dequal": { "version": "2.0.3", @@ -18164,7 +18135,6 @@ "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.6.11 <=0.7.0 || >=0.7.3" } @@ -18238,7 +18208,6 @@ "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", "dev": true, - "license": "MIT", "dependencies": { "@fastify/busboy": "^2.0.0" }, @@ -18332,8 +18301,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/universalify": { "version": "2.0.1", @@ -18354,11 +18322,10 @@ } }, "node_modules/unplugin": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.14.0.tgz", - "integrity": "sha512-cfkZeALGyW7tKYjZbi0G+pn0XnUFa0QvLIeLJEUUlnU0R8YYsBQnt5+h9Eu1B7AB7KETld+UBFI5lOeBL+msoQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.14.1.tgz", + "integrity": "sha512-lBlHbfSFPToDYp9pjXlUEFVxYLaue9f9T1HC+4OHlmj+HnMDdz9oZY+erXfoCe/5V/7gKUSY2jpXPb9S7f0f/w==", "dev": true, - "license": "MIT", "dependencies": { "acorn": "^8.12.1", "webpack-virtual-modules": "^0.6.2" @@ -18923,8 +18890,7 @@ "version": "0.6.2", "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/webpack/node_modules/ajv": { "version": "6.12.6", @@ -19335,7 +19301,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", "dev": true, - "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/src-ui/package.json b/src-ui/package.json index 488afd273..f46f06f64 100644 --- a/src-ui/package.json +++ b/src-ui/package.json @@ -51,7 +51,7 @@ "@angular-eslint/template-parser": "18.3.1", "@angular/cli": "~18.2.6", "@angular/compiler-cli": "~18.2.2", - "@codecov/webpack-plugin": "^1.0.1", + "@codecov/webpack-plugin": "^1.2.0", "@playwright/test": "^1.47.2", "@types/jest": "^29.5.13", "@types/node": "^22.7.4", From 74001bd0da663b0dec7b151447607d8eb46af572 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 1 Oct 2024 16:13:45 -0700 Subject: [PATCH 07/11] Fix: wrap table header columns in row (#7832) --- src-ui/messages.xlf | 58 ++--- .../document-list.component.html | 202 +++++++++--------- .../document-list.component.spec.ts | 2 +- 3 files changed, 132 insertions(+), 130 deletions(-) diff --git a/src-ui/messages.xlf b/src-ui/messages.xlf index f5d270376..9b588ac6b 100644 --- a/src-ui/messages.xlf +++ b/src-ui/messages.xlf @@ -1042,7 +1042,7 @@ src/app/components/document-list/document-list.component.html - 211 + 212 src/app/data/document.ts @@ -1950,7 +1950,7 @@ src/app/components/document-list/document-list.component.html - 238 + 239 src/app/data/document.ts @@ -2752,7 +2752,7 @@ src/app/components/document-list/document-list.component.html - 193 + 194 src/app/components/document-list/filter-editor/filter-editor.component.html @@ -3341,7 +3341,7 @@ src/app/components/document-list/document-list.component.html - 247 + 248 src/app/data/document.ts @@ -5510,7 +5510,7 @@ src/app/components/document-list/document-list.component.html - 286 + 288 @@ -5525,7 +5525,7 @@ src/app/components/document-list/document-list.component.html - 321 + 323 @@ -5540,7 +5540,7 @@ src/app/components/document-list/document-list.component.html - 328 + 330 @@ -5830,7 +5830,7 @@ src/app/components/document-list/document-list.component.html - 190 + 191 src/app/components/document-list/filter-editor/filter-editor.component.ts @@ -5871,7 +5871,7 @@ src/app/components/document-list/document-list.component.html - 180 + 181 src/app/components/document-list/filter-editor/filter-editor.component.html @@ -5898,7 +5898,7 @@ src/app/components/document-list/document-list.component.html - 220 + 221 src/app/components/document-list/filter-editor/filter-editor.component.html @@ -5925,7 +5925,7 @@ src/app/components/document-list/document-list.component.html - 229 + 230 src/app/components/document-list/filter-editor/filter-editor.component.html @@ -6726,7 +6726,7 @@ src/app/components/document-list/document-list.component.html - 297 + 299 @@ -6939,14 +6939,14 @@ Sort by ASN src/app/components/document-list/document-list.component.html - 167 + 168 ASN src/app/components/document-list/document-list.component.html - 171 + 172 src/app/components/document-list/filter-editor/filter-editor.component.ts @@ -6965,28 +6965,28 @@ Sort by correspondent src/app/components/document-list/document-list.component.html - 176 + 177 Sort by title src/app/components/document-list/document-list.component.html - 185 + 186 Sort by owner src/app/components/document-list/document-list.component.html - 198 + 199 Owner src/app/components/document-list/document-list.component.html - 202 + 203 src/app/data/document.ts @@ -7001,49 +7001,49 @@ Sort by notes src/app/components/document-list/document-list.component.html - 207 + 208 Sort by document type src/app/components/document-list/document-list.component.html - 216 + 217 Sort by storage path src/app/components/document-list/document-list.component.html - 225 + 226 Sort by created date src/app/components/document-list/document-list.component.html - 234 + 235 Sort by added date src/app/components/document-list/document-list.component.html - 243 + 244 Sort by number of pages src/app/components/document-list/document-list.component.html - 252 + 253 Pages src/app/components/document-list/document-list.component.html - 256 + 257 src/app/data/document.ts @@ -7062,21 +7062,21 @@ Shared src/app/components/document-list/document-list.component.html - 259,261 + 260,262 Edit document src/app/components/document-list/document-list.component.html - 293 + 295 Yes src/app/components/document-list/document-list.component.html - 349 + 351 src/app/pipes/yes-no.pipe.ts @@ -7087,7 +7087,7 @@ No src/app/components/document-list/document-list.component.html - 349 + 351 src/app/pipes/yes-no.pipe.ts diff --git a/src-ui/src/app/components/document-list/document-list.component.html b/src-ui/src/app/components/document-list/document-list.component.html index 8ca8e111d..e70f4c710 100644 --- a/src-ui/src/app/components/document-list/document-list.component.html +++ b/src-ui/src/app/components/document-list/document-list.component.html @@ -160,111 +160,113 @@
- - @if (activeDisplayFields.includes(DisplayField.ASN)) { - - } - @if (activeDisplayFields.includes(DisplayField.CORRESPONDENT) && permissionService.currentUserCan(PermissionAction.View, PermissionType.Correspondent)) { - - } - @if (activeDisplayFields.includes(DisplayField.TITLE)) { - - } - @if (activeDisplayFields.includes(DisplayField.TAGS) && !activeDisplayFields.includes(DisplayField.TITLE)) { - - } - @if (activeDisplayFields.includes(DisplayField.OWNER) && permissionService.currentUserCan(PermissionAction.View, PermissionType.User)) { - - } - @if (activeDisplayFields.includes(DisplayField.NOTES) && notesEnabled) { - - } - @if (activeDisplayFields.includes(DisplayField.DOCUMENT_TYPE) && permissionService.currentUserCan(PermissionAction.View, PermissionType.DocumentType)) { - - } - @if (activeDisplayFields.includes(DisplayField.STORAGE_PATH) && permissionService.currentUserCan(PermissionAction.View, PermissionType.StoragePath)) { - - } - @if (activeDisplayFields.includes(DisplayField.CREATED)) { - - } - @if (activeDisplayFields.includes(DisplayField.ADDED)) { - - } - @if (activeDisplayFields.includes(DisplayField.PAGE_COUNT)) { + + + @if (activeDisplayFields.includes(DisplayField.ASN)) { + i18n>ASN } - @if (activeDisplayFields.includes(DisplayField.SHARED)) { - - } - @for (field of activeDisplayCustomFields; track field) { - - } + @if (activeDisplayFields.includes(DisplayField.CORRESPONDENT) && permissionService.currentUserCan(PermissionAction.View, PermissionType.Correspondent)) { + + } + @if (activeDisplayFields.includes(DisplayField.TITLE)) { + + } + @if (activeDisplayFields.includes(DisplayField.TAGS) && !activeDisplayFields.includes(DisplayField.TITLE)) { + + } + @if (activeDisplayFields.includes(DisplayField.OWNER) && permissionService.currentUserCan(PermissionAction.View, PermissionType.User)) { + + } + @if (activeDisplayFields.includes(DisplayField.NOTES) && notesEnabled) { + + } + @if (activeDisplayFields.includes(DisplayField.DOCUMENT_TYPE) && permissionService.currentUserCan(PermissionAction.View, PermissionType.DocumentType)) { + + } + @if (activeDisplayFields.includes(DisplayField.STORAGE_PATH) && permissionService.currentUserCan(PermissionAction.View, PermissionType.StoragePath)) { + + } + @if (activeDisplayFields.includes(DisplayField.CREATED)) { + + } + @if (activeDisplayFields.includes(DisplayField.ADDED)) { + + } + @if (activeDisplayFields.includes(DisplayField.PAGE_COUNT)) { + + } + @if (activeDisplayFields.includes(DisplayField.SHARED)) { + + } + @for (field of activeDisplayCustomFields; track field) { + + } + @for (d of list.documents; track trackByDocumentId($index, d)) { diff --git a/src-ui/src/app/components/document-list/document-list.component.spec.ts b/src-ui/src/app/components/document-list/document-list.component.spec.ts index ad85652b8..0a8faa4d3 100644 --- a/src-ui/src/app/components/document-list/document-list.component.spec.ts +++ b/src-ui/src/app/components/document-list/document-list.component.spec.ts @@ -302,7 +302,7 @@ describe('DocumentListComponent', () => { displayModeButtons[0].triggerEventHandler('change') fixture.detectChanges() expect(component.list.displayMode).toEqual('table') - expect(fixture.debugElement.queryAll(By.css('tr'))).toHaveLength(3) + expect(fixture.debugElement.queryAll(By.css('tr'))).toHaveLength(4) displayModeButtons[1].nativeElement.checked = true displayModeButtons[1].triggerEventHandler('change') From 2e3637d7127a4f60233c77daccb41517f8d44277 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 1 Oct 2024 21:37:35 -0700 Subject: [PATCH 08/11] Update .codecov.yml --- .codecov.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index 331e3a283..5fa8e1639 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -14,8 +14,9 @@ flag_management: # codecov will only comment if coverage changes comment: require_changes: true + # https://docs.codecov.com/docs/javascript-bundle-analysis require_bundle_changes: true - bundle_change_threshold: "1Kb" + bundle_change_threshold: "50Kb" coverage: status: project: @@ -24,7 +25,12 @@ coverage: threshold: 1% patch: default: - # For the changed lines only, target 75% covered, but - # allow as low as 50% - target: 75% + # For the changed lines only, target 100% covered, but + # allow as low as 75% + target: 100% threshold: 25% +# https://docs.codecov.com/docs/javascript-bundle-analysis +bundle_analysis: + # Fail if the bundle size increases by more than 1MB + warning_threshold: "1MB" + status: true From f8d79b012fe9c49dd378d16065dcd28b34cc3967 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 2 Oct 2024 17:15:42 -0700 Subject: [PATCH 09/11] Feature: custom fields queries (#7761) --- docs/api.md | 32 +- paperless.conf.example | 1 - src-ui/messages.xlf | 289 ++++++++++++---- src-ui/src/app/app.module.ts | 8 + ...ustom-fields-query-dropdown.component.html | 163 +++++++++ ...ustom-fields-query-dropdown.component.scss | 43 +++ ...om-fields-query-dropdown.component.spec.ts | 320 ++++++++++++++++++ .../custom-fields-query-dropdown.component.ts | 294 ++++++++++++++++ .../document-link.component.html | 101 +++--- .../document-link/document-link.component.ts | 6 + .../document-list.component.html | 6 +- .../document-list/document-list.component.ts | 4 - .../filter-editor.component.html | 11 +- .../filter-editor.component.spec.ts | 230 +++++-------- .../filter-editor/filter-editor.component.ts | 123 +++---- src-ui/src/app/data/custom-field-query.ts | 127 +++++++ src-ui/src/app/data/filter-rule-type.ts | 8 + .../utils/custom-field-query-element.spec.ts | 245 ++++++++++++++ .../app/utils/custom-field-query-element.ts | 210 ++++++++++++ src-ui/src/app/utils/query-params.spec.ts | 60 +++- src-ui/src/app/utils/query-params.ts | 59 +++- src/documents/filters.py | 88 ++--- ...instance_value_monetary_amount_and_more.py | 95 ++++++ src/documents/models.py | 25 ++ .../tests/test_api_filter_by_custom_fields.py | 164 ++------- src/paperless/settings.py | 17 - 26 files changed, 2130 insertions(+), 599 deletions(-) create mode 100644 src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html create mode 100644 src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.scss create mode 100644 src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.spec.ts create mode 100644 src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.ts create mode 100644 src-ui/src/app/data/custom-field-query.ts create mode 100644 src-ui/src/app/utils/custom-field-query-element.spec.ts create mode 100644 src-ui/src/app/utils/custom-field-query-element.ts create mode 100644 src/documents/migrations/1054_customfieldinstance_value_monetary_amount_and_more.py diff --git a/docs/api.md b/docs/api.md index bf9e88659..e5da43a5c 100644 --- a/docs/api.md +++ b/docs/api.md @@ -278,39 +278,39 @@ attribute with various information about the search results: ### Filtering by custom fields You can filter documents by their custom field values by specifying the -`custom_field_lookup` query parameter. Here are some recipes for common +`custom_field_query` query parameter. Here are some recipes for common use cases: 1. Documents with a custom field "due" (date) between Aug 1, 2024 and Sept 1, 2024 (inclusive): - `?custom_field_lookup=["due", "range", ["2024-08-01", "2024-09-01"]]` + `?custom_field_query=["due", "range", ["2024-08-01", "2024-09-01"]]` 2. Documents with a custom field "customer" (text) that equals "bob" (case sensitive): - `?custom_field_lookup=["customer", "exact", "bob"]` + `?custom_field_query=["customer", "exact", "bob"]` 3. Documents with a custom field "answered" (boolean) set to `true`: - `?custom_field_lookup=["answered", "exact", true]` + `?custom_field_query=["answered", "exact", true]` 4. Documents with a custom field "favorite animal" (select) set to either "cat" or "dog": - `?custom_field_lookup=["favorite animal", "in", ["cat", "dog"]]` + `?custom_field_query=["favorite animal", "in", ["cat", "dog"]]` 5. Documents with a custom field "address" (text) that is empty: - `?custom_field_lookup=["OR", ["address", "isnull", true], ["address", "exact", ""]]` + `?custom_field_query=["OR", ["address", "isnull", true], ["address", "exact", ""]]` 6. Documents that don't have a field called "foo": - `?custom_field_lookup=["foo", "exists", false]` + `?custom_field_query=["foo", "exists", false]` 7. Documents that have document links "references" to both document 3 and 7: - `?custom_field_lookup=["references", "contains", [3, 7]]` + `?custom_field_query=["references", "contains", [3, 7]]` All field types support basic operations including `exact`, `in`, `isnull`, and `exists`. String, URL, and monetary fields support case-insensitive @@ -320,22 +320,6 @@ including `gt` (>), `gte` (>=), `lt` (<), `lte` (<=), and `range`. Lastly, document link fields support a `contains` operator that behaves like a "is superset of" check. -!!! warning - - It is possible to do case-insensitive exact match (i.e., `iexact`) and - case-sensitive substring match (i.e., `contains`, `startswith`, - `endswith`) for string, URL, and monetary fields, but - [they may not work as expected on some database backends](https://docs.djangoproject.com/en/5.1/ref/databases/#substring-matching-and-case-sensitivity). - - It is also possible to use regular expressions to match string, URL, and - monetary fields, but the syntax is database-dependent, and accepting - regular expressions from untrusted sources could make your instance - vulnerable to regular expression denial of service attacks. - - For these reasons the above expressions are disabled by default. - If you understand the implications, you may enable them by uncommenting - `PAPERLESS_CUSTOM_FIELD_LOOKUP_OPT_IN` in your configuration file. - ### `/api/search/autocomplete/` Get auto completions for a partial search term. diff --git a/paperless.conf.example b/paperless.conf.example index 5fabbf390..63ee7be22 100644 --- a/paperless.conf.example +++ b/paperless.conf.example @@ -81,7 +81,6 @@ #PAPERLESS_THUMBNAIL_FONT_NAME= #PAPERLESS_IGNORE_DATES= #PAPERLESS_ENABLE_UPDATE_CHECK= -#PAPERLESS_ALLOW_CUSTOM_FIELD_LOOKUP=iexact,contains,startswith,endswith,regex,iregex # Tika settings diff --git a/src-ui/messages.xlf b/src-ui/messages.xlf index 9b588ac6b..3fffe4f6e 100644 --- a/src-ui/messages.xlf +++ b/src-ui/messages.xlf @@ -698,7 +698,7 @@ src/app/components/common/input/document-link/document-link.component.html - 38 + 51 src/app/components/common/permissions-dialog/permissions-dialog.component.html @@ -1031,7 +1031,7 @@ src/app/components/document-list/filter-editor/filter-editor.component.ts - 143 + 152 @@ -1088,7 +1088,7 @@ src/app/components/document-list/filter-editor/filter-editor.component.html - 110 + 105 src/app/components/manage/mail/mail.component.html @@ -3300,6 +3300,102 @@ 63 + + True + + src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html + 40 + + + src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html + 73 + + + src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html + 79 + + + + False + + src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html + 41 + + + src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html + 74 + + + src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html + 80 + + + + Search docs... + + src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html + 96 + + + + Any + + src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html + 126 + + + src/app/components/common/filterable-dropdown/filterable-dropdown.component.html + 17 + + + + All + + src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html + 128 + + + src/app/components/common/filterable-dropdown/filterable-dropdown.component.html + 15 + + + src/app/components/common/permissions-filter-dropdown/permissions-filter-dropdown.component.html + 16 + + + src/app/components/common/permissions-select/permissions-select.component.html + 16 + + + src/app/components/common/permissions-select/permissions-select.component.html + 27 + + + src/app/components/document-list/bulk-editor/bulk-editor.component.html + 14 + + + + Not + + src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html + 131 + + + + Add query + + src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html + 150 + + + + Add expression + + src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html + 153 + + now @@ -4549,36 +4645,6 @@ 146 - - All - - src/app/components/common/filterable-dropdown/filterable-dropdown.component.html - 15 - - - src/app/components/common/permissions-filter-dropdown/permissions-filter-dropdown.component.html - 16 - - - src/app/components/common/permissions-select/permissions-select.component.html - 16 - - - src/app/components/common/permissions-select/permissions-select.component.html - 27 - - - src/app/components/document-list/bulk-editor/bulk-editor.component.html - 14 - - - - Any - - src/app/components/common/filterable-dropdown/filterable-dropdown.component.html - 17 - - Include @@ -4668,7 +4734,7 @@ src/app/components/common/input/document-link/document-link.component.html - 9 + 12 src/app/components/common/input/file/file.component.html @@ -4740,14 +4806,14 @@ Remove link src/app/components/common/input/document-link/document-link.component.html - 30 + 43 Open link src/app/components/common/input/document-link/document-link.component.html - 31 + 44 src/app/components/common/input/url/url.component.html @@ -4761,6 +4827,13 @@ 44 + + Search for documents + + src/app/components/common/input/document-link/document-link.component.ts + 53 + + Selected items @@ -5834,7 +5907,7 @@ src/app/components/document-list/filter-editor/filter-editor.component.ts - 131 + 140 src/app/data/document.ts @@ -6416,7 +6489,7 @@ src/app/components/document-list/filter-editor/filter-editor.component.ts - 139 + 148 @@ -6425,10 +6498,6 @@ src/app/components/document-list/bulk-editor/bulk-editor.component.html 83 - - src/app/components/document-list/filter-editor/filter-editor.component.html - 90 - Merge @@ -6925,7 +6994,7 @@ src/app/components/document-list/filter-editor/filter-editor.component.html - 116 + 111 @@ -6950,7 +7019,7 @@ src/app/components/document-list/filter-editor/filter-editor.component.ts - 136 + 145 src/app/data/document.ts @@ -7126,161 +7195,154 @@ Dates src/app/components/document-list/filter-editor/filter-editor.component.html - 100 + 95 Title & content src/app/components/document-list/filter-editor/filter-editor.component.ts - 134 + 143 More like src/app/components/document-list/filter-editor/filter-editor.component.ts - 149 + 158 equals src/app/components/document-list/filter-editor/filter-editor.component.ts - 155 + 164 is empty src/app/components/document-list/filter-editor/filter-editor.component.ts - 159 + 168 is not empty src/app/components/document-list/filter-editor/filter-editor.component.ts - 163 + 172 greater than src/app/components/document-list/filter-editor/filter-editor.component.ts - 167 + 176 less than src/app/components/document-list/filter-editor/filter-editor.component.ts - 171 + 180 Correspondent: src/app/components/document-list/filter-editor/filter-editor.component.ts - 191,193 + 200,202 Without correspondent src/app/components/document-list/filter-editor/filter-editor.component.ts - 195 + 204 Document type: src/app/components/document-list/filter-editor/filter-editor.component.ts - 201,203 + 210,212 Without document type src/app/components/document-list/filter-editor/filter-editor.component.ts - 205 + 214 Storage path: src/app/components/document-list/filter-editor/filter-editor.component.ts - 211,213 + 220,222 Without storage path src/app/components/document-list/filter-editor/filter-editor.component.ts - 215 + 224 Tag: src/app/components/document-list/filter-editor/filter-editor.component.ts - 219,221 + 228,230 Without any tag src/app/components/document-list/filter-editor/filter-editor.component.ts - 225 + 234 - - Custom fields: + + Custom fields query src/app/components/document-list/filter-editor/filter-editor.component.ts - 229,231 - - - - Without any custom field - - src/app/components/document-list/filter-editor/filter-editor.component.ts - 235 + 238 Title: src/app/components/document-list/filter-editor/filter-editor.component.ts - 239 + 241 ASN: src/app/components/document-list/filter-editor/filter-editor.component.ts - 242 + 244 Owner: src/app/components/document-list/filter-editor/filter-editor.component.ts - 245 + 247 Owner not in: src/app/components/document-list/filter-editor/filter-editor.component.ts - 248 + 250 Without an owner src/app/components/document-list/filter-editor/filter-editor.component.ts - 251 + 253 @@ -8007,6 +8069,83 @@ 9 + + Equal to + + src/app/data/custom-field-query.ts + 24 + + + + In + + src/app/data/custom-field-query.ts + 25 + + + + Is null + + src/app/data/custom-field-query.ts + 26 + + + + Exists + + src/app/data/custom-field-query.ts + 27 + + + + Contains + + src/app/data/custom-field-query.ts + 28 + + + + Contains (case-insensitive) + + src/app/data/custom-field-query.ts + 29 + + + + Greater than + + src/app/data/custom-field-query.ts + 30 + + + + Greater than or equal to + + src/app/data/custom-field-query.ts + 31 + + + + Less than + + src/app/data/custom-field-query.ts + 32 + + + + Less than or equal to + + src/app/data/custom-field-query.ts + 33 + + + + Range + + src/app/data/custom-field-query.ts + 34 + + Boolean diff --git a/src-ui/src/app/app.module.ts b/src-ui/src/app/app.module.ts index 005de5369..93c458ae0 100644 --- a/src-ui/src/app/app.module.ts +++ b/src-ui/src/app/app.module.ts @@ -108,6 +108,7 @@ import { FileDropComponent } from './components/file-drop/file-drop.component' import { CustomFieldsComponent } from './components/manage/custom-fields/custom-fields.component' import { CustomFieldEditDialogComponent } from './components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component' import { CustomFieldsDropdownComponent } from './components/common/custom-fields-dropdown/custom-fields-dropdown.component' +import { CustomFieldsQueryDropdownComponent } from './components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component' import { ProfileEditDialogComponent } from './components/common/profile-edit-dialog/profile-edit-dialog.component' import { PdfViewerModule } from 'ng2-pdf-viewer' import { DocumentLinkComponent } from './components/common/input/document-link/document-link.component' @@ -141,6 +142,7 @@ import { arrowRightShort, arrowUpRight, asterisk, + braces, bodyText, boxArrowUp, boxArrowUpRight, @@ -198,6 +200,7 @@ import { link, listTask, listUl, + nodePlus, pencil, people, peopleFill, @@ -227,6 +230,7 @@ import { uiRadios, upcScan, x, + xCircle, xLg, } from 'ngx-bootstrap-icons' @@ -242,6 +246,7 @@ const icons = { arrowRightShort, arrowUpRight, asterisk, + braces, bodyText, boxArrowUp, boxArrowUpRight, @@ -299,6 +304,7 @@ const icons = { link, listTask, listUl, + nodePlus, pencil, people, peopleFill, @@ -328,6 +334,7 @@ const icons = { uiRadios, upcScan, x, + xCircle, xLg, } @@ -485,6 +492,7 @@ function initializeApp(settings: SettingsService) { CustomFieldsComponent, CustomFieldEditDialogComponent, CustomFieldsDropdownComponent, + CustomFieldsQueryDropdownComponent, ProfileEditDialogComponent, DocumentLinkComponent, PreviewPopupComponent, diff --git a/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html b/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html new file mode 100644 index 000000000..9da2886f4 --- /dev/null +++ b/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.html @@ -0,0 +1,163 @@ +
+ +
+
+ @for (element of selectionModel.queries; track element.id; let i = $index) { +
+ @switch (element.type) { + @case (CustomFieldQueryComponentType.Atom) { + + } + @case (CustomFieldQueryComponentType.Expression) { + + } + } +
+ } +
+
+
+ + + @if (getCustomFieldByID(atom.field)?.data_type === CustomFieldDataType.Date) { + + + } @else if (getCustomFieldByID(atom.field)?.data_type === CustomFieldDataType.Float || getCustomFieldByID(atom.field)?.data_type === CustomFieldDataType.Integer) { + + } @else if (getCustomFieldByID(atom.field)?.data_type === CustomFieldDataType.Boolean) { + + } @else if (getCustomFieldByID(atom.field)?.data_type === CustomFieldDataType.Select) { + + } @else { + + } + + + +
+ + + @switch (atom.operator) { + @case (CustomFieldQueryOperator.Exists) { + + } + @case (CustomFieldQueryOperator.IsNull) { + + } + @case (CustomFieldQueryOperator.GreaterThanOrEqual) { + + } + @case (CustomFieldQueryOperator.LessThanOrEqual) { + + } + @case (CustomFieldQueryOperator.GreaterThan) { + + } + @case (CustomFieldQueryOperator.LessThan) { + + } + @case (CustomFieldQueryOperator.Contains) { + + } + @case (CustomFieldQueryOperator.In) { + + } + @case (CustomFieldQueryOperator.Exact) { + + } + @default { + + } + } + +
+
+ + +
+
+
+ + + + + @if (expression.negatable) { + + + } +
+
+ @for (element of expression.value; track element.id; let i = $index) { +
+ @switch (element.type) { + @case (CustomFieldQueryComponentType.Atom) { + + } + @case (CustomFieldQueryComponentType.Expression) { + + } + } +
+ } +
+
+
+ + + @if (expression.depth > 0) { + + } +
+
+
diff --git a/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.scss b/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.scss new file mode 100644 index 000000000..a10c4658d --- /dev/null +++ b/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.scss @@ -0,0 +1,43 @@ +.dropdown-menu { + width: 370px; + @media(min-width: 768px) { + width: 600px; + } +} + +::ng-deep .ng-select-container { + border-top-right-radius: 0 !important; + border-bottom-right-radius: 0 !important; + height: 100% !important; +} + +::ng-deep .rounded-end .ng-select-container { + border-top-right-radius: var(--bs-border-radius) !important; + border-bottom-right-radius: var(--bs-border-radius) !important; + border-top-left-radius: 0 !important; + border-bottom-left-radius: 0 !important; +} + +::ng-deep .ng-select { + max-width: 100px; + min-width: 35%; + font-size: 14px; +} + +::ng-deep .doc-link-select { + padding-top: 0 !important; + border-top-right-radius: var(--bs-border-radius) !important; + border-bottom-right-radius: var(--bs-border-radius) !important; + background-image: none !important; + + .ng-select-container, + .ng-select.ng-select-opened > .ng-select-container { + border: none !important; + min-height: 34px !important; + background: none !important; + } + .ng-select { + max-width: 200px; + min-width: 140px; + } +} diff --git a/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.spec.ts b/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.spec.ts new file mode 100644 index 000000000..e6199c696 --- /dev/null +++ b/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.spec.ts @@ -0,0 +1,320 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' +import { + CustomFieldQueriesModel, + CustomFieldsQueryDropdownComponent, +} from './custom-fields-query-dropdown.component' +import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service' +import { of } from 'rxjs' +import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field' +import { + CUSTOM_FIELD_QUERY_OPERATORS_BY_GROUP, + CustomFieldQueryLogicalOperator, + CustomFieldQueryOperatorGroups, +} from 'src/app/data/custom-field-query' +import { provideHttpClientTesting } from '@angular/common/http/testing' +import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http' +import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap' +import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' +import { + CustomFieldQueryExpression, + CustomFieldQueryAtom, + CustomFieldQueryElement, +} from 'src/app/utils/custom-field-query-element' + +const customFields = [ + { + id: 1, + name: 'Test Field', + data_type: CustomFieldDataType.String, + extra_data: {}, + }, + { + id: 2, + name: 'Test Select Field', + data_type: CustomFieldDataType.Select, + extra_data: { select_options: ['Option 1', 'Option 2'] }, + }, +] + +describe('CustomFieldsQueryDropdownComponent', () => { + let component: CustomFieldsQueryDropdownComponent + let fixture: ComponentFixture + let customFieldsService: CustomFieldsService + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [CustomFieldsQueryDropdownComponent], + imports: [NgbDropdownModule, NgxBootstrapIconsModule.pick(allIcons)], + providers: [ + provideHttpClient(withInterceptorsFromDi()), + provideHttpClientTesting(), + ], + }).compileComponents() + + customFieldsService = TestBed.inject(CustomFieldsService) + jest.spyOn(customFieldsService, 'listAll').mockReturnValue( + of({ + count: customFields.length, + all: customFields.map((f) => f.id), + results: customFields, + }) + ) + fixture = TestBed.createComponent(CustomFieldsQueryDropdownComponent) + component = fixture.componentInstance + component.icon = 'ui-radios' + fixture.detectChanges() + }) + + it('should initialize custom fields on creation', () => { + expect(component.customFields).toEqual(customFields) + }) + + it('should add an expression when opened if queries are empty', () => { + component.selectionModel.clear() + component.onOpenChange(true) + expect(component.selectionModel.queries.length).toBe(1) + }) + + it('should support reset the selection model', () => { + component.selectionModel.addExpression() + component.reset() + expect(component.selectionModel.isEmpty()).toBeTruthy() + }) + + it('should get operators for a field', () => { + const field: CustomField = { + id: 1, + name: 'Test Field', + data_type: CustomFieldDataType.String, + extra_data: {}, + } + component.customFields = [field] + const operators = component.getOperatorsForField(1) + expect(operators.length).toEqual( + [ + ...CUSTOM_FIELD_QUERY_OPERATORS_BY_GROUP[ + CustomFieldQueryOperatorGroups.Basic + ], + ...CUSTOM_FIELD_QUERY_OPERATORS_BY_GROUP[ + CustomFieldQueryOperatorGroups.String + ], + ].length + ) + + // Fallback to basic operators if field is not found + const operators2 = component.getOperatorsForField(2) + expect(operators2.length).toEqual( + CUSTOM_FIELD_QUERY_OPERATORS_BY_GROUP[ + CustomFieldQueryOperatorGroups.Basic + ].length + ) + }) + + it('should get select options for a field', () => { + const field: CustomField = { + id: 1, + name: 'Test Field', + data_type: CustomFieldDataType.Select, + extra_data: { select_options: ['Option 1', 'Option 2'] }, + } + component.customFields = [field] + const options = component.getSelectOptionsForField(1) + expect(options).toEqual(['Option 1', 'Option 2']) + + // Fallback to empty array if field is not found + const options2 = component.getSelectOptionsForField(2) + expect(options2).toEqual([]) + }) + + it('should remove an element from the selection model', () => { + const expression = new CustomFieldQueryExpression() + const atom = new CustomFieldQueryAtom() + ;(expression.value as CustomFieldQueryElement[]).push(atom) + component.selectionModel.addExpression(expression) + component.removeElement(atom) + expect(component.selectionModel.isEmpty()).toBeTruthy() + const expression2 = new CustomFieldQueryExpression([ + CustomFieldQueryLogicalOperator.And, + [ + [1, 'icontains', 'test'], + [2, 'icontains', 'test'], + ], + ]) + component.selectionModel.addExpression(expression2) + component.removeElement(expression2) + expect(component.selectionModel.isEmpty()).toBeTruthy() + }) + + it('should emit selectionModelChange when model changes', () => { + const nextSpy = jest.spyOn(component.selectionModelChange, 'next') + const atom = new CustomFieldQueryAtom([1, 'icontains', 'test']) + component.selectionModel.addAtom(atom) + atom.changed.next(atom) + expect(nextSpy).toHaveBeenCalled() + }) + + it('should complete selection model subscription when new selection model is set', () => { + const completeSpy = jest.spyOn(component.selectionModel.changed, 'complete') + const selectionModel = new CustomFieldQueriesModel() + component.selectionModel = selectionModel + expect(completeSpy).toHaveBeenCalled() + }) + + it('should support adding an atom', () => { + const expression = new CustomFieldQueryExpression() + component.addAtom(expression) + expect(expression.value.length).toBe(1) + }) + + it('should support adding an expression', () => { + const expression = new CustomFieldQueryExpression() + component.addExpression(expression) + expect(expression.value.length).toBe(1) + }) + + it('should support getting a custom field by ID', () => { + expect(component.getCustomFieldByID(1)).toEqual(customFields[0]) + }) + + it('should sanitize name from title', () => { + component.title = 'Test Title' + expect(component.name).toBe('test_title') + }) + + describe('CustomFieldQueriesModel', () => { + let model: CustomFieldQueriesModel + + beforeEach(() => { + model = new CustomFieldQueriesModel() + }) + + it('should initialize with empty queries', () => { + expect(model.queries).toEqual([]) + }) + + it('should clear queries and fire event', () => { + const nextSpy = jest.spyOn(model.changed, 'next') + model.addExpression() + model.clear() + expect(model.queries).toEqual([]) + expect(nextSpy).toHaveBeenCalledWith(model) + }) + + it('should clear queries without firing event', () => { + const nextSpy = jest.spyOn(model.changed, 'next') + model.addExpression() + model.clear(false) + expect(model.queries).toEqual([]) + expect(nextSpy).not.toHaveBeenCalled() + }) + + it('should validate an empty model as invalid', () => { + expect(model.isValid()).toBeFalsy() + }) + + it('should validate a model with valid expression as valid', () => { + const expression = new CustomFieldQueryExpression() + const atom = new CustomFieldQueryAtom([1, 'icontains', 'test']) + const atom2 = new CustomFieldQueryAtom([2, 'icontains', 'test']) + const expression2 = new CustomFieldQueryExpression() + expression2.addAtom(atom) + expression2.addAtom(atom2) + expression.addExpression(expression2) + model.addExpression(expression) + expect(model.isValid()).toBeTruthy() + }) + + it('should validate a model with invalid expression as invalid', () => { + const expression = new CustomFieldQueryExpression() + model.addExpression(expression) + expect(model.isValid()).toBeFalsy() + }) + + it('should validate an atom with in or contains operator', () => { + const atom = new CustomFieldQueryAtom([1, 'in', '[1,2,3]']) + expect(model['validateAtom'].apply(null, [atom])).toBeTruthy() + atom.operator = 'contains' + atom.value = [1, 2, 3] + expect(model['validateAtom'].apply(null, [atom])).toBeTruthy() + atom.value = null + expect(model['validateAtom'].apply(null, [atom])).toBeFalsy() + }) + + it('should check if model is empty', () => { + expect(model.isEmpty()).toBeTruthy() + model.addExpression() + expect(model.isEmpty()).toBeTruthy() + const atom = new CustomFieldQueryAtom([1, 'icontains', 'test']) + model.addAtom(atom) + expect(model.isEmpty()).toBeFalsy() + }) + + it('should add an atom to the model', () => { + const atom = new CustomFieldQueryAtom([1, 'icontains', 'test']) + model.addAtom(atom) + expect(model.queries.length).toBe(1) + expect( + (model.queries[0] as CustomFieldQueryExpression).value.length + ).toBe(1) + }) + + it('should add an expression to the model, propagate changes', () => { + const expression = new CustomFieldQueryExpression() + model.addExpression(expression) + expect(model.queries.length).toBe(1) + const expression2 = new CustomFieldQueryExpression([ + CustomFieldQueryLogicalOperator.And, + [ + [1, 'icontains', 'test'], + [2, 'icontains', 'test'], + ], + ]) + model.addExpression(expression2) + const nextSpy = jest.spyOn(model.changed, 'next') + expression2.changed.next(expression2) + expect(nextSpy).toHaveBeenCalled() + }) + + it('should remove an element from the model', () => { + const expression = new CustomFieldQueryExpression([ + CustomFieldQueryLogicalOperator.And, + [ + [1, 'icontains', 'test'], + [2, 'icontains', 'test'], + ], + ]) + const atom = new CustomFieldQueryAtom([1, 'icontains', 'test']) + const expression2 = new CustomFieldQueryExpression([ + CustomFieldQueryLogicalOperator.And, + [ + [3, 'icontains', 'test'], + [4, 'icontains', 'test'], + ], + ]) + expression.addAtom(atom) + expression2.addExpression(expression) + model.addExpression(expression2) + model.removeElement(atom) + expect(model.queries.length).toBe(1) + model.removeElement(expression2) + }) + + it('should fire changed event when an atom changes', () => { + const nextSpy = jest.spyOn(model.changed, 'next') + const atom = new CustomFieldQueryAtom([1, 'icontains', 'test']) + model.addAtom(atom) + atom.changed.next(atom) + expect(nextSpy).toHaveBeenCalledWith(model) + }) + + it('should complete changed subject when element is removed', () => { + const expression = new CustomFieldQueryExpression() + const atom = new CustomFieldQueryAtom([1, 'icontains', 'test']) + ;(expression.value as CustomFieldQueryElement[]).push(atom) + model.addExpression(expression) + const completeSpy = jest.spyOn(atom.changed, 'complete') + model.removeElement(atom) + expect(completeSpy).toHaveBeenCalled() + }) + }) +}) diff --git a/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.ts b/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.ts new file mode 100644 index 000000000..923907158 --- /dev/null +++ b/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.ts @@ -0,0 +1,294 @@ +import { + Component, + EventEmitter, + Input, + Output, + ViewChild, +} from '@angular/core' +import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' +import { Subject, first, takeUntil } from 'rxjs' +import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field' +import { + CustomFieldQueryElementType, + CustomFieldQueryOperator, + CUSTOM_FIELD_QUERY_OPERATOR_GROUPS_BY_TYPE, + CUSTOM_FIELD_QUERY_OPERATORS_BY_GROUP, + CustomFieldQueryOperatorGroups, + CUSTOM_FIELD_QUERY_OPERATOR_LABELS, + CUSTOM_FIELD_QUERY_MAX_DEPTH, + CUSTOM_FIELD_QUERY_MAX_ATOMS, +} from 'src/app/data/custom-field-query' +import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service' +import { + CustomFieldQueryElement, + CustomFieldQueryExpression, + CustomFieldQueryAtom, +} from 'src/app/utils/custom-field-query-element' +import { popperOptionsReenablePreventOverflow } from 'src/app/utils/popper-options' + +export class CustomFieldQueriesModel { + public queries: CustomFieldQueryElement[] = [] + + public readonly changed = new Subject() + + public clear(fireEvent = true) { + this.queries = [] + if (fireEvent) { + this.changed.next(this) + } + } + + public isValid(): boolean { + return ( + this.queries.length > 0 && + this.validateExpression(this.queries[0] as CustomFieldQueryExpression) + ) + } + + public isEmpty(): boolean { + return ( + this.queries.length === 0 || + (this.queries.length === 1 && this.queries[0].value.length === 0) + ) + } + + private validateAtom(atom: CustomFieldQueryAtom) { + let valid = !!(atom.field && atom.operator && atom.value !== null) + if ( + [ + CustomFieldQueryOperator.In.valueOf(), + CustomFieldQueryOperator.Contains.valueOf(), + ].includes(atom.operator) && + atom.value + ) { + valid = valid && atom.value.length > 0 + } + return valid + } + + private validateExpression(expression: CustomFieldQueryExpression) { + return ( + expression.operator && + expression.value.length > 0 && + (expression.value as CustomFieldQueryElement[]).every((e) => + e.type === CustomFieldQueryElementType.Atom + ? this.validateAtom(e as CustomFieldQueryAtom) + : this.validateExpression(e as CustomFieldQueryExpression) + ) + ) + } + + public addAtom(atom: CustomFieldQueryAtom) { + if (this.queries.length === 0) { + this.addExpression() + } + ;(this.queries[0].value as CustomFieldQueryElement[]).push(atom) + atom.changed.subscribe(() => { + if (atom.field && atom.operator && atom.value) { + this.changed.next(this) + } + }) + } + + public addExpression( + expression: CustomFieldQueryExpression = new CustomFieldQueryExpression() + ) { + if (this.queries.length > 0) { + ;( + (this.queries[0] as CustomFieldQueryExpression) + .value as CustomFieldQueryElement[] + ).push(expression) + } else { + this.queries.push(expression) + } + expression.changed.subscribe(() => { + this.changed.next(this) + }) + } + + private findElement( + queryElement: CustomFieldQueryElement, + elements: any[] + ): CustomFieldQueryElement { + for (let i = 0; i < elements.length; i++) { + if (elements[i] === queryElement) { + return elements.splice(i, 1)[0] + } else if (elements[i].type === CustomFieldQueryElementType.Expression) { + return this.findElement( + queryElement, + elements[i].value as CustomFieldQueryElement[] + ) + } + } + } + + public removeElement(queryElement: CustomFieldQueryElement) { + let foundComponent + for (let i = 0; i < this.queries.length; i++) { + let query = this.queries[i] + if (query === queryElement) { + foundComponent = this.queries.splice(i, 1)[0] + break + } else if (query.type === CustomFieldQueryElementType.Expression) { + foundComponent = this.findElement(queryElement, query.value as any[]) + } + } + if (foundComponent) { + foundComponent.changed.complete() + if (this.isEmpty()) { + this.clear() + } + this.changed.next(this) + } + } +} + +@Component({ + selector: 'pngx-custom-fields-query-dropdown', + templateUrl: './custom-fields-query-dropdown.component.html', + styleUrls: ['./custom-fields-query-dropdown.component.scss'], +}) +export class CustomFieldsQueryDropdownComponent { + public CustomFieldQueryComponentType = CustomFieldQueryElementType + public CustomFieldQueryOperator = CustomFieldQueryOperator + public CustomFieldDataType = CustomFieldDataType + public CUSTOM_FIELD_QUERY_MAX_DEPTH = CUSTOM_FIELD_QUERY_MAX_DEPTH + public CUSTOM_FIELD_QUERY_MAX_ATOMS = CUSTOM_FIELD_QUERY_MAX_ATOMS + public popperOptions = popperOptionsReenablePreventOverflow + + @Input() + title: string + + @Input() + filterPlaceholder: string = '' + + @Input() + icon: string + + @Input() + allowSelectNone: boolean = false + + @Input() + editing = false + + @Input() + applyOnClose = false + + get name(): string { + return this.title ? this.title.replace(/\s/g, '_').toLowerCase() : null + } + + @Input() + disabled: boolean = false + + @ViewChild('dropdown') dropdown: NgbDropdown + + private _selectionModel: CustomFieldQueriesModel + + @Input() + set selectionModel(model: CustomFieldQueriesModel) { + if (this._selectionModel) { + this._selectionModel.changed.complete() + } + model.changed.subscribe(() => { + this.onModelChange() + }) + this._selectionModel = model + } + + get selectionModel(): CustomFieldQueriesModel { + return this._selectionModel + } + + private onModelChange() { + if (this.selectionModel.isEmpty() || this.selectionModel.isValid()) { + this.selectionModelChange.next(this.selectionModel) + this.selectionModel.isEmpty() && this.dropdown?.close() + } + } + + @Output() + selectionModelChange = new EventEmitter() + + customFields: CustomField[] = [] + + private unsubscribeNotifier: Subject = new Subject() + + constructor(protected customFieldsService: CustomFieldsService) { + this.selectionModel = new CustomFieldQueriesModel() + this.getFields() + this.reset() + } + + ngOnDestroy(): void { + this.unsubscribeNotifier.next(this) + this.unsubscribeNotifier.complete() + } + + public onOpenChange(open: boolean) { + if (open && this.selectionModel.queries.length === 0) { + this.selectionModel.addExpression() + } + } + + public get isActive(): boolean { + return ( + (this.selectionModel.queries[0] as CustomFieldQueryExpression)?.value + ?.length > 0 + ) + } + + private getFields() { + this.customFieldsService + .listAll() + .pipe(first(), takeUntil(this.unsubscribeNotifier)) + .subscribe((result) => { + this.customFields = result.results + }) + } + + public getCustomFieldByID(id: number): CustomField { + return this.customFields.find((field) => field.id === id) + } + + public addAtom(expression: CustomFieldQueryExpression) { + expression.addAtom() + } + + public addExpression(expression: CustomFieldQueryExpression) { + expression.addExpression() + } + + public removeElement(element: CustomFieldQueryElement) { + this.selectionModel.removeElement(element) + } + + public reset() { + this.selectionModel.clear(false) + this.selectionModel.changed.next(this.selectionModel) + } + + getOperatorsForField( + fieldID: number + ): Array<{ value: string; label: string }> { + const field = this.customFields.find((field) => field.id === fieldID) + const groups: CustomFieldQueryOperatorGroups[] = field + ? CUSTOM_FIELD_QUERY_OPERATOR_GROUPS_BY_TYPE[field.data_type] + : [CustomFieldQueryOperatorGroups.Basic] + const operators = groups.flatMap( + (group) => CUSTOM_FIELD_QUERY_OPERATORS_BY_GROUP[group] + ) + return operators.map((operator) => ({ + value: operator, + label: CUSTOM_FIELD_QUERY_OPERATOR_LABELS[operator], + })) + } + + getSelectOptionsForField(fieldID: number): string[] { + const field = this.customFields.find((field) => field.id === fieldID) + if (field) { + return field.extra_data['select_options'] + } + return [] + } +} diff --git a/src-ui/src/app/components/common/input/document-link/document-link.component.html b/src-ui/src/app/components/common/input/document-link/document-link.component.html index a8ecce4e6..94f4f21b4 100644 --- a/src-ui/src/app/components/common/input/document-link/document-link.component.html +++ b/src-ui/src/app/components/common/input/document-link/document-link.component.html @@ -1,50 +1,57 @@ -
-
-
- @if (title) { - - } - @if (removable) { - - } -
-
-
- - - - - -
-
Loading...
-
- -
{{document.title}} ({{document.created | customDate:'shortDate'}})
-
-
+@if (minimal) { + +} @else { +
+
+
+ @if (title) { + + } + @if (removable) { + + } +
+
+ + @if (hint) { + {{hint}} + }
- @if (hint) { - {{hint}} - }
-
+} + + + + + + + +
+
Loading...
+
+ +
{{document.title}} ({{document.created | customDate:'shortDate'}})
+
+
+
diff --git a/src-ui/src/app/components/common/input/document-link/document-link.component.ts b/src-ui/src/app/components/common/input/document-link/document-link.component.ts index 83a6a742e..882aacad5 100644 --- a/src-ui/src/app/components/common/input/document-link/document-link.component.ts +++ b/src-ui/src/app/components/common/input/document-link/document-link.component.ts @@ -46,6 +46,12 @@ export class DocumentLinkComponent @Input() parentDocumentID: number + @Input() + minimal: boolean = false + + @Input() + placeholder: string = $localize`Search for documents` + constructor(private documentsService: DocumentService) { super() } diff --git a/src-ui/src/app/components/document-list/document-list.component.html b/src-ui/src/app/components/document-list/document-list.component.html index e70f4c710..4eb9d179e 100644 --- a/src-ui/src/app/components/document-list/document-list.component.html +++ b/src-ui/src/app/components/document-list/document-list.component.html @@ -140,7 +140,7 @@ } @else { @if (list.displayMode === DisplayMode.LARGE_CARDS) {
- @for (d of list.documents; track trackByDocumentId($index, d)) { + @for (d of list.documents; track d.id) {
- @for (d of list.documents; track trackByDocumentId($index, d)) { + @for (d of list.documents; track d.id) {
ASNCorrespondentTitleTagsOwnerNotesDocument typeStorage pathCreatedAdded
Pages - Shared - - {{getDisplayCustomFieldTitle(field)}} - CorrespondentTitleTagsOwnerNotesDocument typeStorage pathCreatedAddedPages + Shared + + {{getDisplayCustomFieldTitle(field)}} +
@@ -364,7 +364,7 @@ } @if (list.displayMode === DisplayMode.SMALL_CARDS) {
- @for (d of list.documents; track trackByDocumentId($index, d)) { + @for (d of list.documents; track d.id) { 0) { - + > } { ToggleableDropdownButtonComponent, DatesDropdownComponent, CustomDatePipe, + CustomFieldsQueryDropdownComponent, ], imports: [ RouterModule, @@ -190,6 +198,7 @@ describe('FilterEditorComponent', () => { NgbDatepickerModule, NgxBootstrapIconsModule.pick(allIcons), NgbTypeaheadModule, + NgSelectModule, ], providers: [ FilterPipe, @@ -838,108 +847,79 @@ describe('FilterEditorComponent', () => { ] })) - it('should ingest filter rules for has all custom fields', fakeAsync(() => { - expect(component.customFieldSelectionModel.getSelectedItems()).toHaveLength( - 0 - ) + it('should ingest filter rules for custom fields all', fakeAsync(() => { + expect(component.customFieldQueriesModel.isEmpty()).toBeTruthy() component.filterRules = [ { rule_type: FILTER_HAS_CUSTOM_FIELDS_ALL, - value: '42', - }, - { - rule_type: FILTER_HAS_CUSTOM_FIELDS_ALL, - value: '43', + value: '42,43', }, ] - expect(component.customFieldSelectionModel.logicalOperator).toEqual( - LogicalOperator.And + expect(component.customFieldQueriesModel.queries[0].operator).toEqual( + CustomFieldQueryLogicalOperator.And ) - expect(component.customFieldSelectionModel.getSelectedItems()).toEqual( - custom_fields - ) - // coverage - component.filterRules = [ - { - rule_type: FILTER_HAS_CUSTOM_FIELDS_ALL, - value: null, - }, - ] - component.toggleTag(2) // coverage + expect(component.customFieldQueriesModel.queries[0].value.length).toEqual(2) + expect( + ( + component.customFieldQueriesModel.queries[0] + .value[0] as CustomFieldQueryAtom + ).serialize() + ).toEqual(['42', CustomFieldQueryOperator.Exists, 'true']) })) it('should ingest filter rules for has any custom fields', fakeAsync(() => { - expect(component.customFieldSelectionModel.getSelectedItems()).toHaveLength( - 0 - ) + expect(component.customFieldQueriesModel.isEmpty()).toBeTruthy() component.filterRules = [ { rule_type: FILTER_HAS_CUSTOM_FIELDS_ANY, - value: '42', - }, - { - rule_type: FILTER_HAS_CUSTOM_FIELDS_ANY, - value: '43', + value: '42,43', }, ] - expect(component.customFieldSelectionModel.logicalOperator).toEqual( - LogicalOperator.Or + expect(component.customFieldQueriesModel.queries[0].operator).toEqual( + CustomFieldQueryLogicalOperator.Or ) - expect(component.customFieldSelectionModel.getSelectedItems()).toEqual( - custom_fields - ) - // coverage - component.filterRules = [ - { - rule_type: FILTER_HAS_CUSTOM_FIELDS_ANY, - value: null, - }, - ] + expect(component.customFieldQueriesModel.queries[0].value.length).toEqual(2) + expect( + ( + component.customFieldQueriesModel.queries[0] + .value[0] as CustomFieldQueryAtom + ).serialize() + ).toEqual(['42', CustomFieldQueryOperator.Exists, 'true']) })) - it('should ingest filter rules for has any custom field', fakeAsync(() => { - expect(component.customFieldSelectionModel.getSelectedItems()).toHaveLength( - 0 - ) + it('should ingest filter rules for custom field queries', fakeAsync(() => { + expect(component.customFieldQueriesModel.isEmpty()).toBeTruthy() component.filterRules = [ { - rule_type: FILTER_HAS_ANY_CUSTOM_FIELDS, - value: '1', + rule_type: FILTER_CUSTOM_FIELDS_QUERY, + value: '["AND", [[42, "exists", "true"],[43, "exists", "true"]]]', }, ] - expect(component.customFieldSelectionModel.getSelectedItems()).toHaveLength( - 1 + expect(component.customFieldQueriesModel.queries[0].operator).toEqual( + CustomFieldQueryLogicalOperator.And ) - expect(component.customFieldSelectionModel.get(null)).toBeTruthy() - })) + expect(component.customFieldQueriesModel.queries[0].value.length).toEqual(2) + expect( + ( + component.customFieldQueriesModel.queries[0] + .value[0] as CustomFieldQueryAtom + ).serialize() + ).toEqual([42, CustomFieldQueryOperator.Exists, 'true']) - it('should ingest filter rules for exclude tag(s)', fakeAsync(() => { - expect(component.customFieldSelectionModel.getExcludedItems()).toHaveLength( - 0 - ) + // atom component.filterRules = [ { - rule_type: FILTER_DOES_NOT_HAVE_CUSTOM_FIELDS, - value: '42', - }, - { - rule_type: FILTER_DOES_NOT_HAVE_CUSTOM_FIELDS, - value: '43', - }, - ] - expect(component.customFieldSelectionModel.logicalOperator).toEqual( - LogicalOperator.And - ) - expect(component.customFieldSelectionModel.getExcludedItems()).toEqual( - custom_fields - ) - // coverage - component.filterRules = [ - { - rule_type: FILTER_DOES_NOT_HAVE_CUSTOM_FIELDS, - value: null, + rule_type: FILTER_CUSTOM_FIELDS_QUERY, + value: '[42, "exists", "true"]', }, ] + expect(component.customFieldQueriesModel.queries[0].value.length).toEqual(1) + expect( + ( + component.customFieldQueriesModel.queries[0] + .value[0] as CustomFieldQueryAtom + ).serialize() + ).toEqual([42, CustomFieldQueryOperator.Exists, 'true']) })) it('should ingest filter rules for owner', fakeAsync(() => { @@ -1453,71 +1433,37 @@ describe('FilterEditorComponent', () => { ]) })) - it('should convert user input to correct filter rules on custom field select not assigned', fakeAsync(() => { - const customFieldsFilterableDropdown = fixture.debugElement.queryAll( - By.directive(FilterableDropdownComponent) - )[4] - customFieldsFilterableDropdown.triggerEventHandler('opened') - const customFieldButton = customFieldsFilterableDropdown.queryAll( - By.directive(ToggleableDropdownButtonComponent) - )[0] - customFieldButton.triggerEventHandler('toggle') - fixture.detectChanges() - expect(component.filterRules).toEqual([ - { - rule_type: FILTER_HAS_ANY_CUSTOM_FIELDS, - value: 'false', - }, - ]) - })) - it('should convert user input to correct filter rules on custom field selections', fakeAsync(() => { - const customFieldsFilterableDropdown = fixture.debugElement.queryAll( - By.directive(FilterableDropdownComponent) - )[4] // CF dropdown - customFieldsFilterableDropdown.triggerEventHandler('opened') - const customFieldButtons = customFieldsFilterableDropdown.queryAll( - By.directive(ToggleableDropdownButtonComponent) + const customFieldsQueryDropdown = fixture.debugElement.queryAll( + By.directive(CustomFieldsQueryDropdownComponent) + )[0] + const customFieldToggleButton = customFieldsQueryDropdown.query( + By.css('button') ) - customFieldButtons[1].triggerEventHandler('toggle') - customFieldButtons[2].triggerEventHandler('toggle') + customFieldToggleButton.triggerEventHandler('click') fixture.detectChanges() - expect(component.filterRules).toEqual([ - { - rule_type: FILTER_HAS_CUSTOM_FIELDS_ALL, - value: custom_fields[0].id.toString(), - }, - { - rule_type: FILTER_HAS_CUSTOM_FIELDS_ALL, - value: custom_fields[1].id.toString(), - }, - ]) - const toggleOperatorButtons = customFieldsFilterableDropdown.queryAll( - By.css('input[type=radio]') + const customFieldButtons = customFieldsQueryDropdown.queryAll( + By.css('button') ) - toggleOperatorButtons[1].nativeElement.checked = true - toggleOperatorButtons[1].triggerEventHandler('change') + customFieldButtons[1].triggerEventHandler('click') fixture.detectChanges() + const query = component.customFieldQueriesModel + .queries[0] as CustomFieldQueryAtom + query.field = custom_fields[0].id + const fieldSelect: NgSelectComponent = customFieldsQueryDropdown.queryAll( + By.directive(NgSelectComponent) + )[0].componentInstance + fieldSelect.open() + const options = customFieldsQueryDropdown.queryAll(By.css('.ng-option')) + options[0].nativeElement.click() + expect(component.customFieldQueriesModel.queries[0].value.length).toEqual(1) expect(component.filterRules).toEqual([ { - rule_type: FILTER_HAS_CUSTOM_FIELDS_ANY, - value: custom_fields[0].id.toString(), - }, - { - rule_type: FILTER_HAS_CUSTOM_FIELDS_ANY, - value: custom_fields[1].id.toString(), - }, - ]) - customFieldButtons[2].triggerEventHandler('exclude') - fixture.detectChanges() - expect(component.filterRules).toEqual([ - { - rule_type: FILTER_HAS_CUSTOM_FIELDS_ALL, - value: custom_fields[0].id.toString(), - }, - { - rule_type: FILTER_DOES_NOT_HAVE_CUSTOM_FIELDS, - value: custom_fields[1].id.toString(), + rule_type: FILTER_CUSTOM_FIELDS_QUERY, + value: JSON.stringify([ + CustomFieldQueryLogicalOperator.Or, + [[custom_fields[0].id, 'exists', 'true']], + ]), }, ]) })) @@ -1930,21 +1876,11 @@ describe('FilterEditorComponent', () => { component.filterRules = [ { - rule_type: FILTER_HAS_CUSTOM_FIELDS_ALL, - value: '42', + rule_type: FILTER_CUSTOM_FIELDS_QUERY, + value: '["AND",[["42","exists","true"],["43","exists","true"]]]', }, ] - expect(component.generateFilterName()).toEqual( - `Custom fields: ${custom_fields[0].name}` - ) - - component.filterRules = [ - { - rule_type: FILTER_HAS_ANY_CUSTOM_FIELDS, - value: 'false', - }, - ] - expect(component.generateFilterName()).toEqual('Without any custom field') + expect(component.generateFilterName()).toEqual(`Custom fields query`) component.filterRules = [ { diff --git a/src-ui/src/app/components/document-list/filter-editor/filter-editor.component.ts b/src-ui/src/app/components/document-list/filter-editor/filter-editor.component.ts index fe1f6cc8c..24ef1b347 100644 --- a/src-ui/src/app/components/document-list/filter-editor/filter-editor.component.ts +++ b/src-ui/src/app/components/document-list/filter-editor/filter-editor.component.ts @@ -12,7 +12,7 @@ import { import { Tag } from 'src/app/data/tag' import { Correspondent } from 'src/app/data/correspondent' import { DocumentType } from 'src/app/data/document-type' -import { Observable, Subject, Subscription, from } from 'rxjs' +import { Observable, Subject, from } from 'rxjs' import { catchError, debounceTime, @@ -62,7 +62,7 @@ import { FILTER_HAS_CUSTOM_FIELDS_ANY, FILTER_HAS_CUSTOM_FIELDS_ALL, FILTER_HAS_ANY_CUSTOM_FIELDS, - FILTER_DOES_NOT_HAVE_CUSTOM_FIELDS, + FILTER_CUSTOM_FIELDS_QUERY, } from 'src/app/data/filter-rule-type' import { FilterableDropdownSelectionModel, @@ -92,6 +92,15 @@ import { ComponentWithPermissions } from '../../with-permissions/with-permission import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service' import { CustomField } from 'src/app/data/custom-field' import { SearchService } from 'src/app/services/rest/search.service' +import { + CustomFieldQueryLogicalOperator, + CustomFieldQueryOperator, +} from 'src/app/data/custom-field-query' +import { CustomFieldQueriesModel } from '../../common/custom-fields-query-dropdown/custom-fields-query-dropdown.component' +import { + CustomFieldQueryExpression, + CustomFieldQueryAtom, +} from 'src/app/utils/custom-field-query-element' const TEXT_FILTER_TARGET_TITLE = 'title' const TEXT_FILTER_TARGET_TITLE_CONTENT = 'title-content' @@ -225,15 +234,8 @@ export class FilterEditorComponent return $localize`Without any tag` } - case FILTER_HAS_CUSTOM_FIELDS_ALL: - return $localize`Custom fields: ${ - this.customFields.find((f) => f.id == +rule.value)?.name - }` - - case FILTER_HAS_ANY_CUSTOM_FIELDS: - if (rule.value == 'false') { - return $localize`Without any custom field` - } + case FILTER_CUSTOM_FIELDS_QUERY: + return $localize`Custom fields query` case FILTER_TITLE: return $localize`Title: ${rule.value}` @@ -321,7 +323,7 @@ export class FilterEditorComponent correspondentSelectionModel = new FilterableDropdownSelectionModel() documentTypeSelectionModel = new FilterableDropdownSelectionModel() storagePathSelectionModel = new FilterableDropdownSelectionModel() - customFieldSelectionModel = new FilterableDropdownSelectionModel() + customFieldQueriesModel = new CustomFieldQueriesModel() dateCreatedBefore: string dateCreatedAfter: string @@ -356,7 +358,7 @@ export class FilterEditorComponent this.storagePathSelectionModel.clear(false) this.tagSelectionModel.clear(false) this.correspondentSelectionModel.clear(false) - this.customFieldSelectionModel.clear(false) + this.customFieldQueriesModel.clear(false) this._textFilter = null this._moreLikeId = null this.dateAddedBefore = null @@ -523,34 +525,45 @@ export class FilterEditorComponent false ) break + case FILTER_CUSTOM_FIELDS_QUERY: + try { + const query = JSON.parse(rule.value) + if (Array.isArray(query)) { + if (query.length === 2) { + // expression + this.customFieldQueriesModel.addExpression( + new CustomFieldQueryExpression(query as any) + ) + } else if (query.length === 3) { + // atom + this.customFieldQueriesModel.addAtom( + new CustomFieldQueryAtom(query as any) + ) + } + } + } catch (e) { + // error handled by list view service + } + break + // Legacy custom field filters case FILTER_HAS_CUSTOM_FIELDS_ALL: - this.customFieldSelectionModel.logicalOperator = LogicalOperator.And - this.customFieldSelectionModel.set( - rule.value ? +rule.value : null, - ToggleableItemState.Selected, - false + this.customFieldQueriesModel.addExpression( + new CustomFieldQueryExpression([ + CustomFieldQueryLogicalOperator.And, + rule.value + .split(',') + .map((id) => [id, CustomFieldQueryOperator.Exists, 'true']), + ]) ) break case FILTER_HAS_CUSTOM_FIELDS_ANY: - this.customFieldSelectionModel.logicalOperator = LogicalOperator.Or - this.customFieldSelectionModel.set( - rule.value ? +rule.value : null, - ToggleableItemState.Selected, - false - ) - break - case FILTER_HAS_ANY_CUSTOM_FIELDS: - this.customFieldSelectionModel.set( - null, - ToggleableItemState.Selected, - false - ) - break - case FILTER_DOES_NOT_HAVE_CUSTOM_FIELDS: - this.customFieldSelectionModel.set( - rule.value ? +rule.value : null, - ToggleableItemState.Excluded, - false + this.customFieldQueriesModel.addExpression( + new CustomFieldQueryExpression([ + CustomFieldQueryLogicalOperator.Or, + rule.value + .split(',') + .map((id) => [id, CustomFieldQueryOperator.Exists, 'true']), + ]) ) break case FILTER_ASN_ISNULL: @@ -768,34 +781,14 @@ export class FilterEditorComponent }) }) } - if (this.customFieldSelectionModel.isNoneSelected()) { + let queries = this.customFieldQueriesModel.queries.map((query) => + query.serialize() + ) + if (queries.length > 0) { filterRules.push({ - rule_type: FILTER_HAS_ANY_CUSTOM_FIELDS, - value: 'false', + rule_type: FILTER_CUSTOM_FIELDS_QUERY, + value: JSON.stringify(queries[0]), }) - } else { - const customFieldFilterType = - this.customFieldSelectionModel.logicalOperator == LogicalOperator.And - ? FILTER_HAS_CUSTOM_FIELDS_ALL - : FILTER_HAS_CUSTOM_FIELDS_ANY - this.customFieldSelectionModel - .getSelectedItems() - .filter((field) => field.id) - .forEach((field) => { - filterRules.push({ - rule_type: customFieldFilterType, - value: field.id?.toString(), - }) - }) - this.customFieldSelectionModel - .getExcludedItems() - .filter((field) => field.id) - .forEach((field) => { - filterRules.push({ - rule_type: FILTER_DOES_NOT_HAVE_CUSTOM_FIELDS, - value: field.id?.toString(), - }) - }) } if (this.dateCreatedBefore) { filterRules.push({ @@ -1079,10 +1072,6 @@ export class FilterEditorComponent this.storagePathSelectionModel.apply() } - onCustomFieldsDropdownOpen() { - this.customFieldSelectionModel.apply() - } - updateTextFilter(text, updateRules = true) { this._textFilter = text if (updateRules) { diff --git a/src-ui/src/app/data/custom-field-query.ts b/src-ui/src/app/data/custom-field-query.ts new file mode 100644 index 000000000..226a10605 --- /dev/null +++ b/src-ui/src/app/data/custom-field-query.ts @@ -0,0 +1,127 @@ +import { CustomFieldDataType } from './custom-field' + +export enum CustomFieldQueryLogicalOperator { + And = 'AND', + Or = 'OR', + Not = 'NOT', +} + +export enum CustomFieldQueryOperator { + Exact = 'exact', + In = 'in', + IsNull = 'isnull', + Exists = 'exists', + Contains = 'contains', + IContains = 'icontains', + GreaterThan = 'gt', + GreaterThanOrEqual = 'gte', + LessThan = 'lt', + LessThanOrEqual = 'lte', + Range = 'range', +} + +export const CUSTOM_FIELD_QUERY_OPERATOR_LABELS = { + [CustomFieldQueryOperator.Exact]: $localize`Equal to`, + [CustomFieldQueryOperator.In]: $localize`In`, + [CustomFieldQueryOperator.IsNull]: $localize`Is null`, + [CustomFieldQueryOperator.Exists]: $localize`Exists`, + [CustomFieldQueryOperator.Contains]: $localize`Contains`, + [CustomFieldQueryOperator.IContains]: $localize`Contains (case-insensitive)`, + [CustomFieldQueryOperator.GreaterThan]: $localize`Greater than`, + [CustomFieldQueryOperator.GreaterThanOrEqual]: $localize`Greater than or equal to`, + [CustomFieldQueryOperator.LessThan]: $localize`Less than`, + [CustomFieldQueryOperator.LessThanOrEqual]: $localize`Less than or equal to`, + [CustomFieldQueryOperator.Range]: $localize`Range`, +} + +export enum CustomFieldQueryOperatorGroups { + Basic = 'basic', + String = 'string', + Arithmetic = 'arithmetic', + Containment = 'containment', + Subset = 'subset', + Date = 'date', +} + +// Modified from filters.py > SUPPORTED_EXPR_OPERATORS +export const CUSTOM_FIELD_QUERY_OPERATORS_BY_GROUP = { + [CustomFieldQueryOperatorGroups.Basic]: [ + CustomFieldQueryOperator.Exists, + CustomFieldQueryOperator.IsNull, + CustomFieldQueryOperator.Exact, + ], + [CustomFieldQueryOperatorGroups.String]: [CustomFieldQueryOperator.IContains], + [CustomFieldQueryOperatorGroups.Arithmetic]: [ + CustomFieldQueryOperator.GreaterThan, + CustomFieldQueryOperator.GreaterThanOrEqual, + CustomFieldQueryOperator.LessThan, + CustomFieldQueryOperator.LessThanOrEqual, + ], + [CustomFieldQueryOperatorGroups.Containment]: [ + CustomFieldQueryOperator.Contains, + ], + [CustomFieldQueryOperatorGroups.Subset]: [CustomFieldQueryOperator.In], + [CustomFieldQueryOperatorGroups.Date]: [ + CustomFieldQueryOperator.GreaterThanOrEqual, + CustomFieldQueryOperator.LessThanOrEqual, + ], +} + +// filters.py > SUPPORTED_EXPR_CATEGORIES +export const CUSTOM_FIELD_QUERY_OPERATOR_GROUPS_BY_TYPE = { + [CustomFieldDataType.String]: [ + CustomFieldQueryOperatorGroups.Basic, + CustomFieldQueryOperatorGroups.String, + ], + [CustomFieldDataType.Url]: [ + CustomFieldQueryOperatorGroups.Basic, + CustomFieldQueryOperatorGroups.String, + ], + [CustomFieldDataType.Date]: [ + CustomFieldQueryOperatorGroups.Basic, + CustomFieldQueryOperatorGroups.Date, + ], + [CustomFieldDataType.Boolean]: [CustomFieldQueryOperatorGroups.Basic], + [CustomFieldDataType.Integer]: [ + CustomFieldQueryOperatorGroups.Basic, + CustomFieldQueryOperatorGroups.Arithmetic, + ], + [CustomFieldDataType.Float]: [ + CustomFieldQueryOperatorGroups.Basic, + CustomFieldQueryOperatorGroups.Arithmetic, + ], + [CustomFieldDataType.Monetary]: [ + CustomFieldQueryOperatorGroups.Basic, + CustomFieldQueryOperatorGroups.String, + CustomFieldQueryOperatorGroups.Arithmetic, + ], + [CustomFieldDataType.DocumentLink]: [ + CustomFieldQueryOperatorGroups.Basic, + CustomFieldQueryOperatorGroups.Containment, + ], + [CustomFieldDataType.Select]: [ + CustomFieldQueryOperatorGroups.Basic, + CustomFieldQueryOperatorGroups.Subset, + ], +} + +export const CUSTOM_FIELD_QUERY_VALUE_TYPES_BY_OPERATOR = { + [CustomFieldQueryOperator.Exact]: 'string|boolean', + [CustomFieldQueryOperator.IsNull]: 'boolean', + [CustomFieldQueryOperator.Exists]: 'boolean', + [CustomFieldQueryOperator.IContains]: 'string', + [CustomFieldQueryOperator.GreaterThanOrEqual]: 'string|number', + [CustomFieldQueryOperator.LessThanOrEqual]: 'string|number', + [CustomFieldQueryOperator.GreaterThan]: 'number', + [CustomFieldQueryOperator.LessThan]: 'number', + [CustomFieldQueryOperator.Contains]: 'array', + [CustomFieldQueryOperator.In]: 'array', +} + +export const CUSTOM_FIELD_QUERY_MAX_DEPTH = 4 +export const CUSTOM_FIELD_QUERY_MAX_ATOMS = 5 + +export enum CustomFieldQueryElementType { + Atom = 'Atom', + Expression = 'Expression', +} diff --git a/src-ui/src/app/data/filter-rule-type.ts b/src-ui/src/app/data/filter-rule-type.ts index 9a87a421c..1c6b1cdf8 100644 --- a/src-ui/src/app/data/filter-rule-type.ts +++ b/src-ui/src/app/data/filter-rule-type.ts @@ -55,6 +55,8 @@ export const FILTER_HAS_CUSTOM_FIELDS_ANY = 39 export const FILTER_DOES_NOT_HAVE_CUSTOM_FIELDS = 40 export const FILTER_HAS_ANY_CUSTOM_FIELDS = 41 +export const FILTER_CUSTOM_FIELDS_QUERY = 42 + export const FILTER_RULE_TYPES: FilterRuleType[] = [ { id: FILTER_TITLE, @@ -317,6 +319,12 @@ export const FILTER_RULE_TYPES: FilterRuleType[] = [ multi: false, default: true, }, + { + id: FILTER_CUSTOM_FIELDS_QUERY, + filtervar: 'custom_field_query', + datatype: 'string', + multi: false, + }, ] export interface FilterRuleType { diff --git a/src-ui/src/app/utils/custom-field-query-element.spec.ts b/src-ui/src/app/utils/custom-field-query-element.spec.ts new file mode 100644 index 000000000..65be3738a --- /dev/null +++ b/src-ui/src/app/utils/custom-field-query-element.spec.ts @@ -0,0 +1,245 @@ +import { + CustomFieldQueryElement, + CustomFieldQueryAtom, + CustomFieldQueryExpression, +} from './custom-field-query-element' +import { + CustomFieldQueryElementType, + CustomFieldQueryLogicalOperator, + CustomFieldQueryOperator, +} from '../data/custom-field-query' +import { fakeAsync, tick } from '@angular/core/testing' + +describe('CustomFieldQueryElement', () => { + it('should initialize with correct type and id', () => { + const element = new CustomFieldQueryElement( + CustomFieldQueryElementType.Atom + ) + expect(element.type).toBe(CustomFieldQueryElementType.Atom) + expect(element.id).toBeDefined() + }) + + it('should trigger changed on operator change', () => { + const element = new CustomFieldQueryElement( + CustomFieldQueryElementType.Atom + ) + element.changed.subscribe((changedElement) => { + expect(changedElement).toBe(element) + }) + element.operator = CustomFieldQueryOperator.Exists + }) + + it('should trigger changed subject on value change', () => { + const element = new CustomFieldQueryElement( + CustomFieldQueryElementType.Atom + ) + element.changed.subscribe((changedElement) => { + expect(changedElement).toBe(element) + }) + element.value = 'new value' + }) + + it('should throw error on serialize call', () => { + const element = new CustomFieldQueryElement( + CustomFieldQueryElementType.Atom + ) + expect(() => element.serialize()).toThrow('Implemented in subclass') + }) +}) + +describe('CustomFieldQueryAtom', () => { + it('should initialize with correct field, operator, and value', () => { + const atom = new CustomFieldQueryAtom([1, 'operator', 'value']) + expect(atom.field).toBe(1) + expect(atom.operator).toBe('operator') + expect(atom.value).toBe('value') + }) + + it('should trigger changed subject on field change', () => { + const atom = new CustomFieldQueryAtom() + atom.changed.subscribe((changedAtom) => { + expect(changedAtom).toBe(atom) + }) + atom.field = 2 + }) + + it('should set value to null if operator is not found in CUSTOM_FIELD_QUERY_VALUE_TYPES_BY_OPERATOR', () => { + const atom = new CustomFieldQueryAtom() + atom.operator = 'nonexistent_operator' + expect(atom.value).toBeNull() + }) + + it('should set value to empty string if new type is string', () => { + const atom = new CustomFieldQueryAtom() + atom.operator = CustomFieldQueryOperator.IContains + expect(atom.value).toBe('') + }) + + it('should set value to "true" if new type is boolean', () => { + const atom = new CustomFieldQueryAtom() + atom.operator = CustomFieldQueryOperator.Exists + expect(atom.value).toBe('true') + }) + + it('should set value to empty array if new type is array', () => { + const atom = new CustomFieldQueryAtom() + atom.operator = CustomFieldQueryOperator.In + expect(atom.value).toEqual([]) + }) + + it('should try to set existing value to number if new type is number', () => { + const atom = new CustomFieldQueryAtom() + atom.value = '42' + atom.operator = CustomFieldQueryOperator.GreaterThan + expect(atom.value).toBe('42') + + // fallback to null if value is not parseable + atom.value = 'not_a_number' + atom.operator = CustomFieldQueryOperator.GreaterThan + expect(atom.value).toBeNull() + }) + + it('should change boolean values to empty string if operator is not boolean', () => { + const atom = new CustomFieldQueryAtom() + atom.value = 'true' + atom.operator = CustomFieldQueryOperator.Exact + expect(atom.value).toBe('') + }) + + it('should serialize correctly', () => { + const atom = new CustomFieldQueryAtom([1, 'operator', 'value']) + expect(atom.serialize()).toEqual([1, 'operator', 'value']) + }) + + it('should emit changed on value change after debounce', fakeAsync(() => { + const atom = new CustomFieldQueryAtom() + const changeSpy = jest.spyOn(atom.changed, 'next') + atom.value = 'new value' + tick(1000) + expect(changeSpy).toHaveBeenCalled() + })) +}) + +describe('CustomFieldQueryExpression', () => { + it('should initialize with default operator and empty value', () => { + const expression = new CustomFieldQueryExpression() + expect(expression.operator).toBe(CustomFieldQueryLogicalOperator.Or) + expect(expression.value).toEqual([]) + }) + + it('should initialize with correct operator and value, propagate changes', () => { + const expression = new CustomFieldQueryExpression([ + CustomFieldQueryLogicalOperator.And, + [ + [1, 'exists', 'true'], + [2, 'exists', 'true'], + ], + ]) + expect(expression.operator).toBe(CustomFieldQueryLogicalOperator.And) + expect(expression.value.length).toBe(2) + + // propagate changes + const expressionChangeSpy = jest.spyOn(expression.changed, 'next') + ;(expression.value[0] as CustomFieldQueryAtom).changed.next( + expression.value[0] as any + ) + expect(expressionChangeSpy).toHaveBeenCalled() + + const expression2 = new CustomFieldQueryExpression([ + CustomFieldQueryLogicalOperator.Not, + [[CustomFieldQueryLogicalOperator.Or, []]], + ]) + const expressionChangeSpy2 = jest.spyOn(expression2.changed, 'next') + ;(expression2.value[0] as CustomFieldQueryExpression).changed.next( + expression2.value[0] as any + ) + expect(expressionChangeSpy2).toHaveBeenCalled() + }) + + it('should initialize with a sub-expression i.e. NOT', () => { + const expression = new CustomFieldQueryExpression([ + CustomFieldQueryLogicalOperator.Not, + [ + 'AND', + [ + [1, 'exists', 'true'], + [2, 'exists', 'true'], + ], + ], + ]) + expect(expression.value).toHaveLength(1) + const changedSpy = jest.spyOn(expression.changed, 'next') + ;(expression.value[0] as CustomFieldQueryExpression).changed.next( + expression.value[0] as any + ) + expect(changedSpy).toHaveBeenCalled() + }) + + it('should add atom correctly, propagate changes', () => { + const expression = new CustomFieldQueryExpression() + const atom = new CustomFieldQueryAtom([ + 1, + CustomFieldQueryOperator.Exists, + 'true', + ]) + expression.addAtom(atom) + expect(expression.value).toContain(atom) + const changeSpy = jest.spyOn(expression.changed, 'next') + atom.changed.next(atom) + expect(changeSpy).toHaveBeenCalled() + // coverage + expression.addAtom() + }) + + it('should add expression correctly, propagate changes', () => { + const expression = new CustomFieldQueryExpression() + const subExpression = new CustomFieldQueryExpression([ + CustomFieldQueryLogicalOperator.Or, + [], + ]) + expression.addExpression(subExpression) + expect(expression.value).toContain(subExpression) + const changeSpy = jest.spyOn(expression.changed, 'next') + subExpression.changed.next(subExpression) + expect(changeSpy).toHaveBeenCalled() + // coverage + expression.addExpression() + }) + + it('should serialize correctly', () => { + const expression = new CustomFieldQueryExpression([ + CustomFieldQueryLogicalOperator.And, + [[1, 'exists', 'true']], + ]) + expect(expression.serialize()).toEqual([ + CustomFieldQueryLogicalOperator.And, + [[1, 'exists', 'true']], + ]) + }) + + it('should serialize NOT expressions correctly', () => { + const expression = new CustomFieldQueryExpression() + expression.addExpression( + new CustomFieldQueryExpression([ + CustomFieldQueryLogicalOperator.And, + [ + [1, 'exists', 'true'], + [2, 'exists', 'true'], + ], + ]) + ) + expression.operator = CustomFieldQueryLogicalOperator.Not + const serialized = expression.serialize() + expect(serialized[0]).toBe(CustomFieldQueryLogicalOperator.Not) + expect(serialized[1][0]).toBe(CustomFieldQueryLogicalOperator.And) + expect(serialized[1][1].length).toBe(2) + }) + + it('should be negatable if it has one child which is an expression', () => { + const expression = new CustomFieldQueryExpression([ + CustomFieldQueryLogicalOperator.Not, + [[CustomFieldQueryLogicalOperator.Or, []]], + ]) + expect(expression.negatable).toBe(true) + }) +}) diff --git a/src-ui/src/app/utils/custom-field-query-element.ts b/src-ui/src/app/utils/custom-field-query-element.ts new file mode 100644 index 000000000..696853f12 --- /dev/null +++ b/src-ui/src/app/utils/custom-field-query-element.ts @@ -0,0 +1,210 @@ +import { Subject, debounceTime, distinctUntilChanged } from 'rxjs' +import { v4 as uuidv4 } from 'uuid' +import { + CustomFieldQueryElementType, + CUSTOM_FIELD_QUERY_VALUE_TYPES_BY_OPERATOR, + CustomFieldQueryLogicalOperator, + CustomFieldQueryOperator, +} from '../data/custom-field-query' + +export class CustomFieldQueryElement { + public readonly type: CustomFieldQueryElementType + public changed: Subject + protected valueModelChanged: Subject< + string | string[] | number[] | CustomFieldQueryElement[] + > + public depth: number = 0 + public id: string = uuidv4() + + constructor(type: CustomFieldQueryElementType) { + this.type = type + this.changed = new Subject() + this.valueModelChanged = new Subject() + this.connectValueModelChanged() + } + + protected connectValueModelChanged() { + // Allows overriding in subclasses + this.valueModelChanged.subscribe(() => { + this.changed.next(this) + }) + } + + public serialize() { + throw new Error('Implemented in subclass') + } + + protected _operator: string = null + public set operator(value: string) { + this._operator = value + this.changed.next(this) + } + public get operator(): string { + return this._operator + } + + protected _value: string | string[] | number[] | CustomFieldQueryElement[] = + null + public set value( + value: string | string[] | number[] | CustomFieldQueryElement[] + ) { + this._value = value + this.valueModelChanged.next(value) + } + public get value(): string | string[] | number[] | CustomFieldQueryElement[] { + return this._value + } +} + +export class CustomFieldQueryAtom extends CustomFieldQueryElement { + protected _field: number + set field(field: any) { + this._field = parseInt(field, 10) + this.changed.next(this) + } + get field(): number { + return this._field + } + + override set operator(operator: string) { + const newTypes: string[] = + CUSTOM_FIELD_QUERY_VALUE_TYPES_BY_OPERATOR[operator]?.split('|') + if (!newTypes) { + this.value = null + } else { + if (!newTypes.includes(typeof this.value)) { + switch (newTypes[0]) { + case 'string': + this.value = '' + break + case 'boolean': + this.value = 'true' + break + case 'array': + this.value = [] + break + case 'number': + const num = parseFloat(this.value as string) + this.value = isNaN(num) ? null : num.toString() + break + } + } else if ( + ['true', 'false'].includes(this.value as string) && + newTypes.includes('string') + ) { + this.value = '' + } + } + super.operator = operator + } + + override get operator(): string { + // why? + return super.operator + } + + constructor(queryArray: [number, string, string] = [null, null, null]) { + super(CustomFieldQueryElementType.Atom) + ;[this._field, this._operator, this._value] = queryArray + } + + protected override connectValueModelChanged(): void { + this.valueModelChanged + .pipe(debounceTime(1000), distinctUntilChanged()) + .subscribe(() => { + this.changed.next(this) + }) + } + + public override serialize() { + return [this._field, this._operator, this._value] + } +} + +export class CustomFieldQueryExpression extends CustomFieldQueryElement { + protected _value: string[] | number[] | CustomFieldQueryElement[] + + constructor( + expressionArray: [CustomFieldQueryLogicalOperator, any[]] = [ + CustomFieldQueryLogicalOperator.Or, + null, + ] + ) { + super(CustomFieldQueryElementType.Expression) + let values + ;[this._operator, values] = expressionArray + if (!values || values.length === 0) { + this._value = [] + } else if (values?.length > 0 && values[0] instanceof Array) { + this._value = values.map((value) => { + if (value.length === 3) { + const atom = new CustomFieldQueryAtom(value) + atom.depth = this.depth + 1 + atom.changed.subscribe(() => { + this.changed.next(this) + }) + return atom + } else { + const expression = new CustomFieldQueryExpression(value) + expression.depth = this.depth + 1 + expression.changed.subscribe(() => { + this.changed.next(this) + }) + return expression + } + }) + } else { + const expression = new CustomFieldQueryExpression(values as any) + expression.depth = this.depth + 1 + expression.changed.subscribe(() => { + this.changed.next(this) + }) + this._value = [expression] + } + } + + public override serialize() { + let value + value = this._value.map((element) => element.serialize()) + // If the expression is negated it should have only one child which is an expression + if ( + this._operator === CustomFieldQueryLogicalOperator.Not && + value.length === 1 + ) { + value = value[0] + } + return [this._operator, value] + } + + public addAtom( + atom: CustomFieldQueryAtom = new CustomFieldQueryAtom([ + null, + CustomFieldQueryOperator.Exists, + 'true', + ]) + ) { + atom.depth = this.depth + 1 + ;(this._value as CustomFieldQueryElement[]).push(atom) + atom.changed.subscribe(() => { + this.changed.next(this) + }) + } + + public addExpression( + expression: CustomFieldQueryExpression = new CustomFieldQueryExpression() + ) { + expression.depth = this.depth + 1 + ;(this._value as CustomFieldQueryElement[]).push(expression) + expression.changed.subscribe(() => { + this.changed.next(this) + }) + } + + public get negatable(): boolean { + return ( + this.value.length === 1 && + (this.value[0] as CustomFieldQueryElement).type === + CustomFieldQueryElementType.Expression + ) + } +} diff --git a/src-ui/src/app/utils/query-params.spec.ts b/src-ui/src/app/utils/query-params.spec.ts index a1bc0cdcd..64a89efec 100644 --- a/src-ui/src/app/utils/query-params.spec.ts +++ b/src-ui/src/app/utils/query-params.spec.ts @@ -2,13 +2,17 @@ import { convertToParamMap } from '@angular/router' import { FilterRule } from '../data/filter-rule' import { FILTER_CORRESPONDENT, + FILTER_CUSTOM_FIELDS_QUERY, FILTER_HAS_ANY_TAG, + FILTER_HAS_CUSTOM_FIELDS_ALL, + FILTER_HAS_CUSTOM_FIELDS_ANY, FILTER_HAS_TAGS_ALL, } from '../data/filter-rule-type' -import { paramsToViewState } from './query-params' +import { paramsToViewState, transformLegacyFilterRules } from './query-params' import { paramsFromViewState } from './query-params' import { queryParamsFromFilterRules } from './query-params' import { filterRulesFromQueryParams } from './query-params' +import { CustomFieldQueryLogicalOperator } from '../data/custom-field-query' const tags__id__all = '9' const filterRules: FilterRule[] = [ @@ -193,4 +197,58 @@ describe('QueryParams Utils', () => { }, ]) }) + + it('should transform legacy filter rules', () => { + let filterRules: FilterRule[] = [ + { + rule_type: FILTER_HAS_CUSTOM_FIELDS_ANY, + value: '1', + }, + { + rule_type: FILTER_HAS_CUSTOM_FIELDS_ANY, + value: '2', + }, + ] + + let transformedFilterRules = transformLegacyFilterRules(filterRules) + + expect(transformedFilterRules).toEqual([ + { + rule_type: FILTER_CUSTOM_FIELDS_QUERY, + value: JSON.stringify([ + CustomFieldQueryLogicalOperator.Or, + [ + [1, 'exists', true], + [2, 'exists', true], + ], + ]), + }, + ]) + + filterRules = [ + { + rule_type: FILTER_HAS_CUSTOM_FIELDS_ALL, + value: '3', + }, + { + rule_type: FILTER_HAS_CUSTOM_FIELDS_ALL, + value: '4', + }, + ] + + transformedFilterRules = transformLegacyFilterRules(filterRules) + + expect(transformedFilterRules).toEqual([ + { + rule_type: FILTER_CUSTOM_FIELDS_QUERY, + value: JSON.stringify([ + CustomFieldQueryLogicalOperator.And, + [ + [3, 'exists', true], + [4, 'exists', true], + ], + ]), + }, + ]) + }) }) diff --git a/src-ui/src/app/utils/query-params.ts b/src-ui/src/app/utils/query-params.ts index 1121bd6a3..608d4edfb 100644 --- a/src-ui/src/app/utils/query-params.ts +++ b/src-ui/src/app/utils/query-params.ts @@ -1,7 +1,17 @@ import { ParamMap, Params } from '@angular/router' import { FilterRule } from '../data/filter-rule' -import { FilterRuleType, FILTER_RULE_TYPES } from '../data/filter-rule-type' +import { + FilterRuleType, + FILTER_RULE_TYPES, + FILTER_HAS_CUSTOM_FIELDS_ANY, + FILTER_CUSTOM_FIELDS_QUERY, + FILTER_HAS_CUSTOM_FIELDS_ALL, +} from '../data/filter-rule-type' import { ListViewState } from '../services/document-list-view.service' +import { + CustomFieldQueryLogicalOperator, + CustomFieldQueryOperator, +} from '../data/custom-field-query' const SORT_FIELD_PARAMETER = 'sort' const SORT_REVERSE_PARAMETER = 'reverse' @@ -40,6 +50,49 @@ export function paramsToViewState(queryParams: ParamMap): ListViewState { } } +export function transformLegacyFilterRules( + filterRules: FilterRule[] +): FilterRule[] { + const LEGACY_CUSTOM_FIELD_FILTER_RULE_TYPES = [ + FILTER_HAS_CUSTOM_FIELDS_ANY, + FILTER_HAS_CUSTOM_FIELDS_ALL, + ] + if ( + filterRules.filter((rule) => + LEGACY_CUSTOM_FIELD_FILTER_RULE_TYPES.includes(rule.rule_type) + ).length + ) { + const anyRules = filterRules.filter( + (rule) => rule.rule_type === FILTER_HAS_CUSTOM_FIELDS_ANY + ) + const allRules = filterRules.filter( + (rule) => rule.rule_type === FILTER_HAS_CUSTOM_FIELDS_ALL + ) + const customFieldQueryLogicalOperator = allRules.length + ? CustomFieldQueryLogicalOperator.And + : CustomFieldQueryLogicalOperator.Or + const valueRules = allRules.length ? allRules : anyRules + const customFieldQueryExpression = [ + customFieldQueryLogicalOperator, + [ + ...valueRules.map((rule) => [ + parseInt(rule.value), + CustomFieldQueryOperator.Exists, + true, + ]), + ], + ] + filterRules.push({ + rule_type: FILTER_CUSTOM_FIELDS_QUERY, + value: JSON.stringify(customFieldQueryExpression), + }) + } + // TODO: can we support FILTER_DOES_NOT_HAVE_CUSTOM_FIELDS or FILTER_HAS_ANY_CUSTOM_FIELDS? + return filterRules.filter( + (rule) => !LEGACY_CUSTOM_FIELD_FILTER_RULE_TYPES.includes(rule.rule_type) + ) +} + export function filterRulesFromQueryParams( queryParams: ParamMap ): FilterRule[] { @@ -77,7 +130,9 @@ export function filterRulesFromQueryParams( }) ) }) - + filterRulesFromQueryParams = transformLegacyFilterRules( + filterRulesFromQueryParams + ) return filterRulesFromQueryParams } diff --git a/src/documents/filters.py b/src/documents/filters.py index 25e840141..f0a9a55b3 100644 --- a/src/documents/filters.py +++ b/src/documents/filters.py @@ -29,13 +29,15 @@ from documents.models import Log from documents.models import ShareLink from documents.models import StoragePath from documents.models import Tag -from paperless import settings CHAR_KWARGS = ["istartswith", "iendswith", "icontains", "iexact"] ID_KWARGS = ["in", "exact"] INT_KWARGS = ["exact", "gt", "gte", "lt", "lte", "isnull"] DATE_KWARGS = ["year", "month", "day", "date__gt", "gt", "date__lt", "lt"] +CUSTOM_FIELD_QUERY_MAX_DEPTH = 10 +CUSTOM_FIELD_QUERY_MAX_ATOMS = 20 + class CorrespondentFilterSet(FilterSet): class Meta: @@ -234,19 +236,13 @@ def handle_validation_prefix(func: Callable): return wrapper -class CustomFieldLookupParser: +class CustomFieldQueryParser: EXPR_BY_CATEGORY = { "basic": ["exact", "in", "isnull", "exists"], "string": [ - "iexact", - "contains", "icontains", - "startswith", "istartswith", - "endswith", "iendswith", - "regex", - "iregex", ], "arithmetic": [ "gt", @@ -258,23 +254,6 @@ class CustomFieldLookupParser: "containment": ["contains"], } - # These string lookup expressions are problematic. We shall disable - # them by default unless the user explicitly opts in. - STR_EXPR_DISABLED_BY_DEFAULT = [ - # SQLite: is case-sensitive outside the ASCII range - "iexact", - # SQLite: behaves the same as icontains - "contains", - # SQLite: behaves the same as istartswith - "startswith", - # SQLite: behaves the same as iendswith - "endswith", - # Syntax depends on database backends, can be exploited for ReDoS - "regex", - # Syntax depends on database backends, can be exploited for ReDoS - "iregex", - ] - SUPPORTED_EXPR_CATEGORIES = { CustomField.FieldDataType.STRING: ("basic", "string"), CustomField.FieldDataType.URL: ("basic", "string"), @@ -282,7 +261,7 @@ class CustomFieldLookupParser: CustomField.FieldDataType.BOOL: ("basic",), CustomField.FieldDataType.INT: ("basic", "arithmetic"), CustomField.FieldDataType.FLOAT: ("basic", "arithmetic"), - CustomField.FieldDataType.MONETARY: ("basic", "string"), + CustomField.FieldDataType.MONETARY: ("basic", "string", "arithmetic"), CustomField.FieldDataType.DOCUMENTLINK: ("basic", "containment"), CustomField.FieldDataType.SELECT: ("basic",), } @@ -371,7 +350,7 @@ class CustomFieldLookupParser: elif len(expr) == 3: return self._parse_atom(*expr) raise serializers.ValidationError( - [_("Invalid custom field lookup expression")], + [_("Invalid custom field query expression")], ) @handle_validation_prefix @@ -416,13 +395,7 @@ class CustomFieldLookupParser: self._atom_count += 1 if self._atom_count > self._max_atom_count: raise serializers.ValidationError( - [ - _( - "Maximum number of query conditions exceeded. You can raise " - "the limit by setting PAPERLESS_CUSTOM_FIELD_LOOKUP_MAX_ATOMS " - "in your configuration file.", - ), - ], + [_("Maximum number of query conditions exceeded.")], ) custom_field = self._get_custom_field(id_or_name, validation_prefix="0") @@ -444,6 +417,11 @@ class CustomFieldLookupParser: value_field_name = CustomFieldInstance.get_value_field_name( custom_field.data_type, ) + if ( + custom_field.data_type == CustomField.FieldDataType.MONETARY + and op in self.EXPR_BY_CATEGORY["arithmetic"] + ): + value_field_name = "value_monetary_amount" has_field = Q(custom_fields__field=custom_field) # Our special exists operator. @@ -494,22 +472,6 @@ class CustomFieldLookupParser: # Check if the operator is supported for the current data_type. supported = False for category in self.SUPPORTED_EXPR_CATEGORIES[custom_field.data_type]: - if ( - category == "string" - and op in self.STR_EXPR_DISABLED_BY_DEFAULT - and op not in settings.CUSTOM_FIELD_LOOKUP_OPT_IN - ): - raise serializers.ValidationError( - [ - _( - "{expr!r} is disabled by default because it does not " - "behave consistently across database backends, or can " - "cause security risks. If you understand the implications " - "you may enabled it by adding it to " - "`PAPERLESS_CUSTOM_FIELD_LOOKUP_OPT_IN`.", - ).format(expr=op), - ], - ) if op in self.EXPR_BY_CATEGORY[category]: supported = True break @@ -527,7 +489,7 @@ class CustomFieldLookupParser: if not supported: raise serializers.ValidationError( [ - _("{data_type} does not support lookup expr {expr!r}.").format( + _("{data_type} does not support query expr {expr!r}.").format( data_type=custom_field.data_type, expr=raw_op, ), @@ -548,7 +510,7 @@ class CustomFieldLookupParser: custom_field.data_type == CustomField.FieldDataType.DATE and prefix in self.DATE_COMPONENTS ): - # DateField admits lookups in the form of `year__exact`, etc. These take integers. + # DateField admits queries in the form of `year__exact`, etc. These take integers. field = serializers.IntegerField() elif custom_field.data_type == CustomField.FieldDataType.DOCUMENTLINK: # We can be more specific here and make sure the value is a list. @@ -610,7 +572,7 @@ class CustomFieldLookupParser: custom_fields__value_document_ids__isnull=False, ) - # First we lookup reverse links from the requested documents. + # First we look up reverse links from the requested documents. links = CustomFieldInstance.objects.filter( document_id__in=value, field__data_type=CustomField.FieldDataType.DOCUMENTLINK, @@ -635,22 +597,14 @@ class CustomFieldLookupParser: # guard against queries that are too deeply nested self._current_depth += 1 if self._current_depth > self._max_query_depth: - raise serializers.ValidationError( - [ - _( - "Maximum nesting depth exceeded. You can raise the limit " - "by setting PAPERLESS_CUSTOM_FIELD_LOOKUP_MAX_DEPTH in " - "your configuration file.", - ), - ], - ) + raise serializers.ValidationError([_("Maximum nesting depth exceeded.")]) try: yield finally: self._current_depth -= 1 -class CustomFieldLookupFilter(Filter): +class CustomFieldQueryFilter(Filter): def __init__(self, validation_prefix): """ A filter that filters documents based on custom field name and value. @@ -665,10 +619,10 @@ class CustomFieldLookupFilter(Filter): if not value: return qs - parser = CustomFieldLookupParser( + parser = CustomFieldQueryParser( self._validation_prefix, - max_query_depth=settings.CUSTOM_FIELD_LOOKUP_MAX_DEPTH, - max_atom_count=settings.CUSTOM_FIELD_LOOKUP_MAX_ATOMS, + max_query_depth=CUSTOM_FIELD_QUERY_MAX_DEPTH, + max_atom_count=CUSTOM_FIELD_QUERY_MAX_ATOMS, ) q, annotations = parser.parse(value) @@ -722,7 +676,7 @@ class DocumentFilterSet(FilterSet): exclude=True, ) - custom_field_lookup = CustomFieldLookupFilter("custom_field_lookup") + custom_field_query = CustomFieldQueryFilter("custom_field_query") shared_by__id = SharedByUser() diff --git a/src/documents/migrations/1054_customfieldinstance_value_monetary_amount_and_more.py b/src/documents/migrations/1054_customfieldinstance_value_monetary_amount_and_more.py new file mode 100644 index 000000000..92d45de33 --- /dev/null +++ b/src/documents/migrations/1054_customfieldinstance_value_monetary_amount_and_more.py @@ -0,0 +1,95 @@ +# Generated by Django 5.1.1 on 2024-09-29 16:26 + +import django.db.models.functions.comparison +import django.db.models.functions.text +from django.db import migrations +from django.db import models + + +class Migration(migrations.Migration): + dependencies = [ + ("documents", "1053_document_page_count"), + ] + + operations = [ + migrations.AddField( + model_name="customfieldinstance", + name="value_monetary_amount", + field=models.GeneratedField( + db_persist=True, + expression=models.Case( + models.When( + then=django.db.models.functions.comparison.Cast( + django.db.models.functions.text.Substr("value_monetary", 1), + output_field=models.DecimalField( + decimal_places=2, + max_digits=65, + ), + ), + value_monetary__regex="^\\d+", + ), + default=django.db.models.functions.comparison.Cast( + django.db.models.functions.text.Substr("value_monetary", 4), + output_field=models.DecimalField( + decimal_places=2, + max_digits=65, + ), + ), + output_field=models.DecimalField(decimal_places=2, max_digits=65), + ), + output_field=models.DecimalField(decimal_places=2, max_digits=65), + ), + ), + migrations.AlterField( + model_name="savedviewfilterrule", + name="rule_type", + field=models.PositiveIntegerField( + choices=[ + (0, "title contains"), + (1, "content contains"), + (2, "ASN is"), + (3, "correspondent is"), + (4, "document type is"), + (5, "is in inbox"), + (6, "has tag"), + (7, "has any tag"), + (8, "created before"), + (9, "created after"), + (10, "created year is"), + (11, "created month is"), + (12, "created day is"), + (13, "added before"), + (14, "added after"), + (15, "modified before"), + (16, "modified after"), + (17, "does not have tag"), + (18, "does not have ASN"), + (19, "title or content contains"), + (20, "fulltext query"), + (21, "more like this"), + (22, "has tags in"), + (23, "ASN greater than"), + (24, "ASN less than"), + (25, "storage path is"), + (26, "has correspondent in"), + (27, "does not have correspondent in"), + (28, "has document type in"), + (29, "does not have document type in"), + (30, "has storage path in"), + (31, "does not have storage path in"), + (32, "owner is"), + (33, "has owner in"), + (34, "does not have owner"), + (35, "does not have owner in"), + (36, "has custom field value"), + (37, "is shared by me"), + (38, "has custom fields"), + (39, "has custom field in"), + (40, "does not have custom field in"), + (41, "does not have custom field"), + (42, "custom fields query"), + ], + verbose_name="rule type", + ), + ), + ] diff --git a/src/documents/models.py b/src/documents/models.py index 6dae8ba65..80476bffa 100644 --- a/src/documents/models.py +++ b/src/documents/models.py @@ -22,6 +22,9 @@ from multiselectfield import MultiSelectField if settings.AUDIT_LOG_ENABLED: from auditlog.registry import auditlog +from django.db.models import Case +from django.db.models.functions import Cast +from django.db.models.functions import Substr from django_softdelete.models import SoftDeleteModel from documents.data_models import DocumentSource @@ -519,6 +522,7 @@ class SavedViewFilterRule(models.Model): (39, _("has custom field in")), (40, _("does not have custom field in")), (41, _("does not have custom field")), + (42, _("custom fields query")), ] saved_view = models.ForeignKey( @@ -921,6 +925,27 @@ class CustomFieldInstance(models.Model): value_monetary = models.CharField(null=True, max_length=128) + value_monetary_amount = models.GeneratedField( + expression=Case( + # If the value starts with a number and no currency symbol, use the whole string + models.When( + value_monetary__regex=r"^\d+", + then=Cast( + Substr("value_monetary", 1), + output_field=models.DecimalField(decimal_places=2, max_digits=65), + ), + ), + # If the value starts with a 3-char currency symbol, use the rest of the string + default=Cast( + Substr("value_monetary", 4), + output_field=models.DecimalField(decimal_places=2, max_digits=65), + ), + output_field=models.DecimalField(decimal_places=2, max_digits=65), + ), + output_field=models.DecimalField(decimal_places=2, max_digits=65), + db_persist=True, + ) + value_document_ids = models.JSONField(null=True) value_select = models.PositiveSmallIntegerField(null=True) diff --git a/src/documents/tests/test_api_filter_by_custom_fields.py b/src/documents/tests/test_api_filter_by_custom_fields.py index 0f7da0a61..c9a0cdcfc 100644 --- a/src/documents/tests/test_api_filter_by_custom_fields.py +++ b/src/documents/tests/test_api_filter_by_custom_fields.py @@ -1,11 +1,9 @@ import json -import re from collections.abc import Callable from datetime import date from unittest.mock import Mock from urllib.parse import quote -import pytest from django.contrib.auth.models import User from rest_framework.test import APITestCase @@ -13,7 +11,6 @@ from documents.models import CustomField from documents.models import Document from documents.serialisers import DocumentSerializer from documents.tests.utils import DirectoriesMixin -from paperless import settings class DocumentWrapper: @@ -31,11 +28,7 @@ class DocumentWrapper: return self._document.custom_fields.get(field__name=custom_field).value -def string_expr_opted_in(op): - return op in settings.CUSTOM_FIELD_LOOKUP_OPT_IN - - -class TestDocumentSearchApi(DirectoriesMixin, APITestCase): +class TestCustomFieldsSearch(DirectoriesMixin, APITestCase): def setUp(self): super().setUp() @@ -111,6 +104,7 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase): self._create_document(monetary_field="USD100.00") self._create_document(monetary_field="USD1.00") self._create_document(monetary_field="EUR50.00") + self._create_document(monetary_field="101.00") # CustomField.FieldDataType.DOCUMENTLINK self._create_document(documentlink_field=None) @@ -188,7 +182,7 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase): "/api/documents/?" + "&".join( ( - f"custom_field_lookup={query_string}", + f"custom_field_query={query_string}", "ordering=archive_serial_number", "page=1", f"page_size={len(self.documents)}", @@ -212,7 +206,7 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase): "/api/documents/?" + "&".join( ( - f"custom_field_lookup={query_string}", + f"custom_field_query={query_string}", "ordering=archive_serial_number", "page=1", f"page_size={len(self.documents)}", @@ -313,32 +307,6 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase): # ==========================================================# # Expressions for string, URL, and monetary fields # # ==========================================================# - @pytest.mark.skipif( - not string_expr_opted_in("iexact"), - reason="iexact expr is disabled.", - ) - def test_iexact(self): - self._assert_query_match_predicate( - ["string_field", "iexact", "paperless"], - lambda document: "string_field" in document - and document["string_field"] is not None - and document["string_field"].lower() == "paperless", - ) - - @pytest.mark.skipif( - not string_expr_opted_in("contains"), - reason="contains expr is disabled.", - ) - def test_contains(self): - # WARNING: SQLite treats "contains" as "icontains"! - # You should avoid "contains" unless you know what you are doing! - self._assert_query_match_predicate( - ["string_field", "contains", "aper"], - lambda document: "string_field" in document - and document["string_field"] is not None - and "aper" in document["string_field"], - ) - def test_icontains(self): self._assert_query_match_predicate( ["string_field", "icontains", "aper"], @@ -347,20 +315,6 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase): and "aper" in document["string_field"].lower(), ) - @pytest.mark.skipif( - not string_expr_opted_in("startswith"), - reason="startswith expr is disabled.", - ) - def test_startswith(self): - # WARNING: SQLite treats "startswith" as "istartswith"! - # You should avoid "startswith" unless you know what you are doing! - self._assert_query_match_predicate( - ["string_field", "startswith", "paper"], - lambda document: "string_field" in document - and document["string_field"] is not None - and document["string_field"].startswith("paper"), - ) - def test_istartswith(self): self._assert_query_match_predicate( ["string_field", "istartswith", "paper"], @@ -369,20 +323,6 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase): and document["string_field"].lower().startswith("paper"), ) - @pytest.mark.skipif( - not string_expr_opted_in("endswith"), - reason="endswith expr is disabled.", - ) - def test_endswith(self): - # WARNING: SQLite treats "endswith" as "iendswith"! - # You should avoid "endswith" unless you know what you are doing! - self._assert_query_match_predicate( - ["string_field", "iendswith", "less"], - lambda document: "string_field" in document - and document["string_field"] is not None - and document["string_field"].lower().endswith("less"), - ) - def test_iendswith(self): self._assert_query_match_predicate( ["string_field", "iendswith", "less"], @@ -391,32 +331,6 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase): and document["string_field"].lower().endswith("less"), ) - @pytest.mark.skipif( - not string_expr_opted_in("regex"), - reason="regex expr is disabled.", - ) - def test_regex(self): - # WARNING: the regex syntax is database dependent! - self._assert_query_match_predicate( - ["string_field", "regex", r"^p.+s$"], - lambda document: "string_field" in document - and document["string_field"] is not None - and re.match(r"^p.+s$", document["string_field"]), - ) - - @pytest.mark.skipif( - not string_expr_opted_in("iregex"), - reason="iregex expr is disabled.", - ) - def test_iregex(self): - # WARNING: the regex syntax is database dependent! - self._assert_query_match_predicate( - ["string_field", "iregex", r"^p.+s$"], - lambda document: "string_field" in document - and document["string_field"] is not None - and re.match(r"^p.+s$", document["string_field"], re.IGNORECASE), - ) - def test_url_field_istartswith(self): # URL fields supports all of the expressions above. # Just showing one of them here. @@ -427,28 +341,6 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase): and document["url_field"].startswith("http://"), ) - @pytest.mark.skipif( - not string_expr_opted_in("iregex"), - reason="regex expr is disabled.", - ) - def test_monetary_field_iregex(self): - # Monetary fields supports all of the expressions above. - # Just showing one of them here. - # - # Unfortunately we can't do arithmetic comparisons on monetary field, - # but you are welcome to use regex to do some of that. - # E.g., USD between 100.00 and 999.99: - self._assert_query_match_predicate( - ["monetary_field", "regex", r"USD[1-9][0-9]{2}\.[0-9]{2}"], - lambda document: "monetary_field" in document - and document["monetary_field"] is not None - and re.match( - r"USD[1-9][0-9]{2}\.[0-9]{2}", - document["monetary_field"], - re.IGNORECASE, - ), - ) - # ==========================================================# # Arithmetic comparisons # # ==========================================================# @@ -502,6 +394,17 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase): and document["date_field"].year >= 2024, ) + def test_gt_monetary(self): + self._assert_query_match_predicate( + ["monetary_field", "gt", "99"], + lambda document: "monetary_field" in document + and document["monetary_field"] is not None + and ( + document["monetary_field"] == "USD100.00" # With currency symbol + or document["monetary_field"] == "101.00" # No currency symbol + ), + ) + # ==========================================================# # Subset check (document link field only) # # ==========================================================# @@ -586,68 +489,57 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase): def test_invalid_json(self): self._assert_validation_error( "not valid json", - ["custom_field_lookup"], + ["custom_field_query"], "must be valid JSON", ) def test_invalid_expression(self): self._assert_validation_error( json.dumps("valid json but not valid expr"), - ["custom_field_lookup"], - "Invalid custom field lookup expression", + ["custom_field_query"], + "Invalid custom field query expression", ) def test_invalid_custom_field_name(self): self._assert_validation_error( json.dumps(["invalid name", "iexact", "foo"]), - ["custom_field_lookup", "0"], + ["custom_field_query", "0"], "is not a valid custom field", ) def test_invalid_operator(self): self._assert_validation_error( json.dumps(["integer_field", "iexact", "foo"]), - ["custom_field_lookup", "1"], - "does not support lookup expr", + ["custom_field_query", "1"], + "does not support query expr", ) def test_invalid_value(self): self._assert_validation_error( json.dumps(["select_field", "exact", "not an option"]), - ["custom_field_lookup", "2"], + ["custom_field_query", "2"], "integer", ) def test_invalid_logical_operator(self): self._assert_validation_error( json.dumps(["invalid op", ["integer_field", "gt", 0]]), - ["custom_field_lookup", "0"], + ["custom_field_query", "0"], "Invalid logical operator", ) def test_invalid_expr_list(self): self._assert_validation_error( json.dumps(["AND", "not a list"]), - ["custom_field_lookup", "1"], + ["custom_field_query", "1"], "Invalid expression list", ) def test_invalid_operator_prefix(self): self._assert_validation_error( json.dumps(["integer_field", "foo__gt", 0]), - ["custom_field_lookup", "1"], - "does not support lookup expr", - ) - - @pytest.mark.skipif( - string_expr_opted_in("regex"), - reason="user opted into allowing regex expr", - ) - def test_disabled_operator(self): - self._assert_validation_error( - json.dumps(["string_field", "regex", r"^p.+s$"]), - ["custom_field_lookup", "1"], - "disabled by default", + ["custom_field_query", "1"], + "does not support query expr", ) def test_query_too_deep(self): @@ -656,7 +548,7 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase): query = ["NOT", query] self._assert_validation_error( json.dumps(query), - ["custom_field_lookup", *(["1"] * 10)], + ["custom_field_query", *(["1"] * 10)], "Maximum nesting depth exceeded", ) @@ -665,6 +557,6 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase): query = ["AND", [atom for _ in range(21)]] self._assert_validation_error( json.dumps(query), - ["custom_field_lookup", "1", "20"], + ["custom_field_query", "1", "20"], "Maximum number of query conditions exceeded", ) diff --git a/src/paperless/settings.py b/src/paperless/settings.py index 023e826c9..ab943f30f 100644 --- a/src/paperless/settings.py +++ b/src/paperless/settings.py @@ -1195,20 +1195,3 @@ EMAIL_ENABLE_GPG_DECRYPTOR: Final[bool] = __get_boolean( # Soft Delete # ############################################################################### EMPTY_TRASH_DELAY = max(__get_int("PAPERLESS_EMPTY_TRASH_DELAY", 30), 1) - -############################################################################### -# custom_field_lookup Filter Settings # -############################################################################### - -CUSTOM_FIELD_LOOKUP_OPT_IN = __get_list( - "PAPERLESS_CUSTOM_FIELD_LOOKUP_OPT_IN", - default=[], -) -CUSTOM_FIELD_LOOKUP_MAX_DEPTH = __get_int( - "PAPERLESS_CUSTOM_FIELD_LOOKUP_MAX_DEPTH", - default=10, -) -CUSTOM_FIELD_LOOKUP_MAX_ATOMS = __get_int( - "PAPERLESS_CUSTOM_FIELD_LOOKUP_MAX_ATOMS", - default=20, -) From b3487f1843a9af5b4d6e5c2747364dbabc8ed03a Mon Sep 17 00:00:00 2001 From: Martin Richtarsky Date: Thu, 3 Oct 2024 05:21:35 +0200 Subject: [PATCH 10/11] Enhancement: check for mail destination directory, log post-consume errors (#7808) --------- Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com> --- src/paperless_mail/mail.py | 24 +++++-- src/paperless_mail/tests/test_mail.py | 92 +++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 6 deletions(-) diff --git a/src/paperless_mail/mail.py b/src/paperless_mail/mail.py index 84f97b742..77d293ea0 100644 --- a/src/paperless_mail/mail.py +++ b/src/paperless_mail/mail.py @@ -28,6 +28,7 @@ from imap_tools import MailboxFolderSelectError from imap_tools import MailBoxUnencrypted from imap_tools import MailMessage from imap_tools import MailMessageFlags +from imap_tools import errors from imap_tools.mailbox import MailBoxTls from imap_tools.query import LogicOperator @@ -266,7 +267,14 @@ def apply_mail_action( M.folder.set(rule.folder) action = get_rule_action(rule, supports_gmail_labels) - action.post_consume(M, message_uid, rule.action_parameter) + try: + action.post_consume(M, message_uid, rule.action_parameter) + except errors.ImapToolsError: + logger = logging.getLogger("paperless_mail") + logger.exception( + "Error while processing mail action during post_consume", + ) + raise ProcessedMail.objects.create( owner=rule.owner, @@ -570,13 +578,17 @@ class MailAccountHandler(LoggingMixin): rule: MailRule, supports_gmail_labels: bool, ): - self.log.debug(f"Rule {rule}: Selecting folder {rule.folder}") - + folders = [rule.folder] + # In case of MOVE, make sure also the destination exists + if rule.action == MailRule.MailAction.MOVE: + folders.insert(0, rule.action_parameter) try: - M.folder.set(rule.folder) + for folder in folders: + self.log.debug(f"Rule {rule}: Selecting folder {folder}") + M.folder.set(folder) except MailboxFolderSelectError as err: self.log.error( - f"Unable to access folder {rule.folder}, attempting folder listing", + f"Unable to access folder {folder}, attempting folder listing", ) try: for folder_info in M.folder.list(): @@ -588,7 +600,7 @@ class MailAccountHandler(LoggingMixin): ) raise MailError( - f"Rule {rule}: Folder {rule.folder} " + f"Rule {rule}: Folder {folder} " f"does not exist in account {rule.account}", ) from err diff --git a/src/paperless_mail/tests/test_mail.py b/src/paperless_mail/tests/test_mail.py index 9078335a6..b1e3ff06e 100644 --- a/src/paperless_mail/tests/test_mail.py +++ b/src/paperless_mail/tests/test_mail.py @@ -10,6 +10,7 @@ import pytest from django.core.management import call_command from django.db import DatabaseError from django.test import TestCase +from django.utils import timezone from imap_tools import NOT from imap_tools import EmailAddress from imap_tools import FolderInfo @@ -17,6 +18,7 @@ from imap_tools import MailboxFolderSelectError from imap_tools import MailboxLoginError from imap_tools import MailMessage from imap_tools import MailMessageFlags +from imap_tools import errors from documents.models import Correspondent from documents.tests.utils import DirectoriesMixin @@ -28,6 +30,7 @@ from paperless_mail.mail import TagMailAction from paperless_mail.mail import apply_mail_action from paperless_mail.models import MailAccount from paperless_mail.models import MailRule +from paperless_mail.models import ProcessedMail @dataclasses.dataclass @@ -1424,6 +1427,95 @@ class TestMail( ) # still 2 +class TestPostConsumeAction(TestCase): + def setUp(self): + self.account = MailAccount.objects.create( + name="test", + imap_server="imap.test.com", + imap_port=993, + imap_security=MailAccount.ImapSecurity.SSL, + username="testuser", + password="password", + ) + self.rule = MailRule.objects.create( + name="testrule", + account=self.account, + action=MailRule.MailAction.MARK_READ, + action_parameter="", + folder="INBOX", + ) + self.message_uid = "12345" + self.message_subject = "Test Subject" + self.message_date = timezone.make_aware(timezone.datetime(2023, 1, 1, 12, 0, 0)) + + @mock.patch("paperless_mail.mail.get_mailbox") + @mock.patch("paperless_mail.mail.mailbox_login") + @mock.patch("paperless_mail.mail.get_rule_action") + def test_post_consume_success( + self, + mock_get_rule_action, + mock_mailbox_login, + mock_get_mailbox, + ): + mock_mailbox = mock.MagicMock() + mock_get_mailbox.return_value.__enter__.return_value = mock_mailbox + mock_action = mock.MagicMock() + mock_get_rule_action.return_value = mock_action + + apply_mail_action( + result=[], + rule_id=self.rule.pk, + message_uid=self.message_uid, + message_subject=self.message_subject, + message_date=self.message_date, + ) + + mock_mailbox_login.assert_called_once_with(mock_mailbox, self.account) + mock_mailbox.folder.set.assert_called_once_with(self.rule.folder) + mock_action.post_consume.assert_called_once_with( + mock_mailbox, + self.message_uid, + self.rule.action_parameter, + ) + + processed_mail = ProcessedMail.objects.get(uid=self.message_uid) + self.assertEqual(processed_mail.status, "SUCCESS") + + @mock.patch("paperless_mail.mail.get_mailbox") + @mock.patch("paperless_mail.mail.mailbox_login") + @mock.patch("paperless_mail.mail.get_rule_action") + def test_post_consume_failure( + self, + mock_get_rule_action, + mock_mailbox_login, + mock_get_mailbox, + ): + mock_mailbox = mock.MagicMock() + mock_get_mailbox.return_value.__enter__.return_value = mock_mailbox + mock_action = mock.MagicMock() + mock_get_rule_action.return_value = mock_action + mock_action.post_consume.side_effect = errors.ImapToolsError("Test Exception") + + with ( + self.assertRaises(errors.ImapToolsError), + self.assertLogs("paperless.mail", level="ERROR") as cm, + ): + apply_mail_action( + result=[], + rule_id=self.rule.pk, + message_uid=self.message_uid, + message_subject=self.message_subject, + message_date=self.message_date, + ) + error_str = cm.output[0] + expected_str = "Error while processing mail action during post_consume" + self.assertIn(expected_str, error_str) + + processed_mail = ProcessedMail.objects.get(uid=self.message_uid) + self.assertEqual(processed_mail.status, "FAILED") + self.assertIn("Test Exception", processed_mail.error) + + class TestManagementCommand(TestCase): @mock.patch( "paperless_mail.management.commands.mail_fetcher.tasks.process_mail_accounts", From fc683e150a30a243be8042dbb6d24ad219634bf7 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 2 Oct 2024 20:21:47 -0700 Subject: [PATCH 11/11] Add missing interface to resolve test warning --- .../custom-fields-query-dropdown.component.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.ts b/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.ts index 923907158..011ae1bc1 100644 --- a/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.ts +++ b/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.ts @@ -2,6 +2,7 @@ import { Component, EventEmitter, Input, + OnDestroy, Output, ViewChild, } from '@angular/core' @@ -148,7 +149,7 @@ export class CustomFieldQueriesModel { templateUrl: './custom-fields-query-dropdown.component.html', styleUrls: ['./custom-fields-query-dropdown.component.scss'], }) -export class CustomFieldsQueryDropdownComponent { +export class CustomFieldsQueryDropdownComponent implements OnDestroy { public CustomFieldQueryComponentType = CustomFieldQueryElementType public CustomFieldQueryOperator = CustomFieldQueryOperator public CustomFieldDataType = CustomFieldDataType