diff options
28 files changed, 476 insertions, 195 deletions
diff --git a/.browserslistrc b/.browserslistrc deleted file mode 100644 index 16e3010..0000000 --- a/.browserslistrc +++ /dev/null @@ -1,7 +0,0 @@ -last 1 Chrome version -last 1 Firefox version -last 2 Edge major versions -last 2 Safari major versions -last 2 iOS major versions -Firefox ESR -not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.
\ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index a4c8c1c..fb48546 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,17 +1,29 @@ { "root": true, - "ignorePatterns": ["projects/**/*"], + "ignorePatterns": [ + "projects/**/*" + ], "overrides": [ { - "files": ["*.ts"], + "files": [ + "*.ts" + ], "parserOptions": { - "project": ["tsconfig.json", "e2e/tsconfig.json"], + "project": [ + "tsconfig.json", + "e2e/tsconfig.json" + ], "createDefaultProgram": true }, - "extends": ["plugin:@angular-eslint/recommended", "plugin:@angular-eslint/template/process-inline-templates"], + "extends": [ + "plugin:@angular-eslint/recommended", + "plugin:@angular-eslint/template/process-inline-templates" + ], "rules": { "no-unused-vars": "off", - "@typescript-eslint/no-unused-vars": ["error"], + "@typescript-eslint/no-unused-vars": [ + "error" + ], "@angular-eslint/component-selector": [ "error", { @@ -31,8 +43,12 @@ } }, { - "files": ["*.html"], - "extends": ["plugin:@angular-eslint/template/recommended"], + "files": [ + "*.html" + ], + "extends": [ + "plugin:@angular-eslint/template/recommended" + ], "rules": {} } ] diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..8979dd8 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,20 @@ +--- +# Dependabot configured for weekly NPM and Docker scans + +version: 2 +updates: + # Enable version updates for npm + - package-ecosystem: "npm" + # Look for `package.json` and `lock` files in the `root` directory + directory: "/" + # Check the npm registry for updates every day (weekdays) + schedule: + interval: "weekly" + + # Enable version updates for Docker + - package-ecosystem: "docker" + # Look for a `Dockerfile` in the `root` directory + directory: "/" + # Check for updates once a week + schedule: + interval: "weekly" diff --git a/.github/workflows/gerrit-merge.yaml b/.github/workflows/gerrit-merge.yaml new file mode 100644 index 0000000..3fa0a19 --- /dev/null +++ b/.github/workflows/gerrit-merge.yaml @@ -0,0 +1,117 @@ +--- +name: Gerrit Merge + +# yamllint disable-line rule:truthy +on: + workflow_dispatch: + inputs: + GERRIT_BRANCH: + description: "Branch that change is against" + required: true + type: string + GERRIT_CHANGE_ID: + description: "The ID for the change" + required: true + type: string + GERRIT_CHANGE_NUMBER: + description: "The Gerrit number" + required: true + type: string + GERRIT_CHANGE_URL: + description: "URL to the change" + required: true + type: string + GERRIT_EVENT_TYPE: + description: "Type of Gerrit event" + required: true + type: string + GERRIT_PATCHSET_NUMBER: + description: "The patch number for the change" + required: true + type: string + GERRIT_PATCHSET_REVISION: + description: "The revision sha" + required: true + type: string + GERRIT_PROJECT: + description: "Project in Gerrit" + required: true + type: string + GERRIT_REFSPEC: + description: "Gerrit refspec of change" + required: true + type: string + secrets: + SONAR_TOKEN: + description: "Sonar Cloud access token" + required: true + +concurrency: + # yamllint disable-line rule:line-length + group: gerrit-merge-${{ github.workflow }}-${{ github.event.inputs.GERRIT_CHANGE_ID || github.run_id }} + cancel-in-progress: true + +jobs: + notify: + runs-on: ubuntu-latest + steps: + - name: Notify job start + # yamllint disable-line rule:line-length + uses: lfit/gerrit-review-action@9627b9a144f2a2cad70707ddfae87c87dce60729 # v0.8 + with: + host: ${{ vars.GERRIT_SERVER }} + username: ${{ vars.GERRIT_SSH_USER }} + key: ${{ secrets.GERRIT_SSH_PRIVKEY }} + known_hosts: ${{ vars.GERRIT_KNOWN_HOSTS }} + gerrit-change-number: ${{ inputs.GERRIT_CHANGE_NUMBER }} + gerrit-patchset-number: ${{ inputs.GERRIT_PATCHSET_NUMBER }} + vote-type: clear + - name: Allow replication + run: sleep 10s + + call-prescan-sonarcloud: + needs: notify + # yamllint disable-line rule:line-length + uses: lfit/releng-reusable-workflows/.github/workflows/composed-generic-sonar-cloud.yaml@main + with: + GERRIT_BRANCH: ${{ inputs.GERRIT_BRANCH }} + GERRIT_CHANGE_ID: ${{ inputs.GERRIT_CHANGE_ID }} + GERRIT_CHANGE_NUMBER: ${{ inputs.GERRIT_CHANGE_NUMBER }} + GERRIT_CHANGE_URL: ${{ inputs.GERRIT_CHANGE_URL }} + GERRIT_EVENT_TYPE: ${{ inputs.GERRIT_EVENT_TYPE }} + GERRIT_PATCHSET_NUMBER: ${{ inputs.GERRIT_PATCHSET_NUMBER }} + GERRIT_PATCHSET_REVISION: ${{ inputs.GERRIT_PATCHSET_REVISION }} + GERRIT_PROJECT: ${{ inputs.GERRIT_PROJECT }} + GERRIT_REFSPEC: ${{ inputs.GERRIT_REFSPEC }} + JDK_VERSION: "17" + ENV_VARS: "{}" + ENV_SECRETS: "{}" + SONAR_ARGS: > + -Dsonar.organization=onap + -Dsonar.projectKey=onap_portal-ng-ui + -Dsonar.projectName=portal-ng-ui + -Dsonar.build.sourceEncoding=UTF-8 + -Dsonar.sources=. + -Dsonar.go.coverage.reportPaths=**/coverage.txt + -Dsonar.verbose=true + secrets: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + + report-status: + if: ${{ always() }} + needs: [notify, call-prescan-sonarcloud] + runs-on: ubuntu-latest + steps: + - name: Get workflow conclusion + uses: technote-space/workflow-conclusion-action@v3 + - name: Report workflow conclusion + # yamllint disable-line rule:line-length + uses: lfit/gerrit-review-action@9627b9a144f2a2cad70707ddfae87c87dce60729 # v0.8 + with: + host: ${{ vars.GERRIT_SERVER }} + username: ${{ vars.GERRIT_SSH_USER }} + key: ${{ secrets.GERRIT_SSH_PRIVKEY }} + known_hosts: ${{ vars.GERRIT_KNOWN_HOSTS }} + gerrit-change-number: ${{ inputs.GERRIT_CHANGE_NUMBER }} + gerrit-patchset-number: ${{ inputs.GERRIT_PATCHSET_NUMBER }} + vote-type: ${{ env.WORKFLOW_CONCLUSION }} diff --git a/.github/workflows/gerrit-verify.yaml b/.github/workflows/gerrit-verify.yaml new file mode 100644 index 0000000..7518c60 --- /dev/null +++ b/.github/workflows/gerrit-verify.yaml @@ -0,0 +1,96 @@ +--- +name: Gerrit Verify + +# yamllint disable-line rule:truthy +on: + workflow_dispatch: + inputs: + GERRIT_BRANCH: + description: 'Branch that change is against' + required: true + type: string + GERRIT_CHANGE_ID: + description: 'The ID for the change' + required: true + type: string + GERRIT_CHANGE_NUMBER: + description: 'The Gerrit number' + required: true + type: string + GERRIT_CHANGE_URL: + description: 'URL to the change' + required: true + type: string + GERRIT_EVENT_TYPE: + description: 'Type of Gerrit event' + required: true + type: string + GERRIT_PATCHSET_NUMBER: + description: 'The patch number for the change' + required: true + type: string + GERRIT_PATCHSET_REVISION: + description: 'The revision sha' + required: true + type: string + GERRIT_PROJECT: + description: 'Project in Gerrit' + required: true + type: string + GERRIT_REFSPEC: + description: 'Gerrit refspec of change' + required: true + type: string + +concurrency: + # yamllint disable-line rule:line-length + group: gerrit-merge-${{ github.workflow }}-${{ github.event.inputs.GERRIT_CHANGE_ID || github.run_id }} + cancel-in-progress: true + +jobs: + notify: + runs-on: ubuntu-latest + steps: + - name: Notify job start + # yamllint disable-line rule:line-length + uses: lfit/gerrit-review-action@9627b9a144f2a2cad70707ddfae87c87dce60729 # v0.8 + with: + host: ${{ vars.GERRIT_SERVER }} + username: ${{ vars.GERRIT_SSH_USER }} + key: ${{ secrets.GERRIT_SSH_PRIVKEY }} + known_hosts: ${{ vars.GERRIT_KNOWN_HOSTS }} + gerrit-change-number: ${{ inputs.GERRIT_CHANGE_NUMBER }} + gerrit-patchset-number: ${{ inputs.GERRIT_PATCHSET_NUMBER }} + vote-type: clear + - name: Allow replication + run: sleep 10s + + # This is a test workflow, not production, and will be replaced + node-sonartype-lifecycle: + needs: [notify] + # yamllint disable-line rule:line-length + uses: ModeSevenIndustrialSolutions/portal-ng-ui/.github/workflows/node-sonatype-lifecycle.yaml@master + with: + node-version: 20 + build-tool: npm + secrets: + NEXUS_IQ_PASSWORD: ${{ secrets.NEXUS_IQ_PASSWORD }} + + report-status: + if: ${{ always() }} + needs: [notify, node-sonartype-lifecycle] + runs-on: ubuntu-latest + steps: + - name: Get workflow conclusion + uses: technote-space/workflow-conclusion-action@v3 + - name: Report workflow conclusion + # yamllint disable-line rule:line-length + uses: lfit/gerrit-review-action@9627b9a144f2a2cad70707ddfae87c87dce60729 # v0.8 + with: + host: ${{ vars.GERRIT_SERVER }} + username: ${{ vars.GERRIT_SSH_USER }} + key: ${{ secrets.GERRIT_SSH_PRIVKEY }} + known_hosts: ${{ vars.GERRIT_KNOWN_HOSTS }} + gerrit-change-number: ${{ inputs.GERRIT_CHANGE_NUMBER }} + gerrit-patchset-number: ${{ inputs.GERRIT_PATCHSET_NUMBER }} + vote-type: ${{ env.WORKFLOW_CONCLUSION }} @@ -12,6 +12,7 @@ # dependencies /node_modules package-lock.json +node # profiling files chrome-profiler-events*.json @@ -56,4 +57,4 @@ Thumbs.db /.angular .cache -staging.proxy.config.json
\ No newline at end of file +staging.proxy.config.json @@ -1,7 +1,8 @@ # the JRE is required by the openapi-generator-cli -FROM eclipse-temurin:17-jre-alpine as builder +# FROM eclipse-temurin:17-jre-alpine as builder +FROM node:18-alpine AS builder RUN apk update && \ - apk add nodejs npm + apk add openjdk17 WORKDIR /usr/src/app COPY package*.json ./ @@ -3,13 +3,24 @@ ## Getting started The portal-ng ui can either be developed against a remote cluster or it's dependencies can be run locally on your machine. +### Allow unpriviliged (non-sudo) programs to bind to low ports (i.e 80) + +On linux, programs that run in an unprivileged mode are not allowed to bind to low ports (<=1024). +A capability needs to be defined to allow the `node` process to do so: + +```sh +sudo setcap 'cap_net_bind_service=+ep' `readlink -f \`which node\`` +``` + ### Developing against a remote cluster To develop against a remote cluster, the webpack proxy needs to be configured to forward requests to the remote cluster urls. For that we are providing a `staging.proxy.config.json.template` file that needs to be adjusted with your cluster hostnames and then saved as `staging.proxy.config.json`. After that, you can either use `npm start` or the `staging.sh` to launch the portal-ng in development mode: + ```sh ./staging.sh ``` + ```sh npm start ``` @@ -18,13 +29,14 @@ npm start We are providing a docker-compose file that can be used to spin up the portal-ng and it's dependencies (like Keycloak or the bff) on your machine. To do that, execute the `run.sh` in the development folder: -```shell + +```sh development/run.sh ``` To stop the portal-ng, portal backend services, Keycloak and the databases run: -```shell +```sh development/stop.sh ``` @@ -35,21 +47,24 @@ You can access the portal-ng UI via browser with different default user accounts in what they are allowed to see in the portal. URL: http://localhost -``` -username: onap-admin + +``` yaml +username: onap-admin password: password -username: onap-designer +username: onap-designer password: password -username: onap-operator +username: onap-operator password: password ``` + You can access the Keycloak UI via browser. URL: http://localhost:8080 -``` -username: admin + +``` yaml +username: admin password: password ``` @@ -60,7 +75,7 @@ Run `npm run build -- --prod --base-href=/portal-ui/` to get a production build In the configuration of nginx (the `nginx.template`) we have a few environment variables that need to be set. -```bash +```sh export NGINX_PORT=80 export BFF_URL=http://bff:9080/ export WIREMOCK_URL=http://wiremock:8080/ @@ -68,14 +83,16 @@ export WIREMOCK_URL=http://wiremock:8080/ Finally, build the image with -```bash +```sh docker build -t portal-ng . ``` ### Run the docker image -```bash +```sh docker run -e "NGINX_PORT=80" -e "BFF_URL=http:bff:9080/" -e "WIREMOCK_URL=http://wiremock:8080/" -p 8080:80 portal-ng ``` -Note that this will not work on its own, because the referenced containers (`BFF` and `WIREMOCK`) are most likely not available in your local environment. You would have to run them as well, or pass in other urls (like `example.com`) to get the container running locally. Obviously this does not get you very far though. +Note that this will not work on its own, because the referenced containers (`BFF` and `WIREMOCK`) are most likely not available in your local environment. +You would have to run them as well, or pass in other urls (like `example.com`) to get the container running locally. +Obviously this does not get you very far though. diff --git a/angular.json b/angular.json index f749b51..339a24b 100644 --- a/angular.json +++ b/angular.json @@ -13,19 +13,25 @@ "build": { "builder": "@angular-devkit/build-angular:browser", "options": { - "allowedCommonJsDependencies": [ "clone-deep"], + "allowedCommonJsDependencies": [ + "clone-deep" + ], "aot": true, "outputPath": "dist/frontend", "index": "src/index.html", "main": "src/main.ts", "polyfills": "src/polyfills.ts", "tsConfig": "tsconfig.app.json", - "assets": ["src/favicon.ico", "src/assets", "src/keycloak-error.html"], + "assets": [ + "src/favicon.ico", + "src/assets", + "src/keycloak-error.html" + ], "styles": [ "node_modules/bootstrap/dist/css/bootstrap.css", "src/assets/css/bootstrap-icons.css", "src/styles.css", - "./node_modules/@angular/cdk/overlay-prebuilt.css", + "./node_modules/@angular/cdk/overlay-prebuilt.css", { "input": "src/assets/css/onap-styles.css", "bundleName": "onap-styles", @@ -96,15 +102,24 @@ "polyfills": "src/polyfills.ts", "tsConfig": "tsconfig.spec.json", "karmaConfig": "karma.conf.js", - "assets": ["src/favicon.ico", "src/assets", "src/keycloak-error.html"], - "styles": ["src/styles.css"], + "assets": [ + "src/favicon.ico", + "src/assets", + "src/keycloak-error.html" + ], + "styles": [ + "src/styles.css" + ], "scripts": [] } }, "lint": { "builder": "@angular-eslint/builder:lint", "options": { - "lintFilePatterns": ["src/**/*.ts", "src/**/*.html"] + "lintFilePatterns": [ + "src/**/*.ts", + "src/**/*.html" + ] } }, "e2e": { @@ -122,7 +137,6 @@ } } }, - "defaultProject": "frontend", "cli": { "cache": { "enabled": true, @@ -130,6 +144,16 @@ "environment": "all" }, "analytics": "8f37c777-0688-43ed-a5a5-b315e820eff9", - "defaultCollection": "@angular-eslint/schematics" + "schematicCollections": [ + "@angular-eslint/schematics" + ] + }, + "schematics": { + "@angular-eslint/schematics:application": { + "setParserOptionsProject": true + }, + "@angular-eslint/schematics:library": { + "setParserOptionsProject": true + } } } diff --git a/docs/_static/logo_onap_2024.png b/docs/_static/logo_onap_2024.png Binary files differnew file mode 100644 index 0000000..55d307f --- /dev/null +++ b/docs/_static/logo_onap_2024.png diff --git a/docs/conf.py b/docs/conf.py index 8cdff9b..e0df9e1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -11,7 +11,7 @@ html_theme = "sphinx_rtd_theme" html_theme_options = { "style_nav_header_background": "white", "sticky_navigation": "False" } -html_logo = "_static/logo_onap_2017.png" +html_logo = "_static/logo_onap_2024.png" html_favicon = "_static/favicon.ico" html_static_path = ["_static"] html_show_sphinx = False diff --git a/docs/release-notes.rst b/docs/release-notes.rst index b876645..dcc0199 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -5,7 +5,7 @@ .. DO NOT CHANGE THIS LABEL FOR RELEASE NOTES - EVEN THOUGH IT GIVES A WARNING .. _release_notes: -Portal-ng Release Notes +Portal-NG Release Notes ####################### .. contents:: @@ -22,29 +22,30 @@ Version: 0.1.0 Release Data ------------ -+--------------------------------------+--------------------------------------------------------+ -| **Portal-ng Project** | | -| | | -+--------------------------------------+--------------------------------------------------------+ -| **Docker images** | onap/portal-ng/ui:0.1.1 | -| | onap/portal-ng/bff:0.1.0 | -| | onap/portal-ng/preferences:0.1.1 | -| | onap/portal-ng/history:0.1.1 | -| | | -+--------------------------------------+--------------------------------------------------------+ -| **Release designation** | 0.1.1 New Delhi | -| | | -+--------------------------------------+--------------------------------------------------------+ -| **Release date** | 2024 June 13 | -| | | -+--------------------------------------+--------------------------------------------------------+ ++------------------------------+----------------------------------------------+ +| **Portal-ng Project** | | +| | | ++------------------------------+----------------------------------------------+ +| **Docker images** | onap/portal-ng/ui:0.1.1 | +| | onap/portal-ng/bff:0.1.0 | +| | onap/portal-ng/preferences:0.1.1 | +| | onap/portal-ng/history:0.1.1 | +| | | ++------------------------------+----------------------------------------------+ +| **Release designation** | 0.1.1 New Delhi | +| | | ++------------------------------+----------------------------------------------+ +| **Release date** | 2024 June 13 | +| | | ++------------------------------+----------------------------------------------+ Features -------- -The Portal-NG is the successor of the Portal project, which has been discontinued. It offers a dashboard with the last user-actions in the portal, -an app-starter that references the other ui's available in ONAP and -a user management that allows creating and editing users and their roles. +The Portal-NG is the successor of the Portal project, which has been +discontinued. It offers a dashboard with the last user-actions in the portal, +an app-starter that references the other ui's available in ONAP and a user +management that allows creating and editing users and their roles. .. _newdelhi_deliverable: diff --git a/package.json b/package.json index cbd5fd0..c6d404e 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "openapi": "npx openapi-generator-cli generate --generator-key v3.0", "ng": "./node-modules/.bin/ng", "prestart": "npm run openapi", - "start": "ng serve -- --proxy-config staging.proxy.config.json --host 0.0.0.0 --port 80 --disable-host-check", + "start": "ng serve --proxy-config staging.proxy.config.json --host 0.0.0.0 --port 80 --disable-host-check", "prebuild": "npm run openapi", "build": "ng build", "pretest": "npm run openapi", @@ -21,47 +21,46 @@ }, "private": true, "dependencies": { - "@angular/animations": "13.3.10", - "@angular/cdk": "13.3.8", - "@angular/common": "13.3.10", - "@angular/compiler": "13.3.10", - "@angular/core": "13.3.10", - "@angular/forms": "13.3.10", - "@angular/localize": "13.3.10", - "@angular/platform-browser": "13.3.10", - "@angular/platform-browser-dynamic": "13.3.10", - "@angular/router": "13.3.10", - "@ng-bootstrap/ng-bootstrap": "12.1.2", - "@ngx-translate/core": "13.0.0", - "@ngx-translate/http-loader": "6.0.0", - "@popperjs/core": "2.11.5", - "angular-oauth2-oidc": "12.1.0", - "bootstrap": "5.1.3", + "@angular/animations": "16.2.12", + "@angular/cdk": "16.2.14", + "@angular/common": "16.2.12", + "@angular/compiler": "16.2.12", + "@angular/core": "16.2.12", + "@angular/forms": "16.2.12", + "@angular/localize": "16.2.12", + "@angular/platform-browser": "16.2.12", + "@angular/platform-browser-dynamic": "16.2.12", + "@angular/router": "16.2.12", + "@ng-bootstrap/ng-bootstrap": "15.1.2", + "@ngx-translate/core": "14.0.0", + "@ngx-translate/http-loader": "7.0.0", + "@popperjs/core": "^2.11.6", + "angular-oauth2-oidc": "^16.0.0", + "bootstrap": "5.2.3", "license-report": "5.0.2", "lodash": "4.17.21", - "ng-bootstrap": "1.6.3", - "rxjs": "6.6.7", - "tslib": "2.2.0", - "zone.js": "0.11.5" + "rxjs": "~7.4.0", + "tslib": "2.3.0", + "zone.js": "0.13.3" }, "devDependencies": { - "@angular-devkit/build-angular": "13.3.7", - "@angular-eslint/builder": "13.2.1", - "@angular-eslint/eslint-plugin": "13.2.1", - "@angular-eslint/eslint-plugin-template": "13.2.1", - "@angular-eslint/schematics": "13.2.1", - "@angular-eslint/template-parser": "13.2.1", - "@angular/cli": "13.3.7", - "@angular/compiler-cli": "13.3.10", + "@angular-devkit/build-angular": "16.2.16", + "@angular-eslint/builder": "16.3.1", + "@angular-eslint/eslint-plugin": "16.3.1", + "@angular-eslint/eslint-plugin-template": "16.3.1", + "@angular-eslint/schematics": "16.3.1", + "@angular-eslint/template-parser": "16.3.1", + "@angular/cli": "16.2.16", + "@angular/compiler-cli": "16.2.12", "@openapitools/openapi-generator-cli": "^2.6.0", "@types/jasmine": "~3.6.0", "@types/jasminewd2": "2.0.3", - "@types/lodash": "^4.14.188", - "@types/node": "16.0.0", + "@types/lodash": "4.14.188", + "@types/node": "18.0.0", "@types/uuid": "^8.3.4", - "@typescript-eslint/eslint-plugin": "5.17.0", - "@typescript-eslint/parser": "5.17.0", - "eslint": "7.32.0", + "@typescript-eslint/eslint-plugin": "^5.59.2", + "@typescript-eslint/parser": "^5.59.2", + "eslint": "^8.39.0", "git-format-staged": "3.0.0", "husky": "8.0.1", "jasmine-core": "~4.0.0", @@ -81,8 +80,8 @@ "prettier": "2.6.2", "protractor": "7.0.0", "sonar-scanner": "3.1.0", - "ts-node": "10.0.0", - "typescript": "4.6.4" + "ts-node": "10.9.0", + "typescript": "~4.9.3" }, "husky": { "hooks": { diff --git a/releases/0.1.2-container-release.yaml b/releases/0.1.2-container-release.yaml new file mode 100644 index 0000000..c3c426d --- /dev/null +++ b/releases/0.1.2-container-release.yaml @@ -0,0 +1,7 @@ +distribution_type: container +container_release_tag: 0.1.2 +project: portal-ng/ui +ref: 49260befded7829a951168faa36f7dcad9402823 +containers: + - name: portal-ng/ui + version: latest diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 0f5a50e..d4e4d7c 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -49,7 +49,7 @@ const routes: Routes = [ ]; @NgModule({ - imports: [RouterModule.forRoot(routes, { relativeLinkResolution: 'legacy', onSameUrlNavigation: 'reload' })], + imports: [RouterModule.forRoot(routes, { onSameUrlNavigation: 'reload' })], exports: [RouterModule], }) export class AppRoutingModule {} diff --git a/src/app/app.module.ts b/src/app/app.module.ts index faeb566..854c8fc 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -65,39 +65,38 @@ export function changeConfig() { } @NgModule({ - declarations: [ - AppComponent, - HeaderComponent, - ModalContentComponent, - SidemenuComponent, - PageNotFoundComponent, - ConfirmationModalComponent, - StatusPageComponent, - ], - imports: [ - BrowserModule, - AppRoutingModule, - HttpClientModule, - AuthConfigModule, - AlertModule, - AuthConfigModule, - ApiModule.forRoot(changeConfig), - OAuthModule.forRoot({ resourceServer: { sendAccessToken: false } }), - BrowserAnimationsModule, - UserAdministrationModule, - DashboardModule, - SharedModule, - AppStarterModule, - ], - // { provide: ErrorHandler, useClass: SentryErrorHandler }, - providers: [ - { provide: RequestCache, useClass: RequestCacheService }, - httpInterceptorProviders, - { provide: OAuthStorage, useValue: localStorage }, - LoadingIndicatorService, - { provide: RouteReuseStrategy, useClass: AppRouteReuseStrategy }, - ], - bootstrap: [AppComponent], - entryComponents: [ModalContentComponent, ConfirmationModalComponent], + declarations: [ + AppComponent, + HeaderComponent, + ModalContentComponent, + SidemenuComponent, + PageNotFoundComponent, + ConfirmationModalComponent, + StatusPageComponent, + ], + imports: [ + BrowserModule, + AppRoutingModule, + HttpClientModule, + AuthConfigModule, + AlertModule, + AuthConfigModule, + ApiModule.forRoot(changeConfig), + OAuthModule.forRoot({ resourceServer: { sendAccessToken: false } }), + BrowserAnimationsModule, + UserAdministrationModule, + DashboardModule, + SharedModule, + AppStarterModule, + ], + // { provide: ErrorHandler, useClass: SentryErrorHandler }, + providers: [ + { provide: RequestCache, useClass: RequestCacheService }, + httpInterceptorProviders, + { provide: OAuthStorage, useValue: localStorage }, + LoadingIndicatorService, + { provide: RouteReuseStrategy, useClass: AppRouteReuseStrategy }, + ], + bootstrap: [AppComponent] }) export class AppModule {} diff --git a/src/app/guards/auth.guard.ts b/src/app/guards/auth.guard.ts index 54ede0f..645819c 100644 --- a/src/app/guards/auth.guard.ts +++ b/src/app/guards/auth.guard.ts @@ -18,7 +18,7 @@ import { Injectable } from '@angular/core'; -import { CanActivate, UrlTree } from '@angular/router'; +import { UrlTree } from '@angular/router'; import { Observable } from 'rxjs'; import { AuthService } from '../services/auth.service'; @@ -28,7 +28,7 @@ import { AuthService } from '../services/auth.service'; @Injectable({ providedIn: 'root', }) -export class AuthGuard implements CanActivate { +export class AuthGuard { roles: string = ''; constructor(private authService: AuthService) {} diff --git a/src/app/guards/edit-user.can-activate.guard.ts b/src/app/guards/edit-user.can-activate.guard.ts index 81fc36e..29c927a 100644 --- a/src/app/guards/edit-user.can-activate.guard.ts +++ b/src/app/guards/edit-user.can-activate.guard.ts @@ -18,7 +18,7 @@ import { Injectable } from '@angular/core'; -import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router'; +import { ActivatedRouteSnapshot, Router } from '@angular/router'; import { Observable, of } from 'rxjs'; import { UsersService } from '../../../openapi/output'; import { catchError, map } from 'rxjs/operators'; @@ -26,7 +26,7 @@ import { TranslateService } from '@ngx-translate/core'; @Injectable({ providedIn: 'root', }) -export class EditUserCanActivateGuard implements CanActivate { +export class EditUserCanActivateGuard { constructor(private usersService: UsersService, private router: Router, private translateService: TranslateService) {} canActivate(route: ActivatedRouteSnapshot): Observable<boolean> { const userId = route.paramMap.get('userId'); diff --git a/src/app/guards/has-permissions.guard.ts b/src/app/guards/has-permissions.guard.ts index cc04673..1731a33 100644 --- a/src/app/guards/has-permissions.guard.ts +++ b/src/app/guards/has-permissions.guard.ts @@ -18,7 +18,7 @@ import { Inject, Injectable } from '@angular/core'; -import { ActivatedRouteSnapshot, CanActivate, Router, UrlTree } from '@angular/router'; +import { ActivatedRouteSnapshot, Router, UrlTree } from '@angular/router'; import { Observable } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { ACL_CONFIG, AclConfig } from '../modules/auth/injection-tokens'; @@ -29,7 +29,7 @@ import { map } from 'rxjs/operators'; @Injectable({ providedIn: 'root', }) -export class HasPermissionsGuard implements CanActivate { +export class HasPermissionsGuard { constructor( private readonly authService: AuthService, private readonly httpClient: HttpClient, diff --git a/src/app/helpers/helpers.ts b/src/app/helpers/helpers.ts index 7c03dbd..6b0c486 100644 --- a/src/app/helpers/helpers.ts +++ b/src/app/helpers/helpers.ts @@ -17,7 +17,7 @@ */ -import { FormArray, FormGroup } from '@angular/forms'; +import { UntypedFormArray, UntypedFormGroup } from '@angular/forms'; export function isNotUndefined<T>(val: T | undefined): val is T { return val !== undefined; @@ -27,7 +27,7 @@ export function isNotNull<T>(val: T | null): val is T { return val !== null; } -export function markAsDirtyAndValidate(formGroup: FormGroup): void { +export function markAsDirtyAndValidate(formGroup: UntypedFormGroup): void { Object.values(formGroup.controls).forEach(control => { control.markAsDirty(); control.updateValueAndValidity(); @@ -54,10 +54,10 @@ export function getRandomNumber(min: number, max: number) { return Math.floor(Math.random() * (max - min + 1)) + min; } -export function areFormControlsValid(form: FormGroup): boolean { +export function areFormControlsValid(form: UntypedFormGroup): boolean { const formControls = Object.keys(form.controls) .map(key => form.controls[key]) - .filter(control => !(control instanceof FormArray)); + .filter(control => !(control instanceof UntypedFormArray)); return formControls.find(control => control.invalid && (control.dirty || control.touched)) === undefined; } @@ -68,9 +68,9 @@ export function isString(value: any): boolean { export function resetSelectDefaultValue(cssSelector: string): void { setTimeout(() => { const element = document.querySelector(cssSelector); - if (element) { + if (element && document.querySelector(cssSelector)) { //@ts-ignore - document.querySelector(cssSelector)?.selectedIndex = -1; + document.querySelector(cssSelector).selectedIndex = -1; } }, 0); } diff --git a/src/app/modules/dashboard/apps/user-last-action-tile/user-last-action-tile.component.ts b/src/app/modules/dashboard/apps/user-last-action-tile/user-last-action-tile.component.ts index c03016f..ee477b5 100644 --- a/src/app/modules/dashboard/apps/user-last-action-tile/user-last-action-tile.component.ts +++ b/src/app/modules/dashboard/apps/user-last-action-tile/user-last-action-tile.component.ts @@ -61,12 +61,12 @@ export class UserLastActionTileComponent implements OnInit { .pipe(shareReplay({ refCount: true, bufferSize: 1 })); public actionFilterType$ = this.userActionsSettings$.pipe( - selectDistinctState<LastUserActionSettings, ActionFilter>(STATE_KEYS.FILTER_TYPE), + selectDistinctState<LastUserActionSettings, ActionFilter>(STATE_KEYS.FILTER_TYPE as keyof LastUserActionSettings), shareReplay({ refCount: true, bufferSize: 1 }), ); public actionIntervalType$ = this.userActionsSettings$.pipe( - selectDistinctState<LastUserActionSettings, ActionInterval>(STATE_KEYS.INTERVAL), + selectDistinctState<LastUserActionSettings, ActionInterval>(STATE_KEYS.INTERVAL as keyof LastUserActionSettings), shareReplay({ refCount: true, bufferSize: 1 }), ); diff --git a/src/app/modules/i18n/i18n.module.ts b/src/app/modules/i18n/i18n.module.ts index 52bedbe..a6a1da6 100644 --- a/src/app/modules/i18n/i18n.module.ts +++ b/src/app/modules/i18n/i18n.module.ts @@ -41,7 +41,7 @@ import { TranslateHttpLoader } from '@ngx-translate/http-loader'; export class I18nModule { constructor(translate: TranslateService) { translate.addLangs(['en', 'de']); - const browserLang = translate.getBrowserLang(); + const browserLang = translate.getBrowserLang() ?? 'en'; translate.use(browserLang.match(/en|de/) ? browserLang : 'en'); } } diff --git a/src/app/modules/user-administration/user-administration-form/user-administration-form.component.ts b/src/app/modules/user-administration/user-administration-form/user-administration-form.component.ts index 7df2700..2c93654 100644 --- a/src/app/modules/user-administration/user-administration-form/user-administration-form.component.ts +++ b/src/app/modules/user-administration/user-administration-form/user-administration-form.component.ts @@ -18,7 +18,7 @@ import { Component, OnInit } from '@angular/core'; -import { AbstractControl, FormControl, FormGroup, Validators } from '@angular/forms'; +import { AbstractControl, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms'; import { CreateUserRequest, Role, @@ -47,7 +47,7 @@ import { HistoryService } from '../../../services/history.service'; }) export class UserAdministrationFormComponent implements OnInit { public readonly userId: string | null; - public readonly keycloakUserForm: FormGroup; + public readonly keycloakUserForm: UntypedFormGroup; public user: UserResponse | undefined = undefined; public checkBoxes: { @@ -70,17 +70,17 @@ export class UserAdministrationFormComponent implements OnInit { ) { this.userId = this.route.snapshot.paramMap.get('userId'); - this.keycloakUserForm = new FormGroup({ - id: new FormControl({ value: null, disabled: true }), - username: new FormControl({ value: null, disabled: this.userId !== null }, [ + this.keycloakUserForm = new UntypedFormGroup({ + id: new UntypedFormControl({ value: null, disabled: true }), + username: new UntypedFormControl({ value: null, disabled: this.userId !== null }, [ Validators.required, Validators.maxLength(50), Validators.pattern(VALIDATION_PATTERN), Validators.pattern(NON_WHITE_SPACE_PATTERN), ]), - email: new FormControl(null, [Validators.email, Validators.required, Validators.pattern(VALIDATION_PATTERN)]), - firstName: new FormControl(null, [Validators.pattern(VALIDATION_PATTERN)]), - lastName: new FormControl(null, [Validators.pattern(VALIDATION_PATTERN)]), + email: new UntypedFormControl(null, [Validators.email, Validators.required, Validators.pattern(VALIDATION_PATTERN)]), + firstName: new UntypedFormControl(null, [Validators.pattern(VALIDATION_PATTERN)]), + lastName: new UntypedFormControl(null, [Validators.pattern(VALIDATION_PATTERN)]), }); } @@ -121,20 +121,20 @@ export class UserAdministrationFormComponent implements OnInit { } } - get userName(): FormControl { - return this.keycloakUserForm.get('username') as FormControl; + get userName(): UntypedFormControl { + return this.keycloakUserForm.get('username') as UntypedFormControl; } - get email(): FormControl { - return this.keycloakUserForm.get('email') as FormControl; + get email(): UntypedFormControl { + return this.keycloakUserForm.get('email') as UntypedFormControl; } - get firstName(): FormControl { - return this.keycloakUserForm.get('firstName') as FormControl; + get firstName(): UntypedFormControl { + return this.keycloakUserForm.get('firstName') as UntypedFormControl; } - get lastName(): FormControl { - return this.keycloakUserForm.get('lastName') as FormControl; + get lastName(): UntypedFormControl { + return this.keycloakUserForm.get('lastName') as UntypedFormControl; } public onSubmit(): void { @@ -212,7 +212,7 @@ export class UserAdministrationFormComponent implements OnInit { private updateUserData(userResponse: Observable<UserResponse>, roleResponse: Observable<RoleListResponse>): void { forkJoin([userResponse, roleResponse]) .pipe( - switchMap(([,]) => + switchMap(() => this.historyService.createUserHistoryAction({ type: ActionType.EDIT, entity: EntityType.USERADMINISTRATION, diff --git a/src/app/services/tileservice/tiles.service.ts b/src/app/services/tileservice/tiles.service.ts index 167e42a..acdce5b 100644 --- a/src/app/services/tileservice/tiles.service.ts +++ b/src/app/services/tileservice/tiles.service.ts @@ -19,7 +19,7 @@ import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { Observable } from 'rxjs'; +import { firstValueFrom, Observable } from 'rxjs'; import { environment } from 'src/environments/environment'; import { Tile, TilesListResponse } from '../../model/tile'; import { map } from 'rxjs/operators'; @@ -50,8 +50,8 @@ export class TilesService { * GET tile by id * @param id to get specific tile */ - getTileById(id: number): Promise<Tile> { - return this.httpClient.get<Tile>(urlTileApi + '/' + id).toPromise(); + getTileById(id: number): Promise<Tile | undefined> { + return firstValueFrom(this.httpClient.get<Tile>(urlTileApi + '/' + id)); } /** @@ -59,11 +59,11 @@ export class TilesService { * @param tile * @returns the new saved tile */ - saveTiles(tile: Tile): Promise<Tile> { + saveTiles(tile: Tile): Promise<Tile | undefined> { const options = { headers: new HttpHeaders({ 'Content-Type': 'application/json' }), }; - return this.httpClient.post<Tile>(urlTileApi, tile, options).toPromise(); + return firstValueFrom(this.httpClient.post<Tile>(urlTileApi, tile, options)); } /** @@ -71,11 +71,11 @@ export class TilesService { * @returns the updated hero * @param tile */ - updateTiles(tile: Tile): Promise<Tile> { + updateTiles(tile: Tile): Promise<Tile | undefined> { const options = { headers: new HttpHeaders({ 'Content-Type': 'application/json' }), }; - return this.httpClient.put<Tile>(urlTileApi + '/' + tile.id, tile, options).toPromise(); + return firstValueFrom(this.httpClient.put<Tile>(urlTileApi + '/' + tile.id, tile, options)); } /** diff --git a/src/app/services/user-settings.service.ts b/src/app/services/user-settings.service.ts index cbaa992..2f7e578 100644 --- a/src/app/services/user-settings.service.ts +++ b/src/app/services/user-settings.service.ts @@ -49,14 +49,14 @@ export class UserSettingsService { } selectDashboard = () => - this.getPreferences$().pipe(selectDistinctState<UserPreferencesModel, DashboardModel>(STATE_KEYS.DASHBOARD)); + this.getPreferences$().pipe(selectDistinctState<UserPreferencesModel, DashboardModel>(STATE_KEYS.DASHBOARD as keyof UserPreferencesModel)); selectDashboardApps = () => - this.selectDashboard().pipe(selectDistinctState<DashboardModel, DashboardAppsModel>(STATE_KEYS.APPS)); + this.selectDashboard().pipe(selectDistinctState<DashboardModel, DashboardAppsModel>(STATE_KEYS.APPS as keyof DashboardModel)); selectDashboardAvailableTiles = () => - this.selectDashboardApps().pipe(selectDistinctState<DashboardAppsModel, DashboardTileSettings[]>(STATE_KEYS.TILES)); + this.selectDashboardApps().pipe(selectDistinctState<DashboardAppsModel, DashboardTileSettings[]>(STATE_KEYS.TILES as keyof DashboardAppsModel)); selectLastUserAction = () => this.selectDashboardApps().pipe( - selectDistinctState<DashboardAppsModel, LastUserActionSettings>(STATE_KEYS.USER_ACTIONS), + selectDistinctState<DashboardAppsModel, LastUserActionSettings>(STATE_KEYS.USER_ACTIONS as keyof DashboardAppsModel), ); getPreferences(): void { @@ -106,6 +106,8 @@ export class UserSettingsService { } } -export function selectDistinctState<T, I>(key: string): UnaryFunction<Observable<T>, Observable<I>> { - return pipe(pluck<T, I>(key), distinctUntilChanged<I>()); +export function selectDistinctState<T, I>(key: keyof T): UnaryFunction<Observable<T>, Observable<I>> { + // return pipe(map(x => x[key] as I), distinctUntilChanged<I>()); + return pipe(pluck(key), map(value => value as unknown as I), distinctUntilChanged()); + // return pipe(pluck<T, I>(key), distinctUntilChanged<I>()); } diff --git a/src/test.ts b/src/test.ts index 36b9be6..c64afa3 100644 --- a/src/test.ts +++ b/src/test.ts @@ -23,20 +23,5 @@ import 'zone.js/testing'; import { getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; -declare const require: { - context( - path: string, - deep?: boolean, - filter?: RegExp, - ): { - keys(): string[]; - <T>(id: string): T; - }; -}; - // First, initialize the Angular testing environment. getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting()); -// Then we find all the tests. -const context = require.context('./', true, /\.spec\.ts$/); -// And load the modules. -context.keys().forEach(context); diff --git a/tsconfig.json b/tsconfig.json index 536748b..9cdbb46 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,15 +14,18 @@ "module": "es2020", "moduleResolution": "node", "importHelpers": true, - "target": "es2017", - "lib": ["es2018", "dom"], + "target": "ES2022", + "lib": [ + "es2020", + "dom" + ], "resolveJsonModule": true, - "esModuleInterop": true + "esModuleInterop": true, + "useDefineForClassFields": false }, "angularCompilerOptions": { "strictInjectionParameters": true, "strictTemplates": true, - "fullTemplateTypeCheck": true, - "enableIvy": true + "fullTemplateTypeCheck": true } } diff --git a/version.properties b/version.properties index b179fec..30ce569 100644 --- a/version.properties +++ b/version.properties @@ -4,7 +4,7 @@ major=0
minor=1
-patch=1
+patch=2
base_version=${major}.${minor}.${patch}
|