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

View File

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

View File

@ -26,7 +26,7 @@ const routes: Routes = [
.then(m => m.GrantedProjectsModule),
canActivate: [AuthGuard, RoleGuard],
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 { BehaviorSubject, from, Observable, of, Subject } from 'rxjs';
import { catchError, debounceTime, finalize, map, take, takeUntil } from 'rxjs/operators';
import { accountCard, adminLineAnimation, navAnimations, routeAnimations, toolbarAnimation } from './animations';
import { TextQueryMethod } from './proto/generated/zitadel/object_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.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(
'mdi_symbol',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/symbol.svg'),

View File

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

View File

@ -1,6 +1,6 @@
<div class="radio-button-wrapper">
<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">
<div class="cnsl-type-radio-header" [ngStyle]="{'background': type.background}">
<span>{{type.prefix}}</span>

View File

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

View File

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

View File

@ -7,9 +7,16 @@
</div>
<div class="detail-right">
<div class="head">
<h1>{{ title }}</h1>
<p class="desc">{{ description }}</p>
<ng-content></ng-content>
<div class="top-view">
<div>
<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>

View File

@ -46,15 +46,26 @@
.head {
margin-bottom: 2rem;
h1 {
font-size: 1.5rem;
margin-top: 10px;
.top-view {
display: flex;
justify-content: space-between;
}
.desc {
display: block;
font-size: .9rem;
color: var(--grey);
div {
h1 {
font-size: 1.5rem;
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>
<h1>{{'IDP.CREATE.TITLE' | translate}}</h1>
<p>{{'IDP.CREATE.DESCRIPTION' | translate}}</p>
<mat-progress-bar *ngIf="loading" color="primary" mode="indeterminate"></mat-progress-bar>
<form (ngSubmit)="addIdp()">
<ng-container [formGroup]="formGroup">
<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>
<ng-container *ngIf="currentCreateStep === 1">
<p class="desc">{{'IDP.CREATE.DESCRIPTION' | translate}}</p>
<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>
<cnsl-idp-type-radio [types]="idpTypes" (selectedType)="idpType = $event"
[selected]="idpType"></cnsl-idp-type-radio>
<div class="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="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>
<div class="actions">
<button mat-raised-button [disabled]="!idpType" color="primary"
(click)="currentCreateStep = 2">{{'ACTIONS.CONTINUE' | translate}}</button>
</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 }}
</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 color="primary" mat-raised-button class="continue-button" [disabled]="jwtFormGroup.invalid" type="submit">
{{ 'ACTIONS.SAVE' | translate }}
</button>
</div>
</form>
</ng-container>
</div>

View File

@ -1,3 +1,7 @@
.desc {
color: var(--grey);
font-size: 14px;
}
.container {
padding: 4rem 4rem 2rem 4rem;
@ -29,7 +33,6 @@
}
.auto-reg-info {
margin: 0 .5rem 1rem .5rem;
display: block;
width: 100%;
@ -38,7 +41,7 @@
}
}
.content {
.idp-content {
display: flex;
margin: 0 -.5rem;
flex-wrap: wrap;
@ -51,7 +54,7 @@
}
.formfield {
flex: 1 0 auto;
flex: 1;
margin: 0 .5rem;
@media only screen and (max-width: 450px) {
@ -60,13 +63,17 @@
}
}
.continue-button {
margin-top: 3rem;
display: block;
padding: .5rem 4rem;
@media only screen and (max-width: 450px) {
.actions {
button[mat-stroked-button] {
float: left;
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 { Subscription } from 'rxjs';
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 { 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 { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
import { PolicyComponentServiceType } from '../policies/policy-component-types.enum';
import { JWT, OIDC, RadioItemIdpType } from './idptypes';
@Component({
selector: 'app-idp-create',
@ -29,10 +30,23 @@ export class IdpCreateComponent implements OnInit, OnDestroy {
private subscription?: Subscription;
public projectId: string = '';
public formGroup!: FormGroup;
public createSteps: number = 1;
public oidcFormGroup!: FormGroup;
public jwtFormGroup!: FormGroup;
public createSteps: number = 2;
public currentCreateStep: number = 1;
public loading: boolean = false;
public idpTypes: RadioItemIdpType[] = [
OIDC,
JWT,
];
OIDC: any = OIDC;
JWT: any = JWT;
public idpType!: RadioItemIdpType;
constructor(
private router: Router,
private route: ActivatedRoute,
@ -40,7 +54,7 @@ export class IdpCreateComponent implements OnInit, OnDestroy {
private injector: Injector,
private _location: Location,
) {
this.formGroup = new FormGroup({
this.oidcFormGroup = new FormGroup({
name: new FormControl('', [Validators.required]),
clientId: new FormControl('', [Validators.required]),
clientSecret: new FormControl('', [Validators.required]),
@ -51,6 +65,16 @@ export class IdpCreateComponent implements OnInit, OnDestroy {
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.serviceType = data.serviceType;
switch (this.serviceType) {
@ -82,7 +106,7 @@ export class IdpCreateComponent implements OnInit, OnDestroy {
this.projectId = projectid;
}
public addIdp(): void {
public addOIDCIdp(): void {
if (this.serviceType === PolicyComponentServiceType.MGMT) {
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 {
this._location.back();
}
@ -162,35 +236,62 @@ export class IdpCreateComponent implements OnInit, OnDestroy {
}
public get name(): AbstractControl | null {
return this.formGroup.get('name');
return this.oidcFormGroup.get('name');
}
public get clientId(): AbstractControl | null {
return this.formGroup.get('clientId');
return this.oidcFormGroup.get('clientId');
}
public get clientSecret(): AbstractControl | null {
return this.formGroup.get('clientSecret');
return this.oidcFormGroup.get('clientSecret');
}
public get issuer(): AbstractControl | null {
return this.formGroup.get('issuer');
return this.oidcFormGroup.get('issuer');
}
public get scopesList(): AbstractControl | null {
return this.formGroup.get('scopesList');
return this.oidcFormGroup.get('scopesList');
}
public get autoRegister(): AbstractControl | null {
return this.formGroup.get('autoRegister');
return this.oidcFormGroup.get('autoRegister');
}
public get idpDisplayNameMapping(): AbstractControl | null {
return this.formGroup.get('idpDisplayNameMapping');
return this.oidcFormGroup.get('idpDisplayNameMapping');
}
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 { IdpCreateRoutingModule } from './idp-create-routing.module';
import { IdpCreateComponent } from './idp-create.component';
import { IdpTypeRadioComponent } from './idp-type-radio/idp-type-radio.component';
@NgModule({
declarations: [IdpCreateComponent],
declarations: [IdpCreateComponent, IdpTypeRadioComponent],
imports: [
IdpCreateRoutingModule,
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">
<th mat-header-cell *matHeaderCellDef> {{ 'IDP.NAME' | translate }} </th>
<td [routerLink]="routerLinkForRow(idp)" mat-cell *matCellDef="let idp"><strong>{{idp?.name}}</strong>
</td>
<td [routerLink]="routerLinkForRow(idp)" mat-cell *matCellDef="let idp">
<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 matColumnDef="config">
@ -62,7 +68,7 @@
</ng-container>
<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">
<div class="date-block">
<span class="date-sub">{{ 'IDP.CREATIONDATE' | translate }}:</span>
@ -76,7 +82,7 @@
</ng-container>
<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">
{{'IDP.OWNERTYPES.'+idp.owner | translate }} </td>
</ng-container>
@ -88,7 +94,7 @@
[disabled]="serviceType==PolicyComponentServiceType.MGMT && idp?.providerType == IDPOwnerType.IDP_OWNER_TYPE_ORG"
mat-icon-button color="warn" matTooltip="{{'ACTIONS.REMOVE' | translate}}"
(click)="removeIdp(idp)">
<mat-icon>remove_circle</mat-icon>
<i class="las la-trash"></i>
</button>
</td>
</ng-container>

View File

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

View File

@ -6,8 +6,8 @@ import { RouterLink } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { ListIDPsResponse } from 'src/app/proto/generated/zitadel/admin_pb';
import { IDP, IDPOwnerType, IDPState, IDPStylingType } from 'src/app/proto/generated/zitadel/idp_pb';
import { ListOrgIDPsResponse } from 'src/app/proto/generated/zitadel/management_pb';
import { IDP, IDPOwnerType, IDPOwnerTypeQuery, IDPState, IDPStylingType } from 'src/app/proto/generated/zitadel/idp_pb';
import { IDPQuery, ListOrgIDPsResponse } from 'src/app/proto/generated/zitadel/management_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';
@ -17,187 +17,190 @@ import { PolicyComponentServiceType } from '../policies/policy-component-types.e
import { WarnDialogComponent } from '../warn-dialog/warn-dialog.component';
@Component({
selector: 'app-idp-table',
templateUrl: './idp-table.component.html',
styleUrls: ['./idp-table.component.scss'],
selector: 'app-idp-table',
templateUrl: './idp-table.component.html',
styleUrls: ['./idp-table.component.scss'],
})
export class IdpTableComponent implements OnInit {
@Input() public serviceType!: PolicyComponentServiceType;
@Input() service!: AdminService | ManagementService;
@Input() disabled: boolean = false;
@ViewChild(PaginatorComponent) public paginator!: PaginatorComponent;
public dataSource: MatTableDataSource<IDP.AsObject>
= new MatTableDataSource<IDP.AsObject>();
public selection: SelectionModel<IDP.AsObject>
= new SelectionModel<IDP.AsObject>(true, []);
public idpResult!: ListIDPsResponse.AsObject | ListOrgIDPsResponse.AsObject;
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
public PolicyComponentServiceType: any = PolicyComponentServiceType;
public IDPOwnerType: any = IDPOwnerType;
public IDPState: any = IDPState;
public IDPSTYLINGTYPE: any = IDPStylingType;
@Input() public displayedColumns: string[] = ['select', 'name', 'config', 'dates', 'state'];
@Input() public serviceType!: PolicyComponentServiceType;
@Input() service!: AdminService | ManagementService;
@Input() disabled: boolean = false;
@ViewChild(PaginatorComponent) public paginator!: PaginatorComponent;
public dataSource: MatTableDataSource<IDP.AsObject>
= new MatTableDataSource<IDP.AsObject>();
public selection: SelectionModel<IDP.AsObject>
= new SelectionModel<IDP.AsObject>(true, []);
public idpResult!: ListIDPsResponse.AsObject | ListOrgIDPsResponse.AsObject;
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
public PolicyComponentServiceType: any = PolicyComponentServiceType;
public IDPOwnerType: any = IDPOwnerType;
public IDPState: any = IDPState;
public IDPSTYLINGTYPE: any = IDPStylingType;
@Input() public displayedColumns: string[] = ['select', 'name', 'dates', 'state'];
@Output() public changedSelection: EventEmitter<Array<IDP.AsObject>>
= new EventEmitter();
@Output() public changedSelection: EventEmitter<Array<IDP.AsObject>>
= new EventEmitter();
constructor(public translate: TranslateService, private toast: ToastService, private dialog: MatDialog) {
this.selection.changed.subscribe(() => {
this.changedSelection.emit(this.selection.selected);
});
constructor(public translate: TranslateService, private toast: ToastService, private dialog: MatDialog) {
this.selection.changed.subscribe(() => {
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 {
this.getData(10, 0);
if (!this.disabled) {
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) {
this.displayedColumns = ['select', 'name', 'config', 'dates', 'state', 'owner'];
}
if (!this.disabled) {
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.service as ManagementService).removeOrgIDP(idp.id).then(() => {
this.toast.showInfo('IDP.TOAST.DELETED', true);
setTimeout(() => {
this.refreshPage();
}, 1000);
}, 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 {
(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);
});
(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) {
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 {
if (this.service instanceof AdminService) {
return ['/iam', 'idp', 'create'];
} else if (this.service instanceof ManagementService) {
return ['/org', 'idp', 'create'];
}
}
public refreshPage(): void {
this.getData(this.paginator.pageSize, this.paginator.pageIndex * this.paginator.pageSize);
}
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];
}
}
public get createRouterLink(): RouterLink | any {
if (this.service instanceof AdminService) {
return ['/iam', 'idp', 'create'];
} else if (this.service instanceof ManagementService) {
return ['/org', 'idp', 'create'];
}
}
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">
<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">
<form (ngSubmit)="updateIdp()">
<cnsl-info-row *ngIf="idp" [idp]="idp"></cnsl-info-row>
<form class="idp-form" (ngSubmit)="updateIdp()">
<ng-container [formGroup]="idpForm">
<div class="content">
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.ID' | translate }}</cnsl-label>
<input cnslInput formControlName="id" />
</cnsl-form-field>
<div class="idp-content">
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.NAME' | translate }}</cnsl-label>
<input cnslInput formControlName="name" />
@ -40,77 +62,115 @@
</div>
</form>
<ng-container *ngIf="oidcConfigForm">
<h2>{{'IDP.DETAIL.OIDC.TITLE' | translate}}</h2>
<p>{{'IDP.DETAIL.OIDC.DESCRIPTION' | translate}}</p>
<ng-container *ngIf="idp?.oidcConfig && oidcConfigForm">
<h2>{{'IDP.OIDC.TITLE' | translate}}</h2>
<p>{{'IDP.OIDC.DESCRIPTION' | translate}}</p>
<form (ngSubmit)="updateOidcConfig()">
<ng-container [formGroup]="oidcConfigForm">
<div class="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.CLIENTID' | translate }}</cnsl-label>
<input cnslInput formControlName="clientId" />
</cnsl-form-field>
<mat-checkbox class="desc" [(ngModel)]="showIdSecretSection" [disabled]="(canWrite | async) === false"
[ngModelOptions]="{standalone: true}">
Update Client Secret
</mat-checkbox>
<cnsl-form-field appearance="outline" class="formfield" *ngIf="showIdSecretSection">
<cnsl-label>{{ 'IDP.CLIENTSECRET' | translate }}</cnsl-label>
<input cnslInput formControlName="clientSecret" />
</cnsl-form-field>
<div class="line">
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.SCOPESLIST' | translate }}</cnsl-label>
<form (ngSubmit)="updateOidcConfig()">
<ng-container [formGroup]="oidcConfigForm">
<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.CLIENTID' | translate }}</cnsl-label>
<input cnslInput formControlName="clientId" />
</cnsl-form-field>
<mat-checkbox class="idp-desc" [(ngModel)]="showIdSecretSection" [disabled]="(canWrite | async) === false"
[ngModelOptions]="{standalone: true}">
Update Client Secret
</mat-checkbox>
<cnsl-form-field appearance="outline" class="formfield" *ngIf="showIdSecretSection">
<cnsl-label>{{ 'IDP.CLIENTSECRET' | translate }}</cnsl-label>
<input cnslInput formControlName="clientSecret" />
</cnsl-form-field>
<div class="line">
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.SCOPESLIST' | translate }}</cnsl-label>
<input cnslInput [matChipInputFor]="chipScopesList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes" [matChipInputAddOnBlur]="true"
(matChipInputTokenEnd)="addScope($event)">
</cnsl-form-field>
<button (click)="addScope($event)" mat-icon-button>
<mat-icon>add</mat-icon>
</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>
<input cnslInput [matChipInputFor]="chipScopesList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes" [matChipInputAddOnBlur]="true"
(matChipInputTokenEnd)="addScope($event)">
</cnsl-form-field>
<button (click)="addScope($event)" mat-icon-button>
<mat-icon>add</mat-icon>
</button>
</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 *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>
</app-detail-layout>

View File

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

View File

@ -3,18 +3,28 @@ import { Location } from '@angular/common';
import { Component, Injector, OnDestroy, Type } from '@angular/core';
import { AbstractControl, FormControl, FormGroup, Validators } from '@angular/forms';
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 { switchMap, take, takeUntil } from 'rxjs/operators';
import { UpdateIDPOIDCConfigRequest, UpdateIDPRequest } from 'src/app/proto/generated/zitadel/admin_pb';
import { IDPStylingType, OIDCMappingField } from 'src/app/proto/generated/zitadel/idp_pb';
import { UpdateOrgIDPOIDCConfigRequest, UpdateOrgIDPRequest } from 'src/app/proto/generated/zitadel/management_pb';
import {
UpdateIDPJWTConfigRequest,
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 { GrpcAuthService } from 'src/app/services/grpc-auth.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';
@Component({
selector: 'app-idp',
@ -28,13 +38,18 @@ export class IdpComponent implements OnDestroy {
public showIdSecretSection: boolean = false;
public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
private service!: ManagementService | AdminService;
public PolicyComponentServiceType: any = PolicyComponentServiceType;
public readonly separatorKeysCodes: number[] = [ENTER, COMMA, SPACE];
public idp!: IDP.AsObject;
private destroy$: Subject<void> = new Subject();
public projectId: string = '';
public idpForm!: FormGroup;
public oidcConfigForm!: FormGroup;
public jwtConfigForm!: FormGroup;
IDPState: any = IDPState;
public canWrite: Observable<boolean> = this.authService.isAllowed([this.serviceType === PolicyComponentServiceType.ADMIN ?
'iam.idp.write' : this.serviceType === PolicyComponentServiceType.MGMT ?
@ -44,8 +59,10 @@ export class IdpComponent implements OnDestroy {
private toast: ToastService,
private injector: Injector,
private route: ActivatedRoute,
private router: Router,
private _location: Location,
private authService: GrpcAuthService,
private dialog: MatDialog,
) {
this.idpForm = new FormGroup({
id: new FormControl({ disabled: true, value: '' }, [Validators.required]),
@ -63,6 +80,13 @@ export class IdpComponent implements OnDestroy {
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(
takeUntil(this.destroy$),
switchMap(data => {
@ -95,20 +119,26 @@ export class IdpComponent implements OnDestroy {
(this.service as ManagementService).getOrgIDPByID(id).then(resp => {
if (resp.idp) {
const idpObject = resp.idp;
this.idpForm.patchValue(idpObject);
if (idpObject.oidcConfig) {
this.oidcConfigForm.patchValue(idpObject.oidcConfig);
this.idp = resp.idp;
this.idpForm.patchValue(this.idp);
if (this.idp.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) {
(this.service as AdminService).getIDPByID(id).then(resp => {
if (resp.idp) {
const idpObject = resp.idp;
this.idpForm.patchValue(idpObject);
if (idpObject.oidcConfig) {
this.oidcConfigForm.patchValue(idpObject.oidcConfig);
this.idp = resp.idp;
this.idpForm.patchValue(this.idp);
if (this.idp.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) {
this.idpForm.enable();
this.oidcConfigForm.enable();
this.id?.disable();
} else {
this.idpForm.disable();
this.oidcConfigForm.disable();
this.id?.disable();
}
});
}
@ -136,32 +164,98 @@ export class IdpComponent implements OnDestroy {
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 {
if (this.serviceType === PolicyComponentServiceType.MGMT) {
const req = new UpdateOrgIDPRequest();
req.setIdpId(this.id?.value);
req.setIdpId(this.idp.id);
req.setName(this.name?.value);
req.setStylingType(this.stylingType?.value);
req.setAutoRegister(this.autoRegister?.value);
(this.service as ManagementService).updateOrgIDP(req).then(() => {
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 UpdateIDPRequest();
req.setIdpId(this.id?.value);
req.setIdpId(this.idp.id);
req.setName(this.name?.value);
req.setStylingType(this.stylingType?.value);
req.setAutoRegister(this.autoRegister?.value);
(this.service as AdminService).updateIDP(req).then(() => {
this.toast.showInfo('IDP.TOAST.SAVED', true);
// this.router.navigate(['idp', ]);
}).catch(error => {
this.toast.showError(error);
});
@ -172,7 +266,7 @@ export class IdpComponent implements OnDestroy {
if (this.serviceType === PolicyComponentServiceType.MGMT) {
const req = new UpdateOrgIDPOIDCConfigRequest();
req.setIdpId(this.id?.value);
req.setIdpId(this.idp.id);
req.setClientId(this.clientId?.value);
req.setClientSecret(this.clientSecret?.value);
req.setIssuer(this.issuer?.value);
@ -182,14 +276,13 @@ export class IdpComponent implements OnDestroy {
(this.service as ManagementService).updateOrgIDPOIDCConfig(req).then((oidcConfig) => {
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 UpdateIDPOIDCConfigRequest();
req.setIdpId(this.id?.value);
req.setIdpId(this.idp.id);
req.setClientId(this.clientId?.value);
req.setClientSecret(this.clientSecret?.value);
req.setIssuer(this.issuer?.value);
@ -199,6 +292,39 @@ export class IdpComponent implements OnDestroy {
(this.service as AdminService).updateIDPOIDCConfig(req).then((oidcConfig) => {
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', ]);
}).catch(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 {
return this.idpForm.get('name');
}
@ -282,4 +404,21 @@ export class IdpComponent implements OnDestroy {
public get usernameMapping(): AbstractControl | null {
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 { MatChipsModule } from '@angular/material/chips';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { MatSelectModule } from '@angular/material/select';
import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core';
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.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 { WarnDialogModule } from '../warn-dialog/warn-dialog.module';
import { IdpRoutingModule } from './idp-routing.module';
import { IdpComponent } from './idp.component';
@ -24,12 +27,15 @@ import { IdpComponent } from './idp.component';
ReactiveFormsModule,
InputModule,
MatButtonModule,
WarnDialogModule,
MatIconModule,
InfoSectionModule,
MatMenuModule,
MatTooltipModule,
MatSelectModule,
TranslateModule,
MatCheckboxModule,
InfoRowModule,
MatChipsModule,
DetailLayoutModule,
],

View File

@ -79,4 +79,34 @@
</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 { Component, Input, OnInit } from '@angular/core';
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';
@Component({
@ -11,8 +12,11 @@ import { User, UserState } from 'src/app/proto/generated/zitadel/user_pb';
export class InfoRowComponent implements OnInit {
@Input() public user!: User.AsObject;
@Input() public app!: App.AsObject;
@Input() public idp!: IDP.AsObject;
public UserState: any = UserState;
public AppState: any = AppState;
public IDPState: any = IDPState;
public copied: 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>
</cnsl-form-field>
<p>{{'LOGINPOLICY.ADDIDP.SELECTIDPS' | translate}}</p>
<cnsl-form-field class="full-width" appearance="outline">
<cnsl-label>{{ 'LOGINPOLICY.ADDIDP.SELECTIDPS' | translate }}</cnsl-label>
<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 { ToastService } from 'src/app/services/toast.service';
import { PolicyComponentServiceType } from '../../policy-component-types.enum';
import { PolicyComponentServiceType } from '../../../policy-component-types.enum';
@Component({
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">
<mat-spinner diameter="30" *ngIf="loading" color="primary"></mat-spinner>
</div>
<ng-container *ngIf="serviceType === PolicyComponentServiceType.MGMT">
<ng-template appHasRole [appHasRole]="['policy.delete']">
<button *ngIf="!isDefault" color="primary" matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="removePolicy()"
mat-stroked-button>
{{'POLICY.RESET' | translate}}
</button>
</ng-template>
<ng-template appHasRole [appHasRole]="['policy.delete']">
<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>
{{'POLICY.RESET' | translate}}
</button>
</ng-template>
<ng-template appHasRole [appHasRole]="['policy.write']">
<button *ngIf="isDefault" color="primary" matTooltip="{{'POLICY.CREATECUSTOM' | translate}}" (click)="savePolicy()"
mat-raised-button>
{{'POLICY.CREATECUSTOM' | translate}}
</button>
</ng-template>
</ng-container>
<ng-template appHasRole [appHasRole]="['policy.write']">
<button *ngIf="isDefault" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['login_policy'] | hasFeature | async) == false" color="primary" matTooltip="{{'POLICY.CREATECUSTOM' | translate}}" (click)="savePolicy()"
mat-raised-button>
{{'POLICY.CREATECUSTOM' | translate}}
</button>
</ng-template>
</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">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.username_login'})"></span>
</cnsl-info-section>
<app-card title="{{ 'IDP.LIST.ACTIVETITLE' | translate }}"
[expanded]="true">
<ng-template #usernameInfo>
<cnsl-info-section class="info">
{{'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 *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) == false" [featureLink]="['/org/features']" class="info" type="WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.idp'})"></span>
</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>
<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>
<app-card title="{{ 'MFA.LIST.MULTIFACTORTITLE' | translate }}" description="{{'MFA.LIST.MULTIFACTORDESCRIPTION' | translate}}" [expanded]="false">
<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>
@ -139,62 +43,134 @@
[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)">
</app-mfa-table>
</app-card>
<h3 class="subheader">{{ 'MFA.LIST.SECONDFACTORTITLE' | translate }}</h3>
<p class="subdesc">{{ 'MFA.LIST.SECONDFACTORDESCRIPTION' | translate }}</p>
<app-card title="{{ 'MFA.LIST.SECONDFACTORTITLE' | translate }}" description="{{'MFA.LIST.SECONDFACTORDESCRIPTION' | translate}}" [expanded]="false">
<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">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.factors'})"></span>
</cnsl-info-section>
<app-mfa-table [service]="service" [serviceType]="serviceType"
[componentType]="LoginMethodComponentType.SecondFactor"
[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"
[componentType]="LoginMethodComponentType.SecondFactor"
[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>
</ng-container>
<app-card title="{{ 'POLICY.LOGIN_POLICY.ADVANCED' | translate }}" [expanded]="false">
<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>
<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">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.idp'})"></span>
</cnsl-info-section>
<ng-template #usernameInfo>
<cnsl-info-section class="info">
{{'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">
<div class="idp"
[ngClass]="{'disabled': (disabled || (serviceType == PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) == false))}"
*ngFor="let idp of idps">
<button
[disabled]="disabled || (serviceType == PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) == false)"
mat-icon-button (click)="removeIdp(idp)" class="rm">
<mat-icon matTooltip="{{'ACTIONS.REMOVE' | translate}}">
remove_circle</mat-icon>
</button>
<div class="line">
<img src="../../../assets/images/google.png"
*ngIf="idp.stylingType == IDPStylingType.STYLING_TYPE_GOOGLE" alt="google" />
<div>
<span class="name">{{idp.idpName}}</span>
<span class="meta-info">{{ 'IDP.TYPE' | translate }}: {{ 'IDP.TYPES.'+idp.idpType |
translate
}}</span>
<span class="meta-info">{{ 'IDP.ID' | translate }}: {{idp.idpId}}</span>
</div>
</div>
<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>
<ng-template #passwordResetInfo>
<cnsl-info-section class="info">
{{'POLICY.DATA.HIDEPASSWORDRESET_DESC' | translate}}
</cnsl-info-section>
</ng-template>
</div>
<div *ngIf="!disabled && ((serviceType == PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async)) || serviceType == PolicyComponentServiceType.ADMIN)"
class="new-idp" (click)="openDialog()" matRipple>
<mat-icon>add</mat-icon>
</div>
</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>
</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']">
<app-card title="{{ 'IDP.LIST.TITLE' | translate }}" description="{{ 'IDP.LIST.DESCRIPTION' | translate }}"
[expanded]="false">
<app-card title="{{ 'IDP.LIST.TITLE' | translate }}" description="{{ 'IDP.LIST.DESCRIPTION' | translate }}"
[expanded]="false">
<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))">
</app-idp-table>
</app-card>
<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))">
</app-idp-table>
</app-card>
</ng-template>
<app-policy-grid class="grid" [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="security"></app-policy-grid>

View File

@ -37,95 +37,6 @@
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 {
width: 100%;
height: 1px;

View File

@ -1,15 +1,12 @@
import { Component, Injector, OnDestroy, Type } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { LoginMethodComponentType } from 'src/app/modules/mfa-table/mfa-table.component';
import {
GetLoginPolicyResponse as AdminGetLoginPolicyResponse,
UpdateLoginPolicyRequest,
UpdateLoginPolicyResponse,
} from 'src/app/proto/generated/zitadel/admin_pb';
import { IDP, IDPLoginPolicyLink, IDPOwnerType, IDPStylingType } from 'src/app/proto/generated/zitadel/idp_pb';
import {
AddCustomLoginPolicyRequest,
GetLoginPolicyResponse as MgmtGetLoginPolicyResponse,
@ -21,7 +18,7 @@ import { ToastService } from 'src/app/services/toast.service';
import { GridPolicy, LOGIN_POLICY } from '../../policy-grid/policies';
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({
selector: 'app-login-policy',
@ -37,17 +34,14 @@ export class LoginPolicyComponent implements OnDestroy {
public service!: ManagementService | AdminService;
public PolicyComponentServiceType: any = PolicyComponentServiceType;
public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
public idps: IDPLoginPolicyLink.AsObject[] = [];
public loading: boolean = false;
public disabled: boolean = true;
public IDPStylingType: any = IDPStylingType;
public currentPolicy: GridPolicy = LOGIN_POLICY;
constructor(
private route: ActivatedRoute,
private toast: ToastService,
private dialog: MatDialog,
private injector: Injector,
) {
this.sub = this.route.data.pipe(switchMap(data => {
@ -83,9 +77,7 @@ export class LoginPolicyComponent implements OnDestroy {
this.disabled = this.isDefault;
}
});
this.getIdps().then(resp => {
this.idps = resp;
});
}
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():
Promise<UpdateLoginPolicyResponse.AsObject> {
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 {
if (this.loginData && this.serviceType === PolicyComponentServiceType.MGMT) {
return (this.loginData as LoginPolicy.AsObject).isDefault;

View File

@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatRippleModule } from '@angular/material/core';
import { MatDialogModule } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
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 { IdpTableModule } from 'src/app/modules/idp-table/idp-table.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 { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
import { InfoSectionModule } from '../../info-section/info-section.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 { 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({
declarations: [LoginPolicyComponent],
declarations: [
LoginPolicyComponent,
LoginPolicyIdpsComponent,
MfaTableComponent,
DialogAddTypeComponent,
AddIdpDialogComponent,
],
imports: [
LoginPolicyRoutingModule,
CommonModule,
@ -33,21 +42,20 @@ import { LoginPolicyComponent } from './login-policy.component';
FormsModule,
CardModule,
InputModule,
MatIconModule,
MatButtonModule,
HasFeaturePipeModule,
MatSlideToggleModule,
MatIconModule,
HasRoleModule,
MatDialogModule,
HasRolePipeModule,
MatTooltipModule,
TranslateModule,
DetailLayoutModule,
AddIdpDialogModule,
IdpTableModule,
MfaTableModule,
MatProgressSpinnerModule,
MatSelectModule,
MatRippleModule,
TranslateModule,
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()"
[emitRefreshOnPreviousRoutes]="refreshOnPreviousRoutes" [timestamp]="dataSource?.viewTimestamp"
[dataSize]="dataSource?.totalResult" [selection]="selection">
<cnsl-form-field @appearfade *ngIf="userGrantListSearchKey != undefined" actions class="filtername">
<input class="filterinput" cnslInput (keyup)="applyFilter($event)"
[placeholder]="('USER.GRANTS.FILTER.' + userGrantListSearchKey.toString()) | translate" #input>
</cnsl-form-field>
[emitRefreshOnPreviousRoutes]="refreshOnPreviousRoutes" [timestamp]="dataSource?.viewTimestamp"
[dataSize]="dataSource?.totalResult" [selection]="selection">
<cnsl-form-field @appearfade *ngIf="userGrantListSearchKey != undefined" actions class="filtername">
<input class="filterinput" cnslInput (keyup)="applyFilter($event)"
[placeholder]="('USER.GRANTS.FILTER.' + userGrantListSearchKey.toString()) | translate" #input>
</cnsl-form-field>
<button color="warn" matTooltip="{{'GRANTS.DELETE' | translate}}" class="icon-button" mat-icon-button actions
(click)="deleteGrantSelection()" *ngIf="selection.hasValue() && disableDelete == false">
<i class="las la-trash"></i>
</button>
<a *ngIf="disableWrite == false" matTooltip="{{'GRANTS.ADD' | translate}}" actions color="primary" color="primary"
mat-raised-button [routerLink]="routerLink">
<mat-icon class="icon">add</mat-icon>{{ 'GRANTS.ADD_BTN' | translate }}
</a>
<button color="warn" matTooltip="{{'GRANTS.DELETE' | translate}}" class="icon-button" mat-icon-button actions
(click)="deleteGrantSelection()" *ngIf="selection.hasValue() && disableDelete == false">
<i class="las la-trash"></i>
</button>
<a *ngIf="disableWrite == false" matTooltip="{{'GRANTS.ADD' | translate}}" actions color="primary" color="primary"
mat-raised-button [routerLink]="routerLink">
<mat-icon class="icon">add</mat-icon>{{ 'GRANTS.ADD_BTN' | translate }}
</a>
<div class="table-wrapper">
<table mat-table multiTemplateDataRows class="table" aria-label="Elements" [dataSource]="dataSource">
<ng-container matColumnDef="select">
<th class="selection" mat-header-cell *matHeaderCellDef>
<mat-checkbox [disabled]="disableWrite" color="primary" (change)="$event ? masterToggle() : null"
[checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()">
</mat-checkbox>
</th>
<td class="selection" mat-cell *matCellDef="let row">
<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))"
color="primary" (click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(row) : null" [checked]="selection.isSelected(row)">
<app-avatar
*ngIf="context !== UserGrantContext.USER && row && row?.displayName && row.firstName && row.lastName"
class="avatar" [name]="row.displayName" [avatarUrl]="row.avatarUrl || ''" [forColor]="row?.preferredLoginName" [size]="32">
</app-avatar>
</mat-checkbox>
</td>
<div class="table-wrapper">
<table mat-table multiTemplateDataRows class="table" aria-label="Elements" [dataSource]="dataSource">
<ng-container matColumnDef="select">
<th class="selection" mat-header-cell *matHeaderCellDef>
<mat-checkbox [disabled]="disableWrite" color="primary" (change)="$event ? masterToggle() : null"
[checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()">
</mat-checkbox>
</th>
<td class="selection" mat-cell *matCellDef="let row">
<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))"
color="primary" (click)="$event.stopPropagation()" (change)="$event ? selection.toggle(row) : null"
[checked]="selection.isSelected(row)">
<app-avatar
*ngIf="context !== UserGrantContext.USER && row && row?.displayName && row.firstName && row.lastName"
class="avatar" [name]="row.displayName" [avatarUrl]="row.avatarUrl || ''"
[forColor]="row?.preferredLoginName" [size]="32">
</app-avatar>
</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 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">
{{grant?.displayName}}</td>
<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>
<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>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;">
</tr>
</table>
<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
*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 *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>
</app-refresh-table>
<ng-template #templateRef let-key="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_off</mat-icon>
</button>
<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_off</mat-icon>
</button>
</ng-template>

View File

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

View File

@ -82,7 +82,7 @@ export class GrantedProjectGridComponent implements OnChanges {
private async getPrefixedItem(key: string): Promise<string | null> {
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> {

View File

@ -1,148 +1,153 @@
<app-meta-layout>
<div class="max-width-container">
<div class="head" *ngIf="project?.id">
<a [routerLink]="[ '/projects' ]" mat-icon-button>
<mat-icon class="icon">arrow_back</mat-icon>
</a>
<h1>{{ 'PROJECT.PAGES.TITLE' | translate }} {{project?.name}}</h1>
<div class="max-width-container">
<div class="head" *ngIf="project?.id">
<a [routerLink]="[ '/projects' ]" mat-icon-button>
<mat-icon class="icon">arrow_back</mat-icon>
</a>
<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']">
<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 (click)="openNameDialog()"
aria-label="Edit project name" *ngIf="isZitadel === false">
{{'ACTIONS.RENAME' | translate}}
</button>
<ng-template appHasRole [appHasRole]="['project.write:'+projectId, 'project.write']">
<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 (click)="openNameDialog()" aria-label="Edit project name" *ngIf="isZitadel === false">
{{'ACTIONS.RENAME' | translate}}
</button>
<button mat-menu-item
[disabled]="isZitadel || (['project.write$', 'project.write:'+ project.id]| hasRole | async) == false"
*ngIf="project?.state === ProjectState.PROJECT_STATE_ACTIVE"
(click)="changeState(ProjectState.PROJECT_STATE_INACTIVE)">
{{'PROJECT.TABLE.DEACTIVATE' | translate}}
</button>
<button mat-menu-item
[disabled]="isZitadel || (['project.write$', 'project.write:'+ project.id]| hasRole | async) == false"
*ngIf="project?.state === ProjectState.PROJECT_STATE_ACTIVE"
(click)="changeState(ProjectState.PROJECT_STATE_INACTIVE)">
{{'PROJECT.TABLE.DEACTIVATE' | translate}}
</button>
<button mat-menu-item
[disabled]="isZitadel || (['project.write$', 'project.write:'+ project.id]| hasRole | async) == false"
*ngIf="project?.state === ProjectState.PROJECT_STATE_INACTIVE"
(click)="changeState(ProjectState.PROJECT_STATE_ACTIVE)">
{{'PROJECT.TABLE.ACTIVATE' | translate}}
</button>
<button mat-menu-item
[disabled]="isZitadel || (['project.write$', 'project.write:'+ project.id]| hasRole | async) == false"
*ngIf="project?.state === ProjectState.PROJECT_STATE_INACTIVE"
(click)="changeState(ProjectState.PROJECT_STATE_ACTIVE)">
{{'PROJECT.TABLE.ACTIVATE' | translate}}
</button>
<ng-template appHasRole [appHasRole]="['project.delete$', 'project.delete:'+projectId]">
<button mat-menu-item matTooltip="{{'ACTIONS.DELETE' | translate}}"
(click)="deleteProject()" aria-label="Edit project name" *ngIf="isZitadel === false">
<span [style.color]="'var(--warn)'">{{'PROJECT.PAGES.DELETE' | translate}}</span>
</button>
</ng-template>
</mat-menu>
</ng-template>
<ng-template appHasRole [appHasRole]="['project.delete$', 'project.delete:'+projectId]">
<button mat-menu-item matTooltip="{{'ACTIONS.DELETE' | translate}}" (click)="deleteProject()"
aria-label="Edit project name" *ngIf="isZitadel === false">
<span [style.color]="'var(--warn)'">{{'PROJECT.PAGES.DELETE' | translate}}</span>
</button>
</ng-template>
</mat-menu>
</ng-template>
<div class="full-width">
<p class="desc">{{ 'PROJECT.PAGES.DESCRIPTION' | translate }}</p>
<p *ngIf="isZitadel" class="zitadel-warning">{{'PROJECT.PAGES.ZITADELPROJECT' | translate}}</p>
</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 class="full-width">
<p class="desc">{{ 'PROJECT.PAGES.DESCRIPTION' | translate }}</p>
<p *ngIf="isZitadel" class="zitadel-warning">{{'PROJECT.PAGES.ZITADELPROJECT' | translate}}</p>
</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">
<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>
<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 [disabled]="((['project.write$', 'project.write:'+ project.id]| hasRole | async))== false"
(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"
[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>
</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> {
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> {

View File

@ -1,57 +1,64 @@
<app-detail-layout [backRouterLink]="[ '/projects', projectid]" title="{{ 'PROJECT.GRANT.DETAIL.TITLE' | translate }}"
description="{{ 'PROJECT.GRANT.DETAIL.DESC' | translate }}">
<div class="master-row">
<div class="left-col">
<div class="row">
<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>
description="{{ 'PROJECT.GRANT.DETAIL.DESC' | translate }}">
<div class="master-row">
<div class="left-col">
<div class="row">
<span class="first">{{'PROJECT.GRANT.DETAIL.PROJECTNAME' | translate}}</span>
<span class="fill-space"></span>
<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>
<span>{{grant?.projectName}}</span>
</div>
<div class="row">
<span class="first">{{'PROJECT.GRANT.DETAIL.RESOURCEOWNER' | translate}}</span>
<span class="fill-space"></span>
<span>{{grant?.projectOwnerName}}</span>
</div>
<div class="row">
<span class="first">{{'PROJECT.GRANT.DETAIL.GRANTEDORG' | translate}}</span>
<span class="fill-space"></span>
<span>{{grant?.grantedOrgName}}</span>
</div>
</div>
<cnsl-form-field class="formfield" appearance="outline" *ngIf="grant && grant.grantedRoleKeysList">
<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>
<span class="fill-space"></span>
<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>
<p class="desc">{{ 'PROJECT.GRANT.DETAIL.MEMBERDESC' | translate }}</p>
<cnsl-form-field class="formfield" appearance="outline" *ngIf="grant && grant.grantedRoleKeysList">
<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"
[memberRoleOptions]="memberRoleOptions" (updateRoles)="updateMemberRoles($event.member, $event.change)"
[factoryLoadFunc]="changePageFactory" (changedSelection)="selection = $event" [refreshTrigger]="changePage">
<button selectactions (click)="removeProjectMemberSelection()"
[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>
<i class="las la-trash"></i>
{{'ACTIONS.SELECTIONDELETE' | translate}}
</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>
<div class="divider"></div>
<h1 class="h1">{{ 'PROJECT.GRANT.DETAIL.MEMBERTITLE' | translate }}</h1>
<p class="desc">{{ 'PROJECT.GRANT.DETAIL.MEMBERDESC' | translate }}</p>
<app-members-table *ngIf="grant" style="width: 100%;" [dataSource]="dataSource"
[canWrite]="['project.grant.member.write','project.grant.member.write:' + grant.grantId] | hasRole | async"
[memberRoleOptions]="memberRoleOptions" (updateRoles)="updateMemberRoles($event.member, $event.change)"
[factoryLoadFunc]="changePageFactory" (changedSelection)="selection = $event" [refreshTrigger]="changePage">
<button selectactions (click)="removeProjectMemberSelection()"
[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>
<i class="las la-trash"></i>
{{'ACTIONS.SELECTIONDELETE' | translate}}
</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>

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$">
<div class="max-width-container">
<div class="head">
<a (click)="navigateBack()" mat-icon-button>
<mat-icon class="icon">arrow_back</mat-icon>
</a>
<div class="head-row">
<h1>{{user.human ? user.human?.profile?.displayName : user.machine?.name}}</h1>
<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 class="max-width-container">
<div class="head">
<a (click)="navigateBack()" mat-icon-button>
<mat-icon class="icon">arrow_back</mat-icon>
</a>
<div class="head-row">
<h1>{{user.human ? user.human?.profile?.displayName : user.machine?.name}}</h1>
<p *ngIf="user?.preferredLoginName">{{user?.preferredLoginName}}</p>
</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>
<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>
<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>

View File

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

View File

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

View File

@ -9,6 +9,8 @@ import {
AddIAMMemberResponse,
AddIDPToLoginPolicyRequest,
AddIDPToLoginPolicyResponse,
AddJWTIDPRequest,
AddJWTIDPResponse,
AddMultiFactorToLoginPolicyRequest,
AddMultiFactorToLoginPolicyResponse,
AddOIDCIDPRequest,
@ -144,6 +146,8 @@ import {
UpdateCustomOrgIAMPolicyResponse,
UpdateIAMMemberRequest,
UpdateIAMMemberResponse,
UpdateIDPJWTConfigRequest,
UpdateIDPJWTConfigResponse,
UpdateIDPOIDCConfigRequest,
UpdateIDPOIDCConfigResponse,
UpdateIDPRequest,
@ -677,6 +681,18 @@ export class AdminService {
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(
limit: number,
offset: number,

View File

@ -40,6 +40,8 @@ import {
AddOIDCAppResponse,
AddOrgDomainRequest,
AddOrgDomainResponse,
AddOrgJWTIDPRequest,
AddOrgJWTIDPResponse,
AddOrgMemberRequest,
AddOrgMemberResponse,
AddOrgOIDCIDPRequest,
@ -372,6 +374,8 @@ import {
UpdateMachineResponse,
UpdateOIDCAppConfigRequest,
UpdateOIDCAppConfigResponse,
UpdateOrgIDPJWTConfigRequest,
UpdateOrgIDPJWTConfigResponse,
UpdateOrgIDPOIDCConfigRequest,
UpdateOrgIDPOIDCConfigResponse,
UpdateOrgIDPRequest,
@ -773,6 +777,18 @@ export class ManagementService {
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> {
return this.grpcService.mgmt.addHumanUser(req, null).then(resp => resp.toObject());
}

View File

@ -757,6 +757,7 @@
"DESCRIPTION": "Definiere die Loginmethoden für Benutzer",
"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.",
"ADVANCED":"Erweitert",
"SAVED": "Erfolgreich gespeichert."
},
"PRIVACY_POLICY": {
@ -1039,6 +1040,7 @@
"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.",
"PROJECTNAME": "Projektname",
"GRANTEDORG":"Berechtigte Organisation",
"RESOURCEOWNER": "Besitzer"
},
"SHOWDETAIL": "Details anzeigen",
@ -1132,19 +1134,18 @@
"IDP": {
"LIST": {
"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": {
"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": {
"TITLE": "Identitäts Provider",
"DESCRIPTION": "Generelle Konfiguration deines Identitäts Providers",
"OIDC": {
"TITLE": "OIDC Konfiguration",
"DESCRIPTION": "Geben Sie die korrekte OIDC Konfiguration Ihres Identity Providers an!"
}
"DATECREATED":"Erstellt",
"DATECHANGED":"Geändert"
},
"OWNERTYPES": {
"0": "unknown",
@ -1168,9 +1169,11 @@
"0": "kein Styling",
"1": "Google"
},
"ADD":"Identity Provider hinzufügen",
"AUTOREGISTER":"Automatische Registrierung",
"AUTOREGISTER_DESC":"Wenn aktiviert und noch kein Account vorhanden ist, wird einer für den entsprechenden Benutzer erstellt.",
"TYPE": "Typ",
"OWNER":"Besitzer",
"ID": "ID",
"NAME": "Name",
"CONFIG": "Konfiguration",
@ -1181,15 +1184,27 @@
"CLIENTSECRET": "Client Secret",
"IDPDISPLAYNAMMAPPING": "IDP Anzeigename Mapping",
"USERNAMEMAPPING": "Username Mapping",
"DATES":"Datum",
"CREATIONDATE": "Erstelldatum",
"CHANGEDATE": "Letzte Änderung",
"DEACTIVATE": "Deaktivieren",
"ACTIVATE": "Aktivieren",
"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_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?",
"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": {
"SAVED": "Erfolgreich gespeichert.",
"REACTIVATED": "Idp reaktiviert.",

View File

@ -759,6 +759,7 @@
"DESCRIPTION": "Define how Users can be authenticated and configure Identity Providers",
"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.",
"ADVANCED":"Advanced",
"SAVED": "Saved successfully!"
},
"PRIVACY_POLICY": {
@ -1041,6 +1042,7 @@
"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.",
"PROJECTNAME": "Project Name",
"GRANTEDORG":"Granted Organisation",
"RESOURCEOWNER": "Resource Owner"
},
"SHOWDETAIL": "Show Details",
@ -1134,19 +1136,18 @@
"IDP": {
"LIST": {
"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": {
"TITLE": "New Identity Provider",
"DESCRIPTION": "Configure the Endpoint of your new service provider."
"DESCRIPTION": "Choose one of the following Identity Provider types."
},
"DETAIL": {
"TITLE": "Identity Provider",
"DESCRIPTION": "General Configuration of your identity provider.",
"OIDC": {
"TITLE": "OIDC Configuration",
"DESCRIPTION": "Provide the correct OIDC Configuration for your identity Provider below!"
}
"DATECREATED":"Created",
"DATECHANGED":"Changed"
},
"OWNERTYPES": {
"0": "unknown",
@ -1170,9 +1171,11 @@
"0": "No Styling",
"1": "Google"
},
"ADD":"Add Identity Provider",
"AUTOREGISTER":"Auto Register",
"AUTOREGISTER_DESC":"If selected and no account exists yet, one will be created.",
"TYPE": "Type",
"OWNER":"Owner",
"ID": "ID",
"NAME": "Name",
"CONFIG": "Configuration",
@ -1183,6 +1186,7 @@
"CLIENTSECRET": "Client Secret",
"IDPDISPLAYNAMMAPPING": "IDP Anzeigename Mapping",
"USERNAMEMAPPING": "Username Mapping",
"DATES":"Dates",
"CREATIONDATE": "Created At",
"CHANGEDATE": "Last Modified",
"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_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?",
"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": {
"SAVED": "Successfully saved.",
"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/policies/private-labeling-policy/private-labeling-policy.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) {
@include avatar-theme($theme);
@include app-type-radio-theme($theme);
@include idp-type-radio-theme($theme);
@include app-auth-method-radio-theme($theme);
@include card-theme($theme);
@include table-theme($theme);