feat(console): JWT IDP, cleanup login policy, update deps (#2438)

* idp cleanup

* lint

* jwtidp service, create, detail, assets

* idp detail, info row

* detail actions

* delete idp, fix state change

* lint

* cli core

* cdk material

* chore(deps-dev): bump karma-jasmine-html-reporter in /console (#2446)

Bumps [karma-jasmine-html-reporter](https://github.com/dfederm/karma-jasmine-html-reporter) from 1.6.0 to 1.7.0.
- [Release notes](https://github.com/dfederm/karma-jasmine-html-reporter/releases)
- [Commits](https://github.com/dfederm/karma-jasmine-html-reporter/compare/v1.6.0...v1.7.0)

---
updated-dependencies:
- dependency-name: karma-jasmine-html-reporter
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* update deps

* lock

* disable actions, user grant link to user, add granted org desc

* lint

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
Max Peintner 2021-10-21 08:29:13 +02:00 committed by GitHub
parent dbec8e164d
commit 1c20ea5a50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
67 changed files with 2571 additions and 1947 deletions

View File

@ -8,18 +8,18 @@
"name": "console", "name": "console",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@angular/animations": "~12.2.7", "@angular/animations": "~12.2.8",
"@angular/cdk": "~12.2.7", "@angular/cdk": "~12.2.8",
"@angular/common": "~12.2.7", "@angular/common": "~12.2.8",
"@angular/compiler": "~12.2.7", "@angular/compiler": "~12.2.8",
"@angular/core": "~12.2.7", "@angular/core": "~12.2.8",
"@angular/forms": "~12.2.7", "@angular/forms": "~12.2.8",
"@angular/material": "^12.2.7", "@angular/material": "^12.2.8",
"@angular/material-moment-adapter": "^12.2.7", "@angular/material-moment-adapter": "^12.2.8",
"@angular/platform-browser": "~12.2.7", "@angular/platform-browser": "~12.2.8",
"@angular/platform-browser-dynamic": "~12.2.7", "@angular/platform-browser-dynamic": "~12.2.8",
"@angular/router": "~12.2.7", "@angular/router": "~12.2.8",
"@angular/service-worker": "~12.2.7", "@angular/service-worker": "~12.2.8",
"@grpc/grpc-js": "^1.3.2", "@grpc/grpc-js": "^1.3.2",
"@ngx-translate/core": "^13.0.0", "@ngx-translate/core": "^13.0.0",
"@ngx-translate/http-loader": "^6.0.0", "@ngx-translate/http-loader": "^6.0.0",
@ -31,7 +31,7 @@
"cors": "^2.8.5", "cors": "^2.8.5",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"google-proto-files": "^2.4.0", "google-proto-files": "^2.4.0",
"google-protobuf": "^3.17.2", "google-protobuf": "^3.18.0",
"grpc-web": "^1.2.1", "grpc-web": "^1.2.1",
"libphonenumber-js": "^1.9.34", "libphonenumber-js": "^1.9.34",
"moment": "^2.29.1", "moment": "^2.29.1",
@ -46,13 +46,13 @@
"zone.js": "~0.11.4" "zone.js": "~0.11.4"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "~12.2.7", "@angular-devkit/build-angular": "~12.2.8",
"@angular/cli": "~12.2.7", "@angular/cli": "~12.2.8",
"@angular/compiler-cli": "~12.2.7", "@angular/compiler-cli": "~12.2.8",
"@angular/language-service": "~12.2.7", "@angular/language-service": "~12.2.8",
"@types/jasmine": "~3.8.2", "@types/jasmine": "~3.9.1",
"@types/jasminewd2": "~2.0.10", "@types/jasminewd2": "~2.0.10",
"@types/node": "^16.7.6", "@types/node": "^16.10.2",
"codelyzer": "^6.0.0", "codelyzer": "^6.0.0",
"jasmine-core": "~3.9.0", "jasmine-core": "~3.9.0",
"jasmine-spec-reporter": "~7.0.0", "jasmine-spec-reporter": "~7.0.0",
@ -60,12 +60,12 @@
"karma-chrome-launcher": "~3.1.0", "karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~3.0.2", "karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "~4.0.0", "karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0", "karma-jasmine-html-reporter": "^1.7.0",
"prettier": "^2.3.1", "prettier": "^2.4.1",
"protractor": "~7.0.0", "protractor": "~7.0.0",
"stylelint": "^13.10.0", "stylelint": "^13.10.0",
"stylelint-config-standard": "^22.0.0", "stylelint-config-standard": "^22.0.0",
"stylelint-scss": "^3.20.1", "stylelint-scss": "^3.21.0",
"ts-node": "~10.2.1", "ts-node": "~10.2.1",
"tslint": "~6.1.3", "tslint": "~6.1.3",
"typescript": "^4.2.4" "typescript": "^4.2.4"
@ -85,12 +85,12 @@
} }
}, },
"node_modules/@angular-devkit/architect": { "node_modules/@angular-devkit/architect": {
"version": "0.1202.7", "version": "0.1202.8",
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1202.7.tgz", "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1202.8.tgz",
"integrity": "sha512-zqqw3h8jMDYsRrXUNY1J8xtUl6wmBO++yTka+CoEIFetNdLdoWmb5VpaA81i0aSBhXBgnBUUFvqZGdiI7BbV8A==", "integrity": "sha512-aPzwO3coRIuSjZa8FwFHy2y8OJarXG+afsqOk3muR6anvbdl+Av+m2RT8jjwj5J3D4N2eKZ7ob2q9HDUiHi4Pg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@angular-devkit/core": "12.2.7", "@angular-devkit/core": "12.2.8",
"rxjs": "6.6.7" "rxjs": "6.6.7"
}, },
"engines": { "engines": {
@ -118,16 +118,16 @@
"dev": true "dev": true
}, },
"node_modules/@angular-devkit/build-angular": { "node_modules/@angular-devkit/build-angular": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-12.2.8.tgz",
"integrity": "sha512-ZgbdmPEiJ8ShKg6CwNEuot1xCHTC68WfTr1ClUhvMvK9nsBydPdeKAYiqEho8gP4PuC0v3Hssuokfkqdb3Ms/A==", "integrity": "sha512-nntuVk7K4DR0cdw1lAFLQKG6CFXQfnA2Ykk48gsMVAW2FHitrjiRfDuBKitx+D7f+cEXAFUO2wymrp9fIT2Z5w==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@ampproject/remapping": "1.0.1", "@ampproject/remapping": "1.0.1",
"@angular-devkit/architect": "0.1202.7", "@angular-devkit/architect": "0.1202.8",
"@angular-devkit/build-optimizer": "0.1202.7", "@angular-devkit/build-optimizer": "0.1202.8",
"@angular-devkit/build-webpack": "0.1202.7", "@angular-devkit/build-webpack": "0.1202.8",
"@angular-devkit/core": "12.2.7", "@angular-devkit/core": "12.2.8",
"@babel/core": "7.14.8", "@babel/core": "7.14.8",
"@babel/generator": "7.14.8", "@babel/generator": "7.14.8",
"@babel/helper-annotate-as-pure": "7.14.5", "@babel/helper-annotate-as-pure": "7.14.5",
@ -139,7 +139,7 @@
"@babel/template": "7.14.5", "@babel/template": "7.14.5",
"@discoveryjs/json-ext": "0.5.3", "@discoveryjs/json-ext": "0.5.3",
"@jsdevtools/coverage-istanbul-loader": "3.0.5", "@jsdevtools/coverage-istanbul-loader": "3.0.5",
"@ngtools/webpack": "12.2.7", "@ngtools/webpack": "12.2.8",
"ansi-colors": "4.1.1", "ansi-colors": "4.1.1",
"babel-loader": "8.2.2", "babel-loader": "8.2.2",
"browserslist": "^4.9.1", "browserslist": "^4.9.1",
@ -255,9 +255,9 @@
"dev": true "dev": true
}, },
"node_modules/@angular-devkit/build-optimizer": { "node_modules/@angular-devkit/build-optimizer": {
"version": "0.1202.7", "version": "0.1202.8",
"resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.1202.7.tgz", "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.1202.8.tgz",
"integrity": "sha512-/VelwjOjQGZvXLwCuWVJ3MaTb1x0/UKYAqooEUW3yFkv6uXfpCCWywrIBZ3mYrU+m5ZeTjhDY4EFEd2WtBSroA==", "integrity": "sha512-GyzlbIM5RX5RhnX3wW0YV7K9ctoJQv5O7L/VUuDFpK8yaJjqjC+sZT+rnu6oPGFbPnYMx/BkkxzU2D0z98T4Mg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"source-map": "0.7.3", "source-map": "0.7.3",
@ -282,12 +282,12 @@
} }
}, },
"node_modules/@angular-devkit/build-webpack": { "node_modules/@angular-devkit/build-webpack": {
"version": "0.1202.7", "version": "0.1202.8",
"resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1202.7.tgz", "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1202.8.tgz",
"integrity": "sha512-DuWr6jEB/CBlmU1D+n0Jo6BMtYokbpBG0PZtnyzSvcwglIWIhxzFbCC7HTnlEzed+bmCSui7LtlGtkYcpFFsGw==", "integrity": "sha512-ryzstLoMcJfICToZv/RwqeVNNZ3tn71+S1JxNled469gWnsZAvABfexe8BOaT0b0MTo8h49ULeK1DU8McLY78Q==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@angular-devkit/architect": "0.1202.7", "@angular-devkit/architect": "0.1202.8",
"rxjs": "6.6.7" "rxjs": "6.6.7"
}, },
"engines": { "engines": {
@ -319,9 +319,9 @@
"dev": true "dev": true
}, },
"node_modules/@angular-devkit/core": { "node_modules/@angular-devkit/core": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-12.2.8.tgz",
"integrity": "sha512-WeLlDZaudpx10OGDPfVcWu/CaEWiWzAaLTUQz0Ww/yM+01FxR/P8yeH1sYAV1MS6d6KHvXGw7Lpf8PV7IA/zHA==", "integrity": "sha512-N13N1Lm7qllBXSVZYz4Khw75rnQnS3lu5QiJqlsaNklWgVfVz8jt99AAeGGvNGSLEbmZjlr35YLxu8ugD267Ug==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"ajv": "8.6.2", "ajv": "8.6.2",
@ -356,12 +356,12 @@
"dev": true "dev": true
}, },
"node_modules/@angular-devkit/schematics": { "node_modules/@angular-devkit/schematics": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-12.2.8.tgz",
"integrity": "sha512-E0hCFyyfbixjerf0Okt4ynC6F1dsT2Wl7MwAePe+wzPTHCnKIRTa2PQTxJzdWeTlSkQMkSK6ft2iyWOD/FODng==", "integrity": "sha512-SPiMFoCi1TpFXY6h1xGCakgdwT25gGHdbis1MuHE5yMcPLvhl/yr7EQVY1GY00/iMrgeslTHg/UPp4D6xHyQxA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@angular-devkit/core": "12.2.7", "@angular-devkit/core": "12.2.8",
"ora": "5.4.1", "ora": "5.4.1",
"rxjs": "6.6.7" "rxjs": "6.6.7"
}, },
@ -390,9 +390,9 @@
"dev": true "dev": true
}, },
"node_modules/@angular/animations": { "node_modules/@angular/animations": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-12.2.8.tgz",
"integrity": "sha512-ehlI4wnlHN213CiQNjYspoT9cEIrtOqVJfsPxUdzOCqCGBajVLxyqHb1skXtfOQXOIhznRS7P/d/4Ht7mWMizg==", "integrity": "sha512-uRK3EIIFMMIGBMIZscKt9p+H6lzuGXm+zokj5aZsB4Pu+rz8sc/Uxn26lTzq6o5GApiL4I75inK4Vs5f5RZKnA==",
"dependencies": { "dependencies": {
"tslib": "^2.2.0" "tslib": "^2.2.0"
}, },
@ -400,13 +400,13 @@
"node": "^12.14.1 || >=14.0.0" "node": "^12.14.1 || >=14.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/core": "12.2.7" "@angular/core": "12.2.8"
} }
}, },
"node_modules/@angular/cdk": { "node_modules/@angular/cdk": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-12.2.8.tgz",
"integrity": "sha512-OMPewaE1VxCSFkLDZQLFMkGQCQEvbizuVRzArZBB5xfxl98YMjntNQjlWH4rGbRhF+eZlpOrC+UAVnzTX2BzAw==", "integrity": "sha512-M0Y61o0yEVLMg+DSNsaDgiOifAV6OdumTgt2/kNoSuauPRWS0bkZJE58k3LR+cPi1Cho3UXELMKMOXZN9AhofA==",
"dependencies": { "dependencies": {
"tslib": "^2.2.0" "tslib": "^2.2.0"
}, },
@ -420,16 +420,16 @@
} }
}, },
"node_modules/@angular/cli": { "node_modules/@angular/cli": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-12.2.8.tgz",
"integrity": "sha512-FH34528+126Cxh/+1cBppBas8tExizKSJgbjpT3zgV6ijwHD7apT5zU9R1TyOhQPd6BhyaURo9Hnsjg49W4bRA==", "integrity": "sha512-MkTVkQbI+58W1izHKpGdpj0YAFfQnf7oQIyMom13/8l83yuOJdNzFTG/Lt77hx19qaKemEdmqTqdEOOrzaqggw==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@angular-devkit/architect": "0.1202.7", "@angular-devkit/architect": "0.1202.8",
"@angular-devkit/core": "12.2.7", "@angular-devkit/core": "12.2.8",
"@angular-devkit/schematics": "12.2.7", "@angular-devkit/schematics": "12.2.8",
"@schematics/angular": "12.2.7", "@schematics/angular": "12.2.8",
"@yarnpkg/lockfile": "1.1.0", "@yarnpkg/lockfile": "1.1.0",
"ansi-colors": "4.1.1", "ansi-colors": "4.1.1",
"debug": "4.3.2", "debug": "4.3.2",
@ -456,9 +456,9 @@
} }
}, },
"node_modules/@angular/common": { "node_modules/@angular/common": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular/common/-/common-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular/common/-/common-12.2.8.tgz",
"integrity": "sha512-Gug5a59c4NwfmSvO9Ya7DoYjl6ndK7nDuBoPSpp6IHTlNE8FY/BOd29qEp/lYJ4cAWxVk14+lonUPs6C+Szekw==", "integrity": "sha512-4nFlwC97wNEkB4vU2+xrbzpniuzmw8FG9zfqIeMFLLmceHLR7SQmxVKUrZylNXjT5TXXynpQzrpRAxQ1AEcTSA==",
"dependencies": { "dependencies": {
"tslib": "^2.2.0" "tslib": "^2.2.0"
}, },
@ -466,14 +466,14 @@
"node": "^12.14.1 || >=14.0.0" "node": "^12.14.1 || >=14.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/core": "12.2.7", "@angular/core": "12.2.8",
"rxjs": "^6.5.3 || ^7.0.0" "rxjs": "^6.5.3 || ^7.0.0"
} }
}, },
"node_modules/@angular/compiler": { "node_modules/@angular/compiler": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-12.2.8.tgz",
"integrity": "sha512-9X7Vrfg6dWfYKPAJLQYR5W7N8WnESg8PG07gNzHZtavETPrDIoX+Av/kQcEdPu14zTZE5NWx5u5TUByFgouQiQ==", "integrity": "sha512-11AswnrVeipKibK0Ra4n3TjZfr6QYpiNGPvT6XQB9NLgSthAPTa9T1Bige2yQhgyWNaZsOPko/jhhNS8ufSZCg==",
"dependencies": { "dependencies": {
"tslib": "^2.2.0" "tslib": "^2.2.0"
}, },
@ -482,9 +482,9 @@
} }
}, },
"node_modules/@angular/compiler-cli": { "node_modules/@angular/compiler-cli": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-12.2.8.tgz",
"integrity": "sha512-otsy3t3psrEWNbnjADaZVhBGBmBmBGxqknNoJ1+UeqSWf4z7su736jyzerxD684vmk08U6X2loxOuDr90idjPA==", "integrity": "sha512-KFGYsDATUCEoY4KEVcpxyT5S0k2T14hIlmBxzmzRhsCVi2ZLti5E2tnqQi2klKCFxmsk7VYd3kqWRJWBZieQtA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@babel/core": "^7.8.6", "@babel/core": "^7.8.6",
@ -512,7 +512,7 @@
"node": "^12.14.1 || >=14.0.0" "node": "^12.14.1 || >=14.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/compiler": "12.2.7", "@angular/compiler": "12.2.8",
"typescript": ">=4.2.3 <4.4" "typescript": ">=4.2.3 <4.4"
} }
}, },
@ -544,9 +544,9 @@
} }
}, },
"node_modules/@angular/core": { "node_modules/@angular/core": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular/core/-/core-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular/core/-/core-12.2.8.tgz",
"integrity": "sha512-no4mQ4O1euNH6odho1H27dcUmYBaNuyYvpPvv0wbb1pMT3Mm2J/uueePx/fvwg3nQ+vnk/yL1VCCqR7Mt62nHA==", "integrity": "sha512-ko7RJ8BImcMiI64Z8DM54ylkUwu2r/Mhf37BME0EEm+RIrH0KUVzrFOl2rMaxKBZUtY9qaxvVt43bZPrvN2acg==",
"dependencies": { "dependencies": {
"tslib": "^2.2.0" "tslib": "^2.2.0"
}, },
@ -559,9 +559,9 @@
} }
}, },
"node_modules/@angular/forms": { "node_modules/@angular/forms": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-12.2.8.tgz",
"integrity": "sha512-TtXnwE/bouEtGddaaSGytwCoyRN8YPNN/yf81fFM9LOGef4ZpABMtuMnsZxlDS+91AGpVSzvR511O5DG1BXc4Q==", "integrity": "sha512-/4w+ggQdUg9Ab5s2yhd9A06uevRsyoZ28vGgLU861PZUe5hR4+Gv3XdIuyEBOFvifanWzuxR0xL3okVZAKCXyA==",
"dependencies": { "dependencies": {
"tslib": "^2.2.0" "tslib": "^2.2.0"
}, },
@ -569,31 +569,31 @@
"node": "^12.14.1 || >=14.0.0" "node": "^12.14.1 || >=14.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/common": "12.2.7", "@angular/common": "12.2.8",
"@angular/core": "12.2.7", "@angular/core": "12.2.8",
"@angular/platform-browser": "12.2.7", "@angular/platform-browser": "12.2.8",
"rxjs": "^6.5.3 || ^7.0.0" "rxjs": "^6.5.3 || ^7.0.0"
} }
}, },
"node_modules/@angular/language-service": { "node_modules/@angular/language-service": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-12.2.8.tgz",
"integrity": "sha512-LCr4CUL1IFLfW9oHpv43/lhJ/kzCP4vhsGTmtOPup7Oc8/lVGUxvFrnIPx0o9qgSjT/ATbWr29+QY0bk02gsRQ==", "integrity": "sha512-/+GumfwB/j1ILrA5a8iv22FK+0M0GirnHjfOnqiRlcWPlmBkWSr+8RhjjT9TwcZsPS6QFxOBQUj1I5SDwG47Ug==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": "^12.14.1 || >=14.0.0" "node": "^12.14.1 || >=14.0.0"
} }
}, },
"node_modules/@angular/material": { "node_modules/@angular/material": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular/material/-/material-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular/material/-/material-12.2.8.tgz",
"integrity": "sha512-uaenRwRywvn0hMvRIsSaFIgUSrfHHZMr/uAC3uDZh7zAVmsbAqgzyrNo7i4L5vZxQa3q1EXn1fFPPOTrPfqdJw==", "integrity": "sha512-wRTaTZIGC9+2e8aft44V9Qqwp3PsR9AG0FeJ0spl8mdOlYEqMMyoRXjvMiWIjo2ywxHLoQgLXXsWn3ip2xnnVg==",
"dependencies": { "dependencies": {
"tslib": "^2.2.0" "tslib": "^2.2.0"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/animations": "^12.0.0 || ^13.0.0-0", "@angular/animations": "^12.0.0 || ^13.0.0-0",
"@angular/cdk": "12.2.7", "@angular/cdk": "12.2.8",
"@angular/common": "^12.0.0 || ^13.0.0-0", "@angular/common": "^12.0.0 || ^13.0.0-0",
"@angular/core": "^12.0.0 || ^13.0.0-0", "@angular/core": "^12.0.0 || ^13.0.0-0",
"@angular/forms": "^12.0.0 || ^13.0.0-0", "@angular/forms": "^12.0.0 || ^13.0.0-0",
@ -601,22 +601,22 @@
} }
}, },
"node_modules/@angular/material-moment-adapter": { "node_modules/@angular/material-moment-adapter": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular/material-moment-adapter/-/material-moment-adapter-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular/material-moment-adapter/-/material-moment-adapter-12.2.8.tgz",
"integrity": "sha512-nwDTQLP5al5LJ+Sl4fJBnesfRjzaLe4ZJZOy+Yl5F+UU/JJSOZuYs38KqTYb89SJDnWnGsx6SFk9eqjQjGfpXQ==", "integrity": "sha512-uk81sYrY4TfX889ZXo7l7iV8Idxww+sTHzZueq8xC9UTG35FM0KLSvUa/EqwR31lau+6rbgKFCGurQVhBvHrQA==",
"dependencies": { "dependencies": {
"tslib": "^2.2.0" "tslib": "^2.2.0"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/core": "^12.0.0 || ^13.0.0-0", "@angular/core": "^12.0.0 || ^13.0.0-0",
"@angular/material": "12.2.7", "@angular/material": "12.2.8",
"moment": "^2.18.1" "moment": "^2.18.1"
} }
}, },
"node_modules/@angular/platform-browser": { "node_modules/@angular/platform-browser": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-12.2.8.tgz",
"integrity": "sha512-mCQ5KqskMb98DLowyKfixH+ZpTjs5WuIZw9BqPc2knOlUxmfTuDRf5xDQn9Nur2ASF1wfJpaOogW685nB3ojnQ==", "integrity": "sha512-U79tj/fOIKYQbtrRhZm6IcHilUt3UEmloRh7hn2ozhYWOgJmTpygR6FIvvu1X7urAFoOMGY25UZjBNxbnabRNw==",
"dependencies": { "dependencies": {
"tslib": "^2.2.0" "tslib": "^2.2.0"
}, },
@ -624,9 +624,9 @@
"node": "^12.14.1 || >=14.0.0" "node": "^12.14.1 || >=14.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/animations": "12.2.7", "@angular/animations": "12.2.8",
"@angular/common": "12.2.7", "@angular/common": "12.2.8",
"@angular/core": "12.2.7" "@angular/core": "12.2.8"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"@angular/animations": { "@angular/animations": {
@ -635,9 +635,9 @@
} }
}, },
"node_modules/@angular/platform-browser-dynamic": { "node_modules/@angular/platform-browser-dynamic": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-12.2.8.tgz",
"integrity": "sha512-KxIotZR/NaM4r6OyjVxpPIg2AOk2jXpZy77g868tzqt8GQVJ6NXHoNTIAfQhEelr6bSIELm+mTqhDbNNIrXEnQ==", "integrity": "sha512-kHU4mbbrc1TW5Fz9OHRN5IQcFsmEm3zR5g5V1QTFoLjj6jnRao2xd5KZ8Owt0vcf+Qr4/v4kQGh2pIFZJFMaxQ==",
"dependencies": { "dependencies": {
"tslib": "^2.2.0" "tslib": "^2.2.0"
}, },
@ -645,16 +645,16 @@
"node": "^12.14.1 || >=14.0.0" "node": "^12.14.1 || >=14.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/common": "12.2.7", "@angular/common": "12.2.8",
"@angular/compiler": "12.2.7", "@angular/compiler": "12.2.8",
"@angular/core": "12.2.7", "@angular/core": "12.2.8",
"@angular/platform-browser": "12.2.7" "@angular/platform-browser": "12.2.8"
} }
}, },
"node_modules/@angular/router": { "node_modules/@angular/router": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular/router/-/router-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular/router/-/router-12.2.8.tgz",
"integrity": "sha512-3RzeBXbV0B+sdSskRYV07KsCgcS8dMmce6oUQrDskEnAmakzFo+R6OVKBFhPtTrqUstHVUsXr2kcoaaPVLquYw==", "integrity": "sha512-uYPT968IoGH01gUxHTG86hkFIS6G/sWr7QFXhuIzQHTnb+OeyaqWiw4iLc/QDAEPHJ7Wz6gNvCVHv9R2yod00g==",
"dependencies": { "dependencies": {
"tslib": "^2.2.0" "tslib": "^2.2.0"
}, },
@ -662,16 +662,16 @@
"node": "^12.14.1 || >=14.0.0" "node": "^12.14.1 || >=14.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/common": "12.2.7", "@angular/common": "12.2.8",
"@angular/core": "12.2.7", "@angular/core": "12.2.8",
"@angular/platform-browser": "12.2.7", "@angular/platform-browser": "12.2.8",
"rxjs": "^6.5.3 || ^7.0.0" "rxjs": "^6.5.3 || ^7.0.0"
} }
}, },
"node_modules/@angular/service-worker": { "node_modules/@angular/service-worker": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular/service-worker/-/service-worker-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular/service-worker/-/service-worker-12.2.8.tgz",
"integrity": "sha512-0Lz2y/1l2/mOmpuKVmzEFPOC+Gnj8LHJ6w/2DThXQdFxueYInBd+zF9i65TeVtytWe0ZtGEcLXX5GymaQhxsSg==", "integrity": "sha512-7/A6gSHrcUa7XHXp3WezUwqsMR4y8To1AvoZ7u0IGC/hbf9CpFk/RnMsItr8h4mxLQz7z214euEUAehYL9RunA==",
"dependencies": { "dependencies": {
"tslib": "^2.2.0" "tslib": "^2.2.0"
}, },
@ -682,8 +682,8 @@
"node": "^12.14.1 || >=14.0.0" "node": "^12.14.1 || >=14.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/common": "12.2.7", "@angular/common": "12.2.8",
"@angular/core": "12.2.7" "@angular/core": "12.2.8"
} }
}, },
"node_modules/@assemblyscript/loader": { "node_modules/@assemblyscript/loader": {
@ -2411,9 +2411,9 @@
} }
}, },
"node_modules/@ngtools/webpack": { "node_modules/@ngtools/webpack": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-12.2.8.tgz",
"integrity": "sha512-RX5UQA9Bwp/J5GPGtJiwEOQUdf/0UqdeIZtktOZJ4x3K676l//PCFxxxgGqi2qUR2eu/wLAyiDhvDwqDixsngQ==", "integrity": "sha512-utQrOXm4eS3BEQh2/y8zt5fVF9cuakfVQSPD/pWPKLqZsGc9wB3CHkrQED0EizsBfrsn5yLc3yPh3P8yGUmdRw==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": "^12.14.1 || >=14.0.0", "node": "^12.14.1 || >=14.0.0",
@ -2629,13 +2629,13 @@
"integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
}, },
"node_modules/@schematics/angular": { "node_modules/@schematics/angular": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-12.2.8.tgz",
"integrity": "sha512-wGqp0jC545Fwf0ydBkeoJHx9snFW+uqn40WwVqs/27Nh4AEHB5uzwzLY7Ykae95Zn802a61KPqSNWpez2fWWGA==", "integrity": "sha512-xkVcX6lTHC5JzDOjGdRAZutVVpxkRkT84vXtVlJwojyhNjAZg5dm/GC84+gVGfmVnO9vkUIYo/vGoN+/ydcSdA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@angular-devkit/core": "12.2.7", "@angular-devkit/core": "12.2.8",
"@angular-devkit/schematics": "12.2.7", "@angular-devkit/schematics": "12.2.8",
"jsonc-parser": "3.0.0" "jsonc-parser": "3.0.0"
}, },
"engines": { "engines": {
@ -2778,9 +2778,9 @@
"integrity": "sha512-MDpu7lit927cdLtBzTPUFjXGANFUnu5ThPqjygY8XmCyI/oDlIA0jAi4sffGOxYaLK2CCxAuU9wGxsgAQbA6FQ==" "integrity": "sha512-MDpu7lit927cdLtBzTPUFjXGANFUnu5ThPqjygY8XmCyI/oDlIA0jAi4sffGOxYaLK2CCxAuU9wGxsgAQbA6FQ=="
}, },
"node_modules/@types/jasmine": { "node_modules/@types/jasmine": {
"version": "3.8.2", "version": "3.9.1",
"resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.8.2.tgz", "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.9.1.tgz",
"integrity": "sha512-u5h7dqzy2XpXTzhOzSNQUQpKGFvROF8ElNX9P/TJvsHnTg/JvsAseVsGWQAQQldqanYaM+5kwxW909BBFAUYsg==", "integrity": "sha512-PVpjh8S8lqKFKurWSKdFATlfBHGPzgy0PoDdzQ+rr78jTQ0aacyh9YndzZcAUPxhk4kRujItFFGQdUJ7flHumw==",
"dev": true "dev": true
}, },
"node_modules/@types/jasminewd2": { "node_modules/@types/jasminewd2": {
@ -2825,9 +2825,9 @@
"dev": true "dev": true
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "16.7.6", "version": "16.10.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.6.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.2.tgz",
"integrity": "sha512-VESVNFoa/ahYA62xnLBjo5ur6gPsgEE5cNRy8SrdnkZ2nwJSW0kJ4ufbFr2zuU9ALtHM8juY53VcRoTA7htXSg==" "integrity": "sha512-zCclL4/rx+W5SQTzFs9wyvvyCwoK9QtBpratqz2IYJ3O8Umrn0m3nsTv0wQBk9sRGpvUe9CwPDrQFB10f1FIjQ=="
}, },
"node_modules/@types/normalize-package-data": { "node_modules/@types/normalize-package-data": {
"version": "2.4.0", "version": "2.4.0",
@ -7458,9 +7458,9 @@
} }
}, },
"node_modules/google-protobuf": { "node_modules/google-protobuf": {
"version": "3.17.3", "version": "3.18.0",
"resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.17.3.tgz", "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.18.0.tgz",
"integrity": "sha512-OVPzcSWIAJ+d5yiHyeaLrdufQtrvaBrF4JQg+z8ynTkbO3uFcujqXszTumqg1cGsAsjkWnI+M5B1xZ19yR4Wyg==" "integrity": "sha512-WlaQWRkUOo/lm9uTgNH6nk9IQt814RggWPzKBfnAVewOFzSzRUSmS1yUWRT6ixW1vS7er5p6tmLSmwzpPpmc8A=="
}, },
"node_modules/graceful-fs": { "node_modules/graceful-fs": {
"version": "4.2.6", "version": "4.2.6",
@ -9341,12 +9341,12 @@
} }
}, },
"node_modules/karma-jasmine-html-reporter": { "node_modules/karma-jasmine-html-reporter": {
"version": "1.6.0", "version": "1.7.0",
"resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.6.0.tgz", "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.7.0.tgz",
"integrity": "sha512-ELO9yf0cNqpzaNLsfFgXd/wxZVYkE2+ECUwhMHUD4PZ17kcsPsYsVyjquiRqyMn2jkd2sHt0IeMyAyq1MC23Fw==", "integrity": "sha512-pzum1TL7j90DTE86eFt48/s12hqwQuiD+e5aXx2Dc9wDEn2LfGq6RoAxEZZjFiN0RDSCOnosEKRZWxbQ+iMpQQ==",
"dev": true, "dev": true,
"peerDependencies": { "peerDependencies": {
"jasmine-core": ">=3.7.1", "jasmine-core": ">=3.8",
"karma": ">=0.9", "karma": ">=0.9",
"karma-jasmine": ">=1.1" "karma-jasmine": ">=1.1"
} }
@ -14174,9 +14174,9 @@
} }
}, },
"node_modules/prettier": { "node_modules/prettier": {
"version": "2.3.1", "version": "2.4.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.1.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz",
"integrity": "sha512-p+vNbgpLjif/+D+DwAZAbndtRrR0md0MwfmOVN9N+2RgyACMT+7tfaRnT+WDPkqnuVwleyuBIG2XBxKDme3hPA==", "integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==",
"dev": true, "dev": true,
"bin": { "bin": {
"prettier": "bin-prettier.js" "prettier": "bin-prettier.js"
@ -16895,9 +16895,9 @@
} }
}, },
"node_modules/stylelint-scss": { "node_modules/stylelint-scss": {
"version": "3.20.1", "version": "3.21.0",
"resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-3.20.1.tgz", "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-3.21.0.tgz",
"integrity": "sha512-OTd55O1TTAC5nGKkVmUDLpz53LlK39R3MImv1CfuvsK7/qugktqiZAeQLuuC4UBhzxCnsc7fp9u/gfRZwFAIkA==", "integrity": "sha512-CMI2wSHL+XVlNExpauy/+DbUcB/oUZLARDtMIXkpV/5yd8nthzylYd1cdHeDMJVBXeYHldsnebUX6MoV5zPW4A==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"lodash": "^4.17.15", "lodash": "^4.17.15",
@ -19718,12 +19718,12 @@
} }
}, },
"@angular-devkit/architect": { "@angular-devkit/architect": {
"version": "0.1202.7", "version": "0.1202.8",
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1202.7.tgz", "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1202.8.tgz",
"integrity": "sha512-zqqw3h8jMDYsRrXUNY1J8xtUl6wmBO++yTka+CoEIFetNdLdoWmb5VpaA81i0aSBhXBgnBUUFvqZGdiI7BbV8A==", "integrity": "sha512-aPzwO3coRIuSjZa8FwFHy2y8OJarXG+afsqOk3muR6anvbdl+Av+m2RT8jjwj5J3D4N2eKZ7ob2q9HDUiHi4Pg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@angular-devkit/core": "12.2.7", "@angular-devkit/core": "12.2.8",
"rxjs": "6.6.7" "rxjs": "6.6.7"
}, },
"dependencies": { "dependencies": {
@ -19745,16 +19745,16 @@
} }
}, },
"@angular-devkit/build-angular": { "@angular-devkit/build-angular": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-12.2.8.tgz",
"integrity": "sha512-ZgbdmPEiJ8ShKg6CwNEuot1xCHTC68WfTr1ClUhvMvK9nsBydPdeKAYiqEho8gP4PuC0v3Hssuokfkqdb3Ms/A==", "integrity": "sha512-nntuVk7K4DR0cdw1lAFLQKG6CFXQfnA2Ykk48gsMVAW2FHitrjiRfDuBKitx+D7f+cEXAFUO2wymrp9fIT2Z5w==",
"dev": true, "dev": true,
"requires": { "requires": {
"@ampproject/remapping": "1.0.1", "@ampproject/remapping": "1.0.1",
"@angular-devkit/architect": "0.1202.7", "@angular-devkit/architect": "0.1202.8",
"@angular-devkit/build-optimizer": "0.1202.7", "@angular-devkit/build-optimizer": "0.1202.8",
"@angular-devkit/build-webpack": "0.1202.7", "@angular-devkit/build-webpack": "0.1202.8",
"@angular-devkit/core": "12.2.7", "@angular-devkit/core": "12.2.8",
"@babel/core": "7.14.8", "@babel/core": "7.14.8",
"@babel/generator": "7.14.8", "@babel/generator": "7.14.8",
"@babel/helper-annotate-as-pure": "7.14.5", "@babel/helper-annotate-as-pure": "7.14.5",
@ -19766,7 +19766,7 @@
"@babel/template": "7.14.5", "@babel/template": "7.14.5",
"@discoveryjs/json-ext": "0.5.3", "@discoveryjs/json-ext": "0.5.3",
"@jsdevtools/coverage-istanbul-loader": "3.0.5", "@jsdevtools/coverage-istanbul-loader": "3.0.5",
"@ngtools/webpack": "12.2.7", "@ngtools/webpack": "12.2.8",
"ansi-colors": "4.1.1", "ansi-colors": "4.1.1",
"babel-loader": "8.2.2", "babel-loader": "8.2.2",
"browserslist": "^4.9.1", "browserslist": "^4.9.1",
@ -19842,9 +19842,9 @@
} }
}, },
"@angular-devkit/build-optimizer": { "@angular-devkit/build-optimizer": {
"version": "0.1202.7", "version": "0.1202.8",
"resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.1202.7.tgz", "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.1202.8.tgz",
"integrity": "sha512-/VelwjOjQGZvXLwCuWVJ3MaTb1x0/UKYAqooEUW3yFkv6uXfpCCWywrIBZ3mYrU+m5ZeTjhDY4EFEd2WtBSroA==", "integrity": "sha512-GyzlbIM5RX5RhnX3wW0YV7K9ctoJQv5O7L/VUuDFpK8yaJjqjC+sZT+rnu6oPGFbPnYMx/BkkxzU2D0z98T4Mg==",
"dev": true, "dev": true,
"requires": { "requires": {
"source-map": "0.7.3", "source-map": "0.7.3",
@ -19853,12 +19853,12 @@
} }
}, },
"@angular-devkit/build-webpack": { "@angular-devkit/build-webpack": {
"version": "0.1202.7", "version": "0.1202.8",
"resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1202.7.tgz", "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1202.8.tgz",
"integrity": "sha512-DuWr6jEB/CBlmU1D+n0Jo6BMtYokbpBG0PZtnyzSvcwglIWIhxzFbCC7HTnlEzed+bmCSui7LtlGtkYcpFFsGw==", "integrity": "sha512-ryzstLoMcJfICToZv/RwqeVNNZ3tn71+S1JxNled469gWnsZAvABfexe8BOaT0b0MTo8h49ULeK1DU8McLY78Q==",
"dev": true, "dev": true,
"requires": { "requires": {
"@angular-devkit/architect": "0.1202.7", "@angular-devkit/architect": "0.1202.8",
"rxjs": "6.6.7" "rxjs": "6.6.7"
}, },
"dependencies": { "dependencies": {
@ -19880,9 +19880,9 @@
} }
}, },
"@angular-devkit/core": { "@angular-devkit/core": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-12.2.8.tgz",
"integrity": "sha512-WeLlDZaudpx10OGDPfVcWu/CaEWiWzAaLTUQz0Ww/yM+01FxR/P8yeH1sYAV1MS6d6KHvXGw7Lpf8PV7IA/zHA==", "integrity": "sha512-N13N1Lm7qllBXSVZYz4Khw75rnQnS3lu5QiJqlsaNklWgVfVz8jt99AAeGGvNGSLEbmZjlr35YLxu8ugD267Ug==",
"dev": true, "dev": true,
"requires": { "requires": {
"ajv": "8.6.2", "ajv": "8.6.2",
@ -19911,12 +19911,12 @@
} }
}, },
"@angular-devkit/schematics": { "@angular-devkit/schematics": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-12.2.8.tgz",
"integrity": "sha512-E0hCFyyfbixjerf0Okt4ynC6F1dsT2Wl7MwAePe+wzPTHCnKIRTa2PQTxJzdWeTlSkQMkSK6ft2iyWOD/FODng==", "integrity": "sha512-SPiMFoCi1TpFXY6h1xGCakgdwT25gGHdbis1MuHE5yMcPLvhl/yr7EQVY1GY00/iMrgeslTHg/UPp4D6xHyQxA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@angular-devkit/core": "12.2.7", "@angular-devkit/core": "12.2.8",
"ora": "5.4.1", "ora": "5.4.1",
"rxjs": "6.6.7" "rxjs": "6.6.7"
}, },
@ -19939,32 +19939,32 @@
} }
}, },
"@angular/animations": { "@angular/animations": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-12.2.8.tgz",
"integrity": "sha512-ehlI4wnlHN213CiQNjYspoT9cEIrtOqVJfsPxUdzOCqCGBajVLxyqHb1skXtfOQXOIhznRS7P/d/4Ht7mWMizg==", "integrity": "sha512-uRK3EIIFMMIGBMIZscKt9p+H6lzuGXm+zokj5aZsB4Pu+rz8sc/Uxn26lTzq6o5GApiL4I75inK4Vs5f5RZKnA==",
"requires": { "requires": {
"tslib": "^2.2.0" "tslib": "^2.2.0"
} }
}, },
"@angular/cdk": { "@angular/cdk": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-12.2.8.tgz",
"integrity": "sha512-OMPewaE1VxCSFkLDZQLFMkGQCQEvbizuVRzArZBB5xfxl98YMjntNQjlWH4rGbRhF+eZlpOrC+UAVnzTX2BzAw==", "integrity": "sha512-M0Y61o0yEVLMg+DSNsaDgiOifAV6OdumTgt2/kNoSuauPRWS0bkZJE58k3LR+cPi1Cho3UXELMKMOXZN9AhofA==",
"requires": { "requires": {
"parse5": "^5.0.0", "parse5": "^5.0.0",
"tslib": "^2.2.0" "tslib": "^2.2.0"
} }
}, },
"@angular/cli": { "@angular/cli": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-12.2.8.tgz",
"integrity": "sha512-FH34528+126Cxh/+1cBppBas8tExizKSJgbjpT3zgV6ijwHD7apT5zU9R1TyOhQPd6BhyaURo9Hnsjg49W4bRA==", "integrity": "sha512-MkTVkQbI+58W1izHKpGdpj0YAFfQnf7oQIyMom13/8l83yuOJdNzFTG/Lt77hx19qaKemEdmqTqdEOOrzaqggw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@angular-devkit/architect": "0.1202.7", "@angular-devkit/architect": "0.1202.8",
"@angular-devkit/core": "12.2.7", "@angular-devkit/core": "12.2.8",
"@angular-devkit/schematics": "12.2.7", "@angular-devkit/schematics": "12.2.8",
"@schematics/angular": "12.2.7", "@schematics/angular": "12.2.8",
"@yarnpkg/lockfile": "1.1.0", "@yarnpkg/lockfile": "1.1.0",
"ansi-colors": "4.1.1", "ansi-colors": "4.1.1",
"debug": "4.3.2", "debug": "4.3.2",
@ -19983,25 +19983,25 @@
} }
}, },
"@angular/common": { "@angular/common": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular/common/-/common-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular/common/-/common-12.2.8.tgz",
"integrity": "sha512-Gug5a59c4NwfmSvO9Ya7DoYjl6ndK7nDuBoPSpp6IHTlNE8FY/BOd29qEp/lYJ4cAWxVk14+lonUPs6C+Szekw==", "integrity": "sha512-4nFlwC97wNEkB4vU2+xrbzpniuzmw8FG9zfqIeMFLLmceHLR7SQmxVKUrZylNXjT5TXXynpQzrpRAxQ1AEcTSA==",
"requires": { "requires": {
"tslib": "^2.2.0" "tslib": "^2.2.0"
} }
}, },
"@angular/compiler": { "@angular/compiler": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-12.2.8.tgz",
"integrity": "sha512-9X7Vrfg6dWfYKPAJLQYR5W7N8WnESg8PG07gNzHZtavETPrDIoX+Av/kQcEdPu14zTZE5NWx5u5TUByFgouQiQ==", "integrity": "sha512-11AswnrVeipKibK0Ra4n3TjZfr6QYpiNGPvT6XQB9NLgSthAPTa9T1Bige2yQhgyWNaZsOPko/jhhNS8ufSZCg==",
"requires": { "requires": {
"tslib": "^2.2.0" "tslib": "^2.2.0"
} }
}, },
"@angular/compiler-cli": { "@angular/compiler-cli": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-12.2.8.tgz",
"integrity": "sha512-otsy3t3psrEWNbnjADaZVhBGBmBmBGxqknNoJ1+UeqSWf4z7su736jyzerxD684vmk08U6X2loxOuDr90idjPA==", "integrity": "sha512-KFGYsDATUCEoY4KEVcpxyT5S0k2T14hIlmBxzmzRhsCVi2ZLti5E2tnqQi2klKCFxmsk7VYd3kqWRJWBZieQtA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/core": "^7.8.6", "@babel/core": "^7.8.6",
@ -20044,71 +20044,71 @@
} }
}, },
"@angular/core": { "@angular/core": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular/core/-/core-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular/core/-/core-12.2.8.tgz",
"integrity": "sha512-no4mQ4O1euNH6odho1H27dcUmYBaNuyYvpPvv0wbb1pMT3Mm2J/uueePx/fvwg3nQ+vnk/yL1VCCqR7Mt62nHA==", "integrity": "sha512-ko7RJ8BImcMiI64Z8DM54ylkUwu2r/Mhf37BME0EEm+RIrH0KUVzrFOl2rMaxKBZUtY9qaxvVt43bZPrvN2acg==",
"requires": { "requires": {
"tslib": "^2.2.0" "tslib": "^2.2.0"
} }
}, },
"@angular/forms": { "@angular/forms": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-12.2.8.tgz",
"integrity": "sha512-TtXnwE/bouEtGddaaSGytwCoyRN8YPNN/yf81fFM9LOGef4ZpABMtuMnsZxlDS+91AGpVSzvR511O5DG1BXc4Q==", "integrity": "sha512-/4w+ggQdUg9Ab5s2yhd9A06uevRsyoZ28vGgLU861PZUe5hR4+Gv3XdIuyEBOFvifanWzuxR0xL3okVZAKCXyA==",
"requires": { "requires": {
"tslib": "^2.2.0" "tslib": "^2.2.0"
} }
}, },
"@angular/language-service": { "@angular/language-service": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-12.2.8.tgz",
"integrity": "sha512-LCr4CUL1IFLfW9oHpv43/lhJ/kzCP4vhsGTmtOPup7Oc8/lVGUxvFrnIPx0o9qgSjT/ATbWr29+QY0bk02gsRQ==", "integrity": "sha512-/+GumfwB/j1ILrA5a8iv22FK+0M0GirnHjfOnqiRlcWPlmBkWSr+8RhjjT9TwcZsPS6QFxOBQUj1I5SDwG47Ug==",
"dev": true "dev": true
}, },
"@angular/material": { "@angular/material": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular/material/-/material-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular/material/-/material-12.2.8.tgz",
"integrity": "sha512-uaenRwRywvn0hMvRIsSaFIgUSrfHHZMr/uAC3uDZh7zAVmsbAqgzyrNo7i4L5vZxQa3q1EXn1fFPPOTrPfqdJw==", "integrity": "sha512-wRTaTZIGC9+2e8aft44V9Qqwp3PsR9AG0FeJ0spl8mdOlYEqMMyoRXjvMiWIjo2ywxHLoQgLXXsWn3ip2xnnVg==",
"requires": { "requires": {
"tslib": "^2.2.0" "tslib": "^2.2.0"
} }
}, },
"@angular/material-moment-adapter": { "@angular/material-moment-adapter": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular/material-moment-adapter/-/material-moment-adapter-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular/material-moment-adapter/-/material-moment-adapter-12.2.8.tgz",
"integrity": "sha512-nwDTQLP5al5LJ+Sl4fJBnesfRjzaLe4ZJZOy+Yl5F+UU/JJSOZuYs38KqTYb89SJDnWnGsx6SFk9eqjQjGfpXQ==", "integrity": "sha512-uk81sYrY4TfX889ZXo7l7iV8Idxww+sTHzZueq8xC9UTG35FM0KLSvUa/EqwR31lau+6rbgKFCGurQVhBvHrQA==",
"requires": { "requires": {
"tslib": "^2.2.0" "tslib": "^2.2.0"
} }
}, },
"@angular/platform-browser": { "@angular/platform-browser": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-12.2.8.tgz",
"integrity": "sha512-mCQ5KqskMb98DLowyKfixH+ZpTjs5WuIZw9BqPc2knOlUxmfTuDRf5xDQn9Nur2ASF1wfJpaOogW685nB3ojnQ==", "integrity": "sha512-U79tj/fOIKYQbtrRhZm6IcHilUt3UEmloRh7hn2ozhYWOgJmTpygR6FIvvu1X7urAFoOMGY25UZjBNxbnabRNw==",
"requires": { "requires": {
"tslib": "^2.2.0" "tslib": "^2.2.0"
} }
}, },
"@angular/platform-browser-dynamic": { "@angular/platform-browser-dynamic": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-12.2.8.tgz",
"integrity": "sha512-KxIotZR/NaM4r6OyjVxpPIg2AOk2jXpZy77g868tzqt8GQVJ6NXHoNTIAfQhEelr6bSIELm+mTqhDbNNIrXEnQ==", "integrity": "sha512-kHU4mbbrc1TW5Fz9OHRN5IQcFsmEm3zR5g5V1QTFoLjj6jnRao2xd5KZ8Owt0vcf+Qr4/v4kQGh2pIFZJFMaxQ==",
"requires": { "requires": {
"tslib": "^2.2.0" "tslib": "^2.2.0"
} }
}, },
"@angular/router": { "@angular/router": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular/router/-/router-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular/router/-/router-12.2.8.tgz",
"integrity": "sha512-3RzeBXbV0B+sdSskRYV07KsCgcS8dMmce6oUQrDskEnAmakzFo+R6OVKBFhPtTrqUstHVUsXr2kcoaaPVLquYw==", "integrity": "sha512-uYPT968IoGH01gUxHTG86hkFIS6G/sWr7QFXhuIzQHTnb+OeyaqWiw4iLc/QDAEPHJ7Wz6gNvCVHv9R2yod00g==",
"requires": { "requires": {
"tslib": "^2.2.0" "tslib": "^2.2.0"
} }
}, },
"@angular/service-worker": { "@angular/service-worker": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@angular/service-worker/-/service-worker-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@angular/service-worker/-/service-worker-12.2.8.tgz",
"integrity": "sha512-0Lz2y/1l2/mOmpuKVmzEFPOC+Gnj8LHJ6w/2DThXQdFxueYInBd+zF9i65TeVtytWe0ZtGEcLXX5GymaQhxsSg==", "integrity": "sha512-7/A6gSHrcUa7XHXp3WezUwqsMR4y8To1AvoZ7u0IGC/hbf9CpFk/RnMsItr8h4mxLQz7z214euEUAehYL9RunA==",
"requires": { "requires": {
"tslib": "^2.2.0" "tslib": "^2.2.0"
} }
@ -21329,9 +21329,9 @@
} }
}, },
"@ngtools/webpack": { "@ngtools/webpack": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-12.2.8.tgz",
"integrity": "sha512-RX5UQA9Bwp/J5GPGtJiwEOQUdf/0UqdeIZtktOZJ4x3K676l//PCFxxxgGqi2qUR2eu/wLAyiDhvDwqDixsngQ==", "integrity": "sha512-utQrOXm4eS3BEQh2/y8zt5fVF9cuakfVQSPD/pWPKLqZsGc9wB3CHkrQED0EizsBfrsn5yLc3yPh3P8yGUmdRw==",
"dev": true, "dev": true,
"requires": {} "requires": {}
}, },
@ -21507,13 +21507,13 @@
"integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
}, },
"@schematics/angular": { "@schematics/angular": {
"version": "12.2.7", "version": "12.2.8",
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-12.2.7.tgz", "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-12.2.8.tgz",
"integrity": "sha512-wGqp0jC545Fwf0ydBkeoJHx9snFW+uqn40WwVqs/27Nh4AEHB5uzwzLY7Ykae95Zn802a61KPqSNWpez2fWWGA==", "integrity": "sha512-xkVcX6lTHC5JzDOjGdRAZutVVpxkRkT84vXtVlJwojyhNjAZg5dm/GC84+gVGfmVnO9vkUIYo/vGoN+/ydcSdA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@angular-devkit/core": "12.2.7", "@angular-devkit/core": "12.2.8",
"@angular-devkit/schematics": "12.2.7", "@angular-devkit/schematics": "12.2.8",
"jsonc-parser": "3.0.0" "jsonc-parser": "3.0.0"
} }
}, },
@ -21637,9 +21637,9 @@
"integrity": "sha512-MDpu7lit927cdLtBzTPUFjXGANFUnu5ThPqjygY8XmCyI/oDlIA0jAi4sffGOxYaLK2CCxAuU9wGxsgAQbA6FQ==" "integrity": "sha512-MDpu7lit927cdLtBzTPUFjXGANFUnu5ThPqjygY8XmCyI/oDlIA0jAi4sffGOxYaLK2CCxAuU9wGxsgAQbA6FQ=="
}, },
"@types/jasmine": { "@types/jasmine": {
"version": "3.8.2", "version": "3.9.1",
"resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.8.2.tgz", "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.9.1.tgz",
"integrity": "sha512-u5h7dqzy2XpXTzhOzSNQUQpKGFvROF8ElNX9P/TJvsHnTg/JvsAseVsGWQAQQldqanYaM+5kwxW909BBFAUYsg==", "integrity": "sha512-PVpjh8S8lqKFKurWSKdFATlfBHGPzgy0PoDdzQ+rr78jTQ0aacyh9YndzZcAUPxhk4kRujItFFGQdUJ7flHumw==",
"dev": true "dev": true
}, },
"@types/jasminewd2": { "@types/jasminewd2": {
@ -21684,9 +21684,9 @@
"dev": true "dev": true
}, },
"@types/node": { "@types/node": {
"version": "16.7.6", "version": "16.10.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.6.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.2.tgz",
"integrity": "sha512-VESVNFoa/ahYA62xnLBjo5ur6gPsgEE5cNRy8SrdnkZ2nwJSW0kJ4ufbFr2zuU9ALtHM8juY53VcRoTA7htXSg==" "integrity": "sha512-zCclL4/rx+W5SQTzFs9wyvvyCwoK9QtBpratqz2IYJ3O8Umrn0m3nsTv0wQBk9sRGpvUe9CwPDrQFB10f1FIjQ=="
}, },
"@types/normalize-package-data": { "@types/normalize-package-data": {
"version": "2.4.0", "version": "2.4.0",
@ -25371,9 +25371,9 @@
} }
}, },
"google-protobuf": { "google-protobuf": {
"version": "3.17.3", "version": "3.18.0",
"resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.17.3.tgz", "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.18.0.tgz",
"integrity": "sha512-OVPzcSWIAJ+d5yiHyeaLrdufQtrvaBrF4JQg+z8ynTkbO3uFcujqXszTumqg1cGsAsjkWnI+M5B1xZ19yR4Wyg==" "integrity": "sha512-WlaQWRkUOo/lm9uTgNH6nk9IQt814RggWPzKBfnAVewOFzSzRUSmS1yUWRT6ixW1vS7er5p6tmLSmwzpPpmc8A=="
}, },
"graceful-fs": { "graceful-fs": {
"version": "4.2.6", "version": "4.2.6",
@ -26859,9 +26859,9 @@
} }
}, },
"karma-jasmine-html-reporter": { "karma-jasmine-html-reporter": {
"version": "1.6.0", "version": "1.7.0",
"resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.6.0.tgz", "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.7.0.tgz",
"integrity": "sha512-ELO9yf0cNqpzaNLsfFgXd/wxZVYkE2+ECUwhMHUD4PZ17kcsPsYsVyjquiRqyMn2jkd2sHt0IeMyAyq1MC23Fw==", "integrity": "sha512-pzum1TL7j90DTE86eFt48/s12hqwQuiD+e5aXx2Dc9wDEn2LfGq6RoAxEZZjFiN0RDSCOnosEKRZWxbQ+iMpQQ==",
"dev": true, "dev": true,
"requires": {} "requires": {}
}, },
@ -30427,9 +30427,9 @@
} }
}, },
"prettier": { "prettier": {
"version": "2.3.1", "version": "2.4.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.1.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz",
"integrity": "sha512-p+vNbgpLjif/+D+DwAZAbndtRrR0md0MwfmOVN9N+2RgyACMT+7tfaRnT+WDPkqnuVwleyuBIG2XBxKDme3hPA==", "integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==",
"dev": true "dev": true
}, },
"pretty-bytes": { "pretty-bytes": {
@ -32716,9 +32716,9 @@
} }
}, },
"stylelint-scss": { "stylelint-scss": {
"version": "3.20.1", "version": "3.21.0",
"resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-3.20.1.tgz", "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-3.21.0.tgz",
"integrity": "sha512-OTd55O1TTAC5nGKkVmUDLpz53LlK39R3MImv1CfuvsK7/qugktqiZAeQLuuC4UBhzxCnsc7fp9u/gfRZwFAIkA==", "integrity": "sha512-CMI2wSHL+XVlNExpauy/+DbUcB/oUZLARDtMIXkpV/5yd8nthzylYd1cdHeDMJVBXeYHldsnebUX6MoV5zPW4A==",
"dev": true, "dev": true,
"requires": { "requires": {
"lodash": "^4.17.15", "lodash": "^4.17.15",

View File

@ -10,18 +10,18 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "~12.2.7", "@angular/animations": "~12.2.8",
"@angular/cdk": "~12.2.7", "@angular/cdk": "~12.2.8",
"@angular/common": "~12.2.7", "@angular/common": "~12.2.8",
"@angular/compiler": "~12.2.7", "@angular/compiler": "~12.2.8",
"@angular/core": "~12.2.7", "@angular/core": "~12.2.8",
"@angular/forms": "~12.2.7", "@angular/forms": "~12.2.8",
"@angular/material": "^12.2.7", "@angular/material": "^12.2.8",
"@angular/material-moment-adapter": "^12.2.7", "@angular/material-moment-adapter": "^12.2.8",
"@angular/platform-browser": "~12.2.7", "@angular/platform-browser": "~12.2.8",
"@angular/platform-browser-dynamic": "~12.2.7", "@angular/platform-browser-dynamic": "~12.2.8",
"@angular/router": "~12.2.7", "@angular/router": "~12.2.8",
"@angular/service-worker": "~12.2.7", "@angular/service-worker": "~12.2.8",
"@grpc/grpc-js": "^1.3.2", "@grpc/grpc-js": "^1.3.2",
"@ngx-translate/core": "^13.0.0", "@ngx-translate/core": "^13.0.0",
"@ngx-translate/http-loader": "^6.0.0", "@ngx-translate/http-loader": "^6.0.0",
@ -33,7 +33,7 @@
"cors": "^2.8.5", "cors": "^2.8.5",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"google-proto-files": "^2.4.0", "google-proto-files": "^2.4.0",
"google-protobuf": "^3.17.2", "google-protobuf": "^3.18.0",
"grpc-web": "^1.2.1", "grpc-web": "^1.2.1",
"libphonenumber-js": "^1.9.34", "libphonenumber-js": "^1.9.34",
"moment": "^2.29.1", "moment": "^2.29.1",
@ -48,13 +48,13 @@
"zone.js": "~0.11.4" "zone.js": "~0.11.4"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "~12.2.7", "@angular-devkit/build-angular": "~12.2.8",
"@angular/cli": "~12.2.7", "@angular/cli": "~12.2.8",
"@angular/compiler-cli": "~12.2.7", "@angular/compiler-cli": "~12.2.8",
"@angular/language-service": "~12.2.7", "@angular/language-service": "~12.2.8",
"@types/jasmine": "~3.8.2", "@types/jasmine": "~3.9.1",
"@types/jasminewd2": "~2.0.10", "@types/jasminewd2": "~2.0.10",
"@types/node": "^16.7.6", "@types/node": "^16.10.2",
"codelyzer": "^6.0.0", "codelyzer": "^6.0.0",
"jasmine-core": "~3.9.0", "jasmine-core": "~3.9.0",
"jasmine-spec-reporter": "~7.0.0", "jasmine-spec-reporter": "~7.0.0",
@ -62,12 +62,12 @@
"karma-chrome-launcher": "~3.1.0", "karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~3.0.2", "karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "~4.0.0", "karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0", "karma-jasmine-html-reporter": "^1.7.0",
"prettier": "^2.3.1", "prettier": "^2.4.1",
"protractor": "~7.0.0", "protractor": "~7.0.0",
"stylelint": "^13.10.0", "stylelint": "^13.10.0",
"stylelint-config-standard": "^22.0.0", "stylelint-config-standard": "^22.0.0",
"stylelint-scss": "^3.20.1", "stylelint-scss": "^3.21.0",
"ts-node": "~10.2.1", "ts-node": "~10.2.1",
"tslint": "~6.1.3", "tslint": "~6.1.3",
"typescript": "^4.2.4" "typescript": "^4.2.4"

View File

@ -26,7 +26,7 @@ const routes: Routes = [
.then(m => m.GrantedProjectsModule), .then(m => m.GrantedProjectsModule),
canActivate: [AuthGuard, RoleGuard], canActivate: [AuthGuard, RoleGuard],
data: { data: {
roles: ['project.read'], roles: ['project.grant.read'],
}, },
}, },
{ {

View File

@ -10,6 +10,7 @@ import { ActivatedRoute, Router, RouterOutlet } from '@angular/router';
import { LangChangeEvent, TranslateService } from '@ngx-translate/core'; import { LangChangeEvent, TranslateService } from '@ngx-translate/core';
import { BehaviorSubject, from, Observable, of, Subject } from 'rxjs'; import { BehaviorSubject, from, Observable, of, Subject } from 'rxjs';
import { catchError, debounceTime, finalize, map, take, takeUntil } from 'rxjs/operators'; import { catchError, debounceTime, finalize, map, take, takeUntil } from 'rxjs/operators';
import { accountCard, adminLineAnimation, navAnimations, routeAnimations, toolbarAnimation } from './animations'; import { accountCard, adminLineAnimation, navAnimations, routeAnimations, toolbarAnimation } from './animations';
import { TextQueryMethod } from './proto/generated/zitadel/object_pb'; import { TextQueryMethod } from './proto/generated/zitadel/object_pb';
import { Org, OrgNameQuery, OrgQuery } from './proto/generated/zitadel/org_pb'; import { Org, OrgNameQuery, OrgQuery } from './proto/generated/zitadel/org_pb';
@ -159,6 +160,16 @@ export class AppComponent implements OnDestroy {
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/counter.svg'), this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/counter.svg'),
); );
this.matIconRegistry.addSvgIcon(
'mdi_openid',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/openid.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_jwt',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/jwt.svg'),
);
this.matIconRegistry.addSvgIcon( this.matIconRegistry.addSvgIcon(
'mdi_symbol', 'mdi_symbol',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/symbol.svg'), this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/symbol.svg'),

View File

@ -1,42 +1,42 @@
<div class="card" appOutsideClick (clickOutside)="closeCard($event)"> <div class="card" appOutsideClick (clickOutside)="closeCard($event)">
<app-avatar <app-avatar
*ngIf="user.human?.profile && (user.human?.profile?.displayName || (user.human?.profile?.firstName && user.human?.profile?.lastName))" *ngIf="user.human?.profile && (user.human?.profile?.displayName || (user.human?.profile?.firstName && user.human?.profile?.lastName))"
class="avatar" class="avatar" [forColor]="user.preferredLoginName" [avatarUrl]="user.human?.profile?.avatarUrl || ''"
[forColor]="user.preferredLoginName" [avatarUrl]="user.human?.profile?.avatarUrl || ''" [name]="user.human?.profile?.displayName ? user.human?.profile?.displayName : (user.human?.profile?.firstName + ' '+ user.human?.profile?.lastName)"
[name]="user.human?.profile?.displayName ? user.human?.profile?.displayName : (user.human?.profile?.firstName + ' '+ user.human?.profile?.lastName)" [size]="80">
[size]="80"> </app-avatar>
</app-avatar>
<span class="u-name">{{user.human?.profile?.displayName ? user.human?.profile?.displayName : 'A'}}</span> <span class="u-name">{{user.human?.profile?.displayName ? user.human?.profile?.displayName : 'A'}}</span>
<span class="u-email">{{user.human?.profile?.preferredLoginName}}</span> <span class="u-email">{{user?.preferredLoginName}}</span>
<span class="iamuser" *ngIf="iamuser">IAM USER</span> <span class="iamuser" *ngIf="iamuser">IAM USER</span>
<button color="primary" (click)="editUserProfile()" mat-stroked-button>{{'USER.EDITACCOUNT' | translate}}</button> <button color="primary" (click)="editUserProfile()" mat-stroked-button>{{'USER.EDITACCOUNT' | translate}}</button>
<div class="l-accounts"> <div class="l-accounts">
<mat-progress-bar *ngIf="loadingUsers" color="primary" mode="indeterminate"></mat-progress-bar> <mat-progress-bar *ngIf="loadingUsers" color="primary" mode="indeterminate"></mat-progress-bar>
<a class="row" *ngFor="let session of sessions" (click)="selectAccount(session.loginName)"> <a class="row" *ngFor="let session of sessions" (click)="selectAccount(session.loginName)">
<app-avatar *ngIf="session && session.displayName" class="small-avatar" [avatarUrl]="session.avatarUrl || ''" [forColor]="session.loginName" [size]="32"> <app-avatar *ngIf="session && session.displayName" class="small-avatar" [avatarUrl]="session.avatarUrl || ''"
</app-avatar> [forColor]="session.loginName" [size]="32">
</app-avatar>
<div class="col"> <div class="col">
<span class="user-title">{{session.displayName ? session.displayName : session.userName}} </span> <span class="user-title">{{session.displayName ? session.displayName : session.userName}} </span>
<span class="loginname">{{session.loginName}}</span> <span class="loginname">{{session.loginName}}</span>
<span class="email">{{'USER.STATE.'+session.authState | translate}}</span> <span class="email">{{'USER.STATE.'+session.authState | translate}}</span>
</div> </div>
<span class="fill-space"></span> <span class="fill-space"></span>
<mat-icon>keyboard_arrow_right</mat-icon> <mat-icon>keyboard_arrow_right</mat-icon>
</a> </a>
<a class="row" (click)="selectNewAccount()"> <a class="row" (click)="selectNewAccount()">
<div class="icon-wrapper"> <div class="icon-wrapper">
<i class="las la-user-plus"></i> <i class="las la-user-plus"></i>
</div> </div>
<span class="col"> <span class="col">
<span class="user-title">{{'USER.ADDACCOUNT' | translate}}</span> <span class="user-title">{{'USER.ADDACCOUNT' | translate}}</span>
</span> </span>
<span class="fill-space"></span> <span class="fill-space"></span>
<mat-icon>keyboard_arrow_right</mat-icon> <mat-icon>keyboard_arrow_right</mat-icon>
</a> </a>
</div> </div>
<button (click)="logout()" color="primary" mat-stroked-button>{{'MENU.LOGOUT' | translate}}</button> <button (click)="logout()" color="primary" mat-stroked-button>{{'MENU.LOGOUT' | translate}}</button>
</div> </div>

View File

@ -1,6 +1,6 @@
<div class="radio-button-wrapper"> <div class="radio-button-wrapper">
<ng-container *ngFor="let type of types"> <ng-container *ngFor="let type of types">
<input type="radio" (change)="emitChange()" [value]="type" [(ngModel)]="selected" [id]="type.prefix" /> <input class="app" type="radio" (change)="emitChange()" [value]="type" [(ngModel)]="selected" [id]="type.prefix" />
<label class="cnsl-type-radio-button" [for]="type.prefix"> <label class="cnsl-type-radio-button" [for]="type.prefix">
<div class="cnsl-type-radio-header" [ngStyle]="{'background': type.background}"> <div class="cnsl-type-radio-header" [ngStyle]="{'background': type.background}">
<span>{{type.prefix}}</span> <span>{{type.prefix}}</span>

View File

@ -12,13 +12,13 @@
$primary-color: mat.get-color-from-palette($primary, 500); $primary-color: mat.get-color-from-palette($primary, 500);
$is-dark-theme: map-get($theme, is-dark); $is-dark-theme: map-get($theme, is-dark);
input[type="radio"] { input[type="radio"].app {
appearance: none; appearance: none;
opacity: 0; opacity: 0;
display: none; display: none;
} }
input:checked + label { input.app:checked + label {
border-color: if($is-dark-theme, white, var(--grey)); border-color: if($is-dark-theme, white, var(--grey));
.cnsl-type-radio-header span { .cnsl-type-radio-header span {

View File

@ -1,45 +1,43 @@
<div class="groups"> <div class="groups">
<span class="co-header">{{ title }}</span> <span class="co-header">{{ title }}</span>
<span class="sub-header">{{ description }} {{'MEMBER.DOCSINFO' | translate}} <a <span class="sub-header">{{ description }} {{'MEMBER.DOCSINFO' | translate}} <a
href="https://docs.zitadel.ch/docs/manuals/admin-managers" target="_blank">ZITADEL Managers</a>. href="https://docs.zitadel.ch/docs/manuals/admin-managers" target="_blank">ZITADEL Managers</a>.
</span> </span>
<div class="people"> <div class="people">
<div class="img-list" [@cardAnimation]="totalResult"> <div class="img-list" [@cardAnimation]="totalResult">
<mat-spinner class="spinner" diameter="20" *ngIf="loading"></mat-spinner> <mat-spinner class="spinner" diameter="20" *ngIf="loading"></mat-spinner>
<ng-container *ngIf="totalResult < 10; else compact"> <ng-container *ngIf="totalResult < 10; else compact">
<ng-container *ngFor="let member of membersSubject | async; index as i"> <ng-container *ngFor="let member of membersSubject | async; index as i">
<div @animate (click)="emitShowDetail()" class="avatar-circle" <div @animate (click)="emitShowDetail()" class="avatar-circle"
matTooltip="{{ member.displayName }} | {{member.rolesList?.join(' ')}}" matTooltip="{{ member.displayName }} | {{member.rolesList?.join(' ')}}" [ngStyle]="{'z-index': 100 - i}">
[ngStyle]="{'z-index': 100 - i}"> <app-avatar *ngIf="member && member.displayName && member.firstName && member.lastName; else cog"
<app-avatar class="avatar dontcloseonclick" [avatarUrl]="member.avatarUrl|| ''" [forColor]="member?.userName"
*ngIf="member && member.displayName && member.firstName && member.lastName; else cog" [forColor]="member?.preferredLoginName"
class="avatar dontcloseonclick" [avatarUrl]="member.avatarUrl|| ''" [forColor]="member?.userName"[forColor]="member?.preferredLoginName" [name]="member.displayName ? member.displayName : (member.firstName + ' '+ member.lastName)" [size]="32">
[name]="member.displayName ? member.displayName : (member.firstName + ' '+ member.lastName)" </app-avatar>
[size]="32"> <ng-template #cog>
</app-avatar> <div class="sa-icon">
<ng-template #cog> <i class="las la-user-cog"></i>
<div class="sa-icon"> </div>
<i class="las la-user-cog"></i>
</div>
</ng-template>
</div>
</ng-container>
</ng-container>
<ng-template #compact>
<div (click)="emitShowDetail()" class="avatar-circle" matTooltip="Click to show detail">
<span>{{totalResult}}</span>
</div>
</ng-template> </ng-template>
<button class="add-img" (click)="emitAddMember()" [disabled]="disabled" mat-icon-button </div>
matTooltip="{{'ACTIONS.ADD' | translate}}" aria-label="Edit contributors"> </ng-container>
<mat-icon>add</mat-icon> </ng-container>
</button> <ng-template #compact>
<span class="fill-space"></span> <div (click)="emitShowDetail()" class="avatar-circle" matTooltip="Click to show detail">
<button matTooltip="{{'ACTIONS.REFRESH' | translate}}" class="refresh-img" (click)="emitRefresh()" <span>{{totalResult}}</span>
[disabled]="disabled" mat-icon-button aria-label="refresh contributors">
<mat-icon class="icon">refresh</mat-icon>
</button>
</div> </div>
</ng-template>
<button class="add-img" (click)="emitAddMember()" [disabled]="disabled" mat-icon-button
matTooltip="{{'ACTIONS.ADD' | translate}}" aria-label="Edit contributors">
<mat-icon>add</mat-icon>
</button>
<span class="fill-space"></span>
<button matTooltip="{{'ACTIONS.REFRESH' | translate}}" class="refresh-img" (click)="emitRefresh()" mat-icon-button
aria-label="refresh contributors">
<mat-icon class="icon">refresh</mat-icon>
</button>
</div> </div>
</div>
</div> </div>

View File

@ -7,9 +7,16 @@
</div> </div>
<div class="detail-right"> <div class="detail-right">
<div class="head"> <div class="head">
<h1>{{ title }}</h1> <div class="top-view">
<p class="desc">{{ description }}</p> <div>
<ng-content></ng-content> <h1>{{ title }}</h1>
<p class="head-desc">{{ description }}</p>
</div>
<div class="actions-wrap">
<ng-content select="[actions]"></ng-content>
</div>
</div>
<ng-content></ng-content>
</div> </div>
</div> </div>
</div> </div>

View File

@ -46,15 +46,26 @@
.head { .head {
margin-bottom: 2rem; margin-bottom: 2rem;
h1 { .top-view {
font-size: 1.5rem; display: flex;
margin-top: 10px; justify-content: space-between;
} }
.desc { div {
display: block; h1 {
font-size: .9rem; font-size: 1.5rem;
color: var(--grey); margin-top: 10px;
}
.head-desc {
display: block;
font-size: .9rem;
color: var(--grey);
}
}
.actions-wrap {
padding-top: .5rem;
} }
} }
} }

View File

@ -9,78 +9,147 @@
</div> </div>
<h1>{{'IDP.CREATE.TITLE' | translate}}</h1> <h1>{{'IDP.CREATE.TITLE' | translate}}</h1>
<p>{{'IDP.CREATE.DESCRIPTION' | translate}}</p>
<mat-progress-bar *ngIf="loading" color="primary" mode="indeterminate"></mat-progress-bar> <mat-progress-bar *ngIf="loading" color="primary" mode="indeterminate"></mat-progress-bar>
<form (ngSubmit)="addIdp()"> <ng-container *ngIf="currentCreateStep === 1">
<ng-container [formGroup]="formGroup"> <p class="desc">{{'IDP.CREATE.DESCRIPTION' | translate}}</p>
<div class="content">
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.NAME' | translate }}</cnsl-label>
<input cnslInput formControlName="name" />
</cnsl-form-field>
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.ISSUER' | translate }}</cnsl-label>
<input cnslInput formControlName="issuer" />
</cnsl-form-field>
</div>
<cnsl-info-section class="auto-reg-info"> <cnsl-idp-type-radio [types]="idpTypes" (selectedType)="idpType = $event"
<div> [selected]="idpType"></cnsl-idp-type-radio>
<p class="auto-reg-desc">{{'IDP.AUTOREGISTER_DESC' | translate}}</p>
<mat-checkbox formControlName="autoRegister">
{{'IDP.AUTOREGISTER' | translate}}
</mat-checkbox>
</div>
</cnsl-info-section>
<div class="content"> <div class="actions">
<cnsl-form-field appearance="outline" class="formfield"> <button mat-raised-button [disabled]="!idpType" color="primary"
<cnsl-label>{{ 'IDP.CLIENTID' | translate }}</cnsl-label> (click)="currentCreateStep = 2">{{'ACTIONS.CONTINUE' | translate}}</button>
<input cnslInput formControlName="clientId" /> </div>
</cnsl-form-field> </ng-container>
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.CLIENTSECRET' | translate }}</cnsl-label>
<input cnslInput formControlName="clientSecret" />
</cnsl-form-field>
</div>
<div class="content">
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.SCOPESLIST' | translate }}</cnsl-label>
<mat-chip-list #chipScopesList aria-label="scope selection" *ngIf="scopesList">
<mat-chip class="chip" *ngFor="let scope of scopesList.value" selectable="false" removable
(removed)="removeScope(scope)">
{{scope}} <mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
<input cnslInput [matChipInputFor]="chipScopesList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes" [matChipInputAddOnBlur]="true"
(matChipInputTokenEnd)="addScope($event)">
</mat-chip-list>
</cnsl-form-field>
</div>
<div class="content">
<cnsl-form-field class="formfield" appearance="outline">
<cnsl-label>{{ 'IDP.IDPDISPLAYNAMMAPPING' | translate }}</cnsl-label>
<mat-select formControlName="idpDisplayNameMapping">
<mat-option *ngFor="let field of mappingFields" [value]="field">
{{ 'IDP.MAPPINGFIELD.'+field | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="formfield" appearance="outline">
<cnsl-label>{{ 'IDP.USERNAMEMAPPING' | translate }}</cnsl-label>
<mat-select formControlName="usernameMapping">
<mat-option *ngFor="let field of mappingFields" [value]="field">
{{ 'IDP.MAPPINGFIELD.'+field | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
</div>
</ng-container>
<button color="primary" mat-raised-button class="continue-button" [disabled]="formGroup.invalid" type="submit"> <ng-container *ngIf="currentCreateStep === 2 && idpType === OIDC">
<p class="desc">{{'IDP.OIDC.DESCRIPTION' | translate}}</p>
<form [formGroup]="oidcFormGroup" (ngSubmit)="addOIDCIdp()">
<div class="idp-content">
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.NAME' | translate }}</cnsl-label>
<input cnslInput formControlName="name" />
</cnsl-form-field>
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.ISSUER' | translate }}</cnsl-label>
<input cnslInput formControlName="issuer" />
</cnsl-form-field>
</div>
<cnsl-info-section class="auto-reg-info">
<div>
<p class="auto-reg-desc">{{'IDP.AUTOREGISTER_DESC' | translate}}</p>
<mat-checkbox formControlName="autoRegister">
{{'IDP.AUTOREGISTER' | translate}}
</mat-checkbox>
</div>
</cnsl-info-section>
<div class="idp-content">
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.CLIENTID' | translate }}</cnsl-label>
<input cnslInput formControlName="clientId" />
</cnsl-form-field>
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.CLIENTSECRET' | translate }}</cnsl-label>
<input cnslInput formControlName="clientSecret" />
</cnsl-form-field>
</div>
<div class="idp-content">
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.SCOPESLIST' | translate }}</cnsl-label>
<mat-chip-list #chipScopesList aria-label="scope selection" *ngIf="scopesList">
<mat-chip class="chip" *ngFor="let scope of scopesList.value" selectable="false" removable
(removed)="removeScope(scope)">
{{scope}} <mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
<input cnslInput [matChipInputFor]="chipScopesList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes" [matChipInputAddOnBlur]="true"
(matChipInputTokenEnd)="addScope($event)">
</mat-chip-list>
</cnsl-form-field>
</div>
<div class="idp-content">
<cnsl-form-field class="formfield" appearance="outline">
<cnsl-label>{{ 'IDP.IDPDISPLAYNAMMAPPING' | translate }}</cnsl-label>
<mat-select formControlName="idpDisplayNameMapping">
<mat-option *ngFor="let field of mappingFields" [value]="field">
{{ 'IDP.MAPPINGFIELD.'+field | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="formfield" appearance="outline">
<cnsl-label>{{ 'IDP.USERNAMEMAPPING' | translate }}</cnsl-label>
<mat-select formControlName="usernameMapping">
<mat-option *ngFor="let field of mappingFields" [value]="field">
{{ 'IDP.MAPPINGFIELD.'+field | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
</div>
<div class="actions">
<button color="primary" (click)="currentCreateStep = 1" mat-stroked-button class="back-button" type="button">
{{ 'ACTIONS.BACK' | translate }}
</button>
<button color="primary" mat-raised-button class="continue-button" [disabled]="oidcFormGroup.invalid" type="submit">
{{ 'ACTIONS.SAVE' | translate }} {{ 'ACTIONS.SAVE' | translate }}
</button>
</div>
</form>
</ng-container>
<ng-container *ngIf="currentCreateStep === 2 && idpType === JWT">
<p class="desc">{{'IDP.JWT.DESCRIPTION' | translate}}</p>
<form [formGroup]="jwtFormGroup" (ngSubmit)="addJWTIdp()">
<div class="idp-content">
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.NAME' | translate }}</cnsl-label>
<input cnslInput formControlName="jwtName" />
</cnsl-form-field>
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.JWT.HEADERNAME' | translate }}</cnsl-label>
<input cnslInput formControlName="jwtHeaderName" />
</cnsl-form-field>
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.ISSUER' | translate }}</cnsl-label>
<input cnslInput formControlName="jwtIssuer" />
</cnsl-form-field>
</div>
<cnsl-info-section class="auto-reg-info">
<div>
<p class="auto-reg-desc">{{'IDP.AUTOREGISTER_DESC' | translate}}</p>
<mat-checkbox formControlName="jwtAutoRegister">
{{'IDP.AUTOREGISTER' | translate}}
</mat-checkbox>
</div>
</cnsl-info-section>
<div class="idp-content">
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.JWT.JWTENDPOINT' | translate }}</cnsl-label>
<input cnslInput formControlName="jwtEndpoint" />
</cnsl-form-field>
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.JWT.JWTKEYSENDPOINT' | translate }}</cnsl-label>
<input cnslInput formControlName="jwtKeysEndpoint" />
</cnsl-form-field>
</div>
<div class="actions">
<button color="primary" (click)="currentCreateStep = 1" mat-stroked-button class="back-button" type="button">
{{ 'ACTIONS.BACK' | translate }}
</button> </button>
<button color="primary" mat-raised-button class="continue-button" [disabled]="jwtFormGroup.invalid" type="submit">
{{ 'ACTIONS.SAVE' | translate }}
</button>
</div>
</form> </form>
</ng-container>
</div> </div>

View File

@ -1,3 +1,7 @@
.desc {
color: var(--grey);
font-size: 14px;
}
.container { .container {
padding: 4rem 4rem 2rem 4rem; padding: 4rem 4rem 2rem 4rem;
@ -29,7 +33,6 @@
} }
.auto-reg-info { .auto-reg-info {
margin: 0 .5rem 1rem .5rem;
display: block; display: block;
width: 100%; width: 100%;
@ -38,7 +41,7 @@
} }
} }
.content { .idp-content {
display: flex; display: flex;
margin: 0 -.5rem; margin: 0 -.5rem;
flex-wrap: wrap; flex-wrap: wrap;
@ -51,7 +54,7 @@
} }
.formfield { .formfield {
flex: 1 0 auto; flex: 1;
margin: 0 .5rem; margin: 0 .5rem;
@media only screen and (max-width: 450px) { @media only screen and (max-width: 450px) {
@ -60,13 +63,17 @@
} }
} }
.continue-button { .actions {
margin-top: 3rem; button[mat-stroked-button] {
display: block; float: left;
padding: .5rem 4rem;
@media only screen and (max-width: 450px) {
margin-top: 1rem; margin-top: 1rem;
margin-bottom: 2rem; border-radius: .5rem;
}
button[mat-raised-button] {
float: right;
margin-top: 1rem;
border-radius: .5rem;
min-width: 100px;
} }
} }

View File

@ -6,14 +6,15 @@ import { MatChipInputEvent } from '@angular/material/chips';
import { ActivatedRoute, Params, Router } from '@angular/router'; import { ActivatedRoute, Params, Router } from '@angular/router';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { take } from 'rxjs/operators'; import { take } from 'rxjs/operators';
import { AddOIDCIDPRequest } from 'src/app/proto/generated/zitadel/admin_pb'; import { AddJWTIDPRequest, AddOIDCIDPRequest } from 'src/app/proto/generated/zitadel/admin_pb';
import { OIDCMappingField } from 'src/app/proto/generated/zitadel/idp_pb'; import { OIDCMappingField } from 'src/app/proto/generated/zitadel/idp_pb';
import { AddOrgOIDCIDPRequest } from 'src/app/proto/generated/zitadel/management_pb'; import { AddOrgJWTIDPRequest, AddOrgOIDCIDPRequest } from 'src/app/proto/generated/zitadel/management_pb';
import { AdminService } from 'src/app/services/admin.service'; import { AdminService } from 'src/app/services/admin.service';
import { ManagementService } from 'src/app/services/mgmt.service'; import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
import { PolicyComponentServiceType } from '../policies/policy-component-types.enum'; import { PolicyComponentServiceType } from '../policies/policy-component-types.enum';
import { JWT, OIDC, RadioItemIdpType } from './idptypes';
@Component({ @Component({
selector: 'app-idp-create', selector: 'app-idp-create',
@ -29,10 +30,23 @@ export class IdpCreateComponent implements OnInit, OnDestroy {
private subscription?: Subscription; private subscription?: Subscription;
public projectId: string = ''; public projectId: string = '';
public formGroup!: FormGroup; public oidcFormGroup!: FormGroup;
public createSteps: number = 1; public jwtFormGroup!: FormGroup;
public createSteps: number = 2;
public currentCreateStep: number = 1; public currentCreateStep: number = 1;
public loading: boolean = false; public loading: boolean = false;
public idpTypes: RadioItemIdpType[] = [
OIDC,
JWT,
];
OIDC: any = OIDC;
JWT: any = JWT;
public idpType!: RadioItemIdpType;
constructor( constructor(
private router: Router, private router: Router,
private route: ActivatedRoute, private route: ActivatedRoute,
@ -40,7 +54,7 @@ export class IdpCreateComponent implements OnInit, OnDestroy {
private injector: Injector, private injector: Injector,
private _location: Location, private _location: Location,
) { ) {
this.formGroup = new FormGroup({ this.oidcFormGroup = new FormGroup({
name: new FormControl('', [Validators.required]), name: new FormControl('', [Validators.required]),
clientId: new FormControl('', [Validators.required]), clientId: new FormControl('', [Validators.required]),
clientSecret: new FormControl('', [Validators.required]), clientSecret: new FormControl('', [Validators.required]),
@ -51,6 +65,16 @@ export class IdpCreateComponent implements OnInit, OnDestroy {
autoRegister: new FormControl(false), autoRegister: new FormControl(false),
}); });
this.jwtFormGroup = new FormGroup({
jwtName: new FormControl('', [Validators.required]),
jwtHeaderName: new FormControl('', [Validators.required]),
jwtIssuer: new FormControl('', [Validators.required]),
jwtEndpoint: new FormControl('', [Validators.required]),
jwtKeysEndpoint: new FormControl('', [Validators.required]),
jwtStylingType: new FormControl(0),
jwtAutoRegister: new FormControl(false),
});
this.route.data.pipe(take(1)).subscribe(data => { this.route.data.pipe(take(1)).subscribe(data => {
this.serviceType = data.serviceType; this.serviceType = data.serviceType;
switch (this.serviceType) { switch (this.serviceType) {
@ -82,7 +106,7 @@ export class IdpCreateComponent implements OnInit, OnDestroy {
this.projectId = projectid; this.projectId = projectid;
} }
public addIdp(): void { public addOIDCIdp(): void {
if (this.serviceType === PolicyComponentServiceType.MGMT) { if (this.serviceType === PolicyComponentServiceType.MGMT) {
const req = new AddOrgOIDCIDPRequest(); const req = new AddOrgOIDCIDPRequest();
@ -133,6 +157,56 @@ export class IdpCreateComponent implements OnInit, OnDestroy {
} }
} }
public addJWTIdp(): void {
if (this.serviceType === PolicyComponentServiceType.MGMT) {
const req = new AddOrgJWTIDPRequest();
req.setName(this.jwtName?.value);
req.setHeaderName(this.jwtHeaderName?.value);
req.setIssuer(this.jwtIssuer?.value);
req.setJwtEndpoint(this.jwtEndpoint?.value);
req.setKeysEndpoint(this.jwtKeysEndpoint?.value);
req.setAutoRegister(this.jwtAutoRegister?.value);
req.setStylingType(this.jwtStylingType?.value);
this.loading = true;
(this.service as ManagementService).addOrgJWTIDP(req).then((idp) => {
setTimeout(() => {
this.loading = false;
this.router.navigate([
(this.serviceType === PolicyComponentServiceType.MGMT ? 'org' :
this.serviceType === PolicyComponentServiceType.ADMIN ? 'iam' : ''),
'policy', 'login']);
}, 2000);
}).catch(error => {
this.toast.showError(error);
});
} else if (PolicyComponentServiceType.ADMIN) {
const req = new AddJWTIDPRequest();
req.setName(this.jwtName?.value);
req.setHeaderName(this.jwtHeaderName?.value);
req.setIssuer(this.jwtIssuer?.value);
req.setJwtEndpoint(this.jwtEndpoint?.value);
req.setKeysEndpoint(this.jwtKeysEndpoint?.value);
req.setAutoRegister(this.jwtAutoRegister?.value);
req.setStylingType(this.jwtStylingType?.value);
this.loading = true;
(this.service as AdminService).addJWTIDP(req).then((idp) => {
setTimeout(() => {
this.loading = false;
this.router.navigate([
(this.serviceType === PolicyComponentServiceType.MGMT ? 'org' :
this.serviceType === PolicyComponentServiceType.ADMIN ? 'iam' : ''),
'policy', 'login']);
}, 2000);
}).catch(error => {
this.toast.showError(error);
});
}
}
public close(): void { public close(): void {
this._location.back(); this._location.back();
} }
@ -162,35 +236,62 @@ export class IdpCreateComponent implements OnInit, OnDestroy {
} }
public get name(): AbstractControl | null { public get name(): AbstractControl | null {
return this.formGroup.get('name'); return this.oidcFormGroup.get('name');
} }
public get clientId(): AbstractControl | null { public get clientId(): AbstractControl | null {
return this.formGroup.get('clientId'); return this.oidcFormGroup.get('clientId');
} }
public get clientSecret(): AbstractControl | null { public get clientSecret(): AbstractControl | null {
return this.formGroup.get('clientSecret'); return this.oidcFormGroup.get('clientSecret');
} }
public get issuer(): AbstractControl | null { public get issuer(): AbstractControl | null {
return this.formGroup.get('issuer'); return this.oidcFormGroup.get('issuer');
} }
public get scopesList(): AbstractControl | null { public get scopesList(): AbstractControl | null {
return this.formGroup.get('scopesList'); return this.oidcFormGroup.get('scopesList');
} }
public get autoRegister(): AbstractControl | null { public get autoRegister(): AbstractControl | null {
return this.formGroup.get('autoRegister'); return this.oidcFormGroup.get('autoRegister');
} }
public get idpDisplayNameMapping(): AbstractControl | null { public get idpDisplayNameMapping(): AbstractControl | null {
return this.formGroup.get('idpDisplayNameMapping'); return this.oidcFormGroup.get('idpDisplayNameMapping');
} }
public get usernameMapping(): AbstractControl | null { public get usernameMapping(): AbstractControl | null {
return this.formGroup.get('usernameMapping'); return this.oidcFormGroup.get('usernameMapping');
} }
public get jwtName(): AbstractControl | null {
return this.jwtFormGroup.get('jwtName');
}
public get jwtHeaderName(): AbstractControl | null {
return this.jwtFormGroup.get('jwtHeaderName');
}
public get jwtIssuer(): AbstractControl | null {
return this.jwtFormGroup.get('jwtIssuer');
}
public get jwtEndpoint(): AbstractControl | null {
return this.jwtFormGroup.get('jwtEndpoint');
}
public get jwtKeysEndpoint(): AbstractControl | null {
return this.jwtFormGroup.get('jwtKeysEndpoint');
}
public get jwtStylingType(): AbstractControl | null {
return this.jwtFormGroup.get('jwtStylingType');
}
public get jwtAutoRegister(): AbstractControl | null {
return this.jwtFormGroup.get('jwtAutoRegister');
}
} }

View File

@ -14,9 +14,10 @@ import { InputModule } from 'src/app/modules/input/input.module';
import { InfoSectionModule } from '../info-section/info-section.module'; import { InfoSectionModule } from '../info-section/info-section.module';
import { IdpCreateRoutingModule } from './idp-create-routing.module'; import { IdpCreateRoutingModule } from './idp-create-routing.module';
import { IdpCreateComponent } from './idp-create.component'; import { IdpCreateComponent } from './idp-create.component';
import { IdpTypeRadioComponent } from './idp-type-radio/idp-type-radio.component';
@NgModule({ @NgModule({
declarations: [IdpCreateComponent], declarations: [IdpCreateComponent, IdpTypeRadioComponent],
imports: [ imports: [
IdpCreateRoutingModule, IdpCreateRoutingModule,
CommonModule, CommonModule,

View File

@ -0,0 +1,12 @@
<div class="idp-radio-button-wrapper">
<ng-container *ngFor="let type of types">
<input class="idp mat-elevation-z1" type="radio" (change)="emitChange()" [value]="type" [(ngModel)]="selected" [id]="type.titleI18nKey" />
<label class="cnsl-idp-type-radio-button" [for]="type.titleI18nKey">
<div class="cnsl-idp-type-radio-header" >
<mat-icon class="icon" *ngIf="type.mdi" [svgIcon]="type.mdi"></mat-icon>
<span class="fill-space"></span>
<span>{{type.titleI18nKey | translate}}</span>
</div>
</label>
</ng-container>
</div>

View File

@ -0,0 +1,60 @@
@use '~@angular/material' as mat;
.idp-radio-button-wrapper {
display: flex;
flex-direction: column;
flex-wrap: wrap;
margin: 0 -.5rem;
max-width: 500px;
}
@mixin idp-type-radio-theme($theme) {
$primary: map-get($theme, primary);
$primary-color: mat.get-color-from-palette($primary, 500);
$primary-color-light: mat.get-color-from-palette($primary, 300);
$primary-color-dark: mat.get-color-from-palette($primary, 700);
$primary-color-contrast: mat.get-color-from-palette($primary, default-contrast);
$is-dark-theme: map-get($theme, is-dark);
$back: map-get($theme, background);
$fg: map-get($theme, foreground);
$cardback: map-get($back, card);
$text: map-get($fg, text);
input[type="radio"].idp {
appearance: none;
opacity: 0;
display: none;
}
.cnsl-idp-type-radio-button {
margin: .5rem;
border-radius: .5rem;
display: flex;
flex-direction: column;
cursor: pointer;
position: relative;
box-shadow: inset 0 0 6px rgba(0, 0, 0, .1);
background-color: $cardback;
.cnsl-idp-type-radio-header {
display: flex;
align-items: center;
padding: 0 1rem;
span {
margin: 1rem 0;
font-size: 1.1rem;
}
.fill-space {
flex: 1;
}
}
}
input.idp:checked + label {
border: 4px solid if($is-dark-theme, $primary-color-dark, $primary-color-light) !important;
background: $primary-color !important;
color: $primary-color-contrast !important;
}
}

View File

@ -0,0 +1,18 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { OIDC, RadioItemIdpType } from '../idptypes';
@Component({
selector: 'cnsl-idp-type-radio',
templateUrl: './idp-type-radio.component.html',
styleUrls: ['./idp-type-radio.component.scss'],
})
export class IdpTypeRadioComponent {
@Input() selected: RadioItemIdpType = OIDC;
@Input() types!: RadioItemIdpType[];
@Output() selectedType: EventEmitter<RadioItemIdpType> = new EventEmitter();
public emitChange(): void {
this.selectedType.emit(this.selected);
}
}

View File

@ -0,0 +1,23 @@
export enum IdpCreateType {
OIDC = 'OIDC',
JWT = 'JWT',
}
export interface RadioItemIdpType {
createType: IdpCreateType;
titleI18nKey: string;
mdi?: string;
}
export const OIDC = {
titleI18nKey: 'IDP.OIDC.TITLE',
mdi: 'mdi_openid',
createType: IdpCreateType.OIDC,
};
export const JWT = {
titleI18nKey: 'IDP.JWT.TITLE',
mdi: 'mdi_jwt',
createType: IdpCreateType.JWT,
};

View File

@ -38,8 +38,14 @@
<ng-container matColumnDef="name"> <ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> {{ 'IDP.NAME' | translate }} </th> <th mat-header-cell *matHeaderCellDef> {{ 'IDP.NAME' | translate }} </th>
<td [routerLink]="routerLinkForRow(idp)" mat-cell *matCellDef="let idp"><strong>{{idp?.name}}</strong> <td [routerLink]="routerLinkForRow(idp)" mat-cell *matCellDef="let idp">
</td> <div class="name-col">
<span>{{idp?.name}}</span>
<mat-icon matTooltip="{{'IDP.OIDC.TITLE' | translate}}" *ngIf="idp?.oidcConfig" svgIcon="mdi_openid"></mat-icon>
<mat-icon matTooltip="{{'IDP.JWT.TITLE' | translate}}" *ngIf="idp?.jwtConfig" svgIcon="mdi_jwt"></mat-icon>
</div>
</td>
</ng-container> </ng-container>
<ng-container matColumnDef="config"> <ng-container matColumnDef="config">
@ -62,7 +68,7 @@
</ng-container> </ng-container>
<ng-container matColumnDef="dates"> <ng-container matColumnDef="dates">
<th mat-header-cell *matHeaderCellDef> {{ 'IDP.CREATIONDATE' | translate }} </th> <th mat-header-cell *matHeaderCellDef> {{ 'IDP.DATES' | translate }} </th>
<td [routerLink]="routerLinkForRow(idp)" class="pointer" mat-cell *matCellDef="let idp"> <td [routerLink]="routerLinkForRow(idp)" class="pointer" mat-cell *matCellDef="let idp">
<div class="date-block"> <div class="date-block">
<span class="date-sub">{{ 'IDP.CREATIONDATE' | translate }}:</span> <span class="date-sub">{{ 'IDP.CREATIONDATE' | translate }}:</span>
@ -76,7 +82,7 @@
</ng-container> </ng-container>
<ng-container matColumnDef="owner"> <ng-container matColumnDef="owner">
<th mat-header-cell *matHeaderCellDef> {{ 'IDP.TYPE' | translate }} </th> <th mat-header-cell *matHeaderCellDef> {{ 'IDP.OWNER' | translate }} </th>
<td [routerLink]="routerLinkForRow(idp)" class="pointer" mat-cell *matCellDef="let idp"> <td [routerLink]="routerLinkForRow(idp)" class="pointer" mat-cell *matCellDef="let idp">
{{'IDP.OWNERTYPES.'+idp.owner | translate }} </td> {{'IDP.OWNERTYPES.'+idp.owner | translate }} </td>
</ng-container> </ng-container>
@ -88,7 +94,7 @@
[disabled]="serviceType==PolicyComponentServiceType.MGMT && idp?.providerType == IDPOwnerType.IDP_OWNER_TYPE_ORG" [disabled]="serviceType==PolicyComponentServiceType.MGMT && idp?.providerType == IDPOwnerType.IDP_OWNER_TYPE_ORG"
mat-icon-button color="warn" matTooltip="{{'ACTIONS.REMOVE' | translate}}" mat-icon-button color="warn" matTooltip="{{'ACTIONS.REMOVE' | translate}}"
(click)="removeIdp(idp)"> (click)="removeIdp(idp)">
<mat-icon>remove_circle</mat-icon> <i class="las la-trash"></i>
</button> </button>
</td> </td>
</ng-container> </ng-container>

View File

@ -8,6 +8,8 @@
.table, .table,
.paginator { .paginator {
width: 100% !important;
td, td,
th { th {
padding: 0 1rem; padding: 0 1rem;
@ -35,6 +37,15 @@ td {
object-fit: contain; object-fit: contain;
margin-top: .5rem; margin-top: .5rem;
} }
.name-col {
display: flex;
flex-direction: column;
span {
margin-bottom: .5rem;
}
}
} }
tr { tr {

View File

@ -6,8 +6,8 @@ import { RouterLink } from '@angular/router';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { ListIDPsResponse } from 'src/app/proto/generated/zitadel/admin_pb'; import { ListIDPsResponse } from 'src/app/proto/generated/zitadel/admin_pb';
import { IDP, IDPOwnerType, IDPState, IDPStylingType } from 'src/app/proto/generated/zitadel/idp_pb'; import { IDP, IDPOwnerType, IDPOwnerTypeQuery, IDPState, IDPStylingType } from 'src/app/proto/generated/zitadel/idp_pb';
import { ListOrgIDPsResponse } from 'src/app/proto/generated/zitadel/management_pb'; import { IDPQuery, ListOrgIDPsResponse } from 'src/app/proto/generated/zitadel/management_pb';
import { AdminService } from 'src/app/services/admin.service'; import { AdminService } from 'src/app/services/admin.service';
import { ManagementService } from 'src/app/services/mgmt.service'; import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
@ -17,187 +17,190 @@ import { PolicyComponentServiceType } from '../policies/policy-component-types.e
import { WarnDialogComponent } from '../warn-dialog/warn-dialog.component'; import { WarnDialogComponent } from '../warn-dialog/warn-dialog.component';
@Component({ @Component({
selector: 'app-idp-table', selector: 'app-idp-table',
templateUrl: './idp-table.component.html', templateUrl: './idp-table.component.html',
styleUrls: ['./idp-table.component.scss'], styleUrls: ['./idp-table.component.scss'],
}) })
export class IdpTableComponent implements OnInit { export class IdpTableComponent implements OnInit {
@Input() public serviceType!: PolicyComponentServiceType; @Input() public serviceType!: PolicyComponentServiceType;
@Input() service!: AdminService | ManagementService; @Input() service!: AdminService | ManagementService;
@Input() disabled: boolean = false; @Input() disabled: boolean = false;
@ViewChild(PaginatorComponent) public paginator!: PaginatorComponent; @ViewChild(PaginatorComponent) public paginator!: PaginatorComponent;
public dataSource: MatTableDataSource<IDP.AsObject> public dataSource: MatTableDataSource<IDP.AsObject>
= new MatTableDataSource<IDP.AsObject>(); = new MatTableDataSource<IDP.AsObject>();
public selection: SelectionModel<IDP.AsObject> public selection: SelectionModel<IDP.AsObject>
= new SelectionModel<IDP.AsObject>(true, []); = new SelectionModel<IDP.AsObject>(true, []);
public idpResult!: ListIDPsResponse.AsObject | ListOrgIDPsResponse.AsObject; public idpResult!: ListIDPsResponse.AsObject | ListOrgIDPsResponse.AsObject;
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false); private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable(); public loading$: Observable<boolean> = this.loadingSubject.asObservable();
public PolicyComponentServiceType: any = PolicyComponentServiceType; public PolicyComponentServiceType: any = PolicyComponentServiceType;
public IDPOwnerType: any = IDPOwnerType; public IDPOwnerType: any = IDPOwnerType;
public IDPState: any = IDPState; public IDPState: any = IDPState;
public IDPSTYLINGTYPE: any = IDPStylingType; public IDPSTYLINGTYPE: any = IDPStylingType;
@Input() public displayedColumns: string[] = ['select', 'name', 'config', 'dates', 'state']; @Input() public displayedColumns: string[] = ['select', 'name', 'dates', 'state'];
@Output() public changedSelection: EventEmitter<Array<IDP.AsObject>> @Output() public changedSelection: EventEmitter<Array<IDP.AsObject>>
= new EventEmitter(); = new EventEmitter();
constructor(public translate: TranslateService, private toast: ToastService, private dialog: MatDialog) { constructor(public translate: TranslateService, private toast: ToastService, private dialog: MatDialog) {
this.selection.changed.subscribe(() => { this.selection.changed.subscribe(() => {
this.changedSelection.emit(this.selection.selected); this.changedSelection.emit(this.selection.selected);
}); });
}
ngOnInit(): void {
this.getData(10, 0);
if (this.serviceType === PolicyComponentServiceType.MGMT) {
this.displayedColumns = ['select', 'name', 'dates', 'state', 'owner'];
} }
ngOnInit(): void { if (!this.disabled) {
this.getData(10, 0); this.displayedColumns.push('actions');
}
}
public isAllSelected(): boolean {
const numSelected = this.selection.selected.length;
const numRows = this.dataSource.data.length;
return numSelected === numRows;
}
public masterToggle(): void {
this.isAllSelected() ?
this.selection.clear() :
this.dataSource.data.forEach(row => this.selection.select(row));
}
public changePage(event: PageEvent): void {
this.getData(event.pageSize, event.pageIndex * event.pageSize);
}
public deactivateSelectedIdps(): void {
const map: Promise<any>[] = this.selection.selected.map(value => {
if (this.serviceType === PolicyComponentServiceType.MGMT) {
return (this.service as ManagementService).deactivateOrgIDP(value.id);
} else {
return (this.service as AdminService).deactivateIDP(value.id);
}
});
Promise.all(map).then(() => {
this.selection.clear();
this.toast.showInfo('IDP.TOAST.SELECTEDDEACTIVATED', true);
this.refreshPage();
}).catch(error => {
this.toast.showError(error);
});
}
public reactivateSelectedIdps(): void {
const map: Promise<any>[] = this.selection.selected.map(value => {
if (this.serviceType === PolicyComponentServiceType.MGMT) {
return (this.service as ManagementService).reactivateOrgIDP(value.id);
} else {
return (this.service as AdminService).reactivateIDP(value.id);
}
});
Promise.all(map).then(() => {
this.selection.clear();
this.toast.showInfo('IDP.TOAST.SELECTEDREACTIVATED', true);
this.refreshPage();
}).catch(error => {
this.toast.showError(error);
});
}
public removeIdp(idp: IDP.AsObject): void {
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.DELETE',
cancelKey: 'ACTIONS.CANCEL',
titleKey: 'IDP.DELETE_TITLE',
descriptionKey: 'IDP.DELETE_DESCRIPTION',
},
width: '400px',
});
dialogRef.afterClosed().subscribe(resp => {
if (resp) {
if (this.serviceType === PolicyComponentServiceType.MGMT) { if (this.serviceType === PolicyComponentServiceType.MGMT) {
this.displayedColumns = ['select', 'name', 'config', 'dates', 'state', 'owner']; (this.service as ManagementService).removeOrgIDP(idp.id).then(() => {
} this.toast.showInfo('IDP.TOAST.DELETED', true);
setTimeout(() => {
if (!this.disabled) { this.refreshPage();
this.displayedColumns.push('actions'); }, 1000);
} }, error => {
}
public isAllSelected(): boolean {
const numSelected = this.selection.selected.length;
const numRows = this.dataSource.data.length;
return numSelected === numRows;
}
public masterToggle(): void {
this.isAllSelected() ?
this.selection.clear() :
this.dataSource.data.forEach(row => this.selection.select(row));
}
public changePage(event: PageEvent): void {
this.getData(event.pageSize, event.pageIndex * event.pageSize);
}
public deactivateSelectedIdps(): void {
const map: Promise<any>[] = this.selection.selected.map(value => {
if (this.serviceType === PolicyComponentServiceType.MGMT) {
return (this.service as ManagementService).deactivateOrgIDP(value.id);
} else {
return (this.service as AdminService).deactivateIDP(value.id);
}
});
Promise.all(map).then(() => {
this.selection.clear();
this.toast.showInfo('IDP.TOAST.SELECTEDDEACTIVATED', true);
this.refreshPage();
}).catch(error => {
this.toast.showError(error); this.toast.showError(error);
}); });
}
public reactivateSelectedIdps(): void {
const map: Promise<any>[] = this.selection.selected.map(value => {
if (this.serviceType === PolicyComponentServiceType.MGMT) {
return (this.service as ManagementService).reactivateOrgIDP(value.id);
} else {
return (this.service as AdminService).reactivateIDP(value.id);
}
});
Promise.all(map).then(() => {
this.selection.clear();
this.toast.showInfo('IDP.TOAST.SELECTEDREACTIVATED', true);
this.refreshPage();
}).catch(error => {
this.toast.showError(error);
});
}
public removeIdp(idp: IDP.AsObject): void {
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.DELETE',
cancelKey: 'ACTIONS.CANCEL',
titleKey: 'IDP.DELETE_TITLE',
descriptionKey: 'IDP.DELETE_DESCRIPTION',
},
width: '400px',
});
dialogRef.afterClosed().subscribe(resp => {
if (resp) {
if (this.serviceType === PolicyComponentServiceType.MGMT) {
(this.service as ManagementService).removeOrgIDP(idp.id).then(() => {
this.toast.showInfo('IDP.TOAST.DELETED', true);
setTimeout(() => {
this.refreshPage();
}, 1000);
}, error => {
this.toast.showError(error);
});
} else {
(this.service as AdminService).removeIDP(idp.id).then(() => {
this.toast.showInfo('IDP.TOAST.DELETED', true);
setTimeout(() => {
this.refreshPage();
}, 1000);
}, error => {
this.toast.showError(error);
});
}
}
});
}
private async getData(limit: number, offset: number): Promise<void> {
this.loadingSubject.next(true);
if (this.serviceType === PolicyComponentServiceType.MGMT) {
(this.service as ManagementService).listOrgIDPs(limit, offset).then(resp => {
this.idpResult = resp;
this.dataSource.data = resp.resultList;
this.loadingSubject.next(false);
}).catch(error => {
this.toast.showError(error);
this.loadingSubject.next(false);
});
} else { } else {
(this.service as AdminService).listIDPs(limit, offset).then(resp => { (this.service as AdminService).removeIDP(idp.id).then(() => {
this.idpResult = resp; this.toast.showInfo('IDP.TOAST.DELETED', true);
this.dataSource.data = resp.resultList; setTimeout(() => {
this.refreshPage();
this.loadingSubject.next(false); }, 1000);
}).catch(error => { }, error => {
this.toast.showError(error); this.toast.showError(error);
this.loadingSubject.next(false); });
});
} }
}
});
}
private async getData(limit: number, offset: number): Promise<void> {
this.loadingSubject.next(true);
if (this.serviceType === PolicyComponentServiceType.MGMT) {
const query: IDPQuery = new IDPQuery();
const otQuery: IDPOwnerTypeQuery = new IDPOwnerTypeQuery();
otQuery.setOwnerType(IDPOwnerType.IDP_OWNER_TYPE_ORG);
query.setOwnerTypeQuery(otQuery);
(this.service as ManagementService).listOrgIDPs(limit, offset, [query]).then(resp => {
this.idpResult = resp;
this.dataSource.data = resp.resultList;
this.loadingSubject.next(false);
}).catch(error => {
this.toast.showError(error);
this.loadingSubject.next(false);
});
} else {
(this.service as AdminService).listIDPs(limit, offset).then(resp => {
this.idpResult = resp;
this.dataSource.data = resp.resultList;
this.loadingSubject.next(false);
}).catch(error => {
this.toast.showError(error);
this.loadingSubject.next(false);
});
} }
public refreshPage(): void { }
this.getData(this.paginator.pageSize, this.paginator.pageIndex * this.paginator.pageSize);
}
public get createRouterLink(): RouterLink | any { public refreshPage(): void {
if (this.service instanceof AdminService) { this.getData(this.paginator.pageSize, this.paginator.pageIndex * this.paginator.pageSize);
return ['/iam', 'idp', 'create']; }
} else if (this.service instanceof ManagementService) {
return ['/org', 'idp', 'create'];
}
}
public routerLinkForRow(row: IDP.AsObject): any { public get createRouterLink(): RouterLink | any {
if (row.id) { if (this.service instanceof AdminService) {
switch (this.serviceType) { return ['/iam', 'idp', 'create'];
case PolicyComponentServiceType.MGMT: } else if (this.service instanceof ManagementService) {
switch (row.owner) { return ['/org', 'idp', 'create'];
case IDPOwnerType.IDP_OWNER_TYPE_SYSTEM:
return ['/iam', 'idp', row.id];
case IDPOwnerType.IDP_OWNER_TYPE_ORG:
return ['/org', 'idp', row.id];
}
break;
case PolicyComponentServiceType.ADMIN:
return ['/iam', 'idp', row.id];
}
}
} }
}
public routerLinkForRow(row: IDP.AsObject): any {
if (row.id) {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
switch (row.owner) {
case IDPOwnerType.IDP_OWNER_TYPE_SYSTEM:
return ['/iam', 'idp', row.id];
case IDPOwnerType.IDP_OWNER_TYPE_ORG:
return ['/org', 'idp', row.id];
}
break;
case PolicyComponentServiceType.ADMIN:
return ['/iam', 'idp', row.id];
}
}
}
} }

View File

@ -1,13 +1,35 @@
<app-detail-layout [backRouterLink]="backroutes" [title]="'IDP.DETAIL.TITLE' | translate" <app-detail-layout [backRouterLink]="backroutes" [title]="idp?.name"
[description]="'IDP.DETAIL.DESCRIPTION' | translate"> [description]="'IDP.DETAIL.DESCRIPTION' | translate">
<div *ngIf="canWrite | async" actions>
<button class="actions-trigger" mat-raised-button color="primary" [matMenuTriggerFor]="idpactions">
<span>{{'ACTIONS.ACTIONS' | translate}}</span>
<mat-icon class="icon">keyboard_arrow_down</mat-icon>
</button>
<mat-menu #idpactions="matMenu" xPosition="before">
<button mat-menu-item
*ngIf="idp?.state !== IDPState.IDP_STATE_INACTIVE"
(click)="changeState(IDPState.IDP_STATE_INACTIVE)">
{{'ACTIONS.DEACTIVATE' | translate}}
</button>
<button mat-menu-item *ngIf="idp?.state == IDPState.IDP_STATE_INACTIVE"
(click)="changeState(IDPState.IDP_STATE_ACTIVE)">
{{'ACTIONS.REACTIVATE' | translate}}
</button>
<button mat-menu-item matTooltip="{{'IDP.DELETE' | translate}}"
(click)="deleteIdp()">
<span [style.color]="'var(--warn)'">{{'IDP.DELETE_TITLE' | translate}}</span>
</button>
</mat-menu>
</div>
<div class="container"> <div class="container">
<form (ngSubmit)="updateIdp()">
<cnsl-info-row *ngIf="idp" [idp]="idp"></cnsl-info-row>
<form class="idp-form" (ngSubmit)="updateIdp()">
<ng-container [formGroup]="idpForm"> <ng-container [formGroup]="idpForm">
<div class="content"> <div class="idp-content">
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.ID' | translate }}</cnsl-label>
<input cnslInput formControlName="id" />
</cnsl-form-field>
<cnsl-form-field appearance="outline" class="formfield"> <cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.NAME' | translate }}</cnsl-label> <cnsl-label>{{ 'IDP.NAME' | translate }}</cnsl-label>
<input cnslInput formControlName="name" /> <input cnslInput formControlName="name" />
@ -40,77 +62,115 @@
</div> </div>
</form> </form>
<ng-container *ngIf="oidcConfigForm"> <ng-container *ngIf="idp?.oidcConfig && oidcConfigForm">
<h2>{{'IDP.DETAIL.OIDC.TITLE' | translate}}</h2> <h2>{{'IDP.OIDC.TITLE' | translate}}</h2>
<p>{{'IDP.DETAIL.OIDC.DESCRIPTION' | translate}}</p> <p>{{'IDP.OIDC.DESCRIPTION' | translate}}</p>
<form (ngSubmit)="updateOidcConfig()"> <form (ngSubmit)="updateOidcConfig()">
<ng-container [formGroup]="oidcConfigForm"> <ng-container [formGroup]="oidcConfigForm">
<div class="content"> <div class="idp-content">
<cnsl-form-field appearance="outline" class="formfield"> <cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.ISSUER' | translate }}</cnsl-label> <cnsl-label>{{ 'IDP.ISSUER' | translate }}</cnsl-label>
<input cnslInput formControlName="issuer" /> <input cnslInput formControlName="issuer" />
</cnsl-form-field> </cnsl-form-field>
<cnsl-form-field appearance="outline" class="formfield"> <cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.CLIENTID' | translate }}</cnsl-label> <cnsl-label>{{ 'IDP.CLIENTID' | translate }}</cnsl-label>
<input cnslInput formControlName="clientId" /> <input cnslInput formControlName="clientId" />
</cnsl-form-field> </cnsl-form-field>
<mat-checkbox class="desc" [(ngModel)]="showIdSecretSection" [disabled]="(canWrite | async) === false" <mat-checkbox class="idp-desc" [(ngModel)]="showIdSecretSection" [disabled]="(canWrite | async) === false"
[ngModelOptions]="{standalone: true}"> [ngModelOptions]="{standalone: true}">
Update Client Secret Update Client Secret
</mat-checkbox> </mat-checkbox>
<cnsl-form-field appearance="outline" class="formfield" *ngIf="showIdSecretSection"> <cnsl-form-field appearance="outline" class="formfield" *ngIf="showIdSecretSection">
<cnsl-label>{{ 'IDP.CLIENTSECRET' | translate }}</cnsl-label> <cnsl-label>{{ 'IDP.CLIENTSECRET' | translate }}</cnsl-label>
<input cnslInput formControlName="clientSecret" /> <input cnslInput formControlName="clientSecret" />
</cnsl-form-field> </cnsl-form-field>
<div class="line"> <div class="line">
<cnsl-form-field appearance="outline" class="formfield"> <cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.SCOPESLIST' | translate }}</cnsl-label> <cnsl-label>{{ 'IDP.SCOPESLIST' | translate }}</cnsl-label>
<input cnslInput [matChipInputFor]="chipScopesList" <input cnslInput [matChipInputFor]="chipScopesList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes" [matChipInputAddOnBlur]="true" [matChipInputSeparatorKeyCodes]="separatorKeysCodes" [matChipInputAddOnBlur]="true"
(matChipInputTokenEnd)="addScope($event)"> (matChipInputTokenEnd)="addScope($event)">
</cnsl-form-field> </cnsl-form-field>
<button (click)="addScope($event)" mat-icon-button> <button (click)="addScope($event)" mat-icon-button>
<mat-icon>add</mat-icon> <mat-icon>add</mat-icon>
</button> </button>
</div>
<cnsl-form-field appearance="outline" class="formfield fullwidth">
<mat-chip-list class="chip-list" #chipScopesList aria-label="scope selection">
<mat-chip class="chip" *ngFor="let scope of scopesList?.value" selectable="false"
removable (removed)="removeScope(scope)" [disabled]="(canWrite | async) === false">
{{scope}} <mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
</mat-chip-list>
</cnsl-form-field>
<cnsl-form-field class="formfield" appearance="outline">
<cnsl-label>{{ 'IDP.IDPDISPLAYNAMMAPPING' | translate }}</cnsl-label>
<mat-select formControlName="displayNameMapping">
<mat-option *ngFor="let field of mappingFields" [value]="field">
{{ 'IDP.MAPPINGFIELD.'+field | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="formfield" appearance="outline">
<cnsl-label>{{ 'IDP.USERNAMEMAPPING' | translate }}</cnsl-label>
<mat-select formControlName="usernameMapping">
<mat-option *ngFor="let field of mappingFields" [value]="field">
{{ 'IDP.MAPPINGFIELD.'+field | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
</div>
</ng-container>
<div class="btn-wrapper">
<button color="primary" mat-raised-button class="continue-button"
[disabled]="oidcConfigForm.invalid || (canWrite | async) === false" type="submit">
{{ 'ACTIONS.SAVE' | translate }}
</button>
</div> </div>
</form> <cnsl-form-field appearance="outline" class="formfield fullwidth">
<mat-chip-list class="chip-list" #chipScopesList aria-label="scope selection">
<mat-chip class="chip" *ngFor="let scope of scopesList?.value" selectable="false"
removable (removed)="removeScope(scope)" [disabled]="(canWrite | async) === false">
{{scope}} <mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
</mat-chip-list>
</cnsl-form-field>
<cnsl-form-field class="formfield" appearance="outline">
<cnsl-label>{{ 'IDP.IDPDISPLAYNAMMAPPING' | translate }}</cnsl-label>
<mat-select formControlName="displayNameMapping">
<mat-option *ngFor="let field of mappingFields" [value]="field">
{{ 'IDP.MAPPINGFIELD.'+field | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="formfield" appearance="outline">
<cnsl-label>{{ 'IDP.USERNAMEMAPPING' | translate }}</cnsl-label>
<mat-select formControlName="usernameMapping">
<mat-option *ngFor="let field of mappingFields" [value]="field">
{{ 'IDP.MAPPINGFIELD.'+field | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
</div>
</ng-container>
<div class="btn-wrapper">
<button color="primary" mat-raised-button class="continue-button"
[disabled]="oidcConfigForm.invalid || (canWrite | async) === false" type="submit">
{{ 'ACTIONS.SAVE' | translate }}
</button>
</div>
</form>
</ng-container> </ng-container>
<ng-container *ngIf="idp?.jwtConfig && jwtConfigForm">
<h2>{{'IDP.JWT.TITLE' | translate}}</h2>
<p>{{'IDP.JWT.DESCRIPTION' | translate}}</p>
<form (ngSubmit)="updateJwtConfig()">
<ng-container [formGroup]="jwtConfigForm">
<div class="idp-content">
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.ISSUER' | translate }}</cnsl-label>
<input cnslInput formControlName="issuer" />
</cnsl-form-field>
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.JWT.HEADERNAME' | translate }}</cnsl-label>
<input cnslInput formControlName="headerName" />
</cnsl-form-field>
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.JWT.JWTENDPOINT' | translate }}</cnsl-label>
<input cnslInput formControlName="jwtEndpoint" />
</cnsl-form-field>
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.JWT.JWTKEYSENDPOINT' | translate }}</cnsl-label>
<input cnslInput formControlName="keysEndpoint" />
</cnsl-form-field>
</div>
</ng-container>
<div class="btn-wrapper">
<button color="primary" mat-raised-button class="continue-button"
[disabled]="jwtConfigForm.invalid || (canWrite | async) === false" type="submit">
{{ 'ACTIONS.SAVE' | translate }}
</button>
</div>
</form>
</ng-container>
</div> </div>
</app-detail-layout> </app-detail-layout>

View File

@ -5,15 +5,20 @@
@media only screen and (max-width: 450px) { @media only screen and (max-width: 450px) {
padding: 4rem 1rem 2rem 1rem; padding: 4rem 1rem 2rem 1rem;
} }
.idp-form {
border-top: 1px solid #ffffff20;
padding-top: 1rem;
}
} }
.content { .idp-content {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
margin: 0 -.5rem; margin: 0 -.5rem;
flex-wrap: wrap; flex-wrap: wrap;
.desc { .idp-desc {
flex-basis: 100%; flex-basis: 100%;
margin: 0 .5rem; margin: 0 .5rem;
margin-bottom: 1rem; margin-bottom: 1rem;

View File

@ -3,18 +3,28 @@ import { Location } from '@angular/common';
import { Component, Injector, OnDestroy, Type } from '@angular/core'; import { Component, Injector, OnDestroy, Type } from '@angular/core';
import { AbstractControl, FormControl, FormGroup, Validators } from '@angular/forms'; import { AbstractControl, FormControl, FormGroup, Validators } from '@angular/forms';
import { MatChipInputEvent } from '@angular/material/chips'; import { MatChipInputEvent } from '@angular/material/chips';
import { ActivatedRoute } from '@angular/router'; import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute, Router } from '@angular/router';
import { Observable, Subject } from 'rxjs'; import { Observable, Subject } from 'rxjs';
import { switchMap, take, takeUntil } from 'rxjs/operators'; import { switchMap, take, takeUntil } from 'rxjs/operators';
import { UpdateIDPOIDCConfigRequest, UpdateIDPRequest } from 'src/app/proto/generated/zitadel/admin_pb'; import {
import { IDPStylingType, OIDCMappingField } from 'src/app/proto/generated/zitadel/idp_pb'; UpdateIDPJWTConfigRequest,
import { UpdateOrgIDPOIDCConfigRequest, UpdateOrgIDPRequest } from 'src/app/proto/generated/zitadel/management_pb'; UpdateIDPOIDCConfigRequest,
UpdateIDPRequest,
} from 'src/app/proto/generated/zitadel/admin_pb';
import { IDP, IDPState, IDPStylingType, OIDCMappingField } from 'src/app/proto/generated/zitadel/idp_pb';
import {
UpdateOrgIDPJWTConfigRequest,
UpdateOrgIDPOIDCConfigRequest,
UpdateOrgIDPRequest,
} from 'src/app/proto/generated/zitadel/management_pb';
import { AdminService } from 'src/app/services/admin.service'; import { AdminService } from 'src/app/services/admin.service';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service'; import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ManagementService } from 'src/app/services/mgmt.service'; import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
import { PolicyComponentServiceType } from '../policies/policy-component-types.enum'; import { PolicyComponentServiceType } from '../policies/policy-component-types.enum';
import { WarnDialogComponent } from '../warn-dialog/warn-dialog.component';
@Component({ @Component({
selector: 'app-idp', selector: 'app-idp',
@ -28,13 +38,18 @@ export class IdpComponent implements OnDestroy {
public showIdSecretSection: boolean = false; public showIdSecretSection: boolean = false;
public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT; public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
private service!: ManagementService | AdminService; private service!: ManagementService | AdminService;
public PolicyComponentServiceType: any = PolicyComponentServiceType;
public readonly separatorKeysCodes: number[] = [ENTER, COMMA, SPACE]; public readonly separatorKeysCodes: number[] = [ENTER, COMMA, SPACE];
public idp!: IDP.AsObject;
private destroy$: Subject<void> = new Subject(); private destroy$: Subject<void> = new Subject();
public projectId: string = ''; public projectId: string = '';
public idpForm!: FormGroup; public idpForm!: FormGroup;
public oidcConfigForm!: FormGroup; public oidcConfigForm!: FormGroup;
public jwtConfigForm!: FormGroup;
IDPState: any = IDPState;
public canWrite: Observable<boolean> = this.authService.isAllowed([this.serviceType === PolicyComponentServiceType.ADMIN ? public canWrite: Observable<boolean> = this.authService.isAllowed([this.serviceType === PolicyComponentServiceType.ADMIN ?
'iam.idp.write' : this.serviceType === PolicyComponentServiceType.MGMT ? 'iam.idp.write' : this.serviceType === PolicyComponentServiceType.MGMT ?
@ -44,8 +59,10 @@ export class IdpComponent implements OnDestroy {
private toast: ToastService, private toast: ToastService,
private injector: Injector, private injector: Injector,
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router,
private _location: Location, private _location: Location,
private authService: GrpcAuthService, private authService: GrpcAuthService,
private dialog: MatDialog,
) { ) {
this.idpForm = new FormGroup({ this.idpForm = new FormGroup({
id: new FormControl({ disabled: true, value: '' }, [Validators.required]), id: new FormControl({ disabled: true, value: '' }, [Validators.required]),
@ -63,6 +80,13 @@ export class IdpComponent implements OnDestroy {
usernameMapping: new FormControl(0), usernameMapping: new FormControl(0),
}); });
this.jwtConfigForm = new FormGroup({
jwtEndpoint: new FormControl('', [Validators.required]),
issuer: new FormControl('', [Validators.required]),
keysEndpoint: new FormControl('', [Validators.required]),
headerName: new FormControl('', [Validators.required]),
});
this.route.data.pipe( this.route.data.pipe(
takeUntil(this.destroy$), takeUntil(this.destroy$),
switchMap(data => { switchMap(data => {
@ -95,20 +119,26 @@ export class IdpComponent implements OnDestroy {
(this.service as ManagementService).getOrgIDPByID(id).then(resp => { (this.service as ManagementService).getOrgIDPByID(id).then(resp => {
if (resp.idp) { if (resp.idp) {
const idpObject = resp.idp; this.idp = resp.idp;
this.idpForm.patchValue(idpObject); this.idpForm.patchValue(this.idp);
if (idpObject.oidcConfig) { if (this.idp.oidcConfig) {
this.oidcConfigForm.patchValue(idpObject.oidcConfig); this.oidcConfigForm.patchValue(this.idp.oidcConfig);
} else if (this.idp.jwtConfig) {
this.jwtConfigForm.patchValue(this.idp.jwtConfig);
this.jwtIssuer?.setValue(this.idp.jwtConfig.issuer);
} }
} }
}); });
} else if (this.serviceType === PolicyComponentServiceType.ADMIN) { } else if (this.serviceType === PolicyComponentServiceType.ADMIN) {
(this.service as AdminService).getIDPByID(id).then(resp => { (this.service as AdminService).getIDPByID(id).then(resp => {
if (resp.idp) { if (resp.idp) {
const idpObject = resp.idp; this.idp = resp.idp;
this.idpForm.patchValue(idpObject); this.idpForm.patchValue(this.idp);
if (idpObject.oidcConfig) { if (this.idp.oidcConfig) {
this.oidcConfigForm.patchValue(idpObject.oidcConfig); this.oidcConfigForm.patchValue(this.idp.oidcConfig);
} else if (this.idp.jwtConfig) {
this.jwtConfigForm.patchValue(this.idp.jwtConfig);
this.jwtIssuer?.setValue(this.idp.jwtConfig.issuer);
} }
} }
}); });
@ -122,11 +152,9 @@ export class IdpComponent implements OnDestroy {
if (canWrite) { if (canWrite) {
this.idpForm.enable(); this.idpForm.enable();
this.oidcConfigForm.enable(); this.oidcConfigForm.enable();
this.id?.disable();
} else { } else {
this.idpForm.disable(); this.idpForm.disable();
this.oidcConfigForm.disable(); this.oidcConfigForm.disable();
this.id?.disable();
} }
}); });
} }
@ -136,32 +164,98 @@ export class IdpComponent implements OnDestroy {
this.destroy$.complete(); this.destroy$.complete();
} }
public deleteIdp(): void {
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.DELETE',
cancelKey: 'ACTIONS.CANCEL',
titleKey: 'IDP.DELETE_TITLE',
descriptionKey: 'IDP.DELETE_DESCRIPTION',
},
width: '400px',
});
dialogRef.afterClosed().subscribe(resp => {
if (resp) {
if (this.serviceType === PolicyComponentServiceType.MGMT) {
(this.service as ManagementService).removeOrgIDP(this.idp.id).then(() => {
this.toast.showInfo('IDP.TOAST.DELETED', true);
this.router.navigate(this.backroutes);
}).catch((error: any) => {
this.toast.showError(error);
});
} else if (this.serviceType === PolicyComponentServiceType.ADMIN) {
(this.service as AdminService).removeIDP(this.idp.id).then(() => {
this.toast.showInfo('IDP.TOAST.DELETED', true);
this.router.navigate(this.backroutes);
}).catch((error: any) => {
this.toast.showError(error);
});
}
}
});
}
public changeState(state: IDPState): void {
if (this.serviceType === PolicyComponentServiceType.MGMT) {
if (state === IDPState.IDP_STATE_ACTIVE) {
(this.service as ManagementService).reactivateOrgIDP(this.idp.id).then(() => {
this.idp.state = state;
this.toast.showInfo('IDP.TOAST.REACTIVATED', true);
}).catch((error: any) => {
this.toast.showError(error);
});
} else if (state === IDPState.IDP_STATE_INACTIVE) {
(this.service as ManagementService).deactivateOrgIDP(this.idp.id).then(() => {
this.idp.state = state;
this.toast.showInfo('IDP.TOAST.DEACTIVATED', true);
}).catch((error: any) => {
this.toast.showError(error);
});
}
} else if (this.serviceType === PolicyComponentServiceType.ADMIN) {
if (state === IDPState.IDP_STATE_ACTIVE) {
(this.service as AdminService).reactivateIDP(this.idp.id).then(() => {
this.idp.state = state;
this.toast.showInfo('IDP.TOAST.REACTIVATED', true);
}).catch((error: any) => {
this.toast.showError(error);
});
} else if (state === IDPState.IDP_STATE_INACTIVE) {
(this.service as AdminService).deactivateIDP(this.idp.id).then(() => {
this.idp.state = state;
this.toast.showInfo('IDP.TOAST.DEACTIVATED', true);
}).catch((error: any) => {
this.toast.showError(error);
});
}
}
}
public updateIdp(): void { public updateIdp(): void {
if (this.serviceType === PolicyComponentServiceType.MGMT) { if (this.serviceType === PolicyComponentServiceType.MGMT) {
const req = new UpdateOrgIDPRequest(); const req = new UpdateOrgIDPRequest();
req.setIdpId(this.id?.value); req.setIdpId(this.idp.id);
req.setName(this.name?.value); req.setName(this.name?.value);
req.setStylingType(this.stylingType?.value); req.setStylingType(this.stylingType?.value);
req.setAutoRegister(this.autoRegister?.value); req.setAutoRegister(this.autoRegister?.value);
(this.service as ManagementService).updateOrgIDP(req).then(() => { (this.service as ManagementService).updateOrgIDP(req).then(() => {
this.toast.showInfo('IDP.TOAST.SAVED', true); this.toast.showInfo('IDP.TOAST.SAVED', true);
// this.router.navigate(['idp', ]);
}).catch(error => { }).catch(error => {
this.toast.showError(error); this.toast.showError(error);
}); });
} else if (this.serviceType === PolicyComponentServiceType.ADMIN) { } else if (this.serviceType === PolicyComponentServiceType.ADMIN) {
const req = new UpdateIDPRequest(); const req = new UpdateIDPRequest();
req.setIdpId(this.id?.value); req.setIdpId(this.idp.id);
req.setName(this.name?.value); req.setName(this.name?.value);
req.setStylingType(this.stylingType?.value); req.setStylingType(this.stylingType?.value);
req.setAutoRegister(this.autoRegister?.value); req.setAutoRegister(this.autoRegister?.value);
(this.service as AdminService).updateIDP(req).then(() => { (this.service as AdminService).updateIDP(req).then(() => {
this.toast.showInfo('IDP.TOAST.SAVED', true); this.toast.showInfo('IDP.TOAST.SAVED', true);
// this.router.navigate(['idp', ]);
}).catch(error => { }).catch(error => {
this.toast.showError(error); this.toast.showError(error);
}); });
@ -172,7 +266,7 @@ export class IdpComponent implements OnDestroy {
if (this.serviceType === PolicyComponentServiceType.MGMT) { if (this.serviceType === PolicyComponentServiceType.MGMT) {
const req = new UpdateOrgIDPOIDCConfigRequest(); const req = new UpdateOrgIDPOIDCConfigRequest();
req.setIdpId(this.id?.value); req.setIdpId(this.idp.id);
req.setClientId(this.clientId?.value); req.setClientId(this.clientId?.value);
req.setClientSecret(this.clientSecret?.value); req.setClientSecret(this.clientSecret?.value);
req.setIssuer(this.issuer?.value); req.setIssuer(this.issuer?.value);
@ -182,14 +276,13 @@ export class IdpComponent implements OnDestroy {
(this.service as ManagementService).updateOrgIDPOIDCConfig(req).then((oidcConfig) => { (this.service as ManagementService).updateOrgIDPOIDCConfig(req).then((oidcConfig) => {
this.toast.showInfo('IDP.TOAST.SAVED', true); this.toast.showInfo('IDP.TOAST.SAVED', true);
// this.router.navigate(['idp', ]);
}).catch(error => { }).catch(error => {
this.toast.showError(error); this.toast.showError(error);
}); });
} else if (this.serviceType === PolicyComponentServiceType.ADMIN) { } else if (this.serviceType === PolicyComponentServiceType.ADMIN) {
const req = new UpdateIDPOIDCConfigRequest(); const req = new UpdateIDPOIDCConfigRequest();
req.setIdpId(this.id?.value); req.setIdpId(this.idp.id);
req.setClientId(this.clientId?.value); req.setClientId(this.clientId?.value);
req.setClientSecret(this.clientSecret?.value); req.setClientSecret(this.clientSecret?.value);
req.setIssuer(this.issuer?.value); req.setIssuer(this.issuer?.value);
@ -199,6 +292,39 @@ export class IdpComponent implements OnDestroy {
(this.service as AdminService).updateIDPOIDCConfig(req).then((oidcConfig) => { (this.service as AdminService).updateIDPOIDCConfig(req).then((oidcConfig) => {
this.toast.showInfo('IDP.TOAST.SAVED', true); this.toast.showInfo('IDP.TOAST.SAVED', true);
}).catch(error => {
this.toast.showError(error);
});
}
}
public updateJwtConfig(): void {
if (this.serviceType === PolicyComponentServiceType.MGMT) {
const req = new UpdateOrgIDPJWTConfigRequest();
req.setIdpId(this.idp.id);
req.setIssuer(this.jwtIssuer?.value);
req.setHeaderName(this.headerName?.value);
req.setJwtEndpoint(this.jwtEndpoint?.value);
req.setKeysEndpoint(this.keyEndpoint?.value);
(this.service as ManagementService).updateOrgIDPJWTConfig(req).then((jwtConfig) => {
this.toast.showInfo('IDP.TOAST.SAVED', true);
// this.router.navigate(['idp', ]);
}).catch(error => {
this.toast.showError(error);
});
} else if (this.serviceType === PolicyComponentServiceType.ADMIN) {
const req = new UpdateIDPJWTConfigRequest();
req.setIdpId(this.idp.id);
req.setIssuer(this.jwtIssuer?.value);
req.setHeaderName(this.headerName?.value);
req.setJwtEndpoint(this.jwtEndpoint?.value);
req.setKeysEndpoint(this.keyEndpoint?.value);
(this.service as AdminService).updateIDPJWTConfig(req).then((jwtConfig) => {
this.toast.showInfo('IDP.TOAST.SAVED', true);
// this.router.navigate(['idp', ]); // this.router.navigate(['idp', ]);
}).catch(error => { }).catch(error => {
this.toast.showError(error); this.toast.showError(error);
@ -243,10 +369,6 @@ export class IdpComponent implements OnDestroy {
} }
} }
public get id(): AbstractControl | null {
return this.idpForm.get('id');
}
public get name(): AbstractControl | null { public get name(): AbstractControl | null {
return this.idpForm.get('name'); return this.idpForm.get('name');
} }
@ -282,4 +404,21 @@ export class IdpComponent implements OnDestroy {
public get usernameMapping(): AbstractControl | null { public get usernameMapping(): AbstractControl | null {
return this.oidcConfigForm.get('usernameMapping'); return this.oidcConfigForm.get('usernameMapping');
} }
public get jwtIssuer(): AbstractControl | null {
return this.jwtConfigForm.get('issuer');
}
public get jwtEndpoint(): AbstractControl | null {
return this.jwtConfigForm.get('jwtEndpoint');
}
public get keyEndpoint(): AbstractControl | null {
return this.jwtConfigForm.get('keysEndpoint');
}
public get headerName(): AbstractControl | null {
return this.jwtConfigForm.get('headerName');
}
} }

View File

@ -5,13 +5,16 @@ import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatChipsModule } from '@angular/material/chips'; import { MatChipsModule } from '@angular/material/chips';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { MatSelectModule } from '@angular/material/select'; import { MatSelectModule } from '@angular/material/select';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module'; import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module';
import { InputModule } from 'src/app/modules/input/input.module'; import { InputModule } from 'src/app/modules/input/input.module';
import { InfoRowModule } from '../info-row/info-row.module';
import { InfoSectionModule } from '../info-section/info-section.module'; import { InfoSectionModule } from '../info-section/info-section.module';
import { WarnDialogModule } from '../warn-dialog/warn-dialog.module';
import { IdpRoutingModule } from './idp-routing.module'; import { IdpRoutingModule } from './idp-routing.module';
import { IdpComponent } from './idp.component'; import { IdpComponent } from './idp.component';
@ -24,12 +27,15 @@ import { IdpComponent } from './idp.component';
ReactiveFormsModule, ReactiveFormsModule,
InputModule, InputModule,
MatButtonModule, MatButtonModule,
WarnDialogModule,
MatIconModule, MatIconModule,
InfoSectionModule, InfoSectionModule,
MatMenuModule,
MatTooltipModule, MatTooltipModule,
MatSelectModule, MatSelectModule,
TranslateModule, TranslateModule,
MatCheckboxModule, MatCheckboxModule,
InfoRowModule,
MatChipsModule, MatChipsModule,
DetailLayoutModule, DetailLayoutModule,
], ],

View File

@ -79,4 +79,34 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="info-row" *ngIf="idp">
<div class="info width">
<p class="title">{{ 'IDP.ID' | translate }}</p>
<div class="copy-row">
<button [disabled]="copied == idp.id"
[matTooltip]="(copied != idp.id ? 'ACTIONS.COPY' : 'ACTIONS.COPIED' ) | translate"
appCopyToClipboard [valueToCopy]="idp.id" (copiedValue)="copied = $event">
{{idp.id}}
</button>
</div>
</div>
<div class="info">
<p class="title">{{ 'IDP.DETAIL.DATECREATED' | translate }}</p>
<p class="desc">{{idp?.details?.creationDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}</p>
</div>
<div class="info">
<p class="title">{{ 'IDP.DETAIL.DATECHANGED' | translate }}</p>
<p class="desc">{{idp?.details?.changeDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}</p>
</div>
<div class="info">
<p class="title">{{ 'IDP.STATE' | translate }}</p>
<p *ngIf="idp && idp.state !== undefined" class="state"
[ngClass]="{'active': idp.state === IDPState.IDP_STATE_ACTIVE, 'inactive': idp.state === IDPState.IDP_STATE_INACTIVE}">{{'IDP.STATES.'+idp.state
| translate}}</p>
</div>
</div>

View File

@ -1,6 +1,7 @@
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { App, AppState } from 'src/app/proto/generated/zitadel/app_pb'; import { App, AppState } from 'src/app/proto/generated/zitadel/app_pb';
import { IDP, IDPState } from 'src/app/proto/generated/zitadel/idp_pb';
import { User, UserState } from 'src/app/proto/generated/zitadel/user_pb'; import { User, UserState } from 'src/app/proto/generated/zitadel/user_pb';
@Component({ @Component({
@ -11,8 +12,11 @@ import { User, UserState } from 'src/app/proto/generated/zitadel/user_pb';
export class InfoRowComponent implements OnInit { export class InfoRowComponent implements OnInit {
@Input() public user!: User.AsObject; @Input() public user!: User.AsObject;
@Input() public app!: App.AsObject; @Input() public app!: App.AsObject;
@Input() public idp!: IDP.AsObject;
public UserState: any = UserState; public UserState: any = UserState;
public AppState: any = AppState; public AppState: any = AppState;
public IDPState: any = IDPState;
public copied: string = ''; public copied: string = '';
public environmentMap: { [key: string]: string; } = {}; public environmentMap: { [key: string]: string; } = {};

View File

@ -1,16 +0,0 @@
<div class="sp_wrapper">
<mat-spinner diameter="30" *ngIf="loading$ | async"></mat-spinner>
</div>
<div class="mfa-list">
<div class="mfa" *ngFor="let mfa of mfas">
<button [disabled]="disabled" mat-icon-button (click)="removeMfa(mfa)" class="rm">
<mat-icon matTooltip="{{'ACTIONS.REMOVE' | translate}}">
remove_circle</mat-icon>
</button>
{{(componentType == LoginMethodComponentType.SecondFactor ? 'MFA.SECONDFACTORTYPES.':
LoginMethodComponentType.MultiFactor ? 'MFA.MULTIFACTORTYPES.': '')+mfa | translate}}
</div>
<div class="new-mfa" [ngClass]="{'disabled': disabled}" (click)="!disabled ? addMfa(): null" matRipple>
<mat-icon>add</mat-icon>
</div>
</div>

View File

@ -1,62 +0,0 @@
.t .sp_wrapper {
display: block;
}
.mfa-list {
display: flex;
flex-wrap: wrap;
margin: 0 -.5rem;
.mfa {
background-color: #6a506e;
color: white;
}
.mfa,
.new-mfa {
border: 1px solid var(--grey);
border-radius: .5rem;
display: grid;
align-items: center;
justify-content: center;
margin: .5rem;
padding: 10px;
position: relative;
min-height: 70px;
min-width: 150px;
.rm {
position: absolute;
display: none;
top: -2px;
left: -2px;
transform: translateX(-50%) translateY(-50%);
cursor: pointer;
color: var(--warn);
transition: all .2s ease;
&[disabled] {
display: none;
}
}
&:not(.disabled) {
cursor: default;
&:hover {
.rm {
display: block;
}
}
}
&.disabled {
opacity: .5;
cursor: not-allowed;
}
}
.new-mfa:not(.disabled) {
cursor: pointer;
}
}

View File

@ -1,224 +0,0 @@
import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatPaginator } from '@angular/material/paginator';
import { TranslateService } from '@ngx-translate/core';
import { BehaviorSubject, Observable } from 'rxjs';
import {
AddMultiFactorToLoginPolicyRequest as AdminAddMultiFactorToLoginPolicyRequest,
AddSecondFactorToLoginPolicyRequest as AdminAddSecondFactorToLoginPolicyRequest,
RemoveMultiFactorFromLoginPolicyRequest as AdminRemoveMultiFactorFromLoginPolicyRequest,
RemoveSecondFactorFromLoginPolicyRequest as AdminRemoveSecondFactorFromLoginPolicyRequest,
} from 'src/app/proto/generated/zitadel/admin_pb';
import {
AddMultiFactorToLoginPolicyRequest as MgmtAddMultiFactorToLoginPolicyRequest,
AddSecondFactorToLoginPolicyRequest as MgmtAddSecondFactorToLoginPolicyRequest,
RemoveMultiFactorFromLoginPolicyRequest as MgmtRemoveMultiFactorFromLoginPolicyRequest,
RemoveSecondFactorFromLoginPolicyRequest as MgmtRemoveSecondFactorFromLoginPolicyRequest,
} from 'src/app/proto/generated/zitadel/management_pb';
import { MultiFactorType, SecondFactorType } from 'src/app/proto/generated/zitadel/policy_pb';
import { AdminService } from 'src/app/services/admin.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
import { PolicyComponentServiceType } from '../policies/policy-component-types.enum';
import { WarnDialogComponent } from '../warn-dialog/warn-dialog.component';
import { DialogAddTypeComponent } from './dialog-add-type/dialog-add-type.component';
export enum LoginMethodComponentType {
MultiFactor = 1,
SecondFactor = 2,
}
@Component({
selector: 'app-mfa-table',
templateUrl: './mfa-table.component.html',
styleUrls: ['./mfa-table.component.scss'],
})
export class MfaTableComponent implements OnInit {
public LoginMethodComponentType: any = LoginMethodComponentType;
@Input() componentType!: LoginMethodComponentType;
@Input() public serviceType!: PolicyComponentServiceType;
@Input() service!: AdminService | ManagementService;
@Input() disabled: boolean = false;
@ViewChild(MatPaginator) public paginator!: MatPaginator;
public mfas: Array<MultiFactorType | SecondFactorType> = [];
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
public PolicyComponentServiceType: any = PolicyComponentServiceType;
constructor(public translate: TranslateService, private toast: ToastService, private dialog: MatDialog) { }
public ngOnInit(): void {
this.getData();
}
public removeMfa(type: MultiFactorType | SecondFactorType): void {
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.DELETE',
cancelKey: 'ACTIONS.CANCEL',
titleKey: 'MFA.DELETE.TITLE',
descriptionKey: 'MFA.DELETE.DESCRIPTION',
},
width: '400px',
});
dialogRef.afterClosed().subscribe(resp => {
if (resp) {
if (this.serviceType === PolicyComponentServiceType.MGMT) {
if (this.componentType === LoginMethodComponentType.MultiFactor) {
const req = new MgmtRemoveMultiFactorFromLoginPolicyRequest();
req.setType(type as MultiFactorType);
(this.service as ManagementService).removeMultiFactorFromLoginPolicy(req).then(() => {
this.toast.showInfo('MFA.TOAST.DELETED', true);
this.refreshPageAfterTimout(2000);
});
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
const req = new MgmtRemoveSecondFactorFromLoginPolicyRequest();
req.setType(type as SecondFactorType);
(this.service as ManagementService).removeSecondFactorFromLoginPolicy(req).then(() => {
this.toast.showInfo('MFA.TOAST.DELETED', true);
this.refreshPageAfterTimout(2000);
});
}
} else if (this.serviceType === PolicyComponentServiceType.ADMIN) {
if (this.componentType === LoginMethodComponentType.MultiFactor) {
const req = new AdminRemoveMultiFactorFromLoginPolicyRequest();
req.setType(type as MultiFactorType);
(this.service as AdminService).removeMultiFactorFromLoginPolicy(req).then(() => {
this.toast.showInfo('MFA.TOAST.DELETED', true);
this.refreshPageAfterTimout(2000);
});
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
const req = new AdminRemoveSecondFactorFromLoginPolicyRequest();
req.setType(type as SecondFactorType);
(this.service as AdminService).removeSecondFactorFromLoginPolicy(req).then(() => {
this.toast.showInfo('MFA.TOAST.DELETED', true);
this.refreshPageAfterTimout(2000);
});
}
}
}
});
}
public addMfa(): void {
let selection: any[] = [];
if (this.componentType === LoginMethodComponentType.MultiFactor) {
selection = [MultiFactorType.MULTI_FACTOR_TYPE_U2F_WITH_VERIFICATION];
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
selection = [SecondFactorType.SECOND_FACTOR_TYPE_U2F, SecondFactorType.SECOND_FACTOR_TYPE_OTP];
}
this.mfas.forEach(mfa => {
const index = selection.findIndex(sel => sel === mfa);
if (index > -1) {
selection.splice(index, 1);
}
});
const dialogRef = this.dialog.open(DialogAddTypeComponent, {
data: {
title: 'MFA.CREATE.TITLE',
desc: 'MFA.CREATE.DESCRIPTION',
componentType: this.componentType,
types: selection,
},
width: '400px',
});
dialogRef.afterClosed().subscribe((mfaType: MultiFactorType | SecondFactorType) => {
if (mfaType) {
if (this.serviceType === PolicyComponentServiceType.MGMT) {
if (this.componentType === LoginMethodComponentType.MultiFactor) {
const req = new MgmtAddMultiFactorToLoginPolicyRequest();
req.setType(mfaType as MultiFactorType);
(this.service as ManagementService).addMultiFactorToLoginPolicy(req).then(() => {
this.refreshPageAfterTimout(2000);
}).catch(error => {
this.toast.showError(error);
});
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
const req = new MgmtAddSecondFactorToLoginPolicyRequest();
req.setType(mfaType as SecondFactorType);
(this.service as ManagementService).addSecondFactorToLoginPolicy(req).then(() => {
this.refreshPageAfterTimout(2000);
}).catch(error => {
this.toast.showError(error);
});
}
} else if (this.serviceType === PolicyComponentServiceType.ADMIN) {
if (this.componentType === LoginMethodComponentType.MultiFactor) {
const req = new AdminAddMultiFactorToLoginPolicyRequest();
req.setType(mfaType as MultiFactorType);
(this.service as AdminService).addMultiFactorToLoginPolicy(req).then(() => {
this.refreshPageAfterTimout(2000);
}).catch(error => {
this.toast.showError(error);
});
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
const req = new AdminAddSecondFactorToLoginPolicyRequest();
req.setType(mfaType as SecondFactorType);
(this.service as AdminService).addSecondFactorToLoginPolicy(req).then(() => {
this.refreshPageAfterTimout(2000);
}).catch(error => {
this.toast.showError(error);
});
}
}
}
});
}
private async getData(): Promise<void> {
this.loadingSubject.next(true);
if (this.serviceType === PolicyComponentServiceType.MGMT) {
if (this.componentType === LoginMethodComponentType.MultiFactor) {
(this.service as ManagementService).listLoginPolicyMultiFactors().then(resp => {
this.mfas = resp.resultList;
this.loadingSubject.next(false);
}).catch(error => {
this.toast.showError(error);
this.loadingSubject.next(false);
});
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
(this.service as ManagementService).listLoginPolicySecondFactors().then(resp => {
this.mfas = resp.resultList;
this.loadingSubject.next(false);
}).catch(error => {
this.toast.showError(error);
this.loadingSubject.next(false);
});
}
} else if (this.serviceType === PolicyComponentServiceType.ADMIN) {
if (this.componentType === LoginMethodComponentType.MultiFactor) {
(this.service as AdminService).listLoginPolicyMultiFactors().then(resp => {
this.mfas = resp.resultList;
this.loadingSubject.next(false);
}).catch(error => {
this.toast.showError(error);
this.loadingSubject.next(false);
});
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
(this.service as AdminService).listLoginPolicySecondFactors().then(resp => {
this.mfas = resp.resultList;
this.loadingSubject.next(false);
}).catch(error => {
this.toast.showError(error);
this.loadingSubject.next(false);
});
}
}
}
public refreshPageAfterTimout(to: number): void {
setTimeout(() => {
this.getData();
}, to);
}
}

View File

@ -1,46 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatIconModule } from '@angular/material/icon';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatTableModule } from '@angular/material/table';
import { MatTooltipModule } from '@angular/material/tooltip';
import { RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { RefreshTableModule } from 'src/app/modules/refresh-table/refresh-table.module';
import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe/localized-date-pipe.module';
import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe/timestamp-to-date-pipe.module';
import { TruncatePipeModule } from 'src/app/pipes/truncate-pipe/truncate-pipe.module';
import { MfaTableComponent } from './mfa-table.component';
import { DialogAddTypeComponent } from './dialog-add-type/dialog-add-type.component';
import { InputModule } from '../input/input.module';
import { MatSelectModule } from '@angular/material/select';
import { MatRippleModule } from '@angular/material/core';
@NgModule({
declarations: [MfaTableComponent, DialogAddTypeComponent],
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
MatButtonModule,
MatIconModule,
InputModule,
MatSelectModule,
MatTooltipModule,
TranslateModule,
TimestampToDatePipeModule,
HasRoleModule,
MatProgressSpinnerModule,
MatRippleModule,
],
exports: [
MfaTableComponent,
],
})
export class MfaTableModule { }

View File

@ -1,24 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule } from '@angular/material/dialog';
import { MatSelectModule } from '@angular/material/select';
import { TranslateModule } from '@ngx-translate/core';
import { InputModule } from 'src/app/modules/input/input.module';
import { AddIdpDialogComponent } from './add-idp-dialog.component';
@NgModule({
declarations: [AddIdpDialogComponent],
imports: [
CommonModule,
MatDialogModule,
MatButtonModule,
TranslateModule,
InputModule,
MatSelectModule,
FormsModule,
],
})
export class AddIdpDialogModule { }

View File

@ -13,8 +13,6 @@
</mat-select> </mat-select>
</cnsl-form-field> </cnsl-form-field>
<p>{{'LOGINPOLICY.ADDIDP.SELECTIDPS' | translate}}</p>
<cnsl-form-field class="full-width" appearance="outline"> <cnsl-form-field class="full-width" appearance="outline">
<cnsl-label>{{ 'LOGINPOLICY.ADDIDP.SELECTIDPS' | translate }}</cnsl-label> <cnsl-label>{{ 'LOGINPOLICY.ADDIDP.SELECTIDPS' | translate }}</cnsl-label>
<mat-select [(ngModel)]="idp"> <mat-select [(ngModel)]="idp">

View File

@ -5,7 +5,7 @@ import { IDPQuery } from 'src/app/proto/generated/zitadel/management_pb';
import { ManagementService } from 'src/app/services/mgmt.service'; import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
import { PolicyComponentServiceType } from '../../policy-component-types.enum'; import { PolicyComponentServiceType } from '../../../policy-component-types.enum';
@Component({ @Component({
selector: 'app-add-idp-dialog', selector: 'app-add-idp-dialog',

View File

@ -0,0 +1,21 @@
<div class="idps">
<div class="idp mat-elevation-z1"
*ngFor="let idp of idps">
<img src="../../../assets/images/google.png"
*ngIf="idp.idpName.toLowerCase() == 'google'" alt="google" />
<div>
<span class="name">{{idp.idpName}}</span>
<span class="meta-info">{{ 'IDP.TYPES.'+idp.idpType | translate }}</span>
<!-- <span class="meta-info">{{ 'IDP.ID' | translate }}: {{idp.idpId}}</span> -->
</div>
<span class="fill-space"></span>
<button color="warn" *ngIf="!disabled" mat-icon-button (click)="removeIdp(idp)" class="rm">
<mat-icon matTooltip="{{'ACTIONS.REMOVE' | translate}}">remove_circle</mat-icon>
</button>
</div>
<button mat-raised-button color="primary" [disabled]="disabled"
class="new-idp" (click)="openDialog()" >
<span>{{'IDP.ADD' | translate}}</span>
<mat-icon class="icon">add</mat-icon>
</button>
</div>

View File

@ -0,0 +1,63 @@
.idps {
display: flex;
flex-direction: column;
.idp {
display: flex;
align-items: center;
padding: 10px;
border-radius: .5rem;
position: relative;
margin-bottom: .5rem;
min-height: 40px;
img {
height: 30px;
width: 30px;
margin: .5rem;
border-radius: .5rem;
object-fit: contain;
}
div {
display: block;
margin-left: .5rem;
* {
display: block;
}
}
.meta-info {
font-size: 14px;
color: var(--grey);
}
.fill-space {
flex: 1;
}
.rm {
display: none;
transition: all .2s ease;
cursor: pointer;
}
&:hover {
.rm {
display: block;
}
}
}
.new-idp {
display: flex;
align-items: center;
align-self: flex-end;
margin-top: .5rem;
.icon {
margin-left: .5rem;
}
}
}

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LoginPolicyIdpsComponent } from './login-policy-idps.component';
describe('LoginPolicyIdpsComponent', () => {
let component: LoginPolicyIdpsComponent;
let fixture: ComponentFixture<LoginPolicyIdpsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [LoginPolicyIdpsComponent],
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(LoginPolicyIdpsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,108 @@
import { Component, Input, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { IDP, IDPLoginPolicyLink, IDPOwnerType, IDPStylingType } from 'src/app/proto/generated/zitadel/idp_pb';
import { AdminService } from 'src/app/services/admin.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
import { PolicyComponentServiceType } from '../../policy-component-types.enum';
import { AddIdpDialogComponent } from './add-idp-dialog/add-idp-dialog.component';
@Component({
selector: 'cnsl-login-policy-idps',
templateUrl: './login-policy-idps.component.html',
styleUrls: ['./login-policy-idps.component.scss'],
})
export class LoginPolicyIdpsComponent implements OnInit {
@Input() public disabled: boolean = true;
@Input() public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
@Input() public service!: ManagementService | AdminService;
public loading: boolean = false;
public idps: IDPLoginPolicyLink.AsObject[] = [];
public IDPStylingType: any = IDPStylingType;
constructor(
private toast: ToastService,
private dialog: MatDialog,
) { }
ngOnInit(): void {
this.getIdps().then(resp => {
this.idps = resp;
console.log(this.idps);
});
}
private async getIdps(): Promise<IDPLoginPolicyLink.AsObject[]> {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return (this.service as ManagementService).listLoginPolicyIDPs()
.then((resp) => {
return resp.resultList;
});
case PolicyComponentServiceType.ADMIN:
return (this.service as AdminService).listLoginPolicyIDPs()
.then((providers) => {
return providers.resultList;
});
}
}
private addIdp(idp: IDP.AsObject | IDP.AsObject, ownerType: IDPOwnerType): Promise<any> {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return (this.service as ManagementService).addIDPToLoginPolicy(idp.id, ownerType);
case PolicyComponentServiceType.ADMIN:
return (this.service as AdminService).addIDPToLoginPolicy(idp.id);
}
}
public openDialog(): void {
const dialogRef = this.dialog.open(AddIdpDialogComponent, {
data: {
serviceType: this.serviceType,
},
width: '400px',
});
dialogRef.afterClosed().subscribe(resp => {
if (resp && resp.idp && resp.type) {
this.addIdp(resp.idp, resp.type).then(() => {
this.loading = true;
setTimeout(() => {
this.getIdps();
}, 1000);
}).catch(error => {
this.toast.showError(error);
});
}
});
}
public removeIdp(idp: IDPLoginPolicyLink.AsObject): void {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
(this.service as ManagementService).removeIDPFromLoginPolicy(idp.idpId).then(() => {
const index = this.idps.findIndex(temp => temp === idp);
if (index > -1) {
this.idps.splice(index, 1);
}
}, error => {
this.toast.showError(error);
});
break;
case PolicyComponentServiceType.ADMIN:
(this.service as AdminService).removeIDPFromLoginPolicy(idp.idpId).then(() => {
const index = this.idps.findIndex(temp => temp === idp);
if (index > -1) {
this.idps.splice(index, 1);
}
}, error => {
this.toast.showError(error);
});
break;
}
}
}

View File

@ -6,131 +6,35 @@
<div class="spinner-wr"> <div class="spinner-wr">
<mat-spinner diameter="30" *ngIf="loading" color="primary"></mat-spinner> <mat-spinner diameter="30" *ngIf="loading" color="primary"></mat-spinner>
</div> </div>
<ng-container *ngIf="serviceType === PolicyComponentServiceType.MGMT"> <ng-container *ngIf="serviceType === PolicyComponentServiceType.MGMT">
<ng-template appHasRole [appHasRole]="['policy.delete']"> <ng-template appHasRole [appHasRole]="['policy.delete']">
<button *ngIf="!isDefault" color="primary" matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="removePolicy()" <button *ngIf="!isDefault" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['login_policy'] | hasFeature | async) == false" color="primary" matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="removePolicy()"
mat-stroked-button> mat-stroked-button>
{{'POLICY.RESET' | translate}} {{'POLICY.RESET' | translate}}
</button> </button>
</ng-template> </ng-template>
<ng-template appHasRole [appHasRole]="['policy.write']"> <ng-template appHasRole [appHasRole]="['policy.write']">
<button *ngIf="isDefault" color="primary" matTooltip="{{'POLICY.CREATECUSTOM' | translate}}" (click)="savePolicy()" <button *ngIf="isDefault" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['login_policy'] | hasFeature | async) == false" color="primary" matTooltip="{{'POLICY.CREATECUSTOM' | translate}}" (click)="savePolicy()"
mat-raised-button> mat-raised-button>
{{'POLICY.CREATECUSTOM' | translate}} {{'POLICY.CREATECUSTOM' | translate}}
</button> </button>
</ng-template> </ng-template>
</ng-container> </ng-container>
<div class="content" *ngIf="loginData">
<div class="row">
<mat-slide-toggle class="toggle" color="primary" [disabled]="disabled || serviceType == PolicyComponentServiceType.MGMT && (['login_policy.username_login'] | hasFeature | async) == false" ngDefaultControl
[(ngModel)]="loginData.allowUsernamePassword">
{{'POLICY.DATA.ALLOWUSERNAMEPASSWORD' | translate}}
</mat-slide-toggle>
<cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['login_policy.username_login'] | hasFeature | async) == false; else usernameInfo" [featureLink]="['/org/features']" class="info" type="WARN"> <app-card title="{{ 'IDP.LIST.ACTIVETITLE' | translate }}"
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.username_login'})"></span> [expanded]="true">
</cnsl-info-section>
<ng-template #usernameInfo> <cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) == false" [featureLink]="['/org/features']" class="info" type="WARN">
<cnsl-info-section class="info"> <span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.idp'})"></span>
{{'POLICY.DATA.ALLOWUSERNAMEPASSWORD_DESC' | translate}}
</cnsl-info-section>
</ng-template>
</div>
<div class="row">
<mat-slide-toggle class="toggle" color="primary"
[disabled]="disabled || (serviceType == PolicyComponentServiceType.MGMT && (['login_policy.registration'] | hasFeature | async) == false)"
ngDefaultControl [(ngModel)]="loginData.allowRegister">
{{'POLICY.DATA.ALLOWREGISTER' | translate}}
</mat-slide-toggle>
<cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['login_policy.registration'] | hasFeature | async) == false; else regInfo" [featureLink]="['/org/features']" class="info" type="WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.registration'})"></span>
</cnsl-info-section>
<ng-template #regInfo>
<cnsl-info-section class="info">
{{'POLICY.DATA.ALLOWREGISTER_DESC' | translate}}
</cnsl-info-section>
</ng-template>
</div>
<div class="row">
<mat-slide-toggle class="toggle" color="primary" [disabled]="disabled || serviceType == PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) == false" ngDefaultControl
[(ngModel)]="loginData.allowExternalIdp">
{{'POLICY.DATA.ALLOWEXTERNALIDP' | translate}}
</mat-slide-toggle>
<cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) == false; else idpInfo" [featureLink]="['/org/features']" class="info" type="WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.idp'})"></span>
</cnsl-info-section>
<ng-template #idpInfo>
<cnsl-info-section class="info">
{{'POLICY.DATA.ALLOWEXTERNALIDP_DESC' | translate}}
</cnsl-info-section>
</ng-template>
</div>
<div class="row">
<mat-slide-toggle class="toggle" color="primary" [disabled]="disabled || serviceType == PolicyComponentServiceType.MGMT && (['login_policy.factors'] | hasFeature | async) == false" ngDefaultControl
[(ngModel)]="loginData.forceMfa">
{{'POLICY.DATA.FORCEMFA' | translate}}
</mat-slide-toggle>
<cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['login_policy.factors'] | hasFeature | async) == false; else factorsInfo" [featureLink]="['/org/features']" class="info" type="WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.factors'})"></span>
</cnsl-info-section>
<ng-template #factorsInfo>
<cnsl-info-section class="info">
{{'POLICY.DATA.FORCEMFA_DESC' | translate}}
</cnsl-info-section>
</ng-template>
</div>
<div class="row">
<mat-slide-toggle class="toggle" color="primary" [disabled]="disabled || serviceType == PolicyComponentServiceType.MGMT && (['login_policy.password_reset'] | hasFeature | async) == false" ngDefaultControl
[(ngModel)]="loginData.hidePasswordReset">
{{'POLICY.DATA.HIDEPASSWORDRESET' | translate}}
</mat-slide-toggle>
<cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['login_policy.password_reset'] | hasFeature | async) == false; else passwordResetInfo" [featureLink]="['/org/features']" class="info" type="WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.password_reset'})"></span>
</cnsl-info-section> </cnsl-info-section>
<cnsl-login-policy-idps [serviceType]="serviceType" [service]="service" [disabled]="disabled || (serviceType == PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) == false)"></cnsl-login-policy-idps>
</app-card>
<ng-template #passwordResetInfo> <app-card title="{{ 'MFA.LIST.MULTIFACTORTITLE' | translate }}" description="{{'MFA.LIST.MULTIFACTORDESCRIPTION' | translate}}" [expanded]="false">
<cnsl-info-section class="info">
{{'POLICY.DATA.HIDEPASSWORDRESET_DESC' | translate}}
</cnsl-info-section>
</ng-template>
</div>
<div class="row">
<cnsl-form-field class="form-field" label="Access Code" required="true">
<cnsl-label>{{'LOGINPOLICY.PASSWORDLESS' | translate}}</cnsl-label>
<mat-select [(ngModel)]="loginData.passwordlessType"
[disabled]="disabled || (serviceType == PolicyComponentServiceType.MGMT && (['login_policy.passwordless'] | hasFeature | async) == false)">
<mat-option *ngFor="let pt of passwordlessTypes" [value]="pt">
{{'LOGINPOLICY.PASSWORDLESSTYPE.'+pt | translate}}
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['login_policy.passwordless'] | hasFeature | async) == false" [featureLink]="['/org/features']" class="info" type="WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.passwordless'})"></span>
</cnsl-info-section>
</div>
</div>
<button [disabled]="disabled" class="save-button" (click)="savePolicy()" color="primary" type="submit"
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
<div class="divider"></div>
<ng-container *ngIf="!isDefault">
<h3 class="subheader">{{ 'MFA.LIST.MULTIFACTORTITLE' | translate }}</h3>
<p class="subdesc">{{ 'MFA.LIST.MULTIFACTORDESCRIPTION' | translate }}</p>
<cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['login_policy.factors'] | hasFeature | async) == false" [featureLink]="['/org/features']" class="info" type="WARN"> <cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['login_policy.factors'] | hasFeature | async) == false" [featureLink]="['/org/features']" class="info" type="WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.factors'})"></span> <span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.factors'})"></span>
</cnsl-info-section> </cnsl-info-section>
@ -139,62 +43,134 @@
[componentType]="LoginMethodComponentType.MultiFactor" [componentType]="LoginMethodComponentType.MultiFactor"
[disabled]="(([serviceType == PolicyComponentServiceType.ADMIN ? 'iam.policy.write' : serviceType == PolicyComponentServiceType.MGMT ? 'policy.write' : ''] | hasRole | async) == false) || (serviceType == PolicyComponentServiceType.MGMT && (['login_policy.factors'] | hasFeature | async) == false)"> [disabled]="(([serviceType == PolicyComponentServiceType.ADMIN ? 'iam.policy.write' : serviceType == PolicyComponentServiceType.MGMT ? 'policy.write' : ''] | hasRole | async) == false) || (serviceType == PolicyComponentServiceType.MGMT && (['login_policy.factors'] | hasFeature | async) == false)">
</app-mfa-table> </app-mfa-table>
</app-card>
<h3 class="subheader">{{ 'MFA.LIST.SECONDFACTORTITLE' | translate }}</h3> <app-card title="{{ 'MFA.LIST.SECONDFACTORTITLE' | translate }}" description="{{'MFA.LIST.SECONDFACTORDESCRIPTION' | translate}}" [expanded]="false">
<p class="subdesc">{{ 'MFA.LIST.SECONDFACTORDESCRIPTION' | translate }}</p> <cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['login_policy.factors'] | hasFeature | async) == false" [featureLink]="['/org/features']" class="info" type="WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.factors'})"></span>
</cnsl-info-section>
<cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['login_policy.factors'] | hasFeature | async) == false" [featureLink]="['/org/features']" class="info" type="WARN"> <app-mfa-table [service]="service" [serviceType]="serviceType"
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.factors'})"></span> [componentType]="LoginMethodComponentType.SecondFactor"
</cnsl-info-section> [disabled]="([serviceType == PolicyComponentServiceType.ADMIN ? 'iam.policy.write' : serviceType == PolicyComponentServiceType.MGMT ? 'policy.write' : ''] | hasRole | async) == false || (serviceType == PolicyComponentServiceType.MGMT && (['login_policy.factors'] | hasFeature | async) == false)">
</app-mfa-table>
</app-card>
<app-mfa-table [service]="service" [serviceType]="serviceType" <app-card title="{{ 'POLICY.LOGIN_POLICY.ADVANCED' | translate }}" [expanded]="false">
[componentType]="LoginMethodComponentType.SecondFactor" <div class="content" *ngIf="loginData">
[disabled]="([serviceType == PolicyComponentServiceType.ADMIN ? 'iam.policy.write' : serviceType == PolicyComponentServiceType.MGMT ? 'policy.write' : ''] | hasRole | async) == false || (serviceType == PolicyComponentServiceType.MGMT && (['login_policy.factors'] | hasFeature | async) == false)"> <div class="row">
</app-mfa-table> <mat-slide-toggle class="toggle" color="primary" [disabled]="disabled || serviceType == PolicyComponentServiceType.MGMT && (['login_policy.username_login'] | hasFeature | async) == false" ngDefaultControl
</ng-container> [(ngModel)]="loginData.allowUsernamePassword">
{{'POLICY.DATA.ALLOWUSERNAMEPASSWORD' | translate}}
</mat-slide-toggle>
<h3 class="subheader">{{'LOGINPOLICY.IDPS' | translate}}</h3> <cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['login_policy.username_login'] | hasFeature | async) == false; else usernameInfo" [featureLink]="['/org/features']" class="info" type="WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.username_login'})"></span>
</cnsl-info-section>
<cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) == false" [featureLink]="['/org/features']" class="info" type="WARN"> <ng-template #usernameInfo>
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.idp'})"></span> <cnsl-info-section class="info">
</cnsl-info-section> {{'POLICY.DATA.ALLOWUSERNAMEPASSWORD_DESC' | translate}}
</cnsl-info-section>
</ng-template>
</div>
<div class="row">
<mat-slide-toggle class="toggle" color="primary"
[disabled]="disabled || (serviceType == PolicyComponentServiceType.MGMT && (['login_policy.registration'] | hasFeature | async) == false)"
ngDefaultControl [(ngModel)]="loginData.allowRegister">
{{'POLICY.DATA.ALLOWREGISTER' | translate}}
</mat-slide-toggle>
<div class="idps"> <cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['login_policy.registration'] | hasFeature | async) == false; else regInfo" [featureLink]="['/org/features']" class="info" type="WARN">
<div class="idp" <span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.registration'})"></span>
[ngClass]="{'disabled': (disabled || (serviceType == PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) == false))}" </cnsl-info-section>
*ngFor="let idp of idps">
<button <ng-template #regInfo>
[disabled]="disabled || (serviceType == PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) == false)" <cnsl-info-section class="info">
mat-icon-button (click)="removeIdp(idp)" class="rm"> {{'POLICY.DATA.ALLOWREGISTER_DESC' | translate}}
<mat-icon matTooltip="{{'ACTIONS.REMOVE' | translate}}"> </cnsl-info-section>
remove_circle</mat-icon> </ng-template>
</button> </div>
<div class="line"> <div class="row">
<img src="../../../assets/images/google.png" <mat-slide-toggle class="toggle" color="primary" [disabled]="disabled || serviceType == PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) == false" ngDefaultControl
*ngIf="idp.stylingType == IDPStylingType.STYLING_TYPE_GOOGLE" alt="google" /> [(ngModel)]="loginData.allowExternalIdp">
<div> {{'POLICY.DATA.ALLOWEXTERNALIDP' | translate}}
<span class="name">{{idp.idpName}}</span> </mat-slide-toggle>
<span class="meta-info">{{ 'IDP.TYPE' | translate }}: {{ 'IDP.TYPES.'+idp.idpType |
translate <cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) == false; else idpInfo" [featureLink]="['/org/features']" class="info" type="WARN">
}}</span> <span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.idp'})"></span>
<span class="meta-info">{{ 'IDP.ID' | translate }}: {{idp.idpId}}</span> </cnsl-info-section>
</div>
</div> <ng-template #idpInfo>
<cnsl-info-section class="info">
{{'POLICY.DATA.ALLOWEXTERNALIDP_DESC' | translate}}
</cnsl-info-section>
</ng-template>
</div>
<div class="row">
<mat-slide-toggle class="toggle" color="primary" [disabled]="disabled || serviceType == PolicyComponentServiceType.MGMT && (['login_policy.factors'] | hasFeature | async) == false" ngDefaultControl
[(ngModel)]="loginData.forceMfa">
{{'POLICY.DATA.FORCEMFA' | translate}}
</mat-slide-toggle>
<cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['login_policy.factors'] | hasFeature | async) == false; else factorsInfo" [featureLink]="['/org/features']" class="info" type="WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.factors'})"></span>
</cnsl-info-section>
<ng-template #factorsInfo>
<cnsl-info-section class="info">
{{'POLICY.DATA.FORCEMFA_DESC' | translate}}
</cnsl-info-section>
</ng-template>
</div>
<div class="row">
<mat-slide-toggle class="toggle" color="primary" [disabled]="disabled || serviceType == PolicyComponentServiceType.MGMT && (['login_policy.password_reset'] | hasFeature | async) == false" ngDefaultControl
[(ngModel)]="loginData.hidePasswordReset">
{{'POLICY.DATA.HIDEPASSWORDRESET' | translate}}
</mat-slide-toggle>
<cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['login_policy.password_reset'] | hasFeature | async) == false; else passwordResetInfo" [featureLink]="['/org/features']" class="info" type="WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.password_reset'})"></span>
</cnsl-info-section>
<ng-template #passwordResetInfo>
<cnsl-info-section class="info">
{{'POLICY.DATA.HIDEPASSWORDRESET_DESC' | translate}}
</cnsl-info-section>
</ng-template>
</div> </div>
<div *ngIf="!disabled && ((serviceType == PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async)) || serviceType == PolicyComponentServiceType.ADMIN)"
class="new-idp" (click)="openDialog()" matRipple> <div class="row">
<mat-icon>add</mat-icon> <cnsl-form-field class="form-field" label="Access Code" required="true">
</div> <cnsl-label>{{'LOGINPOLICY.PASSWORDLESS' | translate}}</cnsl-label>
</div> <mat-select [(ngModel)]="loginData.passwordlessType"
[disabled]="disabled || (serviceType == PolicyComponentServiceType.MGMT && (['login_policy.passwordless'] | hasFeature | async) == false)">
<mat-option *ngFor="let pt of passwordlessTypes" [value]="pt">
{{'LOGINPOLICY.PASSWORDLESSTYPE.'+pt | translate}}
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['login_policy.passwordless'] | hasFeature | async) == false" [featureLink]="['/org/features']" class="info" type="WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.passwordless'})"></span>
</cnsl-info-section>
</div>
</div>
</app-card>
<button [disabled]="disabled" class="save-button" (click)="savePolicy()" color="primary" type="submit"
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
<div class="divider"></div>
<ng-template appHasRole [appHasRole]="['org.idp.read']"> <ng-template appHasRole [appHasRole]="['org.idp.read']">
<app-card title="{{ 'IDP.LIST.TITLE' | translate }}" description="{{ 'IDP.LIST.DESCRIPTION' | translate }}" <app-card title="{{ 'IDP.LIST.TITLE' | translate }}" description="{{ 'IDP.LIST.DESCRIPTION' | translate }}"
[expanded]="false"> [expanded]="false">
<app-idp-table [service]="service" [serviceType]="serviceType" <app-idp-table [service]="service" [serviceType]="serviceType"
[disabled]="([serviceType == PolicyComponentServiceType.ADMIN ? 'iam.idp.write' : serviceType == PolicyComponentServiceType.MGMT ? 'org.idp.write' : ''] | hasRole | async) == false || ((serviceType == PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) == false))"> [disabled]="([serviceType == PolicyComponentServiceType.ADMIN ? 'iam.idp.write' : serviceType == PolicyComponentServiceType.MGMT ? 'org.idp.write' : ''] | hasRole | async) == false || ((serviceType == PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) == false))">
</app-idp-table> </app-idp-table>
</app-card> </app-card>
</ng-template> </ng-template>
<app-policy-grid class="grid" [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="security"></app-policy-grid> <app-policy-grid class="grid" [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="security"></app-policy-grid>

View File

@ -37,95 +37,6 @@
font-size: 14px; font-size: 14px;
} }
.idps {
display: flex;
margin: 0 -.5rem;
.idp {
background-color: #506e6e;
color: white;
.line {
display: flex;
align-items: center;
img {
height: 30px;
width: 30px;
margin-right: 1rem;
border-radius: .5rem;
object-fit: contain;
}
div {
flex: 1;
display: block;
* {
display: block;
}
}
}
}
.idp,
.new-idp {
display: grid;
align-items: center;
justify-content: center;
margin: .5rem;
padding: 10px;
border: 1px solid var(--grey);
border-radius: .5rem;
position: relative;
min-height: 70px;
min-width: 150px;
.name {
font-weight: 700;
}
span {
padding: 2px;
}
.rm {
color: var(--warn);
position: absolute;
display: none;
top: -2px;
transition: all .2s ease;
left: -2px;
transform: translateX(-50%) translateY(-50%);
cursor: pointer;
&[disabled] {
display: none;
}
}
&:not(.disabled) {
cursor: default;
&:hover {
.rm {
display: block;
}
}
}
img {
height: 100%;
width: 100%;
object-fit: scale-down;
}
}
.new-idp:not(.disabled) {
cursor: pointer;
}
}
.divider { .divider {
width: 100%; width: 100%;
height: 1px; height: 1px;

View File

@ -1,15 +1,12 @@
import { Component, Injector, OnDestroy, Type } from '@angular/core'; import { Component, Injector, OnDestroy, Type } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { switchMap } from 'rxjs/operators'; import { switchMap } from 'rxjs/operators';
import { LoginMethodComponentType } from 'src/app/modules/mfa-table/mfa-table.component';
import { import {
GetLoginPolicyResponse as AdminGetLoginPolicyResponse, GetLoginPolicyResponse as AdminGetLoginPolicyResponse,
UpdateLoginPolicyRequest, UpdateLoginPolicyRequest,
UpdateLoginPolicyResponse, UpdateLoginPolicyResponse,
} from 'src/app/proto/generated/zitadel/admin_pb'; } from 'src/app/proto/generated/zitadel/admin_pb';
import { IDP, IDPLoginPolicyLink, IDPOwnerType, IDPStylingType } from 'src/app/proto/generated/zitadel/idp_pb';
import { import {
AddCustomLoginPolicyRequest, AddCustomLoginPolicyRequest,
GetLoginPolicyResponse as MgmtGetLoginPolicyResponse, GetLoginPolicyResponse as MgmtGetLoginPolicyResponse,
@ -21,7 +18,7 @@ import { ToastService } from 'src/app/services/toast.service';
import { GridPolicy, LOGIN_POLICY } from '../../policy-grid/policies'; import { GridPolicy, LOGIN_POLICY } from '../../policy-grid/policies';
import { PolicyComponentServiceType } from '../policy-component-types.enum'; import { PolicyComponentServiceType } from '../policy-component-types.enum';
import { AddIdpDialogComponent } from './add-idp-dialog/add-idp-dialog.component'; import { LoginMethodComponentType } from './mfa-table/mfa-table.component';
@Component({ @Component({
selector: 'app-login-policy', selector: 'app-login-policy',
@ -37,17 +34,14 @@ export class LoginPolicyComponent implements OnDestroy {
public service!: ManagementService | AdminService; public service!: ManagementService | AdminService;
public PolicyComponentServiceType: any = PolicyComponentServiceType; public PolicyComponentServiceType: any = PolicyComponentServiceType;
public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT; public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
public idps: IDPLoginPolicyLink.AsObject[] = [];
public loading: boolean = false; public loading: boolean = false;
public disabled: boolean = true; public disabled: boolean = true;
public IDPStylingType: any = IDPStylingType;
public currentPolicy: GridPolicy = LOGIN_POLICY; public currentPolicy: GridPolicy = LOGIN_POLICY;
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
private toast: ToastService, private toast: ToastService,
private dialog: MatDialog,
private injector: Injector, private injector: Injector,
) { ) {
this.sub = this.route.data.pipe(switchMap(data => { this.sub = this.route.data.pipe(switchMap(data => {
@ -83,9 +77,7 @@ export class LoginPolicyComponent implements OnDestroy {
this.disabled = this.isDefault; this.disabled = this.isDefault;
} }
}); });
this.getIdps().then(resp => {
this.idps = resp;
});
} }
public ngOnDestroy(): void { public ngOnDestroy(): void {
@ -102,21 +94,6 @@ export class LoginPolicyComponent implements OnDestroy {
} }
} }
private async getIdps(): Promise<IDPLoginPolicyLink.AsObject[]> {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return (this.service as ManagementService).listLoginPolicyIDPs()
.then((resp) => {
return resp.resultList;
});
case PolicyComponentServiceType.ADMIN:
return (this.service as AdminService).listLoginPolicyIDPs()
.then((providers) => {
return providers.resultList;
});
}
}
private async updateData(): private async updateData():
Promise<UpdateLoginPolicyResponse.AsObject> { Promise<UpdateLoginPolicyResponse.AsObject> {
switch (this.serviceType) { switch (this.serviceType) {
@ -172,62 +149,6 @@ export class LoginPolicyComponent implements OnDestroy {
} }
} }
public openDialog(): void {
const dialogRef = this.dialog.open(AddIdpDialogComponent, {
data: {
serviceType: this.serviceType,
},
width: '400px',
});
dialogRef.afterClosed().subscribe(resp => {
if (resp && resp.idp && resp.type) {
this.addIdp(resp.idp, resp.type).then(() => {
this.loading = true;
setTimeout(() => {
this.fetchData();
}, 2000);
}).catch(error => {
this.toast.showError(error);
});
}
});
}
private addIdp(idp: IDP.AsObject | IDP.AsObject, ownerType: IDPOwnerType): Promise<any> {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return (this.service as ManagementService).addIDPToLoginPolicy(idp.id, ownerType);
case PolicyComponentServiceType.ADMIN:
return (this.service as AdminService).addIDPToLoginPolicy(idp.id);
}
}
public removeIdp(idp: IDPLoginPolicyLink.AsObject): void {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
(this.service as ManagementService).removeIDPFromLoginPolicy(idp.idpId).then(() => {
const index = this.idps.findIndex(temp => temp === idp);
if (index > -1) {
this.idps.splice(index, 1);
}
}, error => {
this.toast.showError(error);
});
break;
case PolicyComponentServiceType.ADMIN:
(this.service as AdminService).removeIDPFromLoginPolicy(idp.idpId).then(() => {
const index = this.idps.findIndex(temp => temp === idp);
if (index > -1) {
this.idps.splice(index, 1);
}
}, error => {
this.toast.showError(error);
});
break;
}
}
public get isDefault(): boolean { public get isDefault(): boolean {
if (this.loginData && this.serviceType === PolicyComponentServiceType.MGMT) { if (this.loginData && this.serviceType === PolicyComponentServiceType.MGMT) {
return (this.loginData as LoginPolicy.AsObject).isDefault; return (this.loginData as LoginPolicy.AsObject).isDefault;

View File

@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatRippleModule } from '@angular/material/core'; import { MatRippleModule } from '@angular/material/core';
import { MatDialogModule } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select'; import { MatSelectModule } from '@angular/material/select';
@ -14,18 +15,26 @@ import { CardModule } from 'src/app/modules/card/card.module';
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module'; import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module';
import { IdpTableModule } from 'src/app/modules/idp-table/idp-table.module'; import { IdpTableModule } from 'src/app/modules/idp-table/idp-table.module';
import { InputModule } from 'src/app/modules/input/input.module'; import { InputModule } from 'src/app/modules/input/input.module';
import { MfaTableModule } from 'src/app/modules/mfa-table/mfa-table.module';
import { HasFeaturePipeModule } from 'src/app/pipes/has-feature-pipe/has-feature-pipe.module'; import { HasFeaturePipeModule } from 'src/app/pipes/has-feature-pipe/has-feature-pipe.module';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module'; import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
import { InfoSectionModule } from '../../info-section/info-section.module'; import { InfoSectionModule } from '../../info-section/info-section.module';
import { PolicyGridModule } from '../../policy-grid/policy-grid.module'; import { PolicyGridModule } from '../../policy-grid/policy-grid.module';
import { AddIdpDialogModule } from './add-idp-dialog/add-idp-dialog.module'; import { AddIdpDialogComponent } from './login-policy-idps/add-idp-dialog/add-idp-dialog.component';
import { LoginPolicyIdpsComponent } from './login-policy-idps/login-policy-idps.component';
import { LoginPolicyRoutingModule } from './login-policy-routing.module'; import { LoginPolicyRoutingModule } from './login-policy-routing.module';
import { LoginPolicyComponent } from './login-policy.component'; import { LoginPolicyComponent } from './login-policy.component';
import { DialogAddTypeComponent } from './mfa-table/dialog-add-type/dialog-add-type.component';
import { MfaTableComponent } from './mfa-table/mfa-table.component';
@NgModule({ @NgModule({
declarations: [LoginPolicyComponent], declarations: [
LoginPolicyComponent,
LoginPolicyIdpsComponent,
MfaTableComponent,
DialogAddTypeComponent,
AddIdpDialogComponent,
],
imports: [ imports: [
LoginPolicyRoutingModule, LoginPolicyRoutingModule,
CommonModule, CommonModule,
@ -33,21 +42,20 @@ import { LoginPolicyComponent } from './login-policy.component';
FormsModule, FormsModule,
CardModule, CardModule,
InputModule, InputModule,
MatIconModule,
MatButtonModule, MatButtonModule,
HasFeaturePipeModule, HasFeaturePipeModule,
MatSlideToggleModule, MatSlideToggleModule,
MatIconModule,
HasRoleModule, HasRoleModule,
MatDialogModule,
HasRolePipeModule, HasRolePipeModule,
MatTooltipModule, MatTooltipModule,
TranslateModule,
DetailLayoutModule, DetailLayoutModule,
AddIdpDialogModule,
IdpTableModule, IdpTableModule,
MfaTableModule,
MatProgressSpinnerModule, MatProgressSpinnerModule,
MatSelectModule, MatSelectModule,
MatRippleModule, MatRippleModule,
TranslateModule,
PolicyGridModule, PolicyGridModule,
], ],
}) })

View File

@ -0,0 +1,19 @@
<div class="sp_wrapper">
<mat-spinner diameter="30" *ngIf="loading$ | async"></mat-spinner>
</div>
<div class="mfa-list">
<div class="mfa mat-elevation-z1" *ngFor="let mfa of mfas">
<span>
{{(componentType == LoginMethodComponentType.SecondFactor ? 'MFA.SECONDFACTORTYPES.':
LoginMethodComponentType.MultiFactor ? 'MFA.MULTIFACTORTYPES.': '')+mfa | translate}}
</span>
<button color="warn" *ngIf="!disabled" mat-icon-button (click)="removeMfa(mfa)" class="rm">
<mat-icon matTooltip="{{'ACTIONS.REMOVE' | translate}}">remove_circle</mat-icon>
</button>
</div>
<button mat-raised-button color="primary" class="new-mfa" [disabled]="disabled" (click)="!disabled ? addMfa(): null">
<span>{{'ACTIONS.ADD' | translate}}</span>
<mat-icon class="icon">add</mat-icon>
</button>
</div>

View File

@ -0,0 +1,41 @@
.mfa-list {
display: flex;
flex-direction: column;
.mfa {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px;
border-radius: .5rem;
margin-bottom: .5rem;
min-height: 40px;
.fill-space {
flex: 1;
}
.rm {
display: none;
transition: all .2s ease;
cursor: pointer;
}
&:hover {
.rm {
display: block;
}
}
}
.new-mfa {
display: flex;
align-items: center;
align-self: flex-end;
margin-top: .5rem;
.icon {
margin-left: .5rem;
}
}
}

View File

@ -0,0 +1,224 @@
import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatPaginator } from '@angular/material/paginator';
import { TranslateService } from '@ngx-translate/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { PolicyComponentServiceType } from 'src/app/modules/policies/policy-component-types.enum';
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
import {
AddMultiFactorToLoginPolicyRequest as AdminAddMultiFactorToLoginPolicyRequest,
AddSecondFactorToLoginPolicyRequest as AdminAddSecondFactorToLoginPolicyRequest,
RemoveMultiFactorFromLoginPolicyRequest as AdminRemoveMultiFactorFromLoginPolicyRequest,
RemoveSecondFactorFromLoginPolicyRequest as AdminRemoveSecondFactorFromLoginPolicyRequest,
} from 'src/app/proto/generated/zitadel/admin_pb';
import {
AddMultiFactorToLoginPolicyRequest as MgmtAddMultiFactorToLoginPolicyRequest,
AddSecondFactorToLoginPolicyRequest as MgmtAddSecondFactorToLoginPolicyRequest,
RemoveMultiFactorFromLoginPolicyRequest as MgmtRemoveMultiFactorFromLoginPolicyRequest,
RemoveSecondFactorFromLoginPolicyRequest as MgmtRemoveSecondFactorFromLoginPolicyRequest,
} from 'src/app/proto/generated/zitadel/management_pb';
import { MultiFactorType, SecondFactorType } from 'src/app/proto/generated/zitadel/policy_pb';
import { AdminService } from 'src/app/services/admin.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
import { DialogAddTypeComponent } from './dialog-add-type/dialog-add-type.component';
export enum LoginMethodComponentType {
MultiFactor = 1,
SecondFactor = 2,
}
@Component({
selector: 'app-mfa-table',
templateUrl: './mfa-table.component.html',
styleUrls: ['./mfa-table.component.scss'],
})
export class MfaTableComponent implements OnInit {
public LoginMethodComponentType: any = LoginMethodComponentType;
@Input() componentType!: LoginMethodComponentType;
@Input() public serviceType!: PolicyComponentServiceType;
@Input() service!: AdminService | ManagementService;
@Input() disabled: boolean = false;
@ViewChild(MatPaginator) public paginator!: MatPaginator;
public mfas: Array<MultiFactorType | SecondFactorType> = [];
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
public PolicyComponentServiceType: any = PolicyComponentServiceType;
constructor(public translate: TranslateService, private toast: ToastService, private dialog: MatDialog) { }
public ngOnInit(): void {
this.getData();
}
public removeMfa(type: MultiFactorType | SecondFactorType): void {
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.DELETE',
cancelKey: 'ACTIONS.CANCEL',
titleKey: 'MFA.DELETE.TITLE',
descriptionKey: 'MFA.DELETE.DESCRIPTION',
},
width: '400px',
});
dialogRef.afterClosed().subscribe(resp => {
if (resp) {
if (this.serviceType === PolicyComponentServiceType.MGMT) {
if (this.componentType === LoginMethodComponentType.MultiFactor) {
const req = new MgmtRemoveMultiFactorFromLoginPolicyRequest();
req.setType(type as MultiFactorType);
(this.service as ManagementService).removeMultiFactorFromLoginPolicy(req).then(() => {
this.toast.showInfo('MFA.TOAST.DELETED', true);
this.refreshPageAfterTimout(2000);
});
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
const req = new MgmtRemoveSecondFactorFromLoginPolicyRequest();
req.setType(type as SecondFactorType);
(this.service as ManagementService).removeSecondFactorFromLoginPolicy(req).then(() => {
this.toast.showInfo('MFA.TOAST.DELETED', true);
this.refreshPageAfterTimout(2000);
});
}
} else if (this.serviceType === PolicyComponentServiceType.ADMIN) {
if (this.componentType === LoginMethodComponentType.MultiFactor) {
const req = new AdminRemoveMultiFactorFromLoginPolicyRequest();
req.setType(type as MultiFactorType);
(this.service as AdminService).removeMultiFactorFromLoginPolicy(req).then(() => {
this.toast.showInfo('MFA.TOAST.DELETED', true);
this.refreshPageAfterTimout(2000);
});
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
const req = new AdminRemoveSecondFactorFromLoginPolicyRequest();
req.setType(type as SecondFactorType);
(this.service as AdminService).removeSecondFactorFromLoginPolicy(req).then(() => {
this.toast.showInfo('MFA.TOAST.DELETED', true);
this.refreshPageAfterTimout(2000);
});
}
}
}
});
}
public addMfa(): void {
let selection: any[] = [];
if (this.componentType === LoginMethodComponentType.MultiFactor) {
selection = [MultiFactorType.MULTI_FACTOR_TYPE_U2F_WITH_VERIFICATION];
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
selection = [SecondFactorType.SECOND_FACTOR_TYPE_U2F, SecondFactorType.SECOND_FACTOR_TYPE_OTP];
}
this.mfas.forEach(mfa => {
const index = selection.findIndex(sel => sel === mfa);
if (index > -1) {
selection.splice(index, 1);
}
});
const dialogRef = this.dialog.open(DialogAddTypeComponent, {
data: {
title: 'MFA.CREATE.TITLE',
desc: 'MFA.CREATE.DESCRIPTION',
componentType: this.componentType,
types: selection,
},
width: '400px',
});
dialogRef.afterClosed().subscribe((mfaType: MultiFactorType | SecondFactorType) => {
if (mfaType) {
if (this.serviceType === PolicyComponentServiceType.MGMT) {
if (this.componentType === LoginMethodComponentType.MultiFactor) {
const req = new MgmtAddMultiFactorToLoginPolicyRequest();
req.setType(mfaType as MultiFactorType);
(this.service as ManagementService).addMultiFactorToLoginPolicy(req).then(() => {
this.refreshPageAfterTimout(2000);
}).catch(error => {
this.toast.showError(error);
});
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
const req = new MgmtAddSecondFactorToLoginPolicyRequest();
req.setType(mfaType as SecondFactorType);
(this.service as ManagementService).addSecondFactorToLoginPolicy(req).then(() => {
this.refreshPageAfterTimout(2000);
}).catch(error => {
this.toast.showError(error);
});
}
} else if (this.serviceType === PolicyComponentServiceType.ADMIN) {
if (this.componentType === LoginMethodComponentType.MultiFactor) {
const req = new AdminAddMultiFactorToLoginPolicyRequest();
req.setType(mfaType as MultiFactorType);
(this.service as AdminService).addMultiFactorToLoginPolicy(req).then(() => {
this.refreshPageAfterTimout(2000);
}).catch(error => {
this.toast.showError(error);
});
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
const req = new AdminAddSecondFactorToLoginPolicyRequest();
req.setType(mfaType as SecondFactorType);
(this.service as AdminService).addSecondFactorToLoginPolicy(req).then(() => {
this.refreshPageAfterTimout(2000);
}).catch(error => {
this.toast.showError(error);
});
}
}
}
});
}
private async getData(): Promise<void> {
this.loadingSubject.next(true);
if (this.serviceType === PolicyComponentServiceType.MGMT) {
if (this.componentType === LoginMethodComponentType.MultiFactor) {
(this.service as ManagementService).listLoginPolicyMultiFactors().then(resp => {
this.mfas = resp.resultList;
this.loadingSubject.next(false);
}).catch(error => {
this.toast.showError(error);
this.loadingSubject.next(false);
});
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
(this.service as ManagementService).listLoginPolicySecondFactors().then(resp => {
this.mfas = resp.resultList;
this.loadingSubject.next(false);
}).catch(error => {
this.toast.showError(error);
this.loadingSubject.next(false);
});
}
} else if (this.serviceType === PolicyComponentServiceType.ADMIN) {
if (this.componentType === LoginMethodComponentType.MultiFactor) {
(this.service as AdminService).listLoginPolicyMultiFactors().then(resp => {
this.mfas = resp.resultList;
this.loadingSubject.next(false);
}).catch(error => {
this.toast.showError(error);
this.loadingSubject.next(false);
});
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
(this.service as AdminService).listLoginPolicySecondFactors().then(resp => {
this.mfas = resp.resultList;
this.loadingSubject.next(false);
}).catch(error => {
this.toast.showError(error);
this.loadingSubject.next(false);
});
}
}
}
public refreshPageAfterTimout(to: number): void {
setTimeout(() => {
this.getData();
}, to);
}
}

View File

@ -1,180 +1,181 @@
<app-refresh-table [loading]="dataSource?.loading$ | async" (refreshed)="changePage()" <app-refresh-table [loading]="dataSource?.loading$ | async" (refreshed)="changePage()"
[emitRefreshOnPreviousRoutes]="refreshOnPreviousRoutes" [timestamp]="dataSource?.viewTimestamp" [emitRefreshOnPreviousRoutes]="refreshOnPreviousRoutes" [timestamp]="dataSource?.viewTimestamp"
[dataSize]="dataSource?.totalResult" [selection]="selection"> [dataSize]="dataSource?.totalResult" [selection]="selection">
<cnsl-form-field @appearfade *ngIf="userGrantListSearchKey != undefined" actions class="filtername"> <cnsl-form-field @appearfade *ngIf="userGrantListSearchKey != undefined" actions class="filtername">
<input class="filterinput" cnslInput (keyup)="applyFilter($event)" <input class="filterinput" cnslInput (keyup)="applyFilter($event)"
[placeholder]="('USER.GRANTS.FILTER.' + userGrantListSearchKey.toString()) | translate" #input> [placeholder]="('USER.GRANTS.FILTER.' + userGrantListSearchKey.toString()) | translate" #input>
</cnsl-form-field> </cnsl-form-field>
<button color="warn" matTooltip="{{'GRANTS.DELETE' | translate}}" class="icon-button" mat-icon-button actions <button color="warn" matTooltip="{{'GRANTS.DELETE' | translate}}" class="icon-button" mat-icon-button actions
(click)="deleteGrantSelection()" *ngIf="selection.hasValue() && disableDelete == false"> (click)="deleteGrantSelection()" *ngIf="selection.hasValue() && disableDelete == false">
<i class="las la-trash"></i> <i class="las la-trash"></i>
</button> </button>
<a *ngIf="disableWrite == false" matTooltip="{{'GRANTS.ADD' | translate}}" actions color="primary" color="primary" <a *ngIf="disableWrite == false" matTooltip="{{'GRANTS.ADD' | translate}}" actions color="primary" color="primary"
mat-raised-button [routerLink]="routerLink"> mat-raised-button [routerLink]="routerLink">
<mat-icon class="icon">add</mat-icon>{{ 'GRANTS.ADD_BTN' | translate }} <mat-icon class="icon">add</mat-icon>{{ 'GRANTS.ADD_BTN' | translate }}
</a> </a>
<div class="table-wrapper"> <div class="table-wrapper">
<table mat-table multiTemplateDataRows class="table" aria-label="Elements" [dataSource]="dataSource"> <table mat-table multiTemplateDataRows class="table" aria-label="Elements" [dataSource]="dataSource">
<ng-container matColumnDef="select"> <ng-container matColumnDef="select">
<th class="selection" mat-header-cell *matHeaderCellDef> <th class="selection" mat-header-cell *matHeaderCellDef>
<mat-checkbox [disabled]="disableWrite" color="primary" (change)="$event ? masterToggle() : null" <mat-checkbox [disabled]="disableWrite" color="primary" (change)="$event ? masterToggle() : null"
[checked]="selection.hasValue() && isAllSelected()" [checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()"> [indeterminate]="selection.hasValue() && !isAllSelected()">
</mat-checkbox> </mat-checkbox>
</th> </th>
<td class="selection" mat-cell *matCellDef="let row"> <td class="selection" mat-cell *matCellDef="let row">
<mat-checkbox <mat-checkbox
[disabled]="disableWrite || !((['user.grant.write$'] | hasRole | async) || ((context === UserGrantContext.OWNED_PROJECT ? ['user.grant.write:' + row?.projectId] : context === UserGrantContext.GRANTED_PROJECT ? ['user.grant.write:' + row?.id] : []) | hasRole | async))" [disabled]="disableWrite || !((['user.grant.write$'] | hasRole | async) || ((context === UserGrantContext.OWNED_PROJECT ? ['user.grant.write:' + row?.projectId] : context === UserGrantContext.GRANTED_PROJECT ? ['user.grant.write:' + row?.id] : []) | hasRole | async))"
color="primary" (click)="$event.stopPropagation()" color="primary" (click)="$event.stopPropagation()" (change)="$event ? selection.toggle(row) : null"
(change)="$event ? selection.toggle(row) : null" [checked]="selection.isSelected(row)"> [checked]="selection.isSelected(row)">
<app-avatar <app-avatar
*ngIf="context !== UserGrantContext.USER && row && row?.displayName && row.firstName && row.lastName" *ngIf="context !== UserGrantContext.USER && row && row?.displayName && row.firstName && row.lastName"
class="avatar" [name]="row.displayName" [avatarUrl]="row.avatarUrl || ''" [forColor]="row?.preferredLoginName" [size]="32"> class="avatar" [name]="row.displayName" [avatarUrl]="row.avatarUrl || ''"
</app-avatar> [forColor]="row?.preferredLoginName" [size]="32">
</mat-checkbox> </app-avatar>
</td> </mat-checkbox>
</td>
</ng-container>
<ng-container matColumnDef="user">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.USER' | translate }}
<template [ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{key: UserGrantListSearchKey.DISPLAY_NAME}"></template>
</th>
<td mat-cell *matCellDef="let grant">
<a class="user" [routerLink]="['/users', grant.userId]">{{grant?.displayName}}</a>
</td>
</ng-container>
<ng-container matColumnDef="org">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.GRANTEDORGDOMAIN' | translate }}
<template [ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{key: UserGrantListSearchKey.ORG_NAME}"></template>
</th>
<td mat-cell *matCellDef="let grant">
{{grant.orgName}} </td>
</ng-container>
<ng-container matColumnDef="projectId">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.PROJECTNAME' | translate }}
<template [ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{key: UserGrantListSearchKey.PROJECT_NAME}"></template>
</th>
<td mat-cell *matCellDef="let grant">
{{grant.projectName}} </td>
</ng-container>
<ng-container matColumnDef="dates">
<th mat-header-cell *matHeaderCellDef> DATES </th>
<td mat-cell *matCellDef="let grant">
<div class="date-block" *ngIf="grant.details?.creationDate">
<span class="date-sub">{{ 'PROJECT.GRANT.CREATIONDATE' | translate }}:</span>
<span>{{grant.details.creationDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}</span>
</div>
<div class="date-block" *ngIf="grant.details?.changeDate">
<span class="date-sub">{{ 'PROJECT.GRANT.CHANGEDATE' | translate }}</span>
<span>{{grant.details.changeDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}</span>
</div>
</td>
</ng-container>
<ng-container matColumnDef="creationDate">
<th mat-header-cell *matHeaderCellDef> {{'PROJECT.GRANT.CREATIONDATE' | translate}} </th>
<td mat-cell *matCellDef="let grant">
<span>{{grant.details.creationDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}</span>
</td>
</ng-container>
<ng-container matColumnDef="changeDate">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.CHANGEDATE' | translate }} </th>
<td mat-cell *matCellDef="let grant">
<span>{{grant.details.changeDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}</span>
</td>
</ng-container>
<ng-container matColumnDef="roleNamesList">
<th mat-header-cell *matHeaderCellDef class="role-data">
{{ 'PROJECT.GRANT.ROLENAMESLIST' | translate }}
<template [ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{key: UserGrantListSearchKey.ROLE_KEY}"></template>
</th>
<td mat-cell *matCellDef="let grant; let i = index" class="role-data">
<ng-container
*ngIf="(context === UserGrantContext.USER || context === UserGrantContext.NONE) && (grant.id && grantToEdit !== grant.id) || (grantToEdit !== grant.id)">
<div class="flex-row">
<div class="role">
<span *ngFor="let role of grant.roleKeysList">{{ role }}</span>
</div>
<span class="fill-space"></span>
<button mat-stroked-button *ngIf="grant.id ? grantToEdit !== grant.id : grantToEdit !== grant.id"
[disabled]="disableWrite || !((['user.grant.write$'] | hasRole | async) || ((context === UserGrantContext.OWNED_PROJECT ? ['user.grant.write:' + grant?.projectId] : context === UserGrantContext.GRANTED_PROJECT ? ['user.grant.write:' + grant?.id] : []) | hasRole | async))"
(click)="loadGrantOptions(grant)" matTooltip="{{'ACTIONS.CHANGE' | translate}}">
<i class="las la-edit"></i>
{{'ACTIONS.EDIT' | translate}}
</button>
</div>
</ng-container>
<div class="row-form">
<ng-container
*ngIf="(context === UserGrantContext.OWNED_PROJECT || context === UserGrantContext.USER || context === UserGrantContext.NONE) && grantToEdit == grant.id && loadedProjectId && loadedProjectId === grant.projectId">
<cnsl-form-field class="form-field" appearance="outline">
<mat-select [(ngModel)]="grant.roleKeysList" multiple
[disabled]="disableWrite || !((['user.grant.write$'] | hasRole | async) || ((context === UserGrantContext.OWNED_PROJECT ? ['user.grant.write:' + grant?.projectId] : context === UserGrantContext.GRANTED_PROJECT ? ['user.grant.write:' + grant?.id] : []) | hasRole | async))"
(selectionChange)="updateRoles(grant, $event)">
<mat-option *ngFor="let role of projectRoleOptions" [value]="role.key">
{{role.key}}
</mat-option>
</mat-select>
</cnsl-form-field>
<button matTooltip="{{'ACTIONS.CLOSE' | translate}}"
*ngIf="context === UserGrantContext.USER || context === UserGrantContext.NONE" mat-icon-button
(click)="grantToEdit=''">
<mat-icon>close</mat-icon>
</button>
</ng-container> </ng-container>
<ng-container matColumnDef="user"> <ng-container
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.USER' | translate }} *ngIf="(context === UserGrantContext.GRANTED_PROJECT || context === UserGrantContext.USER || context === UserGrantContext.NONE) && loadedId && loadedId === grant.projectId && grantToEdit == grant.id">
<template [ngTemplateOutlet]="templateRef" <cnsl-form-field class="form-field" appearance="outline">
[ngTemplateOutletContext]="{key: UserGrantListSearchKey.DISPLAY_NAME}"></template> <mat-select [(ngModel)]="grant.roleKeysList" multiple
</th> [disabled]="disableWrite || !((['user.grant.write$'] | hasRole | async) || ((context === UserGrantContext.OWNED_PROJECT ? ['user.grant.write:' + grant?.projectId] : context === UserGrantContext.GRANTED_PROJECT ? ['user.grant.write:' + grant?.id] : []) | hasRole | async))"
<td mat-cell *matCellDef="let grant"> (selectionChange)="updateRoles(grant, $event)">
{{grant?.displayName}}</td> <mat-option *ngFor="let role of grantRoleOptions" [value]="role">
{{role}}
</mat-option>
</mat-select>
</cnsl-form-field>
<button matTooltip="{{'ACTIONS.CLOSE' | translate}}"
*ngIf="context === UserGrantContext.USER || context === UserGrantContext.NONE" mat-icon-button
(click)="grantToEdit=''">
<mat-icon>close</mat-icon>
</button>
</ng-container> </ng-container>
</div>
</td>
</ng-container>
<ng-container matColumnDef="org"> <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.GRANTEDORGDOMAIN' | translate }} <tr mat-row *matRowDef="let row; columns: displayedColumns;">
<template [ngTemplateOutlet]="templateRef" </tr>
[ngTemplateOutletContext]="{key: UserGrantListSearchKey.ORG_NAME}"></template> </table>
</th>
<td mat-cell *matCellDef="let grant">
{{grant.orgName}} </td>
</ng-container>
<ng-container matColumnDef="projectId"> <div *ngIf="(dataSource.loading$ | async) == false && !dataSource?.totalResult" class="no-content-row">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.PROJECTNAME' | translate }} <i class="las la-exclamation"></i>
<template [ngTemplateOutlet]="templateRef" <span>{{'GRANTS.EMPTY' | translate}}</span>
[ngTemplateOutletContext]="{key: UserGrantListSearchKey.PROJECT_NAME}"></template>
</th>
<td mat-cell *matCellDef="let grant">
{{grant.projectName}} </td>
</ng-container>
<ng-container matColumnDef="dates">
<th mat-header-cell *matHeaderCellDef> DATES </th>
<td mat-cell *matCellDef="let grant">
<div class="date-block" *ngIf="grant.details?.creationDate">
<span class="date-sub">{{ 'PROJECT.GRANT.CREATIONDATE' | translate }}:</span>
<span>{{grant.details.creationDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}</span>
</div>
<div class="date-block" *ngIf="grant.details?.changeDate">
<span class="date-sub">{{ 'PROJECT.GRANT.CHANGEDATE' | translate }}</span>
<span>{{grant.details.changeDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}</span>
</div>
</td>
</ng-container>
<ng-container matColumnDef="creationDate">
<th mat-header-cell *matHeaderCellDef> {{'PROJECT.GRANT.CREATIONDATE' | translate}} </th>
<td mat-cell *matCellDef="let grant">
<span>{{grant.details.creationDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}</span>
</td>
</ng-container>
<ng-container matColumnDef="changeDate">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.CHANGEDATE' | translate }} </th>
<td mat-cell *matCellDef="let grant">
<span>{{grant.details.changeDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}</span>
</td>
</ng-container>
<ng-container matColumnDef="roleNamesList">
<th mat-header-cell *matHeaderCellDef class="role-data">
{{ 'PROJECT.GRANT.ROLENAMESLIST' | translate }}
<template [ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{key: UserGrantListSearchKey.ROLE_KEY}"></template>
</th>
<td mat-cell *matCellDef="let grant; let i = index" class="role-data">
<ng-container
*ngIf="(context === UserGrantContext.USER || context === UserGrantContext.NONE) && (grant.id && grantToEdit !== grant.id) || (grantToEdit !== grant.id)">
<div class="flex-row">
<div class="role">
<span *ngFor="let role of grant.roleKeysList">{{ role }}</span>
</div>
<span class="fill-space"></span>
<button mat-stroked-button
*ngIf="grant.id ? grantToEdit !== grant.id : grantToEdit !== grant.id"
[disabled]="disableWrite || !((['user.grant.write$'] | hasRole | async) || ((context === UserGrantContext.OWNED_PROJECT ? ['user.grant.write:' + grant?.projectId] : context === UserGrantContext.GRANTED_PROJECT ? ['user.grant.write:' + grant?.id] : []) | hasRole | async))"
(click)="loadGrantOptions(grant)" matTooltip="{{'ACTIONS.CHANGE' | translate}}">
<i class="las la-edit"></i>
{{'ACTIONS.EDIT' | translate}}
</button>
</div>
</ng-container>
<div class="row-form">
<ng-container
*ngIf="(context === UserGrantContext.OWNED_PROJECT || context === UserGrantContext.USER || context === UserGrantContext.NONE) && grantToEdit == grant.id && loadedProjectId && loadedProjectId === grant.projectId">
<cnsl-form-field class="form-field" appearance="outline">
<mat-select [(ngModel)]="grant.roleKeysList" multiple
[disabled]="disableWrite || !((['user.grant.write$'] | hasRole | async) || ((context === UserGrantContext.OWNED_PROJECT ? ['user.grant.write:' + grant?.projectId] : context === UserGrantContext.GRANTED_PROJECT ? ['user.grant.write:' + grant?.id] : []) | hasRole | async))"
(selectionChange)="updateRoles(grant, $event)">
<mat-option *ngFor="let role of projectRoleOptions" [value]="role.key">
{{role.key}}
</mat-option>
</mat-select>
</cnsl-form-field>
<button matTooltip="{{'ACTIONS.CLOSE' | translate}}"
*ngIf="context === UserGrantContext.USER || context === UserGrantContext.NONE"
mat-icon-button (click)="grantToEdit=''">
<mat-icon>close</mat-icon>
</button>
</ng-container>
<ng-container
*ngIf="(context === UserGrantContext.GRANTED_PROJECT || context === UserGrantContext.USER || context === UserGrantContext.NONE) && loadedId && loadedId === grant.projectId && grantToEdit == grant.id">
<cnsl-form-field class="form-field" appearance="outline">
<mat-select [(ngModel)]="grant.roleKeysList" multiple
[disabled]="disableWrite || !((['user.grant.write$'] | hasRole | async) || ((context === UserGrantContext.OWNED_PROJECT ? ['user.grant.write:' + grant?.projectId] : context === UserGrantContext.GRANTED_PROJECT ? ['user.grant.write:' + grant?.id] : []) | hasRole | async))"
(selectionChange)="updateRoles(grant, $event)">
<mat-option *ngFor="let role of grantRoleOptions" [value]="role">
{{role}}
</mat-option>
</mat-select>
</cnsl-form-field>
<button matTooltip="{{'ACTIONS.CLOSE' | translate}}"
*ngIf="context === UserGrantContext.USER || context === UserGrantContext.NONE"
mat-icon-button (click)="grantToEdit=''">
<mat-icon>close</mat-icon>
</button>
</ng-container>
</div>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;">
</tr>
</table>
<div *ngIf="(dataSource.loading$ | async) == false && !dataSource?.totalResult" class="no-content-row">
<i class="las la-exclamation"></i>
<span>{{'GRANTS.EMPTY' | translate}}</span>
</div>
<cnsl-paginator class="paginator" #paginator [timestamp]="dataSource?.viewTimestamp"
[length]="dataSource.totalResult" [pageSize]="INITIAL_PAGE_SIZE" [length]="dataSource.totalResult"
[pageSizeOptions]="[2, 3, 25, 50, 100, 250]" (page)="changePage($event)">
</cnsl-paginator>
</div> </div>
<cnsl-paginator class="paginator" #paginator [timestamp]="dataSource?.viewTimestamp"
[length]="dataSource.totalResult" [pageSize]="INITIAL_PAGE_SIZE" [length]="dataSource.totalResult"
[pageSizeOptions]="[2, 3, 25, 50, 100, 250]" (page)="changePage($event)">
</cnsl-paginator>
</div>
</app-refresh-table> </app-refresh-table>
<ng-template #templateRef let-key="key"> <ng-template #templateRef let-key="key">
<button class="search-button" mat-icon-button (click)="setFilter(key)"> <button class="search-button" mat-icon-button (click)="setFilter(key)">
<mat-icon class="icon" *ngIf="this.userGrantListSearchKey != key">search</mat-icon> <mat-icon class="icon" *ngIf="this.userGrantListSearchKey != key">search</mat-icon>
<mat-icon class="icon" *ngIf="this.userGrantListSearchKey == key">search_off</mat-icon> <mat-icon class="icon" *ngIf="this.userGrantListSearchKey == key">search_off</mat-icon>
</button> </button>
</ng-template> </ng-template>

View File

@ -21,6 +21,11 @@
} }
} }
.user {
text-decoration: none;
color: inherit;
}
th { th {
.search-button { .search-button {
visibility: hidden; visibility: hidden;

View File

@ -82,7 +82,7 @@ export class GrantedProjectGridComponent implements OnChanges {
private async getPrefixedItem(key: string): Promise<string | null> { private async getPrefixedItem(key: string): Promise<string | null> {
const org = this.storage.getItem<Org.AsObject>(StorageKey.organization, StorageLocation.session) as Org.AsObject; const org = this.storage.getItem<Org.AsObject>(StorageKey.organization, StorageLocation.session) as Org.AsObject;
return localStorage.getItem(`${org.id}:${key}`); return localStorage.getItem(`${org?.id}:${key}`);
} }
private async setPrefixedItem(key: string, value: any): Promise<void> { private async setPrefixedItem(key: string, value: any): Promise<void> {

View File

@ -1,148 +1,153 @@
<app-meta-layout> <app-meta-layout>
<div class="max-width-container"> <div class="max-width-container">
<div class="head" *ngIf="project?.id"> <div class="head" *ngIf="project?.id">
<a [routerLink]="[ '/projects' ]" mat-icon-button> <a [routerLink]="[ '/projects' ]" mat-icon-button>
<mat-icon class="icon">arrow_back</mat-icon> <mat-icon class="icon">arrow_back</mat-icon>
</a> </a>
<h1>{{ 'PROJECT.PAGES.TITLE' | translate }} {{project?.name}}</h1> <h1>{{ 'PROJECT.PAGES.TITLE' | translate }} {{project?.name}}</h1>
<span class="fill-space"></span> <span class="fill-space"></span>
<ng-template appHasRole [appHasRole]="['project.write:'+projectId, 'project.write']"> <ng-template appHasRole [appHasRole]="['project.write:'+projectId, 'project.write']">
<button class="actions-trigger" mat-raised-button color="primary" [matMenuTriggerFor]="actions"> <button class="actions-trigger" mat-raised-button color="primary" [matMenuTriggerFor]="actions">
<span>{{'ACTIONS.ACTIONS' | translate}}</span> <span>{{'ACTIONS.ACTIONS' | translate}}</span>
<mat-icon class="icon">keyboard_arrow_down</mat-icon> <mat-icon class="icon">keyboard_arrow_down</mat-icon>
</button> </button>
<mat-menu #actions="matMenu" xPosition="before"> <mat-menu #actions="matMenu" xPosition="before">
<button mat-menu-item (click)="openNameDialog()" <button mat-menu-item (click)="openNameDialog()" aria-label="Edit project name" *ngIf="isZitadel === false">
aria-label="Edit project name" *ngIf="isZitadel === false"> {{'ACTIONS.RENAME' | translate}}
{{'ACTIONS.RENAME' | translate}} </button>
</button>
<button mat-menu-item <button mat-menu-item
[disabled]="isZitadel || (['project.write$', 'project.write:'+ project.id]| hasRole | async) == false" [disabled]="isZitadel || (['project.write$', 'project.write:'+ project.id]| hasRole | async) == false"
*ngIf="project?.state === ProjectState.PROJECT_STATE_ACTIVE" *ngIf="project?.state === ProjectState.PROJECT_STATE_ACTIVE"
(click)="changeState(ProjectState.PROJECT_STATE_INACTIVE)"> (click)="changeState(ProjectState.PROJECT_STATE_INACTIVE)">
{{'PROJECT.TABLE.DEACTIVATE' | translate}} {{'PROJECT.TABLE.DEACTIVATE' | translate}}
</button> </button>
<button mat-menu-item <button mat-menu-item
[disabled]="isZitadel || (['project.write$', 'project.write:'+ project.id]| hasRole | async) == false" [disabled]="isZitadel || (['project.write$', 'project.write:'+ project.id]| hasRole | async) == false"
*ngIf="project?.state === ProjectState.PROJECT_STATE_INACTIVE" *ngIf="project?.state === ProjectState.PROJECT_STATE_INACTIVE"
(click)="changeState(ProjectState.PROJECT_STATE_ACTIVE)"> (click)="changeState(ProjectState.PROJECT_STATE_ACTIVE)">
{{'PROJECT.TABLE.ACTIVATE' | translate}} {{'PROJECT.TABLE.ACTIVATE' | translate}}
</button> </button>
<ng-template appHasRole [appHasRole]="['project.delete$', 'project.delete:'+projectId]"> <ng-template appHasRole [appHasRole]="['project.delete$', 'project.delete:'+projectId]">
<button mat-menu-item matTooltip="{{'ACTIONS.DELETE' | translate}}" <button mat-menu-item matTooltip="{{'ACTIONS.DELETE' | translate}}" (click)="deleteProject()"
(click)="deleteProject()" aria-label="Edit project name" *ngIf="isZitadel === false"> aria-label="Edit project name" *ngIf="isZitadel === false">
<span [style.color]="'var(--warn)'">{{'PROJECT.PAGES.DELETE' | translate}}</span> <span [style.color]="'var(--warn)'">{{'PROJECT.PAGES.DELETE' | translate}}</span>
</button> </button>
</ng-template> </ng-template>
</mat-menu> </mat-menu>
</ng-template> </ng-template>
<div class="full-width"> <div class="full-width">
<p class="desc">{{ 'PROJECT.PAGES.DESCRIPTION' | translate }}</p> <p class="desc">{{ 'PROJECT.PAGES.DESCRIPTION' | translate }}</p>
<p *ngIf="isZitadel" class="zitadel-warning">{{'PROJECT.PAGES.ZITADELPROJECT' | translate}}</p> <p *ngIf="isZitadel" class="zitadel-warning">{{'PROJECT.PAGES.ZITADELPROJECT' | translate}}</p>
</div> </div>
</div>
<ng-container *ngIf="project">
<div class="privatelabel-info">
<h2 class="setting-title">{{'PROJECT.PAGES.PRIVATELABEL.TITLE' | translate}}</h2>
<p class="setting-desc">
<span>{{'PROJECT.PAGES.PRIVATELABEL.'+project.privateLabelingSetting+'.TITLE' | translate}}</span>
<button (click)="openPrivateLabelingDialog()" mat-icon-button><i class="las la-edit"></i></button>
</p>
</div>
<ng-template appHasRole [appHasRole]="['project.app.read:' + project.id, 'project.app.read']">
<app-application-grid *ngIf="grid" [disabled]="isZitadel" (changeView)="grid = false"
[projectId]="projectId"></app-application-grid>
<app-card *ngIf="!grid" title="{{ 'PROJECT.APP.TITLE' | translate }}">
<div class="card-actions" card-actions>
<button mat-icon-button (click)="grid = true">
<i matTooltip="show grid view" class="las la-th-large"></i>
</button>
</div>
<app-applications [disabled]="isZitadel" [projectId]="projectId"></app-applications>
</app-card>
</ng-template>
<ng-container *ngIf="isZitadel == false">
<ng-template appHasRole [appHasRole]="['project.grant.read:' + project.id, 'project.grant.read']">
<app-card title="{{ 'PROJECT.GRANT.TITLE' | translate }}"
description="{{ 'PROJECT.GRANT.DESCRIPTION' | translate }}">
<app-project-grants
[refreshOnPreviousRoutes]="['/projects/'+projectId+'/grants/create','/projects/'+projectId+'/roles/create']"
[disabled]="((['project.grant.write$', 'project.grant.write:'+ project.id]| hasRole | async))== false"
[projectId]="projectId">
</app-project-grants>
</app-card>
</ng-template>
<ng-template appHasRole [appHasRole]="['project.role.read:' + project.id, 'project.role.read']">
<app-card id="roles" title="{{ 'PROJECT.ROLE.TITLE' | translate }}"
description="{{ 'PROJECT.ROLE.DESCRIPTION' | translate }}">
<p>{{'PROJECT.ROLE.OPTIONS' | translate}}</p>
<mat-checkbox [(ngModel)]="project.projectRoleAssertion" (change)="saveProject()"
color="primary">
{{'PROJECT.ROLE.ASSERTION' | translate}}</mat-checkbox>
<p class="desc">{{'PROJECT.ROLE.ASSERTION_DESCRIPTION' | translate}}</p>
<mat-checkbox [(ngModel)]="project.projectRoleCheck" (change)="saveProject()" color="primary">
{{'PROJECT.ROLE.CHECK' | translate}}</mat-checkbox>
<p class="desc">{{'PROJECT.ROLE.CHECK_DESCRIPTION' | translate}}</p>
<mat-checkbox [(ngModel)]="project.hasProjectCheck" (change)="saveProject()" color="primary">
{{'PROJECT.HAS_PROJECT' | translate}}</mat-checkbox>
<p class="desc">{{'PROJECT.HAS_PROJECT_DESCRIPTION' | translate}}</p>
<div class="divider"></div>
<app-project-roles
[disabled]="(['project.role.write$', 'project.role.write:'+ project.id]| hasRole | async) == false"
[actionsVisible]="true" [projectId]="projectId">
</app-project-roles>
</app-card>
</ng-template>
<ng-template appHasRole [appHasRole]="['user.grant.read']">
<app-card *ngIf="project?.id" title="{{ 'GRANTS.PROJECT.TITLE' | translate }}"
description="{{'GRANTS.PROJECT.DESCRIPTION' | translate }}">
<app-user-grants [context]="UserGrantContext.OWNED_PROJECT" [projectId]="projectId"
[refreshOnPreviousRoutes]="['/grant-create/project/'+projectId]"
[disableWrite]="((['user.grant.write$', 'user.grant.write:'+projectId] | hasRole) | async) == false"
[disableDelete]="((['user.grant.delete$','user.grant.delete:'+projectId] | hasRole) | async) == false">
</app-user-grants>
</app-card>
</ng-template>
</ng-container>
</ng-container>
</div> </div>
<div class="side" metainfo>
<div class="meta-details">
<div class="meta-row">
<span class="first">{{'RESOURCEID' | translate}}:</span>
<span *ngIf="projectId" class="second">{{ projectId }}</span>
</div>
<div class="meta-row">
<span class="first">{{'PROJECT.STATE.TITLE' | translate}}:</span>
<span *ngIf="project && project.state !== undefined" class="state"
[ngClass]="{'active': project.state === ProjectState.PROJECT_STATE_ACTIVE, 'inactive': project.state === ProjectState.PROJECT_STATE_INACTIVE}">{{'PROJECT.STATE.'+project.state
| translate}}</span>
</div>
</div>
<mat-tab-group mat-stretch-tabs class="tab-group" disablePagination="true"> <ng-container *ngIf="project">
<mat-tab label="Details"> <div class="privatelabel-info">
<app-contributors *ngIf="project" [loading]="loading$ | async" [totalResult]="totalMemberResult" <h2 class="setting-title">{{'PROJECT.PAGES.PRIVATELABEL.TITLE' | translate}}</h2>
[membersSubject]="membersSubject" title="{{ 'PROJECT.MEMBER.TITLE' | translate }}" <p class="setting-desc">
description="{{ 'PROJECT.MEMBER.TITLEDESC' | translate }}" (addClicked)="openAddMember()" <span>{{'PROJECT.PAGES.PRIVATELABEL.'+project.privateLabelingSetting+'.TITLE' | translate}}</span>
(showDetailClicked)="showDetail()" (refreshClicked)="loadMembers()" <button [disabled]="((['project.write$', 'project.write:'+ project.id]| hasRole | async))== false"
[disabled]="(['project.member.write$', 'project.member.write:'+ project.id]| hasRole | async) == false"> (click)="openPrivateLabelingDialog()" mat-icon-button><i class="las la-edit"></i></button>
</app-contributors> </p>
</mat-tab> </div>
<mat-tab label="{{ 'CHANGES.PROJECT.TITLE' | translate }}" class="meta-flex-col">
<app-changes *ngIf="project" [changeType]="ChangeType.PROJECT" [id]="project.id"></app-changes> <ng-template appHasRole [appHasRole]="['project.app.read:' + project.id, 'project.app.read']">
</mat-tab> <app-application-grid *ngIf="grid" [disabled]="isZitadel" (changeView)="grid = false" [projectId]="projectId">
</mat-tab-group> </app-application-grid>
<app-card *ngIf="!grid" title="{{ 'PROJECT.APP.TITLE' | translate }}">
<div class="card-actions" card-actions>
<button mat-icon-button (click)="grid = true">
<i matTooltip="show grid view" class="las la-th-large"></i>
</button>
</div>
<app-applications [disabled]="isZitadel" [projectId]="projectId"></app-applications>
</app-card>
</ng-template>
<ng-container *ngIf="isZitadel == false">
<ng-template appHasRole [appHasRole]="['project.grant.read:' + project.id, 'project.grant.read']">
<app-card title="{{ 'PROJECT.GRANT.TITLE' | translate }}"
description="{{ 'PROJECT.GRANT.DESCRIPTION' | translate }}">
<app-project-grants
[refreshOnPreviousRoutes]="['/projects/'+projectId+'/grants/create','/projects/'+projectId+'/roles/create']"
[disabled]="((['project.grant.write$', 'project.grant.write:'+ project.id]| hasRole | async))== false"
[projectId]="projectId">
</app-project-grants>
</app-card>
</ng-template>
<ng-template appHasRole [appHasRole]="['project.role.read:' + project.id, 'project.role.read']">
<app-card id="roles" title="{{ 'PROJECT.ROLE.TITLE' | translate }}"
description="{{ 'PROJECT.ROLE.DESCRIPTION' | translate }}">
<p>{{'PROJECT.ROLE.OPTIONS' | translate}}</p>
<mat-checkbox [(ngModel)]="project.projectRoleAssertion"
[disabled]="((['project.write$', 'project.write:'+ project.id]| hasRole | async))== false"
(change)="saveProject()" color="primary">
{{'PROJECT.ROLE.ASSERTION' | translate}}</mat-checkbox>
<p class="desc">{{'PROJECT.ROLE.ASSERTION_DESCRIPTION' | translate}}</p>
<mat-checkbox [(ngModel)]="project.projectRoleCheck"
[disabled]="((['project.write$', 'project.write:'+ project.id]| hasRole | async))== false"
(change)="saveProject()" color="primary">
{{'PROJECT.ROLE.CHECK' | translate}}</mat-checkbox>
<p class="desc">{{'PROJECT.ROLE.CHECK_DESCRIPTION' | translate}}</p>
<mat-checkbox [(ngModel)]="project.hasProjectCheck"
[disabled]="((['project.write$', 'project.write:'+ project.id]| hasRole | async))== false"
(change)="saveProject()" color="primary">
{{'PROJECT.HAS_PROJECT' | translate}}</mat-checkbox>
<p class="desc">{{'PROJECT.HAS_PROJECT_DESCRIPTION' | translate}}</p>
<div class="divider"></div>
<app-project-roles
[disabled]="(['project.role.write$', 'project.role.write:'+ project.id]| hasRole | async) == false"
[actionsVisible]="true" [projectId]="projectId">
</app-project-roles>
</app-card>
</ng-template>
<ng-template appHasRole [appHasRole]="['user.grant.read']">
<app-card *ngIf="project?.id" title="{{ 'GRANTS.PROJECT.TITLE' | translate }}"
description="{{'GRANTS.PROJECT.DESCRIPTION' | translate }}">
<app-user-grants [context]="UserGrantContext.OWNED_PROJECT" [projectId]="projectId"
[refreshOnPreviousRoutes]="['/grant-create/project/'+projectId]"
[disableWrite]="((['user.grant.write$', 'user.grant.write:'+projectId] | hasRole) | async) == false"
[disableDelete]="((['user.grant.delete$','user.grant.delete:'+projectId] | hasRole) | async) == false">
</app-user-grants>
</app-card>
</ng-template>
</ng-container>
</ng-container>
</div>
<div class="side" metainfo>
<div class="meta-details">
<div class="meta-row">
<span class="first">{{'RESOURCEID' | translate}}:</span>
<span *ngIf="projectId" class="second">{{ projectId }}</span>
</div>
<div class="meta-row">
<span class="first">{{'PROJECT.STATE.TITLE' | translate}}:</span>
<span *ngIf="project && project.state !== undefined" class="state"
[ngClass]="{'active': project.state === ProjectState.PROJECT_STATE_ACTIVE, 'inactive': project.state === ProjectState.PROJECT_STATE_INACTIVE}">{{'PROJECT.STATE.'+project.state
| translate}}</span>
</div>
</div> </div>
</app-meta-layout>
<mat-tab-group mat-stretch-tabs class="tab-group" disablePagination="true">
<mat-tab label="Details">
<app-contributors *ngIf="project" [loading]="loading$ | async" [totalResult]="totalMemberResult"
[membersSubject]="membersSubject" title="{{ 'PROJECT.MEMBER.TITLE' | translate }}"
description="{{ 'PROJECT.MEMBER.TITLEDESC' | translate }}" (addClicked)="openAddMember()"
(showDetailClicked)="showDetail()" (refreshClicked)="loadMembers()"
[disabled]="(['project.member.write$', 'project.member.write:'+ project.id]| hasRole | async) == false">
</app-contributors>
</mat-tab>
<mat-tab label="{{ 'CHANGES.PROJECT.TITLE' | translate }}" class="meta-flex-col">
<app-changes *ngIf="project" [changeType]="ChangeType.PROJECT" [id]="project.id"></app-changes>
</mat-tab>
</mat-tab-group>
</div>
</app-meta-layout>

View File

@ -106,7 +106,7 @@ export class OwnedProjectGridComponent implements OnChanges {
private async getPrefixedItem(key: string): Promise<string | null> { private async getPrefixedItem(key: string): Promise<string | null> {
const org = this.storage.getItem<Org.AsObject>(StorageKey.organization, StorageLocation.session) as Org.AsObject; const org = this.storage.getItem<Org.AsObject>(StorageKey.organization, StorageLocation.session) as Org.AsObject;
return localStorage.getItem(`${org.id}:${key}`); return localStorage.getItem(`${org?.id}:${key}`);
} }
private async setPrefixedItem(key: string, value: any): Promise<void> { private async setPrefixedItem(key: string, value: any): Promise<void> {

View File

@ -1,57 +1,64 @@
<app-detail-layout [backRouterLink]="[ '/projects', projectid]" title="{{ 'PROJECT.GRANT.DETAIL.TITLE' | translate }}" <app-detail-layout [backRouterLink]="[ '/projects', projectid]" title="{{ 'PROJECT.GRANT.DETAIL.TITLE' | translate }}"
description="{{ 'PROJECT.GRANT.DETAIL.DESC' | translate }}"> description="{{ 'PROJECT.GRANT.DETAIL.DESC' | translate }}">
<div class="master-row"> <div class="master-row">
<div class="left-col"> <div class="left-col">
<div class="row"> <div class="row">
<span class="first">{{'PROJECT.GRANT.DETAIL.PROJECTNAME' | translate}}</span> <span class="first">{{'PROJECT.GRANT.DETAIL.PROJECTNAME' | translate}}</span>
<span class="fill-space"></span>
<span>{{grant?.projectName}}</span>
</div>
<div class="row">
<span class="first">{{'PROJECT.GRANT.DETAIL.RESOURCEOWNER' | translate}}</span>
<span class="fill-space"></span>
<span>{{grant?.details?.resourceOwner}}</span>
</div>
</div>
<span class="fill-space"></span> <span class="fill-space"></span>
<span>{{grant?.projectName}}</span>
<div> </div>
<button mat-stroked-button color="warn" *ngIf="grant?.state === ProjectGrantState.PROJECTGRANTSTATE_ACTIVE" <div class="row">
(click)="changeState(ProjectGrantState.PROJECTGRANTSTATE_INACTIVE)">{{'USER.PAGES.DEACTIVATE' | translate}}</button> <span class="first">{{'PROJECT.GRANT.DETAIL.RESOURCEOWNER' | translate}}</span>
<button mat-stroked-button color="warn" <span class="fill-space"></span>
*ngIf="grant?.state === ProjectGrantState.PROJECTGRANTSTATE_INACTIVE" <span>{{grant?.projectOwnerName}}</span>
(click)="changeState(ProjectGrantState.PROJECTGRANTSTATE_ACTIVE)">{{'USER.PAGES.REACTIVATE' | translate}}</button> </div>
</div> <div class="row">
<span class="first">{{'PROJECT.GRANT.DETAIL.GRANTEDORG' | translate}}</span>
<span class="fill-space"></span>
<span>{{grant?.grantedOrgName}}</span>
</div>
</div> </div>
<cnsl-form-field class="formfield" appearance="outline" *ngIf="grant && grant.grantedRoleKeysList"> <span class="fill-space"></span>
<cnsl-label>{{ 'PROJECT.GRANT.ROLENAMESLIST' | translate }}</cnsl-label>
<mat-select [(ngModel)]="grant.grantedRoleKeysList" multiple (selectionChange)="updateRoles($event)">
<mat-option *ngFor="let role of projectRoleOptions" [value]="role.key">
{{role.key}}
</mat-option>
</mat-select>
</cnsl-form-field>
<div class="divider"></div> <div>
<button mat-stroked-button color="warn" *ngIf="grant?.state === ProjectGrantState.PROJECTGRANTSTATE_ACTIVE"
(click)="changeState(ProjectGrantState.PROJECTGRANTSTATE_INACTIVE)">{{'USER.PAGES.DEACTIVATE' |
translate}}</button>
<button mat-stroked-button color="warn" *ngIf="grant?.state === ProjectGrantState.PROJECTGRANTSTATE_INACTIVE"
(click)="changeState(ProjectGrantState.PROJECTGRANTSTATE_ACTIVE)">{{'USER.PAGES.REACTIVATE' |
translate}}</button>
</div>
</div>
<h1 class="h1">{{ 'PROJECT.GRANT.DETAIL.MEMBERTITLE' | translate }}</h1> <cnsl-form-field class="formfield" appearance="outline" *ngIf="grant && grant.grantedRoleKeysList">
<p class="desc">{{ 'PROJECT.GRANT.DETAIL.MEMBERDESC' | translate }}</p> <cnsl-label>{{ 'PROJECT.GRANT.ROLENAMESLIST' | translate }}</cnsl-label>
<mat-select [(ngModel)]="grant.grantedRoleKeysList" multiple (selectionChange)="updateRoles($event)">
<mat-option *ngFor="let role of projectRoleOptions" [value]="role.key">
{{role.key}}
</mat-option>
</mat-select>
</cnsl-form-field>
<app-members-table *ngIf="grant" style="width: 100%;" [dataSource]="dataSource" [canWrite]="['project.grant.member.write','project.grant.member.write:' + grant.grantId] | hasRole | async" <div class="divider"></div>
[memberRoleOptions]="memberRoleOptions" (updateRoles)="updateMemberRoles($event.member, $event.change)"
[factoryLoadFunc]="changePageFactory" (changedSelection)="selection = $event" [refreshTrigger]="changePage"> <h1 class="h1">{{ 'PROJECT.GRANT.DETAIL.MEMBERTITLE' | translate }}</h1>
<button selectactions (click)="removeProjectMemberSelection()" <p class="desc">{{ 'PROJECT.GRANT.DETAIL.MEMBERDESC' | translate }}</p>
[disabled]="(['project.grant.member.delete','project.grant.member.delete:' + grant.grantId] | hasRole | async) == false"
matTooltip="{{'ORG_DETAIL.TABLE.DELETE' | translate}}" class="del-button" color="warn" mat-raised-button> <app-members-table *ngIf="grant" style="width: 100%;" [dataSource]="dataSource"
<i class="las la-trash"></i> [canWrite]="['project.grant.member.write','project.grant.member.write:' + grant.grantId] | hasRole | async"
{{'ACTIONS.SELECTIONDELETE' | translate}} [memberRoleOptions]="memberRoleOptions" (updateRoles)="updateMemberRoles($event.member, $event.change)"
</button> [factoryLoadFunc]="changePageFactory" (changedSelection)="selection = $event" [refreshTrigger]="changePage">
<a writeactions color="primary" <button selectactions (click)="removeProjectMemberSelection()"
[disabled]="(['project.grant.member.write','project.grant.member.write:' + grant.grantId] | hasRole | async) == false" [disabled]="(['project.grant.member.delete','project.grant.member.delete:' + grant.grantId] | hasRole | async) == false"
(click)="openAddMember()" color="primary" mat-raised-button> matTooltip="{{'ORG_DETAIL.TABLE.DELETE' | translate}}" class="del-button" color="warn" mat-raised-button>
<mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }} <i class="las la-trash"></i>
</a> {{'ACTIONS.SELECTIONDELETE' | translate}}
</app-members-table> </button>
<a writeactions color="primary"
[disabled]="(['project.grant.member.write','project.grant.member.write:' + grant.grantId] | hasRole | async) == false"
(click)="openAddMember()" color="primary" mat-raised-button>
<mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }}
</a>
</app-members-table>
</app-detail-layout> </app-detail-layout>

View File

@ -1,135 +1,136 @@
<div class="max-width-container" *ngIf="error">{{error}}</div>
<app-meta-layout *ngIf="user && (['user.write$','user.write:' + user.id] | hasRole) as canWrite$"> <app-meta-layout *ngIf="user && (['user.write$','user.write:' + user.id] | hasRole) as canWrite$">
<div class="max-width-container"> <div class="max-width-container">
<div class="head"> <div class="head">
<a (click)="navigateBack()" mat-icon-button> <a (click)="navigateBack()" mat-icon-button>
<mat-icon class="icon">arrow_back</mat-icon> <mat-icon class="icon">arrow_back</mat-icon>
</a> </a>
<div class="head-row"> <div class="head-row">
<h1>{{user.human ? user.human?.profile?.displayName : user.machine?.name}}</h1> <h1>{{user.human ? user.human?.profile?.displayName : user.machine?.name}}</h1>
<p *ngIf="user?.preferredLoginName">{{user?.preferredLoginName}}</p> <p *ngIf="user?.preferredLoginName">{{user?.preferredLoginName}}</p>
</div>
<span class="fill-space"></span>
<ng-template appHasRole [appHasRole]="['user.write$', 'user.write:'+user?.id]">
<button class="actions-trigger" mat-raised-button color="primary" [matMenuTriggerFor]="actions">
<span>{{'ACTIONS.ACTIONS' | translate}}</span>
<mat-icon class="icon">keyboard_arrow_down</mat-icon>
</button>
<mat-menu #actions="matMenu" xPosition="before">
<button mat-menu-item color="warn"
*ngIf="user?.state === UserState.USER_STATE_LOCKED"
(click)="unlockUser()">{{'USER.PAGES.UNLOCK' |
translate}}</button>
<button mat-menu-item
*ngIf="user?.state !== UserState.USER_STATE_INACTIVE"
(click)="changeState(UserState.USER_STATE_INACTIVE)">{{'USER.PAGES.DEACTIVATE' |
translate}}</button>
<button mat-menu-item
*ngIf="user?.state == UserState.USER_STATE_INACTIVE"
(click)="changeState(UserState.USER_STATE_ACTIVE)">{{'USER.PAGES.REACTIVATE' | translate}}</button>
<ng-template appHasRole [appHasRole]="['user.delete$', 'user.delete:'+user?.id]">
<button mat-menu-item matTooltip="{{'USER.PAGES.DELETE' | translate}}"
(click)="deleteUser()"><span [style.color]="'var(--warn)'">{{'USER.PAGES.DELETE' | translate}}</span></button>
</ng-template>
</mat-menu>
</ng-template>
</div>
<mat-progress-bar *ngIf="loading" color="primary" mode="indeterminate"></mat-progress-bar>
<cnsl-info-section class="locked" *ngIf="user?.state === UserState.USER_STATE_LOCKED" type="WARN">{{'USER.PAGES.LOCKEDDESCRIPTION' | translate}}</cnsl-info-section>
<span *ngIf="!loading && !user">{{ 'USER.PAGES.NOUSER' | translate }}</span>
<cnsl-info-row *ngIf="user" [user]="user"></cnsl-info-row>
<ng-template appHasRole [appHasRole]="['user.read$', 'user.read:'+user?.id]">
<app-card *ngIf="user.human" title="{{ 'USER.PROFILE.TITLE' | translate }}">
<app-detail-form [preferredLoginName]="user.preferredLoginName" [disabled]="(canWrite$ | async) == false" [genders]="genders" [languages]="languages"
[username]="user.userName" [user]="user.human" (submitData)="saveProfile($event)">
</app-detail-form>
</app-card>
<app-card *ngIf="user.human" title="{{ 'USER.LOGINMETHODS.TITLE' | translate }}"
description="{{ 'USER.LOGINMETHODS.DESCRIPTION' | translate }}">
<button card-actions class="icon-button" mat-icon-button (click)="refreshUser()" matTooltip="{{'ACTIONS.REFRESH' | translate}}">
<mat-icon class="icon">refresh</mat-icon>
</button>
<app-contact disablePhoneCode="true"
[canWrite]="(['user.write:' + user?.id, 'user.write$'] | hasRole | async)" *ngIf="user?.human"
[human]="user.human" (editType)="openEditDialog($event)" (deletedPhone)="deletePhone()"
(resendEmailVerification)="resendEmailVerification()"
(resendPhoneVerification)="resendPhoneVerification()">
<button pwdAction [disabled]="(canWrite$ | async) == false" (click)="sendSetPasswordNotification()"
mat-stroked-button color="primary" *ngIf="user.state === UserState.USER_STATE_INITIAL">{{
'USER.PASSWORD.RESENDNOTIFICATION' | translate }}</button>
<button emailAction class="resendemail" *ngIf="user.state == UserState.USER_STATE_INITIAL"
mat-stroked-button color="primary" (click)="resendInitEmail()">{{'USER.RESENDINITIALEMAIL' |
translate}}</button>
</app-contact>
</app-card>
<app-external-idps *ngIf="user && user.human && user.id" [userId]="user.id" [service]="mgmtUserService"></app-external-idps>
<app-card *ngIf="user.machine" title="{{ 'USER.MACHINE.TITLE' | translate }}">
<app-detail-form-machine [disabled]="(canWrite$ | async) == false" [username]="user.userName"
[user]="user.machine" (submitData)="saveMachine($event)">
</app-detail-form-machine>
</app-card>
<app-card *ngIf="user.machine && user.id" title="{{ 'USER.MACHINE.KEYSTITLE' | translate }}"
description="{{ 'USER.MACHINE.KEYSDESC' | translate }}">
<app-machine-keys [userId]="user.id"></app-machine-keys>
</app-card>
</ng-template>
<app-passwordless *ngIf="user && user.human" [user]="user"></app-passwordless>
<app-user-mfa *ngIf="user && user.human" [user]="user"></app-user-mfa>
<app-card *ngIf="user?.id" title="{{ 'GRANTS.USER.TITLE' | translate }}"
description="{{'GRANTS.USER.DESCRIPTION' | translate }}">
<app-user-grants [userId]="user.id" [context]="USERGRANTCONTEXT"
[displayedColumns]="['select', 'projectId', 'dates', 'roleNamesList']"
[disableWrite]="((['user.grant.write$'] | hasRole) | async) == false"
[disableDelete]="((['user.grant.delete$'] | hasRole) | async) == false">
</app-user-grants>
</app-card>
<ng-template appHasFeature [appHasFeature]="['metadata.user']">
<cnsl-metadata *ngIf="user" [userId]="user.id"></cnsl-metadata>
</ng-template>
</div>
<div *ngIf="user" class="side" metainfo>
<div class="meta-details">
<div class="meta-row">
<span class="first">{{'RESOURCEID' | translate}}:</span>
<span *ngIf="user?.id" class="second">{{ user.id }}</span>
</div>
<div class="meta-row" *ngIf="user?.preferredLoginName">
<span class="first">{{'USER.PREFERRED_LOGINNAME' | translate}}</span>
<span class="second"><span style="display: block;">{{user.preferredLoginName}}</span></span>
</div>
<div class="meta-row">
<span class="first">{{'USER.PAGES.STATE' | translate}}</span>
<span *ngIf="user && user.state !== undefined" class="state"
[ngClass]="{'active': user.state === UserState.USER_STATE_ACTIVE, 'inactive': user.state === UserState.USER_STATE_INACTIVE}">{{'USER.DATA.STATE'+user.state
| translate}}</span>
</div>
</div> </div>
<mat-tab-group mat-stretch-tabs class="tab-group" disablePagination="true"> <span class="fill-space"></span>
<mat-tab label="Details"> <ng-template appHasRole [appHasRole]="['user.write$', 'user.write:'+user?.id]">
<div class="side-padding"> <button class="actions-trigger" mat-raised-button color="primary" [matMenuTriggerFor]="actions">
<ng-template appHasRole [appHasRole]="['user.membership.read']"> <span>{{'ACTIONS.ACTIONS' | translate}}</span>
<app-memberships [user]="user" [disabled]="(canWrite$ | async) == false"></app-memberships> <mat-icon class="icon">keyboard_arrow_down</mat-icon>
</ng-template> </button>
</div> <mat-menu #actions="matMenu" xPosition="before">
</mat-tab> <button mat-menu-item color="warn" *ngIf="user?.state === UserState.USER_STATE_LOCKED"
<mat-tab label="{{ 'CHANGES.PROJECT.TITLE' | translate }}" class="meta-flex-col"> (click)="unlockUser()">{{'USER.PAGES.UNLOCK' |
<app-changes class="changes" [refresh]="refreshChanges$" [changeType]="ChangeType.USER" [id]="user.id"> translate}}</button>
</app-changes> <button mat-menu-item *ngIf="user?.state !== UserState.USER_STATE_INACTIVE"
</mat-tab> (click)="changeState(UserState.USER_STATE_INACTIVE)">{{'USER.PAGES.DEACTIVATE' |
</mat-tab-group> translate}}</button>
<button mat-menu-item *ngIf="user?.state == UserState.USER_STATE_INACTIVE"
(click)="changeState(UserState.USER_STATE_ACTIVE)">{{'USER.PAGES.REACTIVATE' | translate}}</button>
<ng-template appHasRole [appHasRole]="['user.delete$', 'user.delete:'+user?.id]">
<button mat-menu-item matTooltip="{{'USER.PAGES.DELETE' | translate}}" (click)="deleteUser()"><span
[style.color]="'var(--warn)'">{{'USER.PAGES.DELETE' | translate}}</span></button>
</ng-template>
</mat-menu>
</ng-template>
</div> </div>
<mat-progress-bar *ngIf="loading" color="primary" mode="indeterminate"></mat-progress-bar>
<cnsl-info-section class="locked" *ngIf="user?.state === UserState.USER_STATE_LOCKED" type="WARN">
{{'USER.PAGES.LOCKEDDESCRIPTION' | translate}}</cnsl-info-section>
<span *ngIf="!loading && !user">{{ 'USER.PAGES.NOUSER' | translate }}</span>
<cnsl-info-row *ngIf="user" [user]="user"></cnsl-info-row>
<ng-template appHasRole [appHasRole]="['user.read$', 'user.read:'+user?.id]">
<app-card *ngIf="user.human" title="{{ 'USER.PROFILE.TITLE' | translate }}">
<app-detail-form [preferredLoginName]="user.preferredLoginName" [disabled]="(canWrite$ | async) == false"
[genders]="genders" [languages]="languages" [username]="user.userName" [user]="user.human"
(submitData)="saveProfile($event)">
</app-detail-form>
</app-card>
<app-card *ngIf="user.human" title="{{ 'USER.LOGINMETHODS.TITLE' | translate }}"
description="{{ 'USER.LOGINMETHODS.DESCRIPTION' | translate }}">
<button card-actions class="icon-button" mat-icon-button (click)="refreshUser()"
matTooltip="{{'ACTIONS.REFRESH' | translate}}">
<mat-icon class="icon">refresh</mat-icon>
</button>
<app-contact disablePhoneCode="true" [canWrite]="(['user.write:' + user?.id, 'user.write$'] | hasRole | async)"
*ngIf="user?.human" [human]="user.human" (editType)="openEditDialog($event)" (deletedPhone)="deletePhone()"
(resendEmailVerification)="resendEmailVerification()" (resendPhoneVerification)="resendPhoneVerification()">
<button pwdAction [disabled]="(canWrite$ | async) == false" (click)="sendSetPasswordNotification()"
mat-stroked-button color="primary" *ngIf="user.state === UserState.USER_STATE_INITIAL">{{
'USER.PASSWORD.RESENDNOTIFICATION' | translate }}</button>
<button emailAction class="resendemail" *ngIf="user.state == UserState.USER_STATE_INITIAL" mat-stroked-button
color="primary" (click)="resendInitEmail()">{{'USER.RESENDINITIALEMAIL' |
translate}}</button>
</app-contact>
</app-card>
<app-external-idps *ngIf="user && user.human && user.id" [userId]="user.id" [service]="mgmtUserService">
</app-external-idps>
<app-card *ngIf="user.machine" title="{{ 'USER.MACHINE.TITLE' | translate }}">
<app-detail-form-machine [disabled]="(canWrite$ | async) == false" [username]="user.userName"
[user]="user.machine" (submitData)="saveMachine($event)">
</app-detail-form-machine>
</app-card>
<app-card *ngIf="user.machine && user.id" title="{{ 'USER.MACHINE.KEYSTITLE' | translate }}"
description="{{ 'USER.MACHINE.KEYSDESC' | translate }}">
<app-machine-keys [userId]="user.id"></app-machine-keys>
</app-card>
</ng-template>
<app-passwordless *ngIf="user && user.human" [user]="user"></app-passwordless>
<app-user-mfa *ngIf="user && user.human" [user]="user"></app-user-mfa>
<app-card *ngIf="user?.id" title="{{ 'GRANTS.USER.TITLE' | translate }}"
description="{{'GRANTS.USER.DESCRIPTION' | translate }}">
<app-user-grants [userId]="user.id" [context]="USERGRANTCONTEXT"
[displayedColumns]="['select', 'projectId', 'dates', 'roleNamesList']"
[disableWrite]="((['user.grant.write$'] | hasRole) | async) == false"
[disableDelete]="((['user.grant.delete$'] | hasRole) | async) == false">
</app-user-grants>
</app-card>
<ng-template appHasFeature [appHasFeature]="['metadata.user']">
<cnsl-metadata *ngIf="user" [userId]="user.id"></cnsl-metadata>
</ng-template>
</div>
<div *ngIf="user" class="side" metainfo>
<div class="meta-details">
<div class="meta-row">
<span class="first">{{'RESOURCEID' | translate}}:</span>
<span *ngIf="user?.id" class="second">{{ user.id }}</span>
</div>
<div class="meta-row" *ngIf="user?.preferredLoginName">
<span class="first">{{'USER.PREFERRED_LOGINNAME' | translate}}</span>
<span class="second"><span style="display: block;">{{user.preferredLoginName}}</span></span>
</div>
<div class="meta-row">
<span class="first">{{'USER.PAGES.STATE' | translate}}</span>
<span *ngIf="user && user.state !== undefined" class="state"
[ngClass]="{'active': user.state === UserState.USER_STATE_ACTIVE, 'inactive': user.state === UserState.USER_STATE_INACTIVE}">{{'USER.DATA.STATE'+user.state
| translate}}</span>
</div>
</div>
<mat-tab-group mat-stretch-tabs class="tab-group" disablePagination="true">
<mat-tab label="Details">
<div class="side-padding">
<ng-template appHasRole [appHasRole]="['user.membership.read']">
<app-memberships [user]="user" [disabled]="(canWrite$ | async) == false"></app-memberships>
</ng-template>
</div>
</mat-tab>
<mat-tab label="{{ 'CHANGES.PROJECT.TITLE' | translate }}" class="meta-flex-col">
<app-changes class="changes" [refresh]="refreshChanges$" [changeType]="ChangeType.USER" [id]="user.id">
</app-changes>
</mat-tab>
</mat-tab-group>
</div>
</app-meta-layout> </app-meta-layout>

View File

@ -37,6 +37,8 @@ export class UserDetailComponent implements OnInit {
public EditDialogType: any = EditDialogType; public EditDialogType: any = EditDialogType;
public refreshChanges$: EventEmitter<void> = new EventEmitter(); public refreshChanges$: EventEmitter<void> = new EventEmitter();
public error: string = '';
constructor( constructor(
public translate: TranslateService, public translate: TranslateService,
private route: ActivatedRoute, private route: ActivatedRoute,
@ -56,7 +58,8 @@ export class UserDetailComponent implements OnInit {
this.user = resp.user; this.user = resp.user;
} }
}).catch(err => { }).catch(err => {
console.error(err); this.error = err.message ?? '';
this.toast.showError(err);
}); });
this.mgmtUserService.listUserMetadata(id, 0, 100, []).then(resp => { this.mgmtUserService.listUserMetadata(id, 0, 100, []).then(resp => {

View File

@ -5,14 +5,14 @@ import { TimestampToDatePipe } from './timestamp-to-date.pipe';
@NgModule({ @NgModule({
declarations: [ declarations: [
TimestampToDatePipe, TimestampToDatePipe,
], ],
imports: [ imports: [
CommonModule, CommonModule,
], ],
exports: [ exports: [
TimestampToDatePipe, TimestampToDatePipe,
], ],
}) })
export class TimestampToDatePipeModule { } export class TimestampToDatePipeModule { }

View File

@ -9,6 +9,8 @@ import {
AddIAMMemberResponse, AddIAMMemberResponse,
AddIDPToLoginPolicyRequest, AddIDPToLoginPolicyRequest,
AddIDPToLoginPolicyResponse, AddIDPToLoginPolicyResponse,
AddJWTIDPRequest,
AddJWTIDPResponse,
AddMultiFactorToLoginPolicyRequest, AddMultiFactorToLoginPolicyRequest,
AddMultiFactorToLoginPolicyResponse, AddMultiFactorToLoginPolicyResponse,
AddOIDCIDPRequest, AddOIDCIDPRequest,
@ -144,6 +146,8 @@ import {
UpdateCustomOrgIAMPolicyResponse, UpdateCustomOrgIAMPolicyResponse,
UpdateIAMMemberRequest, UpdateIAMMemberRequest,
UpdateIAMMemberResponse, UpdateIAMMemberResponse,
UpdateIDPJWTConfigRequest,
UpdateIDPJWTConfigResponse,
UpdateIDPOIDCConfigRequest, UpdateIDPOIDCConfigRequest,
UpdateIDPOIDCConfigResponse, UpdateIDPOIDCConfigResponse,
UpdateIDPRequest, UpdateIDPRequest,
@ -677,6 +681,18 @@ export class AdminService {
return this.grpcService.admin.reactivateIDP(req, null).then(resp => resp.toObject()); return this.grpcService.admin.reactivateIDP(req, null).then(resp => resp.toObject());
} }
public addJWTIDP(
req: AddJWTIDPRequest,
): Promise<AddJWTIDPResponse.AsObject> {
return this.grpcService.admin.addJWTIDP(req, null).then(resp => resp.toObject());
}
public updateIDPJWTConfig(
req: UpdateIDPJWTConfigRequest,
): Promise<UpdateIDPJWTConfigResponse.AsObject> {
return this.grpcService.admin.updateIDPJWTConfig(req, null).then(resp => resp.toObject());
}
public listIAMMembers( public listIAMMembers(
limit: number, limit: number,
offset: number, offset: number,

View File

@ -40,6 +40,8 @@ import {
AddOIDCAppResponse, AddOIDCAppResponse,
AddOrgDomainRequest, AddOrgDomainRequest,
AddOrgDomainResponse, AddOrgDomainResponse,
AddOrgJWTIDPRequest,
AddOrgJWTIDPResponse,
AddOrgMemberRequest, AddOrgMemberRequest,
AddOrgMemberResponse, AddOrgMemberResponse,
AddOrgOIDCIDPRequest, AddOrgOIDCIDPRequest,
@ -372,6 +374,8 @@ import {
UpdateMachineResponse, UpdateMachineResponse,
UpdateOIDCAppConfigRequest, UpdateOIDCAppConfigRequest,
UpdateOIDCAppConfigResponse, UpdateOIDCAppConfigResponse,
UpdateOrgIDPJWTConfigRequest,
UpdateOrgIDPJWTConfigResponse,
UpdateOrgIDPOIDCConfigRequest, UpdateOrgIDPOIDCConfigRequest,
UpdateOrgIDPOIDCConfigResponse, UpdateOrgIDPOIDCConfigResponse,
UpdateOrgIDPRequest, UpdateOrgIDPRequest,
@ -773,6 +777,18 @@ export class ManagementService {
return this.grpcService.mgmt.reactivateOrgIDP(req, null).then(resp => resp.toObject()); return this.grpcService.mgmt.reactivateOrgIDP(req, null).then(resp => resp.toObject());
} }
public addOrgJWTIDP(
req: AddOrgJWTIDPRequest,
): Promise<AddOrgJWTIDPResponse.AsObject> {
return this.grpcService.mgmt.addOrgJWTIDP(req, null).then(resp => resp.toObject());
}
public updateOrgIDPJWTConfig(
req: UpdateOrgIDPJWTConfigRequest,
): Promise<UpdateOrgIDPJWTConfigResponse.AsObject> {
return this.grpcService.mgmt.updateOrgIDPJWTConfig(req, null).then(resp => resp.toObject());
}
public addHumanUser(req: AddHumanUserRequest): Promise<AddHumanUserResponse.AsObject> { public addHumanUser(req: AddHumanUserRequest): Promise<AddHumanUserResponse.AsObject> {
return this.grpcService.mgmt.addHumanUser(req, null).then(resp => resp.toObject()); return this.grpcService.mgmt.addHumanUser(req, null).then(resp => resp.toObject());
} }

View File

@ -757,6 +757,7 @@
"DESCRIPTION": "Definiere die Loginmethoden für Benutzer", "DESCRIPTION": "Definiere die Loginmethoden für Benutzer",
"DESCRIPTIONCREATEADMIN": "Nutzer können sich mit den verfügbaren Idps authentifizieren.", "DESCRIPTIONCREATEADMIN": "Nutzer können sich mit den verfügbaren Idps authentifizieren.",
"DESCRIPTIONCREATEMGMT": "Nutzer können sich mit den verfügbaren Idps authentifizieren. Achtung: Es kann zwischen System- und organisationsspezifischen Providern gewählt werden.", "DESCRIPTIONCREATEMGMT": "Nutzer können sich mit den verfügbaren Idps authentifizieren. Achtung: Es kann zwischen System- und organisationsspezifischen Providern gewählt werden.",
"ADVANCED":"Erweitert",
"SAVED": "Erfolgreich gespeichert." "SAVED": "Erfolgreich gespeichert."
}, },
"PRIVACY_POLICY": { "PRIVACY_POLICY": {
@ -1039,6 +1040,7 @@
"MEMBERTITLE": "Berechtigte Manager der Organisation", "MEMBERTITLE": "Berechtigte Manager der Organisation",
"MEMBERDESC": "Dies sind die Manager der berechtigten Organisation. Wähle die Benutzer, die Zugriff zum Bearbeiten der jeweiligen Zugänge erhalten sollen.", "MEMBERDESC": "Dies sind die Manager der berechtigten Organisation. Wähle die Benutzer, die Zugriff zum Bearbeiten der jeweiligen Zugänge erhalten sollen.",
"PROJECTNAME": "Projektname", "PROJECTNAME": "Projektname",
"GRANTEDORG":"Berechtigte Organisation",
"RESOURCEOWNER": "Besitzer" "RESOURCEOWNER": "Besitzer"
}, },
"SHOWDETAIL": "Details anzeigen", "SHOWDETAIL": "Details anzeigen",
@ -1132,19 +1134,18 @@
"IDP": { "IDP": {
"LIST": { "LIST": {
"TITLE": "Identitäts Provider", "TITLE": "Identitäts Provider",
"DESCRIPTION": "Definieren Sie hier Ihre zusätzlichen Idps, die sie für die Authentifizierung in Ihren Organisationen verwenden können." "DESCRIPTION": "Definieren Sie hier Ihre zusätzlichen Idps, die sie für die Authentifizierung in Ihren Organisationen verwenden können.",
"ACTIVETITLE":"Aktive Identitäts Provider"
}, },
"CREATE": { "CREATE": {
"TITLE": "Neuer Identitäts Provider", "TITLE": "Neuer Identitäts Provider",
"DESCRIPTION": "Definieren Sie hier die Zugangsdaten des neuen Identitäts Providers" "DESCRIPTION": "Wählen Sie einen der folgenden Typen von Identitäts Providern"
}, },
"DETAIL": { "DETAIL": {
"TITLE": "Identitäts Provider", "TITLE": "Identitäts Provider",
"DESCRIPTION": "Generelle Konfiguration deines Identitäts Providers", "DESCRIPTION": "Generelle Konfiguration deines Identitäts Providers",
"OIDC": { "DATECREATED":"Erstellt",
"TITLE": "OIDC Konfiguration", "DATECHANGED":"Geändert"
"DESCRIPTION": "Geben Sie die korrekte OIDC Konfiguration Ihres Identity Providers an!"
}
}, },
"OWNERTYPES": { "OWNERTYPES": {
"0": "unknown", "0": "unknown",
@ -1168,9 +1169,11 @@
"0": "kein Styling", "0": "kein Styling",
"1": "Google" "1": "Google"
}, },
"ADD":"Identity Provider hinzufügen",
"AUTOREGISTER":"Automatische Registrierung", "AUTOREGISTER":"Automatische Registrierung",
"AUTOREGISTER_DESC":"Wenn aktiviert und noch kein Account vorhanden ist, wird einer für den entsprechenden Benutzer erstellt.", "AUTOREGISTER_DESC":"Wenn aktiviert und noch kein Account vorhanden ist, wird einer für den entsprechenden Benutzer erstellt.",
"TYPE": "Typ", "TYPE": "Typ",
"OWNER":"Besitzer",
"ID": "ID", "ID": "ID",
"NAME": "Name", "NAME": "Name",
"CONFIG": "Konfiguration", "CONFIG": "Konfiguration",
@ -1181,15 +1184,27 @@
"CLIENTSECRET": "Client Secret", "CLIENTSECRET": "Client Secret",
"IDPDISPLAYNAMMAPPING": "IDP Anzeigename Mapping", "IDPDISPLAYNAMMAPPING": "IDP Anzeigename Mapping",
"USERNAMEMAPPING": "Username Mapping", "USERNAMEMAPPING": "Username Mapping",
"DATES":"Datum",
"CREATIONDATE": "Erstelldatum", "CREATIONDATE": "Erstelldatum",
"CHANGEDATE": "Letzte Änderung", "CHANGEDATE": "Letzte Änderung",
"DEACTIVATE": "Deaktivieren", "DEACTIVATE": "Deaktivieren",
"ACTIVATE": "Aktivieren", "ACTIVATE": "Aktivieren",
"DELETE": "Löschen", "DELETE": "Löschen",
"DELETE_TITLE": "Idp löschen", "DELETE_TITLE": "IDP löschen",
"DELETE_DESCRIPTION": "Sie sind im Begriff einen Identity Provider zu löschen. Die daruch hervorgerufenen Änderungen sind unwiederruflich. Wollen Sie dies wirklich tun?", "DELETE_DESCRIPTION": "Sie sind im Begriff einen Identity Provider zu löschen. Die daruch hervorgerufenen Änderungen sind unwiederruflich. Wollen Sie dies wirklich tun?",
"DELETE_SELECTION_TITLE": "Identity Providers löschen", "DELETE_SELECTION_TITLE": "Identity Providers löschen",
"DELETE_SELECTION_DESCRIPTION": "Sie sind im Begriff mehrere Identity Provider zu löschen. Die daruch hervorgerufenen Änderungen sind unwiederruflich. Wollen Sie dies wirklich tun?", "DELETE_SELECTION_DESCRIPTION": "Sie sind im Begriff mehrere Identity Provider zu löschen. Die daruch hervorgerufenen Änderungen sind unwiederruflich. Wollen Sie dies wirklich tun?",
"OIDC": {
"TITLE":"OpenId Connect IDP",
"DESCRIPTION":"Geben Sie die Daten OIDC Identity Providers ein."
},
"JWT": {
"TITLE":"JWT IDP",
"DESCRIPTION":"Geben Sie die Daten JWT Identity Providers ein. ",
"HEADERNAME":"Header Name",
"JWTENDPOINT":"JWT Endpoint",
"JWTKEYSENDPOINT":"JWT Keys Endpoint"
},
"TOAST": { "TOAST": {
"SAVED": "Erfolgreich gespeichert.", "SAVED": "Erfolgreich gespeichert.",
"REACTIVATED": "Idp reaktiviert.", "REACTIVATED": "Idp reaktiviert.",

View File

@ -759,6 +759,7 @@
"DESCRIPTION": "Define how Users can be authenticated and configure Identity Providers", "DESCRIPTION": "Define how Users can be authenticated and configure Identity Providers",
"DESCRIPTIONCREATEADMIN": "Users can choose from the available identity providers below.", "DESCRIPTIONCREATEADMIN": "Users can choose from the available identity providers below.",
"DESCRIPTIONCREATEMGMT": "Users can choose from the available identity providers below. Note: You can use System-set providers as well as providers set for your organisation only.", "DESCRIPTIONCREATEMGMT": "Users can choose from the available identity providers below. Note: You can use System-set providers as well as providers set for your organisation only.",
"ADVANCED":"Advanced",
"SAVED": "Saved successfully!" "SAVED": "Saved successfully!"
}, },
"PRIVACY_POLICY": { "PRIVACY_POLICY": {
@ -1041,6 +1042,7 @@
"MEMBERTITLE": "Managers", "MEMBERTITLE": "Managers",
"MEMBERDESC": "These are the managers of the granted organisation. Add users here who should gain access to edit the data of the project.", "MEMBERDESC": "These are the managers of the granted organisation. Add users here who should gain access to edit the data of the project.",
"PROJECTNAME": "Project Name", "PROJECTNAME": "Project Name",
"GRANTEDORG":"Granted Organisation",
"RESOURCEOWNER": "Resource Owner" "RESOURCEOWNER": "Resource Owner"
}, },
"SHOWDETAIL": "Show Details", "SHOWDETAIL": "Show Details",
@ -1134,19 +1136,18 @@
"IDP": { "IDP": {
"LIST": { "LIST": {
"TITLE": "Identity Providers", "TITLE": "Identity Providers",
"DESCRIPTION": "Define additional Identity Providers, which can be used to authenticate in your organisations." "DESCRIPTION": "Manage your Identity Provider configuration, which can then be activated in your Login Policy.",
"ACTIVETITLE":"Active Identity Providers"
}, },
"CREATE": { "CREATE": {
"TITLE": "New Identity Provider", "TITLE": "New Identity Provider",
"DESCRIPTION": "Configure the Endpoint of your new service provider." "DESCRIPTION": "Choose one of the following Identity Provider types."
}, },
"DETAIL": { "DETAIL": {
"TITLE": "Identity Provider", "TITLE": "Identity Provider",
"DESCRIPTION": "General Configuration of your identity provider.", "DESCRIPTION": "General Configuration of your identity provider.",
"OIDC": { "DATECREATED":"Created",
"TITLE": "OIDC Configuration", "DATECHANGED":"Changed"
"DESCRIPTION": "Provide the correct OIDC Configuration for your identity Provider below!"
}
}, },
"OWNERTYPES": { "OWNERTYPES": {
"0": "unknown", "0": "unknown",
@ -1170,9 +1171,11 @@
"0": "No Styling", "0": "No Styling",
"1": "Google" "1": "Google"
}, },
"ADD":"Add Identity Provider",
"AUTOREGISTER":"Auto Register", "AUTOREGISTER":"Auto Register",
"AUTOREGISTER_DESC":"If selected and no account exists yet, one will be created.", "AUTOREGISTER_DESC":"If selected and no account exists yet, one will be created.",
"TYPE": "Type", "TYPE": "Type",
"OWNER":"Owner",
"ID": "ID", "ID": "ID",
"NAME": "Name", "NAME": "Name",
"CONFIG": "Configuration", "CONFIG": "Configuration",
@ -1183,6 +1186,7 @@
"CLIENTSECRET": "Client Secret", "CLIENTSECRET": "Client Secret",
"IDPDISPLAYNAMMAPPING": "IDP Anzeigename Mapping", "IDPDISPLAYNAMMAPPING": "IDP Anzeigename Mapping",
"USERNAMEMAPPING": "Username Mapping", "USERNAMEMAPPING": "Username Mapping",
"DATES":"Dates",
"CREATIONDATE": "Created At", "CREATIONDATE": "Created At",
"CHANGEDATE": "Last Modified", "CHANGEDATE": "Last Modified",
"DEACTIVATE": "Deactivate", "DEACTIVATE": "Deactivate",
@ -1192,6 +1196,17 @@
"DELETE_DESCRIPTION": "You are about to delete an identity provider. The resulting changes are irrevocable. Do you really want to do this?", "DELETE_DESCRIPTION": "You are about to delete an identity provider. The resulting changes are irrevocable. Do you really want to do this?",
"DELETE_SELECTION_TITLE": "Delete Idp", "DELETE_SELECTION_TITLE": "Delete Idp",
"DELETE_SELECTION_DESCRIPTION": "You are about to delete an identity provider. The resulting changes are irrevocable. Do you really want to do this?", "DELETE_SELECTION_DESCRIPTION": "You are about to delete an identity provider. The resulting changes are irrevocable. Do you really want to do this?",
"OIDC": {
"TITLE":"OpenId Connect IDP",
"DESCRIPTION":"Enter the data for OIDC Identity Provider."
},
"JWT": {
"TITLE":"JWT IDP",
"DESCRIPTION":"Enter the data for JWT Identity Provider.",
"HEADERNAME":"Header Name",
"JWTENDPOINT":"JWT Endpoint",
"JWTKEYSENDPOINT":"JWT Keys Endpoint"
},
"TOAST": { "TOAST": {
"SAVED": "Successfully saved.", "SAVED": "Successfully saved.",
"REACTIVATED": "Idp reactivated.", "REACTIVATED": "Idp reactivated.",

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="158px" height="50px" viewBox="0 0 158 50" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<g id="Page-1" stroke="none" stroke-width="1" sketch:type="MSPage">
<g id="jwt_logo" sketch:type="MSLayerGroup" transform="translate(-292.000000, -563.000000)">
<g id="Group" transform="translate(0.000000, 413.000000)" sketch:type="MSShapeGroup">
<g transform="translate(150.000000, 125.000000)" id="Shape">
<path d="M156.1,25.8 L156.1,60.8 C156.1,68.5 149.8,74.8 142.1,74.8 L142.1,67.8 C146,67.8 149.1,64.7 149.1,60.8 L149.1,25.8 L156.1,25.8 L156.1,25.8 Z M283.9,32.8 L299.7,32.8 L299.7,25.8 L261.2,25.8 L261.2,32.8 L276.9,32.8 L276.9,74.8 L283.9,74.8 L283.9,32.8 L283.9,32.8 Z M240.1,25.8 L240.1,60.8 C240.1,64.7 237,67.8 233.1,67.8 C229.2,67.8 226.1,64.7 226.1,60.8 L226.1,39.8 C226.1,32.1 219.8,25.8 212.1,25.8 C204.4,25.8 198.1,32.1 198.1,39.8 L198.1,60.8 C198.1,64.7 195,67.8 191.1,67.8 C187.2,67.8 184.1,64.7 184.1,60.8 L184.1,25.8 L177.1,25.8 L177.1,60.8 C177.1,68.5 183.4,74.8 191.1,74.8 C198.8,74.8 205.1,68.5 205.1,60.8 L205.1,39.8 C205.1,35.9 208.2,32.8 212.1,32.8 C216,32.8 219.1,35.9 219.1,39.8 L219.1,39.8 L219.1,60.8 C219.1,68.5 225.4,74.8 233.1,74.8 C240.8,74.8 247.1,68.5 247.1,60.8 L247.1,25.8 L240.1,25.8 L240.1,25.8 Z"></path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M14,2L11,3.5V19.94C7,19.5 4,17.46 4,15C4,12.75 6.5,10.85 10,10.22V8.19C4.86,8.88 1,11.66 1,15C1,18.56 5.36,21.5 11,21.94C11.03,21.94 11.06,21.94 11.09,21.94L14,20.5V2M15,8.19V10.22C16.15,10.43 17.18,10.77 18.06,11.22L16.5,12L23,13.5L22.5,9L20.5,10C19,9.12 17.12,8.47 15,8.19Z" /></svg>

After

Width:  |  Height:  |  Size: 570 B

View File

@ -23,10 +23,12 @@
@import 'src/app/modules/onboarding/onboarding.component.scss'; @import 'src/app/modules/onboarding/onboarding.component.scss';
@import 'src/app/modules/policies/private-labeling-policy/private-labeling-policy.component.scss'; @import 'src/app/modules/policies/private-labeling-policy/private-labeling-policy.component.scss';
@import 'src/app/modules/info-row/info-row.component.scss'; @import 'src/app/modules/info-row/info-row.component.scss';
@import 'src/app/modules/idp-create/idp-type-radio/idp-type-radio.component.scss';
@mixin component-themes($theme) { @mixin component-themes($theme) {
@include avatar-theme($theme); @include avatar-theme($theme);
@include app-type-radio-theme($theme); @include app-type-radio-theme($theme);
@include idp-type-radio-theme($theme);
@include app-auth-method-radio-theme($theme); @include app-auth-method-radio-theme($theme);
@include card-theme($theme); @include card-theme($theme);
@include table-theme($theme); @include table-theme($theme);