diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1e03d5a34..0c1073709 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,8 +4,11 @@ on: push: branches: - 'main' - tags: - - '*' + - 'v**' + pull_request: + branches: + - 'main' + - 'v**' concurrency: group: ${{ github.workflow }}-${{ github.event.number || github.sha }} @@ -18,7 +21,7 @@ jobs: steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - - run: npm install -g pnpm@9.1.4 + - run: npm install -g pnpm@9.7.0 - run: corepack enable - name: Setup Node @@ -40,7 +43,7 @@ jobs: steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - - run: npm install -g pnpm@9.1.4 + - run: npm install -g pnpm@9.7.0 - run: corepack enable - uses: actions/setup-node@v4 # v4 @@ -61,7 +64,7 @@ jobs: steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - - run: npm install -g pnpm@9.1.4 + - run: npm install -g pnpm@9.7.0 - run: corepack enable - uses: actions/setup-node@v4 # v4 @@ -93,7 +96,7 @@ jobs: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - - run: npm install -g pnpm@9.1.4 + - run: npm install -g pnpm@9.7.0 - run: corepack enable - uses: actions/setup-node@v4 # v4 diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml index e70c07cdc..3d7af6e32 100644 --- a/.github/workflows/release-pr.yml +++ b/.github/workflows/release-pr.yml @@ -3,7 +3,8 @@ name: CD Release PR on: pull_request: branches: - - main + - 'main' + - 'v**' permissions: contents: write diff --git a/Dockerfile b/Dockerfile index f77ad3c85..be95ad2ab 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM node:lts-alpine AS build WORKDIR /usr/src/app -RUN npm install --ignore-scripts -g pnpm@9.1.4 +RUN npm install --ignore-scripts -g pnpm@9.7.0 COPY package.json ./ COPY pnpm-lock.yaml ./ diff --git a/angular.json b/angular.json index 7364a54ad..4327f181a 100644 --- a/angular.json +++ b/angular.json @@ -44,9 +44,6 @@ "base": "dist/admin" }, "index": "src/index.html", - "polyfills": [ - "zone.js" - ], "tsConfig": "tsconfig.app.json", "assets": [ "src/favicon.ico", diff --git a/package.json b/package.json index d897daa19..c2d2c534d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "armonik-admin-gui", "version": "0.10.7", - "packageManager": "pnpm@9.1.4", + "packageManager": "pnpm@9.7.0", "scripts": { "ng": "ng", "start": "ng serve", @@ -18,19 +18,19 @@ }, "private": true, "dependencies": { - "@aneoconsultingfr/armonik.api.angular": "^3.18.1", + "@aneoconsultingfr/armonik.api.angular": "^3.19.0", "@angular-material-components/datetime-picker": "^16.0.1", - "@angular/animations": "^18.0.3", - "@angular/cdk": "18.0.4", - "@angular/common": "^18.0.3", - "@angular/compiler": "^18.0.3", - "@angular/core": "^18.0.3", - "@angular/forms": "^18.0.3", - "@angular/material": "18.0.4", - "@angular/material-luxon-adapter": "^18.0.4", - "@angular/platform-browser": "^18.0.3", - "@angular/platform-browser-dynamic": "^18.0.3", - "@angular/router": "^18.0.3", + "@angular/animations": "^18.1.0", + "@angular/cdk": "18.1.0", + "@angular/common": "^18.1.0", + "@angular/compiler": "^18.1.0", + "@angular/core": "^18.1.0", + "@angular/forms": "^18.1.0", + "@angular/material": "18.1.0", + "@angular/material-luxon-adapter": "^18.1.0", + "@angular/platform-browser": "^18.1.0", + "@angular/platform-browser-dynamic": "^18.1.0", + "@angular/router": "^18.1.0", "@ngx-grpc/common": "^3.1.2", "@ngx-grpc/core": "^3.1.2", "@ngx-grpc/grpc-web-client": "^3.1.2", @@ -38,19 +38,18 @@ "jest-junit": "^16.0.0", "luxon": "^3.4.4", "rxjs": "~7.8.1", - "tslib": "^2.6.2", - "zone.js": "~0.14.4" + "tslib": "^2.6.2" }, "devDependencies": { - "@angular-eslint/builder": "18.0.1", - "@angular-eslint/eslint-plugin": "18.0.1", - "@angular-eslint/eslint-plugin-template": "18.0.1", - "@angular-eslint/schematics": "18.0.1", - "@angular-eslint/template-parser": "18.0.1", - "@angular/build": "^18.0.4", - "@angular/cli": "~18.0.4", - "@angular/compiler-cli": "^18.0.3", - "@angular/localize": "18.0.3", + "@angular-eslint/builder": "18.1.0", + "@angular-eslint/eslint-plugin": "18.1.0", + "@angular-eslint/eslint-plugin-template": "18.1.0", + "@angular-eslint/schematics": "18.1.0", + "@angular-eslint/template-parser": "18.1.0", + "@angular/build": "^18.1.0", + "@angular/cli": "~18.1.0", + "@angular/compiler-cli": "^18.1.0", + "@angular/localize": "18.1.0", "@types/google-protobuf": "^3.15.12", "@types/jest": "^29.5.12", "@types/luxon": "^3.4.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b2c64c538..675237940 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,53 +9,53 @@ importers: .: dependencies: '@aneoconsultingfr/armonik.api.angular': - specifier: ^3.18.1 - version: 3.18.1(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@ngx-grpc/common@3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1))(@ngx-grpc/core@3.1.2(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@ngx-grpc/common@3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1))(rxjs@7.8.1))(@ngx-grpc/well-known-types@3.1.2(@ngx-grpc/common@3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1))(google-protobuf@3.21.2))(google-protobuf@3.21.2)(rxjs@7.8.1) + specifier: ^3.19.0 + version: 3.19.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@ngx-grpc/common@3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1))(@ngx-grpc/core@3.1.2(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@ngx-grpc/common@3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1))(rxjs@7.8.1))(@ngx-grpc/well-known-types@3.1.2(@ngx-grpc/common@3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1))(google-protobuf@3.21.2))(google-protobuf@3.21.2)(rxjs@7.8.1) '@angular-material-components/datetime-picker': specifier: ^16.0.1 - version: 16.0.1(@angular/cdk@18.0.4(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/forms@18.0.3(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1))(@angular/material@18.0.4(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/cdk@18.0.4(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/forms@18.0.3(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1))(@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1))(@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))) + version: 16.0.1(@angular/cdk@18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/forms@18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1))(@angular/material@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/cdk@18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/forms@18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1))(@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1))(@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))) '@angular/animations': - specifier: ^18.0.3 - version: 18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)) + specifier: ^18.1.0 + version: 18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)) '@angular/cdk': - specifier: 18.0.4 - version: 18.0.4(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1) + specifier: 18.1.0 + version: 18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1) '@angular/common': - specifier: ^18.0.3 - version: 18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1) + specifier: ^18.1.0 + version: 18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1) '@angular/compiler': - specifier: ^18.0.3 - version: 18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)) + specifier: ^18.1.0 + version: 18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)) '@angular/core': - specifier: ^18.0.3 - version: 18.0.3(rxjs@7.8.1)(zone.js@0.14.4) + specifier: ^18.1.0 + version: 18.1.0(rxjs@7.8.1)(zone.js@0.14.4) '@angular/forms': - specifier: ^18.0.3 - version: 18.0.3(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1) + specifier: ^18.1.0 + version: 18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1) '@angular/material': - specifier: 18.0.4 - version: 18.0.4(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/cdk@18.0.4(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/forms@18.0.3(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1))(@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1) + specifier: 18.1.0 + version: 18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/cdk@18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/forms@18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1))(@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1) '@angular/material-luxon-adapter': - specifier: ^18.0.4 - version: 18.0.4(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/material@18.0.4(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/cdk@18.0.4(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/forms@18.0.3(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1))(@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1))(luxon@3.4.4) + specifier: ^18.1.0 + version: 18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/material@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/cdk@18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/forms@18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1))(@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1))(luxon@3.4.4) '@angular/platform-browser': - specifier: ^18.0.3 - version: 18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)) + specifier: ^18.1.0 + version: 18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)) '@angular/platform-browser-dynamic': - specifier: ^18.0.3 - version: 18.0.3(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))) + specifier: ^18.1.0 + version: 18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))) '@angular/router': - specifier: ^18.0.3 - version: 18.0.3(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1) + specifier: ^18.1.0 + version: 18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1) '@ngx-grpc/common': specifier: ^3.1.2 version: 3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1) '@ngx-grpc/core': specifier: ^3.1.2 - version: 3.1.2(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@ngx-grpc/common@3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1))(rxjs@7.8.1) + version: 3.1.2(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@ngx-grpc/common@3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1))(rxjs@7.8.1) '@ngx-grpc/grpc-web-client': specifier: ^3.1.2 - version: 3.1.2(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@ngx-grpc/common@3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1))(@ngx-grpc/core@3.1.2(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@ngx-grpc/common@3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1))(rxjs@7.8.1))(grpc-web@1.4.2)(rxjs@7.8.1) + version: 3.1.2(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@ngx-grpc/common@3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1))(@ngx-grpc/core@3.1.2(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@ngx-grpc/common@3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1))(rxjs@7.8.1))(grpc-web@1.4.2)(rxjs@7.8.1) '@ungap/structured-clone': specifier: ^1.2.0 version: 1.2.0 @@ -71,37 +71,34 @@ importers: tslib: specifier: ^2.6.2 version: 2.6.2 - zone.js: - specifier: ~0.14.4 - version: 0.14.4 devDependencies: '@angular-eslint/builder': - specifier: 18.0.1 - version: 18.0.1(eslint@8.57.0)(typescript@5.4.5) + specifier: 18.1.0 + version: 18.1.0(eslint@8.57.0)(typescript@5.4.5) '@angular-eslint/eslint-plugin': - specifier: 18.0.1 - version: 18.0.1(@typescript-eslint/utils@7.11.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) + specifier: 18.1.0 + version: 18.1.0(@typescript-eslint/utils@7.11.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) '@angular-eslint/eslint-plugin-template': - specifier: 18.0.1 - version: 18.0.1(@typescript-eslint/utils@7.11.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) + specifier: 18.1.0 + version: 18.1.0(@typescript-eslint/utils@7.11.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) '@angular-eslint/schematics': - specifier: 18.0.1 - version: 18.0.1(@angular-devkit/core@18.0.4(chokidar@3.6.0))(@angular-devkit/schematics@18.0.4(chokidar@3.6.0))(@typescript-eslint/utils@7.11.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) + specifier: 18.1.0 + version: 18.1.0(@angular-devkit/core@18.1.0(chokidar@3.6.0))(@angular-devkit/schematics@18.1.0(chokidar@3.6.0))(@typescript-eslint/utils@7.11.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) '@angular-eslint/template-parser': - specifier: 18.0.1 - version: 18.0.1(eslint@8.57.0)(typescript@5.4.5) + specifier: 18.1.0 + version: 18.1.0(eslint@8.57.0)(typescript@5.4.5) '@angular/build': - specifier: ^18.0.4 - version: 18.0.4(@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/localize@18.0.3(@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))))(@types/node@20.12.7)(chokidar@3.6.0)(less@4.2.0)(postcss@8.4.38)(terser@5.31.0)(typescript@5.4.5) + specifier: ^18.1.0 + version: 18.1.0(@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/localize@18.1.0(@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))))(@types/node@20.12.7)(chokidar@3.6.0)(less@4.2.0)(postcss@8.4.38)(terser@5.31.0)(typescript@5.4.5) '@angular/cli': - specifier: ~18.0.4 - version: 18.0.4(chokidar@3.6.0) + specifier: ~18.1.0 + version: 18.1.0(chokidar@3.6.0) '@angular/compiler-cli': - specifier: ^18.0.3 - version: 18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5) + specifier: ^18.1.0 + version: 18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5) '@angular/localize': - specifier: 18.0.3 - version: 18.0.3(@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))) + specifier: 18.1.0 + version: 18.1.0(@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))) '@types/google-protobuf': specifier: ^3.15.12 version: 3.15.12 @@ -143,13 +140,13 @@ importers: version: 29.7.0 jest-preset-angular: specifier: ^14.1.0 - version: 14.1.0(@angular-devkit/build-angular@18.0.2(@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/localize@18.0.3(@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))))(@types/node@20.12.7)(chokidar@3.6.0)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)))(typescript@5.4.5))(@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser-dynamic@18.0.3(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))))(@babel/core@7.24.5)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.5))(jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)))(typescript@5.4.5) + version: 14.1.0(@angular-devkit/build-angular@18.0.2(@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/localize@18.1.0(@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))))(@types/node@20.12.7)(chokidar@3.6.0)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)))(typescript@5.4.5))(@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser-dynamic@18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))))(@babel/core@7.24.7)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)))(typescript@5.4.5) ng-extract-i18n-merge: specifier: 2.12.0 - version: 2.12.0(@angular-devkit/build-angular@18.0.2(@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/localize@18.0.3(@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))))(@types/node@20.12.7)(chokidar@3.6.0)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)))(typescript@5.4.5))(chokidar@3.6.0) + version: 2.12.0(@angular-devkit/build-angular@18.0.2(@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/localize@18.1.0(@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))))(@types/node@20.12.7)(chokidar@3.6.0)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)))(typescript@5.4.5))(chokidar@3.6.0) ts-jest: specifier: ^29.1.2 - version: 29.1.2(@babel/core@7.24.5)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.5))(esbuild@0.20.1)(jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)))(typescript@5.4.5) + version: 29.1.2(@babel/core@7.24.7)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(esbuild@0.20.1)(jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)))(typescript@5.4.5) ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@20.12.7)(typescript@5.4.5) @@ -167,8 +164,8 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - '@aneoconsultingfr/armonik.api.angular@3.18.1': - resolution: {integrity: sha512-9OJ4JhLwLPx1kMI+60cYa30g3S4SvNMZyECPBaTPhRXRs5QRViDF3ZHecooFTs5tHm1j6wwAFRSQlQnLx3YANQ==} + '@aneoconsultingfr/armonik.api.angular@3.19.0': + resolution: {integrity: sha512-2xuWp6u1GgJ+U6X0wR92yEtljXq4euSuo9cIMA3fYX5gSmCsl5ad+3pgsy0Hb2AuvDQWSzwNvo/82+DAQrPcqQ==} peerDependencies: '@angular/common': ^16.2.1 '@angular/core': ^16.2.1 @@ -186,8 +183,8 @@ packages: resolution: {integrity: sha512-PX7lCTAqWe9C40+fie+DAc8vhpGA+JgZKWWrMHUTV/iZx8RXx2X4xGQsqYu36p4i3MSfQdbn+0xLWGmjScPVOQ==} 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-devkit/architect@0.1800.4': - resolution: {integrity: sha512-82TKhYnSO8aGIBo5TxPtyUQnZFcbV+qB2bIIYOAKsJgxAVxLeFD6QA6gTmHOZPXw5pBEPUO/+PUwq+Uk5xesgw==} + '@angular-devkit/architect@0.1801.0': + resolution: {integrity: sha512-iZa3J3CrZT6MKiHPw8ijgVwMyCMewCsP4xc75SetUwF/yuqRUHygALs5jJVZQFQjSFUrkg9gqXa1cCjFDwpT8A==} 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-devkit/build-angular@18.0.2': @@ -265,8 +262,8 @@ packages: chokidar: optional: true - '@angular-devkit/core@18.0.4': - resolution: {integrity: sha512-8vYvJ5FF2NjFUia00hv8KWakOjOZ+09PbnNqd+lntJBekIg1lHDOF/vNMlVHtU5LiE1aNi9P/69/VXTckPfU9g==} + '@angular-devkit/core@18.1.0': + resolution: {integrity: sha512-6eXQDzHZCbpSMLv9Ohl+1QyLVDmGEXpuuHz3y64LfUTP0aEiBaxk96FjLXIxzJ4f2pbbW2XHzc+yuboGToRA0w==} 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: chokidar: ^3.5.2 @@ -278,49 +275,49 @@ packages: resolution: {integrity: sha512-QRVEYpIfgkprNHc916JlPuNbLzOgrm9DZalHasnLUz4P6g7pR21olb8YCyM2OTJjombNhya9ZpckcADU5Qyvlg==} engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} - '@angular-devkit/schematics@18.0.4': - resolution: {integrity: sha512-hCHmuu/Z1teOQPx1AMJa/gcK6depk+XgU5dIpEvflC+ApW3hglNe2QKaqajDZ+34s+PKAVWa86M8IOV7o/mHuA==} + '@angular-devkit/schematics@18.1.0': + resolution: {integrity: sha512-BjrYutLfYFiPOSEcLBWCj3ENkwDn8gMfBSJesaBz7OrZBZGK5j0dVgBLIsGTP96TKo4o4vszJQOvS4AtV6xMGg==} 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/builder@18.0.1': - resolution: {integrity: sha512-b/VUeTQznAmGdwP4OyPWyegqSRWub7E8/WXBqojrSFyLkFhpTiHpk/3/5G3LsgTb0zBfyAsqkA0yaadsHu9pjA==} + '@angular-eslint/builder@18.1.0': + resolution: {integrity: sha512-ttcp+M3XDqt4lpj4C9XWO/JJY5iC1kNWmK1FUtd1YR0+1mLhrZTbakFYFe06qoiRqb4MX0WW2oUvonh52y5/NQ==} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '*' - '@angular-eslint/bundled-angular-compiler@18.0.1': - resolution: {integrity: sha512-lr4Ysoo28FBOKcJFQUGTMpbWDcak+gyuYvyggp37ERvazE6EDomPFxzEHNqVT9EI9sZ+GDBOoPR+EdFh0ALGNw==} + '@angular-eslint/bundled-angular-compiler@18.1.0': + resolution: {integrity: sha512-2JNlMEnCvLz8q1Qa4sWR9BddtpDWMKYguMzHJKm5zUDwH90CgWHolQlXumtpqbL8r78xd57t35IkbEFLF3UsQw==} - '@angular-eslint/eslint-plugin-template@18.0.1': - resolution: {integrity: sha512-u/eov/CFBb8l35D8dW78Dx5fBLd8FZFibKN9XQknhzXnDMpISuUOMny5g5/wvYYjqLgqEySXMiHKEAxEup7xtA==} + '@angular-eslint/eslint-plugin-template@18.1.0': + resolution: {integrity: sha512-k7Zq2JRd4jjg6PB0M24UnnmdhCeRFQ7Q4GlMGmeJLQGan+HFKDBu973yN2/Vmk4RYi+rTVuin0gy4HBeiGiiaw==} peerDependencies: - '@typescript-eslint/utils': ^7.11.0 || ^8.0.0-alpha.20 + '@typescript-eslint/utils': ^7.11.0 || ^8.0.0-alpha.37 eslint: ^8.57.0 || ^9.0.0 typescript: '*' - '@angular-eslint/eslint-plugin@18.0.1': - resolution: {integrity: sha512-pS3SYLa9DA+ENklGxEUlcw6/xCxgDk9fgjyaheuSjDxL3TIh1pTa4V2TptODdcPh7XCYXiVmy+e/w79mXlGzOw==} + '@angular-eslint/eslint-plugin@18.1.0': + resolution: {integrity: sha512-rV1RLhcg9TTNE5hB7pMddkJvnH0+q3FnhhWVE+IJNkzlGxEktDwVx7hG17sy8YkRS2CxR0P6Dr5C6wMBdEwAsw==} peerDependencies: - '@typescript-eslint/utils': ^7.11.0 || ^8.0.0-alpha.20 + '@typescript-eslint/utils': ^7.11.0 || ^8.0.0-alpha.37 eslint: ^8.57.0 || ^9.0.0 typescript: '*' - '@angular-eslint/schematics@18.0.1': - resolution: {integrity: sha512-G9PgFrjyvBaQR8enMnP2scnQDLk99GMpifh3voiOmdEkxaQHRWqhCWncV7GATwpXDzeyj9J9XT9iHGJjnZTpJQ==} + '@angular-eslint/schematics@18.1.0': + resolution: {integrity: sha512-wZll/9/RSER1Vl6m9fXA/866OAUz2DSWYufvHEpJUoDPug/uZ+l9jOMZwlSk4PeMrF+/fNXoWx5HK2ZEwTv2qw==} peerDependencies: '@angular-devkit/core': '>= 18.0.0 < 19.0.0' '@angular-devkit/schematics': '>= 18.0.0 < 19.0.0' - '@angular-eslint/template-parser@18.0.1': - resolution: {integrity: sha512-22fKzkWo9Ts8aY/WHL1A6seS2tpltgRRXVfnZnnqvQRyRiuPnx1FC0ly7+QPZkThh8vdLwxU+BvtLq9Uiqh9OQ==} + '@angular-eslint/template-parser@18.1.0': + resolution: {integrity: sha512-YqBNusbt3vWbm8eo2dICytU8hP8/ez4uETkwKpMvB+H1E0rYaD2F17D47YO9BBFUHCNzAGIBlA8BWDN1kLEMlw==} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '*' - '@angular-eslint/utils@18.0.1': - resolution: {integrity: sha512-Q9lCySqg+9h2cz08+SoWj48cY1i04tL1k3bsQJmF2TsylAw2mSsNGX2X3h9WkdxY7sUoY0mP7MVW1iU54Gobcg==} + '@angular-eslint/utils@18.1.0': + resolution: {integrity: sha512-pTCwbm9TPU1B0fxwhJg5qnJA2ILUJR0cT+rc7kejV0Xwl6RBXpMrzbuMzB9CucEY1au8hAR55I+Sc9znwSwuIw==} peerDependencies: - '@typescript-eslint/utils': ^7.11.0 || ^8.0.0-alpha.20 + '@typescript-eslint/utils': ^7.11.0 || ^8.0.0-alpha.37 eslint: ^8.57.0 || ^9.0.0 typescript: '*' @@ -334,11 +331,11 @@ packages: '@angular/material': ^16.0.0 '@angular/platform-browser': ^16.0.0 - '@angular/animations@18.0.3': - resolution: {integrity: sha512-Wlll6y7euIXYsOHpTh0hvVTBs7lVnbKDHiyd4Dz7kAMSeE2zyQo6OcRN+FFH3GH9BUi5UooAICNX8dJDfps6Mw==} + '@angular/animations@18.1.0': + resolution: {integrity: sha512-K0BhvZ/SIVoGXZVuh1KOJDdgcGlHfFGMGrs58utndndAb+gYXReMfz4GR5cQs2OObH6TKmIOY2EH7Og1CY2tsw==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0} peerDependencies: - '@angular/core': 18.0.3 + '@angular/core': 18.1.0 '@angular/build@18.0.2': resolution: {integrity: sha512-iPPHdAJ3LiR8t/+39xjvrqMWcTmRrfphzKxXoIVDcswQjVQIk00EYuxinC6EVa7dSKDl1thk1MeCNZ9DIjaAvQ==} @@ -366,8 +363,8 @@ packages: tailwindcss: optional: true - '@angular/build@18.0.4': - resolution: {integrity: sha512-70HQQnbCOXFT5F3ROyWNNfS9A63Fzts5ANJKJY1MJLrn+dgNEG7jdIWjTtvohL3RZz97rlzSq3qRZnfxqf1lsQ==} + '@angular/build@18.1.0': + resolution: {integrity: sha512-4yLrGqMDoNBis2Z4s8F3wSqlB2XLtwy/10tREBk9xVaCojERiwDvtHqzbMeHqD6ZMGDFtdhI12q8FT5jZVUmAw==} 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 @@ -377,7 +374,7 @@ packages: less: ^4.2.0 postcss: ^8.4.0 tailwindcss: ^2.0.0 || ^3.0.0 - typescript: '>=5.4 <5.5' + typescript: '>=5.4 <5.6' peerDependenciesMeta: '@angular/localize': optional: true @@ -392,125 +389,117 @@ packages: tailwindcss: optional: true - '@angular/cdk@18.0.4': - resolution: {integrity: sha512-OCG1EGv/nyZYGcSu7y6IAuarC5gZcZYhhvEQsgMUDrf1TGRSa+0dBN5W2HxRWKs6NsGgDjW1VcK+AC85PYLXPA==} + '@angular/cdk@18.1.0': + resolution: {integrity: sha512-GWUyJQ7KdOS0WwQPp7UKnRd7lUKvOrMvktqNWfSxBy/VEdyEeKlXfghk6GQ4u1RXFZ2RU0m1KhX8rY9srYJOwA==} peerDependencies: '@angular/common': ^18.0.0 || ^19.0.0 '@angular/core': ^18.0.0 || ^19.0.0 rxjs: ^6.5.3 || ^7.4.0 - '@angular/cli@18.0.4': - resolution: {integrity: sha512-i7DLVIc4HN0CFZZKbEeVeQSADRG1Dt2CwXh/wTUzglRLu/tE7Q+WMrqJ2+lGTT2edZp2KKysM4Gxp+ATAzP8AQ==} + '@angular/cli@18.1.0': + resolution: {integrity: sha512-2E+b7S/736AOmxf5je9OWoPpgPY240TfJfFXwQiVvq/4KyC+ZR9lBrqRx72Xghn8nu3z8Q2BPZIXVGZppl0USQ==} 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'} hasBin: true - '@angular/common@18.0.3': - resolution: {integrity: sha512-lmT9QbWHduqzpsB0osQFHeSwvQB1iUeNwTVUyMtcs6i46l4qOPtAt2/9DvHUWEUp01EBDxyi385ZI3vD+FHH/w==} + '@angular/common@18.1.0': + resolution: {integrity: sha512-noHDLarQSCZZh7hyNd0HR61Fut+q4QCVq9qc/jKPglfbV/6nPujQSmSpT+rNJlNuBOrCLuvH/CNBNbiqii+x3g==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0} peerDependencies: - '@angular/core': 18.0.3 + '@angular/core': 18.1.0 rxjs: ^6.5.3 || ^7.4.0 - '@angular/compiler-cli@18.0.3': - resolution: {integrity: sha512-mxwQEeP94YBM6C9A2YfkV7ug1sHgh0fU/TSBpQcm5ni4cZiVPu6q/+Ft7hyFTKe2p3tKQme33+xVjsWhtOCx0A==} + '@angular/compiler-cli@18.1.0': + resolution: {integrity: sha512-BBsogLPJwxkPh7f8RVHsxyyqNE8XpHbAanjB5fAwnU4W6Sw1kR5rFzkeZM3xaRm2MDiC8DovIl6hlf+s/mKYOw==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0} hasBin: true peerDependencies: - '@angular/compiler': 18.0.3 - typescript: '>=5.4 <5.5' + '@angular/compiler': 18.1.0 + typescript: '>=5.4 <5.6' - '@angular/compiler@18.0.3': - resolution: {integrity: sha512-wrXxgBsZX4yTrj/oZ8PDGmvhqj9S2TZfcuivaUitprNC2uBWTVb1UcOS45Qw9YlLB0sYa2AmBudICDqYpb8lfw==} + '@angular/compiler@18.1.0': + resolution: {integrity: sha512-JRQzVTeJGSfRLY+dx+gwu/hPQVB8K+5pW12Z42M9x/HBgGW4in0cO2zHkeQPvImqm0nak82Us1Hyf5C+qTlMMQ==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0} peerDependencies: - '@angular/core': 18.0.3 + '@angular/core': 18.1.0 peerDependenciesMeta: '@angular/core': optional: true - '@angular/core@18.0.3': - resolution: {integrity: sha512-376hijhEqNpeA+qKncpVTIaZXRdBT6RctEBnFhJ2l57aHPH5S3oaSBQu1k3TEi07FlKOD4XF1+NzX9dvdup1eg==} + '@angular/core@18.1.0': + resolution: {integrity: sha512-/57/s7CD/0CwlN+3FlhVmx7ypCWXjKi5UKtnlBAUg0D1denIf6ADxwTHFZABYZcYBqOTJgeQUtUw9u/A+0CIlg==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0} peerDependencies: rxjs: ^6.5.3 || ^7.4.0 zone.js: ~0.14.0 - '@angular/forms@18.0.3': - resolution: {integrity: sha512-+CjDiooUi5FkTP3YQmdO8YRbjZicgLGZonvCdz3mSucLrTY6w3oBocNs6+Kc7fLuO1NKSkFmAfYApBwK3fKBMg==} + '@angular/forms@18.1.0': + resolution: {integrity: sha512-m+7m9wa+n5dEacd458eSZsZTz0B+HbOtr7/uqM0YTMQaPrhwl1epG5Y103mB6yr00JiJcLNlPLjP888cHFjldQ==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0} peerDependencies: - '@angular/common': 18.0.3 - '@angular/core': 18.0.3 - '@angular/platform-browser': 18.0.3 + '@angular/common': 18.1.0 + '@angular/core': 18.1.0 + '@angular/platform-browser': 18.1.0 rxjs: ^6.5.3 || ^7.4.0 - '@angular/localize@18.0.3': - resolution: {integrity: sha512-IrqLmuJncctJsgj5Z3uO68vuQBxdLdEB2B9p0SfX/oiWg2wYScKirePS5chsj3ZciwVWH5wc06cEzlZ+bSgNSA==} + '@angular/localize@18.1.0': + resolution: {integrity: sha512-84D06p2Th5NxoJZzsSIn4FkTJGImj7rtNnvyTrHvHdomzzUKwiBOXDB2FiCLDstND0DsCtgjD/uBJivg77z9tg==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0} hasBin: true peerDependencies: - '@angular/compiler': 18.0.3 - '@angular/compiler-cli': 18.0.3 + '@angular/compiler': 18.1.0 + '@angular/compiler-cli': 18.1.0 - '@angular/material-luxon-adapter@18.0.4': - resolution: {integrity: sha512-K4mvjLgVSm3Tlq8xeRo8RVUHCXwDE0ENAG7WgGJrWq4f4//J+HsGQ8toif/nlbX4VFR6x0OlBrVJMsIlknY21g==} + '@angular/material-luxon-adapter@18.1.0': + resolution: {integrity: sha512-XwyGwuxmCS+P2yzMl/JOvjagoILyQYhApBFtAkR0Kurxvce+rR65c6oX8ORte3n3azhhdtWPvFkOuVfWpObU4g==} peerDependencies: '@angular/core': ^18.0.0 || ^19.0.0 - '@angular/material': 18.0.4 + '@angular/material': 18.1.0 luxon: ^3.0.0 - '@angular/material@18.0.4': - resolution: {integrity: sha512-ES4peq3+tMEPKe9RgdQ3pp3CcjM0Cr+vi4f+0ruH2wu1NTBk522/1/ABHncg3A/eCurKS96JJdihqOAjMek4Ow==} + '@angular/material@18.1.0': + resolution: {integrity: sha512-tL6Qx+E/Q/TaNXec9uuCRNoqK9yjABEraDSbd9WSh2/UYbGlEnE2MaXSha12GKa/l8RRrRYUR7y+v0fIGECBOg==} peerDependencies: '@angular/animations': ^18.0.0 || ^19.0.0 - '@angular/cdk': 18.0.4 + '@angular/cdk': 18.1.0 '@angular/common': ^18.0.0 || ^19.0.0 '@angular/core': ^18.0.0 || ^19.0.0 '@angular/forms': ^18.0.0 || ^19.0.0 '@angular/platform-browser': ^18.0.0 || ^19.0.0 rxjs: ^6.5.3 || ^7.4.0 - '@angular/platform-browser-dynamic@18.0.3': - resolution: {integrity: sha512-+kHMn7P552YKk1gkVQNO1QXzHVaIeFiVa1rV1MNvX4DvumKT3puknx1SzcmtxZTX+9ee22OuPuyLNSAKREDAQQ==} + '@angular/platform-browser-dynamic@18.1.0': + resolution: {integrity: sha512-D/wuOQf+gULld9DVEzn2Lw3WbTyAYf/sp3DC5k83O+DQsG3eAIsVkt0zdE+U3DrDYsiWg8M3X+ioi3ouqK0mNg==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0} peerDependencies: - '@angular/common': 18.0.3 - '@angular/compiler': 18.0.3 - '@angular/core': 18.0.3 - '@angular/platform-browser': 18.0.3 + '@angular/common': 18.1.0 + '@angular/compiler': 18.1.0 + '@angular/core': 18.1.0 + '@angular/platform-browser': 18.1.0 - '@angular/platform-browser@18.0.3': - resolution: {integrity: sha512-1fl/oJOca8BLxLxN0EjwxQZ3xzn3PCCN96ytM54bjdEMiELz+0AcQe5GNKcVjXlwMkibRLl1BP5GIdvnQYqJRA==} + '@angular/platform-browser@18.1.0': + resolution: {integrity: sha512-jCmxthiI4Zef54crckNht60xwfIsuciGeyZvb7SsXna2maLW9fA4uz1VhZqIWTiBnHwNynVlyfBX3/jBD7S9+g==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0} peerDependencies: - '@angular/animations': 18.0.3 - '@angular/common': 18.0.3 - '@angular/core': 18.0.3 + '@angular/animations': 18.1.0 + '@angular/common': 18.1.0 + '@angular/core': 18.1.0 peerDependenciesMeta: '@angular/animations': optional: true - '@angular/router@18.0.3': - resolution: {integrity: sha512-/cglLev0USxUNMc4M+EBFGrqw1EpKq87LUJL3+0Ztr012sVSeOU38ad41fs6pPcMBePBDZIw7KmSXypvUJJFMA==} + '@angular/router@18.1.0': + resolution: {integrity: sha512-dl2cSxZkl4we+rWMxdm123TZzlor6yxwNFI2yT7b6DP2i+rXaaHBSSPet0ASp+UX6djz+Osr56Bifs6wi4rhiQ==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0} peerDependencies: - '@angular/common': 18.0.3 - '@angular/core': 18.0.3 - '@angular/platform-browser': 18.0.3 + '@angular/common': 18.1.0 + '@angular/core': 18.1.0 + '@angular/platform-browser': 18.1.0 rxjs: ^6.5.3 || ^7.4.0 - '@babel/code-frame@7.24.6': - resolution: {integrity: sha512-ZJhac6FkEd1yhG2AHOmfcXG4ceoLltoCVJjN5XsWN9BifBQr+cHJbWi0h68HZuSORq+3WtJ2z0hwF2NG1b5kcA==} - engines: {node: '>=6.9.0'} - '@babel/code-frame@7.24.7': resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.24.6': - resolution: {integrity: sha512-aC2DGhBq5eEdyXWqrDInSqQjO0k8xtPRf5YylULqx8MCd6jBtzqfta/3ETMRpuKIc5hyswfO80ObyA1MvkCcUQ==} - engines: {node: '>=6.9.0'} - '@babel/compat-data@7.24.7': resolution: {integrity: sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==} engines: {node: '>=6.9.0'} @@ -527,10 +516,6 @@ packages: resolution: {integrity: sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==} engines: {node: '>=6.9.0'} - '@babel/generator@7.24.6': - resolution: {integrity: sha512-S7m4eNa6YAPJRHmKsLHIDJhNAGNKoWNiWefz1MBbpnt8g9lvMDl1hir4P9bo/57bQEmuwEhnRU/AMWsD0G/Fbg==} - engines: {node: '>=6.9.0'} - '@babel/generator@7.24.7': resolution: {integrity: sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==} engines: {node: '>=6.9.0'} @@ -539,18 +524,14 @@ packages: resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==} engines: {node: '>=6.9.0'} - '@babel/helper-annotate-as-pure@7.24.6': - resolution: {integrity: sha512-DitEzDfOMnd13kZnDqns1ccmftwJTS9DMkyn9pYTxulS7bZxUxpMly3Nf23QQ6NwA4UB8lAqjbqWtyvElEMAkg==} + '@babel/helper-annotate-as-pure@7.24.7': + resolution: {integrity: sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==} engines: {node: '>=6.9.0'} '@babel/helper-builder-binary-assignment-operator-visitor@7.24.6': resolution: {integrity: sha512-+wnfqc5uHiMYtvRX7qu80Toef8BXeh4HHR1SPeonGb1SKPniNEd4a/nlaJJMv/OIEYvIVavvo0yR7u10Gqz0Iw==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.24.6': - resolution: {integrity: sha512-VZQ57UsDGlX/5fFA7GkVPplZhHsVc+vuErWgdOiysI9Ksnw0Pbbd6pnPiR/mmJyKHgyIW0c7KT32gmhiF+cirg==} - engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.24.7': resolution: {integrity: sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==} engines: {node: '>=6.9.0'} @@ -572,26 +553,14 @@ packages: peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - '@babel/helper-environment-visitor@7.24.6': - resolution: {integrity: sha512-Y50Cg3k0LKLMjxdPjIl40SdJgMB85iXn27Vk/qbHZCFx/o5XO3PSnpi675h1KEmmDb6OFArfd5SCQEQ5Q4H88g==} - engines: {node: '>=6.9.0'} - '@babel/helper-environment-visitor@7.24.7': resolution: {integrity: sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==} engines: {node: '>=6.9.0'} - '@babel/helper-function-name@7.24.6': - resolution: {integrity: sha512-xpeLqeeRkbxhnYimfr2PC+iA0Q7ljX/d1eZ9/inYbmfG2jpl8Lu3DyXvpOAnrS5kxkfOWJjioIMQsaMBXFI05w==} - engines: {node: '>=6.9.0'} - '@babel/helper-function-name@7.24.7': resolution: {integrity: sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==} engines: {node: '>=6.9.0'} - '@babel/helper-hoist-variables@7.24.6': - resolution: {integrity: sha512-SF/EMrC3OD7dSta1bLJIlrsVxwtd0UpjRJqLno6125epQMJ/kyFmpTT4pbvPbdQHzCHg+biQ7Syo8lnDtbR+uA==} - engines: {node: '>=6.9.0'} - '@babel/helper-hoist-variables@7.24.7': resolution: {integrity: sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==} engines: {node: '>=6.9.0'} @@ -600,20 +569,10 @@ packages: resolution: {integrity: sha512-OTsCufZTxDUsv2/eDXanw/mUZHWOxSbEmC3pP8cgjcy5rgeVPWWMStnv274DV60JtHxTk0adT0QrCzC4M9NWGg==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.24.6': - resolution: {integrity: sha512-a26dmxFJBF62rRO9mmpgrfTLsAuyHk4e1hKTUkD/fcMfynt8gvEKwQPQDVxWhca8dHoDck+55DFt42zV0QMw5g==} - engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.24.7': resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.24.6': - resolution: {integrity: sha512-Y/YMPm83mV2HJTbX1Qh2sjgjqcacvOlhbzdCCsSlblOKjSYmQqEbO6rUniWQyRo9ncyfjT8hnUjlG06RXDEmcA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - '@babel/helper-module-transforms@7.24.7': resolution: {integrity: sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==} engines: {node: '>=6.9.0'} @@ -628,6 +587,10 @@ packages: resolution: {integrity: sha512-MZG/JcWfxybKwsA9N9PmtF2lOSFSEMVCpIRrbxccZFLJPrJciJdG/UhSh5W96GEteJI2ARqm5UAHxISwRDLSNg==} engines: {node: '>=6.9.0'} + '@babel/helper-plugin-utils@7.24.7': + resolution: {integrity: sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==} + engines: {node: '>=6.9.0'} + '@babel/helper-remap-async-to-generator@7.24.6': resolution: {integrity: sha512-1Qursq9ArRZPAMOZf/nuzVW8HgJLkTB9y9LfP4lW2MVp4e9WkLJDovfKBxoDcCk6VuzIxyqWHyBoaCtSRP10yg==} engines: {node: '>=6.9.0'} @@ -640,10 +603,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-simple-access@7.24.6': - resolution: {integrity: sha512-nZzcMMD4ZhmB35MOOzQuiGO5RzL6tJbsT37Zx8M5L/i9KSrukGXWTjLe1knIbb/RmxoJE9GON9soq0c0VEMM5g==} - engines: {node: '>=6.9.0'} - '@babel/helper-simple-access@7.24.7': resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==} engines: {node: '>=6.9.0'} @@ -656,34 +615,18 @@ packages: resolution: {integrity: sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==} engines: {node: '>=6.9.0'} - '@babel/helper-split-export-declaration@7.24.6': - resolution: {integrity: sha512-CvLSkwXGWnYlF9+J3iZUvwgAxKiYzK3BWuo+mLzD/MDGOZDj7Gq8+hqaOkMxmJwmlv0iu86uH5fdADd9Hxkymw==} - engines: {node: '>=6.9.0'} - '@babel/helper-split-export-declaration@7.24.7': resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.24.6': - resolution: {integrity: sha512-WdJjwMEkmBicq5T9fm/cHND3+UlFa2Yj8ALLgmoSQAJZysYbBjw+azChSGPN4DSPLXOcooGRvDwZWMcF/mLO2Q==} - engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.24.7': resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.24.6': - resolution: {integrity: sha512-4yA7s865JHaqUdRbnaxarZREuPTHrjpDT+pXoAZ1yhyo6uFnIEpS8VMu16siFOHDpZNKYv5BObhsB//ycbICyw==} - engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.24.7': resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.24.6': - resolution: {integrity: sha512-Jktc8KkF3zIkePb48QO+IapbXlSapOW9S+ogZZkcO6bABgYAxtZcjZ/O005111YLf+j4M84uEgwYoidDkXbCkQ==} - engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.24.7': resolution: {integrity: sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==} engines: {node: '>=6.9.0'} @@ -692,27 +635,14 @@ packages: resolution: {integrity: sha512-f1JLrlw/jbiNfxvdrfBgio/gRBk3yTAEJWirpAkiJG2Hb22E7cEYKHWo0dFPTv/niPovzIdPdEDetrv6tC6gPQ==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.24.6': - resolution: {integrity: sha512-V2PI+NqnyFu1i0GyTd/O/cTpxzQCYioSkUIRmgo7gFEHKKCg5w46+r/A6WeUR1+P3TeQ49dspGPNd/E3n9AnnA==} - engines: {node: '>=6.9.0'} - '@babel/helpers@7.24.7': resolution: {integrity: sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==} engines: {node: '>=6.9.0'} - '@babel/highlight@7.24.6': - resolution: {integrity: sha512-2YnuOp4HAk2BsBrJJvYCbItHx0zWscI1C3zgWkz+wDyD9I7GIVrfnLyrR4Y1VR+7p+chAEcrgRQYZAGIKMV7vQ==} - engines: {node: '>=6.9.0'} - '@babel/highlight@7.24.7': resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} engines: {node: '>=6.9.0'} - '@babel/parser@7.24.6': - resolution: {integrity: sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q==} - engines: {node: '>=6.0.0'} - hasBin: true - '@babel/parser@7.24.7': resolution: {integrity: sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==} engines: {node: '>=6.0.0'} @@ -785,8 +715,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-import-attributes@7.24.6': - resolution: {integrity: sha512-D+CfsVZousPXIdudSII7RGy52+dYRtbyKAZcvtQKq/NpsivyMVduepzcLqG5pMBugtMdedxdC8Ramdpcne9ZWQ==} + '@babel/plugin-syntax-import-attributes@7.24.7': + resolution: {integrity: sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1173,26 +1103,14 @@ packages: resolution: {integrity: sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==} engines: {node: '>=6.9.0'} - '@babel/template@7.24.6': - resolution: {integrity: sha512-3vgazJlLwNXi9jhrR1ef8qiB65L1RK90+lEQwv4OxveHnqC3BfmnHdgySwRLzf6akhlOYenT+b7AfWq+a//AHw==} - engines: {node: '>=6.9.0'} - '@babel/template@7.24.7': resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.24.6': - resolution: {integrity: sha512-OsNjaJwT9Zn8ozxcfoBc+RaHdj3gFmCmYoQLUII1o6ZrUwku0BMg80FoOTPx+Gi6XhcQxAYE4xyjPTo4SxEQqw==} - engines: {node: '>=6.9.0'} - '@babel/traverse@7.24.7': resolution: {integrity: sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==} engines: {node: '>=6.9.0'} - '@babel/types@7.24.6': - resolution: {integrity: sha512-WaMsgi6Q8zMgMth93GvWPXkhAIEobfsIkLTacoVZoK1J0CevIPGYY2Vo5YvJGqyHqXM6P4ppOYGsIRU8MM9pFQ==} - engines: {node: '>=6.9.0'} - '@babel/types@7.24.7': resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==} engines: {node: '>=6.9.0'} @@ -1220,6 +1138,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.20.1': resolution: {integrity: sha512-hCnXNF0HM6AjowP+Zou0ZJMWWa1VkD77BXe959zERgGJBBxB+sV+J9f/rcjeg2c5bsukD/n17RKWXGFCO5dD5A==} engines: {node: '>=12'} @@ -1232,6 +1156,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.20.1': resolution: {integrity: sha512-4j0+G27/2ZXGWR5okcJi7pQYhmkVgb4D7UKwxcqrjhvp5TKWx3cUjgB1CGj1mfdmJBQ9VnUGgUhign+FPF2Zgw==} engines: {node: '>=12'} @@ -1244,6 +1174,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.20.1': resolution: {integrity: sha512-MSfZMBoAsnhpS+2yMFYIQUPs8Z19ajwfuaSZx+tSl09xrHZCjbeXXMsUF/0oq7ojxYEpsSo4c0SfjxOYXRbpaA==} engines: {node: '>=12'} @@ -1256,6 +1192,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.20.1': resolution: {integrity: sha512-Ylk6rzgMD8klUklGPzS414UQLa5NPXZD5tf8JmQU8GQrj6BrFA/Ic9tb2zRe1kOZyCbGl+e8VMbDRazCEBqPvA==} engines: {node: '>=12'} @@ -1268,6 +1210,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.20.1': resolution: {integrity: sha512-pFIfj7U2w5sMp52wTY1XVOdoxw+GDwy9FsK3OFz4BpMAjvZVs0dT1VXs8aQm22nhwoIWUmIRaE+4xow8xfIDZA==} engines: {node: '>=12'} @@ -1280,6 +1228,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.20.1': resolution: {integrity: sha512-UyW1WZvHDuM4xDz0jWun4qtQFauNdXjXOtIy7SYdf7pbxSWWVlqhnR/T2TpX6LX5NI62spt0a3ldIIEkPM6RHw==} engines: {node: '>=12'} @@ -1292,6 +1246,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.20.1': resolution: {integrity: sha512-itPwCw5C+Jh/c624vcDd9kRCCZVpzpQn8dtwoYIt2TJF3S9xJLiRohnnNrKwREvcZYx0n8sCSbvGH349XkcQeg==} engines: {node: '>=12'} @@ -1304,6 +1264,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.20.1': resolution: {integrity: sha512-cX8WdlF6Cnvw/DO9/X7XLH2J6CkBnz7Twjpk56cshk9sjYVcuh4sXQBy5bmTwzBjNVZze2yaV1vtcJS04LbN8w==} engines: {node: '>=12'} @@ -1316,6 +1282,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.20.1': resolution: {integrity: sha512-LojC28v3+IhIbfQ+Vu4Ut5n3wKcgTu6POKIHN9Wpt0HnfgUGlBuyDDQR4jWZUZFyYLiz4RBBBmfU6sNfn6RhLw==} engines: {node: '>=12'} @@ -1328,6 +1300,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.20.1': resolution: {integrity: sha512-4H/sQCy1mnnGkUt/xszaLlYJVTz3W9ep52xEefGtd6yXDQbz/5fZE5dFLUgsPdbUOQANcVUa5iO6g3nyy5BJiw==} engines: {node: '>=12'} @@ -1340,6 +1318,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.20.1': resolution: {integrity: sha512-c0jgtB+sRHCciVXlyjDcWb2FUuzlGVRwGXgI+3WqKOIuoo8AmZAddzeOHeYLtD+dmtHw3B4Xo9wAUdjlfW5yYA==} engines: {node: '>=12'} @@ -1352,6 +1336,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.20.1': resolution: {integrity: sha512-TgFyCfIxSujyuqdZKDZ3yTwWiGv+KnlOeXXitCQ+trDODJ+ZtGOzLkSWngynP0HZnTsDyBbPy7GWVXWaEl6lhA==} engines: {node: '>=12'} @@ -1364,6 +1354,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.20.1': resolution: {integrity: sha512-b+yuD1IUeL+Y93PmFZDZFIElwbmFfIKLKlYI8M6tRyzE6u7oEP7onGk0vZRh8wfVGC2dZoy0EqX1V8qok4qHaw==} engines: {node: '>=12'} @@ -1376,6 +1372,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.20.1': resolution: {integrity: sha512-wpDlpE0oRKZwX+GfomcALcouqjjV8MIX8DyTrxfyCfXxoKQSDm45CZr9fanJ4F6ckD4yDEPT98SrjvLwIqUCgg==} engines: {node: '>=12'} @@ -1388,6 +1390,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.20.1': resolution: {integrity: sha512-5BepC2Au80EohQ2dBpyTquqGCES7++p7G+7lXe1bAIvMdXm4YYcEfZtQrP4gaoZ96Wv1Ute61CEHFU7h4FMueQ==} engines: {node: '>=12'} @@ -1400,6 +1408,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.20.1': resolution: {integrity: sha512-5gRPk7pKuaIB+tmH+yKd2aQTRpqlf1E4f/mC+tawIm/CGJemZcHZpp2ic8oD83nKgUPMEd0fNanrnFljiruuyA==} engines: {node: '>=12'} @@ -1412,6 +1426,12 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-x64@0.20.1': resolution: {integrity: sha512-4fL68JdrLV2nVW2AaWZBv3XEm3Ae3NZn/7qy2KGAt3dexAgSVT+Hc97JKSZnqezgMlv9x6KV0ZkZY7UO5cNLCg==} engines: {node: '>=12'} @@ -1424,6 +1444,12 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-x64@0.20.1': resolution: {integrity: sha512-GhRuXlvRE+twf2ES+8REbeCb/zeikNqwD3+6S5y5/x+DYbAQUNl0HNBs4RQJqrechS4v4MruEr8ZtAin/hK5iw==} engines: {node: '>=12'} @@ -1436,6 +1462,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + '@esbuild/sunos-x64@0.20.1': resolution: {integrity: sha512-ZnWEyCM0G1Ex6JtsygvC3KUUrlDXqOihw8RicRuQAzw+c4f1D66YlPNNV3rkjVW90zXVsHwZYWbJh3v+oQFM9Q==} engines: {node: '>=12'} @@ -1448,6 +1480,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.20.1': resolution: {integrity: sha512-QZ6gXue0vVQY2Oon9WyLFCdSuYbXSoxaZrPuJ4c20j6ICedfsDilNPYfHLlMH7vGfU5DQR0czHLmJvH4Nzis/A==} engines: {node: '>=12'} @@ -1460,6 +1498,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.20.1': resolution: {integrity: sha512-HzcJa1NcSWTAU0MJIxOho8JftNp9YALui3o+Ny7hCh0v5f90nprly1U3Sj1Ldj/CvKKdvvFsCRvDkpsEMp4DNw==} engines: {node: '>=12'} @@ -1472,6 +1516,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.20.1': resolution: {integrity: sha512-0MBh53o6XtI6ctDnRMeQ+xoCN8kD2qI1rY1KgF/xdWQwoFeKou7puvDfV8/Wv4Ctx2rRpET/gGdz3YlNtNACSA==} engines: {node: '>=12'} @@ -1484,6 +1534,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.4.0': resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1515,10 +1571,62 @@ packages: resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} deprecated: Use @eslint/object-schema instead + '@inquirer/checkbox@2.3.10': + resolution: {integrity: sha512-CTc864M2/523rKc9AglIzAcUCuPXDZENgc5S2KZFVRbnMzpXcYTsUWmbqSeL0XLvtlvEtNevkkVbfVhJpruOyQ==} + engines: {node: '>=18'} + + '@inquirer/confirm@3.1.11': + resolution: {integrity: sha512-3wWw10VPxQP279FO4bzWsf8YjIAq7NdwATJ4xS2h1uwsXZu/RmtOVV95rZ7yllS1h/dzu+uLewjMAzNDEj8h2w==} + engines: {node: '>=18'} + + '@inquirer/confirm@3.1.14': + resolution: {integrity: sha512-nbLSX37b2dGPtKWL3rPuR/5hOuD30S+pqJ/MuFiUEgN6GiMs8UMxiurKAMDzKt6C95ltjupa8zH6+3csXNHWpA==} + engines: {node: '>=18'} + + '@inquirer/core@8.2.4': + resolution: {integrity: sha512-7vsXSfxtrrbwMTirfaKwPcjqJy7pzeuF/bP62yo1NQrRJ5HjmMlrhZml/Ljm9ODc1RnbhJlTeSnCkjtFddKjwA==} + engines: {node: '>=18'} + + '@inquirer/core@9.0.2': + resolution: {integrity: sha512-nguvH3TZar3ACwbytZrraRTzGqyxJfYJwv+ZwqZNatAosdWQMP1GV8zvmkNlBe2JeZSaw0WYBHZk52pDpWC9qA==} + engines: {node: '>=18'} + + '@inquirer/editor@2.1.14': + resolution: {integrity: sha512-6nWpoJyVAKwAcv67bkbBmmi3f32xua79fP7TRmNUoR4K+B1GiOBsHO1YdvET/jvC+nTlBZL7puKAKyM7G+Lkzw==} + engines: {node: '>=18'} + + '@inquirer/expand@2.1.14': + resolution: {integrity: sha512-JcxsLajwPykF2kq6biIUdoOzTQ3LXqb8XMVrWkCprG/pFeU1SsxcSSFbF1T5jJGvvlTVcsE+JdGjbQ8ZRZ82RA==} + engines: {node: '>=18'} + '@inquirer/figures@1.0.3': resolution: {integrity: sha512-ErXXzENMH5pJt5/ssXV0DfWUZqly8nGzf0UcBV9xTnP+KyffE2mqyxIMBrZ8ijQck2nU0TQm40EQB53YreyWHw==} engines: {node: '>=18'} + '@inquirer/input@2.2.1': + resolution: {integrity: sha512-Yl1G6h7qWydzrJwqN777geeJVaAFL5Ly83aZlw4xHf8Z/BoTMfKRheyuMaQwOG7LQ4e5nQP7PxXdEg4SzQ+OKw==} + engines: {node: '>=18'} + + '@inquirer/password@2.1.14': + resolution: {integrity: sha512-sPzOkXLhWJQ96K6nPZFnF8XB8tsDrcCRobd1d3EDz81F+4hp8BbdmsnsQcqZ7oYDIOVM/mWJyIUtJ35TrssJxQ==} + engines: {node: '>=18'} + + '@inquirer/prompts@5.0.7': + resolution: {integrity: sha512-GFcigCxJTKCH3aECzMIu4FhgLJWnFvMXzpI4CCSoELWFtkOOU2P+goYA61+OKpGrB8fPE7q6n8zAXBSlZRrHjQ==} + engines: {node: '>=18'} + + '@inquirer/rawlist@2.1.14': + resolution: {integrity: sha512-pLpEzhKNQ/ugFAFfgCNaXljB+dcCwmXwR1jOxAbVeFIdB3l02E5gjI+h1rb136tq0T8JO6P5KFR1oTeld/wdrA==} + engines: {node: '>=18'} + + '@inquirer/select@2.3.10': + resolution: {integrity: sha512-rr7iR0Zj1YFfgM8IUGimPD9Yukd+n/U63CnYT9kdum6DbRXtMxR45rrreP+EA9ixCnShr+W4xj7suRxC1+8t9g==} + engines: {node: '>=18'} + + '@inquirer/type@1.4.0': + resolution: {integrity: sha512-AjOqykVyjdJQvtfkNDGUyMYGF8xN50VUxftCQWsOyIo4DFRLr6VQhW0VItGI1JIyQGCGgIpKa7hMMwNhZb4OIw==} + engines: {node: '>=18'} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -1646,35 +1754,71 @@ packages: '@leichtgewicht/ip-codec@2.0.4': resolution: {integrity: sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==} + '@listr2/prompt-adapter-inquirer@2.0.13': + resolution: {integrity: sha512-nAl6teTt7EWSjttNavAnv3uFR3w3vPP3OTYmHyPNHzKhAj2NoBDHmbS3MGpvvO8KXXPASnHjEGrrKrdKTMKPnQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@inquirer/prompts': '>= 3 < 6' + '@ljharb/through@2.3.13': resolution: {integrity: sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ==} engines: {node: '>= 0.4'} + '@lmdb/lmdb-darwin-arm64@3.0.12': + resolution: {integrity: sha512-vgTwzNUD3Hy4aqtGhX2+nV/usI0mwy3hDRuTjs8VcK0BLiMVEpNQXgzwlWEgPmA8AAPloUgyOs2nK5clJF5oIg==} + cpu: [arm64] + os: [darwin] + '@lmdb/lmdb-darwin-arm64@3.0.8': resolution: {integrity: sha512-+lFwFvU+zQ9zVIFETNtmW++syh3Ps5JS8MPQ8zOYtQZoU+dTR8ivWHTaE2QVk1JG2payGDLUAvpndLAjGMdeeA==} cpu: [arm64] os: [darwin] + '@lmdb/lmdb-darwin-x64@3.0.12': + resolution: {integrity: sha512-qOt0hAhj2ZLY6aEWu85rzt5zcyCAQITMhCMEPNlo1tuYekpVAdkQNiwXxEkCjBYvwTskvXuwXOOUpjuSc+aJnA==} + cpu: [x64] + os: [darwin] + '@lmdb/lmdb-darwin-x64@3.0.8': resolution: {integrity: sha512-T98rfsgfdQMS5/mqdsPb6oHSJ+iBYNa+PQDLtXLh6rzTEBsYP9x2uXxIj6VS4qXVDWXVi8rv85NCOG+UBOsHXQ==} cpu: [x64] os: [darwin] + '@lmdb/lmdb-linux-arm64@3.0.12': + resolution: {integrity: sha512-Qy4cFXFe9h1wAWMsojex8x1ifvw2kqiZv686YiRTdQEzAfc3vJASHFcD/QejHUCx7YHMYdnUoCS45rG2AiGDTQ==} + cpu: [arm64] + os: [linux] + '@lmdb/lmdb-linux-arm64@3.0.8': resolution: {integrity: sha512-uEBGCQIChsixpykL0pjCxfF64btv64vzsb1NoM5u0qvabKvKEvErhXGoqovyldDu9u1T/fswD8Kf6ih0vJEvDQ==} cpu: [arm64] os: [linux] + '@lmdb/lmdb-linux-arm@3.0.12': + resolution: {integrity: sha512-Ggd/UXpE+alMncbELCXA3OKpDj9bDBR3qVO7WRTxstloDglRAHfZmUJgTkeaNKjFO1JHqS7AKy0jba9XebZB1w==} + cpu: [arm] + os: [linux] + '@lmdb/lmdb-linux-arm@3.0.8': resolution: {integrity: sha512-gVNCi3bYWatdPMeFpFjuZl6bzVL55FkeZU3sPeU+NsMRXC+Zl3qOx3M6cM4OMlJWbhHjYjf2b8q83K0mczaiWQ==} cpu: [arm] os: [linux] + '@lmdb/lmdb-linux-x64@3.0.12': + resolution: {integrity: sha512-c+noT9IofktxktFllKHFmci8ka2SYGSLN17pj/KSl1hg7mmfAiGp4xxFxEwMLTb+SX95vP1DFiR++1I3WLVxvA==} + cpu: [x64] + os: [linux] + '@lmdb/lmdb-linux-x64@3.0.8': resolution: {integrity: sha512-6v0B4sa9ulNezmDZtVpLjNHmA0qZzUl3001YJ2RF0naxsuv/Jq/xEwNYpOzfcdizHfpCE0oBkWzk/r+Slr+0zw==} cpu: [x64] os: [linux] + '@lmdb/lmdb-win32-x64@3.0.12': + resolution: {integrity: sha512-CO3MFV8gUx16NU/CyyuumAKblESwvoGVA2XhQKZ976OTOxaTbb8F8D3f0iiZ4MYqsN74jIrFuCmXpPnpjbhfOQ==} + cpu: [x64] + os: [win32] + '@lmdb/lmdb-win32-x64@3.0.8': resolution: {integrity: sha512-lDLGRIMqdwYD39vinwNqqZUxCdL2m2iIdn+0HyQgIHEiT0g5rIAlzaMKzoGWon5NQumfxXFk9y0DarttkR7C1w==} cpu: [x64] @@ -2105,8 +2249,8 @@ packages: resolution: {integrity: sha512-2g4OmSyE9YGq50Uj7fNI26P/TSAFJ7ZuirwTF2O7Xc4XRQ29/tYIIqhezpNlTb6rlYblcQuMcUZBrMfWJHcqJw==} engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} - '@schematics/angular@18.0.4': - resolution: {integrity: sha512-fN4whuym9ZmcQFdTfwLZr4j+NcZ4LzbdLk8XYrYdxt1z8c9ujs5LqJYn0LYc3UWiYl7z2RVc9NOxzNrkiXdwlw==} + '@schematics/angular@18.1.0': + resolution: {integrity: sha512-k9Dy6JD7hqvCzDqnMjDm7J8H/P6m5mLuX2yEgQWKRAJ/YMINtBQAaKA1T9qXk97kEX6RNLpHMuDIsrIfK/H31Q==} 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'} '@sigstore/bundle@2.3.2': @@ -2244,12 +2388,18 @@ packages: '@types/mime@1.3.2': resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==} + '@types/mute-stream@0.0.4': + resolution: {integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==} + '@types/node-forge@1.3.11': resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==} '@types/node@20.12.7': resolution: {integrity: sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==} + '@types/node@20.14.10': + resolution: {integrity: sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==} + '@types/qs@6.9.7': resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==} @@ -2283,6 +2433,9 @@ packages: '@types/ungap__structured-clone@0.3.3': resolution: {integrity: sha512-RNmhIPwoip6K/zZOv3ypksTAqaqLEXvlNSXKyrC93xMSOAHZCR7PifW6xKZCwkbbnbM9dwB9X56PPoNTlNwEqw==} + '@types/wrap-ansi@3.0.0': + resolution: {integrity: sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==} + '@types/ws@8.5.10': resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} @@ -2538,6 +2691,9 @@ packages: ajv@8.13.0: resolution: {integrity: sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==} + ajv@8.16.0: + resolution: {integrity: sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==} + ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -2546,6 +2702,10 @@ packages: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} + ansi-escapes@6.2.1: + resolution: {integrity: sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==} + engines: {node: '>=14.16'} + ansi-html-community@0.0.8: resolution: {integrity: sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==} engines: {'0': node >= 0.8.0} @@ -2854,6 +3014,10 @@ packages: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} + cli-cursor@4.0.0: + resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + cli-spinners@2.6.1: resolution: {integrity: sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==} engines: {node: '>=6'} @@ -2862,6 +3026,14 @@ packages: resolution: {integrity: sha512-4/aL9X3Wh0yiMQlE+eeRhWP6vclO3QRtw1JHKIT0FFUs5FjpFmESqtMvYZ0+lbzBw900b95mS0hohy+qn2VK/g==} engines: {node: '>=6'} + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + + cli-truncate@4.0.0: + resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} + engines: {node: '>=18'} + cli-width@4.1.0: resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} engines: {node: '>= 12'} @@ -2989,6 +3161,9 @@ packages: critters@0.0.22: resolution: {integrity: sha512-NU7DEcQZM2Dy8XTKFHxtdnIM/drE312j2T4PCVaSUcS0oBeyT/NImpRw/Ap0zOr/1SE7SgPK9tGPg1WK/sVakw==} + critters@0.0.24: + resolution: {integrity: sha512-Oyqew0FGM0wYUSNqR0L6AteO5MpMoUU0rhKRieXeiKs+PmRTxiJMyaunYB2KF6fQ3dzChXKCpbFOEJx3OQ1v/Q==} + cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -3233,6 +3408,9 @@ packages: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} engines: {node: '>=12'} + emoji-regex@10.3.0: + resolution: {integrity: sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -3336,6 +3514,11 @@ packages: engines: {node: '>=12'} hasBin: true + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + escalade@3.1.2: resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} engines: {node: '>=6'} @@ -3458,6 +3641,9 @@ packages: eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} @@ -3637,6 +3823,10 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} + get-east-asian-width@1.2.0: + resolution: {integrity: sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==} + engines: {node: '>=18'} + get-intrinsic@1.2.1: resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==} @@ -3840,6 +4030,10 @@ packages: resolution: {integrity: sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==} engines: {node: '>= 14'} + https-proxy-agent@7.0.5: + resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} + engines: {node: '>= 14'} + human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -3916,8 +4110,8 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - ini@4.1.2: - resolution: {integrity: sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw==} + ini@4.1.3: + resolution: {integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} inquirer@9.2.22: @@ -3993,6 +4187,14 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + is-fullwidth-code-point@4.0.0: + resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} + engines: {node: '>=12'} + + is-fullwidth-code-point@5.0.0: + resolution: {integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==} + engines: {node: '>=18'} + is-generator-fn@2.1.0: resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} engines: {node: '>=6'} @@ -4361,6 +4563,9 @@ packages: jsonc-parser@3.2.1: resolution: {integrity: sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==} + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} @@ -4423,6 +4628,14 @@ packages: resolution: {integrity: sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + listr2@8.2.3: + resolution: {integrity: sha512-Lllokma2mtoniUOS94CcOErHWAug5iu7HOmDrvWgpw8jyQH2fomgB+7lZS4HWZxytUuQwkGOwe49FvwVaA85Xw==} + engines: {node: '>=18.0.0'} + + lmdb@3.0.12: + resolution: {integrity: sha512-JnoEulTgveoC64vlYJ9sufGLuNkk6TcxSYpKxSC9aM42I61jIv3pQH0fgb6qW7HV0+FNqA3g1WCQQYfhfawGoQ==} + hasBin: true + lmdb@3.0.8: resolution: {integrity: sha512-9rp8JT4jPhCRJUL7vRARa2N06OLSYzLwQsEkhC6Qu5XbcLyM/XBLMzDlgS/K7l7c5CdURLdDk9uE+hPFIogHTQ==} hasBin: true @@ -4467,6 +4680,10 @@ packages: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} + log-update@6.0.0: + resolution: {integrity: sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==} + engines: {node: '>=18'} + lru-cache@10.2.2: resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} engines: {node: 14 || >=16.14} @@ -4707,6 +4924,10 @@ packages: resolution: {integrity: sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==} hasBin: true + node-gyp-build-optional-packages@5.2.2: + resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==} + hasBin: true + node-gyp-build@4.6.0: resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==} hasBin: true @@ -4988,6 +5209,9 @@ packages: picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + picocolors@1.0.1: + resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -5015,6 +5239,9 @@ packages: piscina@4.5.0: resolution: {integrity: sha512-iBaLWI56PFP81cfBSomWTmhOo9W2/yhIOL+Tk8O1vBCpK39cM0tGxB+wgYjG31qq4ohGvysfXSdnj8h7g4rZxA==} + piscina@4.6.1: + resolution: {integrity: sha512-z30AwWGtQE+Apr+2WBZensP2lIvwoaMcOPkQlIEmSGMJNUvaYACylPYrQM6wSdUNJlnDVMSpLv7xTMJqlVshOA==} + pkg-dir@4.2.0: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} @@ -5247,6 +5474,10 @@ packages: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} + restore-cursor@4.0.0: + resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + retry@0.12.0: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} @@ -5259,6 +5490,9 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} deprecated: Rimraf versions prior to v4 are no longer supported @@ -5334,6 +5568,11 @@ packages: engines: {node: '>=14.0.0'} hasBin: true + sass@1.77.6: + resolution: {integrity: sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==} + engines: {node: '>=14.0.0'} + hasBin: true + sax@1.2.4: resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} @@ -5448,6 +5687,14 @@ packages: resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} engines: {node: '>=12'} + slice-ansi@5.0.0: + resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} + engines: {node: '>=12'} + + slice-ansi@7.1.0: + resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==} + engines: {node: '>=18'} + smart-buffer@4.2.0: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} @@ -5547,6 +5794,10 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + string.prototype.trim@1.2.9: resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} engines: {node: '>= 0.4'} @@ -5831,6 +6082,10 @@ packages: resolution: {integrity: sha512-nT8jjv/fE9Et1ilR6QoW8ingRTY2Pp4l2RUrdzV5Yz35RJDrtPc1DXvuNqcpsJSGIRHFdt3YKKktTzJA6r0fTA==} engines: {node: '>=18.17'} + undici@6.19.2: + resolution: {integrity: sha512-JfjKqIauur3Q6biAtHJ564e3bWa8VvT+7cSiOJHFbX4Erv6CLGDpg8z+Fmg/1OI/47RA+GI2QZaF48SSaLvyBA==} + engines: {node: '>=18.17'} + unicode-canonical-property-names-ecmascript@2.0.0: resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} engines: {node: '>=4'} @@ -5936,6 +6191,34 @@ packages: terser: optional: true + vite@5.3.2: + resolution: {integrity: sha512-6lA7OBHBlXUxiJxbO5aAY2fsHHzDr1q7DvXYnyZycRs2Dz+dXBWuhpWHvmljTRTpQC2uvGmUFFkSHF2vGo90MA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + w3c-xmlserializer@4.0.0: resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==} engines: {node: '>=14'} @@ -6062,6 +6345,10 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} + wrap-ansi@9.0.0: + resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} + engines: {node: '>=18'} + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -6136,6 +6423,10 @@ packages: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} engines: {node: '>=12.20'} + yoctocolors-cjs@2.1.2: + resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} + engines: {node: '>=18'} + zone.js@0.14.4: resolution: {integrity: sha512-NtTUvIlNELez7Q1DzKVIFZBzNb646boQMgpATo9z3Ftuu/gWvzxCW7jdjcUDoRGxRikrhVHB/zLXh1hxeJawvw==} @@ -6148,12 +6439,12 @@ snapshots: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 - '@aneoconsultingfr/armonik.api.angular@3.18.1(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@ngx-grpc/common@3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1))(@ngx-grpc/core@3.1.2(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@ngx-grpc/common@3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1))(rxjs@7.8.1))(@ngx-grpc/well-known-types@3.1.2(@ngx-grpc/common@3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1))(google-protobuf@3.21.2))(google-protobuf@3.21.2)(rxjs@7.8.1)': + '@aneoconsultingfr/armonik.api.angular@3.19.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@ngx-grpc/common@3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1))(@ngx-grpc/core@3.1.2(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@ngx-grpc/common@3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1))(rxjs@7.8.1))(@ngx-grpc/well-known-types@3.1.2(@ngx-grpc/common@3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1))(google-protobuf@3.21.2))(google-protobuf@3.21.2)(rxjs@7.8.1)': dependencies: - '@angular/common': 18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1) - '@angular/core': 18.0.3(rxjs@7.8.1)(zone.js@0.14.4) + '@angular/common': 18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1) + '@angular/core': 18.1.0(rxjs@7.8.1)(zone.js@0.14.4) '@ngx-grpc/common': 3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1) - '@ngx-grpc/core': 3.1.2(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@ngx-grpc/common@3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1))(rxjs@7.8.1) + '@ngx-grpc/core': 3.1.2(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@ngx-grpc/common@3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1))(rxjs@7.8.1) '@ngx-grpc/well-known-types': 3.1.2(@ngx-grpc/common@3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1))(google-protobuf@3.21.2) google-protobuf: 3.21.2 rxjs: 7.8.1 @@ -6173,21 +6464,21 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular-devkit/architect@0.1800.4(chokidar@3.6.0)': + '@angular-devkit/architect@0.1801.0(chokidar@3.6.0)': dependencies: - '@angular-devkit/core': 18.0.4(chokidar@3.6.0) + '@angular-devkit/core': 18.1.0(chokidar@3.6.0) rxjs: 7.8.1 transitivePeerDependencies: - chokidar - '@angular-devkit/build-angular@18.0.2(@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/localize@18.0.3(@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))))(@types/node@20.12.7)(chokidar@3.6.0)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)))(typescript@5.4.5)': + '@angular-devkit/build-angular@18.0.2(@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/localize@18.1.0(@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))))(@types/node@20.12.7)(chokidar@3.6.0)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)))(typescript@5.4.5)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1800.2(chokidar@3.6.0) '@angular-devkit/build-webpack': 0.1800.2(chokidar@3.6.0)(webpack-dev-server@5.0.4(webpack@5.91.0(esbuild@0.21.3)))(webpack@5.91.0(esbuild@0.21.3)) '@angular-devkit/core': 18.0.2(chokidar@3.6.0) - '@angular/build': 18.0.2(@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/localize@18.0.3(@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))))(@types/node@20.12.7)(chokidar@3.6.0)(less@4.2.0)(postcss@8.4.38)(terser@5.31.0)(typescript@5.4.5) - '@angular/compiler-cli': 18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5) + '@angular/build': 18.0.2(@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/localize@18.1.0(@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))))(@types/node@20.12.7)(chokidar@3.6.0)(less@4.2.0)(postcss@8.4.38)(terser@5.31.0)(typescript@5.4.5) + '@angular/compiler-cli': 18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5) '@babel/core': 7.24.5 '@babel/generator': 7.24.5 '@babel/helper-annotate-as-pure': 7.22.5 @@ -6198,7 +6489,7 @@ snapshots: '@babel/preset-env': 7.24.5(@babel/core@7.24.5) '@babel/runtime': 7.24.5 '@discoveryjs/json-ext': 0.5.7 - '@ngtools/webpack': 18.0.2(@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(typescript@5.4.5)(webpack@5.91.0(esbuild@0.21.3)) + '@ngtools/webpack': 18.0.2(@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(typescript@5.4.5)(webpack@5.91.0(esbuild@0.21.3)) '@vitejs/plugin-basic-ssl': 1.1.0(vite@5.2.11(@types/node@20.12.7)(less@4.2.0)(sass@1.77.2)(terser@5.31.0)) ansi-colors: 4.1.3 autoprefixer: 10.4.19(postcss@8.4.38) @@ -6249,7 +6540,7 @@ snapshots: webpack-merge: 5.10.0 webpack-subresource-integrity: 5.1.0(webpack@5.91.0(esbuild@0.21.3)) optionalDependencies: - '@angular/localize': 18.0.3(@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))) + '@angular/localize': 18.1.0(@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))) esbuild: 0.21.3 jest: 29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)) jest-environment-jsdom: 29.7.0 @@ -6313,11 +6604,11 @@ snapshots: optionalDependencies: chokidar: 3.6.0 - '@angular-devkit/core@18.0.4(chokidar@3.6.0)': + '@angular-devkit/core@18.1.0(chokidar@3.6.0)': dependencies: - ajv: 8.13.0 - ajv-formats: 3.0.1(ajv@8.13.0) - jsonc-parser: 3.2.1 + ajv: 8.16.0 + ajv-formats: 3.0.1(ajv@8.16.0) + jsonc-parser: 3.3.1 picomatch: 4.0.2 rxjs: 7.8.1 source-map: 0.7.4 @@ -6334,17 +6625,17 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular-devkit/schematics@18.0.4(chokidar@3.6.0)': + '@angular-devkit/schematics@18.1.0(chokidar@3.6.0)': dependencies: - '@angular-devkit/core': 18.0.4(chokidar@3.6.0) - jsonc-parser: 3.2.1 + '@angular-devkit/core': 18.1.0(chokidar@3.6.0) + jsonc-parser: 3.3.1 magic-string: 0.30.10 ora: 5.4.1 rxjs: 7.8.1 transitivePeerDependencies: - chokidar - '@angular-eslint/builder@18.0.1(eslint@8.57.0)(typescript@5.4.5)': + '@angular-eslint/builder@18.1.0(eslint@8.57.0)(typescript@5.4.5)': dependencies: '@nx/devkit': 19.1.1(nx@19.1.1) eslint: 8.57.0 @@ -6355,32 +6646,32 @@ snapshots: - '@swc/core' - debug - '@angular-eslint/bundled-angular-compiler@18.0.1': {} + '@angular-eslint/bundled-angular-compiler@18.1.0': {} - '@angular-eslint/eslint-plugin-template@18.0.1(@typescript-eslint/utils@7.11.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)': + '@angular-eslint/eslint-plugin-template@18.1.0(@typescript-eslint/utils@7.11.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)': dependencies: - '@angular-eslint/bundled-angular-compiler': 18.0.1 - '@angular-eslint/utils': 18.0.1(@typescript-eslint/utils@7.11.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) + '@angular-eslint/bundled-angular-compiler': 18.1.0 + '@angular-eslint/utils': 18.1.0(@typescript-eslint/utils@7.11.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) '@typescript-eslint/utils': 7.11.0(eslint@8.57.0)(typescript@5.4.5) aria-query: 5.3.0 axobject-query: 4.0.0 eslint: 8.57.0 typescript: 5.4.5 - '@angular-eslint/eslint-plugin@18.0.1(@typescript-eslint/utils@7.11.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)': + '@angular-eslint/eslint-plugin@18.1.0(@typescript-eslint/utils@7.11.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)': dependencies: - '@angular-eslint/bundled-angular-compiler': 18.0.1 - '@angular-eslint/utils': 18.0.1(@typescript-eslint/utils@7.11.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) + '@angular-eslint/bundled-angular-compiler': 18.1.0 + '@angular-eslint/utils': 18.1.0(@typescript-eslint/utils@7.11.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) '@typescript-eslint/utils': 7.11.0(eslint@8.57.0)(typescript@5.4.5) eslint: 8.57.0 typescript: 5.4.5 - '@angular-eslint/schematics@18.0.1(@angular-devkit/core@18.0.4(chokidar@3.6.0))(@angular-devkit/schematics@18.0.4(chokidar@3.6.0))(@typescript-eslint/utils@7.11.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)': + '@angular-eslint/schematics@18.1.0(@angular-devkit/core@18.1.0(chokidar@3.6.0))(@angular-devkit/schematics@18.1.0(chokidar@3.6.0))(@typescript-eslint/utils@7.11.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)': dependencies: - '@angular-devkit/core': 18.0.4(chokidar@3.6.0) - '@angular-devkit/schematics': 18.0.4(chokidar@3.6.0) - '@angular-eslint/eslint-plugin': 18.0.1(@typescript-eslint/utils@7.11.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) - '@angular-eslint/eslint-plugin-template': 18.0.1(@typescript-eslint/utils@7.11.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) + '@angular-devkit/core': 18.1.0(chokidar@3.6.0) + '@angular-devkit/schematics': 18.1.0(chokidar@3.6.0) + '@angular-eslint/eslint-plugin': 18.1.0(@typescript-eslint/utils@7.11.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) + '@angular-eslint/eslint-plugin-template': 18.1.0(@typescript-eslint/utils@7.11.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) '@nx/devkit': 19.1.1(nx@19.1.1) ignore: 5.3.1 nx: 19.1.1 @@ -6394,40 +6685,40 @@ snapshots: - eslint - typescript - '@angular-eslint/template-parser@18.0.1(eslint@8.57.0)(typescript@5.4.5)': + '@angular-eslint/template-parser@18.1.0(eslint@8.57.0)(typescript@5.4.5)': dependencies: - '@angular-eslint/bundled-angular-compiler': 18.0.1 + '@angular-eslint/bundled-angular-compiler': 18.1.0 eslint: 8.57.0 eslint-scope: 8.0.1 typescript: 5.4.5 - '@angular-eslint/utils@18.0.1(@typescript-eslint/utils@7.11.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)': + '@angular-eslint/utils@18.1.0(@typescript-eslint/utils@7.11.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)': dependencies: - '@angular-eslint/bundled-angular-compiler': 18.0.1 + '@angular-eslint/bundled-angular-compiler': 18.1.0 '@typescript-eslint/utils': 7.11.0(eslint@8.57.0)(typescript@5.4.5) eslint: 8.57.0 typescript: 5.4.5 - ? '@angular-material-components/datetime-picker@16.0.1(@angular/cdk@18.0.4(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/forms@18.0.3(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1))(@angular/material@18.0.4(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/cdk@18.0.4(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/forms@18.0.3(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1))(@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1))(@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))' + ? '@angular-material-components/datetime-picker@16.0.1(@angular/cdk@18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/forms@18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1))(@angular/material@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/cdk@18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/forms@18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1))(@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1))(@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))' : dependencies: - '@angular/cdk': 18.0.4(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1) - '@angular/common': 18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1) - '@angular/core': 18.0.3(rxjs@7.8.1)(zone.js@0.14.4) - '@angular/forms': 18.0.3(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1) - '@angular/material': 18.0.4(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/cdk@18.0.4(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/forms@18.0.3(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1))(@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1) - '@angular/platform-browser': 18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)) + '@angular/cdk': 18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1) + '@angular/common': 18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1) + '@angular/core': 18.1.0(rxjs@7.8.1)(zone.js@0.14.4) + '@angular/forms': 18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1) + '@angular/material': 18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/cdk@18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/forms@18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1))(@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1) + '@angular/platform-browser': 18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)) tslib: 2.6.2 - '@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))': + '@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))': dependencies: - '@angular/core': 18.0.3(rxjs@7.8.1)(zone.js@0.14.4) + '@angular/core': 18.1.0(rxjs@7.8.1)(zone.js@0.14.4) tslib: 2.6.2 - '@angular/build@18.0.2(@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/localize@18.0.3(@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))))(@types/node@20.12.7)(chokidar@3.6.0)(less@4.2.0)(postcss@8.4.38)(terser@5.31.0)(typescript@5.4.5)': + '@angular/build@18.0.2(@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/localize@18.1.0(@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))))(@types/node@20.12.7)(chokidar@3.6.0)(less@4.2.0)(postcss@8.4.38)(terser@5.31.0)(typescript@5.4.5)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1800.2(chokidar@3.6.0) - '@angular/compiler-cli': 18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5) + '@angular/compiler-cli': 18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5) '@babel/core': 7.24.5 '@babel/helper-annotate-as-pure': 7.22.5 '@babel/helper-split-export-declaration': 7.24.5 @@ -6453,7 +6744,7 @@ snapshots: vite: 5.2.11(@types/node@20.12.7)(less@4.2.0)(sass@1.77.2)(terser@5.31.0) watchpack: 2.4.1 optionalDependencies: - '@angular/localize': 18.0.3(@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))) + '@angular/localize': 18.1.0(@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))) less: 4.2.0 postcss: 8.4.38 transitivePeerDependencies: @@ -6465,37 +6756,39 @@ snapshots: - supports-color - terser - '@angular/build@18.0.4(@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/localize@18.0.3(@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))))(@types/node@20.12.7)(chokidar@3.6.0)(less@4.2.0)(postcss@8.4.38)(terser@5.31.0)(typescript@5.4.5)': + '@angular/build@18.1.0(@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/localize@18.1.0(@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))))(@types/node@20.12.7)(chokidar@3.6.0)(less@4.2.0)(postcss@8.4.38)(terser@5.31.0)(typescript@5.4.5)': dependencies: '@ampproject/remapping': 2.3.0 - '@angular-devkit/architect': 0.1800.4(chokidar@3.6.0) - '@angular/compiler-cli': 18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5) - '@babel/core': 7.24.5 - '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-split-export-declaration': 7.24.5 - '@vitejs/plugin-basic-ssl': 1.1.0(vite@5.2.11(@types/node@20.12.7)(less@4.2.0)(sass@1.77.2)(terser@5.31.0)) + '@angular-devkit/architect': 0.1801.0(chokidar@3.6.0) + '@angular/compiler-cli': 18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5) + '@babel/core': 7.24.7 + '@babel/helper-annotate-as-pure': 7.24.7 + '@babel/helper-split-export-declaration': 7.24.7 + '@babel/plugin-syntax-import-attributes': 7.24.7(@babel/core@7.24.7) + '@inquirer/confirm': 3.1.11 + '@vitejs/plugin-basic-ssl': 1.1.0(vite@5.3.2(@types/node@20.12.7)(less@4.2.0)(sass@1.77.6)(terser@5.31.0)) ansi-colors: 4.1.3 browserslist: 4.23.0 - critters: 0.0.22 - esbuild: 0.21.3 + critters: 0.0.24 + esbuild: 0.21.5 fast-glob: 3.3.2 - https-proxy-agent: 7.0.4 - inquirer: 9.2.22 - lmdb: 3.0.8 + https-proxy-agent: 7.0.5 + lmdb: 3.0.12 magic-string: 0.30.10 mrmime: 2.0.0 ora: 5.4.1 parse5-html-rewriting-stream: 7.0.0 picomatch: 4.0.2 - piscina: 4.5.0 - sass: 1.77.2 + piscina: 4.6.1 + rollup: 4.18.0 + sass: 1.77.6 semver: 7.6.2 typescript: 5.4.5 - undici: 6.18.0 - vite: 5.2.11(@types/node@20.12.7)(less@4.2.0)(sass@1.77.2)(terser@5.31.0) + undici: 6.19.2 + vite: 5.3.2(@types/node@20.12.7)(less@4.2.0)(sass@1.77.6)(terser@5.31.0) watchpack: 2.4.1 optionalDependencies: - '@angular/localize': 18.0.3(@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))) + '@angular/localize': 18.1.0(@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))) less: 4.2.0 postcss: 8.4.38 transitivePeerDependencies: @@ -6507,29 +6800,29 @@ snapshots: - supports-color - terser - '@angular/cdk@18.0.4(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1)': + '@angular/cdk@18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1)': dependencies: - '@angular/common': 18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1) - '@angular/core': 18.0.3(rxjs@7.8.1)(zone.js@0.14.4) + '@angular/common': 18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1) + '@angular/core': 18.1.0(rxjs@7.8.1)(zone.js@0.14.4) rxjs: 7.8.1 tslib: 2.6.2 optionalDependencies: parse5: 7.1.2 - '@angular/cli@18.0.4(chokidar@3.6.0)': + '@angular/cli@18.1.0(chokidar@3.6.0)': dependencies: - '@angular-devkit/architect': 0.1800.4(chokidar@3.6.0) - '@angular-devkit/core': 18.0.4(chokidar@3.6.0) - '@angular-devkit/schematics': 18.0.4(chokidar@3.6.0) - '@schematics/angular': 18.0.4(chokidar@3.6.0) + '@angular-devkit/architect': 0.1801.0(chokidar@3.6.0) + '@angular-devkit/core': 18.1.0(chokidar@3.6.0) + '@angular-devkit/schematics': 18.1.0(chokidar@3.6.0) + '@inquirer/prompts': 5.0.7 + '@listr2/prompt-adapter-inquirer': 2.0.13(@inquirer/prompts@5.0.7) + '@schematics/angular': 18.1.0(chokidar@3.6.0) '@yarnpkg/lockfile': 1.1.0 - ansi-colors: 4.1.3 - ini: 4.1.2 - inquirer: 9.2.22 - jsonc-parser: 3.2.1 + ini: 4.1.3 + jsonc-parser: 3.3.1 + listr2: 8.2.3 npm-package-arg: 11.0.2 npm-pick-manifest: 9.0.1 - ora: 5.4.1 pacote: 18.0.6 resolve: 1.22.8 semver: 7.6.2 @@ -6540,15 +6833,15 @@ snapshots: - chokidar - supports-color - '@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1)': + '@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1)': dependencies: - '@angular/core': 18.0.3(rxjs@7.8.1)(zone.js@0.14.4) + '@angular/core': 18.1.0(rxjs@7.8.1)(zone.js@0.14.4) rxjs: 7.8.1 tslib: 2.6.2 - '@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5)': + '@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5)': dependencies: - '@angular/compiler': 18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)) + '@angular/compiler': 18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)) '@babel/core': 7.24.7 '@jridgewell/sourcemap-codec': 1.4.15 chokidar: 3.6.0 @@ -6561,30 +6854,30 @@ snapshots: transitivePeerDependencies: - supports-color - '@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))': + '@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))': dependencies: tslib: 2.6.2 optionalDependencies: - '@angular/core': 18.0.3(rxjs@7.8.1)(zone.js@0.14.4) + '@angular/core': 18.1.0(rxjs@7.8.1)(zone.js@0.14.4) - '@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)': + '@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)': dependencies: rxjs: 7.8.1 tslib: 2.6.2 zone.js: 0.14.4 - '@angular/forms@18.0.3(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1)': + '@angular/forms@18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1)': dependencies: - '@angular/common': 18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1) - '@angular/core': 18.0.3(rxjs@7.8.1)(zone.js@0.14.4) - '@angular/platform-browser': 18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)) + '@angular/common': 18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1) + '@angular/core': 18.1.0(rxjs@7.8.1)(zone.js@0.14.4) + '@angular/platform-browser': 18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)) rxjs: 7.8.1 tslib: 2.6.2 - '@angular/localize@18.0.3(@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))': + '@angular/localize@18.1.0(@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))': dependencies: - '@angular/compiler': 18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)) - '@angular/compiler-cli': 18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5) + '@angular/compiler': 18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)) + '@angular/compiler-cli': 18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5) '@babel/core': 7.24.7 '@types/babel__core': 7.20.5 fast-glob: 3.3.2 @@ -6592,21 +6885,21 @@ snapshots: transitivePeerDependencies: - supports-color - ? '@angular/material-luxon-adapter@18.0.4(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/material@18.0.4(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/cdk@18.0.4(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/forms@18.0.3(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1))(@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1))(luxon@3.4.4)' + ? '@angular/material-luxon-adapter@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/material@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/cdk@18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/forms@18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1))(@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1))(luxon@3.4.4)' : dependencies: - '@angular/core': 18.0.3(rxjs@7.8.1)(zone.js@0.14.4) - '@angular/material': 18.0.4(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/cdk@18.0.4(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/forms@18.0.3(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1))(@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1) + '@angular/core': 18.1.0(rxjs@7.8.1)(zone.js@0.14.4) + '@angular/material': 18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/cdk@18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/forms@18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1))(@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1) luxon: 3.4.4 tslib: 2.6.2 - ? '@angular/material@18.0.4(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/cdk@18.0.4(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/forms@18.0.3(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1))(@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1)' + ? '@angular/material@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/cdk@18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/forms@18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1))(@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1)' : dependencies: - '@angular/animations': 18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)) - '@angular/cdk': 18.0.4(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1) - '@angular/common': 18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1) - '@angular/core': 18.0.3(rxjs@7.8.1)(zone.js@0.14.4) - '@angular/forms': 18.0.3(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1) - '@angular/platform-browser': 18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)) + '@angular/animations': 18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)) + '@angular/cdk': 18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1) + '@angular/common': 18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1) + '@angular/core': 18.1.0(rxjs@7.8.1)(zone.js@0.14.4) + '@angular/forms': 18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1) + '@angular/platform-browser': 18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)) '@material/animation': 15.0.0-canary.7f224ddd4.0 '@material/auto-init': 15.0.0-canary.7f224ddd4.0 '@material/banner': 15.0.0-canary.7f224ddd4.0 @@ -6658,56 +6951,49 @@ snapshots: rxjs: 7.8.1 tslib: 2.6.2 - '@angular/platform-browser-dynamic@18.0.3(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))': + '@angular/platform-browser-dynamic@18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))': dependencies: - '@angular/common': 18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1) - '@angular/compiler': 18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)) - '@angular/core': 18.0.3(rxjs@7.8.1)(zone.js@0.14.4) - '@angular/platform-browser': 18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)) + '@angular/common': 18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1) + '@angular/compiler': 18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)) + '@angular/core': 18.1.0(rxjs@7.8.1)(zone.js@0.14.4) + '@angular/platform-browser': 18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)) tslib: 2.6.2 - '@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))': + '@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))': dependencies: - '@angular/common': 18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1) - '@angular/core': 18.0.3(rxjs@7.8.1)(zone.js@0.14.4) + '@angular/common': 18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1) + '@angular/core': 18.1.0(rxjs@7.8.1)(zone.js@0.14.4) tslib: 2.6.2 optionalDependencies: - '@angular/animations': 18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)) + '@angular/animations': 18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)) - '@angular/router@18.0.3(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1)': + '@angular/router@18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(rxjs@7.8.1)': dependencies: - '@angular/common': 18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1) - '@angular/core': 18.0.3(rxjs@7.8.1)(zone.js@0.14.4) - '@angular/platform-browser': 18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)) + '@angular/common': 18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1) + '@angular/core': 18.1.0(rxjs@7.8.1)(zone.js@0.14.4) + '@angular/platform-browser': 18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)) rxjs: 7.8.1 tslib: 2.6.2 - '@babel/code-frame@7.24.6': - dependencies: - '@babel/highlight': 7.24.6 - picocolors: 1.0.0 - '@babel/code-frame@7.24.7': dependencies: '@babel/highlight': 7.24.7 picocolors: 1.0.0 - '@babel/compat-data@7.24.6': {} - '@babel/compat-data@7.24.7': {} '@babel/core@7.24.5': dependencies: '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.24.6 - '@babel/generator': 7.24.6 - '@babel/helper-compilation-targets': 7.24.6 - '@babel/helper-module-transforms': 7.24.6(@babel/core@7.24.5) - '@babel/helpers': 7.24.6 - '@babel/parser': 7.24.6 - '@babel/template': 7.24.6 - '@babel/traverse': 7.24.6 - '@babel/types': 7.24.6 + '@babel/code-frame': 7.24.7 + '@babel/generator': 7.24.7 + '@babel/helper-compilation-targets': 7.24.7 + '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.5) + '@babel/helpers': 7.24.7 + '@babel/parser': 7.24.7 + '@babel/template': 7.24.7 + '@babel/traverse': 7.24.7 + '@babel/types': 7.24.7 convert-source-map: 2.0.0 debug: 4.3.4 gensync: 1.0.0-beta.2 @@ -6743,13 +7029,6 @@ snapshots: '@jridgewell/trace-mapping': 0.3.25 jsesc: 2.5.2 - '@babel/generator@7.24.6': - dependencies: - '@babel/types': 7.24.6 - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 - jsesc: 2.5.2 - '@babel/generator@7.24.7': dependencies: '@babel/types': 7.24.7 @@ -6759,9 +7038,9 @@ snapshots: '@babel/helper-annotate-as-pure@7.22.5': dependencies: - '@babel/types': 7.24.6 + '@babel/types': 7.24.7 - '@babel/helper-annotate-as-pure@7.24.6': + '@babel/helper-annotate-as-pure@7.24.7': dependencies: '@babel/types': 7.24.7 @@ -6769,14 +7048,6 @@ snapshots: dependencies: '@babel/types': 7.24.7 - '@babel/helper-compilation-targets@7.24.6': - dependencies: - '@babel/compat-data': 7.24.6 - '@babel/helper-validator-option': 7.24.6 - browserslist: 4.23.0 - lru-cache: 5.1.1 - semver: 6.3.1 - '@babel/helper-compilation-targets@7.24.7': dependencies: '@babel/compat-data': 7.24.7 @@ -6788,7 +7059,7 @@ snapshots: '@babel/helper-create-class-features-plugin@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-annotate-as-pure': 7.24.6 + '@babel/helper-annotate-as-pure': 7.24.7 '@babel/helper-environment-visitor': 7.24.7 '@babel/helper-function-name': 7.24.7 '@babel/helper-member-expression-to-functions': 7.24.6 @@ -6801,7 +7072,7 @@ snapshots: '@babel/helper-create-regexp-features-plugin@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-annotate-as-pure': 7.24.6 + '@babel/helper-annotate-as-pure': 7.24.7 regexpu-core: 5.3.2 semver: 6.3.1 @@ -6809,33 +7080,22 @@ snapshots: dependencies: '@babel/core': 7.24.5 '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 debug: 4.3.4 lodash.debounce: 4.0.8 resolve: 1.22.8 transitivePeerDependencies: - supports-color - '@babel/helper-environment-visitor@7.24.6': {} - '@babel/helper-environment-visitor@7.24.7': dependencies: '@babel/types': 7.24.7 - '@babel/helper-function-name@7.24.6': - dependencies: - '@babel/template': 7.24.6 - '@babel/types': 7.24.6 - '@babel/helper-function-name@7.24.7': dependencies: '@babel/template': 7.24.7 '@babel/types': 7.24.7 - '@babel/helper-hoist-variables@7.24.6': - dependencies: - '@babel/types': 7.24.6 - '@babel/helper-hoist-variables@7.24.7': dependencies: '@babel/types': 7.24.7 @@ -6844,10 +7104,6 @@ snapshots: dependencies: '@babel/types': 7.24.7 - '@babel/helper-module-imports@7.24.6': - dependencies: - '@babel/types': 7.24.6 - '@babel/helper-module-imports@7.24.7': dependencies: '@babel/traverse': 7.24.7 @@ -6855,15 +7111,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.24.6(@babel/core@7.24.5)': - dependencies: - '@babel/core': 7.24.5 - '@babel/helper-environment-visitor': 7.24.6 - '@babel/helper-module-imports': 7.24.6 - '@babel/helper-simple-access': 7.24.6 - '@babel/helper-split-export-declaration': 7.24.6 - '@babel/helper-validator-identifier': 7.24.6 - '@babel/helper-module-transforms@7.24.7(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 @@ -6892,10 +7139,12 @@ snapshots: '@babel/helper-plugin-utils@7.24.6': {} + '@babel/helper-plugin-utils@7.24.7': {} + '@babel/helper-remap-async-to-generator@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-annotate-as-pure': 7.24.6 + '@babel/helper-annotate-as-pure': 7.24.7 '@babel/helper-environment-visitor': 7.24.7 '@babel/helper-wrap-function': 7.24.6 @@ -6906,10 +7155,6 @@ snapshots: '@babel/helper-member-expression-to-functions': 7.24.6 '@babel/helper-optimise-call-expression': 7.24.6 - '@babel/helper-simple-access@7.24.6': - dependencies: - '@babel/types': 7.24.6 - '@babel/helper-simple-access@7.24.7': dependencies: '@babel/traverse': 7.24.7 @@ -6923,26 +7168,16 @@ snapshots: '@babel/helper-split-export-declaration@7.24.5': dependencies: - '@babel/types': 7.24.6 - - '@babel/helper-split-export-declaration@7.24.6': - dependencies: - '@babel/types': 7.24.6 + '@babel/types': 7.24.7 '@babel/helper-split-export-declaration@7.24.7': dependencies: '@babel/types': 7.24.7 - '@babel/helper-string-parser@7.24.6': {} - '@babel/helper-string-parser@7.24.7': {} - '@babel/helper-validator-identifier@7.24.6': {} - '@babel/helper-validator-identifier@7.24.7': {} - '@babel/helper-validator-option@7.24.6': {} - '@babel/helper-validator-option@7.24.7': {} '@babel/helper-wrap-function@7.24.6': @@ -6951,23 +7186,11 @@ snapshots: '@babel/template': 7.24.7 '@babel/types': 7.24.7 - '@babel/helpers@7.24.6': - dependencies: - '@babel/template': 7.24.6 - '@babel/types': 7.24.6 - '@babel/helpers@7.24.7': dependencies: '@babel/template': 7.24.7 '@babel/types': 7.24.7 - '@babel/highlight@7.24.6': - dependencies: - '@babel/helper-validator-identifier': 7.24.6 - chalk: 2.4.2 - js-tokens: 4.0.0 - picocolors: 1.0.0 - '@babel/highlight@7.24.7': dependencies: '@babel/helper-validator-identifier': 7.24.7 @@ -6975,10 +7198,6 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.0.0 - '@babel/parser@7.24.6': - dependencies: - '@babel/types': 7.24.6 - '@babel/parser@7.24.7': dependencies: '@babel/types': 7.24.7 @@ -6987,17 +7206,17 @@ snapshots: dependencies: '@babel/core': 7.24.5 '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/helper-skip-transparent-expression-wrappers': 7.24.6 '@babel/plugin-transform-optional-chaining': 7.24.6(@babel/core@7.24.5) @@ -7005,7 +7224,7 @@ snapshots: dependencies: '@babel/core': 7.24.5 '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.24.5)': dependencies: @@ -7016,9 +7235,14 @@ snapshots: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.6 - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.24.5)': + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.24.7)': dependencies: - '@babel/core': 7.24.5 + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.6 + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 '@babel/helper-plugin-utils': 7.24.6 '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.24.5)': @@ -7026,44 +7250,64 @@ snapshots: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.6 + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.6 + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-syntax-import-assertions@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-import-attributes@7.24.6(@babel/core@7.24.5)': + '@babel/plugin-syntax-import-attributes@7.24.7(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-syntax-import-attributes@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.6 + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.6 + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.6 - '@babel/plugin-syntax-jsx@7.22.5(@babel/core@7.24.5)': + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.24.7)': dependencies: - '@babel/core': 7.24.5 + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.6 + + '@babel/plugin-syntax-jsx@7.22.5(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 '@babel/helper-plugin-utils': 7.24.6 '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.24.5)': @@ -7071,62 +7315,97 @@ snapshots: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.6 + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.6 + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.6 + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.6 + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.6 + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.6 + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.6 + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.6 + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.6 + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.6 + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.6 + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.6 + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.24.6 - '@babel/plugin-syntax-typescript@7.22.5(@babel/core@7.24.5)': + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.24.7)': dependencies: - '@babel/core': 7.24.5 + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.6 + + '@babel/plugin-syntax-typescript@7.22.5(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 '@babel/helper-plugin-utils': 7.24.6 '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 '@babel/helper-create-regexp-features-plugin': 7.24.6(@babel/core@7.24.5) - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-transform-arrow-functions@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-transform-async-generator-functions@7.24.3(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/helper-remap-async-to-generator': 7.24.6(@babel/core@7.24.5) '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.5) @@ -7134,7 +7413,7 @@ snapshots: dependencies: '@babel/core': 7.24.5 '@babel/helper-module-imports': 7.24.7 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/helper-remap-async-to-generator': 7.24.6(@babel/core@7.24.5) transitivePeerDependencies: - supports-color @@ -7142,34 +7421,34 @@ snapshots: '@babel/plugin-transform-block-scoped-functions@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-transform-block-scoping@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-transform-class-properties@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 '@babel/helper-create-class-features-plugin': 7.24.6(@babel/core@7.24.5) - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-transform-class-static-block@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 '@babel/helper-create-class-features-plugin': 7.24.6(@babel/core@7.24.5) - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.24.5) '@babel/plugin-transform-classes@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-annotate-as-pure': 7.24.6 + '@babel/helper-annotate-as-pure': 7.24.7 '@babel/helper-compilation-targets': 7.24.7 '@babel/helper-environment-visitor': 7.24.7 '@babel/helper-function-name': 7.24.7 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/helper-replace-supers': 7.24.6(@babel/core@7.24.5) '@babel/helper-split-export-declaration': 7.24.7 globals: 11.12.0 @@ -7177,47 +7456,47 @@ snapshots: '@babel/plugin-transform-computed-properties@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/template': 7.24.7 '@babel/plugin-transform-destructuring@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-transform-dotall-regex@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 '@babel/helper-create-regexp-features-plugin': 7.24.6(@babel/core@7.24.5) - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-transform-duplicate-keys@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-transform-dynamic-import@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.5) '@babel/plugin-transform-exponentiation-operator@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 '@babel/helper-builder-binary-assignment-operator-visitor': 7.24.6 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-transform-export-namespace-from@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.5) '@babel/plugin-transform-for-of@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/helper-skip-transparent-expression-wrappers': 7.24.6 '@babel/plugin-transform-function-name@7.24.6(@babel/core@7.24.5)': @@ -7225,35 +7504,35 @@ snapshots: '@babel/core': 7.24.5 '@babel/helper-compilation-targets': 7.24.7 '@babel/helper-function-name': 7.24.7 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-transform-json-strings@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.5) '@babel/plugin-transform-literals@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-transform-logical-assignment-operators@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.5) '@babel/plugin-transform-member-expression-literals@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-transform-modules-amd@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.5) - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 transitivePeerDependencies: - supports-color @@ -7261,7 +7540,7 @@ snapshots: dependencies: '@babel/core': 7.24.5 '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.5) - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/helper-simple-access': 7.24.7 transitivePeerDependencies: - supports-color @@ -7271,7 +7550,7 @@ snapshots: '@babel/core': 7.24.5 '@babel/helper-hoist-variables': 7.24.7 '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.5) - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/helper-validator-identifier': 7.24.7 transitivePeerDependencies: - supports-color @@ -7280,7 +7559,7 @@ snapshots: dependencies: '@babel/core': 7.24.5 '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.5) - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 transitivePeerDependencies: - supports-color @@ -7288,92 +7567,92 @@ snapshots: dependencies: '@babel/core': 7.24.5 '@babel/helper-create-regexp-features-plugin': 7.24.6(@babel/core@7.24.5) - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-transform-new-target@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-transform-nullish-coalescing-operator@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.5) '@babel/plugin-transform-numeric-separator@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.5) '@babel/plugin-transform-object-rest-spread@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.5) '@babel/plugin-transform-parameters': 7.24.6(@babel/core@7.24.5) '@babel/plugin-transform-object-super@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/helper-replace-supers': 7.24.6(@babel/core@7.24.5) '@babel/plugin-transform-optional-catch-binding@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.5) '@babel/plugin-transform-optional-chaining@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/helper-skip-transparent-expression-wrappers': 7.24.6 '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.5) '@babel/plugin-transform-parameters@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-transform-private-methods@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 '@babel/helper-create-class-features-plugin': 7.24.6(@babel/core@7.24.5) - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-transform-private-property-in-object@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-annotate-as-pure': 7.24.6 + '@babel/helper-annotate-as-pure': 7.24.7 '@babel/helper-create-class-features-plugin': 7.24.6(@babel/core@7.24.5) - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.24.5) '@babel/plugin-transform-property-literals@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-transform-regenerator@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 regenerator-transform: 0.15.2 '@babel/plugin-transform-reserved-words@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-transform-runtime@7.24.3(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 '@babel/helper-module-imports': 7.24.7 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.24.5) babel-plugin-polyfill-corejs3: 0.10.4(@babel/core@7.24.5) babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.24.5) @@ -7384,58 +7663,58 @@ snapshots: '@babel/plugin-transform-shorthand-properties@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-transform-spread@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/helper-skip-transparent-expression-wrappers': 7.24.6 '@babel/plugin-transform-sticky-regex@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-transform-template-literals@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-transform-typeof-symbol@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-transform-unicode-escapes@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-transform-unicode-property-regex@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 '@babel/helper-create-regexp-features-plugin': 7.24.6(@babel/core@7.24.5) - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-transform-unicode-regex@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 '@babel/helper-create-regexp-features-plugin': 7.24.6(@babel/core@7.24.5) - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/plugin-transform-unicode-sets-regex@7.24.6(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 '@babel/helper-create-regexp-features-plugin': 7.24.6(@babel/core@7.24.5) - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/preset-env@7.24.5(@babel/core@7.24.5)': dependencies: '@babel/compat-data': 7.24.7 '@babel/core': 7.24.5 '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/helper-validator-option': 7.24.7 '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.24.6(@babel/core@7.24.5) '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.24.6(@babel/core@7.24.5) @@ -7448,7 +7727,7 @@ snapshots: '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.5) '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.5) '@babel/plugin-syntax-import-assertions': 7.24.6(@babel/core@7.24.5) - '@babel/plugin-syntax-import-attributes': 7.24.6(@babel/core@7.24.5) + '@babel/plugin-syntax-import-attributes': 7.24.7(@babel/core@7.24.5) '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.5) '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.5) '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.5) @@ -7520,7 +7799,7 @@ snapshots: '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.24.5)': dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.6 + '@babel/helper-plugin-utils': 7.24.7 '@babel/types': 7.24.7 esutils: 2.0.3 @@ -7530,33 +7809,12 @@ snapshots: dependencies: regenerator-runtime: 0.14.1 - '@babel/template@7.24.6': - dependencies: - '@babel/code-frame': 7.24.6 - '@babel/parser': 7.24.6 - '@babel/types': 7.24.6 - '@babel/template@7.24.7': dependencies: '@babel/code-frame': 7.24.7 '@babel/parser': 7.24.7 '@babel/types': 7.24.7 - '@babel/traverse@7.24.6': - dependencies: - '@babel/code-frame': 7.24.6 - '@babel/generator': 7.24.6 - '@babel/helper-environment-visitor': 7.24.6 - '@babel/helper-function-name': 7.24.6 - '@babel/helper-hoist-variables': 7.24.6 - '@babel/helper-split-export-declaration': 7.24.6 - '@babel/parser': 7.24.6 - '@babel/types': 7.24.6 - debug: 4.3.4 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - '@babel/traverse@7.24.7': dependencies: '@babel/code-frame': 7.24.7 @@ -7572,12 +7830,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/types@7.24.6': - dependencies: - '@babel/helper-string-parser': 7.24.6 - '@babel/helper-validator-identifier': 7.24.6 - to-fast-properties: 2.0.0 - '@babel/types@7.24.7': dependencies: '@babel/helper-string-parser': 7.24.7 @@ -7598,52 +7850,79 @@ snapshots: '@esbuild/aix-ppc64@0.21.3': optional: true + '@esbuild/aix-ppc64@0.21.5': + optional: true + '@esbuild/android-arm64@0.20.1': optional: true '@esbuild/android-arm64@0.21.3': optional: true + '@esbuild/android-arm64@0.21.5': + optional: true + '@esbuild/android-arm@0.20.1': optional: true '@esbuild/android-arm@0.21.3': optional: true + '@esbuild/android-arm@0.21.5': + optional: true + '@esbuild/android-x64@0.20.1': optional: true '@esbuild/android-x64@0.21.3': optional: true + '@esbuild/android-x64@0.21.5': + optional: true + '@esbuild/darwin-arm64@0.20.1': optional: true '@esbuild/darwin-arm64@0.21.3': optional: true + '@esbuild/darwin-arm64@0.21.5': + optional: true + '@esbuild/darwin-x64@0.20.1': optional: true '@esbuild/darwin-x64@0.21.3': optional: true + '@esbuild/darwin-x64@0.21.5': + optional: true + '@esbuild/freebsd-arm64@0.20.1': optional: true '@esbuild/freebsd-arm64@0.21.3': optional: true + '@esbuild/freebsd-arm64@0.21.5': + optional: true + '@esbuild/freebsd-x64@0.20.1': optional: true '@esbuild/freebsd-x64@0.21.3': optional: true + '@esbuild/freebsd-x64@0.21.5': + optional: true + '@esbuild/linux-arm64@0.20.1': optional: true - '@esbuild/linux-arm64@0.21.3': + '@esbuild/linux-arm64@0.21.3': + optional: true + + '@esbuild/linux-arm64@0.21.5': optional: true '@esbuild/linux-arm@0.20.1': @@ -7652,84 +7931,126 @@ snapshots: '@esbuild/linux-arm@0.21.3': optional: true + '@esbuild/linux-arm@0.21.5': + optional: true + '@esbuild/linux-ia32@0.20.1': optional: true '@esbuild/linux-ia32@0.21.3': optional: true + '@esbuild/linux-ia32@0.21.5': + optional: true + '@esbuild/linux-loong64@0.20.1': optional: true '@esbuild/linux-loong64@0.21.3': optional: true + '@esbuild/linux-loong64@0.21.5': + optional: true + '@esbuild/linux-mips64el@0.20.1': optional: true '@esbuild/linux-mips64el@0.21.3': optional: true + '@esbuild/linux-mips64el@0.21.5': + optional: true + '@esbuild/linux-ppc64@0.20.1': optional: true '@esbuild/linux-ppc64@0.21.3': optional: true + '@esbuild/linux-ppc64@0.21.5': + optional: true + '@esbuild/linux-riscv64@0.20.1': optional: true '@esbuild/linux-riscv64@0.21.3': optional: true + '@esbuild/linux-riscv64@0.21.5': + optional: true + '@esbuild/linux-s390x@0.20.1': optional: true '@esbuild/linux-s390x@0.21.3': optional: true + '@esbuild/linux-s390x@0.21.5': + optional: true + '@esbuild/linux-x64@0.20.1': optional: true '@esbuild/linux-x64@0.21.3': optional: true + '@esbuild/linux-x64@0.21.5': + optional: true + '@esbuild/netbsd-x64@0.20.1': optional: true '@esbuild/netbsd-x64@0.21.3': optional: true + '@esbuild/netbsd-x64@0.21.5': + optional: true + '@esbuild/openbsd-x64@0.20.1': optional: true '@esbuild/openbsd-x64@0.21.3': optional: true + '@esbuild/openbsd-x64@0.21.5': + optional: true + '@esbuild/sunos-x64@0.20.1': optional: true '@esbuild/sunos-x64@0.21.3': optional: true + '@esbuild/sunos-x64@0.21.5': + optional: true + '@esbuild/win32-arm64@0.20.1': optional: true '@esbuild/win32-arm64@0.21.3': optional: true + '@esbuild/win32-arm64@0.21.5': + optional: true + '@esbuild/win32-ia32@0.20.1': optional: true '@esbuild/win32-ia32@0.21.3': optional: true + '@esbuild/win32-ia32@0.21.5': + optional: true + '@esbuild/win32-x64@0.20.1': optional: true '@esbuild/win32-x64@0.21.3': optional: true + '@esbuild/win32-x64@0.21.5': + optional: true + '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)': dependencies: eslint: 8.57.0 @@ -7743,7 +8064,7 @@ snapshots: debug: 4.3.4 espree: 9.6.1 globals: 13.20.0 - ignore: 5.2.4 + ignore: 5.3.1 import-fresh: 3.3.0 js-yaml: 4.1.0 minimatch: 3.1.2 @@ -7765,8 +8086,110 @@ snapshots: '@humanwhocodes/object-schema@2.0.3': {} + '@inquirer/checkbox@2.3.10': + dependencies: + '@inquirer/core': 9.0.2 + '@inquirer/figures': 1.0.3 + '@inquirer/type': 1.4.0 + ansi-escapes: 4.3.2 + yoctocolors-cjs: 2.1.2 + + '@inquirer/confirm@3.1.11': + dependencies: + '@inquirer/core': 8.2.4 + '@inquirer/type': 1.4.0 + + '@inquirer/confirm@3.1.14': + dependencies: + '@inquirer/core': 9.0.2 + '@inquirer/type': 1.4.0 + + '@inquirer/core@8.2.4': + dependencies: + '@inquirer/figures': 1.0.3 + '@inquirer/type': 1.4.0 + '@types/mute-stream': 0.0.4 + '@types/node': 20.14.10 + '@types/wrap-ansi': 3.0.0 + ansi-escapes: 4.3.2 + cli-spinners: 2.9.2 + cli-width: 4.1.0 + mute-stream: 1.0.0 + picocolors: 1.0.1 + signal-exit: 4.1.0 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + + '@inquirer/core@9.0.2': + dependencies: + '@inquirer/figures': 1.0.3 + '@inquirer/type': 1.4.0 + '@types/mute-stream': 0.0.4 + '@types/node': 20.14.10 + '@types/wrap-ansi': 3.0.0 + ansi-escapes: 4.3.2 + cli-spinners: 2.9.2 + cli-width: 4.1.0 + mute-stream: 1.0.0 + signal-exit: 4.1.0 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.2 + + '@inquirer/editor@2.1.14': + dependencies: + '@inquirer/core': 9.0.2 + '@inquirer/type': 1.4.0 + external-editor: 3.1.0 + + '@inquirer/expand@2.1.14': + dependencies: + '@inquirer/core': 9.0.2 + '@inquirer/type': 1.4.0 + yoctocolors-cjs: 2.1.2 + '@inquirer/figures@1.0.3': {} + '@inquirer/input@2.2.1': + dependencies: + '@inquirer/core': 9.0.2 + '@inquirer/type': 1.4.0 + + '@inquirer/password@2.1.14': + dependencies: + '@inquirer/core': 9.0.2 + '@inquirer/type': 1.4.0 + ansi-escapes: 4.3.2 + + '@inquirer/prompts@5.0.7': + dependencies: + '@inquirer/checkbox': 2.3.10 + '@inquirer/confirm': 3.1.14 + '@inquirer/editor': 2.1.14 + '@inquirer/expand': 2.1.14 + '@inquirer/input': 2.2.1 + '@inquirer/password': 2.1.14 + '@inquirer/rawlist': 2.1.14 + '@inquirer/select': 2.3.10 + + '@inquirer/rawlist@2.1.14': + dependencies: + '@inquirer/core': 9.0.2 + '@inquirer/type': 1.4.0 + yoctocolors-cjs: 2.1.2 + + '@inquirer/select@2.3.10': + dependencies: + '@inquirer/core': 9.0.2 + '@inquirer/figures': 1.0.3 + '@inquirer/type': 1.4.0 + ansi-escapes: 4.3.2 + yoctocolors-cjs: 2.1.2 + + '@inquirer/type@1.4.0': + dependencies: + mute-stream: 1.0.0 + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -7921,7 +8344,7 @@ snapshots: '@jest/transform@29.7.0': dependencies: - '@babel/core': 7.24.5 + '@babel/core': 7.24.7 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.25 babel-plugin-istanbul: 6.1.1 @@ -8000,25 +8423,48 @@ snapshots: '@leichtgewicht/ip-codec@2.0.4': {} + '@listr2/prompt-adapter-inquirer@2.0.13(@inquirer/prompts@5.0.7)': + dependencies: + '@inquirer/prompts': 5.0.7 + '@inquirer/type': 1.4.0 + '@ljharb/through@2.3.13': dependencies: call-bind: 1.0.7 + '@lmdb/lmdb-darwin-arm64@3.0.12': + optional: true + '@lmdb/lmdb-darwin-arm64@3.0.8': optional: true + '@lmdb/lmdb-darwin-x64@3.0.12': + optional: true + '@lmdb/lmdb-darwin-x64@3.0.8': optional: true + '@lmdb/lmdb-linux-arm64@3.0.12': + optional: true + '@lmdb/lmdb-linux-arm64@3.0.8': optional: true + '@lmdb/lmdb-linux-arm@3.0.12': + optional: true + '@lmdb/lmdb-linux-arm@3.0.8': optional: true + '@lmdb/lmdb-linux-x64@3.0.12': + optional: true + '@lmdb/lmdb-linux-x64@3.0.8': optional: true + '@lmdb/lmdb-win32-x64@3.0.12': + optional: true + '@lmdb/lmdb-win32-x64@3.0.8': optional: true @@ -8592,9 +9038,9 @@ snapshots: '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.2': optional: true - '@ngtools/webpack@18.0.2(@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(typescript@5.4.5)(webpack@5.91.0(esbuild@0.21.3))': + '@ngtools/webpack@18.0.2(@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(typescript@5.4.5)(webpack@5.91.0(esbuild@0.21.3))': dependencies: - '@angular/compiler-cli': 18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5) + '@angular/compiler-cli': 18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5) typescript: 5.4.5 webpack: 5.91.0(esbuild@0.21.3) @@ -8604,18 +9050,18 @@ snapshots: rxjs: 7.8.1 tslib: 2.6.2 - '@ngx-grpc/core@3.1.2(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@ngx-grpc/common@3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1))(rxjs@7.8.1)': + '@ngx-grpc/core@3.1.2(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@ngx-grpc/common@3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1))(rxjs@7.8.1)': dependencies: - '@angular/core': 18.0.3(rxjs@7.8.1)(zone.js@0.14.4) + '@angular/core': 18.1.0(rxjs@7.8.1)(zone.js@0.14.4) '@ngx-grpc/common': 3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1) rxjs: 7.8.1 tslib: 2.6.2 - '@ngx-grpc/grpc-web-client@3.1.2(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@ngx-grpc/common@3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1))(@ngx-grpc/core@3.1.2(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@ngx-grpc/common@3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1))(rxjs@7.8.1))(grpc-web@1.4.2)(rxjs@7.8.1)': + '@ngx-grpc/grpc-web-client@3.1.2(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@ngx-grpc/common@3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1))(@ngx-grpc/core@3.1.2(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@ngx-grpc/common@3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1))(rxjs@7.8.1))(grpc-web@1.4.2)(rxjs@7.8.1)': dependencies: - '@angular/core': 18.0.3(rxjs@7.8.1)(zone.js@0.14.4) + '@angular/core': 18.1.0(rxjs@7.8.1)(zone.js@0.14.4) '@ngx-grpc/common': 3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1) - '@ngx-grpc/core': 3.1.2(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@ngx-grpc/common@3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1))(rxjs@7.8.1) + '@ngx-grpc/core': 3.1.2(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@ngx-grpc/common@3.1.2(google-protobuf@3.21.2)(rxjs@7.8.1))(rxjs@7.8.1) grpc-web: 1.4.2 rxjs: 7.8.1 tslib: 2.6.2 @@ -8819,11 +9265,11 @@ snapshots: transitivePeerDependencies: - chokidar - '@schematics/angular@18.0.4(chokidar@3.6.0)': + '@schematics/angular@18.1.0(chokidar@3.6.0)': dependencies: - '@angular-devkit/core': 18.0.4(chokidar@3.6.0) - '@angular-devkit/schematics': 18.0.4(chokidar@3.6.0) - jsonc-parser: 3.2.1 + '@angular-devkit/core': 18.1.0(chokidar@3.6.0) + '@angular-devkit/schematics': 18.1.0(chokidar@3.6.0) + jsonc-parser: 3.3.1 transitivePeerDependencies: - chokidar @@ -8888,42 +9334,42 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.24.6 - '@babel/types': 7.24.6 + '@babel/parser': 7.24.7 + '@babel/types': 7.24.7 '@types/babel__generator': 7.6.4 '@types/babel__template': 7.4.1 '@types/babel__traverse': 7.20.1 '@types/babel__generator@7.6.4': dependencies: - '@babel/types': 7.24.6 + '@babel/types': 7.24.7 '@types/babel__template@7.4.1': dependencies: - '@babel/parser': 7.24.6 - '@babel/types': 7.24.6 + '@babel/parser': 7.24.7 + '@babel/types': 7.24.7 '@types/babel__traverse@7.20.1': dependencies: - '@babel/types': 7.24.6 + '@babel/types': 7.24.7 '@types/body-parser@1.19.2': dependencies: '@types/connect': 3.4.35 - '@types/node': 20.12.7 + '@types/node': 20.14.10 '@types/bonjour@3.5.13': dependencies: - '@types/node': 20.12.7 + '@types/node': 20.14.10 '@types/connect-history-api-fallback@1.5.4': dependencies: '@types/express-serve-static-core': 4.17.35 - '@types/node': 20.12.7 + '@types/node': 20.14.10 '@types/connect@3.4.35': dependencies: - '@types/node': 20.12.7 + '@types/node': 20.14.10 '@types/eslint-scope@3.7.4': dependencies: @@ -8939,7 +9385,7 @@ snapshots: '@types/express-serve-static-core@4.17.35': dependencies: - '@types/node': 20.12.7 + '@types/node': 20.14.10 '@types/qs': 6.9.7 '@types/range-parser': 1.2.4 '@types/send': 0.17.1 @@ -8961,7 +9407,7 @@ snapshots: '@types/http-proxy@1.17.11': dependencies: - '@types/node': 20.12.7 + '@types/node': 20.14.10 '@types/istanbul-lib-coverage@2.0.4': {} @@ -8992,14 +9438,22 @@ snapshots: '@types/mime@1.3.2': {} + '@types/mute-stream@0.0.4': + dependencies: + '@types/node': 20.14.10 + '@types/node-forge@1.3.11': dependencies: - '@types/node': 20.12.7 + '@types/node': 20.14.10 '@types/node@20.12.7': dependencies: undici-types: 5.26.5 + '@types/node@20.14.10': + dependencies: + undici-types: 5.26.5 + '@types/qs@6.9.7': {} '@types/range-parser@1.2.4': {} @@ -9011,7 +9465,7 @@ snapshots: '@types/send@0.17.1': dependencies: '@types/mime': 1.3.2 - '@types/node': 20.12.7 + '@types/node': 20.14.10 '@types/serve-index@1.9.4': dependencies: @@ -9020,12 +9474,12 @@ snapshots: '@types/serve-static@1.15.7': dependencies: '@types/http-errors': 2.0.1 - '@types/node': 20.12.7 + '@types/node': 20.14.10 '@types/send': 0.17.1 '@types/sockjs@0.3.36': dependencies: - '@types/node': 20.12.7 + '@types/node': 20.14.10 '@types/stack-utils@2.0.1': {} @@ -9033,9 +9487,11 @@ snapshots: '@types/ungap__structured-clone@0.3.3': {} + '@types/wrap-ansi@3.0.0': {} + '@types/ws@8.5.10': dependencies: - '@types/node': 20.12.7 + '@types/node': 20.14.10 '@types/yargs-parser@21.0.0': {} @@ -9173,6 +9629,10 @@ snapshots: dependencies: vite: 5.2.11(@types/node@20.12.7)(less@4.2.0)(sass@1.77.2)(terser@5.31.0) + '@vitejs/plugin-basic-ssl@1.1.0(vite@5.3.2(@types/node@20.12.7)(less@4.2.0)(sass@1.77.6)(terser@5.31.0))': + dependencies: + vite: 5.3.2(@types/node@20.12.7)(less@4.2.0)(sass@1.77.6)(terser@5.31.0) + '@webassemblyjs/ast@1.12.1': dependencies: '@webassemblyjs/helper-numbers': 1.11.6 @@ -9318,21 +9778,25 @@ snapshots: optionalDependencies: ajv: 8.12.0 - ajv-formats@2.1.1(ajv@8.13.0): + ajv-formats@2.1.1(ajv@8.16.0): optionalDependencies: - ajv: 8.13.0 + ajv: 8.16.0 ajv-formats@3.0.1(ajv@8.13.0): optionalDependencies: ajv: 8.13.0 + ajv-formats@3.0.1(ajv@8.16.0): + optionalDependencies: + ajv: 8.16.0 + ajv-keywords@3.5.2(ajv@6.12.6): dependencies: ajv: 6.12.6 - ajv-keywords@5.1.0(ajv@8.13.0): + ajv-keywords@5.1.0(ajv@8.16.0): dependencies: - ajv: 8.13.0 + ajv: 8.16.0 fast-deep-equal: 3.1.3 ajv@6.12.6: @@ -9356,12 +9820,21 @@ snapshots: require-from-string: 2.0.2 uri-js: 4.4.1 + ajv@8.16.0: + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + ansi-colors@4.1.3: {} ansi-escapes@4.3.2: dependencies: type-fest: 0.21.3 + ansi-escapes@6.2.1: {} + ansi-html-community@0.0.8: {} ansi-regex@5.0.1: {} @@ -9459,7 +9932,7 @@ snapshots: caniuse-lite: 1.0.30001627 fraction.js: 4.3.7 normalize-range: 0.1.2 - picocolors: 1.0.0 + picocolors: 1.0.1 postcss: 8.4.38 postcss-value-parser: 4.2.0 @@ -9479,13 +9952,13 @@ snapshots: dependencies: dequal: 2.0.3 - babel-jest@29.7.0(@babel/core@7.24.5): + babel-jest@29.7.0(@babel/core@7.24.7): dependencies: - '@babel/core': 7.24.5 + '@babel/core': 7.24.7 '@jest/transform': 29.7.0 '@types/babel__core': 7.20.5 babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.24.5) + babel-preset-jest: 29.6.3(@babel/core@7.24.7) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 @@ -9511,8 +9984,8 @@ snapshots: babel-plugin-jest-hoist@29.6.3: dependencies: - '@babel/template': 7.24.6 - '@babel/types': 7.24.6 + '@babel/template': 7.24.7 + '@babel/types': 7.24.7 '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.20.1 @@ -9540,27 +10013,27 @@ snapshots: transitivePeerDependencies: - supports-color - babel-preset-current-node-syntax@1.0.1(@babel/core@7.24.5): + babel-preset-current-node-syntax@1.0.1(@babel/core@7.24.7): dependencies: - '@babel/core': 7.24.5 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.5) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.24.5) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.24.5) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.5) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.5) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.5) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.5) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.5) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.5) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.5) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.5) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.5) - - babel-preset-jest@29.6.3(@babel/core@7.24.5): + '@babel/core': 7.24.7 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.7) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.24.7) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.24.7) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.7) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.7) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.7) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.7) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.7) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.7) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.7) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.7) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.7) + + babel-preset-jest@29.6.3(@babel/core@7.24.7): dependencies: - '@babel/core': 7.24.5 + '@babel/core': 7.24.7 babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.24.5) + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.24.7) balanced-match@1.0.2: {} @@ -9762,10 +10235,21 @@ snapshots: dependencies: restore-cursor: 3.1.0 + cli-cursor@4.0.0: + dependencies: + restore-cursor: 4.0.0 + cli-spinners@2.6.1: {} cli-spinners@2.9.0: {} + cli-spinners@2.9.2: {} + + cli-truncate@4.0.0: + dependencies: + slice-ansi: 5.0.0 + string-width: 7.2.0 + cli-width@4.1.0: {} cliui@8.0.1: @@ -9902,6 +10386,16 @@ snapshots: postcss: 8.4.38 postcss-media-query-parser: 0.2.3 + critters@0.0.24: + dependencies: + chalk: 4.1.2 + css-select: 5.1.0 + dom-serializer: 2.0.0 + domhandler: 5.0.3 + htmlparser2: 8.0.2 + postcss: 8.4.38 + postcss-media-query-parser: 0.2.3 + cross-spawn@7.0.3: dependencies: path-key: 3.1.1 @@ -10103,6 +10597,8 @@ snapshots: emittery@0.13.1: {} + emoji-regex@10.3.0: {} + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -10286,6 +10782,32 @@ snapshots: '@esbuild/win32-ia32': 0.21.3 '@esbuild/win32-x64': 0.21.3 + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + escalade@3.1.2: {} escape-html@1.0.3: {} @@ -10453,6 +10975,8 @@ snapshots: eventemitter3@4.0.7: {} + eventemitter3@5.0.1: {} + events@3.3.0: {} execa@5.1.1: @@ -10687,6 +11211,8 @@ snapshots: get-caller-file@2.0.5: {} + get-east-asian-width@1.2.0: {} + get-intrinsic@1.2.1: dependencies: function-bind: 1.1.1 @@ -10771,7 +11297,7 @@ snapshots: array-union: 2.1.0 dir-glob: 3.0.1 fast-glob: 3.3.2 - ignore: 5.2.4 + ignore: 5.3.1 merge2: 1.4.1 slash: 3.0.0 @@ -10940,6 +11466,13 @@ snapshots: transitivePeerDependencies: - supports-color + https-proxy-agent@7.0.5: + dependencies: + agent-base: 7.1.1 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + human-signals@2.1.0: {} human-signals@5.0.0: {} @@ -10996,7 +11529,7 @@ snapshots: inherits@2.0.4: {} - ini@4.1.2: {} + ini@4.1.3: {} inquirer@9.2.22: dependencies: @@ -11074,6 +11607,12 @@ snapshots: is-fullwidth-code-point@3.0.0: {} + is-fullwidth-code-point@4.0.0: {} + + is-fullwidth-code-point@5.0.0: + dependencies: + get-east-asian-width: 1.2.0 + is-generator-fn@2.1.0: {} is-glob@4.0.3: @@ -11163,8 +11702,8 @@ snapshots: istanbul-lib-instrument@5.2.1: dependencies: - '@babel/core': 7.24.5 - '@babel/parser': 7.24.6 + '@babel/core': 7.24.7 + '@babel/parser': 7.24.7 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.0 semver: 6.3.1 @@ -11173,8 +11712,8 @@ snapshots: istanbul-lib-instrument@6.0.0: dependencies: - '@babel/core': 7.24.5 - '@babel/parser': 7.24.6 + '@babel/core': 7.24.7 + '@babel/parser': 7.24.7 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.0 semver: 7.6.2 @@ -11266,10 +11805,10 @@ snapshots: jest-config@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)): dependencies: - '@babel/core': 7.24.5 + '@babel/core': 7.24.7 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.24.5) + babel-jest: 29.7.0(@babel/core@7.24.7) chalk: 4.1.2 ci-info: 3.8.0 deepmerge: 4.3.1 @@ -11377,7 +11916,7 @@ snapshots: jest-message-util@29.7.0: dependencies: - '@babel/code-frame': 7.24.6 + '@babel/code-frame': 7.24.7 '@jest/types': 29.6.3 '@types/stack-utils': 2.0.1 chalk: 4.1.2 @@ -11397,19 +11936,19 @@ snapshots: optionalDependencies: jest-resolve: 29.7.0 - ? jest-preset-angular@14.1.0(@angular-devkit/build-angular@18.0.2(@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/localize@18.0.3(@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))))(@types/node@20.12.7)(chokidar@3.6.0)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)))(typescript@5.4.5))(@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser-dynamic@18.0.3(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))))(@babel/core@7.24.5)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.5))(jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)))(typescript@5.4.5) + ? jest-preset-angular@14.1.0(@angular-devkit/build-angular@18.0.2(@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/localize@18.1.0(@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))))(@types/node@20.12.7)(chokidar@3.6.0)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)))(typescript@5.4.5))(@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser-dynamic@18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))))(@babel/core@7.24.7)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)))(typescript@5.4.5) : dependencies: - '@angular-devkit/build-angular': 18.0.2(@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/localize@18.0.3(@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))))(@types/node@20.12.7)(chokidar@3.6.0)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)))(typescript@5.4.5) - '@angular/compiler-cli': 18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5) - '@angular/core': 18.0.3(rxjs@7.8.1)(zone.js@0.14.4) - '@angular/platform-browser-dynamic': 18.0.3(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.0.3(@angular/animations@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))) + '@angular-devkit/build-angular': 18.0.2(@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/localize@18.1.0(@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))))(@types/node@20.12.7)(chokidar@3.6.0)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)))(typescript@5.4.5) + '@angular/compiler-cli': 18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5) + '@angular/core': 18.1.0(rxjs@7.8.1)(zone.js@0.14.4) + '@angular/platform-browser-dynamic': 18.1.0(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(@angular/platform-browser@18.1.0(@angular/animations@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(@angular/common@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))(rxjs@7.8.1))(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))) bs-logger: 0.2.6 esbuild-wasm: 0.20.1 jest: 29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)) jest-environment-jsdom: 29.7.0 jest-util: 29.7.0 pretty-format: 29.7.0 - ts-jest: 29.1.2(@babel/core@7.24.5)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.5))(esbuild@0.20.1)(jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)))(typescript@5.4.5) + ts-jest: 29.1.2(@babel/core@7.24.7)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(esbuild@0.20.1)(jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)))(typescript@5.4.5) typescript: 5.4.5 optionalDependencies: esbuild: 0.20.1 @@ -11498,15 +12037,15 @@ snapshots: jest-snapshot@29.7.0: dependencies: - '@babel/core': 7.24.5 - '@babel/generator': 7.24.6 - '@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.24.5) - '@babel/plugin-syntax-typescript': 7.22.5(@babel/core@7.24.5) - '@babel/types': 7.24.6 + '@babel/core': 7.24.7 + '@babel/generator': 7.24.7 + '@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.24.7) + '@babel/plugin-syntax-typescript': 7.22.5(@babel/core@7.24.7) + '@babel/types': 7.24.7 '@jest/expect-utils': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.24.5) + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.24.7) chalk: 4.1.2 expect: 29.7.0 graceful-fs: 4.2.11 @@ -11552,7 +12091,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 20.12.7 + '@types/node': 20.14.10 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -11645,6 +12184,8 @@ snapshots: jsonc-parser@3.2.1: {} + jsonc-parser@3.3.1: {} + jsonfile@6.1.0: dependencies: universalify: 2.0.0 @@ -11663,7 +12204,7 @@ snapshots: launch-editor@2.6.1: dependencies: - picocolors: 1.0.0 + picocolors: 1.0.1 shell-quote: 1.8.1 less-loader@12.2.0(less@4.2.0)(webpack@5.91.0(esbuild@0.21.3)): @@ -11705,6 +12246,30 @@ snapshots: lines-and-columns@2.0.3: {} + listr2@8.2.3: + dependencies: + cli-truncate: 4.0.0 + colorette: 2.0.20 + eventemitter3: 5.0.1 + log-update: 6.0.0 + rfdc: 1.4.1 + wrap-ansi: 9.0.0 + + lmdb@3.0.12: + dependencies: + msgpackr: 1.10.2 + node-addon-api: 6.1.0 + node-gyp-build-optional-packages: 5.2.2 + ordered-binary: 1.5.1 + weak-lru-cache: 1.2.2 + optionalDependencies: + '@lmdb/lmdb-darwin-arm64': 3.0.12 + '@lmdb/lmdb-darwin-x64': 3.0.12 + '@lmdb/lmdb-linux-arm': 3.0.12 + '@lmdb/lmdb-linux-arm64': 3.0.12 + '@lmdb/lmdb-linux-x64': 3.0.12 + '@lmdb/lmdb-win32-x64': 3.0.12 + lmdb@3.0.8: dependencies: msgpackr: 1.10.2 @@ -11755,6 +12320,14 @@ snapshots: chalk: 4.1.2 is-unicode-supported: 0.1.0 + log-update@6.0.0: + dependencies: + ansi-escapes: 6.2.1 + cli-cursor: 4.0.0 + slice-ansi: 7.1.0 + strip-ansi: 7.1.0 + wrap-ansi: 9.0.0 + lru-cache@10.2.2: {} lru-cache@5.1.1: @@ -11967,10 +12540,10 @@ snapshots: neo-async@2.6.2: {} - ng-extract-i18n-merge@2.12.0(@angular-devkit/build-angular@18.0.2(@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/localize@18.0.3(@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))))(@types/node@20.12.7)(chokidar@3.6.0)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)))(typescript@5.4.5))(chokidar@3.6.0): + ng-extract-i18n-merge@2.12.0(@angular-devkit/build-angular@18.0.2(@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/localize@18.1.0(@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))))(@types/node@20.12.7)(chokidar@3.6.0)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)))(typescript@5.4.5))(chokidar@3.6.0): dependencies: '@angular-devkit/architect': 0.1700.10(chokidar@3.6.0) - '@angular-devkit/build-angular': 18.0.2(@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/localize@18.0.3(@angular/compiler-cli@18.0.3(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.0.3(@angular/core@18.0.3(rxjs@7.8.1)(zone.js@0.14.4))))(@types/node@20.12.7)(chokidar@3.6.0)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)))(typescript@5.4.5) + '@angular-devkit/build-angular': 18.0.2(@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/localize@18.1.0(@angular/compiler-cli@18.1.0(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4)))(typescript@5.4.5))(@angular/compiler@18.1.0(@angular/core@18.1.0(rxjs@7.8.1)(zone.js@0.14.4))))(@types/node@20.12.7)(chokidar@3.6.0)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)))(typescript@5.4.5) '@angular-devkit/core': 17.3.8(chokidar@3.6.0) '@angular-devkit/schematics': 17.3.8(chokidar@3.6.0) '@schematics/angular': 17.3.8(chokidar@3.6.0) @@ -12000,6 +12573,10 @@ snapshots: dependencies: detect-libc: 2.0.3 + node-gyp-build-optional-packages@5.2.2: + dependencies: + detect-libc: 2.0.3 + node-gyp-build@4.6.0: optional: true @@ -12319,7 +12896,7 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.24.6 + '@babel/code-frame': 7.24.7 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -12369,6 +12946,8 @@ snapshots: picocolors@1.0.0: {} + picocolors@1.0.1: {} + picomatch@2.3.1: {} picomatch@3.0.1: {} @@ -12386,6 +12965,10 @@ snapshots: optionalDependencies: nice-napi: 1.0.2 + piscina@4.6.1: + optionalDependencies: + nice-napi: 1.0.2 + pkg-dir@4.2.0: dependencies: find-up: 4.1.0 @@ -12613,12 +13196,19 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 + restore-cursor@4.0.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + retry@0.12.0: {} retry@0.13.1: {} reusify@1.0.4: {} + rfdc@1.4.1: {} + rimraf@3.0.2: dependencies: glob: 7.2.3 @@ -12695,6 +13285,12 @@ snapshots: immutable: 4.3.0 source-map-js: 1.2.0 + sass@1.77.6: + dependencies: + chokidar: 3.6.0 + immutable: 4.3.0 + source-map-js: 1.2.0 + sax@1.2.4: {} saxes@6.0.0: @@ -12710,9 +13306,9 @@ snapshots: schema-utils@4.2.0: dependencies: '@types/json-schema': 7.0.12 - ajv: 8.13.0 - ajv-formats: 2.1.1(ajv@8.13.0) - ajv-keywords: 5.1.0(ajv@8.13.0) + ajv: 8.16.0 + ajv-formats: 2.1.1(ajv@8.16.0) + ajv-keywords: 5.1.0(ajv@8.16.0) select-hose@2.0.0: {} @@ -12838,6 +13434,16 @@ snapshots: slash@4.0.0: {} + slice-ansi@5.0.0: + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 4.0.0 + + slice-ansi@7.1.0: + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 5.0.0 + smart-buffer@4.2.0: {} sockjs@0.3.24: @@ -12954,6 +13560,12 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.0 + string-width@7.2.0: + dependencies: + emoji-regex: 10.3.0 + get-east-asian-width: 1.2.0 + strip-ansi: 7.1.0 + string.prototype.trim@1.2.9: dependencies: call-bind: 1.0.7 @@ -13117,7 +13729,7 @@ snapshots: dependencies: typescript: 5.4.5 - ts-jest@29.1.2(@babel/core@7.24.5)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.5))(esbuild@0.20.1)(jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)))(typescript@5.4.5): + ts-jest@29.1.2(@babel/core@7.24.7)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(esbuild@0.20.1)(jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)))(typescript@5.4.5): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 @@ -13130,9 +13742,9 @@ snapshots: typescript: 5.4.5 yargs-parser: 21.1.1 optionalDependencies: - '@babel/core': 7.24.5 + '@babel/core': 7.24.7 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.24.5) + babel-jest: 29.7.0(@babel/core@7.24.7) esbuild: 0.20.1 ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5): @@ -13240,6 +13852,8 @@ snapshots: undici@6.18.0: {} + undici@6.19.2: {} + unicode-canonical-property-names-ecmascript@2.0.0: {} unicode-match-property-ecmascript@2.0.0: @@ -13317,6 +13931,18 @@ snapshots: sass: 1.77.2 terser: 5.31.0 + vite@5.3.2(@types/node@20.12.7)(less@4.2.0)(sass@1.77.6)(terser@5.31.0): + dependencies: + esbuild: 0.21.5 + postcss: 8.4.38 + rollup: 4.18.0 + optionalDependencies: + '@types/node': 20.12.7 + fsevents: 2.3.3 + less: 4.2.0 + sass: 1.77.6 + terser: 5.31.0 + w3c-xmlserializer@4.0.0: dependencies: xml-name-validator: 4.0.0 @@ -13500,6 +14126,12 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.0 + wrap-ansi@9.0.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 7.2.0 + strip-ansi: 7.1.0 + wrappy@1.0.2: {} write-file-atomic@4.0.2: @@ -13545,6 +14177,8 @@ snapshots: yocto-queue@1.0.0: {} + yoctocolors-cjs@2.1.2: {} + zone.js@0.14.4: dependencies: tslib: 2.6.2 diff --git a/src/app/app.config.ts b/src/app/app.config.ts index 75b50a018..10a9cf4cb 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -1,5 +1,5 @@ import { HttpClient, provideHttpClient } from '@angular/common/http'; -import { APP_INITIALIZER, ApplicationConfig, importProvidersFrom } from '@angular/core'; +import { APP_INITIALIZER, ApplicationConfig, importProvidersFrom, provideExperimentalZonelessChangeDetection } from '@angular/core'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { provideRouter } from '@angular/router'; import { GrpcCoreModule } from '@ngx-grpc/core'; @@ -96,6 +96,7 @@ export const appConfig: ApplicationConfig = { deps: [UserGrpcService, UserService, VersionsGrpcService, VersionsService, HttpClient, EnvironmentService, StorageService], multi: true }, + provideExperimentalZonelessChangeDetection(), provideRouter(routes), importProvidersFrom(BrowserAnimationsModule), provideHttpClient(), diff --git a/src/app/applications/components/table.component.html b/src/app/applications/components/table.component.html index b53395532..45e65ad54 100644 --- a/src/app/applications/components/table.component.html +++ b/src/app/applications/components/table.component.html @@ -1,3 +1,3 @@ - \ No newline at end of file diff --git a/src/app/applications/components/table.component.spec.ts b/src/app/applications/components/table.component.spec.ts index 66de17b81..8cd2523b6 100644 --- a/src/app/applications/components/table.component.spec.ts +++ b/src/app/applications/components/table.component.spec.ts @@ -7,7 +7,8 @@ import { Router } from '@angular/router'; import { BehaviorSubject, Subject, of, throwError } from 'rxjs'; import { ManageGroupsDialogResult, TasksStatusesGroup } from '@app/dashboard/types'; import { TableColumn } from '@app/types/column.type'; -import { ApplicationData } from '@app/types/data'; +import { ApplicationData, ColumnKey } from '@app/types/data'; +import { FiltersOr } from '@app/types/filters'; import { CacheService } from '@services/cache.service'; import { FiltersService } from '@services/filters.service'; import { IconsService } from '@services/icons.service'; @@ -16,12 +17,12 @@ import { TasksByStatusService } from '@services/tasks-by-status.service'; import { ApplicationsTableComponent } from './table.component'; import { ApplicationsGrpcService } from '../services/applications-grpc.service'; import { ApplicationsIndexService } from '../services/applications-index.service'; -import { ApplicationRaw, ApplicationRawColumnKey, ApplicationRawFilters } from '../types'; +import { ApplicationRaw } from '../types'; describe('TasksTableComponent', () => { let component: ApplicationsTableComponent; - const displayedColumns: TableColumn[] = [ + const displayedColumns: TableColumn[] = [ { name: 'Namespace', key: 'namespace', @@ -60,7 +61,8 @@ describe('TasksTableComponent', () => { isSimpleColumn: jest.fn(), isNotSortableColumn: jest.fn(), columnToLabel: jest.fn(), - saveColumns: jest.fn() + saveColumns: jest.fn(), + saveOptions: jest.fn(), }; const mockNotificationService = { @@ -147,7 +149,7 @@ describe('TasksTableComponent', () => { }).inject(ApplicationsTableComponent); component.displayedColumns = displayedColumns; - component.filters$ = new BehaviorSubject([]); + component.filters$ = new BehaviorSubject>([]); component.options = { pageIndex: 0, pageSize: 10, @@ -294,14 +296,21 @@ describe('TasksTableComponent', () => { }); }); - it('should refresh data on options changes', () => { - const spy = jest.spyOn(component.refresh$, 'next'); - component.onOptionsChange(); - expect(spy).toHaveBeenCalled(); + describe('options changes', () => { + it('should refresh data', () => { + const spy = jest.spyOn(component.refresh$, 'next'); + component.onOptionsChange(); + expect(spy).toHaveBeenCalled(); + }); + + it('should save options', () => { + component.onOptionsChange(); + expect(mockApplicationsIndexService.saveOptions).toHaveBeenCalled(); + }); }); test('onDrop should call ApplicationsIndexService', () => { - const newColumns: ApplicationRawColumnKey[] = ['actions', 'name', 'namespace', 'version']; + const newColumns: ColumnKey[] = ['actions', 'name', 'namespace', 'version']; component.onDrop(newColumns); expect(mockApplicationsIndexService.saveColumns).toHaveBeenCalledWith(newColumns); }); @@ -455,4 +464,9 @@ describe('TasksTableComponent', () => { expect(component.isDataRawEqual(application1, application2)).toBeFalsy(); }); }); + + it('should track an application by its name and version', () => { + const application = {raw: { name: 'application', version: '0.1.2'}} as ApplicationData; + expect(component.trackBy(0, application)).toEqual(`${application.raw.name}-${application.raw.version}`); + }); }); \ No newline at end of file diff --git a/src/app/applications/components/table.component.ts b/src/app/applications/components/table.component.ts index fb9ca68fa..dc3fca195 100644 --- a/src/app/applications/components/table.component.ts +++ b/src/app/applications/components/table.component.ts @@ -6,7 +6,7 @@ import { Subject } from 'rxjs'; import { TaskSummaryFilters } from '@app/tasks/types'; import { AbstractTaskByStatusTableComponent } from '@app/types/components/table'; import { Scope } from '@app/types/config'; -import { ApplicationData } from '@app/types/data'; +import { ApplicationData, ArmonikData } from '@app/types/data'; import { Filter } from '@app/types/filters'; import { ActionTable } from '@app/types/table'; import { TableComponent } from '@components/table/table.component'; @@ -17,7 +17,7 @@ import { NotificationService } from '@services/notification.service'; import { TableTasksByStatus, TasksByStatusService } from '@services/tasks-by-status.service'; import { ApplicationsGrpcService } from '../services/applications-grpc.service'; import { ApplicationsIndexService } from '../services/applications-index.service'; -import { ApplicationRaw, ApplicationRawColumnKey, ApplicationRawFieldKey, ApplicationRawListOptions } from '../types'; +import { ApplicationRaw } from '../types'; @Component({ selector: 'app-application-table', @@ -36,7 +36,7 @@ import { ApplicationRaw, ApplicationRawColumnKey, ApplicationRawFieldKey, Applic TableComponent, ] }) -export class ApplicationsTableComponent extends AbstractTaskByStatusTableComponent +export class ApplicationsTableComponent extends AbstractTaskByStatusTableComponent implements OnInit, AfterViewInit { scope: Scope = 'applications'; table: TableTasksByStatus = 'applications'; @@ -46,10 +46,10 @@ export class ApplicationsTableComponent extends AbstractTaskByStatusTableCompone readonly iconsService = inject(IconsService); readonly router = inject(Router); - seeSessions$ = new Subject(); + seeSessions$ = new Subject>(); seeSessionsSubscription = this.seeSessions$.subscribe(data => this.router.navigate(['/sessions'], { queryParams: this.createViewSessionsQueryParams(data.raw.name, data.raw.version) })); - actions: ActionTable[] = [ + actions: ActionTable[] = [ { label: $localize`See session`, icon: this.getIcon('sessions'), @@ -160,4 +160,8 @@ export class ApplicationsTableComponent extends AbstractTaskByStatusTableCompone ] ]; } + + trackBy(index: number, item: ArmonikData) { + return `${item.raw.name}-${item.raw.version}`; + } } \ No newline at end of file diff --git a/src/app/applications/index.component.spec.ts b/src/app/applications/index.component.spec.ts index 7d9ad2f8f..45a37f1bc 100644 --- a/src/app/applications/index.component.spec.ts +++ b/src/app/applications/index.component.spec.ts @@ -3,6 +3,7 @@ import { MatDialog } from '@angular/material/dialog'; import { Subject, of } from 'rxjs'; import { DashboardIndexService } from '@app/dashboard/services/dashboard-index.service'; import { TableColumn } from '@app/types/column.type'; +import { ColumnKey } from '@app/types/data'; import { FiltersOr } from '@app/types/filters'; import { AutoRefreshService } from '@services/auto-refresh.service'; import { DefaultConfigService } from '@services/default-config.service'; @@ -11,13 +12,13 @@ import { ShareUrlService } from '@services/share-url.service'; import { IndexComponent } from './index.component'; import { ApplicationsFiltersService } from './services/applications-filters.service'; import { ApplicationsIndexService } from './services/applications-index.service'; -import { ApplicationRawColumnKey, ApplicationRawFilters, ApplicationRawListOptions } from './types'; +import { ApplicationRaw, ApplicationRawFilters, ApplicationRawListOptions } from './types'; describe('Application component', () => { let component: IndexComponent; - const displayedColumns: TableColumn[] = [ + const displayedColumns: TableColumn[] = [ { name: 'Name', key: 'name', @@ -209,7 +210,7 @@ describe('Application component', () => { }); it('should change columns', () => { - const newColumns: ApplicationRawColumnKey[] = ['name', 'count', 'service']; + const newColumns: ColumnKey[] = ['name', 'count', 'service']; component.onColumnsChange(newColumns); expect(component.displayedColumnsKeys).toEqual(newColumns); expect(mockApplicationIndexService.saveColumns).toHaveBeenCalledWith(newColumns); diff --git a/src/app/applications/index.component.ts b/src/app/applications/index.component.ts index 87f6ec44a..066503aef 100644 --- a/src/app/applications/index.component.ts +++ b/src/app/applications/index.component.ts @@ -26,7 +26,7 @@ import { UtilsService } from '@services/utils.service'; import { ApplicationsTableComponent } from './components/table.component'; import { ApplicationsFiltersService } from './services/applications-filters.service'; import { ApplicationsIndexService } from './services/applications-index.service'; -import { ApplicationRawColumnKey, ApplicationRawFilters, ApplicationRawListOptions } from './types'; +import { ApplicationRaw } from './types'; @Component({ selector: 'app-applications-index', @@ -64,7 +64,7 @@ import { ApplicationRawColumnKey, ApplicationRawFilters, ApplicationRawListOptio ApplicationsTableComponent ] }) -export class IndexComponent extends TableHandler implements OnInit, AfterViewInit, OnDestroy { +export class IndexComponent extends TableHandler implements OnInit, AfterViewInit, OnDestroy { readonly filtersService = inject(ApplicationsFiltersService); readonly indexService = inject(ApplicationsIndexService); diff --git a/src/app/applications/services/applications-filters.service.ts b/src/app/applications/services/applications-filters.service.ts index cf4a075ab..c321bce0f 100644 --- a/src/app/applications/services/applications-filters.service.ts +++ b/src/app/applications/services/applications-filters.service.ts @@ -9,7 +9,7 @@ import { ApplicationFilterField, ApplicationRawFilters, ApplicationsFiltersDefin @Injectable({ providedIn: 'root' }) -export class ApplicationsFiltersService implements FiltersServiceInterface { +export class ApplicationsFiltersService implements FiltersServiceInterface { readonly defaultConfigService = inject(DefaultConfigService); readonly tableService = inject(TableService); diff --git a/src/app/applications/services/applications-grpc.service.ts b/src/app/applications/services/applications-grpc.service.ts index 5482b439c..910c12b09 100644 --- a/src/app/applications/services/applications-grpc.service.ts +++ b/src/app/applications/services/applications-grpc.service.ts @@ -5,10 +5,10 @@ import { Filter, FilterType } from '@app/types/filters'; import { GrpcTableService, ListApplicationSortField, RequestFilterField } from '@app/types/services/grpcService'; import { FilterField, buildStringFilter } from '@services/grpc-build-request.service'; import { ApplicationsFiltersService } from './applications-filters.service'; -import { ApplicationRawFieldKey, ApplicationRawFilters, ApplicationRawListOptions } from '../types'; +import { ApplicationRaw, ApplicationRawFieldKey, ApplicationRawFilters, ApplicationRawListOptions } from '../types'; @Injectable() -export class ApplicationsGrpcService extends GrpcTableService { +export class ApplicationsGrpcService extends GrpcTableService { readonly filterService = inject(ApplicationsFiltersService); readonly grpcClient = inject(ApplicationsClient); diff --git a/src/app/applications/services/applications-index.service.ts b/src/app/applications/services/applications-index.service.ts index ca3575598..6da9ae6fe 100644 --- a/src/app/applications/services/applications-index.service.ts +++ b/src/app/applications/services/applications-index.service.ts @@ -6,7 +6,7 @@ import { TableService } from '@services/table.service'; import { ApplicationRaw, ApplicationRawColumnKey, ApplicationRawListOptions } from '../types'; @Injectable() -export class ApplicationsIndexService implements IndexServiceInterface { +export class ApplicationsIndexService implements IndexServiceInterface { readonly tableService = inject(TableService); readonly defaultConfigService = inject(DefaultConfigService); @@ -15,7 +15,7 @@ export class ApplicationsIndexService implements IndexServiceInterface[] = [ + readonly availableTableColumns: TableColumn[] = [ { name: $localize`Name`, key: 'name', diff --git a/src/app/components/columns-button.component.spec.ts b/src/app/components/columns-button.component.spec.ts index b82f7a8a8..550df581a 100644 --- a/src/app/components/columns-button.component.spec.ts +++ b/src/app/components/columns-button.component.spec.ts @@ -3,11 +3,13 @@ import { MatButtonModule } from '@angular/material/button'; import { MatDialog, MatDialogModule } from '@angular/material/dialog'; import { MatIconModule } from '@angular/material/icon'; import { BehaviorSubject } from 'rxjs'; +import { SessionRaw } from '@app/sessions/types'; +import { TaskOptions } from '@app/tasks/types'; import { IconsService } from '@services/icons.service'; import { ColumnsButtonComponent } from './columns-button.component'; describe('Auto-refresh component', () => { - let component: ColumnsButtonComponent; + let component: ColumnsButtonComponent; let intervalValueChangeSpy: jest.SpyInstance; let dialogSubject: BehaviorSubject; @@ -28,9 +30,9 @@ describe('Auto-refresh component', () => { { provide: MatDialogModule, useValue: {} }, { provide: MatButtonModule, useValue: {} }, { provide: MatIconModule, useValue: {} }, - { provide: IconsService, useValue: {} } + IconsService, ] - }).inject(ColumnsButtonComponent); + }).inject(ColumnsButtonComponent); intervalValueChangeSpy = jest.spyOn(component, 'emit'); }); @@ -50,4 +52,8 @@ describe('Auto-refresh component', () => { component.openModifyColumnsDialog(); expect(intervalValueChangeSpy).toHaveBeenCalledTimes(0); }); + + it('should get icons', () => { + expect(component.getIcon('heart')).toEqual('favorite'); + }); }); \ No newline at end of file diff --git a/src/app/components/columns-button.component.ts b/src/app/components/columns-button.component.ts index d4c8a25f0..d7da7412d 100644 --- a/src/app/components/columns-button.component.ts +++ b/src/app/components/columns-button.component.ts @@ -2,7 +2,8 @@ import { Component, EventEmitter, Input, Output, inject } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog, MatDialogModule } from '@angular/material/dialog'; import { MatIconModule } from '@angular/material/icon'; -import { ColumnKey, RawColumnKey } from '@app/types/data'; +import { TaskOptions } from '@app/tasks/types'; +import { ColumnKey, DataRaw } from '@app/types/data'; import { ColumnsModifyDialogData, ColumnsModifyDialogResult } from '@app/types/dialog'; import { IconsService } from '@services/icons.service'; import { ColumnsModifyDialogComponent } from './columns-modify-dialog.component'; @@ -25,12 +26,12 @@ import { ColumnsModifyDialogComponent } from './columns-modify-dialog.component' MatIconModule ] }) -export class ColumnsButtonComponent { - #iconsService = inject(IconsService); +export class ColumnsButtonComponent { + readonly iconsService = inject(IconsService); @Input({ required: true }) columnsLabels: Record, string>; - @Input({ required: true }) displayedColumns: RawColumnKey[] = []; - @Input({ required: true }) availableColumns: RawColumnKey[]; + @Input({ required: true }) displayedColumns: ColumnKey[] = []; + @Input({ required: true }) availableColumns: ColumnKey[]; @Input({ required: true}) disabled: boolean = false; @Output() displayedColumnsChange: EventEmitter[]> = new EventEmitter[]>(); @@ -38,7 +39,7 @@ export class ColumnsButtonComponent { constructor(private _dialog: MatDialog) { } getIcon(name: string): string { - return this.#iconsService.getIcon(name); + return this.iconsService.getIcon(name); } emit(result: ColumnsModifyDialogResult): void { diff --git a/src/app/components/columns-modify-dialog.component.html b/src/app/components/columns-modify-dialog.component.html index b84fda977..71e77f76a 100644 --- a/src/app/components/columns-modify-dialog.component.html +++ b/src/app/components/columns-modify-dialog.component.html @@ -5,41 +5,42 @@

Modify Columns

@for(column of availableColumns; track column) { - - {{ columnToLabel(column) }} - + + {{ columnsLabels[column] }} + }
@if (availableOptionsColumns.length) { -

- Options -

- -
- @for(column of availableOptionsColumns; track column) { - - {{ columnToLabel(column) }} - - } -
+

+ Options +

+ +
+ @for(column of availableOptionsColumns; track column) { + + {{ columnsLabels[column] }} + + } +
} @if (availableCustomColumns.length) { -

- Custom Columns -

- -
- @for(column of availableCustomColumns; track column) { - - {{ columnToLabel(column) }} - - } -
+

+ Custom Columns +

+ +
+ @for(column of availableCustomColumns; track column) { + + {{ column }} + + } +
} - - - - \ No newline at end of file + + + + \ No newline at end of file diff --git a/src/app/components/columns-modify-dialog.component.spec.ts b/src/app/components/columns-modify-dialog.component.spec.ts index 13b297a18..05f27d022 100644 --- a/src/app/components/columns-modify-dialog.component.spec.ts +++ b/src/app/components/columns-modify-dialog.component.spec.ts @@ -1,29 +1,35 @@ import { TestBed } from '@angular/core/testing'; import { MatCheckboxChange } from '@angular/material/checkbox'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { ColumnKey } from '@app/types/data'; +import { TaskOptions, TaskSummary } from '@app/tasks/types'; +import { ColumnKey, CustomColumn, PrefixedOptions } from '@app/types/data'; import { ColumnsModifyDialogData } from '@app/types/dialog'; import { ColumnsModifyDialogComponent } from './columns-modify-dialog.component'; describe('', () => { - let component: ColumnsModifyDialogComponent; + let component: ColumnsModifyDialogComponent; const mockMatDialogRef = { close: jest.fn() - } as unknown as MatDialogRef>; + } as unknown as MatDialogRef>; + + const currentColumns: ColumnKey[] = ['id', 'createdAt']; + const columnsLabels = { + 'id': 'ID', + 'createdAt': 'Creation time', + 'options.engineType': 'Engine Type', + 'options.partitionId': 'Partition Ids', + 'actions': 'Actions' + } as Record, string>; + const columns: ColumnKey[] = ['id', 'createdAt', 'actions']; + const optionsColumns: PrefixedOptions[] = ['options.engineType', 'options.partitionId']; + const customColumns: CustomColumn[] = ['options.options.FastCompute', 'options.options.EngineReady']; const mockMatDialogData = { - currentColumns: ['id', 'created_time'], - columnsLabels: { - 'id': 'ID', - 'created_time': 'Creation time', - 'name': 'Name', - 'duration': 'Duration', - 'options.task_id': 'Task ID', - 'actions': 'Actions' - } as Record, string>, - availableColumns: ['name', 'duration', 'options.task_id', 'actions', 'options.options.FastCompute'] - } as ColumnsModifyDialogData; + currentColumns: currentColumns, + columnsLabels: columnsLabels, + availableColumns: [...columns, ...optionsColumns, ...customColumns] + } as ColumnsModifyDialogData; beforeEach(() => { component = TestBed.configureTestingModule({ @@ -40,102 +46,68 @@ describe('', () => { expect(component).toBeTruthy(); }); - describe('on init', () => { - it('should load columns', () => { + describe('initialisation', () => { + it('should get current columns', () => { expect(component.columns).toEqual(mockMatDialogData.currentColumns); }); - it('should copy the columns', () => { + it('should copy current columns', () => { expect(component.columns).not.toBe(mockMatDialogData.currentColumns); }); - it('should load columns labels', () => { - expect(component.columnsLabels).toEqual(mockMatDialogData.columnsLabels); + it('should set and sort alphabetically the "normal" columns', () => { + expect(component.availableColumns).toEqual(columns.sort((a: string, b: string) => a.localeCompare(b))); }); - }); - - test('optionsColumnValue should add "options." to any provided string', () => { - expect(component.optionsColumnValue('nameColumn')).toEqual('options.nameColumn'); - }); - describe('columnToLabel', () => { - it('should get the label of the specified column', () => { - expect(component.columnToLabel('name' as ColumnKey)) - .toEqual('Name'); + it('should set and sort alphabetically the "options" columns', () => { + expect(component.availableOptionsColumns).toEqual(optionsColumns.sort((a: string, b: string) => a.localeCompare(b))); }); - it('should return the stringified key in case of non-existing label', () => { - expect(component.columnToLabel('nonExistingLabel' as ColumnKey)) - .toEqual('nonExistingLabel'); + it('should set, sort alphabetically and map the "customs" columns', () => { + expect(component.availableCustomColumns).toEqual(customColumns.sort((a: string, b: string) => a.localeCompare(b)).map(c => c.replace('options.options.', ''))); }); + }); - it('should remove the "options.options." key from custom columns', () => { - expect(component.columnToLabel('options.options.FastCompute')).toEqual('FastCompute'); - }); + test('toCustom should turn a string into a custom column', () => { + const value = 'column'; + expect(component.toCustom(value)).toEqual(`options.options.${value}`); }); describe('isCustomColumn', () => { - it('should be true in case of a custom column', () => { - expect(component.isCustomColumn('options.options.FastCompute')).toBeTruthy(); + it('should return true in case of a custom column', () => { + expect(component.isCustomColumn(customColumns[0])).toBeTruthy(); }); - it('should be false in case of a non-custom column', () => { - expect(component.isCustomColumn('actions')).toBeFalsy(); + it('should return false in case of a normal column', () => { + expect(component.isCustomColumn(columns[0])).toBeFalsy(); }); }); - describe('Getting available columns', () => { - it('should return every column without the "options." prefix', () => { - expect(component.availableColumns).toEqual(['actions', 'duration', 'name']); - }); - - it('should return every column with the "options." prefix', () => { - expect(component.availableOptionsColumns).toEqual(['options.task_id']); + describe('isSelected', () => { + it('should return true in case of a selected normal column', () => { + expect(component.isSelected(currentColumns[0])).toBeTruthy(); }); - it('should return every custom column', () => { - expect(component.availableCustomColumns).toEqual(['options.options.FastCompute']); + it('should return false in case of unselected normal column', () => { + expect(component.isSelected(optionsColumns[0])).toBeFalsy(); }); }); describe('updateColumn', () => { - it('should push a new column', () => { - component.updateColumn({checked: true} as MatCheckboxChange, 'duration' as ColumnKey); - expect(component.columns).toEqual(['id', 'created_time', 'duration']); - }); - - it('should remove a column if parameter is unchecked', () => { - component.updateColumn({checked: false} as MatCheckboxChange, 'id' as ColumnKey); - expect(component.columns).toEqual(['created_time']); - }); - - it('should not push a column that is already in the column list', () => { - component.updateColumn({checked: true} as MatCheckboxChange, 'id' as ColumnKey); - expect(component.columns).toEqual(['id', 'created_time']); - }); - - it('should not push a column that is not in the available columns', () => { - component.updateColumn({checked: true} as MatCheckboxChange, 'someColumn' as ColumnKey); - expect(component.columns).toEqual(['id', 'created_time']); - }); - - it('should not do remove a column that is not in the column list', () => { - component.updateColumn({checked: false} as MatCheckboxChange, 'someColumn' as ColumnKey); - expect(component.columns).toEqual(['id', 'created_time']); - }); - }); - - describe('isSelected', () => { - it('should return true if a column is selected', () => { - expect(component.isSelected('id' as ColumnKey)).toBeTruthy(); + it('should add a column on check', () => { + const checkEvent = { checked: true } as MatCheckboxChange; + component.updateColumn(checkEvent, optionsColumns[1]); + expect(component.columns).toContain(optionsColumns[1]); }); - it('should return false if a column is not selected', () => { - expect(component.isSelected('duration' as ColumnKey)).toBeFalsy(); + it('should add a column on unchecked', () => { + const checkEvent = { checked: false } as MatCheckboxChange; + component.updateColumn(checkEvent, 'id'); + expect(component.columns).not.toContain('id'); }); }); - test('onNoClick should call dialogref.close', () => { + it('should close on "No Click"', () => { component.onNoClick(); expect(mockMatDialogRef.close).toHaveBeenCalled(); }); diff --git a/src/app/components/columns-modify-dialog.component.ts b/src/app/components/columns-modify-dialog.component.ts index 5cab4819d..f313587b0 100644 --- a/src/app/components/columns-modify-dialog.component.ts +++ b/src/app/components/columns-modify-dialog.component.ts @@ -3,7 +3,8 @@ import { MatButtonModule } from '@angular/material/button'; import { MatCheckboxChange, MatCheckboxModule } from '@angular/material/checkbox'; import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; import { MatGridListModule } from '@angular/material/grid-list'; -import { ColumnKey, CustomColumn, PrefixedOptions, RawColumnKey } from '@app/types/data'; +import { TaskOptions } from '@app/tasks/types'; +import { ColumnKey, CustomColumn, DataRaw, PrefixedOptions } from '@app/types/data'; import { ColumnsModifyDialogData } from '@app/types/dialog'; @Component({ @@ -27,37 +28,39 @@ import { ColumnsModifyDialogData } from '@app/types/dialog'; MatCheckboxModule ] }) -export class ColumnsModifyDialogComponent implements OnInit { - columns: RawColumnKey[] = []; +export class ColumnsModifyDialogComponent implements OnInit { + columns: ColumnKey[] = []; columnsLabels: Record, string>; + + private _availableColumns: (keyof T | 'actions')[] = []; + private _availableOptionsColumns: PrefixedOptions[] = []; + private _availableCustomColumns: string[] = []; - _availableColumns: (keyof T | 'actions')[] = []; + /* Setters */ - set availableColumns(value: RawColumnKey[]) { + set availableColumns(value: ColumnKey[]) { this._availableColumns = value.filter(column => !column.toString().includes('.')).sort((a, b) => a.toString().localeCompare(b.toString())) as (keyof T | 'actions')[]; } - get availableColumns(): (keyof T | 'actions')[] { - return this._availableColumns; + set availableOptionsColumns(value: ColumnKey[]) { + this._availableOptionsColumns = value.filter(column => !this.isCustomColumn(column) && column.toString().startsWith('options.')).sort((a, b) => a.toString().localeCompare(b.toString())) as PrefixedOptions[]; } - _availableOptionsColumns: PrefixedOptions[] = []; + set availableCustomColumns(value: ColumnKey[]) { + this._availableCustomColumns = value.filter(column => this.isCustomColumn(column)).sort((a, b) => a.toString().localeCompare(b.toString())).map((column) => column.toString().replace('options.options.', '')); + } - set availableOptionsColumns(value: RawColumnKey[]) { - this._availableOptionsColumns = value.filter(column => !this.isCustomColumn(column as ColumnKey) && column.toString().startsWith('options.')).sort((a, b) => a.toString().localeCompare(b.toString())) as PrefixedOptions[]; + /* Getters */ + + get availableColumns(): (keyof T | 'actions')[] { + return this._availableColumns; } get availableOptionsColumns(): PrefixedOptions[] { return this._availableOptionsColumns; } - _availableCustomColumns: CustomColumn[] = []; - - set availableCustomColumns(value: RawColumnKey[]) { - this._availableCustomColumns = value.filter(column => this.isCustomColumn(column as ColumnKey)).sort((a, b) => a.toString().localeCompare(b.toString())) as CustomColumn[]; - } - - get availableCustomColumns(): CustomColumn[] { + get availableCustomColumns(): string[] { return this._availableCustomColumns; } @@ -65,25 +68,25 @@ export class ColumnsModifyDialogComponent imp ngOnInit(): void { // Create a copy in order to not modify the original array - this.columns = Array.from(this.data.currentColumns); + this.columns = [...this.data.currentColumns]; this.columnsLabels = this.data.columnsLabels; this.availableColumns = this.data.availableColumns; this.availableOptionsColumns = this.data.availableColumns; this.availableCustomColumns = this.data.availableColumns; } - optionsColumnValue(column: string): string { - return `options.${column}`; - } - - columnToLabel(column: ColumnKey): string { - return !this.isCustomColumn(column) ? this.columnsLabels[column] ?? column.toString() : column.toString().replace('options.options.', ''); + toCustom(column: string): CustomColumn { + return `options.options.${column}`; } isCustomColumn(column: ColumnKey): boolean { return column.toString().startsWith('options.options.'); } + isSelected(column: ColumnKey): boolean { + return this.columns.includes(column); + } + /** * Update the columns array when a checkbox is checked or unchecked * checked: add the column. @@ -91,21 +94,14 @@ export class ColumnsModifyDialogComponent imp */ updateColumn({ checked }: MatCheckboxChange, column: ColumnKey): void { if (checked) { - if (!this.columns.includes(column as RawColumnKey) && this.data.availableColumns.includes(column as RawColumnKey)) { - this.columns.push(column as RawColumnKey); + if (!this.columns.includes(column) && this.data.availableColumns.includes(column)) { + this.columns.push(column); } - } else if(this.columns.includes(column as RawColumnKey)) { + } else if(this.columns.includes(column)) { this.columns = this.columns.filter(currentColumn => currentColumn !== column); } } - /** - * Check if a column is selected - */ - isSelected(column: ColumnKey): boolean { - return this.data.currentColumns.includes(column as RawColumnKey); - } - onNoClick(): void { this.dialogRef.close(); } diff --git a/src/app/components/count-tasks-by-status.component.spec.ts b/src/app/components/count-tasks-by-status.component.spec.ts index ab2ee6a91..d9cfaa29f 100644 --- a/src/app/components/count-tasks-by-status.component.spec.ts +++ b/src/app/components/count-tasks-by-status.component.spec.ts @@ -5,7 +5,6 @@ import { TasksStatusesGroup } from '@app/dashboard/types'; import { TasksFiltersService } from '@app/tasks/services/tasks-filters.service'; import { TasksGrpcService } from '@app/tasks/services/tasks-grpc.service'; import { StatusCount, TaskSummaryFilters } from '@app/tasks/types'; -import { CacheService } from '@services/cache.service'; import { CountTasksByStatusComponent } from './count-tasks-by-status.component'; describe('CountTasksByStatusComponent', () => { @@ -44,42 +43,37 @@ describe('CountTasksByStatusComponent', () => { const refresh$ = new Subject(); const refreshSpy = jest.spyOn(refresh$, 'next'); - const cachedData: StatusCount[] = [{ status: TaskStatus.TASK_STATUS_CREATING, count: 3 }]; - const mockCacheService = { - getStatuses: jest.fn((): StatusCount[] | undefined => cachedData), - saveStatuses: jest.fn() - }; - beforeEach(() => { component = TestBed.configureTestingModule({ providers: [ CountTasksByStatusComponent, { provide: TasksGrpcService, useValue: mockTasksGrpcService }, - TasksFiltersService, - { provide: CacheService, useValue: mockCacheService } + TasksFiltersService ] }).inject(CountTasksByStatusComponent); component.refresh = refresh$; component.filters = filters; component.statusesGroups = statusesGroups; + component.ngOnInit(); }); it('Should run', () => { expect(component).toBeTruthy(); }); - describe('initCount', () => { - it('should set id', () => { - expect(component.id).toEqual(filters[0][0].value); + describe('initialisation', () => { + it('should subscribe to refresh', () => { + expect(refresh$.observed).toBeTruthy(); }); - it('should load data', () => { - expect(mockCacheService.getStatuses).toHaveBeenCalled(); + it('should set id', () => { + expect(component.id).toEqual(filters[0][0].value); }); it('should not set id if there is no filter value', () => { - component.initCount([]); + component.filters = []; + component.initId(); expect(component.id).toEqual(undefined); }); }); @@ -95,37 +89,20 @@ describe('CountTasksByStatusComponent', () => { }); describe('setting filters', () => { - it('should subscribe to refresh', () => { - expect(refresh$.observed).toBeTruthy(); - }); - - it('should refresh counts', () => { - expect(refreshSpy).toHaveBeenCalled(); + it('should set filters', () => { + expect(component.filters).toEqual(filters); }); }); describe('Refreshing', () => { it('should update statusesCounts', () => { - component.refresh.next(); + refresh$.next(); expect(component.statusesCount()).toEqual(finalStatusesCount); }); it('should set null if there is no response status', () => { mockTasksGrpcService.countByStatus$.mockReturnValue(of({ status: undefined })); - component.refresh.next(); - expect(component.statusesCount()).toEqual([]); - }); - }); - - describe('loadFromCache', () => { - it('should load cached data', () => { - component.loadFromCache(); - expect(component.statusesCount()).toEqual(cachedData); - }); - - it('should load a null value if there is no cache', () => { - mockCacheService.getStatuses.mockReturnValueOnce(undefined); - component.loadFromCache(); + refresh$.next(); expect(component.statusesCount()).toEqual([]); }); }); diff --git a/src/app/components/count-tasks-by-status.component.ts b/src/app/components/count-tasks-by-status.component.ts index 0293a79e6..d8398d79e 100644 --- a/src/app/components/count-tasks-by-status.component.ts +++ b/src/app/components/count-tasks-by-status.component.ts @@ -1,11 +1,10 @@ -import { Component, Input, WritableSignal, inject, signal } from '@angular/core'; -import { Subject, Subscription, switchMap } from 'rxjs'; +import { Component, Input, OnInit, WritableSignal, inject, signal } from '@angular/core'; +import { Subject, switchMap } from 'rxjs'; import { TasksStatusesGroup } from '@app/dashboard/types'; import { TasksFiltersService } from '@app/tasks/services/tasks-filters.service'; import { TasksGrpcService } from '@app/tasks/services/tasks-grpc.service'; import { StatusCount, TaskSummaryFilters } from '@app/tasks/types'; import { ViewTasksByStatusComponent } from '@components/view-tasks-by-status.component'; -import { CacheService } from '@services/cache.service'; @Component({ selector: 'app-count-tasks-by-status', @@ -29,60 +28,56 @@ import { CacheService } from '@services/cache.service'; ViewTasksByStatusComponent, ] }) -export class CountTasksByStatusComponent { +export class CountTasksByStatusComponent implements OnInit { + private readonly tasksGrpcService = inject(TasksGrpcService); + + id: string | undefined; + statusesCount: WritableSignal = signal([]); + loading = true; + + private _statusesGroups: TasksStatusesGroup[] = []; + private _filters: TaskSummaryFilters; + private _refresh$: Subject; + @Input({ required: true }) queryParams: Record = {}; - @Input({ required: true }) refresh: Subject; + + @Input({ required: true }) set refresh(subject: Subject) { + this._refresh$ = subject; + this.initRefresh(); + } @Input({ required: true }) set statusesGroups(entries: TasksStatusesGroup[]) { this._statusesGroups = entries; - if (this.refresh) { - this.refresh.next(); - } + this._refresh$.next(); } - id: string | undefined; - private _statusesGroups: TasksStatusesGroup[] = []; - get statusesGroups(): TasksStatusesGroup[] { return this._statusesGroups; } - statusesCount: WritableSignal = signal([]); - - loading = true; - - #tasksGrpcService = inject(TasksGrpcService); - readonly cacheService = inject(CacheService); - - subscription = new Subscription(); + get filters(): TaskSummaryFilters { + return this._filters; + } @Input({ required: true }) set filters(entries: TaskSummaryFilters) { - this.initCount(entries); - this.refresh.pipe( - switchMap(() => this.#tasksGrpcService.countByStatus$(entries)), - ).subscribe(response => { - this.loading = false; - this.statusesCount.set(response.status ?? []); - this.saveData(response.status); - }); - this.refresh.next(); + this._filters = entries; } - initCount(filters: TaskSummaryFilters) { - this.#setId(filters); - this.loadFromCache(); + ngOnInit(): void { + this.initId(); } - loadFromCache() { - if (this.id) { - this.statusesCount.set(this.cacheService.getStatuses(this.id) ?? []); - } + initId() { + this.#setId(this.filters); } - saveData(data: StatusCount[] | undefined) { - if (this.id && data) { - this.cacheService.saveStatuses(this.id, data); - } + initRefresh() { + this._refresh$.pipe( + switchMap(() => this.tasksGrpcService.countByStatus$(this.filters)), + ).subscribe(response => { + this.loading = false; + this.statusesCount.set(response.status ?? []); + }); } #setId(filter: TaskSummaryFilters) { diff --git a/src/app/components/inspect-list.component.html b/src/app/components/inspect-list.component.html new file mode 100644 index 000000000..ad81f9c4a --- /dev/null +++ b/src/app/components/inspect-list.component.html @@ -0,0 +1,26 @@ + + + @if (queryParams) { + + } + + + @if (list.length === 0) { + No data + } @else { + @for(item of list; track item) { +
+ @if(redirectLink) { + + } @else { + {{item}} + } +
+ @if(!$last) { + + } + } + } +
\ No newline at end of file diff --git a/src/app/components/inspect-list.component.spec.ts b/src/app/components/inspect-list.component.spec.ts new file mode 100644 index 000000000..762e822c9 --- /dev/null +++ b/src/app/components/inspect-list.component.spec.ts @@ -0,0 +1,63 @@ +import { TestBed } from '@angular/core/testing'; +import { InspectListComponent } from './inspect-list.component'; + +describe('InspectListComponent', () => { + let component: InspectListComponent; + const list = ['item1', 'item2', 'item3']; + const queryParams = '0-root-1-0'; + const finalParams = { + '0-root-1-0': list[0], + '1-root-1-0': list[1], + '2-root-1-0': list[2] + }; + + beforeEach(() => { + component = TestBed.configureTestingModule({ + providers: [ + InspectListComponent + ] + }).inject(InspectListComponent); + component.list = list; + component.queryParams = queryParams; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('setting list', () => { + it('should set list', () => { + expect(component.list).toEqual(list); + }); + + it('should not update list if it is undefined', () => { + component.list = undefined; + expect(component.list).toEqual(list); + }); + + it('should update list if a new one is provided', () => { + component.list = []; + expect(component.list).toEqual([]); + }); + }); + + describe('setting queryParams', () => { + it('should create a correct Params object', () => { + expect(component.queryParams).toEqual(finalParams); + }); + + it('should not update queryParams if it is undefined', () => { + component.queryParams = undefined; + expect(component.queryParams).toEqual(finalParams); + }); + + it('should update queryParams if a new one is provided', () => { + component.queryParams = '0-options-2-0'; + expect(component.queryParams).toEqual({ + '0-options-2-0': list[0], + '1-options-2-0': list[1], + '2-options-2-0': list[2] + }); + }); + }); +}); \ No newline at end of file diff --git a/src/app/components/inspect-list.component.ts b/src/app/components/inspect-list.component.ts new file mode 100644 index 000000000..dcc6c4513 --- /dev/null +++ b/src/app/components/inspect-list.component.ts @@ -0,0 +1,86 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; +import { MatDivider } from '@angular/material/divider'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { Params, RouterModule } from '@angular/router'; + +/** + * The inspect list component provide a way to display lists inside a Mat-Card. + * @property { string[] | undefined } list - Displayed list + * @property { string | undefined } redirectLink - Where lists items redirect + */ +@Component({ + selector: 'app-inspect-list', + templateUrl: 'inspect-list.component.html', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ + MatToolbarModule, + MatCardModule, + MatButtonModule, + RouterModule, + MatDivider, + ], + styles: [` + .header { + display: flex; + align-items: center; + justify-content: space-between; + } + + .no-data { + text-align: center; + margin: 1rem; + font-style: italic; + } + + button { + width: fit-content; + } + + .item { + width: 100%; + margin: 0.5rem; + } + + mat-divider { + margin-right: 0.5rem; + } + + mat-toolbar { + padding: 1rem; + } + `] +}) +export class InspectListComponent { + private _list: string[] = []; + private _queryParams: Params; + + @Input({ required: true }) set list(entries: string[] | undefined) { + if (entries) { + this._list = entries; + } + } + + @Input({ required: false }) set queryParams(entry: string | undefined) { + if (entry && this.list.length !== 0) { + this._queryParams = {}; + const paramsKey = entry.slice(1); + this.list.forEach((value, index) => { + const key = `${index}${paramsKey}`; + this._queryParams[key] = value; + }); + } + } + + @Input({ required: false }) redirectLink: string | undefined; + + get list(): string[] { + return this._list; + } + + get queryParams(): Params { + return this._queryParams; + } +} \ No newline at end of file diff --git a/src/app/components/inspection-header.component.html b/src/app/components/inspection-header.component.html new file mode 100644 index 000000000..20ea31694 --- /dev/null +++ b/src/app/components/inspection-header.component.html @@ -0,0 +1,12 @@ + + + {{ id }} + + @if(status) { + + {{ status }} + + } + \ No newline at end of file diff --git a/src/app/components/inspection-header.component.spec.ts b/src/app/components/inspection-header.component.spec.ts new file mode 100644 index 000000000..7b666c4e8 --- /dev/null +++ b/src/app/components/inspection-header.component.spec.ts @@ -0,0 +1,76 @@ +import { Clipboard } from '@angular/cdk/clipboard'; +import { TestBed } from '@angular/core/testing'; +import { IconsService } from '@services/icons.service'; +import { NotificationService } from '@services/notification.service'; +import { InspectionHeaderComponent } from './inspection-header.component'; + +describe('InspectionHeaderComponent', () => { + let component: InspectionHeaderComponent; + + const id = 'id'; + + const mockNotificationService = { + success: jest.fn() + }; + + const mockClipboard = { + copy: jest.fn() + }; + + beforeEach(() => { + component = TestBed.configureTestingModule({ + providers: [ + InspectionHeaderComponent, + IconsService, + { provide: NotificationService, useValue: mockNotificationService }, + { provide: Clipboard, useValue: mockClipboard } + ] + }).inject(InspectionHeaderComponent); + component.id = id; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('id', () => { + it('should have set "id"', () => { + expect(component.id).toEqual(id); + }); + + it('should not change "id" if a null value is provided', () => { + component.id = null; + expect(component.id).toEqual(id); + }); + }); + + it('should set "status"', () => { + const status = 'Completed'; + component.status = status; + expect(component.status).toEqual(status); + }); + + it('should set "sharableURL"', () => { + const url = 'https://localhost'; + component.sharableURL = url; + expect(component.sharableURL).toEqual(url); + }); + + it('should get icons', () => { + expect(component.getIcon('share')).toBeDefined(); + }); + + describe('onCopyId', () => { + beforeEach(() => { + component.onCopyId(); + }); + + it('should call copyService "copy"', () => { + expect(mockClipboard.copy).toHaveBeenCalledWith(id); + }); + + it('should call notificationService "success"', () => { + expect(mockNotificationService.success).toHaveBeenCalled(); + }); + }); +}); \ No newline at end of file diff --git a/src/app/components/inspection-header.component.ts b/src/app/components/inspection-header.component.ts new file mode 100644 index 000000000..be7433c4d --- /dev/null +++ b/src/app/components/inspection-header.component.ts @@ -0,0 +1,64 @@ +import { CdkCopyToClipboard, Clipboard } from '@angular/cdk/clipboard'; +import { Component, Input, inject } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatChipsModule } from '@angular/material/chips'; +import { MatIconModule } from '@angular/material/icon'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { IconsService } from '@services/icons.service'; +import { NotificationService } from '@services/notification.service'; +import { PageHeaderComponent } from './page-header.component'; + +@Component({ + selector: 'app-inspection-header', + templateUrl: 'inspection-header.component.html', + standalone: true, + imports: [ + MatToolbarModule, + PageHeaderComponent, + MatButtonModule, + MatIconModule, + CdkCopyToClipboard, + MatTooltipModule, + MatChipsModule, + ], + providers: [ + IconsService, + Clipboard, + NotificationService, + ], + styles: [` + #status { + font-size: 18px; + } + `] +}) +export class InspectionHeaderComponent { + private readonly iconsService = inject(IconsService); + private readonly copyService = inject(Clipboard); + private readonly notificationService = inject(NotificationService); + + private _id = ''; + + @Input({ required: true }) set id(entry: string | null) { + if (entry) { + this._id = entry; + } + } + + @Input({ required: false }) status: string | undefined; + @Input({ required: false }) sharableURL: string | null; + + get id() { + return this._id; + } + + getIcon(name: string) { + return this.iconsService.getIcon(name); + } + + onCopyId() { + this.copyService.copy(this._id); + this.notificationService.success($localize`Id copy to Clipboard`); + } +} \ No newline at end of file diff --git a/src/app/components/inspection-toolbar.component.html b/src/app/components/inspection-toolbar.component.html new file mode 100644 index 000000000..9fad6bf42 --- /dev/null +++ b/src/app/components/inspection-toolbar.component.html @@ -0,0 +1,14 @@ + + +
+ + +
+
+ + + +
\ No newline at end of file diff --git a/src/app/components/inspection-toolbar.component.spec.ts b/src/app/components/inspection-toolbar.component.spec.ts new file mode 100644 index 000000000..39f981baf --- /dev/null +++ b/src/app/components/inspection-toolbar.component.spec.ts @@ -0,0 +1,28 @@ +import { TestBed } from '@angular/core/testing'; +import { IconsService } from '@services/icons.service'; +import { InspectionToolbarComponent } from './inspection-toolbar.component'; + +describe('InspectionToolbar', () => { + let component: InspectionToolbarComponent; + + beforeEach(() => { + component = TestBed.configureTestingModule({ + providers: [ + InspectionToolbarComponent, + IconsService + ] + }).inject(InspectionToolbarComponent); + }); + + it('should run', () => { + expect(component).toBeTruthy(); + }); + + it('should have a defined refresh', () => { + expect(component.refresh).toBeDefined(); + }); + + it('should have a defined refreshIcon', () => { + expect(component.refreshIcon).toBeDefined(); + }); +}); \ No newline at end of file diff --git a/src/app/components/inspection-toolbar.component.ts b/src/app/components/inspection-toolbar.component.ts new file mode 100644 index 000000000..e2df094c4 --- /dev/null +++ b/src/app/components/inspection-toolbar.component.ts @@ -0,0 +1,45 @@ +import { Component, Output, inject } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { Subject } from 'rxjs'; +import { IconsService } from '@services/icons.service'; + +@Component({ + selector: 'app-inspection-toolbar', + templateUrl: 'inspection-toolbar.component.html', + standalone: true, + imports: [ + MatToolbarModule, + MatIconModule, + MatButtonModule, + ], + providers: [ + IconsService + ], + styles: [` + section { + display: flex; + justify-content: space-between; + align-items: center; + width: 100% + } + + mat-toolbar { + justify-content: center; + gap: 1rem; + padding-top: 1rem; + padding-bottom: 1rem; + } + + mat-toolbar-row { + height: fit-content; + } + `] +}) +export class InspectionToolbarComponent { + private readonly iconsService = inject(IconsService); + readonly refreshIcon = this.iconsService.getIcon('refresh'); + + @Output() refresh = new Subject(); +} \ No newline at end of file diff --git a/src/app/components/inspection/field-content.component.html b/src/app/components/inspection/field-content.component.html new file mode 100644 index 000000000..15545d03f --- /dev/null +++ b/src/app/components/inspection/field-content.component.html @@ -0,0 +1,45 @@ + +

{{ key | pretty }}:

+ @if (type !== 'array') { + + @switch (type) { + @case ('date') { + {{ date | date: 'yyyy-MM-dd  HH:mm:ss' | emptyCell }} + } + @case('duration') { + {{ duration | duration | emptyCell }} + } + @case('object') { + {{ object | json }} + } + @default { + {{ value | emptyCell }} + } + } + @if (value) { + + } + @if (field.link && value) { + + } + + } @else { + @if (array.length !== 0) { +
    + @for (item of array; track $index) { +
  • + + {{ item | emptyCell }} + + @if (field.link) { + + } + +
  • + } +
+ } @else { + - + } + } +
\ No newline at end of file diff --git a/src/app/components/inspection/field-content.component.spec.ts b/src/app/components/inspection/field-content.component.spec.ts new file mode 100644 index 000000000..b9f50d18b --- /dev/null +++ b/src/app/components/inspection/field-content.component.spec.ts @@ -0,0 +1,264 @@ +import { TaskStatus } from '@aneoconsultingfr/armonik.api.angular'; +import { Clipboard } from '@angular/cdk/clipboard'; +import { TestBed } from '@angular/core/testing'; +import { Duration, Timestamp } from '@ngx-grpc/well-known-types'; +import { TaskOptions, TaskRaw } from '@app/tasks/types'; +import { Field } from '@app/types/column.type'; +import { Status } from '@app/types/data'; +import { DurationPipe } from '@pipes/duration.pipe'; +import { IconsService } from '@services/icons.service'; +import { NotificationService } from '@services/notification.service'; +import { FieldContentComponent } from './field-content.component'; + +describe('FieldContentComponent', () => { + let component: FieldContentComponent; + + const mockNotificationService = { + success: jest.fn(), + }; + + const mockClipboard = { + copy: jest.fn(), + }; + + const statuses = { + [TaskStatus.TASK_STATUS_COMPLETED]: 'Completed', + [TaskStatus.TASK_STATUS_CANCELLING]: 'Cancelling', + } as Record; + + const data: TaskRaw = { + id: 'taskId', + status: TaskStatus.TASK_STATUS_CANCELLING, + createdAt: { + seconds: '13343490', + nanos: 0 + }, + creationToEndDuration: { + seconds: '1230', + nanos: 0 + }, + parentTaskIds: [], + options: { + applicationName: 'string', + } + } as unknown as TaskRaw; + + const field: Field = { + key: 'id', + }; + + const statusField: Field = { + key: 'status', + type: 'status', + }; + + const dateField: Field = { + key: 'createdAt', + type: 'date', + }; + + const emptyDateField: Field = { + key: 'acquiredAt', + type: 'date' + }; + + const durationField: Field = { + key: 'creationToEndDuration', + type: 'duration', + }; + + const objectField: Field = { + key: 'options', + type: 'object' + }; + + const arrayField: Field = { + key: 'parentTaskIds' + }; + + beforeEach(() => { + component = TestBed.configureTestingModule({ + providers: [ + { provide: NotificationService, useValue: mockNotificationService }, + { provide: Clipboard, useValue: mockClipboard }, + IconsService, + FieldContentComponent + ] + }).inject(FieldContentComponent); + component.statuses = statuses; + }); + + it('should run', () => { + expect(component).toBeTruthy(); + }); + + describe('Basics', () => { + beforeEach(() => { + component.field = field; + component.data = data; + }); + + it('should get the value', () => { + expect(component.value).toEqual(data.id); + }); + + it('should get the key', () => { + expect(component.key).toEqual(field.key); + }); + + it('should copy the raw value', () => { + component.copy(); + expect(mockClipboard.copy).toHaveBeenCalledWith(data.id); + }); + + it('should notify on copy', () => { + component.copy(); + expect(mockNotificationService.success).toHaveBeenCalled(); + }); + }); + + describe('Statuses', () => { + beforeEach(() => { + component.field = statusField; + component.data = data; + }); + + it('should get the status', () => { + expect(component.value).toEqual(statuses[data.status]); + }); + + it('should get the key', () => { + expect(component.key).toEqual(statusField.key); + }); + + it('should copy the status', () => { + component.copy(); + expect(mockClipboard.copy).toHaveBeenCalledWith(statuses[data.status]); + }); + + it('should notify on copy', () => { + component.copy(); + expect(mockNotificationService.success).toHaveBeenCalled(); + }); + }); + + describe('Dates', () => { + beforeEach(() => { + component.field = dateField; + component.data = data; + }); + + it('should get the value as a date', () => { + expect(component.date).toEqual((new Timestamp(data.createdAt)).toDate()); + }); + + it('should get the key', () => { + expect(component.key).toEqual(dateField.key); + }); + + it('should copy the date', () => { + component.copy(); + expect(mockClipboard.copy).toHaveBeenCalledWith((new Timestamp(data.createdAt)).toDate().toLocaleString()); + }); + + it('should notify on copy', () => { + component.copy(); + expect(mockNotificationService.success).toHaveBeenCalled(); + }); + + it('should set undefined is the date is undefined too', () => { + component.field = emptyDateField; + component.data = data; + expect(component.date).toBeUndefined(); + }); + }); + + describe('Durations', () => { + beforeEach(() => { + component.field = durationField; + component.data = data; + }); + + it('should get the value as a duration', () => { + expect(component.value).toEqual(data.creationToEndDuration); + }); + + it('should get the key', () => { + expect(component.key).toEqual(durationField.key); + }); + + it('should copy the duration', () => { + component.copy(); + const durationPipe = new DurationPipe(); + expect(mockClipboard.copy).toHaveBeenCalledWith(durationPipe.transform(data.creationToEndDuration as Duration)); + }); + + it('should notify on copy', () => { + component.copy(); + expect(mockNotificationService.success).toHaveBeenCalled(); + }); + + it('should not copy if there is no duration', () => { + component.data = {} as TaskRaw; + component.copy(); + expect(mockClipboard.copy).not.toHaveBeenCalled(); + }); + }); + + describe('Objects', () => { + beforeEach(() => { + component.field = objectField; + component.data = data; + }); + + it('should get the value as an object', () => { + expect(component.object).toEqual(data.options); + }); + + it('should get the key', () => { + expect(component.key).toEqual(objectField.key); + }); + }); + + describe('guessType', () => { + it('should guess a date', () => { + component.field = { key: 'acquiredAt' }; + expect(component.type).toEqual('date'); + }); + + it('should guess a duration', () => { + component.field = { key: 'creationToEndDuration' }; + expect(component.type).toEqual('duration'); + }); + + it('should guess an object with "options"', () => { + component.field = { key: 'options' }; + expect(component.type).toEqual('object'); + }); + + it('should guess an object with "options.options"', () => { + component.field = { key: 'options' }; + expect(component.type).toEqual('object'); + }); + + it('should return raw if it cannot guess anything', () => { + component.field = { key: 'id' }; + expect(component.type).toEqual('raw'); + }); + }); + + describe('array', () => { + beforeEach(() => { + component.field = arrayField; + component.data = data; + }); + + it('should set the type to "array" in case of an array', () => { + expect(component.type).toEqual('array'); + }); + + it('should get the array', () => { + expect(component.array).toEqual(data.parentTaskIds); + }); + }); +}); \ No newline at end of file diff --git a/src/app/components/inspection/field-content.component.ts b/src/app/components/inspection/field-content.component.ts new file mode 100644 index 000000000..9ee7950db --- /dev/null +++ b/src/app/components/inspection/field-content.component.ts @@ -0,0 +1,174 @@ +import { Clipboard } from '@angular/cdk/clipboard'; +import { DatePipe, JsonPipe } from '@angular/common'; +import { ChangeDetectionStrategy, Component, Input, inject } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatChipsModule } from '@angular/material/chips'; +import { MatIconModule } from '@angular/material/icon'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { RouterLink } from '@angular/router'; +import { Duration, Timestamp } from '@ngx-grpc/well-known-types'; +import { TaskOptions } from '@app/tasks/types'; +import { ColumnType, Field } from '@app/types/column.type'; +import { DataRaw, Status } from '@app/types/data'; +import { DurationPipe } from '@pipes/duration.pipe'; +import { EmptyCellPipe } from '@pipes/empty-cell.pipe'; +import { PrettyPipe } from '@pipes/pretty.pipe'; +import { IconsService } from '@services/icons.service'; +import { NotificationService } from '@services/notification.service'; + +@Component({ + selector: 'app-field-content', + templateUrl: 'field-content.component.html', + standalone: true, + imports: [ + MatChipsModule, + DatePipe, + DurationPipe, + EmptyCellPipe, + JsonPipe, + MatButtonModule, + MatIconModule, + RouterLink, + MatTooltipModule, + PrettyPipe, + ], + providers: [ + NotificationService, + IconsService + ], + styleUrl: '../../../inspections.css', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class FieldContentComponent { + private readonly clipboard = inject(Clipboard); + private readonly notificationService = inject(NotificationService); + private readonly iconsService = inject(IconsService); + + type: ColumnType | undefined; + private _field: Field | Field; + private _value: unknown; + + readonly viewIcon = this.iconsService.getIcon('view'); + readonly copyIcon = this.iconsService.getIcon('copy'); + + @Input({ required: true }) set field(entry: Field | Field) { + this._field = entry; + if (entry.type) { + this.type = entry.type; + } else { + this.type = this.guessType(entry.key); + } + } + + @Input({ required: true }) set data(entry: T | NonNullable | null) { + if (entry) { + switch (this.type) { + case 'status': { + this._value = this.statuses[entry[this.field.key as keyof (T |O)] as S]; + break; + } + case 'date': { + const value = entry[this.field.key as keyof (T |O)] as Partial | undefined; + if (value) { + const date = new Timestamp(value); + this._value = date.toDate(); + } else { + this._value = undefined; + } + break; + } + default: { + this._value = entry[this.field.key as keyof (T |O)]; + this.checkIfArray(); + } + } + } + } + + @Input({ required: false }) statuses: Record; + + get key(): string { + return this._field.key.toString(); + } + + get field(): Field | Field { + return this._field; + } + + /** + * Can be a string, number, status label + */ + get value(): string { + return this._value as string; + } + + get duration(): Duration { + return this._value as Duration; + } + + get date(): Date { + return this._value as Date; + } + + get object(): object { + return this._value as object; + } + + get array(): Array { + return this._value as Array; + } + + /** + * When the type is not provided by the `field` object, for display purpose, the component has to find the correct type. + * @param value - key of the field. + * @returns The guessed type of the field. + */ + private guessType(value: keyof T | keyof O): ColumnType { + const key = value.toString().toLowerCase().replace('_', ''); + if (key.endsWith('at')) { + return 'date'; + } else if (key.includes('duration') || key.includes('ttl')) { + return 'duration'; + } else if (key === 'options' || key === 'options.options' || key === 'output') { + return 'object'; + } else { + return 'raw'; + } + } + + /** + * Check if the received data is an Array. + * If `true`, then the type is set to `array`. + * If `false`, the provided type does not change. + */ + private checkIfArray() { + if (this._value instanceof Array) { + this.type = 'array'; + } + } + + copy() { + switch (this.field.type) { + case 'date': { + this.clipboardCopy(this.date.toLocaleString()); + break; + } + case 'duration': { + const durationPipe = new DurationPipe(); + const durationString = durationPipe.transform(this.duration); + if (durationString) { + this.clipboardCopy(durationString); + } + break; + } + default: { + this.clipboardCopy(this.value); + } + } + } + + clipboardCopy(value: string) { + this.clipboard.copy(value); + this.notificationService.success('Value copied'); + } +} \ No newline at end of file diff --git a/src/app/components/inspection/inspection-card.component.html b/src/app/components/inspection/inspection-card.component.html new file mode 100644 index 000000000..714b1c4d0 --- /dev/null +++ b/src/app/components/inspection/inspection-card.component.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/app/components/inspection/inspection-card.component.spec.ts b/src/app/components/inspection/inspection-card.component.spec.ts new file mode 100644 index 000000000..01e9b4173 --- /dev/null +++ b/src/app/components/inspection/inspection-card.component.spec.ts @@ -0,0 +1,81 @@ +import { TaskStatus } from '@aneoconsultingfr/armonik.api.angular'; +import { TaskOptions, TaskRaw } from '@app/tasks/types'; +import { Field } from '@app/types/column.type'; +import { Status } from '@app/types/data'; +import { InspectionCardComponent } from './inspection-card.component'; + +describe('InspectionCardComponent', () => { + const component = new InspectionCardComponent(); + + const data: TaskRaw = { + id: 'taskId', + options: { + applicationName: 'string', + } + } as TaskRaw; + + const fields: Field[] = [ + { + key: 'id', + type: 'link', + link: 'tasks' + }, + { + key: 'sessionId', + type: 'link', + link: 'sessions' + }, + { + key: 'options', + type: 'object' + }, + { + key: 'createdAt', + type: 'date' + } + ]; + + const optionsFields: Field[] = [ + { + key: 'applicationName', + }, + { + key: 'options', + type: 'object' + } + ]; + + const statuses = { + [TaskStatus.TASK_STATUS_COMPLETED]: 'Completed', + [TaskStatus.TASK_STATUS_CANCELLING]: 'Cancelling', + } as Record; + + beforeEach(() => { + component.fields = fields; + component.optionsFields = optionsFields; + component.data = data; + component.statuses = statuses; + }); + + it('should run', () => { + expect(component).toBeTruthy(); + }); + + describe('initialisation', () => { + it('should init fields', () => { + expect(component.fields).toEqual(fields); + }); + + it('should init options fields', () => { + expect(component.optionsFields).toEqual(optionsFields); + }); + + it('should init data', () => { + expect(component.data).toEqual(data); + }); + + it('should init statuses', () => { + expect(component.statuses).toEqual(statuses); + }); + }); +}); \ No newline at end of file diff --git a/src/app/components/inspection/inspection-card.component.ts b/src/app/components/inspection/inspection-card.component.ts new file mode 100644 index 000000000..ab0981e43 --- /dev/null +++ b/src/app/components/inspection/inspection-card.component.ts @@ -0,0 +1,24 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { MatCardModule } from '@angular/material/card'; +import { TaskOptions } from '@app/tasks/types'; +import { Field } from '@app/types/column.type'; +import { DataRaw, Status } from '@app/types/data'; +import { InspectionComponent } from './inspection.component'; + +@Component({ + selector: 'app-inspection-card', + templateUrl: 'inspection-card.component.html', + standalone: true, + imports: [ + MatCardModule, + InspectionComponent + ], + styleUrl: '../../../inspections.css', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class InspectionCardComponent { + @Input({ required: false }) fields: Field[]; + @Input({ required: false }) optionsFields: Field[]; + @Input({ required: false }) statuses: Record; + @Input({ required: true }) data: T | null; +} \ No newline at end of file diff --git a/src/app/components/inspection/inspection-list-grid.component.html b/src/app/components/inspection/inspection-list-grid.component.html new file mode 100644 index 000000000..2c70e623a --- /dev/null +++ b/src/app/components/inspection/inspection-list-grid.component.html @@ -0,0 +1,7 @@ +
+ @for (array of arrays; track array.key) { + +

{{ array.key | pretty }}

+
+ } +
\ No newline at end of file diff --git a/src/app/components/inspection/inspection-list-grid.component.spec.ts b/src/app/components/inspection/inspection-list-grid.component.spec.ts new file mode 100644 index 000000000..c6a90e1a2 --- /dev/null +++ b/src/app/components/inspection/inspection-list-grid.component.spec.ts @@ -0,0 +1,47 @@ +import { TaskRaw } from '@app/tasks/types'; +import { Field } from '@app/types/column.type'; +import { InspectionListGridComponent } from './inspection-list-grid.component'; + +describe('InspectionListGridComponent', () => { + const component = new InspectionListGridComponent(); + + const data: TaskRaw = { + id: 'taskId', + dataDependencies: ['1', '2', '3'], + expectedOutputIds: ['4', '5', '6'] + } as TaskRaw; + + const arrays: Field[] = [ + { + key: 'dataDependencies' + }, + { + key: 'expectedOutputIds' + } + ]; + + beforeEach(() => { + component.data = data; + component.arrays = arrays; + }); + + it('should run', () => { + expect(component).toBeTruthy(); + }); + + describe('initialisation', () => { + it('should set data', () => { + expect(component.data).toEqual(data); + }); + + it('should set arrays', () => { + expect(component.arrays).toEqual(arrays); + }); + }); + + describe('get Array', () => { + it('should return the correct array', () => { + expect(component.getArray(arrays[0].key)).toEqual(data.dataDependencies); + }); + }); +}); \ No newline at end of file diff --git a/src/app/components/inspection/inspection-list-grid.component.ts b/src/app/components/inspection/inspection-list-grid.component.ts new file mode 100644 index 000000000..f79dc52da --- /dev/null +++ b/src/app/components/inspection/inspection-list-grid.component.ts @@ -0,0 +1,25 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { Field } from '@app/types/column.type'; +import { DataRaw } from '@app/types/data'; +import { InspectListComponent } from '@components/inspect-list.component'; +import { PrettyPipe } from '@pipes/pretty.pipe'; + +@Component({ + selector: 'app-inspection-list-grid', + templateUrl: 'inspection-list-grid.component.html', + standalone: true, + styleUrl: '../../../inspections.css', + imports: [ + InspectListComponent, + PrettyPipe + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class InspectionListGridComponent { + @Input({ required: true }) data: T; + @Input({ required: true }) arrays: Field[]; + + getArray(key: keyof T) { + return this.data[key] as string[]; + } +} \ No newline at end of file diff --git a/src/app/components/inspection/inspection-object.component.html b/src/app/components/inspection/inspection-object.component.html new file mode 100644 index 000000000..fa42e4c7e --- /dev/null +++ b/src/app/components/inspection/inspection-object.component.html @@ -0,0 +1,20 @@ +@if(data && !isEmpty) { +
+ @for (field of fields; track field.key) { + @if (field.type === 'object') { + + + + {{ field.key | pretty }} + + + + + } @else { + + } + } +
+} @else { +

No data

+} \ No newline at end of file diff --git a/src/app/components/inspection/inspection-object.component.spec.ts b/src/app/components/inspection/inspection-object.component.spec.ts new file mode 100644 index 000000000..5db1319bd --- /dev/null +++ b/src/app/components/inspection/inspection-object.component.spec.ts @@ -0,0 +1,75 @@ +import { TaskStatus } from '@aneoconsultingfr/armonik.api.angular'; +import { TaskRaw } from '@app/tasks/types'; +import { Field } from '@app/types/column.type'; +import { InspectionObjectComponent } from './inspection-object.component'; + +describe('InspectionObjectComponent', () => { + const component = new InspectionObjectComponent(); + + const data: TaskRaw = { + id: 'taskId', + options: { + applicationName: 'string', + } + } as TaskRaw; + + const fields: Field[] = [ + { + key: 'id', + type: 'link', + link: 'tasks' + }, + { + key: 'sessionId', + type: 'link', + link: 'sessions' + }, + { + key: 'options', + type: 'object' + }, + { + key: 'createdAt', + type: 'date' + } + ]; + + const statuses: Record = { + 0: 'Undefined', + 1: 'completed' + } as Record; + + beforeEach(() => { + component.data = data; + component.fields = fields; + component.statuses = statuses; + }); + + it('should run', () => { + expect(component).toBeTruthy(); + }); + + describe('initialisation', () => { + it('should set data', () => { + expect(component.data).toEqual(data); + }); + + it('should set fields', () => { + expect(component.fields).toEqual(fields); + }); + + it('should set statuses', () => { + expect(component.statuses).toEqual(statuses); + }); + + it('should set data keys as fields if none are provided', () => { + component.fields = []; + component.data = data; + expect(component.fields).toEqual([{ key: 'id' }, { key: 'options' }]); + }); + }); + + it('should get an object', () => { + expect(component.getObject(fields[2])).toEqual(data.options); + }); +}); \ No newline at end of file diff --git a/src/app/components/inspection/inspection-object.component.ts b/src/app/components/inspection/inspection-object.component.ts new file mode 100644 index 000000000..8fc59e2bb --- /dev/null +++ b/src/app/components/inspection/inspection-object.component.ts @@ -0,0 +1,64 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { MatExpansionModule } from '@angular/material/expansion'; +import { TaskOptions } from '@app/tasks/types'; +import { Field } from '@app/types/column.type'; +import { DataRaw, Status } from '@app/types/data'; +import { PrettyPipe } from '@pipes/pretty.pipe'; +import { FieldContentComponent } from './field-content.component'; + +@Component({ + selector: 'app-inspection-object', + templateUrl: 'inspection-object.component.html', + standalone: true, + imports: [ + MatExpansionModule, + PrettyPipe, + FieldContentComponent, + ], + styleUrl: '../../../inspections.css', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class InspectionObjectComponent { + private _data: T | NonNullable | null; + private _fields: Field[] | Field[] = []; + + isEmpty: boolean = true; + + @Input({ required: false }) set fields(entries: Field[] | Field[] | undefined) { + if (entries) { + this._fields = entries; + } + } + + @Input({ required: true }) set data(entry: T | NonNullable | null) { + this._data = entry; + if (entry) { + this.isEmpty = Object.keys(entry).length === 0; + } + if (this.fields?.length === 0 && this.data) { + this.setFieldsFromData(this.data); + } + } + + @Input({ required: false }) statuses: Record; + + get data() { + return this._data; + } + + get fields() { + return this._fields; + } + + getObject(field: Field | Field): T { + return (this.data as T | NonNullable)[field.key as keyof (T | O)] as T; + } + + private setFieldsFromData(data: T | NonNullable) { + this.fields = Object.keys(data).map((d) => { + return { + key: d as keyof (T | O) + }; + }).toSorted((a, b) => a.key.toString().localeCompare(b.key.toString())); + } +} \ No newline at end of file diff --git a/src/app/components/inspection/inspection.component.html b/src/app/components/inspection/inspection.component.html new file mode 100644 index 000000000..b42f72c44 --- /dev/null +++ b/src/app/components/inspection/inspection.component.html @@ -0,0 +1,11 @@ + +@if(optionsFields) { + + + + Options + + + + +} \ No newline at end of file diff --git a/src/app/components/inspection/inspection.component.spec.ts b/src/app/components/inspection/inspection.component.spec.ts new file mode 100644 index 000000000..a6c6e3c2a --- /dev/null +++ b/src/app/components/inspection/inspection.component.spec.ts @@ -0,0 +1,80 @@ +import { TaskStatus } from '@aneoconsultingfr/armonik.api.angular'; +import { TaskOptions, TaskRaw } from '@app/tasks/types'; +import { Field } from '@app/types/column.type'; +import { InspectionComponent } from './inspection.component'; + +describe('InspectionComponent', () => { + const component = new InspectionComponent(); + + const data: TaskRaw = { + id: 'taskId', + options: { + applicationName: 'string', + } + } as TaskRaw; + + const optionsFields: Field[] = [ + { + key: 'applicationName', + }, + { + key: 'options', + type: 'object' + } + ]; + + const fields: Field[] = [ + { + key: 'id', + type: 'link', + link: 'tasks' + }, + { + key: 'sessionId', + type: 'link', + link: 'sessions' + }, + { + key: 'options', + type: 'object' + }, + { + key: 'createdAt', + type: 'date' + } + ]; + + const statuses: Record = { + 0: 'Undefined', + 1: 'completed' + } as Record; + + beforeEach(() => { + component.fields = fields; + component.optionsFields = optionsFields; + component.data = data; + component.statuses = statuses; + }); + + describe('initialisation', () => { + it('should set fields without options fields', () => { + expect(component.fields).toEqual(fields); + }); + + it('should set optionsFields', () => { + expect(component.optionsFields).toEqual(optionsFields); + }); + + it('should set data', () => { + expect(component.data).toEqual(data); + }); + + it('should set "options" object', () => { + expect(component.options).toEqual(data.options); + }); + + it('should set statuses', () => { + expect(component.statuses).toEqual(statuses); + }); + }); +}); \ No newline at end of file diff --git a/src/app/components/inspection/inspection.component.ts b/src/app/components/inspection/inspection.component.ts new file mode 100644 index 000000000..82820482a --- /dev/null +++ b/src/app/components/inspection/inspection.component.ts @@ -0,0 +1,52 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { MatExpansionModule } from '@angular/material/expansion'; +import { TaskOptions } from '@app/tasks/types'; +import { Field } from '@app/types/column.type'; +import { DataRaw, Status } from '@app/types/data'; +import { InspectionObjectComponent } from './inspection-object.component'; + +@Component({ + selector: 'app-inspection', + templateUrl: 'inspection.component.html', + standalone: true, + imports: [ + InspectionObjectComponent, + MatExpansionModule, + ], + changeDetection: ChangeDetectionStrategy.OnPush, + styleUrl: '../../../inspections.css', +}) +export class InspectionComponent { + private _data: T = {} as T; + private _options: NonNullable = {} as NonNullable; + + /** + * Displayed fields and their types. + * If none are provided, every field will be displayed as a "string". + */ + @Input({ required: false }) fields: Field[] | undefined; + + @Input({ required: false }) optionsFields: Field[] | undefined; + + @Input({ required: true }) set data(entry: T | null) { + if (entry) { + this._data = entry; + if (entry['options' as keyof (T | O)]) { + this._options = entry['options' as keyof (T | O)] as NonNullable; + } + } + } + + /** + * Required to display a status label. + */ + @Input({ required: false }) statuses: Record; + + get data(): T { + return this._data; + } + + get options() { + return this._options; + } +} \ No newline at end of file diff --git a/src/app/components/navigation/external-services.component.html b/src/app/components/navigation/external-services.component.html new file mode 100644 index 000000000..692e92ebe --- /dev/null +++ b/src/app/components/navigation/external-services.component.html @@ -0,0 +1,19 @@ +
+ + @if (!hasService) { + + } @else { + @for(service of externalServices; track service.name) { + + } + + } + +
\ No newline at end of file diff --git a/src/app/components/navigation/external-services.component.spec.ts b/src/app/components/navigation/external-services.component.spec.ts new file mode 100644 index 000000000..cc35f1459 --- /dev/null +++ b/src/app/components/navigation/external-services.component.spec.ts @@ -0,0 +1,103 @@ +import { URL } from 'url'; +import { TestBed } from '@angular/core/testing'; +import { MatDialog } from '@angular/material/dialog'; +import { of } from 'rxjs'; +import { ExternalService } from '@app/types/external-service'; +import { IconsService } from '@services/icons.service'; +import { NavigationService } from '@services/navigation.service'; +import { ExternalServicesComponent } from './external-services.component'; + +describe('ExternalServicesComponent', () => { + let component: ExternalServicesComponent; + + const savedServices: ExternalService[] = [{name: 'grafana', url: 'https://grafana'}, {name: 'seq', url: 'https://seq'}]; + const mockNavigationService = { + saveExternalServices: jest.fn(), + restoreExternalServices: jest.fn(() => savedServices) + }; + + let dialogResult: ExternalService[]; + const mockMatDialog = { + open: () => { + return { + afterClosed: () => { + return of(dialogResult); + } + }; + } + }; + + beforeEach(() => { + component = TestBed.configureTestingModule({ + providers: [ + ExternalServicesComponent, + IconsService, + { provide: NavigationService, useValue: mockNavigationService }, + { provide: MatDialog, useValue: mockMatDialog } + ] + }).inject(ExternalServicesComponent); + component.ngOnInit(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('initialisation', () => { + it('should load saved services', () => { + expect(mockNavigationService.restoreExternalServices).toHaveBeenCalled(); + }); + + it('should retrieve saved services', () => { + expect(component.externalServices).toEqual(savedServices); + }); + + it('should check if it has a service', () => { + expect(component.hasService).toBeTruthy(); + }); + }); + + it('should check if there is no service', () => { + component.externalServices = []; + expect(component.hasService).toBeFalsy(); + }); + + it('should get icon', () => { + expect(component.getIcon('heart')).toEqual('favorite'); + }); + + describe('manageExternalService', () => { + const newServices: ExternalService[] = [{name: 'api', url: 'https://url'}]; + dialogResult = newServices; + + beforeEach(() => { + component.manageExternalServices(); + }); + + it('should update "externalServices"', () => { + expect(component.externalServices).toEqual(newServices); + }); + + it('should update "hasServices"', () => { + expect(component.hasService).toBeTruthy(); + }); + + it('should save services', () => { + expect(mockNavigationService.saveExternalServices).toHaveBeenCalled(); + }); + }); + + test('saveServices should save local services', () => { + component.saveServices(); + expect(mockNavigationService.saveExternalServices).toHaveBeenCalled(); + }); + + it('should allow to navigate to an url', () => { + const windowSpy = jest.spyOn(window, 'open'); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + windowSpy.mockImplementationOnce((url?: string | URL | null, target?: string | null, features?: string | undefined) => null); + const url = 'https://url'; + component.navigate(url); + expect(windowSpy).toHaveBeenCalledWith(url, '_blank'); + }); +}); \ No newline at end of file diff --git a/src/app/components/navigation/external-services.component.ts b/src/app/components/navigation/external-services.component.ts new file mode 100644 index 000000000..9a3abf847 --- /dev/null +++ b/src/app/components/navigation/external-services.component.ts @@ -0,0 +1,87 @@ +import { Component, OnInit, inject } from '@angular/core'; +import { MatButtonModule, MatIconButton } from '@angular/material/button'; +import { MatDialog } from '@angular/material/dialog'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatIconModule } from '@angular/material/icon'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { ExternalService } from '@app/types/external-service'; +import { IconsService } from '@services/icons.service'; +import { NavigationService } from '@services/navigation.service'; +import { ManageExternalServicesDialogComponent } from './manage-external-services-dialog.component'; + +@Component({ + selector: 'app-external-services', + templateUrl: 'external-services.component.html', + imports: [ + MatIconModule, + MatIconButton, + MatTooltipModule, + MatButtonModule, + MatDividerModule, + ], + providers: [ + IconsService, + NavigationService + ], + standalone: true, + styles: [` + article { + display: flex; + } + + mat-divider { + margin-left: 0.75rem; + margin-right: 0.75rem; + } + `] +}) +export class ExternalServicesComponent implements OnInit { + readonly navigationService = inject(NavigationService); + readonly iconsService = inject(IconsService); + readonly dialog = inject(MatDialog); + + hasService: boolean = false; + private _externalServices: ExternalService[] = []; + + set externalServices(entries: ExternalService[] | undefined) { + if (entries) { + this._externalServices = entries; + this.hasService = entries.length > 0; + } + } + + get externalServices(): ExternalService[] { + return this._externalServices; + } + + ngOnInit(): void { + this.externalServices = this.navigationService.restoreExternalServices(); + } + + getIcon(name: string | undefined) { + return this.iconsService.getIcon(name); + } + + manageExternalServices() { + const dialogRef = this.dialog.open(ManageExternalServicesDialogComponent, { + data: { + externalServices: this.externalServices, + } + }); + + dialogRef.afterClosed().subscribe(result => { + if (result) { + this.externalServices = result; + this.saveServices(); + } + }); + } + + saveServices() { + this.navigationService.saveExternalServices(this.externalServices); + } + + navigate(url: string) { + window.open(url, '_blank'); + } +} \ No newline at end of file diff --git a/src/app/components/navigation/navigation.component.html b/src/app/components/navigation/navigation.component.html index fc0670338..b838db3e0 100644 --- a/src/app/components/navigation/navigation.component.html +++ b/src/app/components/navigation/navigation.component.html @@ -23,29 +23,7 @@
- - - @for (service of externalServices; track service.url) { - - @if (service.icon) { - - } - @if (externalServices.length) { - - } - - + - + + + - + + + + - \ No newline at end of file +@if (data) { + + + +} \ No newline at end of file diff --git a/src/app/components/show-page.component.spec.ts b/src/app/components/show-page.component.spec.ts index fe6224b7b..335dad5b3 100644 --- a/src/app/components/show-page.component.spec.ts +++ b/src/app/components/show-page.component.spec.ts @@ -1,20 +1,14 @@ import { TestBed } from '@angular/core/testing'; import { DataRaw } from '@app/types/data'; -import { NotificationService } from '@services/notification.service'; import { ShowPageComponent } from './show-page.component'; describe('ShowPageComponent', () => { let component: ShowPageComponent; - - const mockNotificationService = { - success: jest.fn() - }; beforeEach(() => { component = TestBed.configureTestingModule({ providers: [ ShowPageComponent, - {provide: NotificationService, useValue: mockNotificationService} ] }).inject(ShowPageComponent); }); @@ -23,11 +17,6 @@ describe('ShowPageComponent', () => { expect(component).toBeTruthy(); }); - it('should call a notification success on copy', () => { - component.onCopiedTaskId(); - expect(mockNotificationService.success).toHaveBeenCalledWith('Task ID copied to clipboard'); - }); - it('should emit on refresh', () => { const refreshSpy = jest.spyOn(component.refresh, 'emit'); component.onRefresh(); diff --git a/src/app/components/show-page.component.ts b/src/app/components/show-page.component.ts index 4e9f70ac0..16aaaddb2 100644 --- a/src/app/components/show-page.component.ts +++ b/src/app/components/show-page.component.ts @@ -1,13 +1,12 @@ -import { ClipboardModule } from '@angular/cdk/clipboard'; -import { CommonModule } from '@angular/common'; -import { Component, EventEmitter, Input, Output, inject } from '@angular/core'; -import { MatButtonModule } from '@angular/material/button'; -import { MatIconModule } from '@angular/material/icon'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; +import { TaskOptions } from '@app/tasks/types'; +import { Field } from '@app/types/column.type'; import { ShowActionButton } from '@app/types/components/show'; import { DataRaw } from '@app/types/data'; -import { NotificationService } from '@services/notification.service'; -import { PageHeaderComponent } from './page-header.component'; +import { InspectionCardComponent } from './inspection/inspection-card.component'; +import { InspectionListGridComponent } from './inspection/inspection-list-grid.component'; +import { InspectionHeaderComponent } from './inspection-header.component'; +import { InspectionToolbarComponent } from './inspection-toolbar.component'; import { ShowActionsComponent } from './show-actions.component'; import { ShowCardComponent } from './show-card.component'; @@ -20,33 +19,27 @@ span { } `], standalone: true, - providers: [ - NotificationService, - MatSnackBar - ], imports: [ - PageHeaderComponent, ShowCardComponent, ShowActionsComponent, - CommonModule, - MatIconModule, - ClipboardModule, - MatButtonModule - ] + InspectionCardComponent, + InspectionHeaderComponent, + InspectionListGridComponent, + InspectionToolbarComponent, + ], + changeDetection: ChangeDetectionStrategy.OnPush }) -export class ShowPageComponent{ +export class ShowPageComponent{ @Input({ required: true }) id: string | null = null; @Input({required: true }) data: T | null; @Input() statuses: Record = []; @Input() sharableURL: string | null = null; - @Input({ required: true }) actionsButton: ShowActionButton[]; + @Input({ required: false }) actionsButton: ShowActionButton[]; @Output() refresh = new EventEmitter(); - - #notificationService = inject(NotificationService); - - onCopiedTaskId() { - this.#notificationService.success('Task ID copied to clipboard'); - } + @Input({ required: false }) arrays: Field[]; + @Input({ required: false }) status: string | undefined; + @Input({ required: false }) fields: Field[]; + @Input({ required: false }) optionsFields: Field[]; onRefresh() { this.refresh.emit(); diff --git a/src/app/components/table-actions-toolbar.component.spec.ts b/src/app/components/table-actions-toolbar.component.spec.ts index 8175caeda..840498263 100644 --- a/src/app/components/table-actions-toolbar.component.spec.ts +++ b/src/app/components/table-actions-toolbar.component.spec.ts @@ -1,29 +1,24 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { TestBed } from '@angular/core/testing'; +import { SessionRaw } from '@app/sessions/types'; +import { TaskOptions } from '@app/tasks/types'; import { ColumnKey } from '@app/types/data'; import { IconsService } from '@services/icons.service'; import { TableActionsToolbarComponent } from './table-actions-toolbar.component'; describe('TableActionsToolbarComponent', () => { - let component: TableActionsToolbarComponent; - let fixture: ComponentFixture>; + let component: TableActionsToolbarComponent; const mockIconService = { getIcon: jest.fn() }; let spyOnEventEmitter: jest.SpyInstance; beforeEach(async () => { - await TestBed.configureTestingModule({ + component = TestBed.configureTestingModule({ providers: [ TableActionsToolbarComponent, { provide: IconsService, useValue: mockIconService } ] - }).compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(TableActionsToolbarComponent); - component = fixture.componentInstance; - fixture.detectChanges(); + }).inject(TableActionsToolbarComponent); }); it('Should run', () => { @@ -49,7 +44,7 @@ describe('TableActionsToolbarComponent', () => { test('onDisplayedColumnsChange should emit the provided list',() => { spyOnEventEmitter = jest.spyOn(component.displayedColumnsChange, 'emit'); - const columnsKeys: ColumnKey[] = ['actions']; + const columnsKeys: ColumnKey[] = ['actions']; component.onDisplayedColumnsChange(columnsKeys); expect(spyOnEventEmitter).toHaveBeenCalledWith(columnsKeys); }); diff --git a/src/app/components/table-actions-toolbar.component.ts b/src/app/components/table-actions-toolbar.component.ts index 27fbee5a4..209960fd8 100644 --- a/src/app/components/table-actions-toolbar.component.ts +++ b/src/app/components/table-actions-toolbar.component.ts @@ -3,7 +3,8 @@ import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatMenuModule } from '@angular/material/menu'; import { MatTooltipModule } from '@angular/material/tooltip'; -import { ColumnKey, RawColumnKey } from '@app/types/data'; +import { TaskOptions } from '@app/tasks/types'; +import { ColumnKey, DataRaw } from '@app/types/data'; import { RefreshButtonComponent } from '@components/refresh-button.component'; import { IconsService } from '@services/icons.service'; import { ActionsToolbarGroupComponent } from './actions-toolbar-group.component'; @@ -32,15 +33,15 @@ import { SpinnerComponent } from './spinner.component'; MatTooltipModule, ] }) -export class TableActionsToolbarComponent { +export class TableActionsToolbarComponent { #iconsService = inject(IconsService); @Input({ required: true }) loading = false; @Input({ required: true }) refreshTooltip = ''; @Input({ required: true }) intervalValue = 0; @Input({ required: true }) columnsLabels: Record, string>; - @Input({ required: true }) displayedColumns: RawColumnKey[] = []; - @Input({ required: true }) availableColumns: RawColumnKey[] = []; + @Input({ required: true }) displayedColumns: ColumnKey[] = []; + @Input({ required: true }) availableColumns: ColumnKey[] = []; @Input({ required: true }) lockColumns = false; @Output() refresh: EventEmitter = new EventEmitter(); diff --git a/src/app/components/table-dashboard-actions-toolbar.component.spec.ts b/src/app/components/table-dashboard-actions-toolbar.component.spec.ts index f8f3f9ae7..316736488 100644 --- a/src/app/components/table-dashboard-actions-toolbar.component.spec.ts +++ b/src/app/components/table-dashboard-actions-toolbar.component.spec.ts @@ -1,11 +1,12 @@ import { TestBed } from '@angular/core/testing'; -import { SessionRawColumnKey } from '@app/sessions/types'; -import { ColumnKey, RawColumnKey } from '@app/types/data'; +import { SessionRaw, SessionRawColumnKey } from '@app/sessions/types'; +import { TaskOptions } from '@app/tasks/types'; +import { ColumnKey } from '@app/types/data'; import { IconsService } from '@services/icons.service'; import { TableDashboardActionsToolbarComponent } from './table-dashboard-actions-toolbar.component'; describe('TableDashboardActionsToolbarComponent', () => { - let component: TableDashboardActionsToolbarComponent; + let component: TableDashboardActionsToolbarComponent; const loading = true; const refreshTooltip = 'refreshTooltip'; @@ -15,8 +16,8 @@ describe('TableDashboardActionsToolbarComponent', () => { 'sessionId': 'Session ID', 'status': 'Status', } as Record; - const displayedColumns: RawColumnKey[] = ['sessionId', 'actions']; - const availableColumns: RawColumnKey[] = ['sessionId', 'actions', 'status']; + const displayedColumns: ColumnKey[] = ['sessionId', 'actions']; + const availableColumns: ColumnKey[] = ['sessionId', 'actions', 'status']; const lockColumns = false; beforeEach(() => { @@ -25,7 +26,7 @@ describe('TableDashboardActionsToolbarComponent', () => { TableDashboardActionsToolbarComponent, IconsService ] - }).inject(TableDashboardActionsToolbarComponent); + }).inject(TableDashboardActionsToolbarComponent); component.loading = loading; component.refreshTooltip = refreshTooltip; component.intervalValue = intervalValue; @@ -58,7 +59,7 @@ describe('TableDashboardActionsToolbarComponent', () => { it('should emit columns change', () => { const spy = jest.spyOn(component.displayedColumnsChange, 'emit'); - const columns = ['actions', 'sessionId'] as ColumnKey[]; + const columns = ['actions', 'sessionId'] as ColumnKey[]; component.onColumnsChange(columns); expect(spy).toHaveBeenCalledWith(columns); }); diff --git a/src/app/components/table-dashboard-actions-toolbar.component.ts b/src/app/components/table-dashboard-actions-toolbar.component.ts index aec6f17cc..2004f76f0 100644 --- a/src/app/components/table-dashboard-actions-toolbar.component.ts +++ b/src/app/components/table-dashboard-actions-toolbar.component.ts @@ -1,7 +1,8 @@ import { Component, EventEmitter, Input, Output, inject } from '@angular/core'; import { MatIconModule } from '@angular/material/icon'; import { MatMenuModule } from '@angular/material/menu'; -import { ColumnKey, RawColumnKey } from '@app/types/data'; +import { TaskOptions } from '@app/tasks/types'; +import { ColumnKey, DataRaw } from '@app/types/data'; import { IconsService } from '@services/icons.service'; import { TableActionsToolbarComponent } from './table-actions-toolbar.component'; @@ -15,15 +16,15 @@ import { TableActionsToolbarComponent } from './table-actions-toolbar.component' MatMenuModule, ] }) -export class TableDashboardActionsToolbarComponent { +export class TableDashboardActionsToolbarComponent { readonly iconsService = inject(IconsService); @Input({ required: true }) loading: boolean; @Input({ required: true }) refreshTooltip: string; @Input({ required: true }) intervalValue: number; @Input({ required: true }) columnsLabels: Record, string>; - @Input({ required: true }) displayedColumns: RawColumnKey[]; - @Input({ required: true }) availableColumns: RawColumnKey[]; + @Input({ required: true }) displayedColumns: ColumnKey[]; + @Input({ required: true }) availableColumns: ColumnKey[]; @Input({ required: true }) lockColumns = false; @Output() refresh: EventEmitter = new EventEmitter(); diff --git a/src/app/components/table-index-actions-toolbar.component.spec.ts b/src/app/components/table-index-actions-toolbar.component.spec.ts index eef59fab8..08266d022 100644 --- a/src/app/components/table-index-actions-toolbar.component.spec.ts +++ b/src/app/components/table-index-actions-toolbar.component.spec.ts @@ -1,22 +1,23 @@ import { TestBed } from '@angular/core/testing'; -import { SessionRawColumnKey } from '@app/sessions/types'; -import { ColumnKey, RawColumnKey } from '@app/types/data'; +import { SessionRaw } from '@app/sessions/types'; +import { TaskOptions } from '@app/tasks/types'; +import { ColumnKey } from '@app/types/data'; import { IconsService } from '@services/icons.service'; import { TableIndexActionsToolbarComponent } from './table-index-actions-toolbar.component'; describe('TableDashboardActionsToolbarComponent', () => { - let component: TableIndexActionsToolbarComponent; + let component: TableIndexActionsToolbarComponent; const loading = true; const refreshTooltip = 'refreshTooltip'; const intervalValue = 10; - const columnsLabels: Record = { + const columnsLabels: Record, string> = { 'actions': 'Actions', 'sessionId': 'Session ID', 'status': 'Status', - } as Record; - const displayedColumns: RawColumnKey[] = ['sessionId', 'actions']; - const availableColumns: RawColumnKey[] = ['sessionId', 'actions', 'status']; + } as Record, string>; + const displayedColumns: ColumnKey[] = ['sessionId', 'actions']; + const availableColumns: ColumnKey[] = ['sessionId', 'actions', 'status']; const lockColumns = false; beforeEach(() => { @@ -25,7 +26,7 @@ describe('TableDashboardActionsToolbarComponent', () => { TableIndexActionsToolbarComponent, IconsService ] - }).inject(TableIndexActionsToolbarComponent); + }).inject(TableIndexActionsToolbarComponent); component.loading = loading; component.refreshTooltip = refreshTooltip; component.intervalValue = intervalValue; @@ -58,7 +59,7 @@ describe('TableDashboardActionsToolbarComponent', () => { it('should emit columns change', () => { const spy = jest.spyOn(component.displayedColumnsChange, 'emit'); - const columns = ['actions', 'sessionId'] as ColumnKey[]; + const columns = ['actions', 'sessionId'] as ColumnKey[]; component.onColumnsChange(columns); expect(spy).toHaveBeenCalledWith(columns); }); diff --git a/src/app/components/table-index-actions-toolbar.component.ts b/src/app/components/table-index-actions-toolbar.component.ts index 5d08569ad..37a7a1b5c 100644 --- a/src/app/components/table-index-actions-toolbar.component.ts +++ b/src/app/components/table-index-actions-toolbar.component.ts @@ -1,7 +1,8 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, inject } from '@angular/core'; import { MatIconModule } from '@angular/material/icon'; import { MatMenuModule } from '@angular/material/menu'; -import { ColumnKey, RawColumnKey } from '@app/types/data'; +import { TaskOptions } from '@app/tasks/types'; +import { ColumnKey, DataRaw } from '@app/types/data'; import { IconsService } from '@services/icons.service'; import { TableActionsToolbarComponent } from './table-actions-toolbar.component'; @@ -16,15 +17,15 @@ import { TableActionsToolbarComponent } from './table-actions-toolbar.component' ], changeDetection: ChangeDetectionStrategy.OnPush }) -export class TableIndexActionsToolbarComponent { +export class TableIndexActionsToolbarComponent { readonly iconsService = inject(IconsService); @Input({ required: true }) loading: boolean; @Input({ required: true }) refreshTooltip: string; @Input({ required: true }) intervalValue: number; @Input({ required: true }) columnsLabels: Record, string>; - @Input({ required: true }) displayedColumns: RawColumnKey[]; - @Input({ required: true }) availableColumns: RawColumnKey[]; + @Input({ required: true }) displayedColumns: ColumnKey[]; + @Input({ required: true }) availableColumns: ColumnKey[]; @Input({ required: true }) lockColumns = false; @Output() refresh: EventEmitter = new EventEmitter(); diff --git a/src/app/components/table/table-actions.component.spec.ts b/src/app/components/table/table-actions.component.spec.ts index ac204e25c..a236e3b35 100644 --- a/src/app/components/table/table-actions.component.spec.ts +++ b/src/app/components/table/table-actions.component.spec.ts @@ -1,10 +1,10 @@ +import { ApplicationRaw } from '@aneoconsultingfr/armonik.api.angular'; import { TestBed } from '@angular/core/testing'; -import { ApplicationData } from '@app/types/data'; import { IconsService } from '@services/icons.service'; import { TableActionsComponent } from './table-actions.component'; describe('TableActionsComponent', () => { - let component: TableActionsComponent; + let component: TableActionsComponent; beforeEach(() => { component = TestBed.configureTestingModule({ @@ -12,7 +12,7 @@ describe('TableActionsComponent', () => { TableActionsComponent, IconsService ] - }).inject(TableActionsComponent); + }).inject(TableActionsComponent); }); it('should run', () => { diff --git a/src/app/components/table/table-actions.component.ts b/src/app/components/table/table-actions.component.ts index af5c3c282..fecbf2c67 100644 --- a/src/app/components/table/table-actions.component.ts +++ b/src/app/components/table/table-actions.component.ts @@ -3,7 +3,8 @@ import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatMenuModule } from '@angular/material/menu'; import { MatTooltipModule } from '@angular/material/tooltip'; -import { ApplicationData, PartitionData, ResultData, SessionData, TaskData } from '@app/types/data'; +import { TaskOptions } from '@app/tasks/types'; +import { ArmonikData, DataRaw } from '@app/types/data'; import { ActionTable } from '@app/types/table'; import { IconsService } from '@services/icons.service'; @@ -23,9 +24,9 @@ import { IconsService } from '@services/icons.service'; MatMenuModule ] }) -export class TableActionsComponent { - @Input() actions: ActionTable[] = []; - @Input() element: T; +export class TableActionsComponent { + @Input() actions: ActionTable[] = []; + @Input() element: ArmonikData; readonly iconsService = inject(IconsService); diff --git a/src/app/components/table/table-cell.component.html b/src/app/components/table/table-cell.component.html index b24102d48..5f18a17d9 100644 --- a/src/app/components/table/table-cell.component.html +++ b/src/app/components/table/table-cell.component.html @@ -19,9 +19,9 @@ @case ('count') { } @case ('select') { diff --git a/src/app/components/table/table-cell.component.spec.ts b/src/app/components/table/table-cell.component.spec.ts index e302cd8e1..96f22729e 100644 --- a/src/app/components/table/table-cell.component.spec.ts +++ b/src/app/components/table/table-cell.component.spec.ts @@ -10,7 +10,7 @@ import { ApplicationData, ArmonikData, DataRaw, PartitionData, SessionData } fro import { TableCellComponent } from './table-cell.component'; describe('TableCellComponent', () => { - let component: TableCellComponent, SessionRawColumnKey, SessionStatus>; + let component: TableCellComponent; const options = { applicationName: 'application-name', @@ -22,7 +22,7 @@ describe('TableCellComponent', () => { nanos: 0, }); - const column: TableColumn = { + const column: TableColumn = { key: 'sessionId', name: 'Session ID', sortable: true, @@ -42,7 +42,7 @@ describe('TableCellComponent', () => { const queryParamsMap = new Map(); queryParamsMap.set('sessionId', { sessionId: 'session-id' }); - const element: SessionData = { + const element: ArmonikData = { raw: { sessionId: 'session-id', status: SessionStatus.SESSION_STATUS_RUNNING, @@ -74,11 +74,11 @@ describe('TableCellComponent', () => { TableCellComponent, { provide: Router, useValue: mockRouter } ] - }).inject(TableCellComponent); + }).inject(TableCellComponent); component.statusesService = new SessionsStatusesService(); component.column = column; - component.element = element as ArmonikData; + component.element = element; }); it('should create', () => { @@ -104,7 +104,7 @@ describe('TableCellComponent', () => { describe('simple value', () => { beforeEach(() => { component.column = column; - component.element = element as ArmonikData; + component.element = element; }); it('should set value', () => { @@ -119,12 +119,12 @@ describe('TableCellComponent', () => { sortable: false, }; const spy = jest.spyOn(component.refreshStatuses, 'next'); - component.element = element as ArmonikData; + component.element = element; expect(spy).toHaveBeenCalled(); }); test('undefined element should return undefined value', () => { - component.element = undefined as unknown as ArmonikData; + component.element = undefined as unknown as ArmonikData; expect(component.value).toBeUndefined(); }); @@ -139,7 +139,7 @@ describe('TableCellComponent', () => { }; const elementCopy = structuredClone(element); elementCopy.queryParams = undefined; - component.element = elementCopy as ArmonikData; + component.element = elementCopy; }); it('should set link', () => { @@ -155,7 +155,7 @@ describe('TableCellComponent', () => { sortable: true, name: 'Duration' }; - component.element = element as ArmonikData; + component.element = element; }); it('should set durationValue', () => { @@ -174,7 +174,7 @@ describe('TableCellComponent', () => { sortable: true, name: 'Status' }; - component.element = element as ArmonikData; + component.element = element; }); it('should set statusValue', () => { @@ -191,7 +191,7 @@ describe('TableCellComponent', () => { column.key = 'createdAt'; column.type = 'date'; component.column = column; - component.element = element as unknown as ArmonikData; + component.element = element; }); it('should set dateValue', () => { @@ -201,7 +201,7 @@ describe('TableCellComponent', () => { it('should return null if value is undefined', () => { component.element = { raw: {} - } as unknown as ArmonikData; + } as ArmonikData; expect(component.dateValue).toBeNull(); }); }); @@ -214,7 +214,7 @@ describe('TableCellComponent', () => { sortable: true, name: 'Options' }; - component.element = element as unknown as ArmonikData; + component.element = element; }); it('should set value', () => { @@ -225,7 +225,7 @@ describe('TableCellComponent', () => { describe('selection', () => { beforeEach(() => { component.column.key = 'sessionId'; - component.element = element as unknown as ArmonikData; + component.element = element; }); it('should emit changeSelection', () => { @@ -257,7 +257,7 @@ describe('TableCellComponent', () => { }); it('should create a link with queryParams', () => { - component.element = element as ArmonikData; + component.element = element; component.createLink(); expect(component.link).toEqual('/sessions'); }); @@ -266,7 +266,7 @@ describe('TableCellComponent', () => { const elementCopy = structuredClone(element); elementCopy.queryParams = undefined; elementCopy.raw.sessionId = 'session-id'; - component.element = elementCopy as ArmonikData; + component.element = elementCopy; component.createLink(); expect(component.link).toEqual(`${component.column.link}/${elementCopy.raw[component.column.key as keyof DataRaw]}`); }); diff --git a/src/app/components/table/table-cell.component.ts b/src/app/components/table/table-cell.component.ts index 96543ef18..e4e9751f7 100644 --- a/src/app/components/table/table-cell.component.ts +++ b/src/app/components/table/table-cell.component.ts @@ -6,8 +6,9 @@ import { NavigationExtras, Params, Router, RouterModule } from '@angular/router' import { Duration, Timestamp } from '@ngx-grpc/well-known-types'; import { Subject } from 'rxjs'; import { TasksStatusesGroup } from '@app/dashboard/types'; +import { TaskOptions } from '@app/tasks/types'; import { TableColumn } from '@app/types/column.type'; -import { ApplicationData, ArmonikData, DataRaw, PartitionData, RawColumnKey, SessionData, Status } from '@app/types/data'; +import { ApplicationData, ArmonikData, DataRaw, PartitionData, SessionData, Status } from '@app/types/data'; import { StatusesServiceI } from '@app/types/services'; import { CountTasksByStatusComponent } from '@components/count-tasks-by-status.component'; import { DurationPipe } from '@pipes/duration.pipe'; @@ -29,15 +30,15 @@ import { TableInspectObjectComponent } from './table-inspect-object.component'; MatCheckboxModule, ] }) -export class TableCellComponent, K extends RawColumnKey, S extends Status>{ - @Input({ required: true }) set column(entry: TableColumn) { +export class TableCellComponent{ + @Input({ required: true }) set column(entry: TableColumn) { this._column = entry; if (entry.key === 'count') { this.refreshStatuses = new Subject(); } } - @Input({ required: true }) set element(entry: T) { + @Input({ required: true }) set element(entry: ArmonikData) { this._element = entry; this._value = this.handleNestedKeys(entry); if (entry) { @@ -58,8 +59,8 @@ export class TableCellComponent, K extends RawCol private router = inject(Router); private _value: unknown; - private _element: T; - private _column: TableColumn; + private _element: ArmonikData; + private _column: TableColumn; private _link: string; private _queryParams: Params | undefined; @@ -126,11 +127,11 @@ export class TableCellComponent, K extends RawCol } } - handleNestedKeys(element: T) { + handleNestedKeys(element: ArmonikData) { if (element === undefined || element.raw === undefined) { return undefined; } - const keys = `${this.column.key}`.split('.') as unknown as K[]; + const keys = this.column.key.toString().split('.'); let resultObject: {[key: string]: object} = element.raw as unknown as {[key: string]: object}; keys.forEach(key => { resultObject = resultObject[key] as {[key: string]: object}; diff --git a/src/app/components/table/table-column-header.component.spec.ts b/src/app/components/table/table-column-header.component.spec.ts index add5bc35a..a2dc06324 100644 --- a/src/app/components/table/table-column-header.component.spec.ts +++ b/src/app/components/table/table-column-header.component.spec.ts @@ -1,13 +1,14 @@ import { TestBed } from '@angular/core/testing'; -import { SessionRawColumnKey } from '@app/sessions/types'; +import { SessionRaw } from '@app/sessions/types'; +import { TaskOptions } from '@app/tasks/types'; import { TableColumn } from '@app/types/column.type'; import { IconsService } from '@services/icons.service'; import { TableColumnHeaderComponent } from './table-column-header.component'; describe('TableColumnHeaderComponent', () => { - let component: TableColumnHeaderComponent; + let component: TableColumnHeaderComponent; - const initColumn: TableColumn = { + const initColumn: TableColumn = { type: 'count', key: 'count', name: 'Tasks by Status', diff --git a/src/app/components/table/table-column-header.component.ts b/src/app/components/table/table-column-header.component.ts index 4fd54924a..7337ae1be 100644 --- a/src/app/components/table/table-column-header.component.ts +++ b/src/app/components/table/table-column-header.component.ts @@ -2,8 +2,9 @@ import { Component, EventEmitter, Input, Output, inject } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatIconModule } from '@angular/material/icon'; +import { TaskOptions } from '@app/tasks/types'; import { ColumnType, TableColumn } from '@app/types/column.type'; -import { RawColumnKey } from '@app/types/data'; +import { DataRaw } from '@app/types/data'; import { IconsService } from '@services/icons.service'; @Component({ @@ -16,7 +17,7 @@ import { IconsService } from '@services/icons.service'; MatButtonModule, ] }) -export class TableColumnHeaderComponent { +export class TableColumnHeaderComponent { readonly iconService = inject(IconsService); @@ -24,7 +25,7 @@ export class TableColumnHeaderComponent { private _type: ColumnType; private _name: string; - @Input({ required: true }) set column(entry: TableColumn) { + @Input({ required: true }) set column(entry: TableColumn) { this._type = entry.type ?? 'raw'; this._name = entry.name; if (entry.type === 'count') { diff --git a/src/app/components/table/table.component.html b/src/app/components/table/table.component.html index c0ed222ad..825bf0d5f 100644 --- a/src/app/components/table/table.component.html +++ b/src/app/components/table/table.component.html @@ -1,44 +1,44 @@ - +
- @for (column of columns; track column.key) { - + @for (column of columns; track column.key) { + - @if (column.type !== 'actions') { - + } @else { - + } - } + } - - - - + + + + - - + +
+ + [isSelectionIndeterminate]="selection.hasValue() && !isAllSelected" (rowsSelectionChange)="toggleAllRows()" + (statusesChange)="onPersonnalizeTasksByStatus()" /> - - + + - - + +
- -
+ +
+ [pageSizeOptions]="[5, 10, 25, 100]" aria-label="Select page" i18n-aria-label>
\ No newline at end of file diff --git a/src/app/components/table/table.component.spec.ts b/src/app/components/table/table.component.spec.ts index 7fe259e6b..e0428d7f6 100644 --- a/src/app/components/table/table.component.spec.ts +++ b/src/app/components/table/table.component.spec.ts @@ -3,15 +3,17 @@ import { CdkDragDrop } from '@angular/cdk/drag-drop'; import { EventEmitter } from '@angular/core'; import { MatPaginator } from '@angular/material/paginator'; import { MatSort } from '@angular/material/sort'; -import { SessionRaw, SessionRawColumnKey, SessionRawListOptions } from '@app/sessions/types'; +import { SessionRaw } from '@app/sessions/types'; +import { TaskOptions } from '@app/tasks/types'; import { TableColumn } from '@app/types/column.type'; import { SessionData } from '@app/types/data'; +import { ListOptions } from '@app/types/options'; import { TableComponent } from './table.component'; describe('TableComponent', () => { - const component = new TableComponent(); + const component = new TableComponent(); - const columns: TableColumn[] = [ + const columns: TableColumn[] = [ { key: 'sessionId', name: 'Session ID', @@ -48,7 +50,7 @@ describe('TableComponent', () => { } ]; - const options: SessionRawListOptions = { + const options: ListOptions = { pageIndex: 1, pageSize: 10, sort: { @@ -234,44 +236,9 @@ describe('TableComponent', () => { expect(spy).toHaveBeenCalled(); }); - describe('trackByElement', () => { - it('should track a session', () => { - const sessionId = 'session'; - const session = {raw: {sessionId: sessionId}} as unknown as SessionData; - expect(component.trackByElement(0, session)).toEqual(sessionId); - }); - - it('should track a result', () => { - const resultId = 'result'; - const result = {raw: {resultId: resultId, name: 'resultName'}} as unknown as SessionData; - expect(component.trackByElement(0, result)).toEqual(resultId); - }); - - it('should track an application', () => { - const applicationName = 'name'; - const applicationVersion = 'version'; - const application = {raw: {name: applicationName, version: applicationVersion}} as unknown as SessionData; - expect(component.trackByElement(0, application)).toEqual(`${applicationName}-${applicationVersion}`); - }); - - it('should track a partition', () => { - const partitionId = 'partition'; - const partition = {raw: {id: partitionId}} as unknown as SessionData; - expect(component.trackByElement(0, partition)).toEqual(partitionId); - }); - - it('should track a task', () => { - const taskId = 'task'; - const task = {raw: {id: taskId}} as unknown as SessionData; - expect(component.trackByElement(0, task)).toEqual(taskId); - }); - - it('should track something that has no ID field by their index', () => { - const sessionId = undefined; - const index = 0; - const session = {raw: {sessionId: sessionId}} as unknown as SessionData; - expect(component.trackByElement(index, session)).toEqual(index); - }); + it('should track by index', () => { + const index = 0; + expect(component.trackBy(index, data[0])).toEqual(index); }); it('should unsubscribe on destroy', () => { diff --git a/src/app/components/table/table.component.ts b/src/app/components/table/table.component.ts index 4967c591f..4b4b9c4e2 100644 --- a/src/app/components/table/table.component.ts +++ b/src/app/components/table/table.component.ts @@ -1,16 +1,14 @@ -import { ResultRaw, SessionRaw } from '@aneoconsultingfr/armonik.api.angular'; import { SelectionModel } from '@angular/cdk/collections'; import { CdkDragDrop, DragDropModule, moveItemInArray } from '@angular/cdk/drag-drop'; import { AfterViewInit, ChangeDetectionStrategy, Component, EventEmitter, Input, OnDestroy, Output, ViewChild } from '@angular/core'; import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator'; import { MatSort, MatSortModule } from '@angular/material/sort'; import { MatTableModule } from '@angular/material/table'; -import { ApplicationRaw } from '@app/applications/types'; import { TasksStatusesGroup } from '@app/dashboard/types'; -import { PartitionRaw } from '@app/partitions/types'; -import { TaskSummary } from '@app/tasks/types'; +import { TaskOptions } from '@app/tasks/types'; import { TableColumn } from '@app/types/column.type'; -import { ArmonikData, ArmonikDataType, DataRaw, IndexListOptions, RawColumnKey, Status } from '@app/types/data'; +import { ArmonikData, ColumnKey, DataRaw, Status } from '@app/types/data'; +import { ListOptions } from '@app/types/options'; import { StatusesServiceI } from '@app/types/services'; import { ActionTable } from '@app/types/table'; import { TableContainerComponent } from '@components/table-container.component'; @@ -36,14 +34,14 @@ import { TableEmptyDataComponent } from './table-empty-data.component'; ], changeDetection: ChangeDetectionStrategy.OnPush }) -export class TableComponent implements AfterViewInit, OnDestroy { +export class TableComponent implements AfterViewInit, OnDestroy { // Required inputs - @Input({ required: true }) set columns(entries: TableColumn[]) { + @Input({ required: true }) set columns(entries: TableColumn[]) { this._columns = entries; this._columnsKeys = entries.map((entry) => entry.key); } - @Input({ required: true }) set data(entries: ArmonikData[]) { + @Input({ required: true }) set data(entries: ArmonikData[]) { this._data = entries; if (this.dataComparator) { const selection = entries.filter(entry => this.isSelected(entry.raw)).map(entry => entry.raw); @@ -55,37 +53,42 @@ export class TableComponent; @Input({ required: true }) lockColumns: boolean; // Optional inputs - @Input({ required: false }) actions: ActionTable[]; + @Input({ required: false }) actions: ActionTable[]; @Input({ required: false }) statusesService: StatusesServiceI; @Input({ required: false }) statusesGroups: TasksStatusesGroup[]; - @Input({ required: false }) dataComparator: ((a: R, b: R) => boolean) | undefined; + @Input({ required: false }) dataComparator: ((a: T, b: T) => boolean) | undefined; - @Output() columnDrop = new EventEmitter(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + @Input({ required: false }) trackBy(index: number, item: ArmonikData): number | string { + return index; + } + + @Output() columnDrop = new EventEmitter[]>(); @Output() optionsChange = new EventEmitter(); - @Output() selectionChange = new EventEmitter(); + @Output() selectionChange = new EventEmitter(); @Output() personnalizeTasksByStatus = new EventEmitter(); @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; - private _data: ArmonikData[]; - private _columns: TableColumn[]; - private _columnsKeys: K[]; + private _data: ArmonikData[]; + private _columns: TableColumn[]; + private _columnsKeys: ColumnKey[]; private _isAllSelected: boolean = false; - get data(): ArmonikData[] { + get data(): ArmonikData[] { return this._data; } - get columns(): TableColumn[] { + get columns(): TableColumn[] { return this._columns; } - get columnsKeys(): K[] { + get columnsKeys(): ColumnKey[] { return this._columnsKeys; } @@ -93,7 +96,7 @@ export class TableComponent(true, []); + selection = new SelectionModel(true, []); ngAfterViewInit(): void { this.sort.sortChange.subscribe(() => { @@ -129,7 +132,7 @@ export class TableComponent { if (this.dataComparator) { return this.dataComparator(row, selectedRow); @@ -150,7 +153,7 @@ export class TableComponent) { - if ((item.raw as SessionRaw).sessionId) { - return (item.raw as SessionRaw).sessionId; - } else if ((item.raw as ResultRaw).resultId) { - return (item.raw as ResultRaw).resultId; - } else if ((item.raw as ApplicationRaw).name) { - return `${(item.raw as ApplicationRaw).name}-${(item.raw as ApplicationRaw).version}`; - } else if ((item.raw as TaskSummary | PartitionRaw).id) { - return (item.raw as TaskSummary | PartitionRaw).id; - } else { - return index; - } - } } \ No newline at end of file diff --git a/src/app/dashboard/components/lines/applications-line.component.spec.ts b/src/app/dashboard/components/lines/applications-line.component.spec.ts index f6bede06e..26df92ea5 100644 --- a/src/app/dashboard/components/lines/applications-line.component.spec.ts +++ b/src/app/dashboard/components/lines/applications-line.component.spec.ts @@ -1,24 +1,27 @@ +import { ApplicationRawEnumField } from '@aneoconsultingfr/armonik.api.angular'; import { TestBed } from '@angular/core/testing'; import { MatDialog } from '@angular/material/dialog'; import { of } from 'rxjs'; import { ApplicationsIndexService } from '@app/applications/services/applications-index.service'; -import { ApplicationRawColumnKey, ApplicationRawFieldKey, ApplicationRawListOptions } from '@app/applications/types'; +import { ApplicationRaw, ApplicationRawColumnKey, ApplicationRawFieldKey, ApplicationRawListOptions } from '@app/applications/types'; import { TableColumn } from '@app/types/column.type'; +import { ColumnKey } from '@app/types/data'; +import { FiltersOr } from '@app/types/filters'; import { AutoRefreshService } from '@services/auto-refresh.service'; import { DefaultConfigService } from '@services/default-config.service'; import { IconsService } from '@services/icons.service'; import { NotificationService } from '@services/notification.service'; import { ApplicationsLineComponent } from './applications-line.component'; -import { Line } from '../../types'; +import { TableLine } from '../../types'; describe('ApplicationsLineComponent', () => { let component: ApplicationsLineComponent; const defaultConfigService = new DefaultConfigService(); - const defaultColumns: ApplicationRawColumnKey[] = ['name', 'count']; + const defaultColumns: ColumnKey[] = ['name', 'count']; - const displayedColumns: TableColumn[] = [ + const displayedColumns: TableColumn[] = [ { name: 'Name', key: 'name', @@ -62,7 +65,7 @@ describe('ApplicationsLineComponent', () => { }, }; - const line: Line = { + const line: TableLine = { name: 'Tasks', type: 'Applications', displayedColumns: displayedColumns.map(c => c.key), @@ -201,7 +204,7 @@ describe('ApplicationsLineComponent', () => { }); describe('onFilterChange', () => { - const newFilters = [[{for: 'root', field: 0, operator: 1, value: 2}]]; + const newFilters: FiltersOr = [[{for: 'root', field: 0, operator: 1, value: 2}]]; it('should update applied filters', () => { component.onFiltersChange(newFilters); diff --git a/src/app/dashboard/components/lines/applications-line.component.ts b/src/app/dashboard/components/lines/applications-line.component.ts index 268f577d3..15b87535c 100644 --- a/src/app/dashboard/components/lines/applications-line.component.ts +++ b/src/app/dashboard/components/lines/applications-line.component.ts @@ -1,3 +1,4 @@ +import { ApplicationRawEnumField } from '@aneoconsultingfr/armonik.api.angular'; import { AfterViewInit, Component, OnDestroy, OnInit, inject } from '@angular/core'; import { MatIconModule } from '@angular/material/icon'; import { MatMenuModule } from '@angular/material/menu'; @@ -7,7 +8,7 @@ import { ApplicationsTableComponent } from '@app/applications/components/table.c import { ApplicationsFiltersService } from '@app/applications/services/applications-filters.service'; import { ApplicationsGrpcService } from '@app/applications/services/applications-grpc.service'; import { ApplicationsIndexService } from '@app/applications/services/applications-index.service'; -import { ApplicationRawColumnKey, ApplicationRawFilters, ApplicationRawListOptions } from '@app/applications/types'; +import { ApplicationRaw } from '@app/applications/types'; import { DATA_FILTERS_SERVICE } from '@app/tokens/filters.token'; import { DashboardLineTableComponent } from '@app/types/components/dashboard-line-table'; import { FiltersToolbarComponent } from '@components/filters/filters-toolbar.component'; @@ -44,7 +45,7 @@ import { ShareUrlService } from '@services/share-url.service'; TableDashboardActionsToolbarComponent, ] }) -export class ApplicationsLineComponent extends DashboardLineTableComponent implements OnInit, AfterViewInit,OnDestroy { +export class ApplicationsLineComponent extends DashboardLineTableComponent implements OnInit, AfterViewInit,OnDestroy { readonly indexService = inject(ApplicationsIndexService); readonly defaultConfig = this.defaultConfigService.defaultApplications; diff --git a/src/app/dashboard/components/lines/partitions-line.component.spec.ts b/src/app/dashboard/components/lines/partitions-line.component.spec.ts index efeb84b06..e488434ef 100644 --- a/src/app/dashboard/components/lines/partitions-line.component.spec.ts +++ b/src/app/dashboard/components/lines/partitions-line.component.spec.ts @@ -1,24 +1,27 @@ +import { PartitionRawEnumField } from '@aneoconsultingfr/armonik.api.angular'; import { TestBed } from '@angular/core/testing'; import { MatDialog } from '@angular/material/dialog'; import { of } from 'rxjs'; import { PartitionsIndexService } from '@app/partitions/services/partitions-index.service'; -import { PartitionRawColumnKey, PartitionRawFieldKey, PartitionRawListOptions } from '@app/partitions/types'; +import { PartitionRaw, PartitionRawColumnKey, PartitionRawFieldKey, PartitionRawListOptions } from '@app/partitions/types'; import { TableColumn } from '@app/types/column.type'; +import { ColumnKey } from '@app/types/data'; +import { FiltersOr } from '@app/types/filters'; import { AutoRefreshService } from '@services/auto-refresh.service'; import { DefaultConfigService } from '@services/default-config.service'; import { IconsService } from '@services/icons.service'; import { NotificationService } from '@services/notification.service'; import { PartitionsLineComponent } from './partitions-line.component'; -import { Line } from '../../types'; +import { TableLine } from '../../types'; describe('PartitionsLineComponent', () => { let component: PartitionsLineComponent; const defaultConfigService = new DefaultConfigService(); - const defaultColumns: PartitionRawColumnKey[] = ['id', 'count']; + const defaultColumns: ColumnKey[] = ['id', 'count']; - const displayedColumns: TableColumn[] = [ + const displayedColumns: TableColumn[] = [ { key: 'id', name: 'ID', @@ -55,7 +58,7 @@ describe('PartitionsLineComponent', () => { }, }; - const line: Line = { + const line: TableLine = { name: 'Tasks', type: 'Partitions', displayedColumns: displayedColumns.map(c => c.key), @@ -193,7 +196,7 @@ describe('PartitionsLineComponent', () => { }); describe('onFilterChange', () => { - const newFilters = [[{for: 'root', field: 0, operator: 1, value: 2}]]; + const newFilters: FiltersOr = [[{for: 'root', field: 0, operator: 1, value: 2}]]; it('should update applied filters', () => { component.onFiltersChange(newFilters); diff --git a/src/app/dashboard/components/lines/partitions-line.component.ts b/src/app/dashboard/components/lines/partitions-line.component.ts index 1b28dafb5..b0d97e9c0 100644 --- a/src/app/dashboard/components/lines/partitions-line.component.ts +++ b/src/app/dashboard/components/lines/partitions-line.component.ts @@ -1,3 +1,4 @@ +import { PartitionRawEnumField } from '@aneoconsultingfr/armonik.api.angular'; import { AfterViewInit, Component, OnDestroy, OnInit, inject } from '@angular/core'; import { MatIconModule } from '@angular/material/icon'; import { MatMenuModule } from '@angular/material/menu'; @@ -6,7 +7,7 @@ import { MatToolbarModule } from '@angular/material/toolbar'; import { PartitionsTableComponent } from '@app/partitions/components/table.component'; import { PartitionsFiltersService } from '@app/partitions/services/partitions-filters.service'; import { PartitionsIndexService } from '@app/partitions/services/partitions-index.service'; -import { PartitionRawColumnKey, PartitionRawFilters, PartitionRawListOptions } from '@app/partitions/types'; +import { PartitionRaw } from '@app/partitions/types'; import { DATA_FILTERS_SERVICE } from '@app/tokens/filters.token'; import { DashboardLineTableComponent } from '@app/types/components/dashboard-line-table'; import { FiltersToolbarComponent } from '@components/filters/filters-toolbar.component'; @@ -36,7 +37,7 @@ import { NotificationService } from '@services/notification.service'; MatMenuModule, ] }) -export class PartitionsLineComponent extends DashboardLineTableComponent implements OnInit, AfterViewInit, OnDestroy { +export class PartitionsLineComponent extends DashboardLineTableComponent implements OnInit, AfterViewInit, OnDestroy { readonly indexService = inject(PartitionsIndexService); readonly defaultConfig = this.defaultConfigService.defaultPartitions; diff --git a/src/app/dashboard/components/lines/results-line.component.spec.ts b/src/app/dashboard/components/lines/results-line.component.spec.ts index 502fa909c..475fe9e59 100644 --- a/src/app/dashboard/components/lines/results-line.component.spec.ts +++ b/src/app/dashboard/components/lines/results-line.component.spec.ts @@ -1,15 +1,17 @@ +import { ResultRawEnumField } from '@aneoconsultingfr/armonik.api.angular'; import { TestBed } from '@angular/core/testing'; import { MatDialog } from '@angular/material/dialog'; import { of } from 'rxjs'; import { ResultsIndexService } from '@app/results/services/results-index.service'; -import { ResultRawColumnKey, ResultRawFieldKey, ResultRawListOptions } from '@app/results/types'; +import { ResultRaw, ResultRawColumnKey, ResultRawFieldKey, ResultRawListOptions } from '@app/results/types'; import { TableColumn } from '@app/types/column.type'; +import { FiltersOr } from '@app/types/filters'; import { AutoRefreshService } from '@services/auto-refresh.service'; import { DefaultConfigService } from '@services/default-config.service'; import { IconsService } from '@services/icons.service'; import { NotificationService } from '@services/notification.service'; import { ResultsLineComponent } from './results-line.component'; -import { Line } from '../../types'; +import { TableLine } from '../../types'; describe('ResultsLineComponent', () => { let component: ResultsLineComponent; @@ -18,7 +20,7 @@ describe('ResultsLineComponent', () => { const defaultColumns: ResultRawColumnKey[] = ['name', 'resultId']; - const displayedColumns: TableColumn[] = [ + const displayedColumns: TableColumn[] = [ { key: 'resultId', name: 'ID', @@ -55,7 +57,7 @@ describe('ResultsLineComponent', () => { }, }; - const line: Line = { + const line: TableLine = { name: 'Tasks', type: 'Results', displayedColumns: displayedColumns.map(c => c.key), @@ -193,7 +195,7 @@ describe('ResultsLineComponent', () => { }); describe('onFilterChange', () => { - const newFilters = [[{for: 'root', field: 0, operator: 1, value: 2}]]; + const newFilters: FiltersOr = [[{for: 'root', field: ResultRawEnumField.RESULT_RAW_ENUM_FIELD_CREATED_AT, operator: 1, value: 2}]]; it('should update applied filters', () => { component.onFiltersChange(newFilters); diff --git a/src/app/dashboard/components/lines/results-line.component.ts b/src/app/dashboard/components/lines/results-line.component.ts index a9afc0a2a..98a1794db 100644 --- a/src/app/dashboard/components/lines/results-line.component.ts +++ b/src/app/dashboard/components/lines/results-line.component.ts @@ -1,3 +1,4 @@ +import { ResultRawEnumField } from '@aneoconsultingfr/armonik.api.angular'; import { AfterViewInit, Component, OnDestroy, OnInit, inject } from '@angular/core'; import { MatIconModule } from '@angular/material/icon'; import { MatMenuModule } from '@angular/material/menu'; @@ -6,7 +7,7 @@ import { MatToolbarModule } from '@angular/material/toolbar'; import { ResultsTableComponent } from '@app/results/components/table.component'; import { ResultsFiltersService } from '@app/results/services/results-filters.service'; import { ResultsIndexService } from '@app/results/services/results-index.service'; -import { ResultRawColumnKey, ResultRawFilters, ResultRawListOptions } from '@app/results/types'; +import { ResultRaw } from '@app/results/types'; import { DATA_FILTERS_SERVICE } from '@app/tokens/filters.token'; import { DashboardLineTableComponent } from '@app/types/components/dashboard-line-table'; import { FiltersToolbarComponent } from '@components/filters/filters-toolbar.component'; @@ -34,7 +35,7 @@ import { TableDashboardActionsToolbarComponent } from '@components/table-dashboa MatMenuModule, ] }) -export class ResultsLineComponent extends DashboardLineTableComponent implements OnInit, OnDestroy, AfterViewInit { +export class ResultsLineComponent extends DashboardLineTableComponent implements OnInit, OnDestroy, AfterViewInit { readonly indexService = inject(ResultsIndexService); readonly defaultConfig = this.defaultConfigService.defaultResults; diff --git a/src/app/dashboard/components/lines/sessions-line.component.spec.ts b/src/app/dashboard/components/lines/sessions-line.component.spec.ts index d72e60722..7f008b2f8 100644 --- a/src/app/dashboard/components/lines/sessions-line.component.spec.ts +++ b/src/app/dashboard/components/lines/sessions-line.component.spec.ts @@ -1,26 +1,30 @@ +import { SessionRawEnumField, TaskOptionEnumField } from '@aneoconsultingfr/armonik.api.angular'; import { TestBed } from '@angular/core/testing'; import { MatDialog } from '@angular/material/dialog'; import { of } from 'rxjs'; import { SessionsIndexService } from '@app/sessions/services/sessions-index.service'; -import { SessionRawColumnKey, SessionRawFieldKey, SessionRawListOptions } from '@app/sessions/types'; +import { SessionRaw } from '@app/sessions/types'; +import { TaskOptions } from '@app/tasks/types'; import { TableColumn } from '@app/types/column.type'; -import { CustomColumn } from '@app/types/data'; +import { ColumnKey, CustomColumn } from '@app/types/data'; +import { FiltersOr } from '@app/types/filters'; +import { ListOptions } from '@app/types/options'; import { AutoRefreshService } from '@services/auto-refresh.service'; import { DefaultConfigService } from '@services/default-config.service'; import { IconsService } from '@services/icons.service'; import { NotificationService } from '@services/notification.service'; import { SessionsLineComponent } from './sessions-line.component'; -import { Line } from '../../types'; +import { TableLine } from '../../types'; describe('SessionsLineComponent', () => { let component: SessionsLineComponent; const defaultConfigService = new DefaultConfigService(); - const defaultColumns: SessionRawColumnKey[] = ['sessionId', 'count']; + const defaultColumns: ColumnKey[] = ['sessionId', 'count']; const customColumns: CustomColumn[] = ['options.options.FastCompute']; - const displayedColumns: TableColumn[] = [ + const displayedColumns: TableColumn[] = [ { key: 'sessionId', name: 'ID', @@ -48,16 +52,16 @@ describe('SessionsLineComponent', () => { } ]; - const options: SessionRawListOptions = { + const options: ListOptions = { pageIndex: 0, pageSize: 5, sort: { - active: 'name' as SessionRawFieldKey, + active: 'sessionId', direction: 'desc', }, }; - const line: Line = { + const line: TableLine = { name: 'Tasks', type: 'Sessions', displayedColumns: displayedColumns.map(c => c.key), @@ -195,7 +199,7 @@ describe('SessionsLineComponent', () => { }); describe('onFilterChange', () => { - const newFilters = [[{for: 'root', field: 0, operator: 1, value: 2}]]; + const newFilters: FiltersOr = [[{for: 'root', field: 0, operator: 1, value: 2}]]; it('should update applied filters', () => { component.onFiltersChange(newFilters); @@ -221,11 +225,11 @@ describe('SessionsLineComponent', () => { }); describe('OnColumnsChange', () => { - const newColumns: SessionRawColumnKey[] = ['sessionId', 'count', 'duration']; + const newColumns: ColumnKey[] = ['sessionId', 'count', 'duration']; beforeEach(() => { - component.displayedColumnsKeys = ['count', 'id'] as SessionRawColumnKey[]; - component.line.displayedColumns = ['sessionId', 'options.maxDuration'] as SessionRawColumnKey[]; + component.displayedColumnsKeys = ['count', 'id'] as ColumnKey[]; + component.line.displayedColumns = ['sessionId', 'options.maxDuration'] as ColumnKey[]; }); it('should change displayedColumns', () => { @@ -247,8 +251,8 @@ describe('SessionsLineComponent', () => { describe('onColumnsReset', () => { beforeEach(() => { - component.displayedColumnsKeys = ['sessionId', 'createdAt'] as SessionRawColumnKey[]; - component.line.displayedColumns = ['closedAt', 'options'] as SessionRawColumnKey[]; + component.displayedColumnsKeys = ['sessionId', 'createdAt'] as ColumnKey[]; + component.line.displayedColumns = ['closedAt', 'options'] as ColumnKey[]; }); it('should reset to default columns', () => { diff --git a/src/app/dashboard/components/lines/sessions-line.component.ts b/src/app/dashboard/components/lines/sessions-line.component.ts index 099e5af9a..36339cf05 100644 --- a/src/app/dashboard/components/lines/sessions-line.component.ts +++ b/src/app/dashboard/components/lines/sessions-line.component.ts @@ -1,3 +1,4 @@ +import { SessionRawEnumField, TaskOptionEnumField } from '@aneoconsultingfr/armonik.api.angular'; import { AfterViewInit, Component, OnDestroy, OnInit, inject } from '@angular/core'; import { MatIconModule } from '@angular/material/icon'; import { MatMenuModule } from '@angular/material/menu'; @@ -7,9 +8,10 @@ import { SessionsTableComponent } from '@app/sessions/components/table.component import { SessionsFiltersService } from '@app/sessions/services/sessions-filters.service'; import { SessionsIndexService } from '@app/sessions/services/sessions-index.service'; import { SessionsStatusesService } from '@app/sessions/services/sessions-statuses.service'; -import { SessionRawColumnKey, SessionRawFilters, SessionRawListOptions } from '@app/sessions/types'; +import { SessionRaw } from '@app/sessions/types'; import { TasksFiltersService } from '@app/tasks/services/tasks-filters.service'; import { TasksStatusesService } from '@app/tasks/services/tasks-statuses.service'; +import { TaskOptions } from '@app/tasks/types'; import { DATA_FILTERS_SERVICE } from '@app/tokens/filters.token'; import { DashboardLineCustomColumnsComponent } from '@app/types/components/dashboard-line-table'; import { FiltersToolbarComponent } from '@components/filters/filters-toolbar.component'; @@ -46,7 +48,7 @@ import { NotificationService } from '@services/notification.service'; MatMenuModule, ], }) -export class SessionsLineComponent extends DashboardLineCustomColumnsComponent implements OnInit, AfterViewInit, OnDestroy { +export class SessionsLineComponent extends DashboardLineCustomColumnsComponent implements OnInit, AfterViewInit, OnDestroy { readonly indexService = inject(SessionsIndexService); readonly defaultConfig = this.defaultConfigService.defaultSessions; diff --git a/src/app/dashboard/components/lines/task-by-status-line.component.html b/src/app/dashboard/components/lines/task-by-status-line.component.html index 34f22a9af..c0d843adb 100644 --- a/src/app/dashboard/components/lines/task-by-status-line.component.html +++ b/src/app/dashboard/components/lines/task-by-status-line.component.html @@ -3,7 +3,7 @@ - @if (loadTasksStatus) { + @if (loading) { } @@ -59,7 +59,7 @@ @for (group of line.taskStatusesGroups; track group.name) { diff --git a/src/app/dashboard/components/lines/task-by-status-line.component.spec.ts b/src/app/dashboard/components/lines/task-by-status-line.component.spec.ts index 4aac4b3f2..d34cd1674 100644 --- a/src/app/dashboard/components/lines/task-by-status-line.component.spec.ts +++ b/src/app/dashboard/components/lines/task-by-status-line.component.spec.ts @@ -63,10 +63,8 @@ describe('TaskByStatusLineComponent', () => { type: 'Tasks', taskStatusesGroups: [] }; - component.ngOnInit(); component.ngAfterViewInit(); component.total = 0; - component.data = []; }); it('should create', () => { @@ -76,22 +74,17 @@ describe('TaskByStatusLineComponent', () => { test('after view init subscription', () => { component.refresh.next(); - expect(component.data).toEqual([ + expect(component.data()).toEqual([ {status:TaskStatus.TASK_STATUS_CANCELLED, count: 3}, {status: TaskStatus.TASK_STATUS_COMPLETED, count: 10}, {status: TaskStatus.TASK_STATUS_PROCESSING, count: 145} ]); expect(component.total).toEqual(158); - expect(component.loadTasksStatus).toBeFalsy(); + expect(component.loading).toBeFalsy(); }); it('should get required icons', () => { expect(component.getIcon('more')).toEqual('more_vert'); - expect(component.getIcon('view')).toEqual('visibility'); - expect(component.getIcon('view-off')).toEqual('visibility_off'); - expect(component.getIcon('tune')).toEqual('tune'); - expect(component.getIcon('edit')).toEqual('edit'); - expect(component.getIcon('delete')).toEqual('delete'); }); it('should say that the auto refresh is enabled', () => { diff --git a/src/app/dashboard/components/lines/task-by-status-line.component.ts b/src/app/dashboard/components/lines/task-by-status-line.component.ts index 4a8e18c26..103938f6c 100644 --- a/src/app/dashboard/components/lines/task-by-status-line.component.ts +++ b/src/app/dashboard/components/lines/task-by-status-line.component.ts @@ -1,4 +1,4 @@ -import { AfterViewInit, Component, EventEmitter, Input, OnDestroy, OnInit, Output, inject } from '@angular/core'; +import { AfterViewInit, Component, EventEmitter, Input, OnDestroy, Output, inject, signal } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { MatIconModule } from '@angular/material/icon'; @@ -21,6 +21,7 @@ import { PageSectionComponent } from '@components/page-section.component'; import { RefreshButtonComponent } from '@components/refresh-button.component'; import { SpinnerComponent } from '@components/spinner.component'; import { ManageGroupsDialogComponent } from '@components/statuses/manage-groups-dialog.component'; +import { TableDashboardActionsToolbarComponent } from '@components/table-dashboard-actions-toolbar.component'; import { AutoRefreshService } from '@services/auto-refresh.service'; import { GrpcSortFieldService } from '@services/grpc-sort-field.service'; import { IconsService } from '@services/icons.service'; @@ -28,7 +29,7 @@ import { QueryParamsService } from '@services/query-params.service'; import { ShareUrlService } from '@services/share-url.service'; import { StorageService } from '@services/storage.service'; import { UtilsService } from '@services/utils.service'; -import { Line, ManageGroupsDialogData, ManageGroupsDialogResult } from '../../types'; +import { CountLine, ManageGroupsDialogData, ManageGroupsDialogResult } from '../../types'; import { EditNameLineDialogComponent } from '../edit-name-line-dialog.component'; import { StatusesGroupCardComponent } from '../statuses-group-card.component'; @@ -80,42 +81,39 @@ app-actions-toolbar { MatMenuModule, MatButtonModule, StatusesGroupCardComponent, + TableDashboardActionsToolbarComponent, ] }) -export class TaskByStatusLineComponent implements OnInit, AfterViewInit,OnDestroy { - readonly #dialog = inject(MatDialog); - readonly #autoRefreshService = inject(AutoRefreshService); - readonly #iconsService = inject(IconsService); - readonly #taskGrpcService = inject(TasksGrpcService); +export class TaskByStatusLineComponent implements AfterViewInit,OnDestroy { + readonly dialog = inject(MatDialog); + readonly autoRefreshService = inject(AutoRefreshService); + readonly iconsService = inject(IconsService); + readonly taskGrpcService = inject(TasksGrpcService); - @Input({ required: true }) line: Line; + @Input({ required: true }) line: CountLine; @Output() lineChange: EventEmitter = new EventEmitter(); - @Output() lineDelete: EventEmitter = new EventEmitter(); + @Output() lineDelete: EventEmitter = new EventEmitter(); total: number; - loadTasksStatus = false; - data: StatusCount[] = []; + loading = false; + data = signal([]); refresh: Subject = new Subject(); stopInterval: Subject = new Subject(); interval: Subject = new Subject(); subscriptions: Subscription = new Subscription(); - interval$: Observable = this.#autoRefreshService.createInterval(this.interval, this.stopInterval); - - ngOnInit(): void { - this.loadTasksStatus = true; - } + interval$: Observable = this.autoRefreshService.createInterval(this.interval, this.stopInterval); ngAfterViewInit() { const mergeSubscription = merge(this.refresh, this.interval$).pipe( startWith(0), - tap(() => (this.loadTasksStatus = true)), - switchMap(() => this.#taskGrpcService.countByStatus$(this.line.filters as TaskSummaryFilters)), + tap(() => (this.loading = true)), + switchMap(() => this.taskGrpcService.countByStatus$(this.line.filters as TaskSummaryFilters)), ).subscribe((data) => { if (data.status) { - this.data = data.status; + this.data.set(data.status); this.total = data.status.reduce((acc, curr) => acc + curr.count, 0); - this.loadTasksStatus = false; + this.loading = false; } }); this.subscriptions.add(mergeSubscription); @@ -126,11 +124,11 @@ export class TaskByStatusLineComponent implements OnInit, AfterViewInit,OnDestro } getIcon(name: string): string { - return this.#iconsService.getIcon(name); + return this.iconsService.getIcon(name); } autoRefreshTooltip(): string { - return this.#autoRefreshService.autoRefreshTooltip(this.line.interval); + return this.autoRefreshService.autoRefreshTooltip(this.line.interval); } onRefresh() { @@ -139,7 +137,6 @@ export class TaskByStatusLineComponent implements OnInit, AfterViewInit,OnDestro onIntervalValueChange(value: number) { this.line.interval = value; - if(value === 0) { this.stopInterval.next(); } else { @@ -157,7 +154,7 @@ export class TaskByStatusLineComponent implements OnInit, AfterViewInit,OnDestro } onEditNameLine(value: string) { - const dialogRef: MatDialogRef = this.#dialog.open(EditNameLineDialogComponent, { + const dialogRef: MatDialogRef = this.dialog.open(EditNameLineDialogComponent, { data: { name: value } @@ -171,12 +168,12 @@ export class TaskByStatusLineComponent implements OnInit, AfterViewInit,OnDestro }); } - onDeleteLine(value: Line): void { + onDeleteLine(value: CountLine): void { this.lineDelete.emit(value); } onManageGroupsDialog() { - const dialogRef: MatDialogRef = this.#dialog.open(ManageGroupsDialogComponent, { + const dialogRef: MatDialogRef = this.dialog.open(ManageGroupsDialogComponent, { data: { groups: this.line.taskStatusesGroups ?? [], } diff --git a/src/app/dashboard/components/lines/tasks-line.component.spec.ts b/src/app/dashboard/components/lines/tasks-line.component.spec.ts index 7862611de..c644a614f 100644 --- a/src/app/dashboard/components/lines/tasks-line.component.spec.ts +++ b/src/app/dashboard/components/lines/tasks-line.component.spec.ts @@ -1,27 +1,30 @@ +import { TaskOptionEnumField, TaskSummaryEnumField } from '@aneoconsultingfr/armonik.api.angular'; import { TestBed } from '@angular/core/testing'; import { MatDialog } from '@angular/material/dialog'; import { Observable, of, throwError } from 'rxjs'; import { TasksGrpcService } from '@app/tasks/services/tasks-grpc.service'; import { TasksIndexService } from '@app/tasks/services/tasks-index.service'; -import { TaskSummaryColumnKey, TaskSummaryFieldKey, TaskSummaryListOptions } from '@app/tasks/types'; +import { TaskOptions, TaskSummary } from '@app/tasks/types'; import { TableColumn } from '@app/types/column.type'; -import { CustomColumn } from '@app/types/data'; +import { ColumnKey, CustomColumn } from '@app/types/data'; +import { FiltersOr } from '@app/types/filters'; +import { ListOptions } from '@app/types/options'; import { AutoRefreshService } from '@services/auto-refresh.service'; import { DefaultConfigService } from '@services/default-config.service'; import { IconsService } from '@services/icons.service'; import { NotificationService } from '@services/notification.service'; import { TasksLineComponent } from './tasks-line.component'; -import { Line } from '../../types'; +import { TableLine } from '../../types'; describe('TasksLineComponent', () => { let component: TasksLineComponent; const defaultConfigService = new DefaultConfigService(); - const defaultColumns: TaskSummaryColumnKey[] = ['id', 'options.applicationName', 'actions', 'status']; + const defaultColumns: ColumnKey[] = ['id', 'options.applicationName', 'actions', 'status']; const customColumns: CustomColumn[] = ['options.options.FastCompute']; - const displayedColumns: TableColumn[] = [ + const displayedColumns: TableColumn[] = [ { key: 'id', name: 'ID', @@ -49,16 +52,16 @@ describe('TasksLineComponent', () => { } ]; - const options: TaskSummaryListOptions = { + const options: ListOptions = { pageIndex: 0, pageSize: 5, sort: { - active: 'name' as TaskSummaryFieldKey, + active: 'id', direction: 'desc', }, }; - const line: Line = { + const line: TableLine = { name: 'Tasks', type: 'Tasks', displayedColumns: [...displayedColumns.map(c => c.key), ...customColumns], @@ -215,7 +218,7 @@ describe('TasksLineComponent', () => { }); describe('onFilterChange', () => { - const newFilters = [[{for: 'root', field: 0, operator: 1, value: 2}]]; + const newFilters: FiltersOr = [[{for: 'root', field: 0, operator: 1, value: 2}]]; it('should update applied filters', () => { component.onFiltersChange(newFilters); @@ -241,11 +244,11 @@ describe('TasksLineComponent', () => { }); describe('OnColumnsChange', () => { - const newColumns: TaskSummaryColumnKey[] = ['id', 'acquiredAt', 'creationToEndDuration']; + const newColumns: ColumnKey[] = ['id', 'acquiredAt', 'creationToEndDuration']; beforeEach(() => { - component.displayedColumnsKeys = ['count', 'id'] as TaskSummaryColumnKey[]; - component.line.displayedColumns = ['id', 'podReserved'] as TaskSummaryColumnKey[]; + component.displayedColumnsKeys = ['count', 'id'] as ColumnKey[]; + component.line.displayedColumns = ['id', 'podReserved'] as ColumnKey[]; }); it('should change displayedColumns', () => { @@ -267,8 +270,8 @@ describe('TasksLineComponent', () => { describe('onColumnsReset', () => { beforeEach(() => { - component.displayedColumnsKeys = ['preemptionPercentage', 'id'] as TaskSummaryColumnKey[]; - component.line.displayedColumns = ['count', 'actions'] as TaskSummaryColumnKey[]; + component.displayedColumnsKeys = ['preemptionPercentage', 'id'] as ColumnKey[]; + component.line.displayedColumns = ['count', 'actions'] as ColumnKey[]; }); it('should reset to default columns', () => { diff --git a/src/app/dashboard/components/lines/tasks-line.component.ts b/src/app/dashboard/components/lines/tasks-line.component.ts index 710916dd4..4f16440ee 100644 --- a/src/app/dashboard/components/lines/tasks-line.component.ts +++ b/src/app/dashboard/components/lines/tasks-line.component.ts @@ -1,3 +1,4 @@ +import { TaskOptionEnumField, TaskSummaryEnumField } from '@aneoconsultingfr/armonik.api.angular'; import { AfterViewInit, Component, OnDestroy, OnInit, inject } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; @@ -9,7 +10,7 @@ import { TasksTableComponent } from '@app/tasks/components/table.component'; import { TasksFiltersService } from '@app/tasks/services/tasks-filters.service'; import { TasksGrpcService } from '@app/tasks/services/tasks-grpc.service'; import { TasksIndexService } from '@app/tasks/services/tasks-index.service'; -import { TaskSummaryColumnKey, TaskSummaryFilters, TaskSummaryListOptions } from '@app/tasks/types'; +import { TaskOptions, TaskSummary } from '@app/tasks/types'; import { DATA_FILTERS_SERVICE } from '@app/tokens/filters.token'; import { DashboardLineCustomColumnsComponent } from '@app/types/components/dashboard-line-table'; import { ManageViewInLogsDialogData, ManageViewInLogsDialogResult } from '@app/types/dialog'; @@ -44,7 +45,7 @@ import { NotificationService } from '@services/notification.service'; MatButtonModule, ] }) -export class TasksLineComponent extends DashboardLineCustomColumnsComponent implements OnInit, AfterViewInit, OnDestroy { +export class TasksLineComponent extends DashboardLineCustomColumnsComponent implements OnInit, AfterViewInit, OnDestroy { readonly indexService = inject(TasksIndexService); readonly tasksGrpcService = inject(TasksGrpcService); diff --git a/src/app/dashboard/components/reorganize-lines-dialog.component.spec.ts b/src/app/dashboard/components/reorganize-lines-dialog.component.spec.ts index b7f0c1081..be64d239e 100644 --- a/src/app/dashboard/components/reorganize-lines-dialog.component.spec.ts +++ b/src/app/dashboard/components/reorganize-lines-dialog.component.spec.ts @@ -5,7 +5,7 @@ import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dial import { Observable, of } from 'rxjs'; import { IconsService } from '@services/icons.service'; import { ReorganizeLinesDialogComponent } from './reorganize-lines-dialog.component'; -import { Line } from '../types'; +import { CountLine, Line } from '../types'; describe('ReorganizeLinesDialogComponent', () => { let component: ReorganizeLinesDialogComponent; @@ -22,7 +22,7 @@ describe('ReorganizeLinesDialogComponent', () => { { name: 'Running', color: 'yellow', statuses: [TaskStatus.TASK_STATUS_CREATING, TaskStatus.TASK_STATUS_PROCESSING]}, { name: 'Error', color: 'red', statuses: [TaskStatus.TASK_STATUS_CANCELLED, TaskStatus.TASK_STATUS_TIMEOUT]} ], - }, + } as CountLine, { name: 'line2', type: 'Tasks', @@ -34,7 +34,7 @@ describe('ReorganizeLinesDialogComponent', () => { { name: 'Running', color: 'yellow', statuses: [TaskStatus.TASK_STATUS_CREATING, TaskStatus.TASK_STATUS_PROCESSING]}, { name: 'Unspecified', color: 'grey', statuses: [TaskStatus.TASK_STATUS_UNSPECIFIED, TaskStatus.TASK_STATUS_RETRIED]} ], - } + } as CountLine ]; let dialogRef$: Observable; diff --git a/src/app/dashboard/components/statuses-group-card.component.ts b/src/app/dashboard/components/statuses-group-card.component.ts index 26457d7ed..07b0dea23 100644 --- a/src/app/dashboard/components/statuses-group-card.component.ts +++ b/src/app/dashboard/components/statuses-group-card.component.ts @@ -1,5 +1,5 @@ import { FilterStringOperator, TaskOptionEnumField, TaskStatus, TaskSummaryEnumField } from '@aneoconsultingfr/armonik.api.angular'; -import { Component, Input, inject } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, inject } from '@angular/core'; import { MatCardModule } from '@angular/material/card'; import { RouterModule } from '@angular/router'; import { TasksStatusesService } from '@app/tasks/services/tasks-statuses.service'; @@ -47,7 +47,8 @@ ul li a { imports: [ RouterModule, MatCardModule, - ] + ], + changeDetection: ChangeDetectionStrategy.OnPush }) export class StatusesGroupCardComponent { @Input({ required: true }) group: TasksStatusesGroup; diff --git a/src/app/dashboard/index.component.html b/src/app/dashboard/index.component.html index 828ce567c..727159b38 100644 --- a/src/app/dashboard/index.component.html +++ b/src/app/dashboard/index.component.html @@ -42,13 +42,13 @@ @switch (line.type) { @case ('CountStatus') { - + } @case ('Applications') { - + } @case ('Results') { - + } @case ('Partitions') { diff --git a/src/app/dashboard/index.component.spec.ts b/src/app/dashboard/index.component.spec.ts index f19984009..ccc6a5b35 100644 --- a/src/app/dashboard/index.component.spec.ts +++ b/src/app/dashboard/index.component.spec.ts @@ -7,7 +7,7 @@ import { IconsService } from '@services/icons.service'; import { ShareUrlService } from '@services/share-url.service'; import { IndexComponent } from './index.component'; import { DashboardIndexService } from './services/dashboard-index.service'; -import { Line, LineType } from './types'; +import { CountLine, Line, LineType } from './types'; describe('IndexComponent', () => { let component: IndexComponent; @@ -27,7 +27,7 @@ describe('IndexComponent', () => { { name: 'Running', color: 'yellow', statuses: [TaskStatus.TASK_STATUS_CREATING, TaskStatus.TASK_STATUS_PROCESSING]}, { name: 'Error', color: 'red', statuses: [TaskStatus.TASK_STATUS_CANCELLED, TaskStatus.TASK_STATUS_TIMEOUT]} ], - }, + } as CountLine, { name: 'line2', type: 'Tasks', @@ -39,7 +39,7 @@ describe('IndexComponent', () => { { name: 'Running', color: 'yellow', statuses: [TaskStatus.TASK_STATUS_CREATING, TaskStatus.TASK_STATUS_PROCESSING]}, { name: 'Unspecified', color: 'grey', statuses: [TaskStatus.TASK_STATUS_UNSPECIFIED, TaskStatus.TASK_STATUS_RETRIED]} ], - } + } as CountLine ]; const mockMatDialog = { @@ -100,7 +100,7 @@ describe('IndexComponent', () => { describe('onAddLineDialog', () => { it('should add a line', () => { - dialogRef$ = of({name: 'New line', type: 'CountStatus'} as unknown as Line); + dialogRef$ = of({name: 'New line', type: 'CountStatus'} as Line); const newLines = structuredClone(defaultLines); newLines.push({ name: 'New line', @@ -133,7 +133,7 @@ describe('IndexComponent', () => { ] }, ], - }); + } as CountLine); component.onAddLineDialog(); expect(component.lines).toEqual(newLines); }); diff --git a/src/app/dashboard/index.component.ts b/src/app/dashboard/index.component.ts index 42ce9db82..f3ca934ae 100644 --- a/src/app/dashboard/index.component.ts +++ b/src/app/dashboard/index.component.ts @@ -216,7 +216,7 @@ export class IndexComponent implements OnInit { ] }, ], - }); + } as Line); this.onSaveChange(); } else { diff --git a/src/app/dashboard/services/dashboard-index.service.spec.ts b/src/app/dashboard/services/dashboard-index.service.spec.ts index 389a1e84b..3d2a8d564 100644 --- a/src/app/dashboard/services/dashboard-index.service.spec.ts +++ b/src/app/dashboard/services/dashboard-index.service.spec.ts @@ -4,7 +4,7 @@ import { TasksStatusesService } from '@app/tasks/services/tasks-statuses.service import { DefaultConfigService } from '@services/default-config.service'; import { DashboardIndexService } from './dashboard-index.service'; import { DashboardStorageService } from './dashboard-storage.service'; -import { Line } from '../types'; +import { CountLine, Line } from '../types'; describe('DashboardIndexService', () => { let service: DashboardIndexService; @@ -30,7 +30,7 @@ describe('DashboardIndexService', () => { { name: 'Running', color: 'yellow', statuses: [TaskStatus.TASK_STATUS_CREATING, TaskStatus.TASK_STATUS_PROCESSING]}, { name: 'Error', color: 'red', statuses: [TaskStatus.TASK_STATUS_CANCELLED, TaskStatus.TASK_STATUS_TIMEOUT]} ], - }, + } as CountLine, { name: 'line2', type: 'Tasks', @@ -42,7 +42,7 @@ describe('DashboardIndexService', () => { { name: 'Running', color: 'yellow', statuses: [TaskStatus.TASK_STATUS_CREATING, TaskStatus.TASK_STATUS_PROCESSING]}, { name: 'Unspecified', color: 'grey', statuses: [TaskStatus.TASK_STATUS_UNSPECIFIED, TaskStatus.TASK_STATUS_RETRIED]} ], - } + } as CountLine ]; beforeEach(() => { @@ -90,7 +90,7 @@ describe('DashboardIndexService', () => { describe('Adding a line', () => { it('should push and save the line to the saved lines', () => { - const newLine: Line = { + const newLine: CountLine = { name: 'line3', type: 'Tasks', interval: 30, diff --git a/src/app/dashboard/services/dashboard-index.service.ts b/src/app/dashboard/services/dashboard-index.service.ts index 477578378..e905baecc 100644 --- a/src/app/dashboard/services/dashboard-index.service.ts +++ b/src/app/dashboard/services/dashboard-index.service.ts @@ -11,7 +11,7 @@ export class DashboardIndexService { readonly defaultLines: Line[] = this.#defaultConfigService.defaultDashboardLines; readonly defaultSplitLines: number = this.#defaultConfigService.defaultDashboardSplitLines; - addLine(line: Line): number | void { + addLine(line: L): number | void { const lines = this.restoreLines(); lines.push(line); this.saveLines(lines); diff --git a/src/app/dashboard/services/dashboard-storage.service.spec.ts b/src/app/dashboard/services/dashboard-storage.service.spec.ts index c7b7e2a61..531fc742d 100644 --- a/src/app/dashboard/services/dashboard-storage.service.spec.ts +++ b/src/app/dashboard/services/dashboard-storage.service.spec.ts @@ -2,7 +2,7 @@ import { TaskStatus } from '@aneoconsultingfr/armonik.api.angular'; import { TestBed } from '@angular/core/testing'; import { StorageService } from '@services/storage.service'; import { DashboardStorageService } from './dashboard-storage.service'; -import { Line } from '../types'; +import { CountLine, Line } from '../types'; describe('DashboardStorageService', () => { let service: DashboardStorageService; @@ -24,7 +24,7 @@ describe('DashboardStorageService', () => { { name: 'Running', color: 'yellow', statuses: [TaskStatus.TASK_STATUS_CREATING, TaskStatus.TASK_STATUS_PROCESSING]}, { name: 'Error', color: 'red', statuses: [TaskStatus.TASK_STATUS_CANCELLED, TaskStatus.TASK_STATUS_TIMEOUT]} ], - }, + } as CountLine, { name: 'line2', type: 'Tasks', @@ -36,7 +36,7 @@ describe('DashboardStorageService', () => { { name: 'Running', color: 'yellow', statuses: [TaskStatus.TASK_STATUS_CREATING, TaskStatus.TASK_STATUS_PROCESSING]}, { name: 'Unspecified', color: 'grey', statuses: [TaskStatus.TASK_STATUS_UNSPECIFIED, TaskStatus.TASK_STATUS_RETRIED]} ], - } + } as CountLine ]; beforeEach(() => { diff --git a/src/app/dashboard/types.ts b/src/app/dashboard/types.ts index 66189ad53..6b091682d 100644 --- a/src/app/dashboard/types.ts +++ b/src/app/dashboard/types.ts @@ -1,32 +1,37 @@ -import { ApplicationRawEnumField, PartitionRawEnumField, ResultRawEnumField, SessionRawEnumField, SessionTaskOptionEnumField, TaskOptionEnumField, TaskStatus, TaskSummaryEnumField } from '@aneoconsultingfr/armonik.api.angular'; -import { ApplicationRaw, ApplicationRawFilters } from '@app/applications/types'; +import { TaskStatus } from '@aneoconsultingfr/armonik.api.angular'; +import { ApplicationRaw } from '@app/applications/types'; import { PartitionRaw } from '@app/partitions/types'; import { ResultRaw } from '@app/results/types'; import { SessionRaw } from '@app/sessions/types'; import { TaskOptions, TaskSummary } from '@app/tasks/types'; -import { CustomColumn, IndexListOptions, RawColumnKey } from '@app/types/data'; -import { FiltersOr } from '@app/types/filters'; +import { ColumnKey, CustomColumn, DataRaw } from '@app/types/data'; +import { FiltersEnums, FiltersOptionsEnums, FiltersOr } from '@app/types/filters'; +import { ListOptions } from '@app/types/options'; import { TableType } from '@app/types/table'; export type LineType = TableType | 'CountStatus'; export type Summary = TaskSummary | ApplicationRaw | PartitionRaw | SessionRaw | ResultRaw; export type SummaryOptions = TaskOptions; -export type FiltersEnums = ApplicationRawEnumField | PartitionRawEnumField | SessionRawEnumField | TaskSummaryEnumField | ResultRawEnumField; -export type FiltersOptionsEnums = SessionTaskOptionEnumField | TaskOptionEnumField | null; - -export type Line = { - name: string, - type: LineType - interval: number, - hideGroupsHeader?: boolean, - filters: FiltersOr | ApplicationRawFilters, - options?: IndexListOptions; - taskStatusesGroups?: TasksStatusesGroup[], - displayedColumns?: RawColumnKey[], - customColumns?: CustomColumn[], - lockColumns?: boolean; + +export interface Line { + name: string; + type: LineType; + interval: number; + filters: FiltersOr; showFilters?: boolean; -}; +} + +export interface CountLine extends Line { + hideGroupsHeader?: boolean; + taskStatusesGroups?: TasksStatusesGroup[]; +} + +export interface TableLine extends Line { + options?: ListOptions; + displayedColumns?: ColumnKey[], + lockColumns?: boolean; + customColumns?: CustomColumn[], +} export type TasksStatusesGroup = { name: string; diff --git a/src/app/healthcheck/healthcheck.component.ts b/src/app/healthcheck/healthcheck.component.ts index ce16c40c2..9ff94bf3d 100644 --- a/src/app/healthcheck/healthcheck.component.ts +++ b/src/app/healthcheck/healthcheck.component.ts @@ -6,6 +6,7 @@ import { MatIconModule } from '@angular/material/icon'; import { MatMenuModule } from '@angular/material/menu'; import { MatSnackBarModule } from '@angular/material/snack-bar'; import { MatTooltipModule } from '@angular/material/tooltip'; +import { GrpcStatusEvent } from '@ngx-grpc/common'; import { Observable, Subject, catchError, of, startWith, switchMap } from 'rxjs'; import { HealthCheckGrpcService } from '@app/healthcheck/services/healthcheck-grpc.service'; import { ServiceHealth } from '@app/healthcheck/types'; @@ -62,7 +63,7 @@ export class HealthCheckComponent implements AfterViewInit { switchMap(() => { return this.healthcheckGrpcService.list$(); }), - catchError((error) => { + catchError((error: GrpcStatusEvent) => { console.error(error); this.notificationService.error($localize`Unable to get service health data`); this.globalStatus = HealthStatusEnum.HEALTH_STATUS_ENUM_UNSPECIFIED; diff --git a/src/app/partitions/components/table.component.html b/src/app/partitions/components/table.component.html index da0d33415..95de1d46f 100644 --- a/src/app/partitions/components/table.component.html +++ b/src/app/partitions/components/table.component.html @@ -1,3 +1,3 @@ - diff --git a/src/app/partitions/components/table.component.spec.ts b/src/app/partitions/components/table.component.spec.ts index 8437576b8..37758e48c 100644 --- a/src/app/partitions/components/table.component.spec.ts +++ b/src/app/partitions/components/table.component.spec.ts @@ -6,7 +6,8 @@ import { MatDialog } from '@angular/material/dialog'; import { BehaviorSubject, Subject, of, throwError } from 'rxjs'; import { ManageGroupsDialogResult, TasksStatusesGroup } from '@app/dashboard/types'; import { TableColumn } from '@app/types/column.type'; -import { PartitionData } from '@app/types/data'; +import { ColumnKey, PartitionData } from '@app/types/data'; +import { FiltersOr } from '@app/types/filters'; import { CacheService } from '@services/cache.service'; import { FiltersService } from '@services/filters.service'; import { NotificationService } from '@services/notification.service'; @@ -14,12 +15,12 @@ import { TasksByStatusService } from '@services/tasks-by-status.service'; import { PartitionsTableComponent } from './table.component'; import { PartitionsGrpcService } from '../services/partitions-grpc.service'; import { PartitionsIndexService } from '../services/partitions-index.service'; -import { PartitionRaw, PartitionRawColumnKey, PartitionRawFilters } from '../types'; +import { PartitionRaw } from '../types'; describe('TasksTableComponent', () => { let component: PartitionsTableComponent; - const displayedColumns: TableColumn[] = [ + const displayedColumns: TableColumn[] = [ { name: 'Count', key: 'count', @@ -52,7 +53,8 @@ describe('TasksTableComponent', () => { isSimpleColumn: jest.fn(), isNotSortableColumn: jest.fn(), columnToLabel: jest.fn(), - saveColumns: jest.fn() + saveColumns: jest.fn(), + saveOptions: jest.fn(), }; const mockNotificationService = { @@ -133,7 +135,7 @@ describe('TasksTableComponent', () => { }).inject(PartitionsTableComponent); component.displayedColumns = displayedColumns; - component.filters$ = new BehaviorSubject([]); + component.filters$ = new BehaviorSubject>([]); component.options = { pageIndex: 0, pageSize: 10, @@ -265,14 +267,21 @@ describe('TasksTableComponent', () => { }); }); - it('should refresh data on options changes', () => { - const spy = jest.spyOn(component.refresh$, 'next'); - component.onOptionsChange(); - expect(spy).toHaveBeenCalled(); + describe('options changes', () => { + it('should refresh data', () => { + const spy = jest.spyOn(component.refresh$, 'next'); + component.onOptionsChange(); + expect(spy).toHaveBeenCalled(); + }); + + it('should save options', () => { + component.onOptionsChange(); + expect(mockPartitionsIndexService.saveOptions).toHaveBeenCalled(); + }); }); test('onDrop should call PartitionsIndexService', () => { - const newColumns: PartitionRawColumnKey[] = ['actions', 'id', 'parentPartitionIds', 'preemptionPercentage']; + const newColumns: ColumnKey[] = ['actions', 'id', 'parentPartitionIds', 'preemptionPercentage']; component.onDrop(newColumns); expect(mockPartitionsIndexService.saveColumns).toHaveBeenCalledWith(newColumns); }); @@ -378,4 +387,9 @@ describe('TasksTableComponent', () => { expect(component.isDataRawEqual(partition1, partition2)).toBeFalsy(); }); }); + + it('should track a partition by its id', () => { + const partition = {raw: { id: 'partition' }} as PartitionData; + expect(component.trackBy(0, partition)).toEqual(partition.raw.id); + }); }); \ No newline at end of file diff --git a/src/app/partitions/components/table.component.ts b/src/app/partitions/components/table.component.ts index e4152fd23..f0fb7ba34 100644 --- a/src/app/partitions/components/table.component.ts +++ b/src/app/partitions/components/table.component.ts @@ -3,14 +3,14 @@ import { AfterViewInit, Component, OnInit, inject } from '@angular/core'; import { TaskSummaryFilters } from '@app/tasks/types'; import { AbstractTaskByStatusTableComponent } from '@app/types/components/table'; import { Scope } from '@app/types/config'; -import { PartitionData } from '@app/types/data'; +import { ArmonikData, PartitionData } from '@app/types/data'; import { TableComponent } from '@components/table/table.component'; import { FiltersService } from '@services/filters.service'; import { GrpcSortFieldService } from '@services/grpc-sort-field.service'; import { TableTasksByStatus, TasksByStatusService } from '@services/tasks-by-status.service'; import { PartitionsGrpcService } from '../services/partitions-grpc.service'; import { PartitionsIndexService } from '../services/partitions-index.service'; -import { PartitionRaw, PartitionRawColumnKey, PartitionRawFieldKey, PartitionRawListOptions } from '../types'; +import { PartitionRaw } from '../types'; @Component({ selector: 'app-partitions-table', @@ -27,7 +27,7 @@ import { PartitionRaw, PartitionRawColumnKey, PartitionRawFieldKey, PartitionRaw TableComponent, ] }) -export class PartitionsTableComponent extends AbstractTaskByStatusTableComponent +export class PartitionsTableComponent extends AbstractTaskByStatusTableComponent implements OnInit, AfterViewInit { readonly grpcService = inject(PartitionsGrpcService); @@ -105,4 +105,8 @@ export class PartitionsTableComponent extends AbstractTaskByStatusTableComponent ] ]; } + + trackBy(index: number, items: ArmonikData) { + return items.raw.id; + } } \ No newline at end of file diff --git a/src/app/partitions/index.component.spec.ts b/src/app/partitions/index.component.spec.ts index b71851b38..352e5dbcb 100644 --- a/src/app/partitions/index.component.spec.ts +++ b/src/app/partitions/index.component.spec.ts @@ -5,7 +5,9 @@ import { Router } from '@angular/router'; import { of } from 'rxjs'; import { DashboardIndexService } from '@app/dashboard/services/dashboard-index.service'; import { TableColumn } from '@app/types/column.type'; -import { CustomColumn } from '@app/types/data'; +import { ColumnKey, CustomColumn } from '@app/types/data'; +import { FiltersOr } from '@app/types/filters'; +import { ListOptions } from '@app/types/options'; import { AutoRefreshService } from '@services/auto-refresh.service'; import { IconsService } from '@services/icons.service'; import { NotificationService } from '@services/notification.service'; @@ -14,7 +16,7 @@ import { IndexComponent } from './index.component'; import { PartitionsFiltersService } from './services/partitions-filters.service'; import { PartitionsGrpcService } from './services/partitions-grpc.service'; import { PartitionsIndexService } from './services/partitions-index.service'; -import { PartitionRawColumnKey, PartitionRawFilters, PartitionRawListOptions } from './types'; +import { PartitionRaw } from './types'; describe('Partitions Index Component', () => { let component: IndexComponent; @@ -41,8 +43,8 @@ describe('Partitions Index Component', () => { navigate: jest.fn() }; - const defaultColumns: PartitionRawColumnKey[] = ['id', 'actions', 'podMax', 'priority']; - const defaultOptions: PartitionRawListOptions = { + const defaultColumns: ColumnKey[] = ['id', 'actions', 'podMax', 'priority']; + const defaultOptions: ListOptions = { pageIndex: 0, pageSize: 10, sort: { @@ -50,7 +52,7 @@ describe('Partitions Index Component', () => { direction: 'desc' } }; - const availableTableColumns: TableColumn[] = [ + const availableTableColumns: TableColumn[] = [ { name: $localize`ID`, key: 'id', @@ -254,7 +256,7 @@ describe('Partitions Index Component', () => { }); describe('On columns change', () => { - const newColumns: PartitionRawColumnKey[] = ['id', 'count']; + const newColumns: ColumnKey[] = ['id', 'count']; beforeEach(() => { component.onColumnsChange(newColumns); }); @@ -325,7 +327,7 @@ describe('Partitions Index Component', () => { }); describe('On Filters Change', () => { - const newFilters: PartitionRawFilters = [ + const newFilters: FiltersOr = [ [ { field: PartitionRawEnumField.PARTITION_RAW_ENUM_FIELD_ID, @@ -422,6 +424,7 @@ describe('Partitions Index Component', () => { name: 'Partitions', type: 'Partitions', interval: 10, + showFilters: false, lockColumns: false, displayedColumns: defaultColumns, options: defaultOptions, diff --git a/src/app/partitions/index.component.ts b/src/app/partitions/index.component.ts index 8ecce223a..62f855106 100644 --- a/src/app/partitions/index.component.ts +++ b/src/app/partitions/index.component.ts @@ -29,7 +29,7 @@ import { PartitionsTableComponent } from './components/table.component'; import { PartitionsFiltersService } from './services/partitions-filters.service'; import { PartitionsGrpcService } from './services/partitions-grpc.service'; import { PartitionsIndexService } from './services/partitions-index.service'; -import { PartitionRawColumnKey, PartitionRawFilters, PartitionRawListOptions } from './types'; +import { PartitionRaw } from './types'; @Component({ selector: 'app-partitions-index', @@ -71,7 +71,7 @@ import { PartitionRawColumnKey, PartitionRawFilters, PartitionRawListOptions } f PartitionsTableComponent ] }) -export class IndexComponent extends TableHandler implements OnInit, AfterViewInit, OnDestroy { +export class IndexComponent extends TableHandler implements OnInit, AfterViewInit, OnDestroy { readonly filtersService = inject(PartitionsFiltersService); readonly indexService = inject(PartitionsIndexService); diff --git a/src/app/partitions/services/partitions-filters.service.ts b/src/app/partitions/services/partitions-filters.service.ts index 233898942..ec3b46d3c 100644 --- a/src/app/partitions/services/partitions-filters.service.ts +++ b/src/app/partitions/services/partitions-filters.service.ts @@ -9,7 +9,7 @@ import { PartitionFilterField, PartitionRawFilters, PartitionsFiltersDefinition @Injectable({ providedIn: 'root' }) -export class PartitionsFiltersService implements FiltersServiceInterface { +export class PartitionsFiltersService implements FiltersServiceInterface { readonly defaultConfigService = inject(DefaultConfigService); readonly tableService = inject(TableService); diff --git a/src/app/partitions/services/partitions-grpc.service.ts b/src/app/partitions/services/partitions-grpc.service.ts index 75aa6f228..2d23bc1f1 100644 --- a/src/app/partitions/services/partitions-grpc.service.ts +++ b/src/app/partitions/services/partitions-grpc.service.ts @@ -5,11 +5,11 @@ import { Filter, FilterType } from '@app/types/filters'; import { GrpcGetInterface, GrpcTableService, ListDefaultSortField, RequestFilterField } from '@app/types/services/grpcService'; import { FilterField, buildArrayFilter, buildNumberFilter, buildStringFilter } from '@services/grpc-build-request.service'; import { PartitionsFiltersService } from './partitions-filters.service'; -import { PartitionRawFieldKey, PartitionRawFilters, PartitionRawListOptions } from '../types'; +import { PartitionRaw, PartitionRawFieldKey, PartitionRawFilters, PartitionRawListOptions } from '../types'; @Injectable() -export class PartitionsGrpcService extends GrpcTableService +export class PartitionsGrpcService extends GrpcTableService implements GrpcGetInterface { readonly filterService = inject(PartitionsFiltersService); readonly grpcClient = inject(PartitionsClient); diff --git a/src/app/partitions/services/partitions-index.service.ts b/src/app/partitions/services/partitions-index.service.ts index 691f20573..989e54d7d 100644 --- a/src/app/partitions/services/partitions-index.service.ts +++ b/src/app/partitions/services/partitions-index.service.ts @@ -6,7 +6,7 @@ import { TableService } from '@services/table.service'; import { PartitionRaw, PartitionRawColumnKey, PartitionRawListOptions } from '../types'; @Injectable() -export class PartitionsIndexService implements IndexServiceInterface { +export class PartitionsIndexService implements IndexServiceInterface { readonly defaultConfigService = inject(DefaultConfigService); readonly tableService = inject(TableService); @@ -15,7 +15,7 @@ export class PartitionsIndexService implements IndexServiceInterface[] = [ + readonly availableTableColumns: TableColumn[] = [ { name: $localize`ID`, key: 'id', diff --git a/src/app/partitions/services/partitions-inspection.service.spec.ts b/src/app/partitions/services/partitions-inspection.service.spec.ts new file mode 100644 index 000000000..1e89c60c6 --- /dev/null +++ b/src/app/partitions/services/partitions-inspection.service.spec.ts @@ -0,0 +1,17 @@ +import { PartitionsInspectionService } from './partitions-inspection.service'; + +describe('PartitionsInspectionService', () => { + const service = new PartitionsInspectionService(); + + it('should run', () => { + expect(service).toBeTruthy(); + }); + + it('should have a defined "fields"', () => { + expect(service.fields).toBeDefined(); + }); + + it('should have a defined "arrays"', () => { + expect(service.arrays).toBeDefined(); + }); +}); \ No newline at end of file diff --git a/src/app/partitions/services/partitions-inspection.service.ts b/src/app/partitions/services/partitions-inspection.service.ts new file mode 100644 index 000000000..4d1685c80 --- /dev/null +++ b/src/app/partitions/services/partitions-inspection.service.ts @@ -0,0 +1,28 @@ +import { Field } from '@app/types/column.type'; +import { InspectionService } from '@app/types/services/inspectionService'; +import { PartitionRaw } from '../types'; + +export class PartitionsInspectionService extends InspectionService { + readonly fields: Field[] = [ + { + key: 'priority' + }, + { + key: 'preemptionPercentage' + }, + { + key: 'podReserved' + }, + { + key: 'podMax' + }, + { + key: 'podConfiguration', + type: 'object' + } + ]; + + readonly arrays: Field[] = [ + { key: 'parentPartitionIds', link: 'partitions', queryParams: '0-root-1-0' } + ]; +} \ No newline at end of file diff --git a/src/app/partitions/show.component.html b/src/app/partitions/show.component.html new file mode 100644 index 000000000..7ba2fad63 --- /dev/null +++ b/src/app/partitions/show.component.html @@ -0,0 +1,16 @@ + +
+ + Partition +
+
+ + +
+
\ No newline at end of file diff --git a/src/app/partitions/show.component.spec.ts b/src/app/partitions/show.component.spec.ts index 8c4bccd69..9620dd1f0 100644 --- a/src/app/partitions/show.component.spec.ts +++ b/src/app/partitions/show.component.spec.ts @@ -1,12 +1,14 @@ import { GetPartitionResponse } from '@aneoconsultingfr/armonik.api.angular'; import { TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; +import { GrpcStatusEvent } from '@ngx-grpc/common'; import { BehaviorSubject, Observable, of, throwError } from 'rxjs'; import { FiltersService } from '@services/filters.service'; import { IconsService } from '@services/icons.service'; import { NotificationService } from '@services/notification.service'; import { ShareUrlService } from '@services/share-url.service'; import { PartitionsGrpcService } from './services/partitions-grpc.service'; +import { PartitionsInspectionService } from './services/partitions-inspection.service'; import { ShowComponent } from './show.component'; import { PartitionRaw } from './types'; @@ -35,6 +37,7 @@ describe('ShowComponent', () => { partitionId: 'partitionId' } } as unknown as PartitionRaw; + const mockPartitionsGrpcService = { get$: jest.fn((): Observable => of({partition: returnedPartition} as GetPartitionResponse)), }; @@ -48,11 +51,11 @@ describe('ShowComponent', () => { { provide: NotificationService, useValue: mockNotificationService }, { provide: ShareUrlService, useValue: mockShareUrlService }, { provide: ActivatedRoute, useValue: mockActivatedRoute }, - { provide: PartitionsGrpcService, useValue: mockPartitionsGrpcService } + { provide: PartitionsGrpcService, useValue: mockPartitionsGrpcService }, + PartitionsInspectionService, ] }).inject(ShowComponent); component.ngOnInit(); - component.ngAfterViewInit(); }); it('should create', () => { @@ -67,6 +70,14 @@ describe('ShowComponent', () => { it('should set sharableURL', () => { expect(mockShareUrlService.generateSharableURL).toHaveBeenCalled(); }); + + it('should set sessionsKey', () => { + expect(component.sessionsKey).toEqual('0-root-3-0'); + }); + + it('should set tasksKey', () => { + expect(component.tasksKey).toEqual('0-options-4-0'); + }); }); it('should get icons', () => { @@ -103,17 +114,29 @@ describe('ShowComponent', () => { component.refresh.next(); expect(spy).toHaveBeenCalled(); }); + + it('should set sessionsQueryParams', () => { + component.refresh.next(); + expect(component.sessionsQueryParams).toEqual({'0-root-3-0': returnedPartition.id}); + }); + + it('should set tasksQueryParams', () => { + component.refresh.next(); + expect(component.tasksQueryParams).toEqual({'0-options-4-0': returnedPartition.id}); + }); }); describe('Handle errors', () => { it('should log errors', () => { const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); - component.handleError(new Error()); + const errorMessage = 'ErrorMessage'; + component.handleError({statusMessage: errorMessage} as GrpcStatusEvent); expect(errorSpy).toHaveBeenCalled(); }); it('should notify the error', () => { - component.handleError(new Error()); + const errorMessage = 'ErrorMessage'; + component.handleError({statusMessage: errorMessage} as GrpcStatusEvent); expect(mockNotificationService.error).toHaveBeenCalledWith('Could not retrieve data.'); }); }); diff --git a/src/app/partitions/show.component.ts b/src/app/partitions/show.component.ts index f707ac742..5cd13758e 100644 --- a/src/app/partitions/show.component.ts +++ b/src/app/partitions/show.component.ts @@ -1,8 +1,11 @@ import { FilterArrayOperator, FilterStringOperator, GetPartitionResponse, SessionRawEnumField, TaskOptionEnumField } from '@aneoconsultingfr/armonik.api.angular'; -import { AfterViewInit, ChangeDetectionStrategy, Component, OnDestroy, OnInit, inject } from '@angular/core'; +import { ChangeDetectionStrategy, Component, OnDestroy, OnInit, inject } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatSnackBar } from '@angular/material/snack-bar'; -import { AppShowComponent, ShowActionButton, ShowActionInterface } from '@app/types/components/show'; +import { Params, RouterModule } from '@angular/router'; +import { Field } from '@app/types/column.type'; +import { AppShowComponent } from '@app/types/components/show'; import { ShowPageComponent } from '@components/show-page.component'; import { FiltersService } from '@services/filters.service'; import { NotificationService } from '@services/notification.service'; @@ -14,18 +17,13 @@ import { TableService } from '@services/table.service'; import { UtilsService } from '@services/utils.service'; import { PartitionsFiltersService } from './services/partitions-filters.service'; import { PartitionsGrpcService } from './services/partitions-grpc.service'; +import { PartitionsInspectionService } from './services/partitions-inspection.service'; import { PartitionRaw } from './types'; @Component({ selector: 'app-partitions-show', - template: ` - - - Partition - - `, - styles: [` - `], + templateUrl: 'show.component.html', + styleUrl: '../../inspections.css', standalone: true, providers: [ UtilsService, @@ -38,68 +36,64 @@ import { PartitionRaw } from './types'; TableStorageService, NotificationService, MatSnackBar, - FiltersService + FiltersService, + PartitionsInspectionService, ], imports: [ ShowPageComponent, - MatIconModule + MatIconModule, + MatButtonModule, + RouterModule, ], changeDetection: ChangeDetectionStrategy.OnPush }) -export class ShowComponent extends AppShowComponent implements OnInit, AfterViewInit, ShowActionInterface, OnDestroy { +export class ShowComponent extends AppShowComponent implements OnInit, OnDestroy { readonly grpcService = inject(PartitionsGrpcService); + readonly inspectionService = inject(PartitionsInspectionService); + private readonly filtersService = inject(FiltersService); - actionButtons: ShowActionButton[] = [ - { - id: 'sessions', - name: $localize`See sessions`, - icon: this.getIcon('sessions'), - link: '/sessions', - queryParams: {}, - area: 'left' - }, - { - id: 'tasks', - name: $localize`See tasks`, - icon: this.getIcon('tasks'), - link: '/tasks', - queryParams: {}, - area: 'left' - } - ]; + sessionsKey: string = ''; + sessionsQueryParams: Params = {}; - ngOnInit(): void { - this.getIdByRoute(); - this.sharableURL = this.getSharableUrl(); - } + tasksKey: string = ''; + tasksQueryParams: Params = {}; + + arrays: Field[] = []; - ngAfterViewInit(): void { - this.subscribeToData(); - this.refresh.next(); + ngOnInit(): void { + this.createTasksKey(); + this.createSessionsKey(); + this.arrays = this.inspectionService.arrays; + this.initInspection(); } ngOnDestroy(): void { this.unsubscribe(); } - getDataFromResponse(data: GetPartitionResponse): PartitionRaw | undefined { - return data.partition; + createSessionsKey() { + this.sessionsKey = this.filtersService.createQueryParamsKey(0, 'root', FilterArrayOperator.FILTER_ARRAY_OPERATOR_CONTAINS, SessionRawEnumField.SESSION_RAW_ENUM_FIELD_PARTITION_IDS); } - afterDataFetching(): void { - const data = this.data(); - if (data) { - this.filtersService.createFilterQueryParams(this.actionButtons, 'sessions', this.partitionsKey, data.id); - this.filtersService.createFilterQueryParams(this.actionButtons, 'tasks', this.tasksKey, data.id); - } + createSessionsQueryParams() { + this.sessionsQueryParams[this.sessionsKey] = this.data()?.id; + } + + createTasksKey() { + this.tasksKey = this.filtersService.createQueryParamsKey(0, 'options', FilterStringOperator.FILTER_STRING_OPERATOR_EQUAL, TaskOptionEnumField.TASK_OPTION_ENUM_FIELD_PARTITION_ID); } - get partitionsKey() { - return this.filtersService.createQueryParamsKey(0, 'root', FilterArrayOperator.FILTER_ARRAY_OPERATOR_CONTAINS, SessionRawEnumField.SESSION_RAW_ENUM_FIELD_PARTITION_IDS); + createTasksQueryParams() { + this.tasksQueryParams[this.tasksKey] = this.data()?.id; } - get tasksKey() { - return this.filtersService.createQueryParamsKey(0, 'options', FilterStringOperator.FILTER_STRING_OPERATOR_EQUAL, TaskOptionEnumField.TASK_OPTION_ENUM_FIELD_PARTITION_ID); + getDataFromResponse(data: GetPartitionResponse): PartitionRaw | undefined { + return data.partition; + } + + afterDataFetching(): void { + this.createSessionsQueryParams(); + this.createTasksQueryParams(); } } diff --git a/src/app/pipes/pretty.pipe.spec.ts b/src/app/pipes/pretty.pipe.spec.ts new file mode 100644 index 000000000..d38de1eea --- /dev/null +++ b/src/app/pipes/pretty.pipe.spec.ts @@ -0,0 +1,11 @@ +import { PrettyPipe } from './pretty.pipe'; + +describe('PrettyPipe', () => { + const pipe = new PrettyPipe(); + + it('should make a string prettier', () => { + const uglyString = '_ABeautiful_String'; + const prettyString = 'A Beautiful String'; + expect(pipe.transform(uglyString)).toEqual(prettyString); + }); +}); \ No newline at end of file diff --git a/src/app/pipes/pretty.pipe.ts b/src/app/pipes/pretty.pipe.ts new file mode 100644 index 000000000..f9573bd92 --- /dev/null +++ b/src/app/pipes/pretty.pipe.ts @@ -0,0 +1,15 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +/** + * Changes the syntax of a camelCase string by removing "_", place a + * space between each UpperCase character, and put this character in lowercase, + * and turn the first character to an uppercase character + * @param key string to format + * @returns formatted string + */ +@Pipe({ name: 'pretty', standalone: true }) +export class PrettyPipe implements PipeTransform { + transform(key: string | number | symbol) { + return key.toString().replaceAll('_', '').replace(/(? str.toUpperCase()); + } +} \ No newline at end of file diff --git a/src/app/profile/index.component.spec.ts b/src/app/profile/index.component.spec.ts index f9b3957ee..bbae9a811 100644 --- a/src/app/profile/index.component.spec.ts +++ b/src/app/profile/index.component.spec.ts @@ -1,7 +1,6 @@ import { User } from '@aneoconsultingfr/armonik.api.angular'; import { TestBed } from '@angular/core/testing'; import { IconsService } from '@services/icons.service'; -import { ShareUrlService } from '@services/share-url.service'; import { UserService } from '@services/user.service'; import { IndexComponent } from './index.component'; import { PermissionGroup } from './types'; @@ -9,11 +8,6 @@ import { PermissionGroup } from './types'; describe('IndexComponent', () => { let component: IndexComponent; - const returnedUrl = 'some-url'; - const mockShareUrlService = { - generateSharableURL: jest.fn(() => returnedUrl), - }; - const user: User.AsObject = { username: 'ArmoniK', permissions: ['Sessions:list', 'Sessions:get', 'Sessions:cancel', 'Tasks:list'], @@ -28,22 +22,18 @@ describe('IndexComponent', () => { component = TestBed.configureTestingModule({ providers: [ IndexComponent, - { provide: ShareUrlService, useValue: mockShareUrlService }, { provide: UserService, useValue: mockUserService }, IconsService ] }).inject(IndexComponent); - component.ngOnInit(); }); it('should create', () => { expect(component).toBeDefined(); }); - describe('Initialisation', () => { - it('should init the sharableUrl', () => { - expect(component.sharableURL).toBe(returnedUrl); - }); + it('should have a null sharabkeurl', () => { + expect(component.sharableURL).toBe(null); }); it('should allows to get user', () => { diff --git a/src/app/profile/index.component.ts b/src/app/profile/index.component.ts index aacc07f46..052b9795d 100644 --- a/src/app/profile/index.component.ts +++ b/src/app/profile/index.component.ts @@ -1,11 +1,10 @@ -import { Component, OnInit, inject } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { MatIconModule } from '@angular/material/icon'; import { PageHeaderComponent } from '@components/page-header.component'; import { PageSectionHeaderComponent } from '@components/page-section-header.component'; import { PageSectionComponent } from '@components/page-section.component'; import { IconsService } from '@services/icons.service'; import { QueryParamsService } from '@services/query-params.service'; -import { ShareUrlService } from '@services/share-url.service'; import { UserService } from '@services/user.service'; import { Group, PermissionGroup, isGroup } from './types'; @@ -42,7 +41,6 @@ import { Group, PermissionGroup, isGroup } from './types'; `], standalone: true, providers: [ - ShareUrlService, QueryParamsService, ], imports: [ @@ -52,17 +50,12 @@ import { Group, PermissionGroup, isGroup } from './types'; MatIconModule, ] }) -export class IndexComponent implements OnInit { - sharableURL = ''; +export class IndexComponent { + sharableURL = null; #userService = inject(UserService); - #shareUrlService = inject(ShareUrlService); #iconsService = inject(IconsService); - ngOnInit(): void { - this.sharableURL = this.#shareUrlService.generateSharableURL(null, null); - } - get user() { return this.#userService.user; } diff --git a/src/app/results/components/table.component.html b/src/app/results/components/table.component.html index 5006e2816..a2a5e041a 100644 --- a/src/app/results/components/table.component.html +++ b/src/app/results/components/table.component.html @@ -1,2 +1,2 @@ - diff --git a/src/app/results/components/table.component.spec.ts b/src/app/results/components/table.component.spec.ts index ea194cb59..89d3c846d 100644 --- a/src/app/results/components/table.component.spec.ts +++ b/src/app/results/components/table.component.spec.ts @@ -3,6 +3,7 @@ import { signal } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { BehaviorSubject, Subject, of, throwError } from 'rxjs'; import { TableColumn } from '@app/types/column.type'; +import { ColumnKey, ResultData } from '@app/types/data'; import { CacheService } from '@services/cache.service'; import { FiltersService } from '@services/filters.service'; import { NotificationService } from '@services/notification.service'; @@ -10,12 +11,12 @@ import { ResultsTableComponent } from './table.component'; import { ResultsGrpcService } from '../services/results-grpc.service'; import { ResultsIndexService } from '../services/results-index.service'; import { ResultsStatusesService } from '../services/results-statuses.service'; -import { ResultRaw, ResultRawColumnKey, ResultRawFilters } from '../types'; +import { ResultRaw, ResultRawFilters } from '../types'; describe('TasksTableComponent', () => { let component: ResultsTableComponent; - const displayedColumns: TableColumn[] = [ + const displayedColumns: TableColumn[] = [ { name: 'Result ID', key: 'resultId', @@ -54,7 +55,8 @@ describe('TasksTableComponent', () => { isSimpleColumn: jest.fn(), isNotSortableColumn: jest.fn(), columnToLabel: jest.fn(), - saveColumns: jest.fn() + saveColumns: jest.fn(), + saveOptions: jest.fn(), }; const mockNotificationService = { @@ -195,14 +197,21 @@ describe('TasksTableComponent', () => { }); }); - it('should refresh data on options changes', () => { - const spy = jest.spyOn(component.refresh$, 'next'); - component.onOptionsChange(); - expect(spy).toHaveBeenCalled(); + describe('options changes', () => { + it('should refresh data', () => { + const spy = jest.spyOn(component.refresh$, 'next'); + component.onOptionsChange(); + expect(spy).toHaveBeenCalled(); + }); + + it('should save options', () => { + component.onOptionsChange(); + expect(mockResultsIndexService.saveOptions).toHaveBeenCalled(); + }); }); test('onDrop should call ResultsIndexService', () => { - const newColumns: ResultRawColumnKey[] = ['actions', 'resultId', 'status']; + const newColumns: ColumnKey[] = ['actions', 'resultId', 'status']; component.onDrop(newColumns); expect(mockResultsIndexService.saveColumns).toHaveBeenCalledWith(newColumns); }); @@ -228,4 +237,9 @@ describe('TasksTableComponent', () => { expect(component.isDataRawEqual(result1, result2)).toBeFalsy(); }); }); + + it('should track a result by its id', () => { + const result = {raw: { resultId: 'result' }} as ResultData; + expect(component.trackBy(0, result)).toEqual(result.raw.resultId); + }); }); \ No newline at end of file diff --git a/src/app/results/components/table.component.ts b/src/app/results/components/table.component.ts index b48dd0d56..1cb865bb6 100644 --- a/src/app/results/components/table.component.ts +++ b/src/app/results/components/table.component.ts @@ -3,7 +3,7 @@ import { AfterViewInit, Component, OnInit, inject } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { AbstractTableComponent } from '@app/types/components/table'; import { Scope } from '@app/types/config'; -import { ResultData } from '@app/types/data'; +import { ArmonikData, ResultData } from '@app/types/data'; import { TableComponent } from '@components/table/table.component'; import { FiltersService } from '@services/filters.service'; import { GrpcSortFieldService } from '@services/grpc-sort-field.service'; @@ -12,7 +12,7 @@ import { ResultsFiltersService } from '../services/results-filters.service'; import { ResultsGrpcService } from '../services/results-grpc.service'; import { ResultsIndexService } from '../services/results-index.service'; import { ResultsStatusesService } from '../services/results-statuses.service'; -import { ResultRaw, ResultRawColumnKey, ResultRawFieldKey, ResultRawListOptions } from '../types'; +import { ResultRaw } from '../types'; @Component({ selector: 'app-results-table', @@ -32,7 +32,7 @@ import { ResultRaw, ResultRawColumnKey, ResultRawFieldKey, ResultRawListOptions TableComponent, ] }) -export class ResultsTableComponent extends AbstractTableComponent +export class ResultsTableComponent extends AbstractTableComponent implements OnInit, AfterViewInit { scope: Scope = 'results'; readonly grpcService = inject(ResultsGrpcService); @@ -68,4 +68,8 @@ export class ResultsTableComponent extends AbstractTableComponent): string | number { + return item.raw.resultId; + } } \ No newline at end of file diff --git a/src/app/results/index.component.spec.ts b/src/app/results/index.component.spec.ts index 9a004ffcb..95e572eab 100644 --- a/src/app/results/index.component.spec.ts +++ b/src/app/results/index.component.spec.ts @@ -5,7 +5,9 @@ import { Router } from '@angular/router'; import { of } from 'rxjs'; import { DashboardIndexService } from '@app/dashboard/services/dashboard-index.service'; import { TableColumn } from '@app/types/column.type'; -import { CustomColumn } from '@app/types/data'; +import { ColumnKey, CustomColumn } from '@app/types/data'; +import { FiltersOr } from '@app/types/filters'; +import { ListOptions } from '@app/types/options'; import { AutoRefreshService } from '@services/auto-refresh.service'; import { IconsService } from '@services/icons.service'; import { NotificationService } from '@services/notification.service'; @@ -14,7 +16,7 @@ import { IndexComponent } from './index.component'; import { ResultsFiltersService } from './services/results-filters.service'; import { ResultsGrpcService } from './services/results-grpc.service'; import { ResultsIndexService } from './services/results-index.service'; -import { ResultRawColumnKey, ResultRawFilters, ResultRawListOptions } from './types'; +import { ResultRaw } from './types'; describe('Results Index Component', () => { let component: IndexComponent; @@ -41,8 +43,8 @@ describe('Results Index Component', () => { navigate: jest.fn() }; - const defaultColumns: ResultRawColumnKey[] = ['resultId', 'actions', 'createdAt', 'size']; - const defaultOptions: ResultRawListOptions = { + const defaultColumns: ColumnKey[] = ['resultId', 'actions', 'createdAt', 'size']; + const defaultOptions: ListOptions = { pageIndex: 0, pageSize: 10, sort: { @@ -50,7 +52,7 @@ describe('Results Index Component', () => { direction: 'desc' } }; - const availableTableColumns: TableColumn[] = [ + const availableTableColumns: TableColumn[] = [ { name: $localize`Result ID`, key: 'resultId', @@ -255,7 +257,7 @@ describe('Results Index Component', () => { }); describe('On columns change', () => { - const newColumns: ResultRawColumnKey[] = ['resultId', 'createdAt']; + const newColumns: ColumnKey[] = ['resultId', 'createdAt']; beforeEach(() => { component.onColumnsChange(newColumns); }); @@ -328,7 +330,7 @@ describe('Results Index Component', () => { describe('On Filters Change', () => { - const newFilters: ResultRawFilters = [ + const newFilters: FiltersOr = [ [ { field: ResultRawEnumField.RESULT_RAW_ENUM_FIELD_NAME, @@ -425,6 +427,7 @@ describe('Results Index Component', () => { name: 'Results', type: 'Results', interval: 10, + showFilters: false, lockColumns: false, displayedColumns: defaultColumns, options: defaultOptions, diff --git a/src/app/results/index.component.ts b/src/app/results/index.component.ts index 1fbf8b3e2..d897f0aaa 100644 --- a/src/app/results/index.component.ts +++ b/src/app/results/index.component.ts @@ -27,7 +27,7 @@ import { ResultsTableComponent } from './components/table.component'; import { ResultsFiltersService } from './services/results-filters.service'; import { ResultsIndexService } from './services/results-index.service'; import { ResultsStatusesService } from './services/results-statuses.service'; -import { ResultRawColumnKey, ResultRawFilters, ResultRawListOptions } from './types'; +import { ResultRaw } from './types'; @Component({ @@ -67,7 +67,7 @@ import { ResultRawColumnKey, ResultRawFilters, ResultRawListOptions } from './ty ResultsTableComponent ] }) -export class IndexComponent extends TableHandler implements OnInit, AfterViewInit, OnDestroy { +export class IndexComponent extends TableHandler implements OnInit, AfterViewInit, OnDestroy { readonly filtersService = inject(ResultsFiltersService); readonly indexService = inject(ResultsIndexService); diff --git a/src/app/results/services/results-filters.service.ts b/src/app/results/services/results-filters.service.ts index 6f7bb4656..c1c98ac52 100644 --- a/src/app/results/services/results-filters.service.ts +++ b/src/app/results/services/results-filters.service.ts @@ -10,7 +10,7 @@ import { ResultFilterField, ResultRawFilters, ResultsFiltersDefinition } from '. @Injectable({ providedIn: 'root' }) -export class ResultsFiltersService implements FiltersServiceInterface, FiltersServiceStatusesInterface { +export class ResultsFiltersService implements FiltersServiceInterface, FiltersServiceStatusesInterface { readonly statusService = inject(ResultsStatusesService); readonly defaultConfigService = inject(DefaultConfigService); readonly tableService = inject(TableService); diff --git a/src/app/results/services/results-grpc.service.ts b/src/app/results/services/results-grpc.service.ts index cce038901..20c4e2a3b 100644 --- a/src/app/results/services/results-grpc.service.ts +++ b/src/app/results/services/results-grpc.service.ts @@ -5,10 +5,10 @@ import { Filter, FilterType } from '@app/types/filters'; import { GrpcGetInterface, GrpcTableService, ListDefaultSortField } from '@app/types/services/grpcService'; import { FilterField, buildDateFilter, buildNumberFilter, buildStatusFilter, buildStringFilter } from '@services/grpc-build-request.service'; import { ResultsFiltersService } from './results-filters.service'; -import { ResultRawFieldKey, ResultRawFilters, ResultRawListOptions } from '../types'; +import { ResultRaw, ResultRawFieldKey, ResultRawFilters, ResultRawListOptions } from '../types'; @Injectable() -export class ResultsGrpcService extends GrpcTableService +export class ResultsGrpcService extends GrpcTableService implements GrpcGetInterface { readonly filterService = inject(ResultsFiltersService); diff --git a/src/app/results/services/results-index.service.ts b/src/app/results/services/results-index.service.ts index 2f032818e..d5857d31b 100644 --- a/src/app/results/services/results-index.service.ts +++ b/src/app/results/services/results-index.service.ts @@ -6,7 +6,7 @@ import { TableService } from '@services/table.service'; import { ResultRaw, ResultRawColumnKey, ResultRawListOptions } from '../types'; @Injectable() -export class ResultsIndexService implements IndexServiceInterface { +export class ResultsIndexService implements IndexServiceInterface { defaultConfigService = inject(DefaultConfigService); tableService = inject(TableService); @@ -15,7 +15,7 @@ export class ResultsIndexService implements IndexServiceInterface[] = [ + readonly availableTableColumns: TableColumn[] = [ { name: $localize`Name`, key: 'name', diff --git a/src/app/results/services/results-inspection.service.spec.ts b/src/app/results/services/results-inspection.service.spec.ts new file mode 100644 index 000000000..b087e03d7 --- /dev/null +++ b/src/app/results/services/results-inspection.service.spec.ts @@ -0,0 +1,13 @@ +import { ResultsInspectionService } from './results-inspection.service'; + +describe('ResultsInspectionService', () => { + const service = new ResultsInspectionService(); + + it('should run', () => { + expect(service).toBeTruthy(); + }); + + it('should have a defined "fields"', () => { + expect(service.fields).toBeDefined(); + }); +}); \ No newline at end of file diff --git a/src/app/results/services/results-inspection.service.ts b/src/app/results/services/results-inspection.service.ts new file mode 100644 index 000000000..0573d3802 --- /dev/null +++ b/src/app/results/services/results-inspection.service.ts @@ -0,0 +1,30 @@ +import { Field } from '@app/types/column.type'; +import { InspectionService } from '@app/types/services/inspectionService'; +import { ResultRaw } from '../types'; + +export class ResultsInspectionService extends InspectionService { + readonly fields: Field[] = [ + { + key: 'name' + }, + { + key: 'sessionId', + link: 'sessions' + }, + { + key: 'ownerTaskId', + link: 'tasks' + }, + { + key: 'createdAt', + type: 'date' + }, + { + key: 'completedAt', + type: 'date' + }, + { + key: 'size' + } + ]; +} \ No newline at end of file diff --git a/src/app/results/show.component.html b/src/app/results/show.component.html new file mode 100644 index 000000000..b8f459d4a --- /dev/null +++ b/src/app/results/show.component.html @@ -0,0 +1,16 @@ + +
+ + Result +
+
+ + +
+
\ No newline at end of file diff --git a/src/app/results/show.component.spec.ts b/src/app/results/show.component.spec.ts index f2594b812..a7db6076a 100644 --- a/src/app/results/show.component.spec.ts +++ b/src/app/results/show.component.spec.ts @@ -1,12 +1,14 @@ -import { GetResultResponse } from '@aneoconsultingfr/armonik.api.angular'; +import { GetResultResponse, ResultStatus } from '@aneoconsultingfr/armonik.api.angular'; import { TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; +import { GrpcStatusEvent } from '@ngx-grpc/common'; import { BehaviorSubject, Observable, of, throwError } from 'rxjs'; import { FiltersService } from '@services/filters.service'; import { IconsService } from '@services/icons.service'; import { NotificationService } from '@services/notification.service'; import { ShareUrlService } from '@services/share-url.service'; import { ResultsGrpcService } from './services/results-grpc.service'; +import { ResultsInspectionService } from './services/results-inspection.service'; import { ResultsStatusesService } from './services/results-statuses.service'; import { ShowComponent } from './show.component'; import { ResultRaw } from './types'; @@ -34,31 +36,28 @@ describe('ShowComponent', () => { id: 'resultId-12345', options: { partitionId: 'partitionId' - } + }, + status: ResultStatus.RESULT_STATUS_CREATED } as unknown as ResultRaw; const mockResultsGrpcService = { get$: jest.fn((): Observable => of({result: returnedResult} as GetResultResponse)), }; - const mockResultsStatusesService = { - statuses: [] - }; - beforeEach(() => { component = TestBed.configureTestingModule({ providers: [ ShowComponent, IconsService, FiltersService, - { provide: ResultsStatusesService, useValue: mockResultsStatusesService}, + ResultsStatusesService, { provide: NotificationService, useValue: mockNotificationService }, { provide: ShareUrlService, useValue: mockShareUrlService }, { provide: ActivatedRoute, useValue: mockActivatedRoute }, - { provide: ResultsGrpcService, useValue: mockResultsGrpcService } + { provide: ResultsGrpcService, useValue: mockResultsGrpcService }, + ResultsInspectionService, ] }).inject(ShowComponent); component.ngOnInit(); - component.ngAfterViewInit(); }); it('should create', () => { @@ -73,6 +72,23 @@ describe('ShowComponent', () => { it('should set sharableURL', () => { expect(mockShareUrlService.generateSharableURL).toHaveBeenCalled(); }); + + it('should set fields', () => { + expect(component.fields).toEqual((new ResultsInspectionService).fields); + }); + }); + + describe('get status', () => { + it('should return the status label if there is data', () => { + component.refresh.next(); + expect(component.status).toEqual('Created'); + }); + + it('should return undefined if there is no data', () => { + mockResultsGrpcService.get$.mockReturnValueOnce(of(null)); + component.refresh.next(); + expect(component.status).toEqual(undefined); + }); }); it('should get icons', () => { @@ -85,11 +101,6 @@ describe('ShowComponent', () => { expect(spy).toHaveBeenCalled(); }); - it('should set link for action', () => { - component.setLink('session', 'sessions', 'sessionId-12345'); - expect(component.actionButtons[0].link).toEqual('/sessions/sessionId-12345'); - }); - describe('Getting data', () => { it('should fetch data on refresh', () => { component.refresh.next(); @@ -101,19 +112,6 @@ describe('ShowComponent', () => { expect(component.data()).toEqual(returnedResult); }); - it(('should set link if sessionId is not the same as ownerTaskId'), () => { - const spy = jest.spyOn(component, 'setLink'); - mockResultsGrpcService.get$.mockImplementationOnce(() => of({result: {...returnedResult, sessionId: 'sessionId', ownerTaskId: 'ownerTaskId'}})); - component.refresh.next(); - expect(spy).toHaveBeenCalledWith('task', 'tasks', 'ownerTaskId'); - }); - - it('should remove an action if sessionId is the same as ownerTaskId', () => { - mockResultsGrpcService.get$.mockImplementationOnce(() => of({result: {...returnedResult, sessionId: 'sessionId', ownerTaskId: 'sessionId'}})); - component.refresh.next(); - expect(component.actionButtons.find(action => action.id === 'task')).toBeUndefined(); - }); - it('should not update data if there is none', () => { mockResultsGrpcService.get$.mockImplementationOnce(() => of({})); component.refresh.next(); @@ -132,12 +130,14 @@ describe('ShowComponent', () => { describe('Handle errors', () => { it('should log errors', () => { const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); - component.handleError(new Error()); + const errorMessage = 'ErrorMessage'; + component.handleError({statusMessage: errorMessage} as GrpcStatusEvent); expect(errorSpy).toHaveBeenCalled(); }); it('should notify the error', () => { - component.handleError(new Error()); + const errorMessage = 'ErrorMessage'; + component.handleError({statusMessage: errorMessage} as GrpcStatusEvent); expect(mockNotificationService.error).toHaveBeenCalledWith('Could not retrieve data.'); }); }); @@ -157,6 +157,6 @@ describe('ShowComponent', () => { }); it('should get statuses', () => { - expect(component.statuses).toEqual(mockResultsStatusesService.statuses); + expect(component.statuses).toEqual((new ResultsStatusesService).statuses); }); }); \ No newline at end of file diff --git a/src/app/results/show.component.ts b/src/app/results/show.component.ts index a30c28297..cfdc61014 100644 --- a/src/app/results/show.component.ts +++ b/src/app/results/show.component.ts @@ -1,8 +1,10 @@ -import { GetResultResponse } from '@aneoconsultingfr/armonik.api.angular'; -import { AfterViewInit, ChangeDetectionStrategy, Component, OnDestroy, OnInit, inject } from '@angular/core'; +import { GetResultResponse, ResultStatus } from '@aneoconsultingfr/armonik.api.angular'; +import { ChangeDetectionStrategy, Component, OnDestroy, OnInit, inject } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatSnackBar } from '@angular/material/snack-bar'; -import { AppShowComponent, ShowActionButton, ShowActionInterface } from '@app/types/components/show'; +import { RouterModule } from '@angular/router'; +import { AppShowComponent } from '@app/types/components/show'; import { ShowPageComponent } from '@components/show-page.component'; import { NotificationService } from '@services/notification.service'; import { QueryParamsService } from '@services/query-params.service'; @@ -13,19 +15,14 @@ import { TableService } from '@services/table.service'; import { UtilsService } from '@services/utils.service'; import { ResultsFiltersService } from './services/results-filters.service'; import { ResultsGrpcService } from './services/results-grpc.service'; +import { ResultsInspectionService } from './services/results-inspection.service'; import { ResultsStatusesService } from './services/results-statuses.service'; import { ResultRaw } from './types'; @Component({ selector: 'app-result-show', - template: ` - - - Result - - `, - styles: [` - `], + templateUrl: 'show.component.html', + styleUrl: '../../inspections.css', standalone: true, providers: [ UtilsService, @@ -38,42 +35,39 @@ import { ResultRaw } from './types'; TableURLService, ResultsFiltersService, NotificationService, - MatSnackBar + MatSnackBar, + ResultsInspectionService, ], imports: [ ShowPageComponent, MatIconModule, + MatButtonModule, + RouterModule, ], changeDetection: ChangeDetectionStrategy.OnPush }) -export class ShowComponent extends AppShowComponent implements OnInit, AfterViewInit, ShowActionInterface, OnDestroy { - +export class ShowComponent extends AppShowComponent implements OnInit, OnDestroy { readonly grpcService = inject(ResultsGrpcService); + readonly inspectionService = inject(ResultsInspectionService); + private readonly resultsStatusesService = inject(ResultsStatusesService); - actionButtons: ShowActionButton[] = [ - { - id: 'session', - name: $localize`See session`, - icon: this.getIcon('sessions'), - link: '/sessions' - }, - { - id: 'task', - name: $localize`See owner task`, - icon: this.getIcon('tasks'), - link: '/tasks' - } - ]; + private _status: string | undefined; - ngOnInit(): void { - this.getIdByRoute(); - this.sharableURL = this.getSharableUrl(); + set status(status: ResultStatus | undefined) { + this._status = status ? this.statuses[status] : undefined; + } + + get status(): string | undefined { + return this._status; + } + + get statuses() { + return this.resultsStatusesService.statuses; } - ngAfterViewInit(): void { - this.subscribeToData(); - this.refresh.next(); + ngOnInit(): void { + this.initInspection(); } ngOnDestroy() { @@ -85,25 +79,6 @@ export class ShowComponent extends AppShowComponent element.id !== 'task'); - } else { - this.setLink('task', 'tasks', data.ownerTaskId); - } - } - } - - get statuses() { - return this.resultsStatusesService.statuses; - } - - setLink(actionId: string, baseLink: string, id: string) { - const index = this.actionButtons.findIndex(element => element.id === actionId); - if (index !== -1) { - this.actionButtons[index].link = `/${baseLink}/${id}`; - } + this.status = this.data()?.status; } } diff --git a/src/app/services/cache.service.spec.ts b/src/app/services/cache.service.spec.ts index a768ee451..9e5f2ea73 100644 --- a/src/app/services/cache.service.spec.ts +++ b/src/app/services/cache.service.spec.ts @@ -1,5 +1,3 @@ -import { TaskStatus } from '@aneoconsultingfr/armonik.api.angular'; -import { StatusCount } from '@app/tasks/types'; import { Scope } from '@app/types/config'; import { GrpcResponse } from '@app/types/data'; import { CacheService } from './cache.service'; @@ -13,18 +11,6 @@ describe('CacheService', () => { data: ['someData'] } as unknown as GrpcResponse; - const id = 'dataId'; - const statusesCount: StatusCount[] = [ - { - count: 10, - status: TaskStatus.TASK_STATUS_COMPLETED - }, - { - count: 9, - status: TaskStatus.TASK_STATUS_ERROR - } - ]; - it('should create', () => { expect(service).toBeTruthy(); }); @@ -37,13 +23,4 @@ describe('CacheService', () => { it('should retrieve the stored data of a scope', () => { expect(service.get(scope)).toBe(tableData); }); - - it('should save the statusesCount of an id', () => { - service.saveStatuses(id, statusesCount); - expect(service['tasksStatusesCache'].get(id)).toBe(statusesCount); - }); - - it('should retrieve the stored statusesCount of an id', () => { - expect(service.getStatuses(id)).toBe(statusesCount); - }); }); \ No newline at end of file diff --git a/src/app/services/cache.service.ts b/src/app/services/cache.service.ts index 2426daaf9..6e7e7bfd8 100644 --- a/src/app/services/cache.service.ts +++ b/src/app/services/cache.service.ts @@ -1,12 +1,10 @@ import { Injectable } from '@angular/core'; -import { StatusCount } from '@app/tasks/types'; import { Scope } from '@app/types/config'; import { GrpcResponse } from '@app/types/data'; @Injectable() export class CacheService { private tableCache: Map = new Map(); - private tasksStatusesCache: Map = new Map(); save(scope: Scope, data: GrpcResponse) { this.tableCache.set(scope, data); @@ -15,14 +13,4 @@ export class CacheService { get(scope: Scope) { return this.tableCache.get(scope); } - - saveStatuses(id: string, statusCount: StatusCount[] | undefined) { - if (statusCount) { - this.tasksStatusesCache.set(id, statusCount); - } - } - - getStatuses(id: string) { - return this.tasksStatusesCache.get(id); - } } \ No newline at end of file diff --git a/src/app/services/default-config-service.spec.ts b/src/app/services/default-config-service.spec.ts index 7aa318e63..7f68b14f1 100644 --- a/src/app/services/default-config-service.spec.ts +++ b/src/app/services/default-config-service.spec.ts @@ -1,4 +1,4 @@ -import { TasksStatusesGroup } from '@app/dashboard/types'; +import { CountLine, TasksStatusesGroup } from '@app/dashboard/types'; import { DefaultConfigService } from './default-config.service'; window = Object.create(window); @@ -94,12 +94,12 @@ describe('DefaultConfigService', () => { it('the line by default should contain 3 task statuses', () => { const defaultDashboardLines = service.defaultDashboardLines; - const taskStatuses = defaultDashboardLines.map( line => line.taskStatusesGroups); + const taskStatuses = defaultDashboardLines.map(line => (line as CountLine).taskStatusesGroups); expect(taskStatuses[0]).toHaveLength(3); }); it('the line by default should have finished, running, error, task statuses by default ', () => { const defaultDashboardLines = service.defaultDashboardLines; - const taskStatuses = defaultDashboardLines.map( line => line.taskStatusesGroups) as unknown as TasksStatusesGroup[][]; + const taskStatuses = defaultDashboardLines.map( line => (line as CountLine).taskStatusesGroups) as unknown as TasksStatusesGroup[][]; expect(taskStatuses[0].map(taskStatus => taskStatus.name === 'Finished')).toBeTruthy(); expect(taskStatuses[0].map(taskStatus => taskStatus.name === 'Running')).toBeTruthy(); expect(taskStatuses[0].map(taskStatus => taskStatus.name === 'Errors')).toBeTruthy(); diff --git a/src/app/services/default-config.service.ts b/src/app/services/default-config.service.ts index c816d299c..b208c0d10 100644 --- a/src/app/services/default-config.service.ts +++ b/src/app/services/default-config.service.ts @@ -1,11 +1,11 @@ -import { TaskStatus } from '@aneoconsultingfr/armonik.api.angular'; +import { ApplicationRawEnumField, PartitionRawEnumField, ResultRawEnumField, SessionRawEnumField, TaskOptionEnumField, TaskStatus, TaskSummaryEnumField } from '@aneoconsultingfr/armonik.api.angular'; import { Injectable } from '@angular/core'; -import { ApplicationRawColumnKey, ApplicationRawFilters, ApplicationRawListOptions } from '@app/applications/types'; -import { Line, TasksStatusesGroup } from '@app/dashboard/types'; -import { PartitionRawColumnKey, PartitionRawFilters, PartitionRawListOptions } from '@app/partitions/types'; -import { ResultRawColumnKey, ResultRawFilters, ResultRawListOptions } from '@app/results/types'; -import { SessionRawColumnKey, SessionRawFilters, SessionRawListOptions } from '@app/sessions/types'; -import { TaskSummaryColumnKey, TaskSummaryFilters, TaskSummaryListOptions } from '@app/tasks/types'; +import { ApplicationRaw } from '@app/applications/types'; +import { CountLine, Line, TasksStatusesGroup } from '@app/dashboard/types'; +import { PartitionRaw } from '@app/partitions/types'; +import { ResultRaw } from '@app/results/types'; +import { SessionRaw } from '@app/sessions/types'; +import { TaskOptions, TaskSummary } from '@app/tasks/types'; import { ExportedDefaultConfig, ScopeConfig } from '@app/types/config'; import { ExternalService } from '@app/types/external-service'; import { Sidebar } from '@app/types/navigation'; @@ -16,7 +16,7 @@ export class DefaultConfigService { readonly #defaultTheme: Theme = 'indigo-pink'; readonly #defaultExternalServices: ExternalService[] = []; - readonly #defaultDashboardLines: Line[] = [ + readonly #defaultDashboardLines: CountLine[] = [ { name: $localize`Tasks by statuses`, type: 'CountStatus', @@ -69,7 +69,7 @@ export class DefaultConfigService { 'divider' ]; - readonly #defaultApplications: ScopeConfig = { + readonly #defaultApplications: ScopeConfig = { interval: 10, lockColumns: false, columns: [ @@ -112,7 +112,7 @@ export class DefaultConfigService { } ]; - readonly #defaultPartitions: ScopeConfig = { + readonly #defaultPartitions: ScopeConfig = { interval: 10, lockColumns: false, columns: [ @@ -131,7 +131,7 @@ export class DefaultConfigService { showFilters: true, }; - readonly #defaultSessions: ScopeConfig = { + readonly #defaultSessions: ScopeConfig = { interval: 10, lockColumns: false, columns: [ @@ -151,7 +151,7 @@ export class DefaultConfigService { showFilters: true, }; - readonly #defaultResults: ScopeConfig = { + readonly #defaultResults: ScopeConfig = { interval: 10, lockColumns: false, columns: [ @@ -170,7 +170,7 @@ export class DefaultConfigService { showFilters: true, }; - readonly #defaultTasks: ScopeConfig = { + readonly #defaultTasks: ScopeConfig = { interval: 10, lockColumns: false, columns: [ @@ -226,7 +226,7 @@ export class DefaultConfigService { return structuredClone(this.#defaultSidebar); } - get defaultApplications(): ScopeConfig { + get defaultApplications(): ScopeConfig { return structuredClone(this.#defaultApplications); } @@ -234,19 +234,19 @@ export class DefaultConfigService { return structuredClone(this.#defaultTasksByStatus); } - get defaultPartitions(): ScopeConfig { + get defaultPartitions(): ScopeConfig { return structuredClone(this.#defaultPartitions); } - get defaultSessions(): ScopeConfig { + get defaultSessions(): ScopeConfig { return structuredClone(this.#defaultSessions); } - get defaultResults(): ScopeConfig { + get defaultResults(): ScopeConfig { return structuredClone(this.#defaultResults); } - get defaultTasks(): ScopeConfig { + get defaultTasks(): ScopeConfig { return structuredClone(this.#defaultTasks); } diff --git a/src/app/services/filter.service.spec.ts b/src/app/services/filter.service.spec.ts index 8d14137cd..13a2edb24 100644 --- a/src/app/services/filter.service.spec.ts +++ b/src/app/services/filter.service.spec.ts @@ -98,14 +98,8 @@ describe('FiltersService', () => { describe('createFilterPartitionQueryParams', () => { it('should return the correct query params', () => { - const actionsButton: ShowActionButton = { - id: 'partitions', - name: 'Partitions', - link: '/partitions', - }; const partitionIds = ['partition1', 'partition2', 'partition3']; - service.createFilterPartitionQueryParams([actionsButton], partitionIds); - expect(actionsButton.queryParams).toEqual({ + expect(service.createFilterPartitionQueryParams(partitionIds)).toEqual({ '0-root-1-0': 'partition1', '1-root-1-0': 'partition2', '2-root-1-0': 'partition3', diff --git a/src/app/services/filters.service.ts b/src/app/services/filters.service.ts index 7e1d5866d..98e11d23b 100644 --- a/src/app/services/filters.service.ts +++ b/src/app/services/filters.service.ts @@ -1,5 +1,6 @@ import { FilterArrayOperator, FilterBooleanOperator, FilterDateOperator, FilterDurationOperator, FilterNumberOperator, FilterStatusOperator, FilterStringOperator, PartitionRawEnumField } from '@aneoconsultingfr/armonik.api.angular'; import { Injectable } from '@angular/core'; +import { Params } from '@angular/router'; import { ShowActionButton } from '@app/types/components/show'; import { FilterOperators, FilterType } from '@app/types/filters'; @@ -82,15 +83,12 @@ export class FiltersService { } } - createFilterPartitionQueryParams(actionButtons: ShowActionButton[], partitionIds: string[]) { - const action = actionButtons.find(element => element.id === 'partitions'); - if (action) { - const params: {[key: string]: string} = {}; - partitionIds.forEach((partitionId, index) => { - const keyPartition = this.createQueryParamsKey(index, 'root' , FilterStringOperator.FILTER_STRING_OPERATOR_EQUAL, PartitionRawEnumField.PARTITION_RAW_ENUM_FIELD_ID); - params[keyPartition] = partitionId; - }); - action.queryParams = params; - } + createFilterPartitionQueryParams(partitionIds: string[]): Params { + const params: Params = {}; + partitionIds.forEach((partitionId, index) => { + const keyPartition = this.createQueryParamsKey(index, 'root' , FilterStringOperator.FILTER_STRING_OPERATOR_EQUAL, PartitionRawEnumField.PARTITION_RAW_ENUM_FIELD_ID); + params[keyPartition] = partitionId; + }); + return params; } } diff --git a/src/app/services/grpc-build-request.service.ts b/src/app/services/grpc-build-request.service.ts index 38bcee54b..2662b50b1 100644 --- a/src/app/services/grpc-build-request.service.ts +++ b/src/app/services/grpc-build-request.service.ts @@ -1,7 +1,6 @@ import { ApplicationFilterField, SortDirection as ArmoniKSortDirection, FilterArrayOperator, FilterBooleanOperator, FilterDateOperator, FilterNumberOperator, FilterStatusOperator, FilterStringOperator, PartitionFilterField, ResultFilterField, SessionFilterField, TaskFilterField } from '@aneoconsultingfr/armonik.api.angular'; import { SortDirection } from '@angular/material/sort'; -import { FiltersEnums, FiltersOptionsEnums } from '@app/dashboard/types'; -import { Filter } from '@app/types/filters'; +import { Filter, FiltersEnums, FiltersOptionsEnums } from '@app/types/filters'; export type FilterField = SessionFilterField.AsObject['field'] | TaskFilterField.AsObject['field'] | ApplicationFilterField.AsObject['field'] | PartitionFilterField.AsObject['field'] | ResultFilterField.AsObject['field']; diff --git a/src/app/services/query-params.service.spec.ts b/src/app/services/query-params.service.spec.ts index 338ceec9b..ed9f1de5e 100644 --- a/src/app/services/query-params.service.spec.ts +++ b/src/app/services/query-params.service.spec.ts @@ -1,5 +1,4 @@ import { ApplicationRaw } from '@aneoconsultingfr/armonik.api.angular'; -import { IndexListOptions } from '@app/types/data'; import { FiltersOr } from '@app/types/filters'; import { ListOptions } from '@app/types/options'; import { QueryParamsService } from './query-params.service'; @@ -31,7 +30,7 @@ describe('QueryParamsService', () => { sortField:'name,service', sortDirection:'asc' }; - expect(service.createOptions(options as IndexListOptions)).toEqual(optionsResult); + expect(service.createOptions(options)).toEqual(optionsResult); }); it('Should create Filters', () => { diff --git a/src/app/services/query-params.service.ts b/src/app/services/query-params.service.ts index eedb18231..9ec01a8c6 100644 --- a/src/app/services/query-params.service.ts +++ b/src/app/services/query-params.service.ts @@ -1,11 +1,14 @@ +import { TaskOptionEnumField } from '@aneoconsultingfr/armonik.api.angular'; import { Injectable } from '@angular/core'; -import { IndexListOptions } from '@app/types/data'; -import { MaybeNull, RawFilters } from '@app/types/filters'; +import { TaskOptions } from '@app/tasks/types'; +import { DataRaw } from '@app/types/data'; +import { FiltersEnums, FiltersOr, MaybeNull } from '@app/types/filters'; +import { ListOptions } from '@app/types/options'; import { QueryParamsOptions } from '@app/types/query-params'; @Injectable() export class QueryParamsService { - createOptions(options: T): QueryParamsOptions { + createOptions(options: ListOptions): QueryParamsOptions { const queryParamsOptions: QueryParamsOptions = { pageIndex: options.pageIndex.toString(), pageSize: options.pageSize.toString(), @@ -16,7 +19,7 @@ export class QueryParamsService { return queryParamsOptions; } - createFilters(filtersOr: F): Record> | null { + createFilters(filtersOr: FiltersOr): Record> | null { const queryParamsFilters: Record> = {}; let i = 0; diff --git a/src/app/services/share-url.service.spec.ts b/src/app/services/share-url.service.spec.ts index 0b8ca90fc..0fdf54694 100644 --- a/src/app/services/share-url.service.spec.ts +++ b/src/app/services/share-url.service.spec.ts @@ -1,6 +1,5 @@ import { ApplicationRaw } from '@aneoconsultingfr/armonik.api.angular'; import { TestBed } from '@angular/core/testing'; -import { IndexListOptions } from '@app/types/data'; import { FiltersOr } from '@app/types/filters'; import { ListOptions } from '@app/types/options'; import { QueryParamsService } from './query-params.service'; @@ -45,7 +44,7 @@ describe('Share Url service', () => { } }; const optionsResult = 'pageIndex=1&pageSize=10&sortField=name,service&sortDirection=asc'; - expect(service.generateSharableURL(options as IndexListOptions, null)) + expect(service.generateSharableURL(options as ListOptions, null)) .toBe(`${urlRoot}?${optionsResult}`); }); it('when field is descending', () => { @@ -58,7 +57,7 @@ describe('Share Url service', () => { } }; const optionsResult = 'pageIndex=1&pageSize=10&sortField=name,service&sortDirection=desc'; - expect(service.generateSharableURL(options as IndexListOptions, null)) + expect(service.generateSharableURL(options as ListOptions, null)) .toBe(`${urlRoot}?${optionsResult}`); }); it('when there is only one field', () => { @@ -71,7 +70,7 @@ describe('Share Url service', () => { } }; const optionsResult = 'pageIndex=1&pageSize=10&sortField=service&sortDirection=desc'; - expect(service.generateSharableURL(options as IndexListOptions, null)) + expect(service.generateSharableURL(options as ListOptions, null)) .toBe(`${urlRoot}?${optionsResult}`); }); it('when there is no field', () => { @@ -84,7 +83,7 @@ describe('Share Url service', () => { } }; const optionsResult = 'pageIndex=1&pageSize=10&sortDirection=desc'; - expect(service.generateSharableURL(options as IndexListOptions, null)) + expect(service.generateSharableURL(options as ListOptions, null)) .toBe(`${urlRoot}?${optionsResult}`); }); }); @@ -188,7 +187,7 @@ describe('Share Url service', () => { }]]; const optionsResult = 'pageIndex=1&pageSize=10&sortDirection=desc'; const filterResult = '0-root-2-1=filterValue'; - expect(service.generateSharableURL(options as IndexListOptions, filters)) + expect(service.generateSharableURL(options as ListOptions, filters)) .toBe(`${urlRoot}?${optionsResult}&${filterResult}`); }); }); diff --git a/src/app/services/share-url.service.ts b/src/app/services/share-url.service.ts index 6fa67b23a..3462140fb 100644 --- a/src/app/services/share-url.service.ts +++ b/src/app/services/share-url.service.ts @@ -1,6 +1,9 @@ +import { TaskOptionEnumField } from '@aneoconsultingfr/armonik.api.angular'; import { Injectable, inject } from '@angular/core'; -import { IndexListOptions } from '@app/types/data'; -import { RawFilters } from '@app/types/filters'; +import { TaskOptions } from '@app/tasks/types'; +import { DataRaw } from '@app/types/data'; +import { FiltersEnums, FiltersOr } from '@app/types/filters'; +import { ListOptions } from '@app/types/options'; import { QueryParamsService } from './query-params.service'; @Injectable() @@ -8,7 +11,7 @@ export class ShareUrlService { #window = inject(Window); #queryParamsService = inject(QueryParamsService); - generateSharableURL(options: T | null, filters: F | null): string { + generateSharableURL(options: ListOptions | null, filters: FiltersOr | null): string { const origin = this.#window.location.origin; const pathname = this.#window.location.pathname; @@ -16,11 +19,11 @@ export class ShareUrlService { return `${origin}${pathname}`; } - const queryParamsOptions = options ? this.#queryParamsService.createOptions(options) : null; - const queryParamsFilters = filters ? this.#queryParamsService.createFilters(filters) : null; + const queryParamsOptions = options ? this.#queryParamsService.createOptions(options) : null; + const queryParamsFilters = filters ? this.#queryParamsService.createFilters(filters) : null; let queryParams = [this.#stringify(queryParamsOptions), this.#stringify(queryParamsFilters)].join('&'); - + if (queryParams.at(0) === '&') queryParams = queryParams.substring(1); if (queryParams.at(-1) === '&') queryParams = queryParams.slice(0, -1); diff --git a/src/app/services/table.service.ts b/src/app/services/table.service.ts index 21cbe9927..3881c68f7 100644 --- a/src/app/services/table.service.ts +++ b/src/app/services/table.service.ts @@ -1,5 +1,6 @@ import { Injectable } from '@angular/core'; import { SortDirection } from '@angular/material/sort'; +import { TaskOptions } from '@app/tasks/types'; import { CustomScope, Scope } from '@app/types/config'; import { DataRaw, FieldKey } from '@app/types/data'; import { FilterDefinition } from '@app/types/filter-definition'; @@ -50,7 +51,7 @@ export class TableService { /** * Restore options from the storage */ - restoreOptions(key: `${Scope}-options`, defaultOptions: ListOptions): ListOptions { + restoreOptions(key: `${Scope}-options`, defaultOptions: ListOptions): ListOptions { const storageData = this._tableStorageService.restore(key) as ListOptions | null; function convertValueToNumber(value: string | null): number | null { @@ -67,11 +68,11 @@ export class TableService { return numberValue; } - const options: ListOptions = { + const options: ListOptions = { pageIndex: convertValueToNumber(this._tableURLService.getQueryParamsOptions('pageIndex')) ?? storageData?.pageIndex ?? defaultOptions?.pageIndex, pageSize: convertValueToNumber(this._tableURLService.getQueryParamsOptions('pageSize')) ?? storageData?.pageSize ?? defaultOptions?.pageSize, sort: { - active: this._tableURLService.getQueryParamsOptions>('sortField') ?? storageData?.sort.active ?? defaultOptions?.sort.active, + active: this._tableURLService.getQueryParamsOptions>('sortField') ?? storageData?.sort.active ?? defaultOptions?.sort.active, direction: this._tableURLService.getQueryParamsOptions('sortDirection') ?? storageData?.sort.direction ?? defaultOptions?.sort.direction, }, }; diff --git a/src/app/services/utils.service.ts b/src/app/services/utils.service.ts index 2012a26f4..7514600a8 100644 --- a/src/app/services/utils.service.ts +++ b/src/app/services/utils.service.ts @@ -1,7 +1,6 @@ import { Injectable } from '@angular/core'; -import { FiltersEnums, FiltersOptionsEnums } from '@app/dashboard/types'; import { FilterDefinition } from '@app/types/filter-definition'; -import { Filter, FilterType, FilterValueOptions } from '@app/types/filters'; +import { Filter, FilterType, FilterValueOptions, FiltersEnums, FiltersOptionsEnums } from '@app/types/filters'; @Injectable() export class UtilsService { diff --git a/src/app/sessions/components/table.component.html b/src/app/sessions/components/table.component.html index 3c8e2befd..75f90427a 100644 --- a/src/app/sessions/components/table.component.html +++ b/src/app/sessions/components/table.component.html @@ -1,4 +1,4 @@ - diff --git a/src/app/sessions/components/table.component.spec.ts b/src/app/sessions/components/table.component.spec.ts index 69469385e..911ab2567 100644 --- a/src/app/sessions/components/table.component.spec.ts +++ b/src/app/sessions/components/table.component.spec.ts @@ -1,4 +1,4 @@ -import { FilterDateOperator, FilterNumberOperator, FilterStatusOperator, FilterStringOperator, SessionRawEnumField, SessionStatus, SessionTaskOptionEnumField, TaskStatus, TaskSummaryEnumField } from '@aneoconsultingfr/armonik.api.angular'; +import { FilterDateOperator, FilterNumberOperator, FilterStatusOperator, FilterStringOperator, SessionRawEnumField, SessionStatus, SessionTaskOptionEnumField, TaskOptionEnumField, TaskStatus, TaskSummaryEnumField } from '@aneoconsultingfr/armonik.api.angular'; import { Clipboard } from '@angular/cdk/clipboard'; import { signal } from '@angular/core'; import { TestBed } from '@angular/core/testing'; @@ -7,8 +7,10 @@ import { Router } from '@angular/router'; import { Timestamp } from '@ngx-grpc/well-known-types'; import { BehaviorSubject, Observable, Subject, of, throwError } from 'rxjs'; import { ManageGroupsDialogResult, TasksStatusesGroup } from '@app/dashboard/types'; +import { TaskOptions } from '@app/tasks/types'; import { TableColumn } from '@app/types/column.type'; -import { SessionData } from '@app/types/data'; +import { ColumnKey, SessionData } from '@app/types/data'; +import { FiltersOr } from '@app/types/filters'; import { CacheService } from '@services/cache.service'; import { FiltersService } from '@services/filters.service'; import { IconsService } from '@services/icons.service'; @@ -18,12 +20,12 @@ import { SessionsTableComponent } from './table.component'; import { SessionsGrpcService } from '../services/sessions-grpc.service'; import { SessionsIndexService } from '../services/sessions-index.service'; import { SessionsStatusesService } from '../services/sessions-statuses.service'; -import { SessionRaw, SessionRawColumnKey, SessionRawFilters } from '../types'; +import { SessionRaw } from '../types'; describe('SessionsTableComponent', () => { let component: SessionsTableComponent; - const displayedColumns: TableColumn[] = [ + const displayedColumns: TableColumn[] = [ { name: 'Session ID', key: 'sessionId', @@ -173,7 +175,7 @@ describe('SessionsTableComponent', () => { }).inject(SessionsTableComponent); component.displayedColumns = displayedColumns; - component.filters$ = new BehaviorSubject([]); + component.filters$ = new BehaviorSubject>([]); component.options = { pageIndex: 0, pageSize: 10, @@ -526,7 +528,7 @@ describe('SessionsTableComponent', () => { describe('filterHadCreatedAt', () => { it('should return true if the filters contain a "createdAt" filter', () => { - const filters: SessionRawFilters = [[{ + const filters: FiltersOr = [[{ field: SessionRawEnumField.SESSION_RAW_ENUM_FIELD_CREATED_AT, for: 'root', operator: FilterDateOperator.FILTER_DATE_OPERATOR_AFTER_OR_EQUAL, @@ -685,11 +687,18 @@ describe('SessionsTableComponent', () => { expect(component.data()).toEqual([]); }); }); + + describe('options changes', () => { + it('should refresh data', () => { + const spy = jest.spyOn(component.refresh$, 'next'); + component.onOptionsChange(); + expect(spy).toHaveBeenCalled(); + }); - it('should refresh data on options changes', () => { - const spy = jest.spyOn(component.refresh$, 'next'); - component.onOptionsChange(); - expect(spy).toHaveBeenCalled(); + it('should save options', () => { + component.onOptionsChange(); + expect(mockSessionsIndexService.saveOptions).toHaveBeenCalled(); + }); }); describe('on Pause', () => { @@ -773,7 +782,7 @@ describe('SessionsTableComponent', () => { }); test('onDrop should call sessionsIndexService', () => { - const newColumns: SessionRawColumnKey[] = ['actions', 'sessionId', 'status']; + const newColumns: ColumnKey[] = ['actions', 'sessionId', 'status']; component.onDrop(newColumns); expect(mockSessionsIndexService.saveColumns).toHaveBeenCalledWith(newColumns); }); @@ -904,4 +913,9 @@ describe('SessionsTableComponent', () => { expect(component.isDataRawEqual(session1, session2)).toBeFalsy(); }); }); + + it('should track a session by its id', () => { + const session = {raw: { sessionId: 'session' }} as SessionData; + expect(component.trackBy(0, session)).toEqual(session.raw.sessionId); + }); }); \ No newline at end of file diff --git a/src/app/sessions/components/table.component.ts b/src/app/sessions/components/table.component.ts index b66c2ef01..9ed749273 100644 --- a/src/app/sessions/components/table.component.ts +++ b/src/app/sessions/components/table.component.ts @@ -1,4 +1,4 @@ -import { FilterDateOperator, FilterStringOperator, ListSessionsResponse, ResultRawEnumField, SessionRawEnumField, SessionTaskOptionEnumField, TaskOptionEnumField, TaskSummaryEnumField } from '@aneoconsultingfr/armonik.api.angular'; +import { FilterDateOperator, FilterStringOperator, ListSessionsResponse, ResultRawEnumField, SessionRawEnumField, TaskOptionEnumField, TaskSummaryEnumField } from '@aneoconsultingfr/armonik.api.angular'; import { Clipboard} from '@angular/cdk/clipboard'; import { AfterViewInit, Component, EventEmitter, OnInit, Output, inject } from '@angular/core'; import { MatDialog, MatDialogModule } from '@angular/material/dialog'; @@ -6,10 +6,10 @@ import { Params, Router, RouterModule } from '@angular/router'; import { Duration, Timestamp } from '@ngx-grpc/well-known-types'; import { Subject, map, mergeAll } from 'rxjs'; import { TasksGrpcService } from '@app/tasks/services/tasks-grpc.service'; -import { TaskSummaryFilters } from '@app/tasks/types'; +import { TaskOptions, TaskSummaryFilters } from '@app/tasks/types'; import { AbstractTableComponent, AbstractTaskByStatusTableComponent } from '@app/types/components/table'; import { Scope } from '@app/types/config'; -import { ColumnKey, SessionData } from '@app/types/data'; +import { ArmonikData, ColumnKey, SessionData } from '@app/types/data'; import { Filter } from '@app/types/filters'; import { ActionTable } from '@app/types/table'; import { TableComponent } from '@components/table/table.component'; @@ -21,7 +21,7 @@ import { TableTasksByStatus, TasksByStatusService } from '@services/tasks-by-sta import { SessionsGrpcService } from '../services/sessions-grpc.service'; import { SessionsIndexService } from '../services/sessions-index.service'; import { SessionsStatusesService } from '../services/sessions-statuses.service'; -import { SessionRaw, SessionRawColumnKey, SessionRawFieldKey, SessionRawFilters, SessionRawListOptions } from '../types'; +import { SessionRaw, SessionRawFilters, SessionRawListOptions } from '../types'; @Component({ selector: 'app-sessions-table', @@ -43,8 +43,8 @@ import { SessionRaw, SessionRawColumnKey, SessionRawFieldKey, SessionRawFilters, MatDialogModule, ] }) -export class SessionsTableComponent extends AbstractTaskByStatusTableComponent - implements OnInit, AfterViewInit, AbstractTableComponent { +export class SessionsTableComponent extends AbstractTaskByStatusTableComponent + implements OnInit, AfterViewInit, AbstractTableComponent { @Output() cancelSession = new EventEmitter(); @Output() closeSession = new EventEmitter(); @Output() deleteSession = new EventEmitter(); @@ -68,31 +68,31 @@ export class SessionsTableComponent extends AbstractTaskByStatusTableComponent(); sessionsIdsComputationError: string[] = []; - copy$ = new Subject(); + copy$ = new Subject>(); copySubscription = this.copy$.subscribe(data => this.onCopiedSessionId(data)); - seeSessions$ = new Subject(); + seeSessions$ = new Subject>(); seeSessionsSubscription = this.seeSessions$.subscribe(data => this.router.navigate(['/sessions', data.raw.sessionId])); - seeResults$ = new Subject(); - seeResultsSubscription = this.seeResults$.subscribe(data => this.router.navigate(['/results'], { queryParams: data.resultsQueryParams })); + seeResults$ = new Subject>(); + seeResultsSubscription = this.seeResults$.subscribe(data => this.router.navigate(['/results'], { queryParams: (data as SessionData).resultsQueryParams })); - pauseSession$ = new Subject(); + pauseSession$ = new Subject>(); pauseSessionSubscription = this.pauseSession$.subscribe(data => this.onPause(data.raw.sessionId)); - resumeSession$ = new Subject(); + resumeSession$ = new Subject>(); resumeSessionSubscription = this.resumeSession$.subscribe(data => this.onResume(data.raw.sessionId)); - cancelSession$ = new Subject(); + cancelSession$ = new Subject>(); cancelSessionSubscription = this.cancelSession$.subscribe(data => this.onCancel(data.raw.sessionId)); - closeSession$ = new Subject(); + closeSession$ = new Subject>(); closeSessionSubscription = this.closeSession$.subscribe(data => this.onClose(data.raw.sessionId)); - deleteSession$ = new Subject(); + deleteSession$ = new Subject>(); deleteSessionSubscription = this.deleteSession$.subscribe(data => this.onDelete(data.raw.sessionId)); - actions: ActionTable[] = [ + actions: ActionTable[] = [ { label: 'Copy session ID', icon: 'copy', @@ -112,31 +112,31 @@ export class SessionsTableComponent extends AbstractTaskByStatusTableComponent this.statusesService.canPause(element.raw.status) + condition: (element: ArmonikData) => this.statusesService.canPause(element.raw.status) }, { label: 'Resume session', icon: this.getIcon('play'), action$: this.resumeSession$, - condition: (element: SessionData) => this.statusesService.canResume(element.raw.status) + condition: (element: ArmonikData) => this.statusesService.canResume(element.raw.status) }, { label: 'Cancel session', icon: this.getIcon('cancel'), action$: this.cancelSession$, - condition: (element: SessionData) => this.statusesService.canCancel(element.raw.status) + condition: (element: ArmonikData) => this.statusesService.canCancel(element.raw.status) }, { label: 'Close session', icon: 'close', action$: this.closeSession$, - condition: (element: SessionData) => this.statusesService.canClose(element.raw.status) + condition: (element: ArmonikData) => this.statusesService.canClose(element.raw.status) }, { label: 'Delete session', icon: 'delete', action$: this.deleteSession$, - condition: (element: SessionData) => this.statusesService.canDelete(element.raw.status) + condition: (element: ArmonikData) => this.statusesService.canDelete(element.raw.status) } ]; @@ -244,7 +244,7 @@ export class SessionsTableComponent extends AbstractTaskByStatusTableComponent) { this.copyService.copy(data.raw.sessionId); this.notificationService.success('Session ID copied to clipboard'); } @@ -411,4 +411,8 @@ export class SessionsTableComponent extends AbstractTaskByStatusTableComponent) { + return item.raw.sessionId; + } } diff --git a/src/app/sessions/index.component.spec.ts b/src/app/sessions/index.component.spec.ts index 649322685..ca67bd5a1 100644 --- a/src/app/sessions/index.component.spec.ts +++ b/src/app/sessions/index.component.spec.ts @@ -1,11 +1,15 @@ -import { FilterStringOperator, SessionRawEnumField } from '@aneoconsultingfr/armonik.api.angular'; +import { FilterStringOperator, SessionRawEnumField, TaskOptionEnumField } from '@aneoconsultingfr/armonik.api.angular'; import { TestBed } from '@angular/core/testing'; import { MatDialog } from '@angular/material/dialog'; import { Router } from '@angular/router'; import { of } from 'rxjs'; import { DashboardIndexService } from '@app/dashboard/services/dashboard-index.service'; +import { TableLine } from '@app/dashboard/types'; +import { TaskOptions } from '@app/tasks/types'; import { TableColumn } from '@app/types/column.type'; -import { CustomColumn } from '@app/types/data'; +import { ColumnKey, CustomColumn } from '@app/types/data'; +import { FiltersOr } from '@app/types/filters'; +import { ListOptions } from '@app/types/options'; import { AutoRefreshService } from '@services/auto-refresh.service'; import { IconsService } from '@services/icons.service'; import { NotificationService } from '@services/notification.service'; @@ -14,7 +18,7 @@ import { IndexComponent } from './index.component'; import { SessionsFiltersService } from './services/sessions-filters.service'; import { SessionsGrpcService } from './services/sessions-grpc.service'; import { SessionsIndexService } from './services/sessions-index.service'; -import { SessionRawColumnKey, SessionRawFilters, SessionRawListOptions } from './types'; +import { SessionRaw } from './types'; describe('Sessions Index Component', () => { let component: IndexComponent; @@ -41,9 +45,9 @@ describe('Sessions Index Component', () => { navigate: jest.fn() }; - const defaultColumns: SessionRawColumnKey[] = ['sessionId', 'actions', 'createdAt', 'options']; + const defaultColumns: ColumnKey[] = ['sessionId', 'actions', 'createdAt', 'options']; const defaultCustomColumns: CustomColumn[] = ['options.options.FastCompute']; - const defaultOptions: SessionRawListOptions = { + const defaultOptions: ListOptions = { pageIndex: 0, pageSize: 10, sort: { @@ -51,7 +55,7 @@ describe('Sessions Index Component', () => { direction: 'desc' } }; - const availableTableColumns: TableColumn[] = [ + const availableTableColumns: TableColumn[] = [ { name: $localize`Session ID`, key: 'sessionId', @@ -271,7 +275,7 @@ describe('Sessions Index Component', () => { }); describe('On columns change', () => { - const newColumns: SessionRawColumnKey[] = ['sessionId', 'createdAt']; + const newColumns: ColumnKey[] = ['sessionId', 'createdAt']; beforeEach(() => { component.onColumnsChange(newColumns); }); @@ -345,7 +349,7 @@ describe('Sessions Index Component', () => { describe('On Filters Change', () => { - const newFilters: SessionRawFilters = [ + const newFilters: FiltersOr = [ [ { field: SessionRawEnumField.SESSION_RAW_ENUM_FIELD_SESSION_ID, @@ -438,12 +442,14 @@ describe('Sessions Index Component', () => { describe('Adding table as a line to dashboard', () => { it('should add a line', () => { component.onAddToDashboard(); - expect(mockDashboardIndexService.addLine).toHaveBeenCalledWith({ + expect(mockDashboardIndexService.addLine).toHaveBeenCalledWith[]>({ name: 'Sessions', type: 'Sessions', interval: 10, + showFilters: false, lockColumns: false, displayedColumns: [...defaultColumns, ...defaultCustomColumns], + customColumns: defaultCustomColumns, options: defaultOptions, filters: [], }); diff --git a/src/app/sessions/index.component.ts b/src/app/sessions/index.component.ts index b3627d403..a58556866 100644 --- a/src/app/sessions/index.component.ts +++ b/src/app/sessions/index.component.ts @@ -1,4 +1,4 @@ -import { SessionRawEnumField } from '@aneoconsultingfr/armonik.api.angular'; +import { SessionRawEnumField, TaskOptionEnumField } from '@aneoconsultingfr/armonik.api.angular'; import { AfterViewInit, Component, OnDestroy, OnInit, inject } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; @@ -11,6 +11,7 @@ import { DashboardStorageService } from '@app/dashboard/services/dashboard-stora import { TasksFiltersService } from '@app/tasks/services/tasks-filters.service'; import { TasksIndexService } from '@app/tasks/services/tasks-index.service'; import { TasksStatusesService } from '@app/tasks/services/tasks-statuses.service'; +import { TaskOptions } from '@app/tasks/types'; import { DATA_FILTERS_SERVICE } from '@app/tokens/filters.token'; import { TableHandlerCustomValues } from '@app/types/components'; import { TableType } from '@app/types/table'; @@ -30,7 +31,7 @@ import { SessionsTableComponent } from './components/table.component'; import { SessionsFiltersService } from './services/sessions-filters.service'; import { SessionsIndexService } from './services/sessions-index.service'; import { SessionsStatusesService } from './services/sessions-statuses.service'; -import { SessionRawColumnKey, SessionRawFilters, SessionRawListOptions } from './types'; +import { SessionRaw } from './types'; @Component({ selector: 'app-sessions-index', @@ -72,7 +73,7 @@ import { SessionRawColumnKey, SessionRawFilters, SessionRawListOptions } from '. SessionsTableComponent ] }) -export class IndexComponent extends TableHandlerCustomValues implements OnInit, AfterViewInit, OnDestroy { +export class IndexComponent extends TableHandlerCustomValues implements OnInit, AfterViewInit, OnDestroy { readonly filtersService = inject(SessionsFiltersService); readonly indexService = inject(SessionsIndexService); diff --git a/src/app/sessions/services/sessions-filters.service.ts b/src/app/sessions/services/sessions-filters.service.ts index 4d87d94bb..ff65c514d 100644 --- a/src/app/sessions/services/sessions-filters.service.ts +++ b/src/app/sessions/services/sessions-filters.service.ts @@ -1,4 +1,4 @@ -import { SessionRawEnumField, SessionStatus, SessionTaskOptionEnumField } from '@aneoconsultingfr/armonik.api.angular'; +import { SessionRawEnumField, SessionStatus, SessionTaskOptionEnumField, TaskOptionEnumField } from '@aneoconsultingfr/armonik.api.angular'; import { Injectable, inject } from '@angular/core'; import { FiltersServiceOptionsInterface, FiltersServiceStatusesInterface } from '@app/types/services/filtersService'; import { DefaultConfigService } from '@services/default-config.service'; @@ -9,7 +9,7 @@ import { SessionFilterDefinition, SessionFilterField, SessionFilterFor, SessionR @Injectable({ providedIn: 'root', }) -export class SessionsFiltersService implements FiltersServiceOptionsInterface, FiltersServiceStatusesInterface { +export class SessionsFiltersService implements FiltersServiceOptionsInterface, FiltersServiceStatusesInterface { readonly statusService = inject(SessionsStatusesService); readonly defaultConfigService = inject(DefaultConfigService); readonly tableService = inject(TableService); @@ -30,7 +30,7 @@ export class SessionsFiltersService implements FiltersServiceOptionsInterface = { + readonly optionsFields: Record = { [SessionTaskOptionEnumField.TASK_OPTION_ENUM_FIELD_UNSPECIFIED]: $localize`Unspecified`, [SessionTaskOptionEnumField.TASK_OPTION_ENUM_FIELD_MAX_DURATION]: $localize`Max Duration`, [SessionTaskOptionEnumField.TASK_OPTION_ENUM_FIELD_MAX_RETRIES]: $localize`Max Retries`, @@ -171,7 +171,7 @@ export class SessionsFiltersService implements FiltersServiceOptionsInterface value.toLowerCase() === filterField.toLowerCase()); return { for: 'options', index: index }; } diff --git a/src/app/sessions/services/sessions-grpc.service.spec.ts b/src/app/sessions/services/sessions-grpc.service.spec.ts index d494db9e9..52913ae0e 100644 --- a/src/app/sessions/services/sessions-grpc.service.spec.ts +++ b/src/app/sessions/services/sessions-grpc.service.spec.ts @@ -1,14 +1,17 @@ -import { CancelSessionRequest, FilterArrayOperator, FilterBooleanOperator, FilterDateOperator, FilterNumberOperator, FilterStatusOperator, FilterStringOperator, GetSessionRequest, ListSessionsRequest, PauseSessionRequest, SessionRawEnumField, SessionStatus, SessionTaskOptionEnumField, SessionsClient, SortDirection } from '@aneoconsultingfr/armonik.api.angular'; +import { CancelSessionRequest, FilterArrayOperator, FilterBooleanOperator, FilterDateOperator, FilterNumberOperator, FilterStatusOperator, FilterStringOperator, GetSessionRequest, ListSessionsRequest, PauseSessionRequest, SessionRawEnumField, SessionStatus, SessionTaskOptionEnumField, SessionsClient, SortDirection, TaskOptionEnumField } from '@aneoconsultingfr/armonik.api.angular'; import { TestBed } from '@angular/core/testing'; import { lastValueFrom, of } from 'rxjs'; import { TasksGrpcService } from '@app/tasks/services/tasks-grpc.service'; -import { ListOptionsSort } from '@app/types/options'; +import { TaskOptions } from '@app/tasks/types'; +import { FieldKey } from '@app/types/data'; +import { FiltersOr } from '@app/types/filters'; +import { ListOptions } from '@app/types/options'; import { GrpcSortFieldService } from '@services/grpc-sort-field.service'; import { UtilsService } from '@services/utils.service'; import { SessionsFiltersService } from './sessions-filters.service'; import { SessionsGrpcService } from './sessions-grpc.service'; import { SessionsStatusesService } from './sessions-statuses.service'; -import { SessionFilterDefinition, SessionRaw, SessionRawFilters, SessionRawListOptions } from '../types'; +import { SessionFilterDefinition, SessionRaw } from '../types'; describe('SessionsGrpcService', () => { let service: SessionsGrpcService; @@ -79,7 +82,7 @@ describe('SessionsGrpcService', () => { deleteSession: jest.fn(), }; - const listOptions: SessionRawListOptions = { + const listOptions: ListOptions = { pageIndex: 0, pageSize: 10, sort: { @@ -88,7 +91,7 @@ describe('SessionsGrpcService', () => { } }; - const listFilters: SessionRawFilters = [ + const listFilters: FiltersOr = [ [ { field: 'options.options.FastCompute', // Custom type string @@ -260,13 +263,13 @@ describe('SessionsGrpcService', () => { }); it('should list by a default sort direction', () => { - const options: SessionRawListOptions = { + const options: ListOptions = { pageIndex: 0, pageSize: 10, sort: { - active: null, + active: null as unknown as FieldKey, direction: 'desc' - } as unknown as ListOptionsSort + } }; service.list$(options, []); expect(mockSessionsGrpcClient.listSessions).toHaveBeenCalledWith(new ListSessionsRequest({ @@ -293,7 +296,7 @@ describe('SessionsGrpcService', () => { }); it('should not allow some filters', () => { - const filters: SessionRawFilters = [ + const filters: FiltersOr = [ [ { for: 'root', diff --git a/src/app/sessions/services/sessions-grpc.service.ts b/src/app/sessions/services/sessions-grpc.service.ts index 27c0f4586..0e89a5f93 100644 --- a/src/app/sessions/services/sessions-grpc.service.ts +++ b/src/app/sessions/services/sessions-grpc.service.ts @@ -1,17 +1,18 @@ -import { CancelSessionRequest, CancelSessionResponse, CloseSessionRequest, CloseSessionResponse, DeleteSessionRequest, DeleteSessionResponse, FilterStringOperator, GetSessionRequest, GetSessionResponse, ListSessionsRequest, ListSessionsResponse, PauseSessionRequest, PauseSessionResponse, ResumeSessionRequest, ResumeSessionResponse, SessionField, SessionFilterField, SessionRawEnumField, SessionTaskOptionEnumField, SessionsClient, TaskSummaryEnumField } from '@aneoconsultingfr/armonik.api.angular'; +import { CancelSessionRequest, CancelSessionResponse, CloseSessionRequest, CloseSessionResponse, DeleteSessionRequest, DeleteSessionResponse, FilterStringOperator, GetSessionRequest, GetSessionResponse, ListSessionsRequest, ListSessionsResponse, PauseSessionRequest, PauseSessionResponse, ResumeSessionRequest, ResumeSessionResponse, SessionField, SessionFilterField, SessionRawEnumField, SessionTaskOptionEnumField, SessionsClient, TaskOptionEnumField, TaskSummaryEnumField } from '@aneoconsultingfr/armonik.api.angular'; import { Injectable, inject } from '@angular/core'; import { SortDirection } from '@angular/material/sort'; import { Observable, map } from 'rxjs'; import { TasksGrpcService } from '@app/tasks/services/tasks-grpc.service'; +import { TaskOptions } from '@app/tasks/types'; import { Filter, FilterType } from '@app/types/filters'; import { GrpcCancelInterface, GrpcGetInterface, GrpcTableService, ListDefaultSortField } from '@app/types/services/grpcService'; import { FilterField, buildArrayFilter, buildBooleanFilter, buildDateFilter, buildNumberFilter, buildStatusFilter, buildStringFilter } from '@services/grpc-build-request.service'; import { GrpcSortFieldService } from '@services/grpc-sort-field.service'; import { SessionsFiltersService } from './sessions-filters.service'; -import { SessionRawFieldKey, SessionRawFilters, SessionRawListOptions } from '../types'; +import { SessionRaw, SessionRawFieldKey, SessionRawFilters, SessionRawListOptions } from '../types'; @Injectable() -export class SessionsGrpcService extends GrpcTableService +export class SessionsGrpcService extends GrpcTableService implements GrpcGetInterface, GrpcCancelInterface { readonly filterService = inject(SessionsFiltersService); readonly grpcClient = inject(SessionsClient); diff --git a/src/app/sessions/services/sessions-index.service.ts b/src/app/sessions/services/sessions-index.service.ts index caecfb1f4..563d13b3f 100644 --- a/src/app/sessions/services/sessions-index.service.ts +++ b/src/app/sessions/services/sessions-index.service.ts @@ -8,7 +8,7 @@ import { TableService } from '@services/table.service'; import { SessionRaw, SessionRawColumnKey, SessionRawListOptions } from '../types'; @Injectable() -export class SessionsIndexService implements IndexServiceCustomInterface { +export class SessionsIndexService implements IndexServiceCustomInterface { defaultConfigService = inject(DefaultConfigService); tableService = inject(TableService); @@ -17,7 +17,7 @@ export class SessionsIndexService implements IndexServiceCustomInterface[] = [ + readonly availableTableColumns: TableColumn[] = [ { name: $localize`Session ID`, key: 'sessionId', @@ -191,7 +191,7 @@ export class SessionsIndexService implements IndexServiceCustomInterface('sessions-options', this.defaultOptions); + const options = this.tableService.restoreOptions('sessions-options', this.defaultOptions); return options; } diff --git a/src/app/sessions/services/sessions-inspection.service.spec.ts b/src/app/sessions/services/sessions-inspection.service.spec.ts new file mode 100644 index 000000000..25fe1b902 --- /dev/null +++ b/src/app/sessions/services/sessions-inspection.service.spec.ts @@ -0,0 +1,17 @@ +import { SessionsInspectionService } from './sessions-inspection.service'; + +describe('SessionsInspectionService', () => { + const service = new SessionsInspectionService(); + + it('should run', () => { + expect(service).toBeTruthy(); + }); + + it('should have a defined "fields"', () => { + expect(service.fields).toBeDefined(); + }); + + it('should have a defined "arrays"', () => { + expect(service.arrays).toBeDefined(); + }); +}); \ No newline at end of file diff --git a/src/app/sessions/services/sessions-inspection.service.ts b/src/app/sessions/services/sessions-inspection.service.ts new file mode 100644 index 000000000..5a8219552 --- /dev/null +++ b/src/app/sessions/services/sessions-inspection.service.ts @@ -0,0 +1,46 @@ +import { Field } from '@app/types/column.type'; +import { InspectionService } from '@app/types/services/inspectionService'; +import { SessionRaw } from '../types'; + +export class SessionsInspectionService extends InspectionService { + readonly fields: Field[] = [ + { + key: 'duration', + type: 'duration' + }, + { + key: 'clientSubmission', + }, + { + key: 'workerSubmission' + }, + { + key: 'createdAt', + type: 'date', + }, + { + key: 'cancelledAt', + type: 'date', + }, + { + key: 'purgedAt', + type: 'date', + }, + { + key: 'closedAt', + type: 'date' + }, + { + key: 'deletedAt', + type: 'date' + } + ]; + + readonly arrays: Field[] = [ + { + key: 'partitionIds', + link: 'partitions', + queryParams: '0-root-1-0' + } + ]; +} \ No newline at end of file diff --git a/src/app/sessions/show.component.html b/src/app/sessions/show.component.html new file mode 100644 index 000000000..5963e16ff --- /dev/null +++ b/src/app/sessions/show.component.html @@ -0,0 +1,45 @@ + +
+ + Sessions +
+
+ + + +
+
+ @if (!disableResume) { + + } @else { + + } + + + +
+
\ No newline at end of file diff --git a/src/app/sessions/show.component.spec.ts b/src/app/sessions/show.component.spec.ts index 66a761f22..67c114d11 100644 --- a/src/app/sessions/show.component.spec.ts +++ b/src/app/sessions/show.component.spec.ts @@ -1,13 +1,16 @@ import { GetSessionResponse, SessionStatus } from '@aneoconsultingfr/armonik.api.angular'; import { TestBed } from '@angular/core/testing'; import { ActivatedRoute, Router } from '@angular/router'; +import { GrpcStatusEvent } from '@ngx-grpc/common'; import { Timestamp } from '@ngx-grpc/well-known-types'; -import { BehaviorSubject, Observable, lastValueFrom, of, throwError } from 'rxjs'; +import { BehaviorSubject, Observable, of, throwError } from 'rxjs'; +import { TasksInspectionService } from '@app/tasks/services/tasks-inspection.service'; import { FiltersService } from '@services/filters.service'; import { IconsService } from '@services/icons.service'; import { NotificationService } from '@services/notification.service'; import { ShareUrlService } from '@services/share-url.service'; import { SessionsGrpcService } from './services/sessions-grpc.service'; +import { SessionsInspectionService } from './services/sessions-inspection.service'; import { SessionsStatusesService } from './services/sessions-statuses.service'; import { ShowComponent } from './show.component'; import { SessionRaw } from './types'; @@ -15,8 +18,6 @@ import { SessionRaw } from './types'; describe('AppShowComponent', () => { let component: ShowComponent; - const sessionStatusesService = new SessionsStatusesService(); - const mockNotificationService = { success: jest.fn(), error: jest.fn(), @@ -56,7 +57,6 @@ describe('AppShowComponent', () => { } as Timestamp }; - const mockSessionsGrpcService = { get$: jest.fn((): Observable => of({session: returnedSession} as GetSessionResponse)), cancel$: jest.fn(() => of({})), @@ -82,12 +82,12 @@ describe('AppShowComponent', () => { { provide: ShareUrlService, useValue: mockShareUrlService }, { provide: ActivatedRoute, useValue: mockActivatedRoute }, { provide: SessionsGrpcService, useValue: mockSessionsGrpcService }, - { provide: Router, useValue: mockRouter } - + { provide: Router, useValue: mockRouter }, + SessionsInspectionService, + TasksInspectionService, ] }).inject(ShowComponent); component.ngOnInit(); - component.ngAfterViewInit(); }); it('should create', () => { @@ -102,6 +102,26 @@ describe('AppShowComponent', () => { it('should set sharableURL', () => { expect(mockShareUrlService.generateSharableURL).toHaveBeenCalled(); }); + + it('should set fields', () => { + expect(component.fields).toEqual((new SessionsInspectionService).fields); + }); + + it('should set optionsFields', () => { + expect(component.optionsFields).toEqual((new TasksInspectionService).optionsFields); + }); + + it('should set arrays', () => { + expect(component.arrays).toEqual((new SessionsInspectionService).arrays); + }); + + it('should set tasksKey', () => { + expect(component.tasksKey).toEqual('0-root-1-0'); + }); + + it('should set resultsKey', () => { + expect(component.resultsKey).toEqual('0-root-1-0'); + }); }); it('should get icons', () => { @@ -125,6 +145,18 @@ describe('AppShowComponent', () => { expect(component.data()).toEqual(returnedSession); }); + it('should set taskQueryParams', () => { + component.tasksQueryParams = {}; + component.refresh.next(); + expect(component.tasksQueryParams).toEqual({'0-root-1-0': returnedSession.sessionId}); + }); + + it('should set resultsQueryParams', () => { + component.resultsQueryParams = {}; + component.refresh.next(); + expect(component.resultsQueryParams).toEqual({'0-root-1-0': returnedSession.sessionId}); + }); + it('should not update data if there is none', () => { mockSessionsGrpcService.get$.mockImplementationOnce(() => of({})); component.refresh.next(); @@ -138,31 +170,6 @@ describe('AppShowComponent', () => { component.refresh.next(); expect(spy).toHaveBeenCalled(); }); - - it('should update query params for partitions', () => { - component.refresh.next(); - expect(component.actionButtons.find(button => button.id === 'partitions')?.queryParams) - .toEqual({ - '0-root-1-0': 'partitionId1', - '1-root-1-0': 'partitionId2' - }); - }); - - it('should update filter params for results', () => { - component.refresh.next(); - expect(component.actionButtons.find(button => button.id === 'results')?.queryParams) - .toEqual({ - '0-root-1-0': returnedSession.sessionId - }); - }); - - it('should update filter params for tasks', () => { - component.refresh.next(); - expect(component.actionButtons.find(button => button.id === 'tasks')?.queryParams) - .toEqual({ - '0-root-1-0': returnedSession.sessionId - }); - }); }); describe('Computing duration', () => { @@ -192,12 +199,14 @@ describe('AppShowComponent', () => { describe('Handle errors', () => { it('should log errors', () => { const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); - component.handleError(new Error()); + const errorMessage = 'ErrorMessage'; + component.handleError({statusMessage: errorMessage} as GrpcStatusEvent); expect(errorSpy).toHaveBeenCalled(); }); it('should notify the error', () => { - component.handleError(new Error()); + const errorMessage = 'ErrorMessage'; + component.handleError({statusMessage: errorMessage} as GrpcStatusEvent); expect(mockNotificationService.error).toHaveBeenCalledWith('Could not retrieve data.'); }); }); @@ -216,156 +225,162 @@ describe('AppShowComponent', () => { }); }); - describe('Cancel session', () => { - it('should cancel sessions', () => { - component.cancel(); - expect(mockSessionsGrpcService.cancel$).toHaveBeenCalledWith(returnedSession.sessionId); - }); - - it('should notify on success', () => { - component.cancel(); - expect(mockNotificationService.success).toHaveBeenCalledWith('Session canceled'); - }); - - it('should refresh on success', () => { - const spy = jest.spyOn(component.refresh, 'next'); - component.cancel(); - expect(spy).toHaveBeenCalled(); + describe('get status', () => { + it('should return the status label if there is data', () => { + component.refresh.next(); + expect(component.status).toEqual('Running'); }); - it('should log errors', () => { - const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); - mockSessionsGrpcService.cancel$.mockReturnValueOnce(throwError(() => new Error())); - component.cancel(); - expect(errorSpy).toHaveBeenCalled(); + it('should return undefined if there is no data', () => { + mockSessionsGrpcService.get$.mockReturnValueOnce(of(null)); + component.refresh.next(); + expect(component.status).toEqual(undefined); }); }); - it('should get statuses', () => { - expect(component.statuses).toEqual(sessionStatusesService.statuses); - }); - it('should get resultKeys', () => { - expect(component.resultsKey()).toEqual('0-root-1-0'); + expect(component.resultsKey).toEqual('0-root-1-0'); }); - - describe('cancel action', () => { + describe('Cancelling', () => { beforeAll(() => { component.refresh.next(); // setting up the RUNNING status }); it('should permit to cancel a session', () => { - lastValueFrom(component.actionButtons.find(button => button.id === 'cancel')?.disabled as Observable) - .then(disabled => expect(disabled).toBeFalsy()); + expect(component.disableCancel).toBeFalsy(); + }); + + it('should call grpc service "cancel$" method', () => { + component.cancel(); + expect(mockSessionsGrpcService.cancel$).toHaveBeenCalled(); }); it('should notify on success when cancelling a session', () => { - component.actionButtons.find(button => button.id === 'cancel')?.action$?.next(); + component.cancel(); expect(mockNotificationService.success).toHaveBeenCalledWith('Session canceled'); }); it('should notify on errors when cancelling a session', () => { jest.spyOn(console, 'error').mockImplementation(() => {}); mockSessionsGrpcService.cancel$.mockReturnValueOnce(throwError(() => new Error())); - component.actionButtons.find(button => button.id === 'cancel')?.action$?.next(); + component.cancel(); expect(mockNotificationService.error).toHaveBeenCalled(); }); }); - describe('Pause action', () => { + describe('Pausing', () => { beforeAll(() => { component.refresh.next(); // setting up the RUNNING status }); - it('should permit to cancel a session', () => { - lastValueFrom(component.actionButtons.find(button => button.id === 'pause')?.disabled as Observable) - .then(disabled => expect(disabled).toBeFalsy()); + it('should permit to pause a session', () => { + expect(component.disablePause).toBeFalsy(); + }); + + it('should call the grpc service "pause$" method', () => { + component.pause(); + expect(mockSessionsGrpcService.pause$).toHaveBeenCalled(); }); it('should notify on success when pausing a session', () => { - component.actionButtons.find(button => button.id === 'pause')?.action$?.next(); + component.pause(); expect(mockNotificationService.success).toHaveBeenCalledWith('Session paused'); }); it('should notify on errors when pausing a session', () => { jest.spyOn(console, 'error').mockImplementation(() => {}); mockSessionsGrpcService.pause$.mockReturnValueOnce(throwError(() => new Error())); - component.actionButtons.find(button => button.id === 'pause')?.action$?.next(); + component.pause(); expect(mockNotificationService.error).toHaveBeenCalled(); }); }); - describe('Resume action', () => { - beforeAll(() => { - component.refresh.next(); // setting up the RUNNING status + describe('Resuming', () => { + beforeEach(() => { + const pausedSession = { + sessionId: 'pausedSession', + partitionIds: ['partitionId1', 'partitionId2'], + status: SessionStatus.SESSION_STATUS_PAUSED + } as SessionRaw; + mockSessionsGrpcService.get$.mockReturnValueOnce(of(pausedSession)); + component.data.set(pausedSession); // setting up the PAUSE status + component.afterDataFetching(); }); - it('should permit to cancel a session', () => { - lastValueFrom(component.actionButtons.find(button => button.id === 'resume')?.disabled as Observable) - .then(disabled => expect(disabled).toBeTruthy()); + it('should permit to resume a session', () => { + expect(component.disableResume).toBeFalsy(); + }); + + it('should call the grpc service "resume$" method', () => { + component.resume(); + expect(mockSessionsGrpcService.resume$).toHaveBeenCalled(); }); it('should notify on success when resuming a session', () => { - component.actionButtons.find(button => button.id === 'resume')?.action$?.next(); + component.resume(); expect(mockNotificationService.success).toHaveBeenCalledWith('Session resumed'); }); it('should notify on errors when resuming a session', () => { jest.spyOn(console, 'error').mockImplementation(() => {}); mockSessionsGrpcService.resume$.mockReturnValueOnce(throwError(() => new Error())); - component.actionButtons.find(button => button.id === 'resume')?.action$?.next(); + component.resume(); expect(mockNotificationService.error).toHaveBeenCalled(); }); }); - describe('Close action', () => { + describe('Closing', () => { beforeAll(() => { component.refresh.next(); // setting up the RUNNING status }); - it('should permit to cancel a session', () => { - lastValueFrom(component.actionButtons.find(button => button.id === 'close')?.disabled as Observable) - .then(disabled => expect(disabled).toBeFalsy()); + it('should permit to close a session', () => { + expect(component.disableClose).toBeFalsy(); + }); + + it('should call the grpc service "close$" method', () => { + component.close(); + expect(mockSessionsGrpcService.close$).toHaveBeenCalled(); }); it('should notify on success when closing a session', () => { - component.actionButtons.find(button => button.id === 'close')?.action$?.next(); + component.close(); expect(mockNotificationService.success).toHaveBeenCalledWith('Session closed'); }); it('should notify on errors when closing a session', () => { jest.spyOn(console, 'error').mockImplementation(() => {}); mockSessionsGrpcService.close$.mockReturnValueOnce(throwError(() => new Error())); - component.actionButtons.find(button => button.id === 'close')?.action$?.next(); + component.close(); expect(mockNotificationService.error).toHaveBeenCalled(); }); }); - describe('Delete action', () => { + describe('Deleting', () => { beforeAll(() => { component.refresh.next(); // setting up the RUNNING status }); - it('should permit to cancel a session', () => { - lastValueFrom(component.actionButtons.find(button => button.id === 'delete')?.disabled as Observable) - .then(disabled => expect(disabled).toBeFalsy()); + it('should call the grpc service "delete$" method', () => { + component.deleteSession(); + expect(mockSessionsGrpcService.delete$).toHaveBeenCalled(); }); it('should notify on success when deleting a session', () => { - component.actionButtons.find(button => button.id === 'delete')?.action$?.next(); + component.deleteSession(); expect(mockNotificationService.success).toHaveBeenCalledWith('Session deleted'); }); it('should navigate to sessions list page after succesfully deleting a session', () => { - component.actionButtons.find(button => button.id === 'delete')?.action$?.next(); + component.deleteSession(); expect(mockRouter.navigate).toHaveBeenCalledWith(['/sessions']); }); it('should notify on errors when deleting a session', () => { jest.spyOn(console, 'error').mockImplementation(() => {}); mockSessionsGrpcService.delete$.mockReturnValueOnce(throwError(() => new Error())); - component.actionButtons.find(button => button.id === 'delete')?.action$?.next(); + component.deleteSession(); expect(mockNotificationService.error).toHaveBeenCalled(); }); }); diff --git a/src/app/sessions/show.component.ts b/src/app/sessions/show.component.ts index 9c713325b..7f69d49a0 100644 --- a/src/app/sessions/show.component.ts +++ b/src/app/sessions/show.component.ts @@ -1,14 +1,17 @@ -import { FilterStringOperator, GetSessionResponse, ResultRawEnumField, TaskSummaryEnumField } from '@aneoconsultingfr/armonik.api.angular'; -import { AfterViewInit, ChangeDetectionStrategy, Component, OnDestroy, OnInit, inject } from '@angular/core'; +import { FilterStringOperator, GetSessionResponse, ResultRawEnumField, SessionStatus, TaskSummaryEnumField } from '@aneoconsultingfr/armonik.api.angular'; +import { ChangeDetectionStrategy, Component, OnDestroy, OnInit, inject } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; -import { MatSnackBar } from '@angular/material/snack-bar'; -import { Router } from '@angular/router'; +import { Params, Router, RouterModule } from '@angular/router'; import { Timestamp } from '@ngx-grpc/well-known-types'; import { Subject, map, switchMap } from 'rxjs'; import { TasksFiltersService } from '@app/tasks/services/tasks-filters.service'; import { TasksGrpcService } from '@app/tasks/services/tasks-grpc.service'; +import { TasksInspectionService } from '@app/tasks/services/tasks-inspection.service'; import { TasksStatusesService } from '@app/tasks/services/tasks-statuses.service'; -import { AppShowComponent, ShowActionButton, ShowActionInterface, ShowCancellableInterface, ShowClosableInterface } from '@app/types/components/show'; +import { TaskOptions } from '@app/tasks/types'; +import { Field } from '@app/types/column.type'; +import { AppShowComponent } from '@app/types/components/show'; import { ShowPageComponent } from '@components/show-page.component'; import { FiltersService } from '@services/filters.service'; import { GrpcSortFieldService } from '@services/grpc-sort-field.service'; @@ -22,19 +25,14 @@ import { UtilsService } from '@services/utils.service'; import { SessionsFiltersService } from './services/sessions-filters.service'; import { SessionsGrpcService } from './services/sessions-grpc.service'; import { SessionsIndexService } from './services/sessions-index.service'; +import { SessionsInspectionService } from './services/sessions-inspection.service'; import { SessionsStatusesService } from './services/sessions-statuses.service'; import { SessionRaw } from './types'; @Component({ selector: 'app-sessions-show', - template: ` - - - Session - - `, - styles: [` - `], + templateUrl: 'show.component.html', + styleUrl: '../../inspections.css', standalone: true, providers: [ UtilsService, @@ -48,122 +46,71 @@ import { SessionRaw } from './types'; TableURLService, TableStorageService, NotificationService, - MatSnackBar, FiltersService, TasksGrpcService, TasksFiltersService, TasksStatusesService, GrpcSortFieldService, + SessionsInspectionService, + TasksInspectionService, ], imports: [ ShowPageComponent, MatIconModule, + MatButtonModule, + RouterModule ], changeDetection: ChangeDetectionStrategy.OnPush }) -export class ShowComponent extends AppShowComponent - implements OnInit, AfterViewInit, ShowActionInterface, ShowCancellableInterface, ShowClosableInterface, OnDestroy { - - cancel$ = new Subject(); - pause$ = new Subject(); - resume$ = new Subject(); - close$ = new Subject(); - delete$ = new Subject(); +export class ShowComponent extends AppShowComponent implements OnInit, OnDestroy { + lowerDate: Timestamp | undefined; + upperDate: Timestamp | undefined; lowerDuration$ = new Subject(); upperDuration$ = new Subject(); computeDuration$ = new Subject(); - lowerDate: Timestamp | undefined; - upperDate: Timestamp | undefined; + readonly grpcService = inject(SessionsGrpcService); + readonly inspectionService = inject(SessionsInspectionService); + readonly tasksInspectionService = inject(TasksInspectionService); private readonly sessionsStatusesService = inject(SessionsStatusesService); - readonly grpcService = inject(SessionsGrpcService); private readonly filtersService = inject(FiltersService); private readonly router = inject(Router); - canPause$ = new Subject(); - canResume$ = new Subject(); - canCancel$ = new Subject(); - canClose$ = new Subject(); + disablePause: boolean = false; + disableResume: boolean = false; + disableCancel: boolean = false; + disableClose: boolean = false; - actionButtons: ShowActionButton[] = [ - { - id: 'tasks', - name: $localize`See tasks`, - icon: this.getIcon('tasks'), - link: '/tasks', - queryParams: {}, - }, - { - id: 'results', - name: $localize`See results`, - icon: this.getIcon('results'), - link: '/results', - queryParams: {}, - }, - { - id: 'partitions', - name: $localize`See partitions`, - icon: this.getIcon('partitions'), - link: '/partitions', - queryParams: {}, - }, - { - id: 'pause', - name: $localize`Pause Session`, - icon: this.getIcon('pause'), - action$: this.pause$, - disabled: this.canPause$, - color: 'accent', - area: 'right' - }, - { - id: 'resume', - name: $localize`Resume Session`, - icon: this.getIcon('play'), - action$: this.resume$, - disabled: this.canResume$, - color: 'accent', - area: 'right' - }, - { - id: 'cancel', - name: $localize`Cancel Session`, - icon: this.getIcon('cancel'), - action$: this.cancel$, - disabled: this.canCancel$, - color: 'accent', - area: 'right' - }, - { - id: 'close', - name: $localize`Close Session`, - icon: this.getIcon('close'), - action$: this.close$, - disabled: this.canClose$, - color: 'accent', - area: 'right' - }, - { - id: 'delete', - name: $localize`Delete Session`, - icon: this.getIcon('delete'), - action$: this.delete$, - color: 'accent', - area: 'right' - } - ]; + tasksKey: string = ''; + tasksQueryParams: Params = {}; - ngOnInit(): void { - this.getIdByRoute(); - this.sharableURL = this.getSharableUrl(); + partitionsQueryParams: Params = {}; + + resultsKey: string = ''; + resultsQueryParams: Params = {}; + + optionsFields: Field[]; + + arrays: Field[]; + + private _status: string | undefined; + + get status(): string | undefined { + return this._status; + } + + set status(value: SessionStatus | undefined) { + this._status = value ? this.statuses[value] : undefined; } - ngAfterViewInit(): void { - this.subscribeToData(); + ngOnInit(): void { this.subscribeToDuration(); - this.subscribeToInteractions(); - this.refresh.next(); + this.initInspection(); + this.arrays = this.inspectionService.arrays; + this.optionsFields = this.tasksInspectionService.optionsFields; + this.resultsKey = this.filtersService.createQueryParamsKey(0, 'root', FilterStringOperator.FILTER_STRING_OPERATOR_EQUAL, ResultRawEnumField.RESULT_RAW_ENUM_FIELD_SESSION_ID); + this.tasksKey = this.filtersService.createQueryParamsKey(0, 'root', FilterStringOperator.FILTER_STRING_OPERATOR_EQUAL, TaskSummaryEnumField.TASK_SUMMARY_ENUM_FIELD_SESSION_ID); } ngOnDestroy(): void { @@ -176,16 +123,17 @@ export class ShowComponent extends AppShowComponent { @@ -219,7 +175,7 @@ export class ShowComponent extends AppShowComponent { - this.cancel(); - }); - - const pauseSubscription = this.pause$.subscribe(() => { - this.pause(); - }); - - const resumeSubscription = this.resume$.subscribe(() => { - this.resume(); - }); - - const deleteSubscription = this.delete$.subscribe(() => { - this.delete(); - }); - - const closeSubscription = this.close$.subscribe(() => { - this.close(); - }); - - this.subscriptions.add(cancelSubscription); - this.subscriptions.add(pauseSubscription); - this.subscriptions.add(resumeSubscription); - this.subscriptions.add(deleteSubscription); - this.subscriptions.add(closeSubscription); - } - cancel(): void { - const data = this.data(); + const data: SessionRaw | null = this.data(); if(data?.sessionId) { this.grpcService.cancel$(data.sessionId).subscribe({ complete: () => { @@ -273,7 +201,7 @@ export class ShowComponent extends AppShowComponent { @@ -289,7 +217,7 @@ export class ShowComponent extends AppShowComponent { @@ -305,7 +233,7 @@ export class ShowComponent extends AppShowComponent { @@ -320,8 +248,8 @@ export class ShowComponent extends AppShowComponent { @@ -335,12 +263,4 @@ export class ShowComponent extends AppShowComponent(0, 'root', FilterStringOperator.FILTER_STRING_OPERATOR_EQUAL, ResultRawEnumField.RESULT_RAW_ENUM_FIELD_SESSION_ID); - } - - tasksKey() { - return this.filtersService.createQueryParamsKey(0, 'root', FilterStringOperator.FILTER_STRING_OPERATOR_EQUAL, TaskSummaryEnumField.TASK_SUMMARY_ENUM_FIELD_SESSION_ID); - } } diff --git a/src/app/sessions/types.ts b/src/app/sessions/types.ts index 24399d969..0b44370ed 100644 --- a/src/app/sessions/types.ts +++ b/src/app/sessions/types.ts @@ -8,7 +8,7 @@ import { ListOptions } from '@app/types/options'; export type SessionRaw = GrpcSessionRaw.AsObject; export type SessionRawColumnKey = ColumnKey | 'count'| `options.options.${string}`; export type SessionRawFieldKey = FieldKey; -export type SessionRawListOptions = ListOptions; +export type SessionRawListOptions = ListOptions; export type SessionRawField = SessionRawEnumField | SessionTaskOptionEnumField | string; diff --git a/src/app/settings/index.component.ts b/src/app/settings/index.component.ts index c5b4e6d9b..a5bd121ac 100644 --- a/src/app/settings/index.component.ts +++ b/src/app/settings/index.component.ts @@ -19,7 +19,6 @@ import { IconsService } from '@services/icons.service'; import { NavigationService } from '@services/navigation.service'; import { NotificationService } from '@services/notification.service'; import { QueryParamsService } from '@services/query-params.service'; -import { ShareUrlService } from '@services/share-url.service'; import { StorageService } from '@services/storage.service'; import { ClearAllDialogComponent } from './component/clear-all-dialog.component'; @@ -128,7 +127,6 @@ main { `], standalone: true, providers: [ - ShareUrlService, QueryParamsService, NotificationService, ], @@ -148,7 +146,7 @@ main { ] }) export class IndexComponent implements OnInit { - sharableURL = ''; + sharableURL = null; fileName: string | undefined; keys: Set = new Set(); selectedKeys: Set = new Set(); @@ -157,15 +155,12 @@ export class IndexComponent implements OnInit { readonly dialog = inject(MatDialog); #iconsService = inject(IconsService); - #shareURLService = inject(ShareUrlService); #notificationService = inject(NotificationService); #navigationService = inject(NavigationService); #storageService = inject(StorageService); httpClient = inject(HttpClient); ngOnInit(): void { - this.sharableURL = this.#shareURLService.generateSharableURL(null, null); - this.keys = this.#sortKeys(this.#storageService.restoreKeys()); this.sidebar = this.#navigationService.restoreSidebar(); } diff --git a/src/app/tasks/components/table.component.html b/src/app/tasks/components/table.component.html index f4fa8e979..2682aeb88 100644 --- a/src/app/tasks/components/table.component.html +++ b/src/app/tasks/components/table.component.html @@ -1,4 +1,4 @@ - diff --git a/src/app/tasks/components/table.component.spec.ts b/src/app/tasks/components/table.component.spec.ts index 7e8febd48..19a94a3cf 100644 --- a/src/app/tasks/components/table.component.spec.ts +++ b/src/app/tasks/components/table.component.spec.ts @@ -1,10 +1,11 @@ -import { FilterStringOperator, TaskStatus, TaskSummaryEnumField } from '@aneoconsultingfr/armonik.api.angular'; +import { FilterStringOperator, TaskOptionEnumField, TaskStatus, TaskSummaryEnumField } from '@aneoconsultingfr/armonik.api.angular'; import { Clipboard } from '@angular/cdk/clipboard'; import { signal } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { BehaviorSubject, Subject, of, throwError } from 'rxjs'; import { TableColumn } from '@app/types/column.type'; -import { TaskData } from '@app/types/data'; +import { ArmonikData, ColumnKey, TaskData } from '@app/types/data'; +import { FiltersOr } from '@app/types/filters'; import { CacheService } from '@services/cache.service'; import { FiltersService } from '@services/filters.service'; import { NotificationService } from '@services/notification.service'; @@ -12,12 +13,12 @@ import { TasksTableComponent } from './table.component'; import { TasksGrpcService } from '../services/tasks-grpc.service'; import { TasksIndexService } from '../services/tasks-index.service'; import { TasksStatusesService } from '../services/tasks-statuses.service'; -import { TaskSummary, TaskSummaryColumnKey, TaskSummaryFilters } from '../types'; +import { TaskOptions, TaskSummary } from '../types'; describe('TasksTableComponent', () => { let component: TasksTableComponent; - const displayedColumns: TableColumn[] = [ + const displayedColumns: TableColumn[] = [ { name: 'Task ID', key: 'id', @@ -56,7 +57,8 @@ describe('TasksTableComponent', () => { isSimpleColumn: jest.fn(), isNotSortableColumn: jest.fn(), columnToLabel: jest.fn(), - saveColumns: jest.fn() + saveColumns: jest.fn(), + saveOptions: jest.fn(), }; const mockNotificationService = { @@ -96,7 +98,7 @@ describe('TasksTableComponent', () => { component.displayedColumns = displayedColumns; component.selection = []; - component.filters$ = new BehaviorSubject([]); + component.filters$ = new BehaviorSubject>([]); component.options = { pageIndex: 0, pageSize: 10, @@ -212,11 +214,18 @@ describe('TasksTableComponent', () => { expect(component.data()).toEqual([]); }); }); + + describe('options changes', () => { + it('should refresh data', () => { + const spy = jest.spyOn(component.refresh$, 'next'); + component.onOptionsChange(); + expect(spy).toHaveBeenCalled(); + }); - it('should refresh data on options changes', () => { - const spy = jest.spyOn(component.refresh$, 'next'); - component.onOptionsChange(); - expect(spy).toHaveBeenCalled(); + it('should save options', () => { + component.onOptionsChange(); + expect(mockTasksIndexService.saveOptions).toHaveBeenCalled(); + }); }); it('should check if the task is retried', () => { @@ -231,7 +240,7 @@ describe('TasksTableComponent', () => { raw: { id: 'taskId' } - } as unknown as TaskData); + } as ArmonikData); expect(mockClipBoard.copy).toHaveBeenCalledWith('taskId'); expect(mockNotificationService.success).toHaveBeenCalledWith('Task ID copied to clipboard'); }); @@ -269,7 +278,7 @@ describe('TasksTableComponent', () => { }); test('onDrop should call tasksIndexService', () => { - const newColumns: TaskSummaryColumnKey[] = ['actions', 'id', 'status']; + const newColumns: ColumnKey[] = ['actions', 'id', 'status']; component.onDrop(newColumns); expect(mockTasksIndexService.saveColumns).toHaveBeenCalledWith(newColumns); }); @@ -446,4 +455,9 @@ describe('TasksTableComponent', () => { expect(component.isDataRawEqual(task1, task2)).toBeFalsy(); }); }); + + it('should track a task by its id', () => { + const task = { raw: { id: 'task' } } as TaskData; + expect(component.trackBy(0, task)).toEqual(task.raw.id); + }); }); \ No newline at end of file diff --git a/src/app/tasks/components/table.component.ts b/src/app/tasks/components/table.component.ts index 6ac958387..81e46ab86 100644 --- a/src/app/tasks/components/table.component.ts +++ b/src/app/tasks/components/table.component.ts @@ -6,7 +6,7 @@ import { Router} from '@angular/router'; import { Subject } from 'rxjs'; import { AbstractTableComponent } from '@app/types/components/table'; import { Scope } from '@app/types/config'; -import { TaskData } from '@app/types/data'; +import { ArmonikData, TaskData } from '@app/types/data'; import { Filter } from '@app/types/filters'; import { ActionTable } from '@app/types/table'; import { TableComponent } from '@components/table/table.component'; @@ -15,7 +15,7 @@ import { GrpcSortFieldService } from '@services/grpc-sort-field.service'; import { TasksGrpcService } from '../services/tasks-grpc.service'; import { TasksIndexService } from '../services/tasks-index.service'; import { TasksStatusesService } from '../services/tasks-statuses.service'; -import { TaskSummary, TaskSummaryColumnKey, TaskSummaryFieldKey, TaskSummaryListOptions } from '../types'; +import { TaskOptions, TaskSummary } from '../types'; @Component({ selector: 'app-tasks-table', @@ -32,7 +32,7 @@ import { TaskSummary, TaskSummaryColumnKey, TaskSummaryFieldKey, TaskSummaryList TableComponent ] }) -export class TasksTableComponent extends AbstractTableComponent +export class TasksTableComponent extends AbstractTableComponent implements OnInit, AfterViewInit { scope: Scope = 'tasks'; @@ -83,22 +83,22 @@ export class TasksTableComponent extends AbstractTableComponent(); - copyS = this.copy$.subscribe((data) => this.onCopiedTaskId(data as TaskData)); + copy$ = new Subject>(); + copyS = this.copy$.subscribe((data) => this.onCopiedTaskId(data)); - seeResult$ = new Subject(); - resultSubscription = this.seeResult$.subscribe((data) => this.router.navigate(['/results'], { queryParams: data.resultsQueryParams })); + seeResult$ = new Subject>(); + resultSubscription = this.seeResult$.subscribe((data) => this.router.navigate(['/results'], { queryParams: (data as TaskData).resultsQueryParams })); - retries$ = new Subject(); + retries$ = new Subject>(); retriesSubscription = this.retries$.subscribe((data) => this.onRetries(data.raw)); - cancelTask$ = new Subject(); + cancelTask$ = new Subject>(); cancelTaskSubscription = this.cancelTask$.subscribe((data) => this.onCancelTask(data.raw.id)); - openViewInLogs$ = new Subject(); + openViewInLogs$ = new Subject>(); openViewInLogsSubscription = this.openViewInLogs$.subscribe((data) => window.open(this.generateViewInLogsUrl(data.raw.id), '_blank')); - actions: ActionTable[] = [ + actions: ActionTable[] = [ { label: $localize`Copy Task ID`, icon: 'copy', @@ -113,13 +113,13 @@ export class TasksTableComponent extends AbstractTableComponent this.isRetried(element.raw), + condition: (element: ArmonikData) => this.isRetried(element.raw), }, { label: $localize`Cancel task`, icon: 'cancel', action$: this.cancelTask$, - condition: (element: TaskData) => this.canCancelTask(element.raw), + condition: (element: ArmonikData) => this.canCancelTask(element.raw), }, ]; @@ -179,7 +179,7 @@ export class TasksTableComponent extends AbstractTableComponent) { this.clipboard.copy(element.raw.id); this.notificationService.success('Task ID copied to clipboard'); } @@ -229,4 +229,8 @@ export class TasksTableComponent extends AbstractTableComponent) { + return item.raw.id; + } } \ No newline at end of file diff --git a/src/app/tasks/index.component.spec.ts b/src/app/tasks/index.component.spec.ts index d4e704a85..70397727d 100644 --- a/src/app/tasks/index.component.spec.ts +++ b/src/app/tasks/index.component.spec.ts @@ -1,11 +1,14 @@ -import { FilterStringOperator, TaskSummaryEnumField } from '@aneoconsultingfr/armonik.api.angular'; +import { FilterStringOperator, TaskOptionEnumField, TaskSummaryEnumField } from '@aneoconsultingfr/armonik.api.angular'; import { TestBed } from '@angular/core/testing'; import { MatDialog } from '@angular/material/dialog'; import { Router } from '@angular/router'; import { of, throwError } from 'rxjs'; import { DashboardIndexService } from '@app/dashboard/services/dashboard-index.service'; +import { TableLine } from '@app/dashboard/types'; import { TableColumn } from '@app/types/column.type'; -import { CustomColumn } from '@app/types/data'; +import { ColumnKey, CustomColumn } from '@app/types/data'; +import { FiltersOr } from '@app/types/filters'; +import { ListOptions } from '@app/types/options'; import { AutoRefreshService } from '@services/auto-refresh.service'; import { IconsService } from '@services/icons.service'; import { NotificationService } from '@services/notification.service'; @@ -14,7 +17,7 @@ import { IndexComponent } from './index.component'; import { TasksFiltersService } from './services/tasks-filters.service'; import { TasksGrpcService } from './services/tasks-grpc.service'; import { TasksIndexService } from './services/tasks-index.service'; -import { TaskSummary, TaskSummaryColumnKey, TaskSummaryFilters, TaskSummaryListOptions } from './types'; +import { TaskOptions, TaskSummary } from './types'; describe('Tasks Index Component', () => { let component: IndexComponent; @@ -41,9 +44,9 @@ describe('Tasks Index Component', () => { navigate: jest.fn() }; - const defaultColumns: TaskSummaryColumnKey[] = ['id', 'actions', 'createdAt', 'options']; + const defaultColumns: ColumnKey[] = ['id', 'actions', 'createdAt', 'options']; const defaultCustomColumns: CustomColumn[] = ['options.options.FastCompute']; - const defaultOptions: TaskSummaryListOptions = { + const defaultOptions: ListOptions = { pageIndex: 0, pageSize: 10, sort: { @@ -51,7 +54,7 @@ describe('Tasks Index Component', () => { direction: 'desc' } }; - const availableTableColumns: TableColumn[] = [ + const availableTableColumns: TableColumn[] = [ { name: $localize`Task ID`, key: 'id', @@ -287,7 +290,7 @@ describe('Tasks Index Component', () => { }); describe('On columns change', () => { - const newColumns: TaskSummaryColumnKey[] = ['id', 'createdAt']; + const newColumns: ColumnKey[] = ['id', 'createdAt']; beforeEach(() => { component.onColumnsChange(newColumns); }); @@ -366,7 +369,7 @@ describe('Tasks Index Component', () => { describe('On Filters Change', () => { - const newFilters: TaskSummaryFilters = [ + const newFilters: FiltersOr = [ [ { field: TaskSummaryEnumField.TASK_SUMMARY_ENUM_FIELD_TASK_ID, @@ -459,12 +462,14 @@ describe('Tasks Index Component', () => { describe('Adding table as a line to dashboard', () => { it('should add a line', () => { component.onAddToDashboard(); - expect(mockDashboardIndexService.addLine).toHaveBeenCalledWith({ + expect(mockDashboardIndexService.addLine).toHaveBeenCalledWith[]>({ name: 'Tasks', type: 'Tasks', interval: 10, + showFilters: false, lockColumns: false, displayedColumns: [...defaultColumns, ...defaultCustomColumns], + customColumns: defaultCustomColumns, options: defaultOptions, filters: [], }); diff --git a/src/app/tasks/index.component.ts b/src/app/tasks/index.component.ts index affabed52..1b2df15da 100644 --- a/src/app/tasks/index.component.ts +++ b/src/app/tasks/index.component.ts @@ -1,4 +1,4 @@ -import { FilterStringOperator, TaskSummaryEnumField } from '@aneoconsultingfr/armonik.api.angular'; +import { FilterStringOperator, TaskOptionEnumField, TaskSummaryEnumField } from '@aneoconsultingfr/armonik.api.angular'; import { AfterViewInit, Component, OnDestroy, OnInit, inject } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; @@ -30,7 +30,7 @@ import { TasksFiltersService } from './services/tasks-filters.service'; import { TasksGrpcService } from './services/tasks-grpc.service'; import { TasksIndexService } from './services/tasks-index.service'; import { TasksStatusesService } from './services/tasks-statuses.service'; -import { TaskSummary, TaskSummaryColumnKey, TaskSummaryFilter, TaskSummaryFilters, TaskSummaryListOptions } from './types'; +import { TaskOptions, TaskSummary, TaskSummaryFilter } from './types'; @Component({ selector: 'app-tasks-index', @@ -71,7 +71,7 @@ import { TaskSummary, TaskSummaryColumnKey, TaskSummaryFilter, TaskSummaryFilter GrpcSortFieldService, ], }) -export class IndexComponent extends TableHandlerCustomValues implements OnInit, AfterViewInit, OnDestroy { +export class IndexComponent extends TableHandlerCustomValues implements OnInit, AfterViewInit, OnDestroy { readonly tasksGrpcService = inject(TasksGrpcService); readonly notificationService = inject(NotificationService); readonly indexService = inject(TasksIndexService); diff --git a/src/app/tasks/services/tasks-filters.service.spec.ts b/src/app/tasks/services/tasks-filters.service.spec.ts index 5622372a5..8f0601cfc 100644 --- a/src/app/tasks/services/tasks-filters.service.spec.ts +++ b/src/app/tasks/services/tasks-filters.service.spec.ts @@ -93,7 +93,7 @@ describe('TasksFilterService', () => { test('the service must return the right label with filterFor options', () => { const mockLabelFilterOptions = service.retrieveLabel(service.filtersDefinitions[18].for, (service.filtersDefinitions[18].field as TaskFilterField)); - expect(mockLabelFilterOptions).toEqual('Application Version'); + expect(mockLabelFilterOptions).toEqual('Application Service'); }); test('the service must throw an error ', () => { diff --git a/src/app/tasks/services/tasks-filters.service.ts b/src/app/tasks/services/tasks-filters.service.ts index 3e8ff2e0a..34b12f086 100644 --- a/src/app/tasks/services/tasks-filters.service.ts +++ b/src/app/tasks/services/tasks-filters.service.ts @@ -9,7 +9,7 @@ import { TaskFilterDefinition, TaskFilterField, TaskFilterFor, TaskSummaryFilter @Injectable({ providedIn: 'root' }) -export class TasksFiltersService implements FiltersServiceOptionsInterface, FiltersServiceStatusesInterface { +export class TasksFiltersService implements FiltersServiceOptionsInterface, FiltersServiceStatusesInterface { readonly statusService = inject(TasksStatusesService); readonly defaultConfigService = inject(DefaultConfigService); readonly tableService = inject(TableService); @@ -35,9 +35,10 @@ export class TasksFiltersService implements FiltersServiceOptionsInterface = { + readonly optionsFields: Record = { [TaskOptionEnumField.TASK_OPTION_ENUM_FIELD_UNSPECIFIED]: $localize`Unspecified`, [TaskOptionEnumField.TASK_OPTION_ENUM_FIELD_MAX_DURATION]: $localize`Max Duration`, [TaskOptionEnumField.TASK_OPTION_ENUM_FIELD_MAX_RETRIES]: $localize`Max Retries`, @@ -134,6 +135,11 @@ export class TasksFiltersService implements FiltersServiceOptionsInterface value.toLowerCase() === filterField.toLowerCase()); return { for: 'options', index: index }; } diff --git a/src/app/tasks/services/tasks-grpc.service.spec.ts b/src/app/tasks/services/tasks-grpc.service.spec.ts index 4c8e0915a..00070d4b9 100644 --- a/src/app/tasks/services/tasks-grpc.service.spec.ts +++ b/src/app/tasks/services/tasks-grpc.service.spec.ts @@ -6,7 +6,7 @@ import { UtilsService } from '@services/utils.service'; import { TasksFiltersService } from './tasks-filters.service'; import { TasksGrpcService } from './tasks-grpc.service'; import { TasksStatusesService } from './tasks-statuses.service'; -import { TaskFilterDefinition, TaskSummary, TaskSummaryFilters, TaskSummaryListOptions } from '../types'; +import { TaskFilterDefinition, TaskOptions, TaskSummary, TaskSummaryFilters, TaskSummaryListOptions } from '../types'; describe('TasksGrpcService', () => { let service: TasksGrpcService; @@ -203,7 +203,7 @@ describe('TasksGrpcService', () => { sort: { active: null, direction: 'desc' - } as unknown as ListOptionsSort + } as unknown as ListOptionsSort }; service.list$(options, []); expect(mockGrpcClient.listTasks).toHaveBeenCalledWith(new ListTasksRequest({ diff --git a/src/app/tasks/services/tasks-grpc.service.ts b/src/app/tasks/services/tasks-grpc.service.ts index b05851b18..bec8878c2 100644 --- a/src/app/tasks/services/tasks-grpc.service.ts +++ b/src/app/tasks/services/tasks-grpc.service.ts @@ -6,11 +6,11 @@ import { GrpcCancelManyInterface, GrpcCountByStatusInterface, GrpcGetInterface, import { FilterField, buildDateFilter, buildNumberFilter, buildStatusFilter, buildStringFilter } from '@services/grpc-build-request.service'; import { GrpcSortFieldService } from '@services/grpc-sort-field.service'; import { TasksFiltersService } from './tasks-filters.service'; -import { TaskOptionsFieldKey, TaskSummaryFieldKey, TaskSummaryFilters, TaskSummaryListOptions } from '../types'; +import { TaskOptions, TaskOptionsFieldKey, TaskSummary, TaskSummaryFieldKey, TaskSummaryFilters, TaskSummaryListOptions } from '../types'; @Injectable() -export class TasksGrpcService extends GrpcTableService - implements GrpcGetInterface, GrpcCancelManyInterface, GrpcCountByStatusInterface { +export class TasksGrpcService extends GrpcTableService + implements GrpcGetInterface, GrpcCancelManyInterface, GrpcCountByStatusInterface { readonly filterService = inject(TasksFiltersService); readonly grpcClient = inject(TasksClient); readonly sortFieldService = inject(GrpcSortFieldService); @@ -41,6 +41,7 @@ export class TasksGrpcService extends GrpcTableService { diff --git a/src/app/tasks/services/tasks-index.service.ts b/src/app/tasks/services/tasks-index.service.ts index 098d36790..543b536fd 100644 --- a/src/app/tasks/services/tasks-index.service.ts +++ b/src/app/tasks/services/tasks-index.service.ts @@ -7,7 +7,7 @@ import { TableService } from '@services/table.service'; import { TaskOptions, TaskSummary, TaskSummaryColumnKey, TaskSummaryListOptions } from '../types'; @Injectable() -export class TasksIndexService implements IndexServiceCustomInterface { +export class TasksIndexService implements IndexServiceCustomInterface { defaultConfigService = inject(DefaultConfigService); tableService = inject(TableService); @@ -17,7 +17,7 @@ export class TasksIndexService implements IndexServiceCustomInterface[] = [ + readonly availableTableColumns: TableColumn[] = [ { name: $localize`Task ID`, key: 'id', @@ -173,6 +173,12 @@ export class TasksIndexService implements IndexServiceCustomInterface('tasks-options', this.defaultOptions); + const options = this.tableService.restoreOptions('tasks-options', this.defaultOptions); return options; } diff --git a/src/app/tasks/services/tasks-inspection.service.spec.ts b/src/app/tasks/services/tasks-inspection.service.spec.ts new file mode 100644 index 000000000..98826088b --- /dev/null +++ b/src/app/tasks/services/tasks-inspection.service.spec.ts @@ -0,0 +1,21 @@ +import { TasksInspectionService } from './tasks-inspection.service'; + +describe('TasksInspectionService', () => { + const service = new TasksInspectionService(); + + it('should run', () => { + expect(service).toBeTruthy(); + }); + + it('should have a defined "fields"', () => { + expect(service.fields).toBeDefined(); + }); + + it('should have a defined "optionsFields"', () => { + expect(service.optionsFields).toBeDefined(); + }); + + it('should have a defined "arrays"', () => { + expect(service.arrays).toBeDefined(); + }); +}); \ No newline at end of file diff --git a/src/app/tasks/services/tasks-inspection.service.ts b/src/app/tasks/services/tasks-inspection.service.ts new file mode 100644 index 000000000..4a90816d5 --- /dev/null +++ b/src/app/tasks/services/tasks-inspection.service.ts @@ -0,0 +1,128 @@ +import { Field } from '@app/types/column.type'; +import { InspectionService } from '@app/types/services/inspectionService'; +import { TaskOptions, TaskRaw } from '../types'; + +export class TasksInspectionService extends InspectionService { + readonly fields: Field[] = [ + { + key: 'sessionId', + link: 'sessions' + }, + { + key: 'initialTaskId', + link: 'tasks' + }, + { + key: 'ownerPodId' + }, + { + key: 'podHostname' + }, + { + key: 'podTtl' + }, + { + key: 'creationToEndDuration', + type: 'duration' + }, + { + key: 'receivedToEndDuration', + type: 'duration' + }, + { + key: 'processingToEndDuration', + type: 'duration' + }, + { + key: 'createdAt', + type: 'date' + }, + { + key: 'submittedAt', + type: 'date' + }, + { + key: 'fetchedAt', + type: 'date' + }, + { + key: 'acquiredAt', + type: 'date' + }, + { + key: 'receivedAt', + type: 'date' + }, + { + key: 'startedAt', + type: 'date' + }, + { + key: 'processedAt', + type: 'date' + }, + { + key: 'statusMessage', + type: 'object' + }, + { + key: 'output', + type: 'object' + }, + ]; + + readonly optionsFields: Field[] = [ + { + key: 'partitionId', + link: 'partitions' + }, + { + key: 'applicationName' + }, + { + key: 'applicationVersion' + }, + { + key: 'applicationService' + }, + { + key: 'applicationNamespace' + }, + { + key: 'maxDuration' + }, + { + key: 'maxRetries' + }, + { + key: 'priority' + }, + { + key: 'options', + type: 'object' + }, + ]; + + readonly arrays: Field[] = [ + { + key: 'dataDependencies', + link: 'results', + queryParams: '0-root-7-0' + }, + { + key: 'expectedOutputIds', + link: 'results', + queryParams: '0-root-7-0' + }, + { + key: 'parentTaskIds', + link: 'tasks', + queryParams: '0-root-1-0' + }, + { + key: 'retryOfIds', + link: 'tasks', + queryParams: '0-root-1-0' + } + ]; +} \ No newline at end of file diff --git a/src/app/tasks/show.component.html b/src/app/tasks/show.component.html new file mode 100644 index 000000000..b55f28aa3 --- /dev/null +++ b/src/app/tasks/show.component.html @@ -0,0 +1,26 @@ + +
+ + Task +
+
+ + + +
+
+ +
+
\ No newline at end of file diff --git a/src/app/tasks/show.component.spec.ts b/src/app/tasks/show.component.spec.ts index 5cab0e46f..43a337250 100644 --- a/src/app/tasks/show.component.spec.ts +++ b/src/app/tasks/show.component.spec.ts @@ -1,14 +1,17 @@ -import { GetTaskResponse, TaskDetailed } from '@aneoconsultingfr/armonik.api.angular'; +import { GetTaskResponse, TaskStatus } from '@aneoconsultingfr/armonik.api.angular'; import { TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; +import { GrpcStatusEvent } from '@ngx-grpc/common'; import { BehaviorSubject, Observable, of, throwError } from 'rxjs'; import { FiltersService } from '@services/filters.service'; import { IconsService } from '@services/icons.service'; import { NotificationService } from '@services/notification.service'; import { ShareUrlService } from '@services/share-url.service'; import { TasksGrpcService } from './services/tasks-grpc.service'; +import { TasksInspectionService } from './services/tasks-inspection.service'; import { TasksStatusesService } from './services/tasks-statuses.service'; import { ShowComponent } from './show.component'; +import { TaskRaw } from './types'; describe('AppShowComponent', () => { let component: ShowComponent; @@ -33,32 +36,29 @@ describe('AppShowComponent', () => { id: 'taskId-12345', options: { partitionId: 'partitionId' - } - } as unknown as TaskDetailed; + }, + status: TaskStatus.TASK_STATUS_PROCESSING + } as TaskRaw; const mockTasksGrpcService = { get$: jest.fn((): Observable => of({task: returnedTask} as GetTaskResponse)), cancel$: jest.fn(() => of({})) }; - const mockTasksStatusesService = { - statuses: [] - }; - beforeEach(() => { component = TestBed.configureTestingModule({ providers: [ ShowComponent, IconsService, FiltersService, - { provide: TasksStatusesService, useValue: mockTasksStatusesService}, + TasksStatusesService, { provide: NotificationService, useValue: mockNotificationService }, { provide: ShareUrlService, useValue: mockShareUrlService }, { provide: ActivatedRoute, useValue: mockActivatedRoute }, - { provide: TasksGrpcService, useValue: mockTasksGrpcService } + { provide: TasksGrpcService, useValue: mockTasksGrpcService }, + TasksInspectionService ] }).inject(ShowComponent); component.ngOnInit(); - component.ngAfterViewInit(); }); it('should create', () => { @@ -73,6 +73,22 @@ describe('AppShowComponent', () => { it('should set sharableURL', () => { expect(mockShareUrlService.generateSharableURL).toHaveBeenCalled(); }); + + it('should set fields', () => { + expect(component.fields).toEqual((new TasksInspectionService).fields); + }); + + it('should set optionsFields', () => { + expect(component.optionsFields).toEqual((new TasksInspectionService).optionsFields); + }); + + it('should set arrays', () => { + expect(component.arrays).toEqual((new TasksInspectionService).arrays); + }); + + it('should set resultsKey', () => { + expect(component.resultsKey).toEqual('0-root-3-0'); + }); }); it('should get icons', () => { @@ -85,9 +101,19 @@ describe('AppShowComponent', () => { expect(spy).toHaveBeenCalled(); }); - it('should set link for action', () => { - component.setLink('session', 'sessions', 'sessionId-12345'); - expect(component.actionButtons[0].link).toEqual('/sessions/sessionId-12345'); + describe('get status', () => { + it('should return undefined if there is no data', () => { + mockTasksGrpcService.get$.mockReturnValueOnce(of(null)); + jest.spyOn(console, 'error').mockImplementation(() => {}); + component.refresh.next(); + expect(component.status).toEqual(undefined); + }); + + it('should return the status label if there is data', () => { + component.status = undefined; + component.refresh.next(); + expect(component.status).toEqual('Processing'); + }); }); describe('Getting data', () => { @@ -106,6 +132,10 @@ describe('AppShowComponent', () => { expect(component.data()).toEqual(null); }); + it('should set resultsQueryParams', () => { + expect(component.resultsQueryParams).toEqual({'0-root-3-0': returnedTask.id}); + }); + it('should catch errors', () => { jest.spyOn(console, 'error').mockImplementation(() => {}); mockTasksGrpcService.get$.mockReturnValueOnce(throwError(() => new Error())); @@ -118,12 +148,14 @@ describe('AppShowComponent', () => { describe('Handle errors', () => { it('should log errors', () => { const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); - component.handleError(new Error()); + const errorMessage = 'ErrorMessage'; + component.handleError({statusMessage: errorMessage} as GrpcStatusEvent); expect(errorSpy).toHaveBeenCalled(); }); it('should notify the error', () => { - component.handleError(new Error()); + const errorMessage = 'ErrorMessage'; + component.handleError({statusMessage: errorMessage} as GrpcStatusEvent); expect(mockNotificationService.error).toHaveBeenCalledWith('Could not retrieve data.'); }); }); @@ -142,8 +174,16 @@ describe('AppShowComponent', () => { }); }); - describe('Cancel task', () => { - it('should cancel tasks', () => { + it('should get statuses', () => { + expect(component.statuses).toEqual((new TasksStatusesService).statuses); + }); + + describe('cancelling', () => { + beforeEach(() => { + component.refresh.next(); // Setting the PROCESSING status. + }); + + it('should cancel a task', () => { component.cancel(); expect(mockTasksGrpcService.cancel$).toHaveBeenCalledWith([returnedTask.id]); }); @@ -167,21 +207,6 @@ describe('AppShowComponent', () => { }); }); - it('should get statuses', () => { - expect(component.statuses).toEqual(mockTasksStatusesService.statuses); - }); - - it('should get resultKeys', () => { - expect(component.resultsKey()).toEqual('1-root-3-0'); - }); - - describe('actions', () => { - it('should cancel a task', () => { - component.actionButtons.find(button => button.id === 'cancel')?.action$?.next(); - expect(mockTasksGrpcService.cancel$).toHaveBeenCalledWith([returnedTask.id]); - }); - }); - describe('on destroy', () => { beforeEach(() => { component.ngOnDestroy(); diff --git a/src/app/tasks/show.component.ts b/src/app/tasks/show.component.ts index 25e4d64be..4c2c094b8 100644 --- a/src/app/tasks/show.component.ts +++ b/src/app/tasks/show.component.ts @@ -1,9 +1,11 @@ -import { FilterStringOperator, GetTaskResponse, ResultRawEnumField } from '@aneoconsultingfr/armonik.api.angular'; -import { AfterViewInit, ChangeDetectionStrategy, Component, OnDestroy, OnInit, inject } from '@angular/core'; +import { FilterStringOperator, GetTaskResponse, ResultRawEnumField, TaskStatus } from '@aneoconsultingfr/armonik.api.angular'; +import { ChangeDetectionStrategy, Component, OnDestroy, OnInit, inject } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatSnackBar } from '@angular/material/snack-bar'; -import { Subject } from 'rxjs'; -import { AppShowComponent, ShowActionButton, ShowActionInterface, ShowCancellableInterface } from '@app/types/components/show'; +import { Params, RouterModule } from '@angular/router'; +import { Field } from '@app/types/column.type'; +import { AppShowComponent } from '@app/types/components/show'; import { ShowPageComponent } from '@components/show-page.component'; import { FiltersService } from '@services/filters.service'; import { GrpcSortFieldService } from '@services/grpc-sort-field.service'; @@ -17,19 +19,14 @@ import { TableService } from '@services/table.service'; import { UtilsService } from '@services/utils.service'; import { TasksFiltersService } from './services/tasks-filters.service'; import { TasksGrpcService } from './services/tasks-grpc.service'; +import { TasksInspectionService } from './services/tasks-inspection.service'; import { TasksStatusesService } from './services/tasks-statuses.service'; -import { TaskRaw } from './types'; +import { TaskOptions, TaskRaw } from './types'; @Component({ selector: 'app-tasks-show', - template: ` - - - Task - - `, - styles: [` - `], + templateUrl: 'show.component.html', + styleUrl: '../../inspections.css', standalone: true, providers: [ IconsService, @@ -46,67 +43,45 @@ import { TaskRaw } from './types'; MatSnackBar, FiltersService, GrpcSortFieldService, + TasksInspectionService, ], imports: [ ShowPageComponent, MatIconModule, + MatButtonModule, + RouterModule ], changeDetection: ChangeDetectionStrategy.OnPush }) -export class ShowComponent extends AppShowComponent implements OnInit, AfterViewInit, ShowCancellableInterface, ShowActionInterface, OnDestroy { +export class ShowComponent extends AppShowComponent implements OnInit, OnDestroy { - cancel$ = new Subject(); + readonly grpcService = inject(TasksGrpcService); + readonly inspectionService = inject(TasksInspectionService); private readonly tasksStatusesService = inject(TasksStatusesService); private readonly filtersService = inject(FiltersService); - readonly grpcService = inject(TasksGrpcService); - canCancel$ = new Subject(); + private _status: string | undefined; - actionButtons: ShowActionButton[] = [ - { - id: 'session', - name: $localize`See session`, - icon: this.getIcon('sessions'), - area: 'left', - link: '/sessions', - }, - { - id: 'results', - name: $localize`See results`, - icon: this.getIcon('results'), - area: 'left', - link: '/results', - queryParams: {} - }, - { - id: 'partition', - name: $localize`See partition`, - icon: this.getIcon('partitions'), - area: 'left', - link: '/partitions', - }, - { - id: 'cancel', - name: $localize`Cancel task`, - icon: this.getIcon('cancel'), - color: 'accent', - area: 'right', - action$: this.cancel$, - disabled: this.canCancel$ - } - ]; + resultsKey: string = ''; + resultsQueryParams: Params = {}; - ngOnInit(): void { - this.sharableURL = this.getSharableUrl(); + canCancel: boolean = false; + + arrays: Field[] = this.inspectionService.arrays; + optionsFields: Field[] = this.inspectionService.optionsFields; + + get status(): string | undefined { + return this._status; } - ngAfterViewInit(): void { - this.subscribeToData(); - this.getIdByRoute(); - const cancelSubscription = this.cancel$.subscribe(() => this.cancel()); - this.subscriptions.add(cancelSubscription); - this.refresh.next(); + set status(value: TaskStatus | undefined) { + this._status = value ? this.statuses[value] : undefined; + } + + ngOnInit(): void { + this.resultsKey = this.filtersService.createQueryParamsKey(0, 'root', FilterStringOperator.FILTER_STRING_OPERATOR_EQUAL, ResultRawEnumField.RESULT_RAW_ENUM_FIELD_OWNER_TASK_ID); + this.initInspection(); } ngOnDestroy(): void { @@ -119,13 +94,10 @@ export class ShowComponent extends AppShowComponent im afterDataFetching(): void { const data = this.data(); + this.status = data?.status; if (data) { - this.setLink('session', 'sessions', data.sessionId); - if (data.options) { - this.setLink('partition', 'partitions', data.options.partitionId); - } - this.filtersService.createFilterQueryParams(this.actionButtons, 'results', this.resultsKey(), data.id); - this.canCancel$.next(!this.tasksStatusesService.taskNotEnded(data.status)); + this.createResultQueryParams(); + this.canCancel = !this.tasksStatusesService.taskNotEnded(data.status); } } @@ -144,15 +116,8 @@ export class ShowComponent extends AppShowComponent im } } - resultsKey() { - return this.filtersService.createQueryParamsKey(1, 'root', FilterStringOperator.FILTER_STRING_OPERATOR_EQUAL, ResultRawEnumField.RESULT_RAW_ENUM_FIELD_OWNER_TASK_ID); - } - - setLink(actionId: string, baseLink: string, id: string) { - const index = this.actionButtons.findIndex(element => element.id === actionId); - if (index !== -1) { - this.actionButtons[index].link = `/${baseLink}/${id}`; - } + createResultQueryParams() { + this.resultsQueryParams[this.resultsKey] = this.data()?.id; } get statuses() { diff --git a/src/app/tasks/types.ts b/src/app/tasks/types.ts index f6e5b9032..4e6fb5af2 100644 --- a/src/app/tasks/types.ts +++ b/src/app/tasks/types.ts @@ -9,7 +9,7 @@ export type TaskSummary = GrpcTaskSummary.AsObject; export type TaskSummaryColumnKey = ColumnKey | 'select' | `options.options.${string}`; export type TaskSummaryFieldKey = FieldKey; export type TaskOptionsFieldKey = FieldKey; -export type TaskSummaryListOptions = ListOptions; +export type TaskSummaryListOptions = ListOptions; export type TaskSummaryField = TaskSummaryEnumField | TaskOptionEnumField | string; diff --git a/src/app/types/column.type.ts b/src/app/types/column.type.ts index d94539837..e354acef4 100644 --- a/src/app/types/column.type.ts +++ b/src/app/types/column.type.ts @@ -1,12 +1,20 @@ -import { RawColumnKey } from '@app/types/data'; +import { TaskOptions } from '@app/tasks/types'; +import { ColumnKey, DataRaw } from '@app/types/data'; -export type ColumnType = 'link' | 'count' | 'object' | 'actions' | 'date' | 'duration' | 'status' | 'select' | 'raw'; +export type DataType = 'raw' | 'link' | 'object' | 'date' | 'duration' | 'status' | 'array'; +export type ColumnType = DataType | 'count' | 'actions' | 'select'; -export type TableColumn = { +export type Field = { + key: keyof T; + type?: DataType; + link?: string; + queryParams?: string; +}; + +export type TableColumn = { name: string; - key: K; + key: ColumnKey; type?: ColumnType; sortable: boolean; link?: string; - queryParams?: string; }; \ No newline at end of file diff --git a/src/app/types/components/dashboard-line-table.ts b/src/app/types/components/dashboard-line-table.ts index 4cf073eb9..0bf0eb6d0 100644 --- a/src/app/types/components/dashboard-line-table.ts +++ b/src/app/types/components/dashboard-line-table.ts @@ -2,7 +2,8 @@ import { Component, EventEmitter, Input, Output, inject, signal } from '@angular import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { BehaviorSubject, Observable, Subject, Subscription, merge } from 'rxjs'; import { EditNameLineDialogComponent } from '@app/dashboard/components/edit-name-line-dialog.component'; -import { Line } from '@app/dashboard/types'; +import { TableLine } from '@app/dashboard/types'; +import { TaskOptions } from '@app/tasks/types'; import { ManageCustomColumnDialogComponent } from '@components/manage-custom-dialog.component'; import { AutoRefreshService } from '@services/auto-refresh.service'; import { DefaultConfigService } from '@services/default-config.service'; @@ -10,42 +11,43 @@ import { IconsService } from '@services/icons.service'; import { NotificationService } from '@services/notification.service'; import { TableColumn } from '../column.type'; import { ScopeConfig } from '../config'; -import { CustomColumn, IndexListOptions, RawColumnKey } from '../data'; +import { ColumnKey, CustomColumn, DataRaw } from '../data'; import { EditNameLineData } from '../dialog'; -import { RawFilters } from '../filters'; +import { FiltersEnums, FiltersOptionsEnums, FiltersOr } from '../filters'; +import { ListOptions } from '../options'; import { IndexServiceCustomInterface, IndexServiceInterface } from '../services/indexService'; @Component({ selector: 'app-dashboard-line-table', template: '' }) -export abstract class DashboardLineTableComponent { +export abstract class DashboardLineTableComponent { readonly autoRefreshService = inject(AutoRefreshService); readonly iconsService = inject(IconsService); readonly defaultConfigService = inject(DefaultConfigService); readonly dialog = inject(MatDialog); readonly notificationService = inject(NotificationService); - abstract readonly indexService: IndexServiceInterface; - abstract readonly defaultConfig: ScopeConfig; + abstract readonly indexService: IndexServiceInterface; + abstract readonly defaultConfig: ScopeConfig; - @Input({ required: true }) line: Line; + @Input({ required: true }) line: TableLine; @Output() lineChange: EventEmitter = new EventEmitter(); - @Output() lineDelete: EventEmitter = new EventEmitter(); + @Output() lineDelete: EventEmitter> = new EventEmitter>(); loading = signal(false); - filters: F; - filters$: Subject; + filters: FiltersOr; + filters$: Subject>; showFilters: boolean; - options: O; + options: ListOptions; - displayedColumnsKeys: K[] = []; - displayedColumns: TableColumn[] = []; - availableColumns: K[] = []; + displayedColumnsKeys: ColumnKey[] = []; + displayedColumns: TableColumn[] = []; + availableColumns: ColumnKey[] = []; lockColumns: boolean = false; - columnsLabels: Record = {} as unknown as Record; + columnsLabels: Record, string> = {} as Record, string>; intervalValue: number; @@ -66,7 +68,7 @@ export abstract class DashboardLineTableComponent c.key); - this.displayedColumnsKeys = this.line.displayedColumns as K[] ?? this.indexService.defaultColumns; + this.displayedColumnsKeys = this.line.displayedColumns ?? this.indexService.defaultColumns; this.updateDisplayedColumns(); this.indexService.availableTableColumns.forEach(column => { this.columnsLabels[column.key] = column.name; @@ -75,7 +77,7 @@ export abstract class DashboardLineTableComponent; this.showFilters = this.line.showFilters ?? this.defaultConfig.showFilters; this.filters$ = new BehaviorSubject(this.filters); } @@ -86,7 +88,7 @@ export abstract class DashboardLineTableComponent) ?? this.defaultConfig.options; } mergeSubscriptions() { @@ -99,7 +101,7 @@ export abstract class DashboardLineTableComponent this.indexService.availableTableColumns.find(c => c.key === key)).filter(Boolean) as TableColumn[]; + this.displayedColumns = this.displayedColumnsKeys.map(key => this.indexService.availableTableColumns.find(c => c.key === key)).filter(Boolean) as TableColumn[]; } getIcon(name: string): string { @@ -147,18 +149,18 @@ export abstract class DashboardLineTableComponent) { + this.filters = value; this.line.filters = value as []; this.lineChange.emit(); this.filters$.next(this.filters); } onFiltersReset() { - this.filters = [] as unknown as F; + this.filters = []; this.line.filters = []; this.lineChange.emit(); - this.filters$.next([] as unknown as F); + this.filters$.next([]); } onShowFiltersChange(value: boolean) { @@ -167,7 +169,7 @@ export abstract class DashboardLineTableComponent[]) { this.displayedColumnsKeys = data; this.updateDisplayedColumns(); this.line.displayedColumns = data; @@ -192,16 +194,16 @@ export abstract class DashboardLineTableComponent extends DashboardLineTableComponent { - abstract override readonly indexService: IndexServiceCustomInterface; +export abstract class DashboardLineCustomColumnsComponent extends DashboardLineTableComponent { + abstract override readonly indexService: IndexServiceCustomInterface; customColumns: CustomColumn[]; override initColumns() { this.customColumns = this.line.customColumns ?? []; - this.displayedColumnsKeys = [...(this.line.displayedColumns as K[] ?? this.indexService.defaultColumns)]; + this.displayedColumnsKeys = [...(this.line.displayedColumns as ColumnKey[] ?? this.indexService.defaultColumns)]; this.availableColumns = this.indexService.availableTableColumns.map(column => column.key); - this.availableColumns.push(...this.customColumns as K[]); + this.availableColumns.push(...this.customColumns as ColumnKey[]); this.lockColumns = this.line.lockColumns ?? false; this.indexService.availableTableColumns.forEach(column => { this.columnsLabels[column.key] = column.name; @@ -211,15 +213,15 @@ export abstract class DashboardLineCustomColumnsComponent { - if (key.includes('options.options.')) { - const customColumnName = key.replaceAll('options.options.', ''); + if (key.toString().includes('options.options.')) { + const customColumnName = key.toString().replaceAll('options.options.', ''); return { key: key, name: customColumnName, sortable: true, - } as TableColumn; + } as TableColumn; } else { - return this.indexService.availableTableColumns.find(column => column.key === key) as TableColumn; + return this.indexService.availableTableColumns.find(column => column.key === key) as TableColumn; } }); } @@ -233,10 +235,10 @@ export abstract class DashboardLineCustomColumnsComponent !column.startsWith('options.options.')); - this.availableColumns.push(...result as K[]); - this.displayedColumnsKeys = this.displayedColumnsKeys.filter(column => !column.startsWith('options.options.')); - this.displayedColumnsKeys.push(...result.filter(column => !oldCustoms.includes(column)) as K[]); + this.availableColumns = this.availableColumns.filter(column => !column.toString().startsWith('options.options.')); + this.availableColumns.push(...result as ColumnKey[]); + this.displayedColumnsKeys = this.displayedColumnsKeys.filter(column => !column.toString().startsWith('options.options.')); + this.displayedColumnsKeys.push(...result.filter(column => !oldCustoms.includes(column)) as ColumnKey[]); this.updateDisplayedColumns(); this.line.displayedColumns = this.displayedColumnsKeys; this.line.customColumns = result; diff --git a/src/app/types/components/index.ts b/src/app/types/components/index.ts index 8e84fe36e..fbe241b6f 100644 --- a/src/app/types/components/index.ts +++ b/src/app/types/components/index.ts @@ -3,19 +3,21 @@ import { MatDialog } from '@angular/material/dialog'; import { Router } from '@angular/router'; import { BehaviorSubject, Observable, Subject, Subscription, merge } from 'rxjs'; import { DashboardIndexService } from '@app/dashboard/services/dashboard-index.service'; -import { FiltersEnums } from '@app/dashboard/types'; +import { TableLine } from '@app/dashboard/types'; +import { TaskOptions } from '@app/tasks/types'; import { ManageCustomColumnDialogComponent } from '@components/manage-custom-dialog.component'; import { AutoRefreshService } from '@services/auto-refresh.service'; import { IconsService } from '@services/icons.service'; import { ShareUrlService } from '@services/share-url.service'; import { TableColumn } from '../column.type'; -import { CustomColumn, IndexListOptions, RawColumnKey, RawCustomColumnKey } from '../data'; -import { RawFilters } from '../filters'; +import { ColumnKey, CustomColumn, DataRaw } from '../data'; +import { FiltersEnums, FiltersOptionsEnums, FiltersOr } from '../filters'; +import { ListOptions } from '../options'; import { FiltersServiceInterface } from '../services/filtersService'; import { IndexServiceCustomInterface, IndexServiceInterface } from '../services/indexService'; import { TableType } from '../table'; -export abstract class TableHandler { +export abstract class TableHandler { readonly shareUrlService = inject(ShareUrlService); readonly iconsService = inject(IconsService); readonly autoRefreshService = inject(AutoRefreshService); @@ -23,23 +25,23 @@ export abstract class TableHandler; - abstract readonly filtersService: FiltersServiceInterface; + abstract readonly indexService: IndexServiceInterface; + abstract readonly filtersService: FiltersServiceInterface; abstract tableType: TableType; - displayedColumns: TableColumn[] = []; - displayedColumnsKeys: K[] = []; - availableColumns: K[] = []; + displayedColumns: TableColumn[] = []; + displayedColumnsKeys: ColumnKey[] = []; + availableColumns: ColumnKey[] = []; lockColumns: boolean = false; - columnsLabels: Record = {} as Record; + columnsLabels: Record, string> = {} as Record, string>; loading = signal(false); - options: O; + options: ListOptions; - filters: F; - filters$: Subject; + filters: FiltersOr; + filters$: Subject>; showFilters: boolean; intervalValue = 0; @@ -91,7 +93,7 @@ export abstract class TableHandler this.indexService.availableTableColumns.find(column => column.key === key) as TableColumn); + this.displayedColumns = this.displayedColumnsKeys.map(key => this.indexService.availableTableColumns.find(column => column.key === key) as TableColumn); } getIcon(name: string): string { @@ -115,9 +117,9 @@ export abstract class TableHandler[]) { if ((columns as string[]).includes('select')) { - columns = ['select' as K, ...columns.filter(column => column !== 'select')]; + columns = ['select' as ColumnKey, ...columns.filter(column => column !== 'select')]; } this.displayedColumnsKeys = [...columns]; this.updateDisplayedColumns(); @@ -129,10 +131,10 @@ export abstract class TableHandler) { + this.filters = value; - this.filtersService.saveFilters(value as F); + this.filtersService.saveFilters(value); this.options.pageIndex = 0; this.filters$.next(this.filters); } @@ -140,7 +142,7 @@ export abstract class TableHandler>({ name: this.tableType, type: this.tableType, interval: 10, filters: this.filters, options: this.options, displayedColumns: this.displayedColumnsKeys, - lockColumns: this.lockColumns + lockColumns: this.lockColumns, + showFilters: this.showFilters }); this.router.navigate(['/dashboard']); } } -export abstract class TableHandlerCustomValues extends TableHandler { +export abstract class TableHandlerCustomValues extends TableHandler { - abstract override readonly indexService: IndexServiceCustomInterface; + abstract override readonly indexService: IndexServiceCustomInterface; customColumns: CustomColumn[]; @@ -190,7 +193,7 @@ export abstract class TableHandlerCustomValues column.key); - this.availableColumns.push(...this.customColumns as K[]); + this.availableColumns.push(...this.customColumns as ColumnKey[]); this.lockColumns = this.indexService.restoreLockColumns(); this.indexService.availableTableColumns.forEach(column => { this.columnsLabels[column.key] = column.name; @@ -200,15 +203,15 @@ export abstract class TableHandlerCustomValues { - if (key.includes('options.options.')) { - const customColumnName = key.replaceAll('options.options.', ''); + if (key.toString().includes('options.options.')) { + const customColumnName = key.toString().replaceAll('options.options.', ''); return { key: key, name: customColumnName, sortable: true, - } as TableColumn; + } as TableColumn; } else { - return this.indexService.availableTableColumns.find(column => column.key === key) as TableColumn; + return this.indexService.availableTableColumns.find(column => column.key === key) as TableColumn; } }); } @@ -221,14 +224,29 @@ export abstract class TableHandlerCustomValues { if(result) { this.customColumns = result; - this.availableColumns = this.availableColumns.filter(column => !column.startsWith('options.options.')); - this.availableColumns.push(...result as K[]); - this.displayedColumnsKeys = this.displayedColumnsKeys.filter(column => !column.startsWith('options.options.')); - this.displayedColumnsKeys.push(...result as K[]); + this.availableColumns = this.availableColumns.filter(column => !column.toString().startsWith('options.options.')); + this.availableColumns.push(...result as ColumnKey[]); + this.displayedColumnsKeys = this.displayedColumnsKeys.filter(column => !column.toString().startsWith('options.options.')); + this.displayedColumnsKeys.push(...result as ColumnKey[]); this.updateDisplayedColumns(); this.indexService.saveColumns(this.displayedColumnsKeys); this.indexService.saveCustomColumns(this.customColumns); } }); } + + override onAddToDashboard() { + this.dashboardIndexService.addLine>({ + name: this.tableType, + type: this.tableType, + interval: 10, + filters: this.filters, + options: this.options, + displayedColumns: this.displayedColumnsKeys, + lockColumns: this.lockColumns, + showFilters: this.showFilters, + customColumns: this.customColumns + }); + this.router.navigate(['/dashboard']); + } } \ No newline at end of file diff --git a/src/app/types/components/show.ts b/src/app/types/components/show.ts index ab1fd7200..5bcb0a696 100644 --- a/src/app/types/components/show.ts +++ b/src/app/types/components/show.ts @@ -1,11 +1,14 @@ import { inject, signal } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; +import { GrpcStatusEvent } from '@ngx-grpc/common'; import { Observable, Subject, Subscription, catchError, map, of, switchMap } from 'rxjs'; import { IconsService } from '@services/icons.service'; import { NotificationService } from '@services/notification.service'; import { ShareUrlService } from '@services/share-url.service'; +import { Field } from '../column.type'; import { DataRaw } from '../data'; import { GetResponse, GrpcGetInterface } from '../services/grpcService'; +import { InspectionService } from '../services/inspectionService'; export type ShowActionButton = { id: string; @@ -42,12 +45,24 @@ export abstract class AppShowComponent data = signal(null); subscriptions = new Subscription(); - private readonly iconsService = inject(IconsService); + fields: Field[]; + abstract readonly grpcService: GrpcGetInterface; + abstract readonly inspectionService: InspectionService; + + private readonly iconsService = inject(IconsService); private readonly shareURLService = inject(ShareUrlService); private readonly notificationService = inject(NotificationService); private readonly route = inject(ActivatedRoute); + initInspection() { + this.sharableURL = this.getSharableUrl(); + this.setFields(); + this.subscribeToData(); + this.getIdByRoute(); + this.refresh.next(); + } + getIcon(name: string): string { return this.iconsService.getIcon(name); } @@ -62,7 +77,7 @@ export abstract class AppShowComponent return this.get$(); }), map((data) => this.getDataFromResponse(data) ?? null), - catchError((error) => this.handleError(error)) + catchError((error: GrpcStatusEvent) => this.handleError(error)) ).subscribe((data) => { this.data.set(data); this.afterDataFetching(); @@ -87,10 +102,15 @@ export abstract class AppShowComponent map(params => params['id']), ).subscribe(id => { this.id = id; + this.refresh.next(); }); } - handleError(error: Error) { + setFields() { + this.fields = this.inspectionService.fields; + } + + handleError(error: GrpcStatusEvent) { this.error($localize`Could not retrieve data.`); console.error(error); return of(null); diff --git a/src/app/types/components/table.ts b/src/app/types/components/table.ts index fc1f2dd46..469313265 100644 --- a/src/app/types/components/table.ts +++ b/src/app/types/components/table.ts @@ -1,9 +1,10 @@ import { SelectionModel } from '@angular/cdk/collections'; import { Component, Input, WritableSignal, inject, signal } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; +import { GrpcStatusEvent } from '@ngx-grpc/common'; import { Observable, Subject, catchError, first, map, of, switchMap } from 'rxjs'; -import { FiltersEnums, FiltersOptionsEnums, ManageGroupsDialogData, ManageGroupsDialogResult, TasksStatusesGroup } from '@app/dashboard/types'; -import { TaskSummaryFilters } from '@app/tasks/types'; +import { ManageGroupsDialogData, ManageGroupsDialogResult, TasksStatusesGroup } from '@app/dashboard/types'; +import { TaskOptions, TaskSummaryFilters } from '@app/tasks/types'; import { ManageGroupsDialogComponent } from '@components/statuses/manage-groups-dialog.component'; import { CacheService } from '@services/cache.service'; import { FiltersService } from '@services/filters.service'; @@ -11,9 +12,10 @@ import { NotificationService } from '@services/notification.service'; import { TableTasksByStatus, TasksByStatusService } from '@services/tasks-by-status.service'; import { TableColumn } from '../column.type'; import { Scope } from '../config'; -import { ArmonikData, DataRaw, GrpcResponse, IndexListOptions, RawColumnKey } from '../data'; -import { FiltersOr } from '../filters'; -import { DataFieldKey, GrpcTableService } from '../services/grpcService'; +import { ArmonikData, ColumnKey, DataRaw, GrpcResponse } from '../data'; +import { FiltersEnums, FiltersOptionsEnums, FiltersOr } from '../filters'; +import { ListOptions } from '../options'; +import { GrpcTableService } from '../services/grpcService'; import { IndexServiceInterface } from '../services/indexService'; export interface SelectableTable { @@ -24,7 +26,7 @@ export interface SelectableTable { } // eslint-disable-next-line @typescript-eslint/no-unused-vars -export interface AbstractTableComponent { +export interface AbstractTableComponent { /** * Modify environment before fetching the data. * It is required for computing values that are not directly returned by the API (example: sessions durations). @@ -45,15 +47,15 @@ export interface AbstractTableComponent { - @Input({ required: true }) displayedColumns: TableColumn[] = []; - @Input({ required: true }) options: O; +export abstract class AbstractTableComponent { + @Input({ required: true }) displayedColumns: TableColumn[] = []; + @Input({ required: true }) options: ListOptions; @Input({ required: true }) filters$: Subject>; @Input({ required: true }) refresh$: Subject; @Input({ required: true }) loading: WritableSignal; @Input() lockColumns = false; - data: WritableSignal[]> = signal([]); + data: WritableSignal[]> = signal([]); total: number = 0; filters: FiltersOr = [] as FiltersOr; @@ -63,8 +65,8 @@ export abstract class AbstractTableComponent c.key); } - abstract readonly grpcService: GrpcTableService; - abstract readonly indexService: IndexServiceInterface; + abstract readonly grpcService: GrpcTableService; + abstract readonly indexService: IndexServiceInterface; readonly cacheService = inject(CacheService); readonly filtersService = inject(FiltersService); readonly notificationService = inject(NotificationService); @@ -86,7 +88,7 @@ export abstract class AbstractTableComponent): Observable { + list$(options: ListOptions, filters: FiltersOr): Observable { return this.grpcService.list$(options, filters); } @@ -107,9 +109,9 @@ export abstract class AbstractTableComponent { + catchError((err: GrpcStatusEvent) => { console.error(err); - this.notificationService.error(err); + this.notificationService.error(err.statusMessage); return of(null); }) ); @@ -132,29 +134,31 @@ export abstract class AbstractTableComponent this.createNewLine(entry))); } - onDrop(columnsKeys: C[]) { + onDrop(columnsKeys: ColumnKey[]) { this.indexService.saveColumns(columnsKeys); } onOptionsChange() { + this.indexService.saveOptions(this.options); this.refresh$.next(); } - abstract computeGrpcData(entries: GrpcResponse): R[] | undefined; - abstract isDataRawEqual(value: R, entry: R): boolean; - abstract createNewLine(entry: R): ArmonikData; + abstract computeGrpcData(entries: GrpcResponse): T[] | undefined; + abstract isDataRawEqual(value: T, entry: T): boolean; + abstract createNewLine(entry: T): ArmonikData; + abstract trackBy(index: number, item: ArmonikData): string | number; } @Component({ selector: 'app-results-table', template: '', }) -export abstract class AbstractTaskByStatusTableComponent - extends AbstractTableComponent { +export abstract class AbstractTaskByStatusTableComponent + extends AbstractTableComponent { readonly tasksByStatusService = inject(TasksByStatusService); readonly dialog = inject(MatDialog); diff --git a/src/app/types/config.ts b/src/app/types/config.ts index 02453f926..07175c06b 100644 --- a/src/app/types/config.ts +++ b/src/app/types/config.ts @@ -1,9 +1,14 @@ -export type ScopeConfig = { +import { TaskOptions } from '@app/tasks/types'; +import { ColumnKey, DataRaw } from './data'; +import { FiltersEnums, FiltersOptionsEnums, FiltersOr } from './filters'; +import { ListOptions } from './options'; + +export type ScopeConfig = { interval: number; lockColumns: boolean; - columns: C[]; - options: O; - filters: F; + columns: ColumnKey[]; + options: ListOptions; + filters: FiltersOr; showFilters: boolean; }; diff --git a/src/app/types/data.ts b/src/app/types/data.ts index 62a904f98..e7019ec6c 100644 --- a/src/app/types/data.ts +++ b/src/app/types/data.ts @@ -1,36 +1,36 @@ -import { ApplicationFilterField, ListApplicationsResponse, ListPartitionsResponse, ListResultsResponse, ListSessionsResponse, ListTasksResponse, PartitionFilterField, ResultFilterField , ResultStatus, SessionFilterField, SessionStatus, TaskFilterField, TaskStatus } from '@aneoconsultingfr/armonik.api.angular'; +import { ListApplicationsResponse, ListPartitionsResponse, ListResultsResponse, ListSessionsResponse, ListTasksResponse , ResultStatus, SessionStatus, TaskStatus } from '@aneoconsultingfr/armonik.api.angular'; import { Params } from '@angular/router'; -import { ApplicationRaw, ApplicationRawColumnKey, ApplicationRawFilters , ApplicationRawListOptions} from '@app/applications/types'; -import { PartitionRaw, PartitionRawColumnKey, PartitionRawFilters , PartitionRawListOptions} from '@app/partitions/types'; -import { ResultRaw, ResultRawColumnKey, ResultRawFilters , ResultRawListOptions} from '@app/results/types'; -import { SessionRaw, SessionRawColumnKey, SessionRawFilters , SessionRawListOptions} from '@app/sessions/types'; -import { TaskOptions, TaskRaw, TaskSummary, TaskSummaryColumnKey, TaskSummaryFilters, TaskSummaryListOptions } from '@app/tasks/types'; +import { ApplicationRaw, } from '@app/applications/types'; +import { PartitionRaw } from '@app/partitions/types'; +import { ResultRaw } from '@app/results/types'; +import { SessionRaw } from '@app/sessions/types'; +import { TaskOptions, TaskRaw, TaskSummary, TaskSummaryFilters } from '@app/tasks/types'; export type PrefixedOptions = `options.${keyof T extends string ? keyof T : never}`; -export type ColumnKey> = keyof T | 'actions' | PrefixedOptions | CustomColumn; +export type ColumnKey = keyof T | 'actions' | PrefixedOptions | CustomColumn | 'count' | 'select'; -export type FieldKey = keyof T; +export type FieldKey = keyof T; -export type DataRaw = SessionRaw | ApplicationRaw | PartitionRaw | ResultRaw | TaskRaw | TaskSummary | TaskOptions; +export type DataRaw = SessionRaw | ApplicationRaw | PartitionRaw | ResultRaw | TaskRaw | TaskSummary; export type CustomColumn = `options.options.${string}`; -export interface ArmonikData { +export interface ArmonikData { raw: T, - queryParams?: Map, Params>; + queryParams?: Map, Params>; } -interface ArmonikTaskByStatusData extends ArmonikData{ +interface ArmonikTaskByStatusData extends ArmonikData{ queryTasksParams: Record; filters: TaskSummaryFilters; } -export interface TaskData extends ArmonikData { +export interface TaskData extends ArmonikData { resultsQueryParams: Record; } -export interface SessionData extends ArmonikTaskByStatusData { +export interface SessionData extends ArmonikTaskByStatusData { resultsQueryParams: Record; } @@ -43,14 +43,6 @@ export interface ApplicationData extends ArmonikTaskByStatusData export interface ResultData extends ArmonikData { } -export type RawColumnKey = SessionRawColumnKey | TaskSummaryColumnKey | ApplicationRawColumnKey | PartitionRawColumnKey | ResultRawColumnKey; -export type RawCustomColumnKey = SessionRawColumnKey | TaskSummaryColumnKey; -export type IndexListOptions = TaskSummaryListOptions | SessionRawListOptions | ApplicationRawListOptions | ResultRawListOptions | PartitionRawListOptions; - export type Status = TaskStatus | SessionStatus | ResultStatus; -export type IndexListFilters = SessionRawFilters | TaskSummaryFilters | PartitionRawFilters | ApplicationRawFilters | ResultRawFilters; - -export type DataFilterField = ApplicationFilterField.AsObject | SessionFilterField.AsObject | ResultFilterField.AsObject | PartitionFilterField.AsObject | TaskFilterField.AsObject; -export type ArmonikDataType = ApplicationData | PartitionData | ResultData | SessionData | TaskData; export type GrpcResponse = ListApplicationsResponse | ListTasksResponse | ListSessionsResponse | ListPartitionsResponse | ListResultsResponse; \ No newline at end of file diff --git a/src/app/types/dialog.ts b/src/app/types/dialog.ts index 83121f07f..fc2cf850c 100644 --- a/src/app/types/dialog.ts +++ b/src/app/types/dialog.ts @@ -1,15 +1,15 @@ import { Line, LineType } from '@app/dashboard/types'; import { TaskOptions } from '@app/tasks/types'; -import { ColumnKey, CustomColumn, RawColumnKey } from './data'; +import { ColumnKey, CustomColumn, DataRaw } from './data'; import { FiltersOr } from './filters'; -export interface ColumnsModifyDialogData { - currentColumns: RawColumnKey[] - availableColumns: RawColumnKey[] +export interface ColumnsModifyDialogData { + currentColumns: ColumnKey[] + availableColumns: ColumnKey[] columnsLabels: Record, string> } -export type ColumnsModifyDialogResult = ColumnKey[]; +export type ColumnsModifyDialogResult = ColumnKey[]; export interface FiltersDialogData { filtersOr: FiltersOr, diff --git a/src/app/types/filters.ts b/src/app/types/filters.ts index 7ea465372..ff5b8edfe 100644 --- a/src/app/types/filters.ts +++ b/src/app/types/filters.ts @@ -1,14 +1,10 @@ -import { FilterArrayOperator, FilterBooleanOperator, FilterDateOperator, FilterDurationOperator, FilterNumberOperator, FilterStatusOperator, FilterStringOperator } from '@aneoconsultingfr/armonik.api.angular'; -import { ApplicationRawFilters } from '@app/applications/types'; -import { PartitionRawFilters } from '@app/partitions/types'; -import { ResultRawFilters } from '@app/results/types'; -import { SessionRawFilters } from '@app/sessions/types'; -import { TaskSummaryFilters } from '@app/tasks/types'; +import { ApplicationRawEnumField, FilterArrayOperator, FilterBooleanOperator, FilterDateOperator, FilterDurationOperator, FilterNumberOperator, FilterStatusOperator, FilterStringOperator, PartitionRawEnumField, ResultRawEnumField, SessionRawEnumField, SessionTaskOptionEnumField, TaskOptionEnumField, TaskSummaryEnumField } from '@aneoconsultingfr/armonik.api.angular'; import { FilterFor } from './filter-definition'; export type MaybeNull = T | null; -export type RawFilters = SessionRawFilters | TaskSummaryFilters | PartitionRawFilters | ApplicationRawFilters | ResultRawFilters; +export type FiltersEnums = ApplicationRawEnumField | PartitionRawEnumField | SessionRawEnumField | TaskSummaryEnumField | ResultRawEnumField; +export type FiltersOptionsEnums = SessionTaskOptionEnumField | TaskOptionEnumField | null; export type FilterType = 'string' | 'number' | 'date' | 'array' | 'status' | 'boolean' | 'duration'; export type FilterValueOptions = { key: string | number, value: string }[]; @@ -18,18 +14,18 @@ export type FilterOperators = FilterStringOperator | FilterNumberOperator | Filt /** * Used to define the filter FOR (a group of AND). */ -export type FiltersOr = FiltersAnd[]; +export type FiltersOr = FiltersAnd[]; /** * Used to define the filter AND (a group of filters). */ -export type FiltersAnd = Filter[]; +export type FiltersAnd = Filter[]; /** * Filters used to filter the data. * * `for` and `field` are used to identify the filter. */ -export type Filter = { +export type Filter = { for: FilterFor | null field: T | U | string | null value: MaybeNull diff --git a/src/app/types/options.ts b/src/app/types/options.ts index 122f9916d..32e481085 100644 --- a/src/app/types/options.ts +++ b/src/app/types/options.ts @@ -1,13 +1,14 @@ import { SortDirection as MatSortDirection } from '@angular/material/sort'; +import { TaskOptions } from '@app/tasks/types'; import { DataRaw, FieldKey } from './data'; -export type ListOptions = { +export type ListOptions = { pageIndex: number pageSize: number - sort: ListOptionsSort + sort: ListOptionsSort }; -export type ListOptionsSort = { - active: FieldKey +export type ListOptionsSort = { + active: FieldKey direction: MatSortDirection }; diff --git a/src/app/types/services/filtersService.ts b/src/app/types/services/filtersService.ts index 7b54bc98b..bcefbc7e6 100644 --- a/src/app/types/services/filtersService.ts +++ b/src/app/types/services/filtersService.ts @@ -1,5 +1,4 @@ import { ApplicationFilterField, ApplicationFilterFor, ApplicationsFiltersDefinition } from '@app/applications/types'; -import { FiltersEnums, FiltersOptionsEnums } from '@app/dashboard/types'; import { PartitionFilterField, PartitionFilterFor, PartitionsFiltersDefinition } from '@app/partitions/types'; import { ResultsStatusesService } from '@app/results/services/results-statuses.service'; import { ResultFilterField, ResultFilterFor, ResultsFiltersDefinition } from '@app/results/types'; @@ -9,26 +8,26 @@ import { TasksStatusesService } from '@app/tasks/services/tasks-statuses.service import { TaskFilterDefinition, TaskFilterField, TaskFilterFor } from '@app/tasks/types'; import { DefaultConfigService } from '@services/default-config.service'; import { TableService } from '@services/table.service'; -import { RawFilters } from '../filters'; +import { FiltersEnums, FiltersOptionsEnums, FiltersOr } from '../filters'; type StatusesService = TasksStatusesService | ResultsStatusesService | SessionsStatusesService; export type FilterFor = TaskFilterFor | ResultFilterFor | SessionFilterFor | PartitionFilterFor | ApplicationFilterFor; export type FilterField = TaskFilterField | ResultFilterField | SessionFilterField | PartitionFilterField | ApplicationFilterField; export type FilterDefinition = TaskFilterDefinition | SessionFilterDefinition | ResultsFiltersDefinition | PartitionsFiltersDefinition | ApplicationsFiltersDefinition; -export interface FiltersServiceInterface { +export interface FiltersServiceInterface { defaultConfigService: DefaultConfigService; tableService: TableService; - readonly rootField: Record; + readonly rootField: Record; readonly filtersDefinitions: FilterDefinition[]; - defaultFilters: F; + defaultFilters: FiltersOr; - saveFilters(filters: F): void; - restoreFilters(): F; + saveFilters(filters: FiltersOr): void; + restoreFilters(): FiltersOr; - resetFilters(): F; + resetFilters(): FiltersOr; saveShowFilters(showFilters: boolean): void; restoreShowFilters(): boolean; @@ -36,8 +35,8 @@ export interface FiltersServiceInterface> extends FiltersServiceInterface { - readonly optionsField: Record; +export interface FiltersServiceOptionsInterface> extends FiltersServiceInterface { + readonly optionsFields: Record; } export interface FiltersServiceStatusesInterface { diff --git a/src/app/types/services/grpcService.ts b/src/app/types/services/grpcService.ts index 36da78f1e..4dd60e831 100644 --- a/src/app/types/services/grpcService.ts +++ b/src/app/types/services/grpcService.ts @@ -1,24 +1,19 @@ import { ApplicationFilterField, ApplicationsClient, CancelSessionResponse, CancelTasksResponse, CountTasksByStatusResponse, GetPartitionResponse, GetResultResponse, GetSessionResponse, GetTaskResponse, PartitionFilterField, PartitionsClient, ResultFilterField, ResultsClient, SessionFilterField, SessionsClient, TaskFilterField, TasksClient } from '@aneoconsultingfr/armonik.api.angular'; import { inject } from '@angular/core'; import { Observable } from 'rxjs'; -import { ApplicationRawFieldKey } from '@app/applications/types'; -import { FiltersEnums, FiltersOptionsEnums } from '@app/dashboard/types'; -import { PartitionRawFieldKey } from '@app/partitions/types'; -import { ResultRawFieldKey } from '@app/results/types'; -import { SessionRawFieldKey } from '@app/sessions/types'; -import { TaskSummaryFieldKey } from '@app/tasks/types'; +import { TaskOptions } from '@app/tasks/types'; import { FilterField, sortDirections } from '@services/grpc-build-request.service'; import { UtilsService } from '@services/utils.service'; import { FiltersServiceInterface } from './filtersService'; -import { GrpcResponse, IndexListOptions } from '../data'; +import { DataRaw, FieldKey, GrpcResponse } from '../data'; import { FilterDefinition } from '../filter-definition'; -import { Filter, FilterType, FiltersAnd, FiltersOr, RawFilters } from '../filters'; +import { Filter, FilterType, FiltersAnd, FiltersEnums, FiltersOptionsEnums, FiltersOr } from '../filters'; +import { ListOptions } from '../options'; export type GrpcClient = TasksClient | ApplicationsClient | ResultsClient | SessionsClient | PartitionsClient; export type GetResponse = GetTaskResponse | GetPartitionResponse | GetResultResponse | GetSessionResponse; export type CancelResponse = CancelSessionResponse | CancelTasksResponse; export type CountByStatusResponse = CountTasksByStatusResponse; -export type DataFieldKey = SessionRawFieldKey | TaskSummaryFieldKey | ApplicationRawFieldKey | PartitionRawFieldKey | ResultRawFieldKey; export type RequestFilterField = TaskFilterField.AsObject | SessionFilterField.AsObject | PartitionFilterField.AsObject | ApplicationFilterField.AsObject | ResultFilterField.AsObject; export type ListDefaultSortField = { @@ -46,16 +41,16 @@ export interface GrpcCancelManyInterface { cancel$(ids: string[]): Observable; } -export interface GrpcCountByStatusInterface { +export interface GrpcCountByStatusInterface { readonly grpcClient: GrpcClient; - countByStatus$(filters: R): Observable; + countByStatus$(filters: FiltersOr): Observable; } -export abstract class GrpcTableService { - abstract readonly sortFields: Record; +export abstract class GrpcTableService { + abstract readonly sortFields: Record, F>; abstract readonly grpcClient: GrpcClient; - abstract readonly filterService: FiltersServiceInterface; + abstract readonly filterService: FiltersServiceInterface; readonly utilsService = inject(UtilsService); @@ -71,13 +66,13 @@ export abstract class GrpcTableService): Observable; + abstract list$(options: ListOptions, filters: FiltersOr): Observable; /** * The sort field can be either an option, custom or basic enum field. * Since the parent class cannot make the difference between two enum field (for example taskSummaryField and sessionRawField), you have to implement it yourself. */ - abstract createSortField(field: K): ListRequestSortField; + abstract createSortField(field: FieldKey): ListRequestSortField; /** * The filter field can be either an option, custom or basic enum field. @@ -94,9 +89,9 @@ export abstract class GrpcTableService) { + createListRequest(options: ListOptions, filters: FiltersOr) { const requestFilter = this.createFilters(filters, this.filterService.filtersDefinitions as FilterDefinition[]); - const sortField = this.createSortField(options.sort.active as K); + const sortField = this.createSortField(options.sort.active); return { page: options.pageIndex, diff --git a/src/app/types/services/indexService.ts b/src/app/types/services/indexService.ts index 4f8f08037..0e654fd0a 100644 --- a/src/app/types/services/indexService.ts +++ b/src/app/types/services/indexService.ts @@ -1,16 +1,18 @@ +import { TaskOptions } from '@app/tasks/types'; import { DefaultConfigService } from '@services/default-config.service'; import { TableService } from '@services/table.service'; import { TableColumn } from '../column.type'; -import { CustomColumn, IndexListOptions, RawColumnKey } from '../data'; +import { ColumnKey, CustomColumn, DataRaw } from '../data'; +import { ListOptions } from '../options'; -export interface IndexServiceInterface { +export interface IndexServiceInterface { readonly defaultConfigService: DefaultConfigService; readonly tableService: TableService; - readonly defaultColumns: K[]; - readonly availableTableColumns: TableColumn[]; + readonly defaultColumns: ColumnKey[]; + readonly availableTableColumns: TableColumn[]; - readonly defaultOptions: O; + readonly defaultOptions: ListOptions; readonly defaultIntervalValue: number; readonly defaultLockColumns: boolean; @@ -24,19 +26,19 @@ export interface IndexServiceInterface): void; + restoreOptions(): ListOptions; // Columns - saveColumns(columns: K[]): void; - restoreColumns(): K[]; - resetColumns(): K[]; + saveColumns(columns: ColumnKey[]): void; + restoreColumns(): ColumnKey[]; + resetColumns(): ColumnKey[]; } -export interface IndexServiceCustomInterface extends IndexServiceInterface { +export interface IndexServiceCustomInterface extends IndexServiceInterface { saveCustomColumns(columns: CustomColumn[]): void; restoreCustomColumns(): CustomColumn[]; - customField(column: K): string; + customField(column: ColumnKey): string; } \ No newline at end of file diff --git a/src/app/types/services/inspectionService.ts b/src/app/types/services/inspectionService.ts new file mode 100644 index 000000000..84dbf760b --- /dev/null +++ b/src/app/types/services/inspectionService.ts @@ -0,0 +1,6 @@ +import { Field } from '../column.type'; +import { DataRaw } from '../data'; + +export abstract class InspectionService { + abstract readonly fields: Field[]; +} \ No newline at end of file diff --git a/src/app/types/table.ts b/src/app/types/table.ts index 06907039f..bbea26e2f 100644 --- a/src/app/types/table.ts +++ b/src/app/types/table.ts @@ -1,11 +1,12 @@ import { Subject } from 'rxjs'; -import { ArmonikDataType } from './data'; +import { TaskOptions } from '@app/tasks/types'; +import { ArmonikData, DataRaw } from './data'; export type TableType = 'Applications' | 'Tasks' | 'Sessions' | 'Partitions' | 'Results'; -export type ActionTable = { +export type ActionTable = { icon: string; label: string; - action$: Subject; - condition?: (element: T) => boolean; + action$: Subject>; + condition?: (element: ArmonikData) => boolean; }; \ No newline at end of file diff --git a/src/index.html b/src/index.html index d98190d61..6d82a642c 100644 --- a/src/index.html +++ b/src/index.html @@ -13,14 +13,17 @@ -
- Loading... -
- +
+
+ Loading... +
+ +
+
diff --git a/src/inspections.css b/src/inspections.css new file mode 100644 index 000000000..271f1d793 --- /dev/null +++ b/src/inspections.css @@ -0,0 +1,73 @@ +#options { + margin-top: 0.5rem; +} + +.no-data { + text-align: center; + font-style: italic; +} + +.actions { + display: flex; + gap: 0.5rem; +} + +.title { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.arrays { + display: grid; + grid-template-columns: repeat(3, 1fr); + margin-top: 1rem; + margin-bottom: 1rem; + gap: 1rem; +} + +.object { + display: grid; + grid-template-columns: repeat(3, 1fr); + column-gap: 2rem; + row-gap: 0.5rem; +} + +mat-accordion { + grid-column: 1 / 4; +} + +.field { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.5rem; +} + +p, h2 { + margin: 0; +} + +app-inspection { + padding: 1rem; +} + +mat-chip { + display: flex; + align-items: center; +} + +mat-icon { + cursor: pointer; +} + +ul { + flex-basis: 100%; + margin: 0; +} + +li { + width: fit-content; + margin-top: 0.25rem; + margin-bottom: 0.25rem; +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 057887a8e..80c78b751 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,11 +1,61 @@ /// import { bootstrapApplication } from '@angular/platform-browser'; +import { Subscription, fromEvent } from 'rxjs'; import { AppComponent } from './app/app.component'; import { appConfig } from './app/app.config'; +const mouseMove = fromEvent(document, 'mousemove'); +const mouse = document.getElementById('mouse-effect'); +const loadingApp = document.getElementById('loading-app'); +const subscriptions = new Subscription(); +let hasError = false; + +const theme = window.localStorage.getItem('navigation-theme'); + +function setDarkBackGroundColor() { + if (loadingApp) { + loadingApp.style.backgroundColor = '#1d1d1d'; + loadingApp.style.color = 'white'; + } +} + +if (mouse) { + switch (theme) { + case 'deeppurple-amber': + mouse.style.background = 'radial-gradient(#9c2a2277 10%, transparent 70%)'; + setDarkBackGroundColor(); + break; + case 'purple-green': + mouse.style.background = 'radial-gradient(#683ab77c 10%, transparent 70%)'; + setDarkBackGroundColor(); + break; + case 'pink-bluegrey': + mouse.style.background = 'radial-gradient(#e91e6377 10%, transparent 70%)'; + break; + default: + mouse.style.background = 'radial-gradient(royalblue 10%, transparent 70%)'; + } + + subscriptions.add(mouseMove.subscribe((mouseEvent) => { + const x = (mouseEvent as MouseEvent).clientX; + const y = (mouseEvent as MouseEvent).clientY; + mouse.style.left = `${x}px`; + mouse.style.top = `${y}px`; + + const dx = Math.abs(x - window.innerWidth / 2); + const dy = Math.abs(y - window.innerHeight / 2); + const distance = Math.sqrt(dx * dx + dy * dy); + const size = Math.max(300 - distance, 125); + + mouse.style.width = `${size}px`; + mouse.style.height = `${size}px`; + })); +} + bootstrapApplication(AppComponent, appConfig) .catch((err) => { + hasError = true; const loading = document.getElementById('loading') as HTMLDivElement; const error = document.getElementById('error') as HTMLDivElement; const errorMessage = document.getElementById('error-message') as HTMLDivElement; @@ -19,4 +69,9 @@ bootstrapApplication(AppComponent, appConfig) } errorMessage.textContent = err.message || err; + }) + .finally(() => { + if (!hasError) { + subscriptions.unsubscribe(); + } }); diff --git a/src/styles.css b/src/styles.css index 7adf0047a..e9aa7b02c 100644 --- a/src/styles.css +++ b/src/styles.css @@ -102,4 +102,31 @@ app-table-index-actions-toolbar, app-table-dashboard-actions-toolbar { input[type="file"] { display: none; +} + +#loading-app { + overflow: hidden; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + font-size: 25px; + text-align: center; + background-color: white; + cursor: none; +} + +#error { + background-color: #f4433677; + border: 1px solid #f44336; + border-radius: 1rem; + padding: 1.5rem; +} + +#mouse-effect { + position: absolute; + border-radius: 100%; + transform: translate(-50%, -50%); } \ No newline at end of file