mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 20:47:32 +00:00
Merge branch 'master' into new-eventstore
This commit is contained in:
8
.github/dependabot.yml
vendored
8
.github/dependabot.yml
vendored
@@ -24,3 +24,11 @@ updates:
|
|||||||
commit-message:
|
commit-message:
|
||||||
prefix: chore
|
prefix: chore
|
||||||
include: scope
|
include: scope
|
||||||
|
- package-ecosystem: npm
|
||||||
|
directory: "/site"
|
||||||
|
schedule:
|
||||||
|
interval: monthly
|
||||||
|
open-pull-requests-limit: 10
|
||||||
|
commit-message:
|
||||||
|
prefix: chore
|
||||||
|
include: scope
|
||||||
|
4
.github/workflows/codeql-analysis.yml
vendored
4
.github/workflows/codeql-analysis.yml
vendored
@@ -3,9 +3,13 @@ name: "Code scanning - action"
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [master, ]
|
branches: [master, ]
|
||||||
|
paths-ignore:
|
||||||
|
- 'site/**'
|
||||||
pull_request:
|
pull_request:
|
||||||
# The branches below must be a subset of the branches above
|
# The branches below must be a subset of the branches above
|
||||||
branches: [master]
|
branches: [master]
|
||||||
|
paths-ignore:
|
||||||
|
- 'site/**'
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 12 * * 2'
|
- cron: '0 12 * * 2'
|
||||||
|
|
||||||
|
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
@@ -47,4 +47,4 @@ jobs:
|
|||||||
ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||||
BRANCH: gh-pages
|
BRANCH: gh-pages
|
||||||
FOLDER: site/__sapper__/export
|
FOLDER: site/__sapper__/export
|
||||||
CLEAN: true
|
CLEAN: true
|
28
.github/workflows/release.yml
vendored
28
.github/workflows/release.yml
vendored
@@ -5,7 +5,7 @@ env:
|
|||||||
GITHUB_TOKEN: ${{ secrets.CR_PAT }}
|
GITHUB_TOKEN: ${{ secrets.CR_PAT }}
|
||||||
REGISTRY: ghcr.io
|
REGISTRY: ghcr.io
|
||||||
NODE_VERSION: '12'
|
NODE_VERSION: '12'
|
||||||
GO_VERSION: '1.14'
|
GO_VERSION: '1.15'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
@@ -126,30 +126,6 @@ jobs:
|
|||||||
repository: ${{ github.repository }}
|
repository: ${{ github.repository }}
|
||||||
tag_with_ref: true
|
tag_with_ref: true
|
||||||
tag_with_sha: true
|
tag_with_sha: true
|
||||||
|
|
||||||
container-vulnerability-scan:
|
|
||||||
runs-on: ubuntu-18.04
|
|
||||||
needs: container-prod
|
|
||||||
steps:
|
|
||||||
- name: Source checkout
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Generate Short SHA Container Tag
|
|
||||||
id: vars
|
|
||||||
run: echo "::set-output name=sha_short::SHA-$(git rev-parse --short HEAD)"
|
|
||||||
- name: Check outputs
|
|
||||||
run: echo ${{ steps.vars.outputs.sha_short }}
|
|
||||||
- name: Docker Login
|
|
||||||
run: docker login $REGISTRY -u $GITHUB_ACTOR -p $GITHUB_TOKEN
|
|
||||||
- uses: anchore/scan-action@master
|
|
||||||
with:
|
|
||||||
image-reference: "${{ env.REGISTRY }}/${{ github.repository }}:${{ steps.vars.outputs.sha_short }}"
|
|
||||||
dockerfile-path: "./build/docker/Dockerfile"
|
|
||||||
fail-build: false
|
|
||||||
acs-report-enable: true
|
|
||||||
- name: Upload Anchore Scan Report
|
|
||||||
uses: github/codeql-action/upload-sarif@v1
|
|
||||||
with:
|
|
||||||
sarif_file: results.sarif
|
|
||||||
|
|
||||||
release:
|
release:
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-18.04
|
||||||
@@ -191,4 +167,4 @@ jobs:
|
|||||||
if: env.CAOS_NEXT_VERSION != ''
|
if: env.CAOS_NEXT_VERSION != ''
|
||||||
- name: Docker Push Latest
|
- name: Docker Push Latest
|
||||||
run: docker push $REGISTRY/$GITHUB_REPOSITORY:latest
|
run: docker push $REGISTRY/$GITHUB_REPOSITORY:latest
|
||||||
if: env.CAOS_NEXT_VERSION != ''
|
if: env.CAOS_NEXT_VERSION != ''
|
19
.github/workflows/spellcheck.yml
vendored
Normal file
19
.github/workflows/spellcheck.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
name: Spellcheck
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
pull_request:
|
||||||
|
branches: [master]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
spellcheck:
|
||||||
|
name: Typo CI (GitHub Action)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 4
|
||||||
|
if: "!contains(github.event.head_commit.message, '[ci skip]')"
|
||||||
|
steps:
|
||||||
|
- name: TypoCheck
|
||||||
|
uses: typoci/spellcheck-action@master
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
40
.typo-ci.yml
Normal file
40
.typo-ci.yml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# What language dictionaries should it use? Currently Typo CI supports:
|
||||||
|
# de
|
||||||
|
# en
|
||||||
|
# en_GB
|
||||||
|
# es
|
||||||
|
# fr
|
||||||
|
# it
|
||||||
|
# pt
|
||||||
|
# pt_BR
|
||||||
|
dictionaries:
|
||||||
|
- en
|
||||||
|
- en_GB
|
||||||
|
- de
|
||||||
|
|
||||||
|
# Any files/folders we should ignore?
|
||||||
|
excluded_files:
|
||||||
|
- ".codecov/*"
|
||||||
|
- ".github/*"
|
||||||
|
- "build/*"
|
||||||
|
- "k8s/*"
|
||||||
|
- "*.min.css"
|
||||||
|
- "*.css.map"
|
||||||
|
- "*.min.js"
|
||||||
|
- "*.js.map"
|
||||||
|
- "package-lock.json"
|
||||||
|
- "package.json"
|
||||||
|
- ".releaserc.js"
|
||||||
|
- ".typo-ci.yml"
|
||||||
|
- ".gitignore"
|
||||||
|
- "go.mod"
|
||||||
|
- "go.sum"
|
||||||
|
|
||||||
|
# Any typos we should ignore?
|
||||||
|
excluded_words:
|
||||||
|
- typoci
|
||||||
|
- idps
|
||||||
|
- ZITADEL's
|
||||||
|
|
||||||
|
# Would you like filenames to also be spellchecked?
|
||||||
|
spellcheck_filenames: false
|
11
CONTRIBUTING.md
Normal file
11
CONTRIBUTING.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# How to contribute to ZITADEL
|
||||||
|
|
||||||
|
## **Did you find a bug?**
|
||||||
|
|
||||||
|
## **Did you find a security flaw?**
|
||||||
|
|
||||||
|
* Please read [Security Policy](SECURITY.md).
|
||||||
|
|
||||||
|
## **Do you want to contribute to the ZITADEL documentation?**
|
||||||
|
|
||||||
|
* Please read [Contributing to the ZITADEL Documentation](site/CONTRIBUTING.md).
|
38
README.md
38
README.md
@@ -1,4 +1,4 @@
|
|||||||
<img src="./docs/img/zitadel-logo-dark@2x.png" alt="Zitadel Logo" height="100px" width="auto" />
|
<img src="./site/static/logos/zitadel-logo-dark@2x.png" alt="Zitadel Logo" height="100px" width="auto" />
|
||||||
|
|
||||||
[](https://github.com/semantic-release/semantic-release)
|
[](https://github.com/semantic-release/semantic-release)
|
||||||
[](https://github.com/caos/zitadel/actions)
|
[](https://github.com/caos/zitadel/actions)
|
||||||
@@ -7,48 +7,49 @@
|
|||||||
[](https://goreportcard.com/report/github.com/caos/zitadel)
|
[](https://goreportcard.com/report/github.com/caos/zitadel)
|
||||||
[](https://codecov.io/gh/caos/zitadel)
|
[](https://codecov.io/gh/caos/zitadel)
|
||||||
|
|
||||||
> This project is in a alpha state. The application will continue breaking until version 1.0.0 is released
|
> This project is in a beta state and API might still change a bit
|
||||||
|
|
||||||
## What Is It
|
## What Is It
|
||||||
|
|
||||||
`ZITADEL` is a Cloud Native Identity and Access Management solution. All server side components are written in `Go` and the management interface, called `Console`, is written in `Angular`.
|
**ZITADEL** is a "Cloud Native Identity and Access Management" solution. All server side components are written in [**Go**](https://golang.org/) and the management interface, called **Console**, is written in [**Angular**](https://angular.io/).
|
||||||
|
|
||||||
We optimized `ZITADEL` for the usage as `service provider IAM`. By `service provider` we think of companies who build services for e.g SaaS cases. Often these companies would like to use an IAM where they can register their application and grant other people or companies the right to self manage a set of roles within that application.
|
We optimized **ZITADEL** for the usage as "service provider" IAM. By "service provider" we think of companies who build services for e.g SaaS cases. Often these companies would like to use an IAM where they can register their application and grant other people or companies the right to self manage a set of roles within that application.
|
||||||
|
|
||||||
## How Does It Work
|
## How Does It Work
|
||||||
|
|
||||||
We built `ZITADEL` around the idea that the IAM should be easy to deploy and scale. That's why we tried to reduce external systems as much as possible.
|
We built **ZITADEL** around the idea that the IAM should be easy to deploy and scale. That's why we tried to reduce external systems as much as possible.
|
||||||
For example, `ZITADEL` is eventsourced but it does not rely on a pub/sub system to function. Instead we built all the functionality right into one binary.
|
For example, **ZITADEL** is event sourced but it does not rely on a pub/sub system to function. Instead we built all the functionality right into one binary.
|
||||||
`ZITADEL` only needs `Kubernetes` for orchestration and `CockroachDB` as storage.
|
**ZITADEL** only needs [**Kubernetes**](https://kubernetes.io/) for orchestration and [**CockroachDB**](https://www.cockroachlabs.com/) as storage.
|
||||||
|
|
||||||
## Why Another IAM
|
## Why Another IAM
|
||||||
|
|
||||||
In the past we already built a closed sourced IAM and tested multiple others. With most of them we had some issues, either technology, feature, pricing or transparency related in nature. For example we find the idea that security related features like `MFA` should not be hidden behind a paywall or a feature price.
|
In the past we already built a closed sourced IAM and tested multiple others. With most of them we had some issues, either technology, feature, pricing or transparency related in nature. For example we find the idea that security related features like **MFA** should not be hidden behind a paywall or a feature price.
|
||||||
One feature that we often missed, was a solid `audit trail` of all IAM resources. Most systems we saw so far either rely on simple log files or use a short retention for this.
|
One feature that we often missed, was a solid **audit trail** of all IAM resources. Most systems we saw so far either rely on simple log files or use a short retention for this.
|
||||||
|
|
||||||
## How To Use It
|
## How To Use It
|
||||||
|
|
||||||
### Use our free tier
|
### Use our free tier
|
||||||
|
|
||||||
Stay tuned, we will publish how you can register an organisation in our cloud offering `zitadel.ch` soon.
|
We provide a shared-cloud ZITADEL system where people can register there own organisation.
|
||||||
Yes we have a free tier!
|
Until end of 2020 we operator under a **early access** model where everything is free.
|
||||||
|
Go check it out under [zitadel.ch](https://zitadel.ch)
|
||||||
|
|
||||||
### Run your own IAM
|
### Run your own IAM
|
||||||
|
|
||||||
Stay tuned, we will soon publish a guide how you can deploy a `hyperconverged` system with our automation tooling called `ORBOS`.
|
Stay tuned, we will soon publish a guide how you can deploy a **hyperconverged** system with our automation tooling called [**ORBOS**](https://github.com/caos/orbos/).
|
||||||
With [ORBOS](https://github.com/caos/orbos/) you will be able to run `ZITADEL` on `GCE` or `StaticProvider` within 20 minutes. To achieve this, [ORBOS](https://github.com/caos/orbos/) will bootstrap and maintain a `Kubernetes` cluster, essential platform components (logging, metrics, ingress, ...), a secure `CockroachDB` cluster and `ZITADEL` itself.
|
With [**ORBOS**](https://github.com/caos/orbos/) you will be able to run [**Kubernetes**](https://kubernetes.io/) on **GCE** or **StaticProvider** within 20 minutes. To achieve this, [[**ORBOS**](https://github.com/caos/orbos/) will bootstrap and maintain a [**Kubernetes**](https://kubernetes.io/) cluster, essential platform components (logging, metrics, ingress, ...), a secure [**CockroachDB**](https://www.cockroachlabs.com/) cluster and **ZITADEL** itself.
|
||||||
|
|
||||||
The combination of the tools [ORBOS](https://github.com/caos/orbos/) and `ZITADEL` is what makes the operation easy and scalable.
|
The combination of the tools [**ORBOS**](https://github.com/caos/orbos/) and **ZITADEL** is what makes the operation easy and scalable.
|
||||||
|
|
||||||
See our progress [here](https://github.com/caos/orbos/pull/256)
|
|
||||||
|
|
||||||
## Give me some docs
|
## Give me some docs
|
||||||
|
|
||||||
This is work in progess but will change soon.
|
Have a look at our constantly evolving docs page [docs.zitadel.ch](https://docs.zitadel.ch).
|
||||||
|
|
||||||
## How To Contribute
|
## How To Contribute
|
||||||
|
|
||||||
TBA
|
Details need to be announced, but feel free to contribute already. As long as you are okay with accepting to contribute under this projects OSS [License](##License) you are fine.
|
||||||
|
|
||||||
|
We already have documentation specific [guidelines](./site/CONTRIBUTING.md).
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
@@ -59,3 +60,4 @@ See the policy [here](./SECURITY.md)
|
|||||||
See the exact licensing terms [here](./LICENSE)
|
See the exact licensing terms [here](./LICENSE)
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
|
@@ -1,5 +0,0 @@
|
|||||||
# Build
|
|
||||||
|
|
||||||
## Console
|
|
||||||
|
|
||||||
## Docker
|
|
@@ -23,52 +23,75 @@ Eventstore:
|
|||||||
MaxCacheSizeInByte: 10485760 #10mb
|
MaxCacheSizeInByte: 10485760 #10mb
|
||||||
|
|
||||||
SetUp:
|
SetUp:
|
||||||
GlobalOrg: 'Global'
|
Step1:
|
||||||
IAMProject: 'Zitadel'
|
GlobalOrg: 'Global'
|
||||||
DefaultLoginPolicy:
|
IAMProject: 'Zitadel'
|
||||||
AllowUsernamePassword: true
|
DefaultLoginPolicy:
|
||||||
AllowRegister: true
|
AllowUsernamePassword: true
|
||||||
AllowExternalIdp: true
|
AllowRegister: true
|
||||||
Orgs:
|
AllowExternalIdp: true
|
||||||
- Name: 'Global'
|
Orgs:
|
||||||
Domain: 'global.caos.ch'
|
- Name: 'Global'
|
||||||
Default: true
|
Domain: 'global.caos.ch'
|
||||||
OrgIamPolicy: true
|
Default: true
|
||||||
Users:
|
OrgIamPolicy: true
|
||||||
- FirstName: 'Global Org'
|
Users:
|
||||||
LastName: 'Administrator'
|
- FirstName: 'Global Org'
|
||||||
UserName: 'zitadel-global-org-admin@caos.ch'
|
LastName: 'Administrator'
|
||||||
Email: 'zitadel-global-org-admin@caos.ch'
|
UserName: 'zitadel-global-org-admin@caos.ch'
|
||||||
Password: 'Password1!'
|
Email: 'zitadel-global-org-admin@caos.ch'
|
||||||
Owners:
|
Password: 'Password1!'
|
||||||
- 'zitadel-global-org-admin@caos.ch'
|
Owners:
|
||||||
- Name: 'CAOS AG'
|
- 'zitadel-global-org-admin@caos.ch'
|
||||||
Domain: 'caos.ch'
|
- Name: 'CAOS AG'
|
||||||
Users:
|
Domain: 'caos.ch'
|
||||||
- FirstName: 'Zitadel'
|
Users:
|
||||||
LastName: 'Administrator'
|
- FirstName: 'Zitadel'
|
||||||
UserName: 'zitadel-admin'
|
LastName: 'Administrator'
|
||||||
Email: 'zitadel-admin@caos.ch'
|
UserName: 'zitadel-admin'
|
||||||
Password: 'Password1!'
|
Email: 'zitadel-admin@caos.ch'
|
||||||
Owners:
|
Password: 'Password1!'
|
||||||
- 'zitadel-admin@caos.ch'
|
Owners:
|
||||||
Projects:
|
- 'zitadel-admin@caos.ch'
|
||||||
- Name: 'Zitadel'
|
Projects:
|
||||||
OIDCApps:
|
- Name: 'Zitadel'
|
||||||
- Name: 'Management-API'
|
OIDCApps:
|
||||||
- Name: 'Auth-API'
|
- Name: 'Management-API'
|
||||||
- Name: 'Admin-API'
|
- Name: 'Auth-API'
|
||||||
- Name: 'Zitadel Console'
|
- Name: 'Admin-API'
|
||||||
RedirectUris:
|
- Name: 'Zitadel Console'
|
||||||
- '$ZITADEL_CONSOLE/auth/callback'
|
RedirectUris:
|
||||||
PostLogoutRedirectUris:
|
- '$ZITADEL_CONSOLE/auth/callback'
|
||||||
- '$ZITADEL_CONSOLE/signedout'
|
PostLogoutRedirectUris:
|
||||||
ResponseTypes:
|
- '$ZITADEL_CONSOLE/signedout'
|
||||||
- $ZITADEL_CONSOLE_RESPONSE_TYPE
|
ResponseTypes:
|
||||||
GrantTypes:
|
- $ZITADEL_CONSOLE_RESPONSE_TYPE
|
||||||
- $ZITADEL_CONSOLE_GRANT_TYPE
|
GrantTypes:
|
||||||
ApplicationType: 'USER_AGENT'
|
- $ZITADEL_CONSOLE_GRANT_TYPE
|
||||||
AuthMethodType: 'NONE'
|
ApplicationType: 'USER_AGENT'
|
||||||
DevMode: $ZITADEL_CONSOLE_DEV_MODE
|
AuthMethodType: 'NONE'
|
||||||
Owners:
|
DevMode: $ZITADEL_CONSOLE_DEV_MODE
|
||||||
- 'zitadel-admin@caos.ch'
|
Owners:
|
||||||
|
- 'zitadel-admin@caos.ch'
|
||||||
|
Step2:
|
||||||
|
DefaultPasswordComplexityPolicy:
|
||||||
|
MinLength: 8
|
||||||
|
HasLowercase: true
|
||||||
|
HasUppercase: true
|
||||||
|
HasSymbol: true
|
||||||
|
HasNumber: true
|
||||||
|
Step3:
|
||||||
|
DefaultPasswordAgePolicy:
|
||||||
|
MaxAgeDays: 0
|
||||||
|
ExpireWarnDays: 0
|
||||||
|
Step4:
|
||||||
|
DefaultPasswordLockoutPolicy:
|
||||||
|
MaxAttempts: 5
|
||||||
|
ShowLockOutFailures: false
|
||||||
|
Step5:
|
||||||
|
DefaultOrgIAMPolicy:
|
||||||
|
UserLoginMustBeDomain: true
|
||||||
|
Step6:
|
||||||
|
DefaultLabelPolicy:
|
||||||
|
PrimaryColor: '#222324'
|
||||||
|
SecondaryColor: '#ffffff'
|
||||||
|
@@ -52,28 +52,10 @@ SystemDefaults:
|
|||||||
EncryptionKeyID: $ZITADEL_OTP_VERIFICATION_KEY
|
EncryptionKeyID: $ZITADEL_OTP_VERIFICATION_KEY
|
||||||
VerificationLifetimes:
|
VerificationLifetimes:
|
||||||
PasswordCheck: 240h #10d
|
PasswordCheck: 240h #10d
|
||||||
|
ExternalLoginCheck: 240h #10d
|
||||||
MfaInitSkip: 720h #30d
|
MfaInitSkip: 720h #30d
|
||||||
MfaSoftwareCheck: 18h
|
MfaSoftwareCheck: 18h
|
||||||
MfaHardwareCheck: 12h
|
MfaHardwareCheck: 12h
|
||||||
DefaultPolicies:
|
|
||||||
Age:
|
|
||||||
Description: Standard age policy
|
|
||||||
MaxAgeDays: 365
|
|
||||||
ExpireWarnDays: 10
|
|
||||||
Complexity:
|
|
||||||
Description: Standard complexity policy
|
|
||||||
MinLength: 8
|
|
||||||
HasLowercase: true
|
|
||||||
HasUppercase: false
|
|
||||||
HasNumber: true
|
|
||||||
HasSymbol: true
|
|
||||||
Lockout:
|
|
||||||
Description: Standard lockout policy
|
|
||||||
MaxAttempts: 5
|
|
||||||
ShowLockOutFailures: true
|
|
||||||
OrgIam:
|
|
||||||
Description: Standard org policy
|
|
||||||
UserLoginMustBeDomain: true
|
|
||||||
IamID: 'IAM'
|
IamID: 'IAM'
|
||||||
DomainVerification:
|
DomainVerification:
|
||||||
VerificationKey:
|
VerificationKey:
|
||||||
|
@@ -26,7 +26,7 @@
|
|||||||
"assets": [
|
"assets": [
|
||||||
"src/favicon.ico",
|
"src/favicon.ico",
|
||||||
"src/assets",
|
"src/assets",
|
||||||
"src/manifest.webmanifest",
|
"src/manifest.webmanifest"
|
||||||
],
|
],
|
||||||
"styles": [
|
"styles": [
|
||||||
"src/styles.scss"
|
"src/styles.scss"
|
||||||
@@ -34,8 +34,9 @@
|
|||||||
"scripts": [],
|
"scripts": [],
|
||||||
"allowedCommonJsDependencies": [
|
"allowedCommonJsDependencies": [
|
||||||
"@angular/common/locales/de",
|
"@angular/common/locales/de",
|
||||||
"src/app/proto/generated/*.js",
|
"src/app/proto/generated/**",
|
||||||
"src/app/proto/generated/**/*.js"
|
"file-saver",
|
||||||
|
"qrcode"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
|
3593
console/package-lock.json
generated
3593
console/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -27,7 +27,7 @@
|
|||||||
"@types/file-saver": "^2.0.1",
|
"@types/file-saver": "^2.0.1",
|
||||||
"@types/uuid": "^8.3.0",
|
"@types/uuid": "^8.3.0",
|
||||||
"@types/google-protobuf": "^3.7.3",
|
"@types/google-protobuf": "^3.7.3",
|
||||||
"angularx-qrcode": "^10.0.10",
|
"angularx-qrcode": "^10.0.11",
|
||||||
"angular-oauth2-oidc": "^10.0.3",
|
"angular-oauth2-oidc": "^10.0.3",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"file-saver": "^2.0.2",
|
"file-saver": "^2.0.2",
|
||||||
@@ -35,34 +35,34 @@
|
|||||||
"google-protobuf": "^3.13.0",
|
"google-protobuf": "^3.13.0",
|
||||||
"grpc": "^1.24.3",
|
"grpc": "^1.24.3",
|
||||||
"grpc-web": "^1.2.1",
|
"grpc-web": "^1.2.1",
|
||||||
"moment": "^2.27.0",
|
"moment": "^2.29.1",
|
||||||
"ngx-moment": "^5.0.0",
|
"ngx-moment": "^5.0.0",
|
||||||
"ngx-quicklink": "^0.2.4",
|
"ngx-quicklink": "^0.2.4",
|
||||||
"rxjs": "~6.6.3",
|
"rxjs": "~6.6.3",
|
||||||
"ts-protoc-gen": "^0.12.0",
|
"ts-protoc-gen": "^0.13.0",
|
||||||
"tslib": "^2.0.1",
|
"tslib": "^2.0.3",
|
||||||
"uuid": "^8.3.0",
|
"uuid": "^8.3.1",
|
||||||
"zone.js": "~0.11.1"
|
"zone.js": "~0.11.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/build-angular": "~0.1000.8",
|
"@angular-devkit/build-angular": "~0.1002.0",
|
||||||
"@angular/cli": "~10.0.7",
|
"@angular/cli": "~10.2.0",
|
||||||
"@angular/compiler-cli": "~10.0.11",
|
"@angular/compiler-cli": "~10.0.11",
|
||||||
"@types/jasmine": "~3.5.13",
|
"@types/jasmine": "~3.6.0",
|
||||||
"@angular/language-service": "~10.1.0",
|
"@angular/language-service": "~10.2.0",
|
||||||
"@types/jasminewd2": "~2.0.3",
|
"@types/jasminewd2": "~2.0.3",
|
||||||
"@types/node": "^14.6.4",
|
"@types/node": "^14.14.3",
|
||||||
"codelyzer": "^6.0.0",
|
"codelyzer": "^6.0.1",
|
||||||
"jasmine-core": "~3.6.0",
|
"jasmine-core": "~3.6.0",
|
||||||
"jasmine-spec-reporter": "~5.0.0",
|
"jasmine-spec-reporter": "~6.0.0",
|
||||||
"karma": "~5.2.1",
|
"karma": "~5.2.3",
|
||||||
"karma-chrome-launcher": "~3.1.0",
|
"karma-chrome-launcher": "~3.1.0",
|
||||||
"karma-coverage-istanbul-reporter": "~3.0.2",
|
"karma-coverage-istanbul-reporter": "~3.0.2",
|
||||||
"karma-jasmine": "~4.0.1",
|
"karma-jasmine": "~4.0.1",
|
||||||
"karma-jasmine-html-reporter": "^1.5.0",
|
"karma-jasmine-html-reporter": "^1.5.0",
|
||||||
"prettier": "^2.1.1",
|
"prettier": "^2.1.2",
|
||||||
"protractor": "~7.0.0",
|
"protractor": "~7.0.0",
|
||||||
"stylelint": "^13.7.1",
|
"stylelint": "^13.7.2",
|
||||||
"stylelint-config-standard": "^20.0.0",
|
"stylelint-config-standard": "^20.0.0",
|
||||||
"stylelint-scss": "^3.18.0",
|
"stylelint-scss": "^3.18.0",
|
||||||
"ts-node": "~9.0.0",
|
"ts-node": "~9.0.0",
|
||||||
|
@@ -77,6 +77,38 @@ export const navAnimations: Array<AnimationTriggerMetadata> = [
|
|||||||
]),
|
]),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
export const enterAnimations: Array<AnimationTriggerMetadata> = [
|
||||||
|
trigger('appearfade', [
|
||||||
|
transition(':enter', [
|
||||||
|
style({
|
||||||
|
transform: 'scale(.9) translateY(-10%)',
|
||||||
|
opacity: 0,
|
||||||
|
}),
|
||||||
|
animate(
|
||||||
|
'100ms ease-in-out',
|
||||||
|
style({
|
||||||
|
transform: 'scale(1) translateY(0%)',
|
||||||
|
opacity: 1,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
transition(':leave', [
|
||||||
|
style({
|
||||||
|
transform: 'scale(1) translateY(0%)',
|
||||||
|
opacity: 1,
|
||||||
|
}),
|
||||||
|
animate(
|
||||||
|
'100ms ease-in-out',
|
||||||
|
style({
|
||||||
|
transform: 'scale(.9) translateY(-10%)',
|
||||||
|
opacity: 0,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
|
||||||
export const routeAnimations: AnimationTriggerMetadata = trigger('routeAnimations', [
|
export const routeAnimations: AnimationTriggerMetadata = trigger('routeAnimations', [
|
||||||
transition('HomePage => AddPage', [
|
transition('HomePage => AddPage', [
|
||||||
style({ transform: 'translateX(100%)', opacity: 0.5 }),
|
style({ transform: 'translateX(100%)', opacity: 0.5 }),
|
||||||
|
@@ -65,6 +65,14 @@ const routes: Routes = [
|
|||||||
roles: ['org.read'],
|
roles: ['org.read'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'grants',
|
||||||
|
loadChildren: () => import('./pages/grants/grants.module').then(m => m.GrantsModule),
|
||||||
|
canActivate: [AuthGuard, RoleGuard],
|
||||||
|
data: {
|
||||||
|
roles: ['user.grant.read'],
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'grant-create',
|
path: 'grant-create',
|
||||||
canActivate: [AuthGuard],
|
canActivate: [AuthGuard],
|
||||||
@@ -87,6 +95,24 @@ const routes: Routes = [
|
|||||||
roles: ['user.grant.write'],
|
roles: ['user.grant.write'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'user/:userid',
|
||||||
|
loadChildren: () => import('src/app/pages/user-grant-create/user-grant-create.module')
|
||||||
|
.then(m => m.UserGrantCreateModule),
|
||||||
|
canActivate: [RoleGuard],
|
||||||
|
data: {
|
||||||
|
roles: ['user.grant.write'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
loadChildren: () => import('src/app/pages/user-grant-create/user-grant-create.module')
|
||||||
|
.then(m => m.UserGrantCreateModule),
|
||||||
|
canActivate: [RoleGuard],
|
||||||
|
data: {
|
||||||
|
roles: ['user.grant.write'],
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<ng-container *ngIf="(authService.user | async) || {} as user">
|
<ng-container *ngIf="(authService.user | async) || {} as user">
|
||||||
<ng-container *ngIf="((['iam.read','iam.write'] | hasRole)) as iamuser$">
|
<ng-container *ngIf="((['iam.read$','iam.write$'] | hasRole)) as iamuser$">
|
||||||
<mat-toolbar class="root-header">
|
<mat-toolbar class="root-header">
|
||||||
<button aria-label="Toggle sidenav" mat-icon-button (click)="drawer.toggle()">
|
<button aria-label="Toggle sidenav" mat-icon-button (click)="drawer.toggle()">
|
||||||
<i class="icon las la-bars"></i>
|
<i class="icon las la-bars"></i>
|
||||||
@@ -12,22 +12,31 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<button (click)="loadOrgs()" *ngIf="profile?.id && org" mat-button
|
<button (click)="loadOrgs()" *ngIf="profile?.id && org" mat-button [matMenuTriggerFor]="menu"
|
||||||
[matMenuTriggerFor]="menu">{{org?.name ? org.name : 'NO NAME'}}
|
(menuOpened)="focusFilter()">{{org?.name ? org.name : 'NO NAME'}}
|
||||||
<mat-icon>
|
<mat-icon>
|
||||||
arrow_drop_down</mat-icon>
|
arrow_drop_down</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<mat-menu #menu="matMenu">
|
<mat-menu class="menu" #menu="matMenu">
|
||||||
<mat-progress-bar *ngIf="orgLoading" color="accent" mode="indeterminate"></mat-progress-bar>
|
<div class="spinner-w">
|
||||||
<button class="show-all" mat-menu-item
|
<mat-spinner diameter="20" *ngIf="orgLoading$ | async" color="accent">
|
||||||
[routerLink]="[ '/org/overview' ]">{{'MENU.SHOWORGS' | translate}}</button>
|
</mat-spinner>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<mat-form-field class="filter-form" appearance="fill">
|
||||||
|
<input matInput [formControl]="filterControl" autocomplete="off" (click)="$event.stopPropagation()"
|
||||||
|
placeholder="{{'ORG.PAGES.FILTERPLACEHOLDER' | translate}}" #input>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
<button [ngClass]="{'active': temporg.id === org?.id}" [disabled]="!temporg.id"
|
<button [ngClass]="{'active': temporg.id === org?.id}" [disabled]="!temporg.id"
|
||||||
*ngFor="let temporg of orgs" mat-menu-item (click)="setActiveOrg(temporg)">
|
*ngFor="let temporg of orgs$ | async" mat-menu-item (click)="setActiveOrg(temporg)">
|
||||||
{{temporg?.name ? temporg.name : 'NO NAME'}}
|
{{temporg?.name ? temporg.name : 'NO NAME'}}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button class="show-all" mat-menu-item
|
||||||
|
[routerLink]="[ '/org/overview' ]">{{'MENU.SHOWORGS' | translate}}</button>
|
||||||
|
|
||||||
<ng-template appHasRole [appHasRole]="['org.create','iam.write']">
|
<ng-template appHasRole [appHasRole]="['org.create','iam.write']">
|
||||||
<button mat-menu-item [routerLink]="[ '/org/create' ]">
|
<button mat-menu-item [routerLink]="[ '/org/create' ]">
|
||||||
<mat-icon class="avatar">add</mat-icon>
|
<mat-icon class="avatar">add</mat-icon>
|
||||||
@@ -129,6 +138,21 @@
|
|||||||
<span class="label">{{ 'MENU.MACHINEUSERS' | translate }}</span>
|
<span class="label">{{ 'MENU.MACHINEUSERS' | translate }}</span>
|
||||||
</a>
|
</a>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template appHasRole [appHasRole]="['user.grant.read(:[0-9]*)?']">
|
||||||
|
<div @navitem class="divider">
|
||||||
|
<div class="line"></div>
|
||||||
|
<span class="label">
|
||||||
|
{{ 'MENU.GRANTSECTION' | translate }}</span>
|
||||||
|
<div class="line"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a @navitem class="nav-item" [routerLinkActive]="['active']" [routerLink]="[ '/grants']"
|
||||||
|
[routerLinkActiveOptions]="{ exact: true }">
|
||||||
|
<i class="icon las la-shield-alt"></i>
|
||||||
|
<span class="label">{{ 'MENU.GRANTS' | translate }}</span>
|
||||||
|
</a>
|
||||||
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="fill-space"></span>
|
<span class="fill-space"></span>
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
@import '~@angular/material/theming';
|
||||||
|
|
||||||
.root-header {
|
.root-header {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -159,13 +160,6 @@
|
|||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.primary-button {
|
|
||||||
margin: 1rem;
|
|
||||||
border-radius: 1.5rem;
|
|
||||||
height: 2.5rem;
|
|
||||||
padding: 0 1rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
@@ -174,7 +168,7 @@
|
|||||||
|
|
||||||
.router {
|
.router {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,18 +208,50 @@
|
|||||||
margin: .5rem 0;
|
margin: .5rem 0;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
border: 1px solid #ffffff10;
|
border: 1px solid #81868a40;
|
||||||
padding: 2px 1rem;
|
padding: 2px 1rem;
|
||||||
border-radius: 50vw;
|
border-radius: 50vw;
|
||||||
color: #8795a1;
|
color: var(--grey);
|
||||||
font-size: 12px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.line {
|
.line {
|
||||||
display: block;
|
display: block;
|
||||||
background-color: #ffffff10;
|
background-color: #81868a40;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
margin: .5rem 0;
|
margin: .5rem 0;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@mixin textvar($theme) {
|
||||||
|
.filter-form {
|
||||||
|
margin: 0 .5rem;
|
||||||
|
/* stylelint-disable */
|
||||||
|
|
||||||
|
$foreground: map-get($theme, foreground);
|
||||||
|
color: mat-color($foreground, text) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-all {
|
||||||
|
$primary: map-get($theme, primary);
|
||||||
|
color: mat-color($primary, 300) !important;
|
||||||
|
border-bottom: 2px solid var(--grey);
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
}
|
||||||
|
/* stylelint-enable */
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.spinner-w {
|
||||||
|
top: 1rem;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,17 +1,24 @@
|
|||||||
import { BreakpointObserver } from '@angular/cdk/layout';
|
import { BreakpointObserver } from '@angular/cdk/layout';
|
||||||
import { OverlayContainer } from '@angular/cdk/overlay';
|
import { OverlayContainer } from '@angular/cdk/overlay';
|
||||||
import { ViewportScroller } from '@angular/common';
|
import { DOCUMENT, ViewportScroller } from '@angular/common';
|
||||||
import { Component, HostBinding, Inject, OnDestroy, ViewChild } from '@angular/core';
|
import { Component, ElementRef, HostBinding, Inject, OnDestroy, ViewChild } from '@angular/core';
|
||||||
|
import { FormControl } from '@angular/forms';
|
||||||
import { MatIconRegistry } from '@angular/material/icon';
|
import { MatIconRegistry } from '@angular/material/icon';
|
||||||
import { MatDrawer } from '@angular/material/sidenav';
|
import { MatDrawer } from '@angular/material/sidenav';
|
||||||
import { DomSanitizer } from '@angular/platform-browser';
|
import { DomSanitizer } from '@angular/platform-browser';
|
||||||
import { Router, RouterOutlet } from '@angular/router';
|
import { Router, RouterOutlet } from '@angular/router';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { LangChangeEvent, TranslateService } from '@ngx-translate/core';
|
||||||
import { Observable, of, Subscription } from 'rxjs';
|
import { BehaviorSubject, from, Observable, of, Subscription } from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { catchError, debounceTime, finalize, map, take } from 'rxjs/operators';
|
||||||
|
|
||||||
import { accountCard, navAnimations, routeAnimations, toolbarAnimation } from './animations';
|
import { accountCard, navAnimations, routeAnimations, toolbarAnimation } from './animations';
|
||||||
import { Org, UserProfileView } from './proto/generated/auth_pb';
|
import {
|
||||||
|
MyProjectOrgSearchKey,
|
||||||
|
MyProjectOrgSearchQuery,
|
||||||
|
Org,
|
||||||
|
SearchMethod,
|
||||||
|
UserProfileView,
|
||||||
|
} from './proto/generated/auth_pb';
|
||||||
import { AuthenticationService } from './services/authentication.service';
|
import { AuthenticationService } from './services/authentication.service';
|
||||||
import { GrpcAuthService } from './services/grpc-auth.service';
|
import { GrpcAuthService } from './services/grpc-auth.service';
|
||||||
import { ManagementService } from './services/mgmt.service';
|
import { ManagementService } from './services/mgmt.service';
|
||||||
@@ -32,6 +39,7 @@ import { UpdateService } from './services/update.service';
|
|||||||
})
|
})
|
||||||
export class AppComponent implements OnDestroy {
|
export class AppComponent implements OnDestroy {
|
||||||
@ViewChild('drawer') public drawer!: MatDrawer;
|
@ViewChild('drawer') public drawer!: MatDrawer;
|
||||||
|
@ViewChild('input', { static: false }) input!: ElementRef;
|
||||||
public isHandset$: Observable<boolean> = this.breakpointObserver
|
public isHandset$: Observable<boolean> = this.breakpointObserver
|
||||||
.observe('(max-width: 599px)')
|
.observe('(max-width: 599px)')
|
||||||
.pipe(map(result => {
|
.pipe(map(result => {
|
||||||
@@ -41,17 +49,18 @@ export class AppComponent implements OnDestroy {
|
|||||||
|
|
||||||
public showAccount: boolean = false;
|
public showAccount: boolean = false;
|
||||||
public org!: Org.AsObject;
|
public org!: Org.AsObject;
|
||||||
public orgs: Org.AsObject[] = [];
|
public orgs$: Observable<Org.AsObject[]> = of([]);
|
||||||
public profile!: UserProfileView.AsObject;
|
public profile!: UserProfileView.AsObject;
|
||||||
public isDarkTheme: Observable<boolean> = of(true);
|
public isDarkTheme: Observable<boolean> = of(true);
|
||||||
|
|
||||||
public orgLoading: boolean = false;
|
public orgLoading$: BehaviorSubject<any> = new BehaviorSubject(false);
|
||||||
|
|
||||||
public showProjectSection: boolean = false;
|
public showProjectSection: boolean = false;
|
||||||
|
|
||||||
public grantedProjectsCount: number = 0;
|
public grantedProjectsCount: number = 0;
|
||||||
public ownedProjectsCount: number = 0;
|
public ownedProjectsCount: number = 0;
|
||||||
|
|
||||||
|
public filterControl: FormControl = new FormControl('');
|
||||||
private authSub: Subscription = new Subscription();
|
private authSub: Subscription = new Subscription();
|
||||||
private orgSub: Subscription = new Subscription();
|
private orgSub: Subscription = new Subscription();
|
||||||
|
|
||||||
@@ -70,6 +79,7 @@ export class AppComponent implements OnDestroy {
|
|||||||
private toast: ToastService,
|
private toast: ToastService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
update: UpdateService,
|
update: UpdateService,
|
||||||
|
@Inject(DOCUMENT) private document: Document,
|
||||||
) {
|
) {
|
||||||
console.log('%cWait!', 'text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black; color: #5282c1; font-size: 50px');
|
console.log('%cWait!', 'text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black; color: #5282c1; font-size: 50px');
|
||||||
console.log('%cInserting something here could give attackers access to your zitadel account.', 'color: red; font-size: 18px');
|
console.log('%cInserting something here could give attackers access to your zitadel account.', 'color: red; font-size: 18px');
|
||||||
@@ -140,7 +150,6 @@ export class AppComponent implements OnDestroy {
|
|||||||
|
|
||||||
this.orgSub = this.authService.activeOrgChanged.subscribe(org => {
|
this.orgSub = this.authService.activeOrgChanged.subscribe(org => {
|
||||||
this.org = org;
|
this.org = org;
|
||||||
|
|
||||||
this.getProjectCount();
|
this.getProjectCount();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -160,6 +169,16 @@ export class AppComponent implements OnDestroy {
|
|||||||
|
|
||||||
this.isDarkTheme = this.themeService.isDarkTheme;
|
this.isDarkTheme = this.themeService.isDarkTheme;
|
||||||
this.isDarkTheme.subscribe(thema => this.onSetTheme(thema ? 'dark-theme' : 'light-theme'));
|
this.isDarkTheme.subscribe(thema => this.onSetTheme(thema ? 'dark-theme' : 'light-theme'));
|
||||||
|
|
||||||
|
this.translate.onLangChange.subscribe((language: LangChangeEvent) => {
|
||||||
|
this.document.documentElement.lang = language.lang;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.filterControl.valueChanges.pipe(debounceTime(300)).subscribe(value => {
|
||||||
|
this.loadOrgs(
|
||||||
|
value.trim().toLowerCase(),
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public ngOnDestroy(): void {
|
public ngOnDestroy(): void {
|
||||||
@@ -167,15 +186,26 @@ export class AppComponent implements OnDestroy {
|
|||||||
this.orgSub.unsubscribe();
|
this.orgSub.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
public loadOrgs(): void {
|
public loadOrgs(filter?: string): void {
|
||||||
this.orgLoading = true;
|
let query;
|
||||||
this.authService.SearchMyProjectOrgs(10, 0).then(res => {
|
if (filter) {
|
||||||
this.orgs = res.toObject().resultList;
|
query = new MyProjectOrgSearchQuery();
|
||||||
this.orgLoading = false;
|
query.setMethod(SearchMethod.SEARCHMETHOD_CONTAINS_IGNORE_CASE);
|
||||||
}).catch(error => {
|
query.setKey(MyProjectOrgSearchKey.MYPROJECTORGSEARCHKEY_ORG_NAME);
|
||||||
this.toast.showError(error);
|
query.setValue(filter);
|
||||||
this.orgLoading = false;
|
}
|
||||||
});
|
|
||||||
|
this.orgLoading$.next(true);
|
||||||
|
this.orgs$ = from(this.authService.SearchMyProjectOrgs(10, 0, query ? [query] : undefined)).pipe(
|
||||||
|
map(resp => {
|
||||||
|
return resp.toObject().resultList;
|
||||||
|
}),
|
||||||
|
catchError(() => of([])),
|
||||||
|
finalize(() => {
|
||||||
|
this.orgLoading$.next(false);
|
||||||
|
this.focusFilter();
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public prepareRoute(outlet: RouterOutlet): boolean {
|
public prepareRoute(outlet: RouterOutlet): boolean {
|
||||||
@@ -200,19 +230,24 @@ export class AppComponent implements OnDestroy {
|
|||||||
|
|
||||||
this.authService.user.subscribe(userprofile => {
|
this.authService.user.subscribe(userprofile => {
|
||||||
this.profile = userprofile;
|
this.profile = userprofile;
|
||||||
const lang = userprofile.preferredLanguage.match(/en|de/) ? userprofile.preferredLanguage : 'en';
|
const cropped = navigator.language.split('-')[0] ?? 'en';
|
||||||
|
const fallbackLang = cropped.match(/en|de/) ? cropped : 'en';
|
||||||
|
const lang = userprofile.preferredLanguage.match(/en|de/) ? userprofile.preferredLanguage : fallbackLang;
|
||||||
this.translate.use(lang);
|
this.translate.use(lang);
|
||||||
|
this.document.documentElement.lang = lang;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public setActiveOrg(org: Org.AsObject): void {
|
public setActiveOrg(org: Org.AsObject): void {
|
||||||
this.org = org;
|
this.org = org;
|
||||||
this.authService.setActiveOrg(org);
|
this.authService.setActiveOrg(org);
|
||||||
this.router.navigate(['/']);
|
this.authService.zitadelPermissionsChanged.pipe(take(1)).subscribe(() => {
|
||||||
|
this.router.navigate(['/']);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private getProjectCount(): void {
|
private getProjectCount(): void {
|
||||||
this.authService.isAllowed(['project.read']).subscribe((allowed) => {
|
this.authService.isAllowed(['project.read$']).subscribe((allowed) => {
|
||||||
if (allowed) {
|
if (allowed) {
|
||||||
this.mgmtService.SearchProjects(0, 0).then(res => {
|
this.mgmtService.SearchProjects(0, 0).then(res => {
|
||||||
this.ownedProjectsCount = res.toObject().totalResult;
|
this.ownedProjectsCount = res.toObject().totalResult;
|
||||||
@@ -224,5 +259,11 @@ export class AppComponent implements OnDestroy {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
focusFilter(): void {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.input.nativeElement.focus();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,13 +3,17 @@ import { CommonModule, registerLocaleData } from '@angular/common';
|
|||||||
import { HttpClient, HttpClientModule } from '@angular/common/http';
|
import { HttpClient, HttpClientModule } from '@angular/common/http';
|
||||||
import localeDe from '@angular/common/locales/de';
|
import localeDe from '@angular/common/locales/de';
|
||||||
import { APP_INITIALIZER, NgModule } from '@angular/core';
|
import { APP_INITIALIZER, NgModule } from '@angular/core';
|
||||||
|
import { ReactiveFormsModule } from '@angular/forms';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
import { MatNativeDateModule } from '@angular/material/core';
|
import { MatNativeDateModule } from '@angular/material/core';
|
||||||
import { MatDialogModule } from '@angular/material/dialog';
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { MatMenuModule } from '@angular/material/menu';
|
import { MatMenuModule } from '@angular/material/menu';
|
||||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||||
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
import { MatSidenavModule } from '@angular/material/sidenav';
|
||||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||||
@@ -21,7 +25,7 @@ import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
|||||||
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
|
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
|
||||||
import { AuthConfig, OAuthModule, OAuthStorage } from 'angular-oauth2-oidc';
|
import { AuthConfig, OAuthModule, OAuthStorage } from 'angular-oauth2-oidc';
|
||||||
import { QuicklinkModule } from 'ngx-quicklink';
|
import { QuicklinkModule } from 'ngx-quicklink';
|
||||||
import { RegExpPipeModule } from 'src/app/pipes/regexp-pipe.module';
|
import { RegExpPipeModule } from 'src/app/pipes/regexp-pipe/regexp-pipe.module';
|
||||||
|
|
||||||
import { environment } from '../environments/environment';
|
import { environment } from '../environments/environment';
|
||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
@@ -32,13 +36,15 @@ import { AccountsCardModule } from './modules/accounts-card/accounts-card.module
|
|||||||
import { AvatarModule } from './modules/avatar/avatar.module';
|
import { AvatarModule } from './modules/avatar/avatar.module';
|
||||||
import { WarnDialogModule } from './modules/warn-dialog/warn-dialog.module';
|
import { WarnDialogModule } from './modules/warn-dialog/warn-dialog.module';
|
||||||
import { SignedoutComponent } from './pages/signedout/signedout.component';
|
import { SignedoutComponent } from './pages/signedout/signedout.component';
|
||||||
import { HasRolePipeModule } from './pipes/has-role-pipe.module';
|
import { HasRolePipeModule } from './pipes/has-role-pipe/has-role-pipe.module';
|
||||||
import { GrpcAuthService } from './services/grpc-auth.service';
|
import { GrpcAuthService } from './services/grpc-auth.service';
|
||||||
import { GrpcService } from './services/grpc.service';
|
import { GrpcService } from './services/grpc.service';
|
||||||
import { AuthInterceptor } from './services/interceptors/auth.interceptor';
|
import { AuthInterceptor } from './services/interceptors/auth.interceptor';
|
||||||
import { GRPC_INTERCEPTORS } from './services/interceptors/grpc-interceptor';
|
import { GRPC_INTERCEPTORS } from './services/interceptors/grpc-interceptor';
|
||||||
|
import { I18nInterceptor } from './services/interceptors/i18n.interceptor';
|
||||||
import { OrgInterceptor } from './services/interceptors/org.interceptor';
|
import { OrgInterceptor } from './services/interceptors/org.interceptor';
|
||||||
import { RefreshService } from './services/refresh.service';
|
import { RefreshService } from './services/refresh.service';
|
||||||
|
import { SeoService } from './services/seo.service';
|
||||||
import { StatehandlerProcessorService, StatehandlerProcessorServiceImpl } from './services/statehandler-processor.service';
|
import { StatehandlerProcessorService, StatehandlerProcessorServiceImpl } from './services/statehandler-processor.service';
|
||||||
import { StatehandlerService, StatehandlerServiceImpl } from './services/statehandler.service';
|
import { StatehandlerService, StatehandlerServiceImpl } from './services/statehandler.service';
|
||||||
import { StorageService } from './services/storage.service';
|
import { StorageService } from './services/storage.service';
|
||||||
@@ -104,9 +110,13 @@ const authConfig: AuthConfig = {
|
|||||||
MatSidenavModule,
|
MatSidenavModule,
|
||||||
MatCardModule,
|
MatCardModule,
|
||||||
OutsideClickModule,
|
OutsideClickModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
MatInputModule,
|
||||||
HasRolePipeModule,
|
HasRolePipeModule,
|
||||||
MatProgressBarModule,
|
MatProgressBarModule,
|
||||||
|
MatProgressSpinnerModule,
|
||||||
MatToolbarModule,
|
MatToolbarModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
MatMenuModule,
|
MatMenuModule,
|
||||||
MatSnackBarModule,
|
MatSnackBarModule,
|
||||||
AvatarModule,
|
AvatarModule,
|
||||||
@@ -150,11 +160,17 @@ const authConfig: AuthConfig = {
|
|||||||
multi: true,
|
multi: true,
|
||||||
useClass: AuthInterceptor,
|
useClass: AuthInterceptor,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: GRPC_INTERCEPTORS,
|
||||||
|
multi: true,
|
||||||
|
useClass: I18nInterceptor,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
provide: GRPC_INTERCEPTORS,
|
provide: GRPC_INTERCEPTORS,
|
||||||
multi: true,
|
multi: true,
|
||||||
useClass: OrgInterceptor,
|
useClass: OrgInterceptor,
|
||||||
},
|
},
|
||||||
|
SeoService,
|
||||||
RefreshService,
|
RefreshService,
|
||||||
GrpcService,
|
GrpcService,
|
||||||
GrpcAuthService,
|
GrpcAuthService,
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
|
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
import { filter, first, switchMap } from 'rxjs/operators';
|
||||||
|
|
||||||
import { GrpcAuthService } from '../services/grpc-auth.service';
|
import { GrpcAuthService } from '../services/grpc-auth.service';
|
||||||
|
|
||||||
@@ -15,6 +16,11 @@ export class RoleGuard implements CanActivate {
|
|||||||
route: ActivatedRouteSnapshot,
|
route: ActivatedRouteSnapshot,
|
||||||
state: RouterStateSnapshot,
|
state: RouterStateSnapshot,
|
||||||
): Observable<boolean> {
|
): Observable<boolean> {
|
||||||
return this.authService.isAllowed(route.data['roles']);
|
return this.authService.fetchedZitadelPermissions.pipe(
|
||||||
|
filter((permissionsFetched) => !!permissionsFetched),
|
||||||
|
first(),
|
||||||
|
).pipe(
|
||||||
|
switchMap(_ => this.authService.isAllowed(route.data['roles'])),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
28
console/src/app/guards/user.guard.ts
Normal file
28
console/src/app/guards/user.guard.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { map, tap } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { GrpcAuthService } from '../services/grpc-auth.service';
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class UserGuard implements CanActivate {
|
||||||
|
constructor(private authService: GrpcAuthService, private router: Router) { }
|
||||||
|
|
||||||
|
public canActivate(
|
||||||
|
route: ActivatedRouteSnapshot,
|
||||||
|
state: RouterStateSnapshot,
|
||||||
|
): Observable<boolean> | Promise<boolean> | boolean {
|
||||||
|
return this.authService.user.pipe(
|
||||||
|
map(user => user.id !== route.params.id),
|
||||||
|
tap((isNotMe) => {
|
||||||
|
if (!isNotMe) {
|
||||||
|
this.router.navigate(['/users', 'me']);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -16,7 +16,7 @@
|
|||||||
</app-avatar>
|
</app-avatar>
|
||||||
|
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<span class="title">{{user.displayName ? user.displayName : user.userName}} </span>
|
<span class="user-title">{{user.displayName ? user.displayName : user.userName}} </span>
|
||||||
<span class="loginname">{{user.loginName}}</span>
|
<span class="loginname">{{user.loginName}}</span>
|
||||||
<span class="email">{{'USER.STATE.'+user.authState | translate}}</span>
|
<span class="email">{{'USER.STATE.'+user.authState | translate}}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<i class="las la-user-plus"></i>
|
<i class="las la-user-plus"></i>
|
||||||
</div>
|
</div>
|
||||||
<span class="col">
|
<span class="col">
|
||||||
<span class="title">{{'USER.ADDACCOUNT' | translate}}</span>
|
<span class="user-title">{{'USER.ADDACCOUNT' | translate}}</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="fill-space"></span>
|
<span class="fill-space"></span>
|
||||||
<mat-icon>keyboard_arrow_right</mat-icon>
|
<mat-icon>keyboard_arrow_right</mat-icon>
|
||||||
|
@@ -41,9 +41,11 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-top: 1px solid #ffffff30;
|
|
||||||
border-bottom: 1px solid #ffffff30;
|
|
||||||
padding: .5rem 0;
|
padding: .5rem 0;
|
||||||
|
max-height: 310px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border-top: 1px solid rgba(#8795a1, .3);
|
||||||
|
border-bottom: 1px solid rgba(#8795a1, .3);
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
padding: .5rem;
|
padding: .5rem;
|
||||||
@@ -84,7 +86,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
.title {
|
.user-title {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: .9rem;
|
font-size: .9rem;
|
||||||
line-height: 1rem;
|
line-height: 1rem;
|
||||||
@@ -92,7 +94,7 @@
|
|||||||
|
|
||||||
.email,
|
.email,
|
||||||
.loginname {
|
.loginname {
|
||||||
color: #8795a1;
|
color: var(--grey);
|
||||||
font-size: .8rem;
|
font-size: .8rem;
|
||||||
line-height: 1rem;
|
line-height: 1rem;
|
||||||
}
|
}
|
||||||
|
@@ -21,7 +21,9 @@ export class AccountsCardComponent implements OnInit {
|
|||||||
this.userService.getMyUserSessions().then(sessions => {
|
this.userService.getMyUserSessions().then(sessions => {
|
||||||
this.users = sessions.toObject().userSessionsList;
|
this.users = sessions.toObject().userSessionsList;
|
||||||
const index = this.users.findIndex(user => user.loginName === this.profile.preferredLoginName);
|
const index = this.users.findIndex(user => user.loginName === this.profile.preferredLoginName);
|
||||||
this.users.splice(index, 1);
|
if (index > -1) {
|
||||||
|
this.users.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
this.loadingUsers = false;
|
this.loadingUsers = false;
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
|
@@ -9,8 +9,9 @@
|
|||||||
<mat-form-field class="full-width" appearance="outline">
|
<mat-form-field class="full-width" appearance="outline">
|
||||||
<mat-label>{{ 'MEMBER.CREATIONTYPE' | translate }}</mat-label>
|
<mat-label>{{ 'MEMBER.CREATIONTYPE' | translate }}</mat-label>
|
||||||
<mat-select [(ngModel)]="creationType" (selectionChange)="loadRoles()">
|
<mat-select [(ngModel)]="creationType" (selectionChange)="loadRoles()">
|
||||||
<mat-option *ngFor="let type of creationTypes" [value]="type">
|
<mat-option *ngFor="let type of creationTypes" [value]="type.type"
|
||||||
{{ 'MEMBER.CREATIONTYPES.'+type | translate}}
|
[disabled]="(type.disabled$ | async) == false">
|
||||||
|
{{ 'MEMBER.CREATIONTYPES.'+type.type | translate}}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.desc {
|
.desc {
|
||||||
color: #8795a1;
|
color: var(--grey);
|
||||||
font-size: .9rem;
|
font-size: .9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,8 +18,4 @@
|
|||||||
.ok-button {
|
.ok-button {
|
||||||
margin-left: .5rem;
|
margin-left: .5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
|
||||||
border-radius: .5rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
import { Component, Inject } from '@angular/core';
|
import { Component, Inject } from '@angular/core';
|
||||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
import { ProjectGrantView, ProjectRole, ProjectView, UserView } from 'src/app/proto/generated/management_pb';
|
import { ProjectGrantView, ProjectRole, ProjectView, UserView } from 'src/app/proto/generated/management_pb';
|
||||||
import { AdminService } from 'src/app/services/admin.service';
|
import { AdminService } from 'src/app/services/admin.service';
|
||||||
|
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||||
import { ToastService } from 'src/app/services/toast.service';
|
import { ToastService } from 'src/app/services/toast.service';
|
||||||
|
|
||||||
@@ -23,13 +25,18 @@ export class MemberCreateDialogComponent {
|
|||||||
private grantId: string = '';
|
private grantId: string = '';
|
||||||
public preselectedUsers: Array<UserView.AsObject> = [];
|
public preselectedUsers: Array<UserView.AsObject> = [];
|
||||||
|
|
||||||
|
|
||||||
public creationType!: CreationType;
|
public creationType!: CreationType;
|
||||||
public creationTypes: CreationType[] = [
|
|
||||||
CreationType.IAM,
|
/**
|
||||||
CreationType.ORG,
|
* Specifies options for creating members,
|
||||||
CreationType.PROJECT_OWNED,
|
* without ending $, to enable write event permission even if user is allowed
|
||||||
CreationType.PROJECT_GRANTED,
|
* to create members for only one specific project.
|
||||||
|
*/
|
||||||
|
public creationTypes: Array<{ type: CreationType, disabled$: Observable<boolean>; }> = [
|
||||||
|
{ type: CreationType.IAM, disabled$: this.authService.isAllowed(['iam.member.write$']) },
|
||||||
|
{ type: CreationType.ORG, disabled$: this.authService.isAllowed(['org.member.write$']) },
|
||||||
|
{ type: CreationType.PROJECT_OWNED, disabled$: this.authService.isAllowed(['project.member.write']) },
|
||||||
|
{ type: CreationType.PROJECT_GRANTED, disabled$: this.authService.isAllowed(['project.grant.member.write']) },
|
||||||
];
|
];
|
||||||
public users: Array<UserView.AsObject> = [];
|
public users: Array<UserView.AsObject> = [];
|
||||||
public roles: Array<ProjectRole.AsObject> | string[] = [];
|
public roles: Array<ProjectRole.AsObject> | string[] = [];
|
||||||
@@ -41,6 +48,7 @@ export class MemberCreateDialogComponent {
|
|||||||
constructor(
|
constructor(
|
||||||
private mgmtService: ManagementService,
|
private mgmtService: ManagementService,
|
||||||
private adminService: AdminService,
|
private adminService: AdminService,
|
||||||
|
private authService: GrpcAuthService,
|
||||||
public dialogRef: MatDialogRef<MemberCreateDialogComponent>,
|
public dialogRef: MatDialogRef<MemberCreateDialogComponent>,
|
||||||
@Inject(MAT_DIALOG_DATA) public data: any,
|
@Inject(MAT_DIALOG_DATA) public data: any,
|
||||||
private toastService: ToastService,
|
private toastService: ToastService,
|
||||||
|
@@ -1,9 +1,11 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatChipsModule } from '@angular/material/chips';
|
||||||
import { MatDialogModule } from '@angular/material/dialog';
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { MatSelectModule } from '@angular/material/select';
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { SearchUserAutocompleteModule } from 'src/app/modules/search-user-autocomplete/search-user-autocomplete.module';
|
import { SearchUserAutocompleteModule } from 'src/app/modules/search-user-autocomplete/search-user-autocomplete.module';
|
||||||
@@ -21,10 +23,13 @@ import { MemberCreateDialogComponent } from './member-create-dialog.component';
|
|||||||
CommonModule,
|
CommonModule,
|
||||||
MatDialogModule,
|
MatDialogModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
|
MatChipsModule,
|
||||||
|
MatInputModule,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
MatFormFieldModule,
|
MatFormFieldModule,
|
||||||
MatSelectModule,
|
MatSelectModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
SearchUserAutocompleteModule,
|
SearchUserAutocompleteModule,
|
||||||
SearchRolesAutocompleteModule,
|
SearchRolesAutocompleteModule,
|
||||||
SearchProjectAutocompleteModule,
|
SearchProjectAutocompleteModule,
|
||||||
|
@@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
.desc {
|
.desc {
|
||||||
font-size: .9rem;
|
font-size: .9rem;
|
||||||
color: #8795a1;
|
color: var(--grey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
.card {
|
.card {
|
||||||
background-color: $primary-dark;
|
background-color: $primary-dark;
|
||||||
transition: background-color .4s ease-in-out;
|
transition: background-color .3s cubic-bezier(.645, .045, .355, 1);
|
||||||
border: 1px solid rgba($border-color, .2);
|
border: 1px solid rgba($border-color, .2);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-radius: .5rem;
|
border-radius: .5rem;
|
||||||
|
@@ -21,13 +21,13 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
.editor {
|
.editor {
|
||||||
color: #8795a1;
|
color: var(--grey);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.seq {
|
.seq {
|
||||||
color: #8795a1;
|
color: var(--grey);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
|
|
||||||
&.change-item-back {
|
&.change-item-back {
|
||||||
background-color: rgba($primary-dark, .93);
|
background-color: rgba($primary-dark, .93);
|
||||||
transition: background-color .4s ease-in-out;
|
transition: background-color .3s cubic-bezier(.645, .045, .355, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
|
|
||||||
.end-container {
|
.end-container {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #8795a1;
|
color: var(--grey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -103,11 +103,9 @@ export class ChangesComponent implements OnInit {
|
|||||||
this._loading.next(true);
|
this._loading.next(true);
|
||||||
|
|
||||||
return from(col).pipe(
|
return from(col).pipe(
|
||||||
|
take(1),
|
||||||
tap((res: Changes) => {
|
tap((res: Changes) => {
|
||||||
let values = res.toObject().changesList;
|
const values = res.toObject().changesList;
|
||||||
// If prepending, reverse the batch order
|
|
||||||
values = false ? values.reverse() : values;
|
|
||||||
|
|
||||||
// update source with new values, done loading
|
// update source with new values, done loading
|
||||||
this._data.next(values);
|
this._data.next(values);
|
||||||
|
|
||||||
@@ -118,12 +116,11 @@ export class ChangesComponent implements OnInit {
|
|||||||
this._done.next(true);
|
this._done.next(true);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
catchError(err => {
|
catchError(_ => {
|
||||||
this._loading.next(false);
|
this._loading.next(false);
|
||||||
this.bottom = true;
|
this.bottom = true;
|
||||||
return of([]);
|
return of([]);
|
||||||
}),
|
}),
|
||||||
take(1),
|
|
||||||
).subscribe();
|
).subscribe();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,9 +4,9 @@ import { NgModule } from '@angular/core';
|
|||||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { ScrollableModule } from 'src/app/directives/scrollable/scrollable.module';
|
import { ScrollableModule } from 'src/app/directives/scrollable/scrollable.module';
|
||||||
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe.module';
|
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
|
||||||
import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe.module';
|
import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe/localized-date-pipe.module';
|
||||||
import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe.module';
|
import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe/timestamp-to-date-pipe.module';
|
||||||
|
|
||||||
import { ChangesComponent } from './changes.component';
|
import { ChangesComponent } from './changes.component';
|
||||||
|
|
||||||
|
@@ -8,13 +8,19 @@
|
|||||||
<ng-container *ngIf="totalResult < 10; else compact">
|
<ng-container *ngIf="totalResult < 10; else compact">
|
||||||
<ng-container *ngFor="let member of membersSubject | async; index as i">
|
<ng-container *ngFor="let member of membersSubject | async; index as i">
|
||||||
<div @animate (click)="emitShowDetail()" class="avatar-circle"
|
<div @animate (click)="emitShowDetail()" class="avatar-circle"
|
||||||
matTooltip="{{ member.email }} | {{member.rolesList?.join(' ')}}"
|
matTooltip="{{ member.displayName }} | {{member.rolesList?.join(' ')}}"
|
||||||
[ngStyle]="{'z-index': 100 - i}">
|
[ngStyle]="{'z-index': 100 - i}">
|
||||||
<app-avatar *ngIf="member && (member.displayName || (member.firstName && member.lastName))"
|
<app-avatar
|
||||||
|
*ngIf="member && member.displayName && member.firstName && member.lastName; else cog"
|
||||||
class="avatar dontcloseonclick"
|
class="avatar dontcloseonclick"
|
||||||
[name]="member.displayName ? member.displayName : (member.firstName + ' '+ member.lastName)"
|
[name]="member.displayName ? member.displayName : (member.firstName + ' '+ member.lastName)"
|
||||||
[size]="32">
|
[size]="32">
|
||||||
</app-avatar>
|
</app-avatar>
|
||||||
|
<ng-template #cog>
|
||||||
|
<div class="sa-icon">
|
||||||
|
<i class="las la-user-cog"></i>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
.sub-header {
|
.sub-header {
|
||||||
font-size: .8rem;
|
font-size: .8rem;
|
||||||
color: #8795a1;
|
color: var(--grey);
|
||||||
}
|
}
|
||||||
|
|
||||||
.people {
|
.people {
|
||||||
@@ -65,6 +65,17 @@
|
|||||||
.avatar {
|
.avatar {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sa-icon {
|
||||||
|
display: block;
|
||||||
|
width: 32px;
|
||||||
|
margin: 0 .5rem;
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin: auto;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.margin-neg {
|
.margin-neg {
|
||||||
|
@@ -1,14 +1,16 @@
|
|||||||
<div class="max-width-container detail-container">
|
<div class="max-width-container">
|
||||||
<div class="detail-left">
|
<div class="detail-container">
|
||||||
<a *ngIf="backRouterLink" [routerLink]="backRouterLink" mat-icon-button>
|
<div class="detail-left">
|
||||||
<mat-icon class="icon">arrow_back</mat-icon>
|
<a *ngIf="backRouterLink" [routerLink]="backRouterLink" mat-icon-button>
|
||||||
</a>
|
<mat-icon class="icon">arrow_back</mat-icon>
|
||||||
</div>
|
</a>
|
||||||
<div class="detail-right">
|
</div>
|
||||||
<div class="head">
|
<div class="detail-right">
|
||||||
<h1>{{ title }}</h1>
|
<div class="head">
|
||||||
<p class="desc">{{ description }}</p>
|
<h1>{{ title }}</h1>
|
||||||
<ng-content></ng-content>
|
<p class="desc">{{ description }}</p>
|
||||||
|
<ng-content></ng-content>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@@ -10,9 +10,9 @@
|
|||||||
$lighter-color: rgba(mat-color($primary, 300), .5);
|
$lighter-color: rgba(mat-color($primary, 300), .5);
|
||||||
|
|
||||||
.detail-container {
|
.detail-container {
|
||||||
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
padding-bottom: 3rem;
|
padding-bottom: 3rem;
|
||||||
padding-top: 3rem;
|
|
||||||
|
|
||||||
.detail-left {
|
.detail-left {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
@@ -29,7 +29,6 @@
|
|||||||
|
|
||||||
.detail-right {
|
.detail-right {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
position: relative;
|
|
||||||
padding-left: 1rem;
|
padding-left: 1rem;
|
||||||
|
|
||||||
@media only screen and (max-width: 500px) {
|
@media only screen and (max-width: 500px) {
|
||||||
@@ -37,20 +36,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.head {
|
.head {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
flex-wrap: wrap;
|
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 1.2rem;
|
font-size: 1.5rem;
|
||||||
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.desc {
|
.desc {
|
||||||
width: 100%;
|
|
||||||
display: block;
|
display: block;
|
||||||
font-size: .9rem;
|
font-size: .9rem;
|
||||||
color: #8795a1;
|
color: var(--grey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,6 +11,8 @@
|
|||||||
<h1>{{'IDP.CREATE.TITLE' | translate}}</h1>
|
<h1>{{'IDP.CREATE.TITLE' | translate}}</h1>
|
||||||
<p>{{'IDP.CREATE.DESCRIPTION' | translate}}</p>
|
<p>{{'IDP.CREATE.DESCRIPTION' | translate}}</p>
|
||||||
|
|
||||||
|
<mat-progress-bar *ngIf="loading" color="primary" mode="indeterminate"></mat-progress-bar>
|
||||||
|
|
||||||
<form (ngSubmit)="addIdp()">
|
<form (ngSubmit)="addIdp()">
|
||||||
<ng-container [formGroup]="formGroup">
|
<ng-container [formGroup]="formGroup">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
@@ -48,22 +50,22 @@
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<mat-form-field class="formfield" appearance="outline">
|
<mat-form-field class="formfield" appearance="outline">
|
||||||
<mat-label>{{ 'IDP.IDPDISPLAYNAMMAPPING' | translate }}</mat-label>
|
<mat-label>{{ 'IDP.IDPDISPLAYNAMMAPPING' | translate }}</mat-label>
|
||||||
<mat-select formControlName="idpDisplayNameMapping">
|
<mat-select formControlName="idpDisplayNameMapping">
|
||||||
<mat-option *ngFor="let field of mappingFields" [value]="field">
|
<mat-option *ngFor="let field of mappingFields" [value]="field">
|
||||||
{{ 'IDP.MAPPINTFIELD.'+field | translate }}
|
{{ 'IDP.MAPPINTFIELD.'+field | translate }}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field class="formfield" appearance="outline">
|
<mat-form-field class="formfield" appearance="outline">
|
||||||
<mat-label>{{ 'IDP.USERNAMEMAPPING' | translate }}</mat-label>
|
<mat-label>{{ 'IDP.USERNAMEMAPPING' | translate }}</mat-label>
|
||||||
<mat-select formControlName="usernameMapping">
|
<mat-select formControlName="usernameMapping">
|
||||||
<mat-option *ngFor="let field of mappingFields" [value]="field">
|
<mat-option *ngFor="let field of mappingFields" [value]="field">
|
||||||
{{ 'IDP.MAPPINTFIELD.'+field | translate }}
|
{{ 'IDP.MAPPINTFIELD.'+field | translate }}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
@@ -71,4 +73,4 @@
|
|||||||
{{ 'ACTIONS.SAVE' | translate }}
|
{{ 'ACTIONS.SAVE' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
@@ -25,7 +25,6 @@
|
|||||||
|
|
||||||
.add-line-btn {
|
.add-line-btn {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
border-radius: .5rem;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +37,7 @@
|
|||||||
flex-basis: 100%;
|
flex-basis: 100%;
|
||||||
margin: 0 .5rem;
|
margin: 0 .5rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
color: #8795a1;
|
color: var(--grey);
|
||||||
}
|
}
|
||||||
|
|
||||||
.formfield {
|
.formfield {
|
||||||
@@ -55,7 +54,6 @@
|
|||||||
margin-top: 3rem;
|
margin-top: 3rem;
|
||||||
display: block;
|
display: block;
|
||||||
padding: .5rem 4rem;
|
padding: .5rem 4rem;
|
||||||
border-radius: .5rem;
|
|
||||||
|
|
||||||
@media only screen and (max-width: 450px) {
|
@media only screen and (max-width: 450px) {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
|
@@ -7,18 +7,18 @@ import { ActivatedRoute, Params, Router } from '@angular/router';
|
|||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { take } from 'rxjs/operators';
|
import { take } from 'rxjs/operators';
|
||||||
import {
|
import {
|
||||||
OidcIdpConfigCreate as AdminOidcIdpConfigCreate,
|
OidcIdpConfigCreate as AdminOidcIdpConfigCreate,
|
||||||
OIDCMappingField as authMappingFields,
|
OIDCMappingField as authMappingFields,
|
||||||
} from 'src/app/proto/generated/admin_pb';
|
} from 'src/app/proto/generated/admin_pb';
|
||||||
import { AdminService } from 'src/app/services/admin.service';
|
import { AdminService } from 'src/app/services/admin.service';
|
||||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||||
import { ToastService } from 'src/app/services/toast.service';
|
import { ToastService } from 'src/app/services/toast.service';
|
||||||
|
|
||||||
import { PolicyComponentServiceType } from '../policies/policy-component-types.enum';
|
|
||||||
import {
|
import {
|
||||||
OidcIdpConfigCreate as MgmtOidcIdpConfigCreate,
|
OidcIdpConfigCreate as MgmtOidcIdpConfigCreate,
|
||||||
OIDCMappingField as mgmtMappingFields,
|
OIDCMappingField as mgmtMappingFields,
|
||||||
} from '../../proto/generated/management_pb';
|
} from '../../proto/generated/management_pb';
|
||||||
|
import { PolicyComponentServiceType } from '../policies/policy-component-types.enum';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-idp-create',
|
selector: 'app-idp-create',
|
||||||
@@ -37,7 +37,7 @@ export class IdpCreateComponent implements OnInit, OnDestroy {
|
|||||||
public formGroup!: FormGroup;
|
public formGroup!: FormGroup;
|
||||||
public createSteps: number = 1;
|
public createSteps: number = 1;
|
||||||
public currentCreateStep: number = 1;
|
public currentCreateStep: number = 1;
|
||||||
|
public loading: boolean = false;
|
||||||
constructor(
|
constructor(
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
@@ -47,7 +47,6 @@ export class IdpCreateComponent implements OnInit, OnDestroy {
|
|||||||
) {
|
) {
|
||||||
this.formGroup = new FormGroup({
|
this.formGroup = new FormGroup({
|
||||||
name: new FormControl('', [Validators.required]),
|
name: new FormControl('', [Validators.required]),
|
||||||
logoSrc: new FormControl('', []),
|
|
||||||
clientId: new FormControl('', [Validators.required]),
|
clientId: new FormControl('', [Validators.required]),
|
||||||
clientSecret: new FormControl('', [Validators.required]),
|
clientSecret: new FormControl('', [Validators.required]),
|
||||||
issuer: new FormControl('', [Validators.required]),
|
issuer: new FormControl('', [Validators.required]),
|
||||||
@@ -68,8 +67,8 @@ export class IdpCreateComponent implements OnInit, OnDestroy {
|
|||||||
case PolicyComponentServiceType.ADMIN:
|
case PolicyComponentServiceType.ADMIN:
|
||||||
this.service = this.injector.get(AdminService as Type<AdminService>);
|
this.service = this.injector.get(AdminService as Type<AdminService>);
|
||||||
this.mappingFields = [
|
this.mappingFields = [
|
||||||
authMappingFields.OIDCMAPPINGFIELD_PREFERRED_USERNAME,
|
authMappingFields.OIDCMAPPINGFIELD_PREFERRED_USERNAME,
|
||||||
authMappingFields.OIDCMAPPINGFIELD_EMAIL];
|
authMappingFields.OIDCMAPPINGFIELD_EMAIL];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -91,25 +90,30 @@ export class IdpCreateComponent implements OnInit, OnDestroy {
|
|||||||
let req: AdminOidcIdpConfigCreate | MgmtOidcIdpConfigCreate;
|
let req: AdminOidcIdpConfigCreate | MgmtOidcIdpConfigCreate;
|
||||||
|
|
||||||
switch (this.serviceType) {
|
switch (this.serviceType) {
|
||||||
case PolicyComponentServiceType.MGMT:
|
case PolicyComponentServiceType.MGMT:
|
||||||
req = new MgmtOidcIdpConfigCreate();
|
req = new MgmtOidcIdpConfigCreate();
|
||||||
break;
|
break;
|
||||||
case PolicyComponentServiceType.ADMIN:
|
case PolicyComponentServiceType.ADMIN:
|
||||||
req = new AdminOidcIdpConfigCreate();
|
req = new AdminOidcIdpConfigCreate();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
req.setName(this.name?.value);
|
req.setName(this.name?.value);
|
||||||
req.setClientId(this.clientId?.value);
|
req.setClientId(this.clientId?.value);
|
||||||
req.setClientSecret(this.clientSecret?.value);
|
req.setClientSecret(this.clientSecret?.value);
|
||||||
req.setIssuer(this.issuer?.value);
|
req.setIssuer(this.issuer?.value);
|
||||||
req.setLogoSrc(this.logoSrc?.value);
|
|
||||||
req.setScopesList(this.scopesList?.value);
|
req.setScopesList(this.scopesList?.value);
|
||||||
req.setIdpDisplayNameMapping(this.idpDisplayNameMapping?.value);
|
req.setIdpDisplayNameMapping(this.idpDisplayNameMapping?.value);
|
||||||
req.setUsernameMapping(this.usernameMapping?.value);
|
req.setUsernameMapping(this.usernameMapping?.value);
|
||||||
|
this.loading = true;
|
||||||
this.service.CreateOidcIdp(req).then((idp) => {
|
this.service.CreateOidcIdp(req).then((idp) => {
|
||||||
this.router.navigate(['idp', idp.getId()]);
|
setTimeout(() => {
|
||||||
|
this.loading = false;
|
||||||
|
this.router.navigate([
|
||||||
|
this.serviceType === PolicyComponentServiceType.MGMT ? 'org' :
|
||||||
|
this.serviceType === PolicyComponentServiceType.ADMIN ? 'iam' : '',
|
||||||
|
'idp', idp.getId()]);
|
||||||
|
}, 2000);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
this.toast.showError(error);
|
this.toast.showError(error);
|
||||||
});
|
});
|
||||||
@@ -147,10 +151,6 @@ export class IdpCreateComponent implements OnInit, OnDestroy {
|
|||||||
return this.formGroup.get('name');
|
return this.formGroup.get('name');
|
||||||
}
|
}
|
||||||
|
|
||||||
public get logoSrc(): AbstractControl | null {
|
|
||||||
return this.formGroup.get('logoSrc');
|
|
||||||
}
|
|
||||||
|
|
||||||
public get clientId(): AbstractControl | null {
|
public get clientId(): AbstractControl | null {
|
||||||
return this.formGroup.get('clientId');
|
return this.formGroup.get('clientId');
|
||||||
}
|
}
|
||||||
@@ -163,7 +163,7 @@ export class IdpCreateComponent implements OnInit, OnDestroy {
|
|||||||
return this.formGroup.get('issuer');
|
return this.formGroup.get('issuer');
|
||||||
}
|
}
|
||||||
public get scopesList(): AbstractControl | null {
|
public get scopesList(): AbstractControl | null {
|
||||||
return this.formGroup.get('scopesList');
|
return this.formGroup.get('scopesList');
|
||||||
}
|
}
|
||||||
|
|
||||||
public get idpDisplayNameMapping(): AbstractControl | null {
|
public get idpDisplayNameMapping(): AbstractControl | null {
|
||||||
@@ -171,7 +171,7 @@ export class IdpCreateComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public get usernameMapping(): AbstractControl | null {
|
public get usernameMapping(): AbstractControl | null {
|
||||||
return this.formGroup.get('usernameMapping');
|
return this.formGroup.get('usernameMapping');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -6,12 +6,13 @@ import { MatChipsModule } from '@angular/material/chips';
|
|||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||||
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
import { IdpCreateRoutingModule } from './idp-create-routing.module';
|
import { IdpCreateRoutingModule } from './idp-create-routing.module';
|
||||||
import { IdpCreateComponent } from './idp-create.component';
|
import { IdpCreateComponent } from './idp-create.component';
|
||||||
import {MatSelectModule} from '@angular/material/select';
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [IdpCreateComponent],
|
declarations: [IdpCreateComponent],
|
||||||
@@ -28,6 +29,7 @@ import {MatSelectModule} from '@angular/material/select';
|
|||||||
MatChipsModule,
|
MatChipsModule,
|
||||||
MatTooltipModule,
|
MatTooltipModule,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
|
MatProgressBarModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class IdpCreateModule { }
|
export class IdpCreateModule { }
|
||||||
|
@@ -1,19 +1,19 @@
|
|||||||
<app-refresh-table [loading]="loading$ | async" (refreshed)="refreshPage()" [dataSize]="dataSource.data.length"
|
<app-refresh-table [loading]="loading$ | async" (refreshed)="refreshPage()" [dataSize]="dataSource.data.length"
|
||||||
[timestamp]="idpResult?.viewTimestamp" [selection]="selection">
|
[emitRefreshOnPreviousRoutes]="['/iam/idp/create']" [timestamp]="idpResult?.viewTimestamp" [selection]="selection">
|
||||||
<ng-template appHasRole [appHasRole]="['iam.write']" actions>
|
<ng-template appHasRole [appHasRole]="['iam.write']" actions>
|
||||||
<button (click)="deactivateSelectedIdps()" matTooltip="{{'IDP.DEACTIVATE' | translate}}" class="icon-button"
|
<button (click)="deactivateSelectedIdps()" matTooltip="{{'IDP.DEACTIVATE' | translate}}" class="icon-button"
|
||||||
mat-icon-button *ngIf="selection.hasValue()" [disabled]="disabled">
|
mat-icon-button *ngIf="selection.hasValue()" [disabled]="disabled">
|
||||||
<mat-icon svgIcon="mdi_account_cancel"></mat-icon>
|
<mat-icon>block</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<button (click)="reactivateSelectedIdps()" matTooltip="{{'IDP.ACTIVATE' | translate}}" class="icon-button"
|
<button (click)="reactivateSelectedIdps()" matTooltip="{{'IDP.ACTIVATE' | translate}}" class="icon-button"
|
||||||
mat-icon-button *ngIf="selection.hasValue()" [disabled]="disabled">
|
mat-icon-button *ngIf="selection.hasValue()" [disabled]="disabled">
|
||||||
<mat-icon svgIcon="mdi_account_check_outline"></mat-icon>
|
<mat-icon>play_circle_outline</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<button color="warn" (click)="removeSelectedIdps()" matTooltip="{{'IDP.DELETE' | translate}}"
|
<button color="warn" (click)="removeSelectedIdps()" matTooltip="{{'IDP.DELETE' | translate}}"
|
||||||
class="icon-button" mat-icon-button *ngIf="selection.hasValue()" [disabled]="disabled">
|
class="icon-button" mat-icon-button *ngIf="selection.hasValue()" [disabled]="disabled">
|
||||||
<i class="las la-trash"></i>
|
<i class="las la-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
<a class="add-button" [routerLink]="createRouterLink" color="primary" mat-raised-button [disabled]="disabled">
|
<a [routerLink]="createRouterLink" color="primary" mat-raised-button [disabled]="disabled">
|
||||||
<mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }}
|
<mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }}
|
||||||
</a>
|
</a>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@@ -24,14 +24,14 @@
|
|||||||
<th mat-header-cell *matHeaderCellDef>
|
<th mat-header-cell *matHeaderCellDef>
|
||||||
<mat-checkbox color="primary" (change)="$event ? masterToggle() : null"
|
<mat-checkbox color="primary" (change)="$event ? masterToggle() : null"
|
||||||
[checked]="selection.hasValue() && isAllSelected()"
|
[checked]="selection.hasValue() && isAllSelected()"
|
||||||
[indeterminate]="selection.hasValue() && !isAllSelected()">
|
[indeterminate]="selection.hasValue() && !isAllSelected()"
|
||||||
|
[disabled]="serviceType==PolicyComponentServiceType.MGMT">
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
</th>
|
</th>
|
||||||
<td mat-cell *matCellDef="let idp">
|
<td mat-cell *matCellDef="let idp">
|
||||||
<mat-checkbox color="primary" (click)="$event.stopPropagation()"
|
<mat-checkbox color="primary" (click)="$event.stopPropagation()"
|
||||||
|
[disabled]="serviceType==PolicyComponentServiceType.MGMT && idp?.providerType == IdpProviderType.IDPPROVIDERTYPE_SYSTEM"
|
||||||
(change)="$event ? selection.toggle(idp) : null" [checked]="selection.isSelected(idp)">
|
(change)="$event ? selection.toggle(idp) : null" [checked]="selection.isSelected(idp)">
|
||||||
<img *ngIf="idp?.logoSrc?.startsWith('https://'); else genAvatar" [src]="idp.logoSrc"
|
|
||||||
alt="ipp logo {{idp?.name}}" />
|
|
||||||
<ng-template #genAvatar>
|
<ng-template #genAvatar>
|
||||||
<div class="avatar">
|
<div class="avatar">
|
||||||
<span>{{idp.name.charAt(0)}}</span>
|
<span>{{idp.name.charAt(0)}}</span>
|
||||||
@@ -43,12 +43,12 @@
|
|||||||
|
|
||||||
<ng-container matColumnDef="name">
|
<ng-container matColumnDef="name">
|
||||||
<th mat-header-cell *matHeaderCellDef> {{ 'IDP.NAME' | translate }} </th>
|
<th mat-header-cell *matHeaderCellDef> {{ 'IDP.NAME' | translate }} </th>
|
||||||
<td mat-cell *matCellDef="let idp"> {{idp?.name}} </td>
|
<td [routerLink]="routerLinkForRow(idp)" mat-cell *matCellDef="let idp"> {{idp?.name}} </td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="config">
|
<ng-container matColumnDef="config">
|
||||||
<th mat-header-cell *matHeaderCellDef> {{ 'IDP.CONFIG' | translate }} </th>
|
<th mat-header-cell *matHeaderCellDef> {{ 'IDP.CONFIG' | translate }} </th>
|
||||||
<td mat-cell *matCellDef="let idp">
|
<td [routerLink]="routerLinkForRow(idp)" mat-cell *matCellDef="let idp">
|
||||||
<div *ngFor="let elem of idp?.oidcConfig | keyvalue" class="flex-row">
|
<div *ngFor="let elem of idp?.oidcConfig | keyvalue" class="flex-row">
|
||||||
<span class="key">{{elem.key}}:</span>
|
<span class="key">{{elem.key}}:</span>
|
||||||
<span class="value">{{elem.value}}</span>
|
<span class="value">{{elem.value}}</span>
|
||||||
@@ -58,28 +58,47 @@
|
|||||||
|
|
||||||
<ng-container matColumnDef="state">
|
<ng-container matColumnDef="state">
|
||||||
<th mat-header-cell *matHeaderCellDef> {{ 'IDP.STATE' | translate }} </th>
|
<th mat-header-cell *matHeaderCellDef> {{ 'IDP.STATE' | translate }} </th>
|
||||||
<td mat-cell *matCellDef="let idp"> {{ 'IDP.STATES.'+idp.state | translate }} </td>
|
<td [routerLink]="routerLinkForRow(idp)" mat-cell *matCellDef="let idp">
|
||||||
|
{{ 'IDP.STATES.'+idp.state | translate }} </td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="creationDate">
|
<ng-container matColumnDef="creationDate">
|
||||||
<th mat-header-cell *matHeaderCellDef> {{ 'IDP.CREATIONDATE' | translate }} </th>
|
<th mat-header-cell *matHeaderCellDef> {{ 'IDP.CREATIONDATE' | translate }} </th>
|
||||||
<td class="pointer" mat-cell *matCellDef="let idp">
|
<td [routerLink]="routerLinkForRow(idp)" class="pointer" mat-cell *matCellDef="let idp">
|
||||||
{{idp.creationDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }} </td>
|
{{idp.creationDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }} </td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="changeDate">
|
<ng-container matColumnDef="changeDate">
|
||||||
<th mat-header-cell *matHeaderCellDef> {{ 'IDP.CHANGEDATE' | translate }} </th>
|
<th mat-header-cell *matHeaderCellDef> {{ 'IDP.CHANGEDATE' | translate }} </th>
|
||||||
<td class="pointer" mat-cell *matCellDef="let idp">
|
<td [routerLink]="routerLinkForRow(idp)" class="pointer" mat-cell *matCellDef="let idp">
|
||||||
{{idp.changeDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }} </td>
|
{{idp.changeDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }} </td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
<ng-container matColumnDef="type">
|
||||||
<tr class="data-row" mat-row *matRowDef="let row; columns: displayedColumns;"
|
<th mat-header-cell *matHeaderCellDef> {{ 'IDP.TYPE' | translate }} </th>
|
||||||
[routerLink]="row.id ? [serviceType == PolicyComponentServiceType.ADMIN ? '/iam' : '/org', 'idp', row.id ]: null">
|
<td [routerLink]="routerLinkForRow(idp)" class="pointer" mat-cell *matCellDef="let idp">
|
||||||
</tr>
|
{{'IDP.TYPES.'+idp.providerType | translate }} </td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="actions" stickyEnd>
|
||||||
|
<th mat-header-cell *matHeaderCellDef></th>
|
||||||
|
<td mat-cell *matCellDef="let idp">
|
||||||
|
<button
|
||||||
|
[disabled]="serviceType==PolicyComponentServiceType.MGMT && idp?.providerType == IdpProviderType.IDPPROVIDERTYPE_SYSTEM"
|
||||||
|
mat-icon-button color="warn" matTooltip="{{'IAM.VIEWS.CLEAR' | translate}}"
|
||||||
|
(click)="removeIdp(idp)">
|
||||||
|
<i class="las la-trash"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||||
|
<tr class="highlight"
|
||||||
|
[ngClass]="{'disabled': serviceType==PolicyComponentServiceType.MGMT && row?.providerType == IdpProviderType.IDPPROVIDERTYPE_SYSTEM}"
|
||||||
|
mat-row *matRowDef="let row; columns: displayedColumns;">
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<mat-paginator #paginator class="paginator" [length]="idpResult?.totalResult || 0" [pageSize]="10"
|
|
||||||
[pageSizeOptions]="[5, 10, 20]" (page)="changePage($event)"></mat-paginator>
|
|
||||||
</div>
|
</div>
|
||||||
|
<mat-paginator #paginator class="paginator" [length]="idpResult?.totalResult || 0" [pageSize]="10"
|
||||||
|
[pageSizeOptions]="[5, 10, 20]" (page)="changePage($event)"></mat-paginator>
|
||||||
</app-refresh-table>
|
</app-refresh-table>
|
@@ -1,11 +1,10 @@
|
|||||||
|
|
||||||
.table-wrapper {
|
.table-wrapper {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
.table,
|
.table,
|
||||||
.paginator {
|
.paginator {
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
td,
|
td,
|
||||||
th {
|
th {
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
@@ -19,23 +18,29 @@
|
|||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-row {
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: #ffffff05;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
tr {
|
tr {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
|
||||||
|
|
||||||
.add-button {
|
&.disabled * {
|
||||||
border-radius: .5rem;
|
opacity: .8;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
button {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar {
|
.avatar {
|
||||||
@@ -57,14 +62,22 @@ tr {
|
|||||||
.flex-row {
|
.flex-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
padding: 2px 0;
|
||||||
|
width: 250px;
|
||||||
|
|
||||||
.key {
|
.key {
|
||||||
margin-right: 1rem;
|
margin-right: .5rem;
|
||||||
font-size: 14px;
|
font-size: 12px;
|
||||||
|
flex: 1 0 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.value {
|
.value {
|
||||||
font-size: 14px;
|
font-size: 12px;
|
||||||
|
flex: 1 0 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
|
@@ -1,17 +1,19 @@
|
|||||||
import { SelectionModel } from '@angular/cdk/collections';
|
import { SelectionModel } from '@angular/cdk/collections';
|
||||||
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
|
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { MatPaginator, PageEvent } from '@angular/material/paginator';
|
import { MatPaginator, PageEvent } from '@angular/material/paginator';
|
||||||
import { MatTableDataSource } from '@angular/material/table';
|
import { MatTableDataSource } from '@angular/material/table';
|
||||||
import { RouterLink } from '@angular/router';
|
import { RouterLink } from '@angular/router';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { BehaviorSubject, Observable } from 'rxjs';
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
import { IdpSearchResponse as AdminIdpSearchResponse, IdpView as AdminIdpView } from 'src/app/proto/generated/admin_pb';
|
import { IdpSearchResponse as AdminIdpSearchResponse, IdpView as AdminIdpView } from 'src/app/proto/generated/admin_pb';
|
||||||
import { IdpView as MgmtIdpView } from 'src/app/proto/generated/management_pb';
|
import { IdpProviderType, IdpView as MgmtIdpView } from 'src/app/proto/generated/management_pb';
|
||||||
import { AdminService } from 'src/app/services/admin.service';
|
import { AdminService } from 'src/app/services/admin.service';
|
||||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||||
import { ToastService } from 'src/app/services/toast.service';
|
import { ToastService } from 'src/app/services/toast.service';
|
||||||
|
|
||||||
import { PolicyComponentServiceType } from '../policies/policy-component-types.enum';
|
import { PolicyComponentServiceType } from '../policies/policy-component-types.enum';
|
||||||
|
import { WarnDialogComponent } from '../warn-dialog/warn-dialog.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-idp-table',
|
selector: 'app-idp-table',
|
||||||
@@ -31,12 +33,13 @@ export class IdpTableComponent implements OnInit {
|
|||||||
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||||
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
|
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
|
||||||
public PolicyComponentServiceType: any = PolicyComponentServiceType;
|
public PolicyComponentServiceType: any = PolicyComponentServiceType;
|
||||||
|
public IdpProviderType: any = IdpProviderType;
|
||||||
@Input() public displayedColumns: string[] = ['select', 'name', 'config', 'creationDate', 'changeDate', 'state'];
|
@Input() public displayedColumns: string[] = ['select', 'name', 'config', 'creationDate', 'changeDate', 'state'];
|
||||||
|
|
||||||
@Output() public changedSelection: EventEmitter<Array<AdminIdpView.AsObject | MgmtIdpView.AsObject>>
|
@Output() public changedSelection: EventEmitter<Array<AdminIdpView.AsObject | MgmtIdpView.AsObject>>
|
||||||
= new EventEmitter();
|
= new EventEmitter();
|
||||||
|
|
||||||
constructor(public translate: TranslateService, private toast: ToastService) {
|
constructor(public translate: TranslateService, private toast: ToastService, private dialog: MatDialog) {
|
||||||
this.selection.changed.subscribe(() => {
|
this.selection.changed.subscribe(() => {
|
||||||
this.changedSelection.emit(this.selection.selected);
|
this.changedSelection.emit(this.selection.selected);
|
||||||
});
|
});
|
||||||
@@ -44,6 +47,13 @@ export class IdpTableComponent implements OnInit {
|
|||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.getData(10, 0);
|
this.getData(10, 0);
|
||||||
|
if (this.serviceType === PolicyComponentServiceType.MGMT) {
|
||||||
|
this.displayedColumns = ['select', 'name', 'config', 'creationDate', 'changeDate', 'state', 'type'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.disabled) {
|
||||||
|
this.displayedColumns.push('actions');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public isAllSelected(): boolean {
|
public isAllSelected(): boolean {
|
||||||
@@ -64,47 +74,79 @@ export class IdpTableComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public deactivateSelectedIdps(): void {
|
public deactivateSelectedIdps(): void {
|
||||||
|
this.selection.clear();
|
||||||
Promise.all(this.selection.selected.map(value => {
|
Promise.all(this.selection.selected.map(value => {
|
||||||
return this.service.DeactivateIdpConfig(value.id);
|
return this.service.DeactivateIdpConfig(value.id);
|
||||||
})).then(() => {
|
})).then(() => {
|
||||||
this.toast.showInfo('USER.TOAST.SELECTEDDEACTIVATED', true);
|
this.toast.showInfo('IDP.TOAST.SELECTEDDEACTIVATED', true);
|
||||||
this.getData(10, 0);
|
this.refreshPage();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public reactivateSelectedIdps(): void {
|
public reactivateSelectedIdps(): void {
|
||||||
|
this.selection.clear();
|
||||||
Promise.all(this.selection.selected.map(value => {
|
Promise.all(this.selection.selected.map(value => {
|
||||||
return this.service.ReactivateIdpConfig(value.id);
|
return this.service.ReactivateIdpConfig(value.id);
|
||||||
})).then(() => {
|
})).then(() => {
|
||||||
this.toast.showInfo('USER.TOAST.SELECTEDREACTIVATED', true);
|
this.toast.showInfo('IDP.TOAST.SELECTEDREACTIVATED', true);
|
||||||
this.getData(10, 0);
|
this.refreshPage();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public removeSelectedIdps(): void {
|
public removeSelectedIdps(): void {
|
||||||
Promise.all(this.selection.selected.map(value => {
|
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
||||||
return this.service.RemoveIdpConfig(value.id);
|
data: {
|
||||||
})).then(() => {
|
confirmKey: 'ACTIONS.DELETE',
|
||||||
this.toast.showInfo('USER.TOAST.SELECTEDDEACTIVATED', true);
|
cancelKey: 'ACTIONS.CANCEL',
|
||||||
this.getData(10, 0);
|
titleKey: 'IDP.DELETE_SELECTION_TITLE',
|
||||||
|
descriptionKey: 'IDP.DELETE_SELECTION_DESCRIPTION',
|
||||||
|
},
|
||||||
|
width: '400px',
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(resp => {
|
||||||
|
if (resp) {
|
||||||
|
this.selection.clear();
|
||||||
|
|
||||||
|
Promise.all(this.selection.selected.map(value => {
|
||||||
|
return this.service.RemoveIdpConfig(value.id);
|
||||||
|
})).then(() => {
|
||||||
|
this.toast.showInfo('IDP.TOAST.SELECTEDDEACTIVATED', true);
|
||||||
|
this.refreshPage();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeIdp(idp: AdminIdpView.AsObject | MgmtIdpView.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) {
|
||||||
|
this.service.RemoveIdpConfig(idp.id).then(() => {
|
||||||
|
this.toast.showInfo('IDP.TOAST.REMOVED', true);
|
||||||
|
setTimeout(() => {
|
||||||
|
this.refreshPage();
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getData(limit: number, offset: number): Promise<void> {
|
private async getData(limit: number, offset: number): Promise<void> {
|
||||||
this.loadingSubject.next(true);
|
this.loadingSubject.next(true);
|
||||||
|
|
||||||
// let query: AdminIdpSearchQuery | MgmtIdpSearchQuery;
|
|
||||||
// if (this.service instanceof AdminService) {
|
|
||||||
// query = new AdminIdpSearchQuery();
|
|
||||||
// query.setKey(AdminIdpSearchKey.)
|
|
||||||
// } else if (this.service instanceof ManagementService) {
|
|
||||||
// return ['/org', 'idp', 'create'];
|
|
||||||
// }
|
|
||||||
|
|
||||||
this.service.SearchIdps(limit, offset).then(resp => {
|
this.service.SearchIdps(limit, offset).then(resp => {
|
||||||
this.idpResult = resp.toObject();
|
this.idpResult = resp.toObject();
|
||||||
this.dataSource.data = this.idpResult.resultList;
|
this.dataSource.data = this.idpResult.resultList;
|
||||||
console.log(this.idpResult.resultList);
|
|
||||||
this.loadingSubject.next(false);
|
this.loadingSubject.next(false);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
this.toast.showError(error);
|
this.toast.showError(error);
|
||||||
@@ -123,4 +165,21 @@ export class IdpTableComponent implements OnInit {
|
|||||||
return ['/org', 'idp', 'create'];
|
return ['/org', 'idp', 'create'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public routerLinkForRow(row: MgmtIdpView.AsObject | AdminIdpView.AsObject): any {
|
||||||
|
if (row.id) {
|
||||||
|
switch (this.serviceType) {
|
||||||
|
case PolicyComponentServiceType.MGMT:
|
||||||
|
switch ((row as MgmtIdpView.AsObject).providerType) {
|
||||||
|
case IdpProviderType.IDPPROVIDERTYPE_SYSTEM:
|
||||||
|
return ['/iam', 'idp', row.id];
|
||||||
|
case IdpProviderType.IDPPROVIDERTYPE_ORG:
|
||||||
|
return ['/org', 'idp', row.id];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PolicyComponentServiceType.ADMIN:
|
||||||
|
return ['/iam', 'idp', row.id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,8 +11,9 @@ import { RouterModule } from '@angular/router';
|
|||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
||||||
import { RefreshTableModule } from 'src/app/modules/refresh-table/refresh-table.module';
|
import { RefreshTableModule } from 'src/app/modules/refresh-table/refresh-table.module';
|
||||||
import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe.module';
|
import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe/localized-date-pipe.module';
|
||||||
import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-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 { IdpTableComponent } from './idp-table.component';
|
import { IdpTableComponent } from './idp-table.component';
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@ import { IdpTableComponent } from './idp-table.component';
|
|||||||
RouterModule,
|
RouterModule,
|
||||||
RefreshTableModule,
|
RefreshTableModule,
|
||||||
HasRoleModule,
|
HasRoleModule,
|
||||||
|
TruncatePipeModule,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
IdpTableComponent,
|
IdpTableComponent,
|
||||||
|
@@ -1,91 +1,99 @@
|
|||||||
<app-detail-layout [backRouterLink]="backroutes" [title]="'IDP.DETAIL.TITLE' | translate"
|
<app-detail-layout [backRouterLink]="backroutes" [title]="'IDP.DETAIL.TITLE' | translate"
|
||||||
[description]="'IDP.DETAIL.DESCRIPTION' | translate">
|
[description]="'IDP.DETAIL.DESCRIPTION' | translate">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<form (ngSubmit)="updateIdp()">
|
<form (ngSubmit)="updateIdp()">
|
||||||
<ng-container [formGroup]="idpForm">
|
<ng-container [formGroup]="idpForm">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<mat-form-field appearance="outline" class="formfield">
|
<mat-form-field appearance="outline" class="formfield">
|
||||||
<mat-label>{{ 'IDP.ID' | translate }}</mat-label>
|
<mat-label>{{ 'IDP.ID' | translate }}</mat-label>
|
||||||
<input matInput formControlName="id" />
|
<input matInput formControlName="id" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field appearance="outline" class="formfield">
|
<mat-form-field appearance="outline" class="formfield">
|
||||||
<mat-label>{{ 'IDP.NAME' | translate }}</mat-label>
|
<mat-label>{{ 'IDP.NAME' | translate }}</mat-label>
|
||||||
<input matInput formControlName="name" />
|
<input matInput formControlName="name" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<!--<mat-form-field appearance="outline" class="formfield">
|
<mat-form-field class="formfield" appearance="outline">
|
||||||
<mat-label>{{ 'IDP.LOGOSRC' | translate }}</mat-label>
|
<mat-label>{{ 'IDP.STYLE' | translate }}</mat-label>
|
||||||
<input matInput formControlName="logoSrc" />
|
<mat-select formControlName="stylingType">
|
||||||
</mat-form-field>-->
|
<mat-option *ngFor="let field of styleFields" [value]="field">
|
||||||
|
{{ 'IDP.STYLEFIELD.'+field | translate }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<div class="btn-wrapper">
|
||||||
|
<button color="primary" mat-raised-button class="continue-button" [disabled]="idpForm.invalid"
|
||||||
|
type="submit">
|
||||||
|
{{ 'ACTIONS.SAVE' | translate }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<ng-container *ngIf="oidcConfigForm">
|
||||||
|
<h2>{{'IDP.DETAIL.OIDC.TITLE' | translate}}</h2>
|
||||||
|
<p>{{'IDP.DETAIL.OIDC.DESCRIPTION' | translate}}</p>
|
||||||
|
|
||||||
|
<form (ngSubmit)="updateOidcConfig()">
|
||||||
|
<ng-container [formGroup]="oidcConfigForm">
|
||||||
|
<div class="content">
|
||||||
|
<mat-form-field appearance="outline" class="formfield">
|
||||||
|
<mat-label>{{ 'IDP.ISSUER' | translate }}</mat-label>
|
||||||
|
<input matInput formControlName="issuer" />
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field appearance="outline" class="formfield">
|
||||||
|
<mat-label>{{ 'IDP.CLIENTID' | translate }}</mat-label>
|
||||||
|
<input matInput formControlName="clientId" />
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-checkbox class="desc" [(ngModel)]="showIdSecretSection"
|
||||||
|
[ngModelOptions]="{standalone: true}">
|
||||||
|
Update Client Secret
|
||||||
|
</mat-checkbox>
|
||||||
|
<mat-form-field appearance="outline" class="formfield" *ngIf="showIdSecretSection">
|
||||||
|
<mat-label>{{ 'IDP.CLIENTSECRET' | translate }}</mat-label>
|
||||||
|
<input matInput formControlName="clientSecret" />
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field appearance="outline" class="formfield fullwidth">
|
||||||
|
<mat-label>{{ 'IDP.SCOPESLIST' | translate }}</mat-label>
|
||||||
|
<mat-chip-list #chipScopesList aria-label="scope selection">
|
||||||
|
<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 [matChipInputFor]="chipScopesList"
|
||||||
|
[matChipInputSeparatorKeyCodes]="separatorKeysCodes" [matChipInputAddOnBlur]="true"
|
||||||
|
(matChipInputTokenEnd)="addScope($event)">
|
||||||
|
</mat-chip-list>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field class="formfield" appearance="outline">
|
||||||
|
<mat-label>{{ 'IDP.IDPDISPLAYNAMMAPPING' | translate }}</mat-label>
|
||||||
|
<mat-select formControlName="idpDisplayNameMapping">
|
||||||
|
<mat-option *ngFor="let field of mappingFields" [value]="field">
|
||||||
|
{{ 'IDP.MAPPINGFIELD.'+field | translate }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field class="formfield" appearance="outline">
|
||||||
|
<mat-label>{{ 'IDP.USERNAMEMAPPING' | translate }}</mat-label>
|
||||||
|
<mat-select formControlName="usernameMapping">
|
||||||
|
<mat-option *ngFor="let field of mappingFields" [value]="field">
|
||||||
|
{{ 'IDP.MAPPINGFIELD.'+field | translate }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<div class="btn-wrapper">
|
||||||
|
<button color="primary" mat-raised-button class="continue-button"
|
||||||
|
[disabled]="oidcConfigForm.invalid" type="submit">
|
||||||
|
{{ 'ACTIONS.SAVE' | translate }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button color="primary" mat-raised-button class="continue-button" [disabled]="idpForm.invalid" type="submit">
|
</app-detail-layout>
|
||||||
{{ 'ACTIONS.SAVE' | translate }}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<h2 *ngIf="oidcConfigForm">{{'IDP.DETAIL.OIDC.TITLE' | translate}}</h2>
|
|
||||||
|
|
||||||
<form (ngSubmit)="updateOidcConfig()" *ngIf="oidcConfigForm">
|
|
||||||
<ng-container [formGroup]="oidcConfigForm">
|
|
||||||
<div class="content">
|
|
||||||
<mat-form-field appearance="outline" class="formfield">
|
|
||||||
<mat-label>{{ 'IDP.ISSUER' | translate }}</mat-label>
|
|
||||||
<input matInput formControlName="issuer" />
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
<mat-form-field appearance="outline" class="formfield">
|
|
||||||
<mat-label>{{ 'IDP.CLIENTID' | translate }}</mat-label>
|
|
||||||
<input matInput formControlName="clientId" />
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
<mat-checkbox class="desc" [(ngModel)]="showIdSecretSection" [ngModelOptions]="{standalone: true}">
|
|
||||||
Update Client Secret
|
|
||||||
</mat-checkbox>
|
|
||||||
<mat-form-field appearance="outline" class="formfield" *ngIf="showIdSecretSection">
|
|
||||||
<mat-label>{{ 'IDP.CLIENTSECRET' | translate }}</mat-label>
|
|
||||||
<input matInput formControlName="clientSecret" />
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
<mat-form-field appearance="outline" class="formfield">
|
|
||||||
<mat-label>{{ 'IDP.SCOPESLIST' | translate }}</mat-label>
|
|
||||||
<mat-chip-list #chipScopesList aria-label="scope selection" >
|
|
||||||
<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 [matChipInputFor]="chipScopesList" [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
|
|
||||||
[matChipInputAddOnBlur]="true" (matChipInputTokenEnd)="addScope($event)">
|
|
||||||
</mat-chip-list>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
<mat-form-field class="formfield" appearance="outline">
|
|
||||||
<mat-label>{{ 'IDP.IDPDISPLAYNAMMAPPING' | translate }}</mat-label>
|
|
||||||
<mat-select formControlName="idpDisplayNameMapping">
|
|
||||||
<mat-option *ngFor="let field of mappingFields" [value]="field">
|
|
||||||
{{ 'IDP.MAPPINTFIELD.'+field | translate }}
|
|
||||||
</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field class="formfield" appearance="outline">
|
|
||||||
<mat-label>{{ 'IDP.USERNAMEMAPPING' | translate }}</mat-label>
|
|
||||||
<mat-select formControlName="usernameMapping">
|
|
||||||
<mat-option *ngFor="let field of mappingFields" [value]="field">
|
|
||||||
{{ 'IDP.MAPPINTFIELD.'+field | translate }}
|
|
||||||
</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<button color="primary" mat-raised-button class="continue-button" [disabled]="oidcConfigForm.invalid" type="submit">
|
|
||||||
{{ 'ACTIONS.SAVE' | translate }}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</app-detail-layout>
|
|
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
.content {
|
.content {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
margin: 0 -.5rem;
|
margin: 0 -.5rem;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
@@ -16,27 +17,35 @@
|
|||||||
flex-basis: 100%;
|
flex-basis: 100%;
|
||||||
margin: 0 .5rem;
|
margin: 0 .5rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
color: #8795a1;
|
color: var(--grey);
|
||||||
}
|
}
|
||||||
|
|
||||||
.formfield {
|
.formfield {
|
||||||
flex: 1 0 auto;
|
flex: 1 1 auto;
|
||||||
margin: 0 .5rem;
|
margin: 0 .5rem;
|
||||||
|
|
||||||
|
&.fullwidth {
|
||||||
|
flex-basis: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 450px) {
|
@media only screen and (max-width: 450px) {
|
||||||
flex-basis: 100%;
|
flex-basis: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.continue-button {
|
.btn-wrapper {
|
||||||
margin-bottom: 4rem;
|
display: flex;
|
||||||
display: block;
|
justify-content: flex-end;
|
||||||
padding: .5rem 4rem;
|
|
||||||
border-radius: .5rem;
|
|
||||||
|
|
||||||
@media only screen and (max-width: 450px) {
|
.continue-button {
|
||||||
margin-top: 1rem;
|
margin-bottom: 4rem;
|
||||||
margin-bottom: 2rem;
|
display: block;
|
||||||
|
padding: .5rem 4rem;
|
||||||
|
|
||||||
|
@media only screen and (max-width: 450px) {
|
||||||
|
margin-top: 1rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,20 +1,22 @@
|
|||||||
import { COMMA, ENTER, SPACE } from '@angular/cdk/keycodes';
|
import { COMMA, ENTER, SPACE } from '@angular/cdk/keycodes';
|
||||||
import { Location } from '@angular/common';
|
import { Location } from '@angular/common';
|
||||||
import {Component, Injector, Input, OnDestroy, OnInit, Type} from '@angular/core';
|
import { Component, Injector, OnDestroy, OnInit, Type } from '@angular/core';
|
||||||
import { AbstractControl, FormControl, FormGroup, Validators } from '@angular/forms';
|
import { AbstractControl, FormControl, FormGroup, Validators } from '@angular/forms';
|
||||||
import { MatChipInputEvent } from '@angular/material/chips';
|
import { MatChipInputEvent } from '@angular/material/chips';
|
||||||
import { ActivatedRoute, Params } from '@angular/router';
|
import { ActivatedRoute, Params } from '@angular/router';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { switchMap, take } from 'rxjs/operators';
|
import { switchMap, take } from 'rxjs/operators';
|
||||||
import {
|
import {
|
||||||
OIDCMappingField as authMappingFields,
|
IdpStylingType as adminIdpStylingType,
|
||||||
OidcIdpConfigUpdate as AdminOidcIdpConfigUpdate,
|
IdpUpdate as AdminIdpConfigUpdate,
|
||||||
IdpUpdate as AdminIdpConfigUpdate,
|
OidcIdpConfigUpdate as AdminOidcIdpConfigUpdate,
|
||||||
|
OIDCMappingField as adminMappingFields,
|
||||||
} from 'src/app/proto/generated/admin_pb';
|
} from 'src/app/proto/generated/admin_pb';
|
||||||
import {
|
import {
|
||||||
OIDCMappingField as mgmtMappingFields,
|
IdpStylingType as mgmtIdpStylingType,
|
||||||
OidcIdpConfigUpdate as MgmtOidcIdpConfigUpdate,
|
IdpUpdate as MgmtIdpConfigUpdate,
|
||||||
IdpUpdate as MgmtIdpConfigUpdate,
|
OidcIdpConfigUpdate as MgmtOidcIdpConfigUpdate,
|
||||||
|
OIDCMappingField as mgmtMappingFields,
|
||||||
} from 'src/app/proto/generated/management_pb';
|
} from 'src/app/proto/generated/management_pb';
|
||||||
import { AdminService } from 'src/app/services/admin.service';
|
import { AdminService } from 'src/app/services/admin.service';
|
||||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||||
@@ -28,7 +30,9 @@ import { PolicyComponentServiceType } from '../policies/policy-component-types.e
|
|||||||
styleUrls: ['./idp.component.scss'],
|
styleUrls: ['./idp.component.scss'],
|
||||||
})
|
})
|
||||||
export class IdpComponent implements OnInit, OnDestroy {
|
export class IdpComponent implements OnInit, OnDestroy {
|
||||||
public mappingFields: mgmtMappingFields[] | authMappingFields[] = [];
|
public mappingFields: mgmtMappingFields[] | adminMappingFields[] = [];
|
||||||
|
public styleFields: mgmtIdpStylingType[] | adminIdpStylingType[] = [];
|
||||||
|
|
||||||
public showIdSecretSection: boolean = false;
|
public showIdSecretSection: boolean = false;
|
||||||
public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
|
public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
|
||||||
private service!: ManagementService | AdminService;
|
private service!: ManagementService | AdminService;
|
||||||
@@ -41,7 +45,6 @@ export class IdpComponent implements OnInit, OnDestroy {
|
|||||||
public oidcConfigForm!: FormGroup;
|
public oidcConfigForm!: FormGroup;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
// private router: Router,
|
|
||||||
private toast: ToastService,
|
private toast: ToastService,
|
||||||
private injector: Injector,
|
private injector: Injector,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
@@ -50,33 +53,38 @@ export class IdpComponent implements OnInit, OnDestroy {
|
|||||||
this.idpForm = new FormGroup({
|
this.idpForm = new FormGroup({
|
||||||
id: new FormControl({ disabled: true, value: '' }, [Validators.required]),
|
id: new FormControl({ disabled: true, value: '' }, [Validators.required]),
|
||||||
name: new FormControl('', [Validators.required]),
|
name: new FormControl('', [Validators.required]),
|
||||||
logoSrc: new FormControl({ disabled: true, value: '' }, [Validators.required]),
|
stylingType: new FormControl('', [Validators.required]),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.oidcConfigForm = new FormGroup({
|
this.oidcConfigForm = new FormGroup({
|
||||||
clientId: new FormControl('', [Validators.required]),
|
clientId: new FormControl('', [Validators.required]),
|
||||||
clientSecret: new FormControl(''),
|
clientSecret: new FormControl(''),
|
||||||
issuer: new FormControl('', [Validators.required]),
|
issuer: new FormControl('', [Validators.required]),
|
||||||
scopesList: new FormControl([], []),
|
scopesList: new FormControl([], []),
|
||||||
idpDisplayNameMapping: new FormControl(0),
|
idpDisplayNameMapping: new FormControl(0),
|
||||||
usernameMapping: new FormControl(0),
|
usernameMapping: new FormControl(0),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.route.data.pipe(switchMap(data => {
|
this.route.data.pipe(switchMap(data => {
|
||||||
console.log(data.serviceType);
|
|
||||||
this.serviceType = data.serviceType;
|
this.serviceType = data.serviceType;
|
||||||
switch (this.serviceType) {
|
switch (this.serviceType) {
|
||||||
case PolicyComponentServiceType.MGMT:
|
case PolicyComponentServiceType.MGMT:
|
||||||
this.service = this.injector.get(ManagementService as Type<ManagementService>);
|
this.service = this.injector.get(ManagementService as Type<ManagementService>);
|
||||||
this.mappingFields = [
|
this.mappingFields = [
|
||||||
mgmtMappingFields.OIDCMAPPINGFIELD_PREFERRED_USERNAME,
|
mgmtMappingFields.OIDCMAPPINGFIELD_PREFERRED_USERNAME,
|
||||||
mgmtMappingFields.OIDCMAPPINGFIELD_EMAIL];
|
mgmtMappingFields.OIDCMAPPINGFIELD_EMAIL];
|
||||||
|
this.styleFields = [
|
||||||
|
mgmtIdpStylingType.IDPSTYLINGTYPE_UNSPECIFIED,
|
||||||
|
mgmtIdpStylingType.IDPSTYLINGTYPE_GOOGLE];
|
||||||
break;
|
break;
|
||||||
case PolicyComponentServiceType.ADMIN:
|
case PolicyComponentServiceType.ADMIN:
|
||||||
this.service = this.injector.get(AdminService as Type<AdminService>);
|
this.service = this.injector.get(AdminService as Type<AdminService>);
|
||||||
this.mappingFields = [
|
this.mappingFields = [
|
||||||
authMappingFields.OIDCMAPPINGFIELD_PREFERRED_USERNAME,
|
adminMappingFields.OIDCMAPPINGFIELD_PREFERRED_USERNAME,
|
||||||
authMappingFields.OIDCMAPPINGFIELD_EMAIL];
|
adminMappingFields.OIDCMAPPINGFIELD_EMAIL];
|
||||||
|
this.styleFields = [
|
||||||
|
adminIdpStylingType.IDPSTYLINGTYPE_UNSPECIFIED,
|
||||||
|
adminIdpStylingType.IDPSTYLINGTYPE_GOOGLE];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,11 +93,11 @@ export class IdpComponent implements OnInit, OnDestroy {
|
|||||||
const { id } = params;
|
const { id } = params;
|
||||||
if (id) {
|
if (id) {
|
||||||
this.service.IdpByID(id).then(idp => {
|
this.service.IdpByID(id).then(idp => {
|
||||||
const idpObject = idp.toObject();
|
const idpObject = idp.toObject();
|
||||||
this.idpForm.patchValue(idpObject);
|
this.idpForm.patchValue(idpObject);
|
||||||
if (idpObject.oidcConfig) {
|
if (idpObject.oidcConfig) {
|
||||||
this.oidcConfigForm.patchValue(idpObject.oidcConfig);
|
this.oidcConfigForm.patchValue(idpObject.oidcConfig);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -121,10 +129,10 @@ export class IdpComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
req.setId(this.id?.value);
|
req.setId(this.id?.value);
|
||||||
req.setName(this.name?.value);
|
req.setName(this.name?.value);
|
||||||
req.setLogoSrc(this.logoSrc?.value);
|
req.setStylingType(this.stylingType?.value);
|
||||||
|
|
||||||
this.service.UpdateIdp(req).then((idp) => {
|
this.service.UpdateIdp(req).then((idp) => {
|
||||||
this.toast.showInfo('IDP.TOAST.SAVED', true);
|
this.toast.showInfo('IDP.TOAST.SAVED', true);
|
||||||
// this.router.navigate(['idp', ]);
|
// this.router.navigate(['idp', ]);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
this.toast.showError(error);
|
this.toast.showError(error);
|
||||||
@@ -132,31 +140,31 @@ export class IdpComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public updateOidcConfig(): void {
|
public updateOidcConfig(): void {
|
||||||
let req: AdminOidcIdpConfigUpdate | MgmtOidcIdpConfigUpdate;
|
let req: AdminOidcIdpConfigUpdate | MgmtOidcIdpConfigUpdate;
|
||||||
|
|
||||||
switch (this.serviceType) {
|
switch (this.serviceType) {
|
||||||
case PolicyComponentServiceType.MGMT:
|
case PolicyComponentServiceType.MGMT:
|
||||||
req = new MgmtOidcIdpConfigUpdate();
|
req = new MgmtOidcIdpConfigUpdate();
|
||||||
break;
|
break;
|
||||||
case PolicyComponentServiceType.ADMIN:
|
case PolicyComponentServiceType.ADMIN:
|
||||||
req = new AdminOidcIdpConfigUpdate();
|
req = new AdminOidcIdpConfigUpdate();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
req.setIdpId(this.id?.value);
|
req.setIdpId(this.id?.value);
|
||||||
req.setClientId(this.clientId?.value);
|
req.setClientId(this.clientId?.value);
|
||||||
req.setClientSecret(this.clientSecret?.value);
|
req.setClientSecret(this.clientSecret?.value);
|
||||||
req.setIssuer(this.issuer?.value);
|
req.setIssuer(this.issuer?.value);
|
||||||
req.setScopesList(this.scopesList?.value);
|
req.setScopesList(this.scopesList?.value);
|
||||||
req.setUsernameMapping(this.usernameMapping?.value);
|
req.setUsernameMapping(this.usernameMapping?.value);
|
||||||
req.setIdpDisplayNameMapping(this.idpDisplayNameMapping?.value);
|
req.setIdpDisplayNameMapping(this.idpDisplayNameMapping?.value);
|
||||||
|
|
||||||
this.service.UpdateOidcIdpConfig(req).then((oidcConfig) => {
|
this.service.UpdateOidcIdpConfig(req).then((oidcConfig) => {
|
||||||
this.toast.showInfo('IDP.TOAST.SAVED', true);
|
this.toast.showInfo('IDP.TOAST.SAVED', true);
|
||||||
// this.router.navigate(['idp', ]);
|
// this.router.navigate(['idp', ]);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
this.toast.showError(error);
|
this.toast.showError(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public close(): void {
|
public close(): void {
|
||||||
@@ -190,12 +198,10 @@ export class IdpComponent implements OnInit, OnDestroy {
|
|||||||
public get backroutes(): string[] {
|
public get backroutes(): string[] {
|
||||||
switch (this.serviceType) {
|
switch (this.serviceType) {
|
||||||
case PolicyComponentServiceType.MGMT:
|
case PolicyComponentServiceType.MGMT:
|
||||||
return ['/org', 'policy', 'login'];
|
return ['/org', 'policy', 'login'];
|
||||||
case PolicyComponentServiceType.ADMIN:
|
case PolicyComponentServiceType.ADMIN:
|
||||||
return ['/iam', 'policy', 'login'];
|
return ['/iam', 'policy', 'login'];
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get id(): AbstractControl | null {
|
public get id(): AbstractControl | null {
|
||||||
@@ -206,8 +212,8 @@ export class IdpComponent implements OnInit, OnDestroy {
|
|||||||
return this.idpForm.get('name');
|
return this.idpForm.get('name');
|
||||||
}
|
}
|
||||||
|
|
||||||
public get logoSrc(): AbstractControl | null {
|
public get stylingType(): AbstractControl | null {
|
||||||
return this.idpForm.get('logoSrc');
|
return this.idpForm.get('stylingType');
|
||||||
}
|
}
|
||||||
|
|
||||||
public get clientId(): AbstractControl | null {
|
public get clientId(): AbstractControl | null {
|
||||||
@@ -227,10 +233,10 @@ export class IdpComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public get idpDisplayNameMapping(): AbstractControl | null {
|
public get idpDisplayNameMapping(): AbstractControl | null {
|
||||||
return this.oidcConfigForm.get('idpDisplayNameMapping');
|
return this.oidcConfigForm.get('idpDisplayNameMapping');
|
||||||
}
|
}
|
||||||
|
|
||||||
public get usernameMapping(): AbstractControl | null {
|
public get usernameMapping(): AbstractControl | null {
|
||||||
return this.oidcConfigForm.get('usernameMapping');
|
return this.oidcConfigForm.get('usernameMapping');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,98 @@
|
|||||||
|
<app-refresh-table *ngIf="dataSource" (refreshed)="changePage()" [dataSize]="dataSource.totalResult"
|
||||||
|
[timestamp]="dataSource.viewTimestamp" [selection]="selection" [loading]="dataSource?.loading$ | async">
|
||||||
|
|
||||||
|
<ng-container actions *ngIf="selection.hasValue()">
|
||||||
|
<ng-content select="[selectactions]"></ng-content>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<div actions>
|
||||||
|
<ng-content select="[writeactions]"></ng-content>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-wrapper">
|
||||||
|
<table mat-table class="table" aria-label="Elements" [dataSource]="dataSource">
|
||||||
|
<ng-container matColumnDef="select">
|
||||||
|
<th class="selection" mat-header-cell *matHeaderCellDef>
|
||||||
|
<mat-checkbox [disabled]="!canWrite" 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]="!canWrite" color="primary" (click)="$event.stopPropagation()"
|
||||||
|
(change)="$event ? selection.toggle(row) : null" [checked]="selection.isSelected(row)">
|
||||||
|
<app-avatar *ngIf="row?.displayName && row.firstName && row.lastName; else cog" class="avatar"
|
||||||
|
[name]="row.displayName" [size]="32">
|
||||||
|
</app-avatar>
|
||||||
|
<ng-template #cog>
|
||||||
|
<div class="sa-icon">
|
||||||
|
<i class="las la-user-cog"></i>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</mat-checkbox>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="userId">
|
||||||
|
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.USERID' | translate }} </th>
|
||||||
|
<td class="pointer" [routerLink]="['/users', member.userId]" mat-cell *matCellDef="let member">
|
||||||
|
{{member.userId}} </td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="firstname">
|
||||||
|
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.FIRSTNAME' | translate }} </th>
|
||||||
|
<td class="pointer" [routerLink]="['/users', member.userId]" mat-cell *matCellDef="let member">
|
||||||
|
{{member.firstName}} </td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="lastname">
|
||||||
|
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.LASTNAME' | translate }} </th>
|
||||||
|
<td class="pointer" [routerLink]="['/users', member.userId]" mat-cell *matCellDef="let member">
|
||||||
|
{{member.lastName}} </td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="username">
|
||||||
|
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.USERNAME' | translate }} </th>
|
||||||
|
<td class="pointer" [routerLink]="['/users', member.userId]" mat-cell *matCellDef="let member">
|
||||||
|
{{member.userName}} </td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="email">
|
||||||
|
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.EMAIL' | translate }} </th>
|
||||||
|
<td class="pointer" [routerLink]="['/users', member.userId]" mat-cell *matCellDef="let member">
|
||||||
|
{{member.email}}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="actions" stickyEnd>
|
||||||
|
<th mat-header-cell *matHeaderCellDef></th>
|
||||||
|
<td mat-cell *matCellDef="let view">
|
||||||
|
<button matTooltip="{{'ACTIONS.REMOVE' | translate}}" color="warn"
|
||||||
|
(click)="triggerDeleteMember(view)" mat-icon-button><i class="las la-trash"></i></button>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="roles">
|
||||||
|
<th mat-header-cell *matHeaderCellDef> {{ 'ROLESLABEL' | translate }} </th>
|
||||||
|
<td mat-cell *matCellDef="let member">
|
||||||
|
<mat-form-field class="form-field" appearance="outline">
|
||||||
|
<mat-label>{{ 'ROLESLABEL' | translate }}</mat-label>
|
||||||
|
<mat-select [(ngModel)]="member.rolesList" multiple [disabled]="!canWrite"
|
||||||
|
(selectionChange)="updateRoles.emit({member: member, change: $event})">
|
||||||
|
<mat-option *ngFor="let role of memberRoleOptions" [value]="role">
|
||||||
|
{{ role }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||||
|
<tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns;">
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<mat-paginator *ngIf="dataSource" class="paginator" #paginator [pageSize]="INITIALPAGESIZE"
|
||||||
|
[length]="dataSource.totalResult" [pageSizeOptions]="[25, 50, 100, 250]" (page)="changePage($event)">
|
||||||
|
</mat-paginator>
|
||||||
|
</app-refresh-table>
|
@@ -1,9 +1,14 @@
|
|||||||
|
.icon-button {
|
||||||
|
margin-right: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.table-wrapper {
|
.table-wrapper {
|
||||||
overflow: auto;
|
overflow-x: auto;
|
||||||
|
|
||||||
.table,
|
.table,
|
||||||
.paginator {
|
.paginator {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
td,
|
td,
|
||||||
th {
|
th {
|
||||||
padding: .5rem;
|
padding: .5rem;
|
||||||
@@ -22,20 +27,31 @@
|
|||||||
width: 40px;
|
width: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-row {
|
|
||||||
&:hover {
|
|
||||||
background-color: #ffffff05;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.selection {
|
.selection {
|
||||||
width: 50px;
|
width: 50px;
|
||||||
max-width: 50px;
|
max-width: 50px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.role {
|
tr {
|
||||||
display: inline-block;
|
button {
|
||||||
margin: .25rem;
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
button {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sa-icon {
|
||||||
|
display: block;
|
||||||
|
width: 32px;
|
||||||
|
margin: 0 .5rem;
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -4,15 +4,15 @@ import { MatSortModule } from '@angular/material/sort';
|
|||||||
import { MatTableModule } from '@angular/material/table';
|
import { MatTableModule } from '@angular/material/table';
|
||||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
|
||||||
import { ProjectGrantMembersComponent } from './project-grant-members.component';
|
import { MembersTableComponent } from './members-table.component';
|
||||||
|
|
||||||
describe('ProjectMembersComponent', () => {
|
describe('MembersTableComponent', () => {
|
||||||
let component: ProjectGrantMembersComponent;
|
let component: MembersTableComponent;
|
||||||
let fixture: ComponentFixture<ProjectGrantMembersComponent>;
|
let fixture: ComponentFixture<MembersTableComponent>;
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [ProjectGrantMembersComponent],
|
declarations: [MembersTableComponent],
|
||||||
imports: [
|
imports: [
|
||||||
NoopAnimationsModule,
|
NoopAnimationsModule,
|
||||||
MatPaginatorModule,
|
MatPaginatorModule,
|
||||||
@@ -23,7 +23,7 @@ describe('ProjectMembersComponent', () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(ProjectGrantMembersComponent);
|
fixture = TestBed.createComponent(MembersTableComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
@@ -0,0 +1,83 @@
|
|||||||
|
import { SelectionModel } from '@angular/cdk/collections';
|
||||||
|
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';
|
||||||
|
import { MatPaginator, PageEvent } from '@angular/material/paginator';
|
||||||
|
import { MatSelectChange } from '@angular/material/select';
|
||||||
|
import { MatTable } from '@angular/material/table';
|
||||||
|
import { Observable, Subject } from 'rxjs';
|
||||||
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
import { IamMembersDataSource } from 'src/app/pages/iam/iam-members/iam-members-datasource';
|
||||||
|
import { OrgMembersDataSource } from 'src/app/pages/orgs/org-members/org-members-datasource';
|
||||||
|
import { IamMemberView } from 'src/app/proto/generated/admin_pb';
|
||||||
|
import { OrgMemberView, ProjectMemberView } from 'src/app/proto/generated/management_pb';
|
||||||
|
|
||||||
|
import { ProjectMembersDataSource } from '../project-members/project-members-datasource';
|
||||||
|
|
||||||
|
type View = OrgMemberView.AsObject | ProjectMemberView.AsObject | IamMemberView.AsObject;
|
||||||
|
type MemberDatasource = OrgMembersDataSource | ProjectMembersDataSource | IamMembersDataSource;
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-members-table',
|
||||||
|
templateUrl: './members-table.component.html',
|
||||||
|
styleUrls: ['./members-table.component.scss'],
|
||||||
|
})
|
||||||
|
export class MembersTableComponent implements OnInit, OnDestroy {
|
||||||
|
public INITIALPAGESIZE: number = 25;
|
||||||
|
@Input() public canDelete: boolean = false;
|
||||||
|
@Input() public canWrite: boolean = false;
|
||||||
|
@ViewChild(MatPaginator) public paginator!: MatPaginator;
|
||||||
|
@ViewChild(MatTable) public table!: MatTable<View>;
|
||||||
|
@Input() public dataSource!: MemberDatasource;
|
||||||
|
public selection: SelectionModel<any> = new SelectionModel<any>(true, []);
|
||||||
|
@Input() public memberRoleOptions: string[] = [];
|
||||||
|
@Input() public factoryLoadFunc!: Function;
|
||||||
|
@Input() public refreshTrigger!: Observable<void>;
|
||||||
|
@Output() public updateRoles: EventEmitter<{ member: View, change: MatSelectChange; }> = new EventEmitter();
|
||||||
|
@Output() public changedSelection: EventEmitter<any[]> = new EventEmitter();
|
||||||
|
@Output() public deleteMember: EventEmitter<View> = new EventEmitter();
|
||||||
|
|
||||||
|
private destroyed: Subject<void> = new Subject();
|
||||||
|
|
||||||
|
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
|
||||||
|
public displayedColumns: string[] = ['select', 'userId', 'firstname', 'lastname', 'username', 'email', 'roles'];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.selection.changed.pipe(takeUntil(this.destroyed)).subscribe(_ => {
|
||||||
|
this.changedSelection.emit(this.selection.selected);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnInit(): void {
|
||||||
|
this.refreshTrigger.pipe(takeUntil(this.destroyed)).subscribe(() => {
|
||||||
|
this.changePage(this.paginator);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.canDelete) {
|
||||||
|
this.displayedColumns.push('actions');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnDestroy(): void {
|
||||||
|
this.destroyed.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
public isAllSelected(): boolean {
|
||||||
|
const numSelected = this.selection.selected.length;
|
||||||
|
const numRows = this.dataSource.membersSubject.value.length;
|
||||||
|
return numSelected === numRows;
|
||||||
|
}
|
||||||
|
|
||||||
|
public masterToggle(): void {
|
||||||
|
this.isAllSelected() ?
|
||||||
|
this.selection.clear() :
|
||||||
|
this.dataSource.membersSubject.value.forEach(row => this.selection.select(row));
|
||||||
|
}
|
||||||
|
|
||||||
|
public changePage(event?: PageEvent | MatPaginator): any {
|
||||||
|
this.selection.clear();
|
||||||
|
return this.factoryLoadFunc(event ?? this.paginator);
|
||||||
|
}
|
||||||
|
|
||||||
|
public triggerDeleteMember(member: any): void {
|
||||||
|
this.deleteMember.emit(member);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,45 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||||
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
|
import { MatSortModule } from '@angular/material/sort';
|
||||||
|
import { MatTableModule } from '@angular/material/table';
|
||||||
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { AvatarModule } from '../avatar/avatar.module';
|
||||||
|
import { RefreshTableModule } from '../refresh-table/refresh-table.module';
|
||||||
|
import { MembersTableComponent } from './members-table.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
MembersTableComponent,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
MatSelectModule,
|
||||||
|
MatCheckboxModule,
|
||||||
|
MatIconModule,
|
||||||
|
MatTableModule,
|
||||||
|
MatPaginatorModule,
|
||||||
|
MatSortModule,
|
||||||
|
MatTooltipModule,
|
||||||
|
FormsModule,
|
||||||
|
TranslateModule,
|
||||||
|
RefreshTableModule,
|
||||||
|
RouterModule,
|
||||||
|
AvatarModule,
|
||||||
|
MatButtonModule,
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
MembersTableComponent,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class MembersTableModule { }
|
@@ -2,7 +2,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
transition: all .2s ease-in-out;
|
transition: all .3s cubic-bezier(.645, .045, .355, 1);
|
||||||
|
|
||||||
.main-content {
|
.main-content {
|
||||||
display: relative;
|
display: relative;
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
.validation-col {
|
.validation-col {
|
||||||
display: flex wrap;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
padding: 1rem 0;
|
padding: 1rem 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@
|
|||||||
|
|
||||||
span {
|
span {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #8795a1;
|
color: var(--grey);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sp-wrapper {
|
.sp-wrapper {
|
||||||
|
@@ -0,0 +1,20 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
|
||||||
|
import { LabelPolicyComponent } from './label-policy.component';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: LabelPolicyComponent,
|
||||||
|
data: {
|
||||||
|
animation: 'DetailPage',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule],
|
||||||
|
})
|
||||||
|
export class LabelPolicyRoutingModule { }
|
@@ -0,0 +1,20 @@
|
|||||||
|
<app-detail-layout [backRouterLink]="['/iam']" [title]="'POLICY.LABEL.TITLE' | translate"
|
||||||
|
[description]="'POLICY.LABEL.DESCRIPTION' | translate">
|
||||||
|
|
||||||
|
<div class="content" *ngIf="labelData">
|
||||||
|
<mat-form-field class="form-field" appearance="outline">
|
||||||
|
<mat-label>{{'POLICY.LABEL.PRIMARYCOLOR' | translate}}</mat-label>
|
||||||
|
<input [(ngModel)]="labelData.primaryColor" matInput />
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field class="form-field" appearance="outline">
|
||||||
|
<mat-label>{{'POLICY.LABEL.SECONDARYCOLOR' | translate}}</mat-label>
|
||||||
|
<input [(ngModel)]="labelData.secondaryColor" matInput />
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="btn-container">
|
||||||
|
<button (click)="savePolicy()" color="primary" type="submit"
|
||||||
|
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
|
||||||
|
</div>
|
||||||
|
</app-detail-layout>
|
@@ -0,0 +1,24 @@
|
|||||||
|
.content {
|
||||||
|
padding-top: 1rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin: 0 -.5rem;
|
||||||
|
|
||||||
|
.form-field {
|
||||||
|
flex: 1;
|
||||||
|
margin: 0 .5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-top: 3rem;
|
||||||
|
display: block;
|
||||||
|
padding: .5rem 4rem;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,20 +1,20 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { IamPolicyGridComponent } from './iam-policy-grid.component';
|
import { LabelPolicyComponent } from './label-policy.component';
|
||||||
|
|
||||||
describe('IamPolicyGridComponent', () => {
|
describe('LabelPolicyComponent', () => {
|
||||||
let component: IamPolicyGridComponent;
|
let component: LabelPolicyComponent;
|
||||||
let fixture: ComponentFixture<IamPolicyGridComponent>;
|
let fixture: ComponentFixture<LabelPolicyComponent>;
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [IamPolicyGridComponent],
|
declarations: [LabelPolicyComponent],
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(IamPolicyGridComponent);
|
fixture = TestBed.createComponent(LabelPolicyComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
@@ -0,0 +1,54 @@
|
|||||||
|
import { Component, OnDestroy } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
import { DefaultLabelPolicyUpdate, DefaultLabelPolicyView } from 'src/app/proto/generated/admin_pb';
|
||||||
|
import { AdminService } from 'src/app/services/admin.service';
|
||||||
|
import { ToastService } from 'src/app/services/toast.service';
|
||||||
|
|
||||||
|
import { PolicyComponentServiceType } from '../policy-component-types.enum';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-label-policy',
|
||||||
|
templateUrl: './label-policy.component.html',
|
||||||
|
styleUrls: ['./label-policy.component.scss'],
|
||||||
|
})
|
||||||
|
export class LabelPolicyComponent implements OnDestroy {
|
||||||
|
public labelData!: DefaultLabelPolicyView.AsObject;
|
||||||
|
|
||||||
|
private sub: Subscription = new Subscription();
|
||||||
|
|
||||||
|
public PolicyComponentServiceType: any = PolicyComponentServiceType;
|
||||||
|
constructor(
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private toast: ToastService,
|
||||||
|
private adminService: AdminService,
|
||||||
|
) {
|
||||||
|
this.route.params.subscribe(() => {
|
||||||
|
this.getData().then(data => {
|
||||||
|
if (data) {
|
||||||
|
this.labelData = data.toObject();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnDestroy(): void {
|
||||||
|
this.sub.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getData(): Promise<DefaultLabelPolicyView> {
|
||||||
|
return this.adminService.GetDefaultLabelPolicy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public savePolicy(): void {
|
||||||
|
const req = new DefaultLabelPolicyUpdate();
|
||||||
|
req.setPrimaryColor(this.labelData.primaryColor);
|
||||||
|
req.setSecondaryColor(this.labelData.secondaryColor);
|
||||||
|
this.adminService.UpdateDefaultLabelPolicy(req).then(() => {
|
||||||
|
this.toast.showInfo('POLICY.TOAST.SET', true);
|
||||||
|
}).catch(error => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -11,13 +11,13 @@ import { TranslateModule } from '@ngx-translate/core';
|
|||||||
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
||||||
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module';
|
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module';
|
||||||
|
|
||||||
import { PasswordIamPolicyRoutingModule } from './password-iam-policy-routing.module';
|
import { LabelPolicyRoutingModule } from './label-policy-routing.module';
|
||||||
import { PasswordIamPolicyComponent } from './password-iam-policy.component';
|
import { LabelPolicyComponent } from './label-policy.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [PasswordIamPolicyComponent],
|
declarations: [LabelPolicyComponent],
|
||||||
imports: [
|
imports: [
|
||||||
PasswordIamPolicyRoutingModule,
|
LabelPolicyRoutingModule,
|
||||||
CommonModule,
|
CommonModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
MatInputModule,
|
MatInputModule,
|
||||||
@@ -31,4 +31,4 @@ import { PasswordIamPolicyComponent } from './password-iam-policy.component';
|
|||||||
DetailLayoutModule,
|
DetailLayoutModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class PasswordIamPolicyModule { }
|
export class LabelPolicyModule { }
|
@@ -6,7 +6,7 @@
|
|||||||
<div mat-dialog-content>
|
<div mat-dialog-content>
|
||||||
<mat-form-field *ngIf="serviceType == PolicyComponentServiceType.MGMT" class="full-width" appearance="outline">
|
<mat-form-field *ngIf="serviceType == PolicyComponentServiceType.MGMT" class="full-width" appearance="outline">
|
||||||
<mat-label>{{ 'IDP.TYPE' | translate }}</mat-label>
|
<mat-label>{{ 'IDP.TYPE' | translate }}</mat-label>
|
||||||
<mat-select [(ngModel)]="idpType" (selectionChange)="loadIdps()" (selectionChange)="loadIdps()">
|
<mat-select [(ngModel)]="idpType" (selectionChange)="loadIdps()">
|
||||||
<mat-option *ngFor="let type of idpTypes" [value]="type">
|
<mat-option *ngFor="let type of idpTypes" [value]="type">
|
||||||
{{ 'IDP.TYPES.'+type | translate}}
|
{{ 'IDP.TYPES.'+type | translate}}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.desc {
|
.desc {
|
||||||
color: #8795a1;
|
color: var(--grey);
|
||||||
font-size: .9rem;
|
font-size: .9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,8 +18,4 @@
|
|||||||
.ok-button {
|
.ok-button {
|
||||||
margin-left: .5rem;
|
margin-left: .5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
|
||||||
border-radius: .5rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -61,7 +61,9 @@ export class AddIdpDialogComponent {
|
|||||||
query.setKey(IdpSearchKey.IDPSEARCHKEY_PROVIDER_TYPE);
|
query.setKey(IdpSearchKey.IDPSEARCHKEY_PROVIDER_TYPE);
|
||||||
query.setMethod(SearchMethod.SEARCHMETHOD_EQUALS);
|
query.setMethod(SearchMethod.SEARCHMETHOD_EQUALS);
|
||||||
query.setValue(this.idpType.toString());
|
query.setValue(this.idpType.toString());
|
||||||
this.mgmtService.SearchIdps().then(idps => {
|
console.log(this.idpType);
|
||||||
|
console.log(query.toObject());
|
||||||
|
this.mgmtService.SearchIdps(undefined, undefined, [query]).then(idps => {
|
||||||
this.availableIdps = idps.toObject().resultList;
|
this.availableIdps = idps.toObject().resultList;
|
||||||
});
|
});
|
||||||
} else if (this.serviceType === PolicyComponentServiceType.ADMIN) {
|
} else if (this.serviceType === PolicyComponentServiceType.ADMIN) {
|
||||||
|
@@ -1,60 +1,76 @@
|
|||||||
<app-detail-layout [backRouterLink]="backroutes" [title]="'ORG.POLICY.LOGIN_POLICY.TITLECREATE' | translate"
|
<app-detail-layout [backRouterLink]="[ serviceType === PolicyComponentServiceType.ADMIN ? '/iam' : '/org']"
|
||||||
[description]="'ORG.POLICY.LOGIN_POLICY.DESCRIPTIONCREATE' | translate">
|
[title]="'POLICY.LOGIN_POLICY.TITLE' | translate"
|
||||||
<ng-container *ngIf="(['policy.delete'] | hasRole | async) && serviceType == PolicyComponentServiceType.MGMT">
|
[description]="(serviceType==PolicyComponentServiceType.MGMT ? 'POLICY.LOGIN_POLICY.DESCRIPTIONCREATEMGMT' : PolicyComponentServiceType.ADMIN ? 'POLICY.LOGIN_POLICY.DESCRIPTIONCREATEADMIN' : '') | translate">
|
||||||
<button matTooltip="{{'ORG.POLICY.DELETE' | translate}}" color="warn" (click)="deletePolicy()"
|
<p class="default" *ngIf="isDefault"> {{'POLICY.DEFAULTLABEL' | translate}}</p>
|
||||||
mat-stroked-button>
|
|
||||||
{{'ORG.POLICY.DELETE' | translate}}
|
<div class="spinner-wr">
|
||||||
</button>
|
<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" 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" matTooltip="{{'POLICY.CREATECUSTOM' | translate}}" (click)="savePolicy()"
|
||||||
|
mat-raised-button>
|
||||||
|
{{'POLICY.CREATECUSTOM' | translate}}
|
||||||
|
</button>
|
||||||
|
</ng-template>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<div class="content" *ngIf="loginData">
|
<div class="content" *ngIf="loginData">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span class="left-desc">{{'ORG.POLICY.DATA.ALLOWUSERNAMEPASSWORD' | translate}}</span>
|
<mat-slide-toggle class="toggle" color="primary" [disabled]="disabled" ngDefaultControl
|
||||||
<span class="fill-space"></span>
|
|
||||||
<mat-slide-toggle color="primary" name="hasNumber" ngDefaultControl
|
|
||||||
[(ngModel)]="loginData.allowUsernamePassword">
|
[(ngModel)]="loginData.allowUsernamePassword">
|
||||||
|
{{'POLICY.DATA.ALLOWUSERNAMEPASSWORD' | translate}}
|
||||||
</mat-slide-toggle>
|
</mat-slide-toggle>
|
||||||
|
<p>{{'POLICY.DATA.ALLOWUSERNAMEPASSWORD_DESC' | translate}}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span class="left-desc">{{'ORG.POLICY.DATA.ALLOWREGISTER' | translate}}</span>
|
<mat-slide-toggle class="toggle" color="primary" [disabled]="disabled" ngDefaultControl
|
||||||
<span class="fill-space"></span>
|
[(ngModel)]="loginData.allowRegister">
|
||||||
<mat-slide-toggle color="primary" name="hasNumber" ngDefaultControl [(ngModel)]="loginData.allowRegister">
|
{{'POLICY.DATA.ALLOWREGISTER' | translate}}
|
||||||
</mat-slide-toggle>
|
</mat-slide-toggle>
|
||||||
|
<p> {{'POLICY.DATA.ALLOWREGISTER_DESC' | translate}} </p>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span class="left-desc">{{'ORG.POLICY.DATA.ALLOWEXTERNALIDP' | translate}}</span>
|
<mat-slide-toggle class="toggle" color="primary" [disabled]="disabled" ngDefaultControl
|
||||||
<span class="fill-space"></span>
|
|
||||||
<mat-slide-toggle color="primary" name="hasNumber" ngDefaultControl
|
|
||||||
[(ngModel)]="loginData.allowExternalIdp">
|
[(ngModel)]="loginData.allowExternalIdp">
|
||||||
|
{{'POLICY.DATA.ALLOWEXTERNALIDP' | translate}}
|
||||||
</mat-slide-toggle>
|
</mat-slide-toggle>
|
||||||
|
<p> {{'POLICY.DATA.ALLOWEXTERNALIDP_DESC' | translate}} </p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="subheader">{{'LOGINPOLICY.IDPS' | translate}}</p>
|
<h3 class="subheader">{{'LOGINPOLICY.IDPS' | translate}}</h3>
|
||||||
|
|
||||||
<div class="idps">
|
<div class="idps">
|
||||||
<div class="idp" *ngFor="let idp of idps">
|
<div class="idp" [ngClass]="{'disabled': disabled}" *ngFor="let idp of idps">
|
||||||
<mat-icon (click)="removeIdp(idp)" class="rm">remove_circle</mat-icon>
|
<button [disabled]="disabled" mat-icon-button (click)="removeIdp(idp)" class="rm">
|
||||||
<span>{{idp.name}}</span>
|
<mat-icon matTooltip="{{'ACTIONS.REMOVE' | translate}}">
|
||||||
|
remove_circle</mat-icon>
|
||||||
|
</button>
|
||||||
|
<span class="name">{{idp.name}}</span>
|
||||||
<span class="meta">{{ 'IDP.TYPE' | translate }}: {{ 'IDP.TYPES.'+idp.type | translate }}</span>
|
<span class="meta">{{ 'IDP.TYPE' | translate }}: {{ 'IDP.TYPES.'+idp.type | translate }}</span>
|
||||||
<span class="meta">{{ 'IDP.ID' | translate }}: {{idp.idpConfigId}}</span>
|
<span class="meta">{{ 'IDP.ID' | translate }}: {{idp.idpConfigId}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="new-idp" (click)="openDialog()">
|
<div *ngIf="!disabled" class="new-idp" (click)="openDialog()">
|
||||||
<mat-icon>add</mat-icon>
|
<mat-icon>add</mat-icon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="btn-container">
|
<button [disabled]="disabled" class="save-button" (click)="savePolicy()" color="primary" type="submit"
|
||||||
<button (click)="savePolicy()" color="primary" type="submit"
|
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
|
||||||
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<ng-template appHasRole [appHasRole]="['org.idp.read']">
|
<ng-template appHasRole [appHasRole]="['org.idp.read']">
|
||||||
<app-card title="{{ 'IDP.LIST.TITLE' | translate }}" description="{{ 'IDP.LIST.DESCRIPTION' | translate }}">
|
<h2>{{ 'IDP.LIST.TITLE' | translate }}</h2>
|
||||||
|
<p>{{ 'IDP.LIST.DESCRIPTION' | translate }}</p>
|
||||||
<app-idp-table [service]="service" [serviceType]="serviceType"
|
<app-idp-table [service]="service" [serviceType]="serviceType"
|
||||||
[disabled]="(['iam.idp.write'] | hasRole | async) == false">
|
[disabled]="([serviceType == PolicyComponentServiceType.ADMIN ? 'iam.idp.write' : serviceType == PolicyComponentServiceType.MGMT ? 'org.idp.write' : ''] | hasRole | async) == false">
|
||||||
</app-idp-table>
|
</app-idp-table>
|
||||||
</app-card>
|
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-detail-layout>
|
</app-detail-layout>
|
@@ -1,46 +1,36 @@
|
|||||||
|
.default {
|
||||||
|
color: #5282c1;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
.spinner-wr {
|
||||||
border-radius: .5rem;
|
margin: .5rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
padding-top: 1rem;
|
padding-top: 1rem;
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
display: flex;
|
.toggle {
|
||||||
align-items: center;
|
margin: .3rem 0;
|
||||||
padding: .5rem 0;
|
|
||||||
|
|
||||||
.left-desc {
|
|
||||||
color: #8795a1;
|
|
||||||
font-size: .9rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fill-space {
|
p {
|
||||||
flex: 1;
|
margin-top: .5rem;
|
||||||
}
|
font-size: 14px;
|
||||||
|
color: var(--grey);
|
||||||
.length-wrapper {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-container {
|
.save-button {
|
||||||
display: flex;
|
margin-top: 3rem;
|
||||||
justify-content: flex-end;
|
display: block;
|
||||||
width: 100%;
|
padding: .5rem 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
.idp-table-card {
|
||||||
margin-top: 3rem;
|
width: 100%;
|
||||||
display: block;
|
|
||||||
padding: .5rem 4rem;
|
|
||||||
border-radius: .5rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.subheader {
|
.subheader {
|
||||||
@@ -58,10 +48,16 @@ button {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin: .5rem;
|
margin: .5rem;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border: 1px solid #8795a1;
|
border: 1px solid var(--grey);
|
||||||
border-radius: .5rem;
|
border-radius: .5rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
min-height: 70px;
|
||||||
|
min-width: 150px;
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
@@ -69,6 +65,7 @@ button {
|
|||||||
|
|
||||||
.meta {
|
.meta {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
color: var(--grey);
|
||||||
}
|
}
|
||||||
|
|
||||||
.rm {
|
.rm {
|
||||||
@@ -77,10 +74,16 @@ button {
|
|||||||
left: 0;
|
left: 0;
|
||||||
transform: translateX(-50%) translateY(-50%);
|
transform: translateX(-50%) translateY(-50%);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
&[disabled] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:not(.disabled) {
|
||||||
background-color: #ffffff10;
|
&:hover {
|
||||||
|
background-color: #ffffff10;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import { Component, Injector, OnDestroy, Type } from '@angular/core';
|
import { Component, Injector, OnDestroy, Type } from '@angular/core';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { Empty } from 'google-protobuf/google/protobuf/empty_pb';
|
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { switchMap } from 'rxjs/operators';
|
import { switchMap } from 'rxjs/operators';
|
||||||
import {
|
import {
|
||||||
@@ -11,11 +10,11 @@ import {
|
|||||||
IdpView as AdminIdpView,
|
IdpView as AdminIdpView,
|
||||||
} from 'src/app/proto/generated/admin_pb';
|
} from 'src/app/proto/generated/admin_pb';
|
||||||
import {
|
import {
|
||||||
IdpProviderType,
|
IdpProviderType,
|
||||||
IdpProviderView as MgmtIdpProviderView,
|
IdpProviderView as MgmtIdpProviderView,
|
||||||
IdpView as MgmtIdpView,
|
IdpView as MgmtIdpView,
|
||||||
LoginPolicy,
|
LoginPolicy,
|
||||||
LoginPolicyView, OrgDomainView,
|
LoginPolicyView,
|
||||||
} from 'src/app/proto/generated/management_pb';
|
} from 'src/app/proto/generated/management_pb';
|
||||||
import { AdminService } from 'src/app/services/admin.service';
|
import { AdminService } from 'src/app/services/admin.service';
|
||||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||||
@@ -34,18 +33,19 @@ export class LoginPolicyComponent implements OnDestroy {
|
|||||||
|
|
||||||
private sub: Subscription = new Subscription();
|
private sub: Subscription = new Subscription();
|
||||||
public service!: ManagementService | AdminService;
|
public service!: ManagementService | AdminService;
|
||||||
PolicyComponentServiceType: any = PolicyComponentServiceType;
|
public PolicyComponentServiceType: any = PolicyComponentServiceType;
|
||||||
public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
|
public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
|
||||||
public idps: MgmtIdpProviderView.AsObject[] | AdminIdpProviderView.AsObject[] = [];
|
public idps: MgmtIdpProviderView.AsObject[] | AdminIdpProviderView.AsObject[] = [];
|
||||||
|
|
||||||
|
public loading: boolean = false;
|
||||||
|
public disabled: boolean = true;
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router,
|
|
||||||
private toast: ToastService,
|
private toast: ToastService,
|
||||||
private dialog: MatDialog,
|
private dialog: MatDialog,
|
||||||
private injector: Injector,
|
private injector: Injector,
|
||||||
) {
|
) {
|
||||||
this.sub = this.route.data.pipe(switchMap(data => {
|
this.sub = this.route.data.pipe(switchMap(data => {
|
||||||
console.log(data.serviceType);
|
|
||||||
this.serviceType = data.serviceType;
|
this.serviceType = data.serviceType;
|
||||||
switch (this.serviceType) {
|
switch (this.serviceType) {
|
||||||
case PolicyComponentServiceType.MGMT:
|
case PolicyComponentServiceType.MGMT:
|
||||||
@@ -58,15 +58,20 @@ export class LoginPolicyComponent implements OnDestroy {
|
|||||||
|
|
||||||
return this.route.params;
|
return this.route.params;
|
||||||
})).subscribe(() => {
|
})).subscribe(() => {
|
||||||
this.getData().then(data => {
|
this.fetchData();
|
||||||
if (data) {
|
});
|
||||||
this.loginData = data.toObject();
|
}
|
||||||
}
|
|
||||||
});
|
private fetchData(): void {
|
||||||
this.getIdps().then(idps => {
|
this.getData().then(data => {
|
||||||
console.log(idps);
|
if (data) {
|
||||||
this.idps = idps;
|
this.loginData = data.toObject();
|
||||||
});
|
this.loading = false;
|
||||||
|
this.disabled = ((this.loginData as LoginPolicyView.AsObject)?.pb_default) ?? false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.getIdps().then(idps => {
|
||||||
|
this.idps = idps;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +112,11 @@ export class LoginPolicyComponent implements OnDestroy {
|
|||||||
mgmtreq.setAllowExternalIdp(this.loginData.allowExternalIdp);
|
mgmtreq.setAllowExternalIdp(this.loginData.allowExternalIdp);
|
||||||
mgmtreq.setAllowRegister(this.loginData.allowRegister);
|
mgmtreq.setAllowRegister(this.loginData.allowRegister);
|
||||||
mgmtreq.setAllowUsernamePassword(this.loginData.allowUsernamePassword);
|
mgmtreq.setAllowUsernamePassword(this.loginData.allowUsernamePassword);
|
||||||
return (this.service as ManagementService).UpdateLoginPolicy(mgmtreq);
|
if ((this.loginData as LoginPolicyView.AsObject).pb_default) {
|
||||||
|
return (this.service as ManagementService).CreateLoginPolicy(mgmtreq);
|
||||||
|
} else {
|
||||||
|
return (this.service as ManagementService).UpdateLoginPolicy(mgmtreq);
|
||||||
|
}
|
||||||
case PolicyComponentServiceType.ADMIN:
|
case PolicyComponentServiceType.ADMIN:
|
||||||
const adminreq = new DefaultLoginPolicy();
|
const adminreq = new DefaultLoginPolicy();
|
||||||
adminreq.setAllowExternalIdp(this.loginData.allowExternalIdp);
|
adminreq.setAllowExternalIdp(this.loginData.allowExternalIdp);
|
||||||
@@ -119,25 +128,27 @@ export class LoginPolicyComponent implements OnDestroy {
|
|||||||
|
|
||||||
public savePolicy(): void {
|
public savePolicy(): void {
|
||||||
this.updateData().then(() => {
|
this.updateData().then(() => {
|
||||||
switch (this.serviceType) {
|
this.toast.showInfo('POLICY.LOGIN_POLICY.SAVED', true);
|
||||||
case PolicyComponentServiceType.MGMT:
|
this.loading = true;
|
||||||
this.router.navigate(['org']);
|
setTimeout(() => {
|
||||||
break;
|
this.fetchData();
|
||||||
case PolicyComponentServiceType.ADMIN:
|
}, 2000);
|
||||||
this.router.navigate(['iam']);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
this.toast.showError(error);
|
this.toast.showError(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public deletePolicy(): Promise<Empty> {
|
public removePolicy(): void {
|
||||||
switch (this.serviceType) {
|
if (this.serviceType === PolicyComponentServiceType.MGMT) {
|
||||||
case PolicyComponentServiceType.MGMT:
|
(this.service as ManagementService).RemoveLoginPolicy().then(() => {
|
||||||
return (this.service as ManagementService).RemoveLoginPolicy();
|
this.toast.showInfo('POLICY.TOAST.RESETSUCCESS', true);
|
||||||
case PolicyComponentServiceType.ADMIN:
|
this.loading = true;
|
||||||
return (this.service as AdminService).GetDefaultLoginPolicy();
|
setTimeout(() => {
|
||||||
|
this.fetchData();
|
||||||
|
}, 2000);
|
||||||
|
}).catch(error => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +162,14 @@ export class LoginPolicyComponent implements OnDestroy {
|
|||||||
|
|
||||||
dialogRef.afterClosed().subscribe(resp => {
|
dialogRef.afterClosed().subscribe(resp => {
|
||||||
if (resp && resp.idp && resp.type) {
|
if (resp && resp.idp && resp.type) {
|
||||||
this.addIdp(resp.idp, resp.type);
|
this.addIdp(resp.idp, resp.type).then(() => {
|
||||||
|
this.loading = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.fetchData();
|
||||||
|
}, 2000);
|
||||||
|
}).catch(error => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -169,23 +187,29 @@ export class LoginPolicyComponent implements OnDestroy {
|
|||||||
public removeIdp(idp: AdminIdpProviderView.AsObject | MgmtIdpProviderView.AsObject): void {
|
public removeIdp(idp: AdminIdpProviderView.AsObject | MgmtIdpProviderView.AsObject): void {
|
||||||
switch (this.serviceType) {
|
switch (this.serviceType) {
|
||||||
case PolicyComponentServiceType.MGMT:
|
case PolicyComponentServiceType.MGMT:
|
||||||
(this.service as ManagementService).RemoveIdpProviderFromLoginPolicy(idp.idpConfigId);
|
(this.service as ManagementService).RemoveIdpProviderFromLoginPolicy(idp.idpConfigId).then(() => {
|
||||||
|
const index = this.idps.findIndex(temp => temp === idp);
|
||||||
|
if (index > -1) {
|
||||||
|
this.idps.splice(index, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
case PolicyComponentServiceType.ADMIN:
|
case PolicyComponentServiceType.ADMIN:
|
||||||
(this.service as AdminService).RemoveIdpProviderFromDefaultLoginPolicy(idp.idpConfigId);
|
(this.service as AdminService).RemoveIdpProviderFromDefaultLoginPolicy(idp.idpConfigId).then(() => {
|
||||||
|
const index = this.idps.findIndex(temp => temp === idp);
|
||||||
|
if (index > -1) {
|
||||||
|
this.idps.splice(index, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public get backroutes(): string[] {
|
public get isDefault(): boolean {
|
||||||
switch (this.serviceType) {
|
if (this.loginData && this.serviceType === PolicyComponentServiceType.MGMT) {
|
||||||
case PolicyComponentServiceType.MGMT:
|
return (this.loginData as LoginPolicyView.AsObject).pb_default;
|
||||||
return ['/org'];
|
} else {
|
||||||
case PolicyComponentServiceType.ADMIN:
|
return false;
|
||||||
return ['/iam'];
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -2,21 +2,22 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { CardModule } from 'src/app/modules/card/card.module';
|
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
||||||
|
import { CardModule } from 'src/app/modules/card/card.module';
|
||||||
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module';
|
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module';
|
||||||
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe.module';
|
import { IdpTableModule } from 'src/app/modules/idp-table/idp-table.module';
|
||||||
|
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
|
||||||
|
|
||||||
import { AddIdpDialogModule } from './add-idp-dialog/add-idp-dialog.module';
|
import { AddIdpDialogModule } from './add-idp-dialog/add-idp-dialog.module';
|
||||||
import { LoginPolicyRoutingModule } from './login-policy-routing.module';
|
import { LoginPolicyRoutingModule } from './login-policy-routing.module';
|
||||||
import { LoginPolicyComponent } from './login-policy.component';
|
import { LoginPolicyComponent } from './login-policy.component';
|
||||||
import { IdpTableModule } from 'src/app/modules/idp-table/idp-table.module';
|
|
||||||
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [LoginPolicyComponent],
|
declarations: [LoginPolicyComponent],
|
||||||
@@ -37,6 +38,7 @@ import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
|||||||
DetailLayoutModule,
|
DetailLayoutModule,
|
||||||
AddIdpDialogModule,
|
AddIdpDialogModule,
|
||||||
IdpTableModule,
|
IdpTableModule,
|
||||||
|
MatProgressSpinnerModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class LoginPolicyModule { }
|
export class LoginPolicyModule { }
|
||||||
|
@@ -0,0 +1,20 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
|
||||||
|
import { OrgIamPolicyComponent } from './org-iam-policy.component';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: OrgIamPolicyComponent,
|
||||||
|
data: {
|
||||||
|
animation: 'DetailPage',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule],
|
||||||
|
})
|
||||||
|
export class OrgIamPolicyRoutingModule { }
|
@@ -0,0 +1,26 @@
|
|||||||
|
<app-detail-layout [backRouterLink]="[ serviceType === PolicyComponentServiceType.ADMIN ? '/iam' : '/org']"
|
||||||
|
[title]="'POLICY.IAM_POLICY.TITLECREATE' | translate" [description]="'POLICY.IAM_POLICY.DESCRIPTION' | translate">
|
||||||
|
<p class="default" *ngIf="isDefault"> {{'POLICY.DEFAULTLABEL' | translate}}</p>
|
||||||
|
|
||||||
|
<ng-template appHasRole [appHasRole]="['iam.policy.delete']">
|
||||||
|
<button *ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault"
|
||||||
|
matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="removePolicy()" mat-stroked-button>
|
||||||
|
{{'POLICY.RESET' | translate}}
|
||||||
|
</button>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<div class="content" *ngIf="iamData">
|
||||||
|
<div class="row">
|
||||||
|
<span class="left-desc">{{'POLICY.DATA.USERLOGINMUSTBEDOMAIN' | translate}}</span>
|
||||||
|
<span class="fill-space"></span>
|
||||||
|
<mat-slide-toggle color="primary" name="hasNumber" ngDefaultControl
|
||||||
|
[(ngModel)]="iamData.userLoginMustBeDomain">
|
||||||
|
</mat-slide-toggle>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="btn-container">
|
||||||
|
<button (click)="savePolicy()" color="primary" type="submit"
|
||||||
|
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
|
||||||
|
</div>
|
||||||
|
</app-detail-layout>
|
@@ -1,6 +1,6 @@
|
|||||||
|
.default {
|
||||||
button {
|
color: #5282c1;
|
||||||
border-radius: .5rem;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
@@ -12,10 +12,9 @@ button {
|
|||||||
.row {
|
.row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: .5rem 0;
|
padding: .3rem 0;
|
||||||
|
|
||||||
.left-desc {
|
.left-desc {
|
||||||
color: #8795a1;
|
|
||||||
font-size: .9rem;
|
font-size: .9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,6 +38,5 @@ button {
|
|||||||
margin-top: 3rem;
|
margin-top: 3rem;
|
||||||
display: block;
|
display: block;
|
||||||
padding: .5rem 4rem;
|
padding: .5rem 4rem;
|
||||||
border-radius: .5rem;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,20 +1,20 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { PasswordIamPolicyComponent } from './password-iam-policy.component';
|
import { OrgIamPolicyComponent } from './org-iam-policy.component';
|
||||||
|
|
||||||
describe('PasswordIamPolicyComponent', () => {
|
describe('OrgIamPolicyComponent', () => {
|
||||||
let component: PasswordIamPolicyComponent;
|
let component: OrgIamPolicyComponent;
|
||||||
let fixture: ComponentFixture<PasswordIamPolicyComponent>;
|
let fixture: ComponentFixture<OrgIamPolicyComponent>;
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [PasswordIamPolicyComponent],
|
declarations: [OrgIamPolicyComponent],
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(PasswordIamPolicyComponent);
|
fixture = TestBed.createComponent(OrgIamPolicyComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
@@ -0,0 +1,136 @@
|
|||||||
|
import { Component, Injector, Input, OnDestroy, Type } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
import { switchMap } from 'rxjs/operators';
|
||||||
|
import { OrgIamPolicyView as AdminOrgIamPolicyView } from 'src/app/proto/generated/admin_pb';
|
||||||
|
import { Org } from 'src/app/proto/generated/auth_pb';
|
||||||
|
import { OrgIamPolicyView as MgmtOrgIamPolicyView } from 'src/app/proto/generated/management_pb';
|
||||||
|
import { AdminService } from 'src/app/services/admin.service';
|
||||||
|
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||||
|
import { StorageService } from 'src/app/services/storage.service';
|
||||||
|
import { ToastService } from 'src/app/services/toast.service';
|
||||||
|
|
||||||
|
import { PolicyComponentServiceType } from '../policy-component-types.enum';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-org-iam-policy',
|
||||||
|
templateUrl: './org-iam-policy.component.html',
|
||||||
|
styleUrls: ['./org-iam-policy.component.scss'],
|
||||||
|
})
|
||||||
|
export class OrgIamPolicyComponent implements OnDestroy {
|
||||||
|
@Input() service!: AdminService;
|
||||||
|
private managementService!: ManagementService;
|
||||||
|
public serviceType!: PolicyComponentServiceType;
|
||||||
|
|
||||||
|
public iamData!: AdminOrgIamPolicyView.AsObject | MgmtOrgIamPolicyView.AsObject;
|
||||||
|
|
||||||
|
private sub: Subscription = new Subscription();
|
||||||
|
private org!: Org.AsObject;
|
||||||
|
|
||||||
|
public PolicyComponentServiceType: any = PolicyComponentServiceType;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private toast: ToastService,
|
||||||
|
private sessionStorage: StorageService,
|
||||||
|
private injector: Injector,
|
||||||
|
private adminService: AdminService,
|
||||||
|
) {
|
||||||
|
const temporg = this.sessionStorage.getItem('organization') as Org.AsObject;
|
||||||
|
if (temporg) {
|
||||||
|
this.org = temporg;
|
||||||
|
}
|
||||||
|
this.sub = this.route.data.pipe(switchMap(data => {
|
||||||
|
this.serviceType = data.serviceType;
|
||||||
|
if (this.serviceType === PolicyComponentServiceType.MGMT) {
|
||||||
|
this.managementService = this.injector.get(ManagementService as Type<ManagementService>);
|
||||||
|
}
|
||||||
|
return this.route.params;
|
||||||
|
})).subscribe(_ => {
|
||||||
|
this.fetchData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnDestroy(): void {
|
||||||
|
this.sub.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
public fetchData(): void {
|
||||||
|
this.getData().then(data => {
|
||||||
|
if (data) {
|
||||||
|
this.iamData = data.toObject();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getData(): Promise<AdminOrgIamPolicyView | MgmtOrgIamPolicyView | undefined> {
|
||||||
|
switch (this.serviceType) {
|
||||||
|
case PolicyComponentServiceType.MGMT:
|
||||||
|
return this.managementService.GetMyOrgIamPolicy();
|
||||||
|
case PolicyComponentServiceType.ADMIN:
|
||||||
|
if (this.org?.id) {
|
||||||
|
return this.adminService.GetOrgIamPolicy(this.org.id);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public savePolicy(): void {
|
||||||
|
switch (this.serviceType) {
|
||||||
|
case PolicyComponentServiceType.MGMT:
|
||||||
|
if ((this.iamData as MgmtOrgIamPolicyView.AsObject).pb_default) {
|
||||||
|
this.adminService.CreateOrgIamPolicy(
|
||||||
|
this.org.id,
|
||||||
|
this.iamData.userLoginMustBeDomain,
|
||||||
|
).then(() => {
|
||||||
|
this.toast.showInfo('POLICY.TOAST.SET', true);
|
||||||
|
}).catch(error => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
this.adminService.UpdateOrgIamPolicy(
|
||||||
|
this.org.id,
|
||||||
|
this.iamData.userLoginMustBeDomain,
|
||||||
|
).then(() => {
|
||||||
|
this.toast.showInfo('POLICY.TOAST.SET', true);
|
||||||
|
}).catch(error => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PolicyComponentServiceType.ADMIN:
|
||||||
|
// update Default org iam policy?
|
||||||
|
this.adminService.UpdateOrgIamPolicy(
|
||||||
|
this.org.id,
|
||||||
|
this.iamData.userLoginMustBeDomain,
|
||||||
|
).then(() => {
|
||||||
|
this.toast.showInfo('POLICY.TOAST.SET', true);
|
||||||
|
}).catch(error => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public removePolicy(): void {
|
||||||
|
if (this.serviceType === PolicyComponentServiceType.MGMT) {
|
||||||
|
this.adminService.RemoveOrgIamPolicy(this.org.id).then(() => {
|
||||||
|
this.toast.showInfo('POLICY.TOAST.RESETSUCCESS', true);
|
||||||
|
setTimeout(() => {
|
||||||
|
this.fetchData();
|
||||||
|
}, 1000);
|
||||||
|
}).catch(error => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isDefault(): boolean {
|
||||||
|
if (this.iamData && this.serviceType === PolicyComponentServiceType.MGMT) {
|
||||||
|
return (this.iamData as MgmtOrgIamPolicyView.AsObject).pb_default;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,34 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||||
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
||||||
|
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module';
|
||||||
|
|
||||||
|
import { OrgIamPolicyRoutingModule } from './org-iam-policy-routing.module';
|
||||||
|
import { OrgIamPolicyComponent } from './org-iam-policy.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [OrgIamPolicyComponent],
|
||||||
|
imports: [
|
||||||
|
OrgIamPolicyRoutingModule,
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
MatInputModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
MatButtonModule,
|
||||||
|
MatSlideToggleModule,
|
||||||
|
MatIconModule,
|
||||||
|
HasRoleModule,
|
||||||
|
MatTooltipModule,
|
||||||
|
TranslateModule,
|
||||||
|
DetailLayoutModule,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class OrgIamPolicyModule { }
|
@@ -1,7 +1,6 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
|
||||||
import { PolicyComponentAction } from '../policy-component-action.enum';
|
|
||||||
import { PasswordAgePolicyComponent } from './password-age-policy.component';
|
import { PasswordAgePolicyComponent } from './password-age-policy.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
@@ -10,15 +9,6 @@ const routes: Routes = [
|
|||||||
component: PasswordAgePolicyComponent,
|
component: PasswordAgePolicyComponent,
|
||||||
data: {
|
data: {
|
||||||
animation: 'DetailPage',
|
animation: 'DetailPage',
|
||||||
action: PolicyComponentAction.MODIFY,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'create',
|
|
||||||
component: PasswordAgePolicyComponent,
|
|
||||||
data: {
|
|
||||||
animation: 'DetailPage',
|
|
||||||
action: PolicyComponentAction.CREATE,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@@ -1,19 +1,15 @@
|
|||||||
<app-detail-layout [backRouterLink]="[ '/org']" [title]="title ? (title | translate) : ''"
|
<app-detail-layout [backRouterLink]="[ serviceType === PolicyComponentServiceType.ADMIN ? '/iam' : '/org']"
|
||||||
[description]="desc ? (desc | translate) : ''">
|
[title]="'POLICY.PWD_AGE.TITLE' | translate" [description]="'POLICY.PWD_AGE.DESCRIPTION' | translate">
|
||||||
<ng-template appHasRole [appHasRole]="['iam.policy.write']">
|
<ng-template appHasRole [appHasRole]="['policy.delete']">
|
||||||
<button matTooltip="{{'ORG.POLICY.DELETE' | translate}}" color="warn" (click)="deletePolicy()"
|
<button *ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault"
|
||||||
mat-stroked-button>
|
matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="removePolicy()" mat-stroked-button>
|
||||||
{{'ORG.POLICY.DELETE' | translate}}
|
{{'POLICY.RESET' | translate}}
|
||||||
</button>
|
</button>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<div class="content" *ngIf="ageData">
|
<div class="content" *ngIf="ageData">
|
||||||
<mat-form-field class="description-formfield" appearance="outline">
|
|
||||||
<mat-label>{{ 'ORG.POLICY.DATA.DESCRIPTION' | translate }}</mat-label>
|
|
||||||
<input matInput name="description" ngDefaultControl [(ngModel)]="ageData.description" required />
|
|
||||||
</mat-form-field>
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span class="left-desc">{{'ORG.POLICY.DATA.EXPIREWARNDAYS' | translate}}</span>
|
<span class="left-desc">{{'POLICY.DATA.EXPIREWARNDAYS' | translate}}</span>
|
||||||
<span class="fill-space"></span>
|
<span class="fill-space"></span>
|
||||||
<div class="length-wrapper">
|
<div class="length-wrapper">
|
||||||
<button mat-icon-button (click)="incrementExpireWarnDays()">
|
<button mat-icon-button (click)="incrementExpireWarnDays()">
|
||||||
@@ -27,7 +23,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span class="left-desc">{{'ORG.POLICY.DATA.MAXAGEDAYS' | translate}}</span>
|
<span class="left-desc">{{'POLICY.DATA.MAXAGEDAYS' | translate}}</span>
|
||||||
<span class="fill-space"></span>
|
<span class="fill-space"></span>
|
||||||
<div class="length-wrapper">
|
<div class="length-wrapper">
|
||||||
<button mat-icon-button (click)="incrementMaxAgeDays()">
|
<button mat-icon-button (click)="incrementMaxAgeDays()">
|
||||||
|
@@ -1,8 +1,3 @@
|
|||||||
|
|
||||||
button {
|
|
||||||
border-radius: .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
padding-top: 1rem;
|
padding-top: 1rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -12,10 +7,10 @@ button {
|
|||||||
.row {
|
.row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: .5rem 0;
|
padding: .3rem 0;
|
||||||
|
|
||||||
.left-desc {
|
.left-desc {
|
||||||
color: #8795a1;
|
color: var(--grey);
|
||||||
font-size: .9rem;
|
font-size: .9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,6 +21,7 @@ button {
|
|||||||
.length-wrapper {
|
.length-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
margin-right: -.6rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,6 +35,5 @@ button {
|
|||||||
margin-top: 3rem;
|
margin-top: 3rem;
|
||||||
display: block;
|
display: block;
|
||||||
padding: .5rem 4rem;
|
padding: .5rem 4rem;
|
||||||
border-radius: .5rem;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,17 +1,14 @@
|
|||||||
import { Component, OnDestroy } from '@angular/core';
|
import { Component, Injector, OnDestroy, Type } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { switchMap } from 'rxjs/operators';
|
import { switchMap } from 'rxjs/operators';
|
||||||
import {
|
import { DefaultPasswordAgePolicyView } from 'src/app/proto/generated/admin_pb';
|
||||||
OrgIamPolicy,
|
import { PasswordAgePolicyView } from 'src/app/proto/generated/management_pb';
|
||||||
PasswordAgePolicy,
|
import { AdminService } from 'src/app/services/admin.service';
|
||||||
PasswordComplexityPolicy,
|
|
||||||
PasswordLockoutPolicy,
|
|
||||||
} from 'src/app/proto/generated/management_pb';
|
|
||||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||||
import { ToastService } from 'src/app/services/toast.service';
|
import { ToastService } from 'src/app/services/toast.service';
|
||||||
|
|
||||||
import { PolicyComponentAction } from '../policy-component-action.enum';
|
import { PolicyComponentServiceType } from '../policy-component-types.enum';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -20,37 +17,37 @@ import { PolicyComponentAction } from '../policy-component-action.enum';
|
|||||||
styleUrls: ['./password-age-policy.component.scss'],
|
styleUrls: ['./password-age-policy.component.scss'],
|
||||||
})
|
})
|
||||||
export class PasswordAgePolicyComponent implements OnDestroy {
|
export class PasswordAgePolicyComponent implements OnDestroy {
|
||||||
public title: string = '';
|
public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
|
||||||
public desc: string = '';
|
public service!: AdminService | ManagementService;
|
||||||
|
|
||||||
componentAction: PolicyComponentAction = PolicyComponentAction.CREATE;
|
public ageData!: PasswordAgePolicyView.AsObject | DefaultPasswordAgePolicyView.AsObject;
|
||||||
|
|
||||||
public PolicyComponentAction: any = PolicyComponentAction;
|
|
||||||
|
|
||||||
public ageData!: PasswordAgePolicy.AsObject;
|
|
||||||
|
|
||||||
private sub: Subscription = new Subscription();
|
private sub: Subscription = new Subscription();
|
||||||
|
|
||||||
|
public PolicyComponentServiceType: any = PolicyComponentServiceType;
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private mgmtService: ManagementService,
|
|
||||||
private router: Router,
|
|
||||||
private toast: ToastService,
|
private toast: ToastService,
|
||||||
|
private injector: Injector,
|
||||||
) {
|
) {
|
||||||
this.sub = this.route.data.pipe(switchMap(data => {
|
this.sub = this.route.data.pipe(switchMap(data => {
|
||||||
this.componentAction = data.action;
|
this.serviceType = data.serviceType;
|
||||||
return this.route.params;
|
switch (this.serviceType) {
|
||||||
})).subscribe(params => {
|
case PolicyComponentServiceType.MGMT:
|
||||||
this.title = 'ORG.POLICY.PWD_AGE.TITLECREATE';
|
this.service = this.injector.get(ManagementService as Type<ManagementService>);
|
||||||
this.desc = 'ORG.POLICY.PWD_AGE.DESCRIPTIONCREATE';
|
break;
|
||||||
|
case PolicyComponentServiceType.ADMIN:
|
||||||
if (this.componentAction === PolicyComponentAction.MODIFY) {
|
this.service = this.injector.get(AdminService as Type<AdminService>);
|
||||||
this.getData(params).then(data => {
|
break;
|
||||||
if (data) {
|
|
||||||
this.ageData = data.toObject() as PasswordAgePolicy.AsObject;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return this.route.params;
|
||||||
|
})).subscribe(() => {
|
||||||
|
this.getData().then(data => {
|
||||||
|
if (data) {
|
||||||
|
this.ageData = data.toObject();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,19 +55,28 @@ export class PasswordAgePolicyComponent implements OnDestroy {
|
|||||||
this.sub.unsubscribe();
|
this.sub.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getData(params: any):
|
private async getData():
|
||||||
Promise<PasswordLockoutPolicy | PasswordAgePolicy | PasswordComplexityPolicy | OrgIamPolicy | undefined> {
|
Promise<PasswordAgePolicyView | DefaultPasswordAgePolicyView> {
|
||||||
this.title = 'ORG.POLICY.PWD_AGE.TITLE';
|
|
||||||
this.desc = 'ORG.POLICY.PWD_AGE.DESCRIPTION';
|
switch (this.serviceType) {
|
||||||
return this.mgmtService.GetPasswordAgePolicy();
|
case PolicyComponentServiceType.MGMT:
|
||||||
|
return (this.service as ManagementService).GetPasswordAgePolicy();
|
||||||
|
case PolicyComponentServiceType.ADMIN:
|
||||||
|
return (this.service as AdminService).GetDefaultPasswordAgePolicy();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public deletePolicy(): void {
|
public removePolicy(): void {
|
||||||
this.mgmtService.DeletePasswordAgePolicy(this.ageData.id).then(() => {
|
if (this.serviceType === PolicyComponentServiceType.MGMT) {
|
||||||
this.toast.showInfo('Successfully deleted');
|
(this.service as ManagementService).RemovePasswordAgePolicy().then(() => {
|
||||||
}).catch(error => {
|
this.toast.showInfo('POLICY.TOAST.RESETSUCCESS', true);
|
||||||
this.toast.showError(error);
|
setTimeout(() => {
|
||||||
});
|
this.getData();
|
||||||
|
}, 1000);
|
||||||
|
}).catch(error => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public incrementExpireWarnDays(): void {
|
public incrementExpireWarnDays(): void {
|
||||||
@@ -98,29 +104,46 @@ export class PasswordAgePolicyComponent implements OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public savePolicy(): void {
|
public savePolicy(): void {
|
||||||
if (this.componentAction === PolicyComponentAction.CREATE) {
|
switch (this.serviceType) {
|
||||||
|
case PolicyComponentServiceType.MGMT:
|
||||||
|
if ((this.ageData as PasswordAgePolicyView.AsObject).pb_default) {
|
||||||
|
(this.service as ManagementService).CreatePasswordAgePolicy(
|
||||||
|
this.ageData.maxAgeDays,
|
||||||
|
this.ageData.expireWarnDays,
|
||||||
|
).then(() => {
|
||||||
|
this.toast.showInfo('POLICY.TOAST.SET', true);
|
||||||
|
}).catch(error => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
(this.service as ManagementService).UpdatePasswordAgePolicy(
|
||||||
|
this.ageData.maxAgeDays,
|
||||||
|
this.ageData.expireWarnDays,
|
||||||
|
).then(() => {
|
||||||
|
this.toast.showInfo('POLICY.TOAST.SET', true);
|
||||||
|
}).catch(error => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PolicyComponentServiceType.ADMIN:
|
||||||
|
(this.service as AdminService).UpdateDefaultPasswordAgePolicy(
|
||||||
|
this.ageData.maxAgeDays,
|
||||||
|
this.ageData.expireWarnDays,
|
||||||
|
).then(() => {
|
||||||
|
this.toast.showInfo('POLICY.TOAST.SET', true);
|
||||||
|
}).catch(error => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.mgmtService.CreatePasswordAgePolicy(
|
public get isDefault(): boolean {
|
||||||
this.ageData.description,
|
if (this.ageData && this.serviceType === PolicyComponentServiceType.MGMT) {
|
||||||
this.ageData.maxAgeDays,
|
return (this.ageData as PasswordAgePolicyView.AsObject).pb_default;
|
||||||
this.ageData.expireWarnDays,
|
} else {
|
||||||
).then(() => {
|
return false;
|
||||||
this.router.navigate(['org']);
|
|
||||||
}).catch(error => {
|
|
||||||
this.toast.showError(error);
|
|
||||||
});
|
|
||||||
|
|
||||||
} else if (this.componentAction === PolicyComponentAction.MODIFY) {
|
|
||||||
|
|
||||||
this.mgmtService.UpdatePasswordAgePolicy(
|
|
||||||
this.ageData.description,
|
|
||||||
this.ageData.maxAgeDays,
|
|
||||||
this.ageData.expireWarnDays,
|
|
||||||
).then(() => {
|
|
||||||
this.router.navigate(['org']);
|
|
||||||
}).catch(error => {
|
|
||||||
this.toast.showError(error);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
|
||||||
import { PolicyComponentAction } from '../policy-component-action.enum';
|
|
||||||
import { PasswordComplexityPolicyComponent } from './password-complexity-policy.component';
|
import { PasswordComplexityPolicyComponent } from './password-complexity-policy.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
@@ -10,15 +9,6 @@ const routes: Routes = [
|
|||||||
component: PasswordComplexityPolicyComponent,
|
component: PasswordComplexityPolicyComponent,
|
||||||
data: {
|
data: {
|
||||||
animation: 'DetailPage',
|
animation: 'DetailPage',
|
||||||
action: PolicyComponentAction.MODIFY,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'create',
|
|
||||||
component: PasswordComplexityPolicyComponent,
|
|
||||||
data: {
|
|
||||||
animation: 'DetailPage',
|
|
||||||
action: PolicyComponentAction.CREATE,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@@ -1,19 +1,22 @@
|
|||||||
<app-detail-layout [backRouterLink]="[ '/org']" [title]="title ? (title | translate) : ''"
|
<app-detail-layout [backRouterLink]="[ serviceType === PolicyComponentServiceType.ADMIN ? '/iam' : '/org']"
|
||||||
[description]="desc ? (desc | translate) : ''">
|
[title]="'POLICY.PWD_COMPLEXITY.TITLE' | translate" [description]="'POLICY.PWD_COMPLEXITY.DESCRIPTION' | translate">
|
||||||
<ng-template appHasRole [appHasRole]="['iam.policy.write']">
|
|
||||||
<button matTooltip="{{'ORG.POLICY.DELETE' | translate}}" color="warn" (click)="deletePolicy()"
|
<p class="default" *ngIf="isDefault"> {{'POLICY.DEFAULTLABEL' | translate}}</p>
|
||||||
mat-stroked-button>
|
|
||||||
{{'ORG.POLICY.DELETE' | translate}}
|
<div class="spinner-wr">
|
||||||
|
<mat-spinner diameter="30" *ngIf="loading" color="primary"></mat-spinner>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-template appHasRole [appHasRole]="['policy.delete']">
|
||||||
|
<button *ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault"
|
||||||
|
matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="removePolicy()" mat-stroked-button>
|
||||||
|
{{'POLICY.RESET' | translate}}
|
||||||
</button>
|
</button>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<div *ngIf="complexityData" class="content">
|
<div *ngIf="complexityData" class="content">
|
||||||
<mat-form-field class="description-formfield" appearance="outline">
|
|
||||||
<mat-label>{{ 'ORG.POLICY.DATA.DESCRIPTION' | translate }}</mat-label>
|
|
||||||
<input matInput name="description" ngDefaultControl [(ngModel)]="complexityData.description" required />
|
|
||||||
</mat-form-field>
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span class="left-desc">{{'ORG.POLICY.DATA.MINLENGTH' | translate}}</span>
|
<span class="left-desc">{{'POLICY.DATA.MINLENGTH' | translate}}</span>
|
||||||
<span class="fill-space"></span>
|
<span class="fill-space"></span>
|
||||||
<div class="length-wrapper">
|
<div class="length-wrapper">
|
||||||
<button mat-icon-button (click)="decrementLength()">
|
<button mat-icon-button (click)="decrementLength()">
|
||||||
@@ -26,26 +29,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span class="left-desc">{{'ORG.POLICY.DATA.HASNUMBER' | translate}}</span>
|
<span class="left-desc">{{'POLICY.DATA.HASNUMBER' | translate}}</span>
|
||||||
<span class="fill-space"></span>
|
<span class="fill-space"></span>
|
||||||
<mat-slide-toggle color="primary" name="hasNumber" ngDefaultControl [(ngModel)]="complexityData.hasNumber">
|
<mat-slide-toggle color="primary" name="hasNumber" ngDefaultControl [(ngModel)]="complexityData.hasNumber">
|
||||||
</mat-slide-toggle>
|
</mat-slide-toggle>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span class="left-desc">{{'ORG.POLICY.DATA.HASSYMBOL' | translate}}</span>
|
<span class="left-desc">{{'POLICY.DATA.HASSYMBOL' | translate}}</span>
|
||||||
<span class="fill-space"></span>
|
<span class="fill-space"></span>
|
||||||
<mat-slide-toggle color="primary" name="hasSymbol" ngDefaultControl [(ngModel)]="complexityData.hasSymbol">
|
<mat-slide-toggle color="primary" name="hasSymbol" ngDefaultControl [(ngModel)]="complexityData.hasSymbol">
|
||||||
</mat-slide-toggle>
|
</mat-slide-toggle>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span class="left-desc">{{'ORG.POLICY.DATA.HASLOWERCASE' | translate}}</span>
|
<span class="left-desc">{{'POLICY.DATA.HASLOWERCASE' | translate}}</span>
|
||||||
<span class="fill-space"></span>
|
<span class="fill-space"></span>
|
||||||
<mat-slide-toggle color="primary" name="hasLowercase" ngDefaultControl
|
<mat-slide-toggle color="primary" name="hasLowercase" ngDefaultControl
|
||||||
[(ngModel)]="complexityData.hasLowercase">
|
[(ngModel)]="complexityData.hasLowercase">
|
||||||
</mat-slide-toggle>
|
</mat-slide-toggle>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span class="left-desc">{{'ORG.POLICY.DATA.HASUPPERCASE' | translate}}</span>
|
<span class="left-desc">{{'POLICY.DATA.HASUPPERCASE' | translate}}</span>
|
||||||
<span class="fill-space"></span>
|
<span class="fill-space"></span>
|
||||||
<mat-slide-toggle color="primary" name="hasUppercase" ngDefaultControl
|
<mat-slide-toggle color="primary" name="hasUppercase" ngDefaultControl
|
||||||
[(ngModel)]="complexityData.hasUppercase">
|
[(ngModel)]="complexityData.hasUppercase">
|
||||||
@@ -54,7 +57,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="btn-container">
|
<div class="btn-container">
|
||||||
<button (click)="savePolicy()" color="primary" type="submit" [disabled]="!complexityData?.description"
|
<button (click)="savePolicy()" color="primary" type="submit"
|
||||||
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
|
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
|
||||||
</div>
|
</div>
|
||||||
</app-detail-layout>
|
</app-detail-layout>
|
@@ -1,6 +1,10 @@
|
|||||||
|
.default {
|
||||||
|
color: #5282c1;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
.spinner-wr {
|
||||||
border-radius: .5rem;
|
margin: .5rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
@@ -12,10 +16,9 @@ button {
|
|||||||
.row {
|
.row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: .5rem 0;
|
padding: .3rem 0;
|
||||||
|
|
||||||
.left-desc {
|
.left-desc {
|
||||||
color: #8795a1;
|
|
||||||
font-size: .9rem;
|
font-size: .9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,6 +29,7 @@ button {
|
|||||||
.length-wrapper {
|
.length-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
margin-right: -.6rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,6 +43,5 @@ button {
|
|||||||
margin-top: 3rem;
|
margin-top: 3rem;
|
||||||
display: block;
|
display: block;
|
||||||
padding: .5rem 4rem;
|
padding: .5rem 4rem;
|
||||||
border-radius: .5rem;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,17 +1,14 @@
|
|||||||
import { Component, OnDestroy } from '@angular/core';
|
import { Component, Injector, OnDestroy, Type } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { switchMap } from 'rxjs/operators';
|
import { switchMap } from 'rxjs/operators';
|
||||||
import {
|
import { DefaultPasswordComplexityPolicy } from 'src/app/proto/generated/admin_pb';
|
||||||
OrgIamPolicy,
|
import { PasswordComplexityPolicyView } from 'src/app/proto/generated/management_pb';
|
||||||
PasswordAgePolicy,
|
import { AdminService } from 'src/app/services/admin.service';
|
||||||
PasswordComplexityPolicy,
|
|
||||||
PasswordLockoutPolicy,
|
|
||||||
} from 'src/app/proto/generated/management_pb';
|
|
||||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||||
import { ToastService } from 'src/app/services/toast.service';
|
import { ToastService } from 'src/app/services/toast.service';
|
||||||
|
|
||||||
import { PolicyComponentAction } from '../policy-component-action.enum';
|
import { PolicyComponentServiceType } from '../policy-component-types.enum';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-password-policy',
|
selector: 'app-password-policy',
|
||||||
@@ -19,36 +16,45 @@ import { PolicyComponentAction } from '../policy-component-action.enum';
|
|||||||
styleUrls: ['./password-complexity-policy.component.scss'],
|
styleUrls: ['./password-complexity-policy.component.scss'],
|
||||||
})
|
})
|
||||||
export class PasswordComplexityPolicyComponent implements OnDestroy {
|
export class PasswordComplexityPolicyComponent implements OnDestroy {
|
||||||
public title: string = '';
|
public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
|
||||||
public desc: string = '';
|
public service!: ManagementService | AdminService;
|
||||||
|
|
||||||
componentAction: PolicyComponentAction = PolicyComponentAction.CREATE;
|
public complexityData!: PasswordComplexityPolicyView.AsObject | DefaultPasswordComplexityPolicy.AsObject;
|
||||||
|
|
||||||
public PolicyComponentAction: any = PolicyComponentAction;
|
|
||||||
|
|
||||||
public complexityData!: PasswordComplexityPolicy.AsObject;
|
|
||||||
|
|
||||||
private sub: Subscription = new Subscription();
|
private sub: Subscription = new Subscription();
|
||||||
|
public PolicyComponentServiceType: any = PolicyComponentServiceType;
|
||||||
|
|
||||||
|
public loading: boolean = false;
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private mgmtService: ManagementService,
|
|
||||||
private router: Router,
|
|
||||||
private toast: ToastService,
|
private toast: ToastService,
|
||||||
|
private injector: Injector,
|
||||||
) {
|
) {
|
||||||
this.sub = this.route.data.pipe(switchMap(data => {
|
this.sub = this.route.data.pipe(switchMap(data => {
|
||||||
this.componentAction = data.action;
|
this.serviceType = data.serviceType;
|
||||||
return this.route.params;
|
|
||||||
})).subscribe(params => {
|
|
||||||
this.title = 'ORG.POLICY.PWD_COMPLEXITY.TITLECREATE';
|
|
||||||
this.desc = 'ORG.POLICY.PWD_COMPLEXITY.DESCRIPTIONCREATE';
|
|
||||||
|
|
||||||
if (this.componentAction === PolicyComponentAction.MODIFY) {
|
switch (this.serviceType) {
|
||||||
this.getData(params).then(data => {
|
case PolicyComponentServiceType.MGMT:
|
||||||
if (data) {
|
this.service = this.injector.get(ManagementService as Type<ManagementService>);
|
||||||
this.complexityData = data.toObject() as PasswordComplexityPolicy.AsObject;
|
break;
|
||||||
}
|
case PolicyComponentServiceType.ADMIN:
|
||||||
});
|
this.service = this.injector.get(AdminService as Type<AdminService>);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.route.params;
|
||||||
|
})).subscribe(() => {
|
||||||
|
this.fetchData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public fetchData(): void {
|
||||||
|
this.loading = true;
|
||||||
|
|
||||||
|
this.getData().then(data => {
|
||||||
|
if (data) {
|
||||||
|
this.complexityData = data.toObject();
|
||||||
|
this.loading = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -57,19 +63,27 @@ export class PasswordComplexityPolicyComponent implements OnDestroy {
|
|||||||
this.sub.unsubscribe();
|
this.sub.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getData(params: any):
|
private async getData():
|
||||||
Promise<PasswordLockoutPolicy | PasswordAgePolicy | PasswordComplexityPolicy | OrgIamPolicy | undefined> {
|
Promise<PasswordComplexityPolicyView | DefaultPasswordComplexityPolicy> {
|
||||||
this.title = 'ORG.POLICY.PWD_COMPLEXITY.TITLE';
|
switch (this.serviceType) {
|
||||||
this.desc = 'ORG.POLICY.PWD_COMPLEXITY.DESCRIPTION';
|
case PolicyComponentServiceType.MGMT:
|
||||||
return this.mgmtService.GetPasswordComplexityPolicy();
|
return (this.service as ManagementService).GetPasswordComplexityPolicy();
|
||||||
|
case PolicyComponentServiceType.ADMIN:
|
||||||
|
return (this.service as AdminService).GetDefaultPasswordComplexityPolicy();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public deletePolicy(): void {
|
public removePolicy(): void {
|
||||||
this.mgmtService.DeletePasswordComplexityPolicy(this.complexityData.id).then(() => {
|
if (this.service instanceof ManagementService) {
|
||||||
this.toast.showInfo('Successfully deleted');
|
this.service.removePasswordComplexityPolicy().then(() => {
|
||||||
}).catch(error => {
|
this.toast.showInfo('POLICY.TOAST.RESETSUCCESS', true);
|
||||||
this.toast.showError(error);
|
setTimeout(() => {
|
||||||
});
|
this.fetchData();
|
||||||
|
}, 1000);
|
||||||
|
}).catch(error => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public incrementLength(): void {
|
public incrementLength(): void {
|
||||||
@@ -85,35 +99,56 @@ export class PasswordComplexityPolicyComponent implements OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public savePolicy(): void {
|
public savePolicy(): void {
|
||||||
if (this.componentAction === PolicyComponentAction.CREATE) {
|
switch (this.serviceType) {
|
||||||
|
case PolicyComponentServiceType.MGMT:
|
||||||
|
if ((this.complexityData as PasswordComplexityPolicyView.AsObject).pb_default) {
|
||||||
|
(this.service as ManagementService).CreatePasswordComplexityPolicy(
|
||||||
|
|
||||||
this.mgmtService.CreatePasswordComplexityPolicy(
|
this.complexityData.hasLowercase,
|
||||||
this.complexityData.description,
|
this.complexityData.hasUppercase,
|
||||||
this.complexityData.hasLowercase,
|
this.complexityData.hasNumber,
|
||||||
this.complexityData.hasUppercase,
|
this.complexityData.hasSymbol,
|
||||||
this.complexityData.hasNumber,
|
this.complexityData.minLength,
|
||||||
this.complexityData.hasSymbol,
|
).then(() => {
|
||||||
this.complexityData.minLength,
|
this.toast.showInfo('POLICY.TOAST.SET', true);
|
||||||
).then(() => {
|
}).catch(error => {
|
||||||
this.router.navigate(['org']);
|
this.toast.showError(error);
|
||||||
}).catch(error => {
|
});
|
||||||
this.toast.showError(error);
|
} else {
|
||||||
});
|
(this.service as ManagementService).UpdatePasswordComplexityPolicy(
|
||||||
|
this.complexityData.hasLowercase,
|
||||||
|
this.complexityData.hasUppercase,
|
||||||
|
this.complexityData.hasNumber,
|
||||||
|
this.complexityData.hasSymbol,
|
||||||
|
this.complexityData.minLength,
|
||||||
|
).then(() => {
|
||||||
|
this.toast.showInfo('POLICY.TOAST.SET', true);
|
||||||
|
}).catch(error => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PolicyComponentServiceType.ADMIN:
|
||||||
|
(this.service as AdminService).UpdateDefaultPasswordComplexityPolicy(
|
||||||
|
this.complexityData.hasLowercase,
|
||||||
|
this.complexityData.hasUppercase,
|
||||||
|
this.complexityData.hasNumber,
|
||||||
|
this.complexityData.hasSymbol,
|
||||||
|
this.complexityData.minLength,
|
||||||
|
).then(() => {
|
||||||
|
this.toast.showInfo('POLICY.TOAST.SET', true);
|
||||||
|
}).catch(error => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else if (this.componentAction === PolicyComponentAction.MODIFY) {
|
public get isDefault(): boolean {
|
||||||
|
if (this.complexityData && this.serviceType === PolicyComponentServiceType.MGMT) {
|
||||||
this.mgmtService.UpdatePasswordComplexityPolicy(
|
return (this.complexityData as PasswordComplexityPolicyView.AsObject).pb_default;
|
||||||
this.complexityData.description,
|
} else {
|
||||||
this.complexityData.hasLowercase,
|
return false;
|
||||||
this.complexityData.hasUppercase,
|
|
||||||
this.complexityData.hasNumber,
|
|
||||||
this.complexityData.hasSymbol,
|
|
||||||
this.complexityData.minLength,
|
|
||||||
).then(() => {
|
|
||||||
this.router.navigate(['org']);
|
|
||||||
}).catch(error => {
|
|
||||||
this.toast.showError(error);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,7 @@ import { MatButtonModule } from '@angular/material/button';
|
|||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
@@ -29,6 +30,7 @@ import { PasswordComplexityPolicyComponent } from './password-complexity-policy.
|
|||||||
MatTooltipModule,
|
MatTooltipModule,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
DetailLayoutModule,
|
DetailLayoutModule,
|
||||||
|
MatProgressSpinnerModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class PasswordComplexityPolicyModule { }
|
export class PasswordComplexityPolicyModule { }
|
||||||
|
@@ -1,30 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core';
|
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
|
||||||
|
|
||||||
import { PolicyComponentAction } from '../policy-component-action.enum';
|
|
||||||
import { PasswordIamPolicyComponent } from './password-iam-policy.component';
|
|
||||||
|
|
||||||
const routes: Routes = [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
component: PasswordIamPolicyComponent,
|
|
||||||
data: {
|
|
||||||
animation: 'DetailPage',
|
|
||||||
action: PolicyComponentAction.MODIFY,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'create',
|
|
||||||
component: PasswordIamPolicyComponent,
|
|
||||||
data: {
|
|
||||||
animation: 'DetailPage',
|
|
||||||
action: PolicyComponentAction.CREATE,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [RouterModule.forChild(routes)],
|
|
||||||
exports: [RouterModule],
|
|
||||||
})
|
|
||||||
export class PasswordIamPolicyRoutingModule { }
|
|
@@ -1,28 +0,0 @@
|
|||||||
<app-detail-layout [backRouterLink]="[ '/org']" [title]="title ? (title | translate) : ''"
|
|
||||||
[description]="desc ? (desc | translate) : ''">
|
|
||||||
<!-- <ng-template appHasRole [appHasRole]="['iam.policy.write']">
|
|
||||||
<button matTooltip="{{'ORG.POLICY.DELETE' | translate}}" color="warn" (click)="deletePolicy()"
|
|
||||||
mat-stroked-button>
|
|
||||||
{{'ORG.POLICY.DELETE' | translate}}
|
|
||||||
</button>
|
|
||||||
</ng-template> -->
|
|
||||||
|
|
||||||
<div class="content" *ngIf="iamData">
|
|
||||||
<mat-form-field class="description-formfield" appearance="outline">
|
|
||||||
<mat-label>{{ 'ORG.POLICY.DATA.DESCRIPTION' | translate }}</mat-label>
|
|
||||||
<input matInput name="description" ngDefaultControl [(ngModel)]="iamData.description" required />
|
|
||||||
</mat-form-field>
|
|
||||||
<div class="row">
|
|
||||||
<span class="left-desc">{{'ORG.POLICY.DATA.USERLOGINMUSTBEDOMAIN' | translate}}</span>
|
|
||||||
<span class="fill-space"></span>
|
|
||||||
<mat-slide-toggle color="primary" name="hasNumber" ngDefaultControl
|
|
||||||
[(ngModel)]="iamData.userLoginMustBeDomain">
|
|
||||||
</mat-slide-toggle>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="btn-container">
|
|
||||||
<button (click)="savePolicy()" color="primary" type="submit" [disabled]="!iamData?.description"
|
|
||||||
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
|
|
||||||
</div>
|
|
||||||
</app-detail-layout>
|
|
@@ -1,102 +0,0 @@
|
|||||||
import { Component, OnDestroy } from '@angular/core';
|
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
|
||||||
import { Subscription } from 'rxjs';
|
|
||||||
import { switchMap } from 'rxjs/operators';
|
|
||||||
import {
|
|
||||||
OrgIamPolicy,
|
|
||||||
PasswordAgePolicy,
|
|
||||||
PasswordComplexityPolicy,
|
|
||||||
PasswordLockoutPolicy,
|
|
||||||
} from 'src/app/proto/generated/management_pb';
|
|
||||||
import { AdminService } from 'src/app/services/admin.service';
|
|
||||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
|
||||||
import { StorageService } from 'src/app/services/storage.service';
|
|
||||||
import { ToastService } from 'src/app/services/toast.service';
|
|
||||||
|
|
||||||
import { PolicyComponentAction } from '../policy-component-action.enum';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-password-iam-policy',
|
|
||||||
templateUrl: './password-iam-policy.component.html',
|
|
||||||
styleUrls: ['./password-iam-policy.component.scss'],
|
|
||||||
})
|
|
||||||
export class PasswordIamPolicyComponent implements OnDestroy {
|
|
||||||
public title: string = '';
|
|
||||||
public desc: string = '';
|
|
||||||
|
|
||||||
componentAction: PolicyComponentAction = PolicyComponentAction.CREATE;
|
|
||||||
|
|
||||||
public PolicyComponentAction: any = PolicyComponentAction;
|
|
||||||
|
|
||||||
public iamData!: OrgIamPolicy.AsObject;
|
|
||||||
|
|
||||||
private sub: Subscription = new Subscription();
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private route: ActivatedRoute,
|
|
||||||
private mgmtService: ManagementService,
|
|
||||||
private adminService: AdminService,
|
|
||||||
private router: Router,
|
|
||||||
private toast: ToastService,
|
|
||||||
private sessionStorage: StorageService,
|
|
||||||
) {
|
|
||||||
this.sub = this.route.data.pipe(switchMap(data => {
|
|
||||||
this.componentAction = data.action;
|
|
||||||
console.log(data.action);
|
|
||||||
return this.route.params;
|
|
||||||
})).subscribe(params => {
|
|
||||||
this.title = 'ORG.POLICY.IAM_POLICY.TITLECREATE';
|
|
||||||
this.desc = 'ORG.POLICY.IAM_POLICY.DESCRIPTIONCREATE';
|
|
||||||
|
|
||||||
if (this.componentAction === PolicyComponentAction.MODIFY) {
|
|
||||||
this.getData(params).then(data => {
|
|
||||||
if (data) {
|
|
||||||
this.iamData = data.toObject() as OrgIamPolicy.AsObject;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public ngOnDestroy(): void {
|
|
||||||
this.sub.unsubscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getData(params: any):
|
|
||||||
Promise<PasswordLockoutPolicy | PasswordAgePolicy | PasswordComplexityPolicy | OrgIamPolicy | undefined> {
|
|
||||||
|
|
||||||
this.title = 'ORG.POLICY.IAM_POLICY.TITLECREATE';
|
|
||||||
this.desc = 'ORG.POLICY.IAM_POLICY.DESCRIPTIONCREATE';
|
|
||||||
return this.mgmtService.GetMyOrgIamPolicy();
|
|
||||||
}
|
|
||||||
|
|
||||||
public savePolicy(): void {
|
|
||||||
if (this.componentAction === PolicyComponentAction.CREATE) {
|
|
||||||
const orgId = this.sessionStorage.getItem('organization');
|
|
||||||
if (orgId) {
|
|
||||||
this.adminService.CreateOrgIamPolicy(
|
|
||||||
orgId,
|
|
||||||
this.iamData.description,
|
|
||||||
this.iamData.userLoginMustBeDomain,
|
|
||||||
).then(() => {
|
|
||||||
this.router.navigate(['org']);
|
|
||||||
}).catch(error => {
|
|
||||||
this.toast.showError(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (this.componentAction === PolicyComponentAction.MODIFY) {
|
|
||||||
const orgId = this.sessionStorage.getItem('organization');
|
|
||||||
if (orgId) {
|
|
||||||
this.adminService.UpdateOrgIamPolicy(
|
|
||||||
orgId,
|
|
||||||
this.iamData.description,
|
|
||||||
this.iamData.userLoginMustBeDomain,
|
|
||||||
).then(() => {
|
|
||||||
this.router.navigate(['org']);
|
|
||||||
}).catch(error => {
|
|
||||||
this.toast.showError(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,7 +1,6 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
|
||||||
import { PolicyComponentAction } from '../policy-component-action.enum';
|
|
||||||
import { PasswordLockoutPolicyComponent } from './password-lockout-policy.component';
|
import { PasswordLockoutPolicyComponent } from './password-lockout-policy.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
@@ -10,15 +9,6 @@ const routes: Routes = [
|
|||||||
component: PasswordLockoutPolicyComponent,
|
component: PasswordLockoutPolicyComponent,
|
||||||
data: {
|
data: {
|
||||||
animation: 'DetailPage',
|
animation: 'DetailPage',
|
||||||
action: PolicyComponentAction.MODIFY,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'create',
|
|
||||||
component: PasswordLockoutPolicyComponent,
|
|
||||||
data: {
|
|
||||||
animation: 'DetailPage',
|
|
||||||
action: PolicyComponentAction.CREATE,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@@ -1,19 +1,17 @@
|
|||||||
<app-detail-layout [backRouterLink]="[ '/org']" [title]="title ? (title | translate) : ''"
|
<app-detail-layout [backRouterLink]="[ serviceType === PolicyComponentServiceType.ADMIN ? '/iam' : '/org']"
|
||||||
[description]="desc ? (desc | translate) : ''">
|
[title]="'POLICY.PWD_LOCKOUT.TITLE' | translate" [description]="'POLICY.PWD_LOCKOUT.DESCRIPTION' | translate">
|
||||||
<ng-template appHasRole [appHasRole]="['iam.policy.write']">
|
<p class="default" *ngIf="isDefault"> {{'POLICY.DEFAULTLABEL' | translate}}</p>
|
||||||
<button matTooltip="{{'ORG.POLICY.DELETE' | translate}}" color="warn" (click)="deletePolicy()"
|
|
||||||
mat-stroked-button>
|
<ng-template appHasRole [appHasRole]="['policy.delete']">
|
||||||
{{'ORG.POLICY.DELETE' | translate}}
|
<button *ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault"
|
||||||
|
matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="removePolicy()" mat-stroked-button>
|
||||||
|
{{'POLICY.RESET' | translate}}
|
||||||
</button>
|
</button>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<div class="content" *ngIf="lockoutData">
|
<div class="content" *ngIf="lockoutData">
|
||||||
<mat-form-field class="description-formfield" appearance="outline">
|
|
||||||
<mat-label>{{ 'ORG.POLICY.DATA.DESCRIPTION' | translate }}</mat-label>
|
|
||||||
<input matInput name="description" ngDefaultControl [(ngModel)]="lockoutData.description" required />
|
|
||||||
</mat-form-field>
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span class="left-desc">{{'ORG.POLICY.DATA.MAXATTEMPTS' | translate}}</span>
|
<span class="left-desc">{{'POLICY.DATA.MAXATTEMPTS' | translate}}</span>
|
||||||
<span class="fill-space"></span>
|
<span class="fill-space"></span>
|
||||||
<div class="length-wrapper">
|
<div class="length-wrapper">
|
||||||
<button mat-icon-button (click)="incrementMaxAttempts()">
|
<button mat-icon-button (click)="incrementMaxAttempts()">
|
||||||
@@ -26,10 +24,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span class="left-desc">{{'ORG.POLICY.DATA.SHOWLOCKOUTFAILURES' | translate}}</span>
|
<span class="left-desc">{{'POLICY.DATA.SHOWLOCKOUTFAILURES' | translate}}</span>
|
||||||
<span class="fill-space"></span>
|
<span class="fill-space"></span>
|
||||||
<mat-slide-toggle color="primary" name="showLockOutFailures" ngDefaultControl
|
<mat-slide-toggle color="primary" name="showLockoutFailure" ngDefaultControl
|
||||||
[(ngModel)]="lockoutData.showLockOutFailures">
|
[(ngModel)]="lockoutData.showLockoutFailure">
|
||||||
</mat-slide-toggle>
|
</mat-slide-toggle>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
|
.default {
|
||||||
button {
|
color: #5282c1;
|
||||||
border-radius: .5rem;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
@@ -12,10 +12,9 @@ button {
|
|||||||
.row {
|
.row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: .5rem 0;
|
padding: .3rem 0;
|
||||||
|
|
||||||
.left-desc {
|
.left-desc {
|
||||||
color: #8795a1;
|
|
||||||
font-size: .9rem;
|
font-size: .9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,6 +38,5 @@ button {
|
|||||||
margin-top: 3rem;
|
margin-top: 3rem;
|
||||||
display: block;
|
display: block;
|
||||||
padding: .5rem 4rem;
|
padding: .5rem 4rem;
|
||||||
border-radius: .5rem;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,18 +1,15 @@
|
|||||||
import { Component, OnDestroy } from '@angular/core';
|
import { Component, Injector, Input, OnDestroy, Type } from '@angular/core';
|
||||||
import { FormGroup } from '@angular/forms';
|
import { FormGroup } from '@angular/forms';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { switchMap } from 'rxjs/operators';
|
import { switchMap } from 'rxjs/operators';
|
||||||
import {
|
import { DefaultPasswordLockoutPolicyView } from 'src/app/proto/generated/admin_pb';
|
||||||
OrgIamPolicy,
|
import { PasswordLockoutPolicyView } from 'src/app/proto/generated/management_pb';
|
||||||
PasswordAgePolicy,
|
import { AdminService } from 'src/app/services/admin.service';
|
||||||
PasswordComplexityPolicy,
|
|
||||||
PasswordLockoutPolicy,
|
|
||||||
} from 'src/app/proto/generated/management_pb';
|
|
||||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||||
import { ToastService } from 'src/app/services/toast.service';
|
import { ToastService } from 'src/app/services/toast.service';
|
||||||
|
|
||||||
import { PolicyComponentAction } from '../policy-component-action.enum';
|
import { PolicyComponentServiceType } from '../policy-component-types.enum';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-password-lockout-policy',
|
selector: 'app-password-lockout-policy',
|
||||||
@@ -20,37 +17,35 @@ import { PolicyComponentAction } from '../policy-component-action.enum';
|
|||||||
styleUrls: ['./password-lockout-policy.component.scss'],
|
styleUrls: ['./password-lockout-policy.component.scss'],
|
||||||
})
|
})
|
||||||
export class PasswordLockoutPolicyComponent implements OnDestroy {
|
export class PasswordLockoutPolicyComponent implements OnDestroy {
|
||||||
public title: string = '';
|
@Input() public service!: ManagementService | AdminService;
|
||||||
public desc: string = '';
|
public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
|
||||||
|
|
||||||
componentAction: PolicyComponentAction = PolicyComponentAction.CREATE;
|
|
||||||
|
|
||||||
public PolicyComponentAction: any = PolicyComponentAction;
|
|
||||||
|
|
||||||
public lockoutForm!: FormGroup;
|
public lockoutForm!: FormGroup;
|
||||||
public lockoutData!: PasswordLockoutPolicy.AsObject;
|
public lockoutData!: PasswordLockoutPolicyView.AsObject;
|
||||||
private sub: Subscription = new Subscription();
|
private sub: Subscription = new Subscription();
|
||||||
|
public PolicyComponentServiceType: any = PolicyComponentServiceType;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private mgmtService: ManagementService,
|
|
||||||
private router: Router,
|
|
||||||
private toast: ToastService,
|
private toast: ToastService,
|
||||||
|
private injector: Injector,
|
||||||
) {
|
) {
|
||||||
this.sub = this.route.data.pipe(switchMap(data => {
|
this.sub = this.route.data.pipe(switchMap(data => {
|
||||||
this.componentAction = data.action;
|
this.serviceType = data.serviceType;
|
||||||
return this.route.params;
|
|
||||||
})).subscribe(params => {
|
|
||||||
this.title = 'ORG.POLICY.PWD_LOCKOUT.TITLECREATE';
|
|
||||||
this.desc = 'ORG.POLICY.PWD_LOCKOUT.DESCRIPTIONCREATE';
|
|
||||||
|
|
||||||
if (this.componentAction === PolicyComponentAction.MODIFY) {
|
switch (this.serviceType) {
|
||||||
this.getData(params).then(data => {
|
case PolicyComponentServiceType.MGMT:
|
||||||
if (data) {
|
this.service = this.injector.get(ManagementService as Type<ManagementService>);
|
||||||
this.lockoutData = data.toObject() as PasswordLockoutPolicy.AsObject;
|
break;
|
||||||
}
|
case PolicyComponentServiceType.ADMIN:
|
||||||
});
|
this.service = this.injector.get(AdminService as Type<AdminService>);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return this.route.params;
|
||||||
|
})).subscribe(() => {
|
||||||
|
this.fetchData();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,20 +53,32 @@ export class PasswordLockoutPolicyComponent implements OnDestroy {
|
|||||||
this.sub.unsubscribe();
|
this.sub.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getData(params: any):
|
private fetchData(): void {
|
||||||
Promise<PasswordLockoutPolicy | PasswordAgePolicy | PasswordComplexityPolicy | OrgIamPolicy | undefined> {
|
this.getData().then(data => {
|
||||||
|
if (data) {
|
||||||
this.title = 'ORG.POLICY.PWD_LOCKOUT.TITLE';
|
this.lockoutData = data.toObject() as PasswordLockoutPolicyView.AsObject;
|
||||||
this.desc = 'ORG.POLICY.PWD_LOCKOUT.DESCRIPTION';
|
}
|
||||||
return this.mgmtService.GetPasswordLockoutPolicy();
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public deletePolicy(): void {
|
private getData(): Promise<PasswordLockoutPolicyView | DefaultPasswordLockoutPolicyView> {
|
||||||
this.mgmtService.DeletePasswordLockoutPolicy(this.lockoutData.id).then(() => {
|
switch (this.serviceType) {
|
||||||
this.toast.showInfo('Successfully deleted');
|
case PolicyComponentServiceType.MGMT:
|
||||||
}).catch(error => {
|
return (this.service as ManagementService).GetPasswordLockoutPolicy();
|
||||||
this.toast.showError(error);
|
case PolicyComponentServiceType.ADMIN:
|
||||||
});
|
return (this.service as AdminService).GetDefaultPasswordLockoutPolicy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public removePolicy(): void {
|
||||||
|
if (this.service instanceof ManagementService) {
|
||||||
|
this.service.RemovePasswordLockoutPolicy().then(() => {
|
||||||
|
this.toast.showInfo('POLICY.TOAST.RESETSUCCESS', true);
|
||||||
|
this.fetchData();
|
||||||
|
}).catch(error => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public incrementMaxAttempts(): void {
|
public incrementMaxAttempts(): void {
|
||||||
@@ -87,27 +94,44 @@ export class PasswordLockoutPolicyComponent implements OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public savePolicy(): void {
|
public savePolicy(): void {
|
||||||
if (this.componentAction === PolicyComponentAction.CREATE) {
|
let promise: Promise<any>;
|
||||||
this.mgmtService.CreatePasswordLockoutPolicy(
|
if (this.service instanceof AdminService) {
|
||||||
this.lockoutData.description,
|
promise = this.service.UpdateDefaultPasswordLockoutPolicy(
|
||||||
this.lockoutData.maxAttempts,
|
this.lockoutData.maxAttempts,
|
||||||
this.lockoutData.showLockOutFailures,
|
this.lockoutData.showLockoutFailure,
|
||||||
).then(() => {
|
).then(() => {
|
||||||
this.router.navigate(['org']);
|
this.toast.showInfo('POLICY.TOAST.SET', true);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
this.toast.showError(error);
|
this.toast.showError(error);
|
||||||
});
|
});
|
||||||
} else if (this.componentAction === PolicyComponentAction.MODIFY) {
|
} else {
|
||||||
|
if ((this.lockoutData as PasswordLockoutPolicyView.AsObject).pb_default) {
|
||||||
|
promise = this.service.CreatePasswordLockoutPolicy(
|
||||||
|
this.lockoutData.maxAttempts,
|
||||||
|
this.lockoutData.showLockoutFailure,
|
||||||
|
).then(() => {
|
||||||
|
this.toast.showInfo('POLICY.TOAST.SET', true);
|
||||||
|
}).catch(error => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
promise = this.service.UpdatePasswordLockoutPolicy(
|
||||||
|
this.lockoutData.maxAttempts,
|
||||||
|
this.lockoutData.showLockoutFailure,
|
||||||
|
).then(() => {
|
||||||
|
this.toast.showInfo('POLICY.TOAST.SET', true);
|
||||||
|
}).catch(error => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.mgmtService.UpdatePasswordLockoutPolicy(
|
public get isDefault(): boolean {
|
||||||
this.lockoutData.description,
|
if (this.lockoutData && this.serviceType === PolicyComponentServiceType.MGMT) {
|
||||||
this.lockoutData.maxAttempts,
|
return (this.lockoutData as PasswordLockoutPolicyView.AsObject).pb_default;
|
||||||
this.lockoutData.showLockOutFailures,
|
} else {
|
||||||
).then(() => {
|
return false;
|
||||||
this.router.navigate(['org']);
|
|
||||||
}).catch(error => {
|
|
||||||
this.toast.showError(error);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +0,0 @@
|
|||||||
|
|
||||||
export enum PolicyComponentAction {
|
|
||||||
CREATE = 'create',
|
|
||||||
MODIFY = 'modify',
|
|
||||||
}
|
|
@@ -4,6 +4,7 @@ export enum PolicyComponentType {
|
|||||||
COMPLEXITY = 'complexity',
|
COMPLEXITY = 'complexity',
|
||||||
IAM = 'iam',
|
IAM = 'iam',
|
||||||
LOGIN = 'login',
|
LOGIN = 'login',
|
||||||
|
LABEL = 'label',
|
||||||
}
|
}
|
||||||
export enum PolicyComponentServiceType {
|
export enum PolicyComponentServiceType {
|
||||||
MGMT = 'mgmt',
|
MGMT = 'mgmt',
|
||||||
|
109
console/src/app/modules/policy-grid/policy-grid.component.html
Normal file
109
console/src/app/modules/policy-grid/policy-grid.component.html
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
<h1>{{'POLICY.TITLE' | translate}}</h1>
|
||||||
|
|
||||||
|
<p class="top-desc">{{'POLICY.DESCRIPTION' | translate}}</p>
|
||||||
|
|
||||||
|
<div class="row-lyt">
|
||||||
|
<ng-template appHasRole
|
||||||
|
[appHasRole]="PolicyGridType.IAM ? ['iam.policy.read'] : PolicyGridType.ORG ? ['policy.read'] : []">
|
||||||
|
<div class="p-item card">
|
||||||
|
<div class="avatar">
|
||||||
|
<mat-icon class="icon" svgIcon="mdi_textbox_password"></mat-icon>
|
||||||
|
</div>
|
||||||
|
<div class="title">
|
||||||
|
<span>{{'POLICY.PWD_COMPLEXITY.TITLE' | translate}}</span>
|
||||||
|
<button mat-icon-button disabled>
|
||||||
|
<i class="icon las la-check-circle"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="desc">
|
||||||
|
{{'POLICY.PWD_COMPLEXITY.DESCRIPTION' | translate}}</p>
|
||||||
|
|
||||||
|
<span class="fill-space"></span>
|
||||||
|
<div class="btn-wrapper">
|
||||||
|
<button [routerLink]="[ 'policy', PolicyComponentType.COMPLEXITY ]"
|
||||||
|
mat-stroked-button>{{'POLICY.BTN_EDIT' | translate}}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template appHasRole [appHasRole]="['iam.policy.read']">
|
||||||
|
<div class="p-item card">
|
||||||
|
<div class="avatar">
|
||||||
|
<i class="icon las la-gem"></i>
|
||||||
|
</div>
|
||||||
|
<div class="title">
|
||||||
|
<span>{{'POLICY.IAM_POLICY.TITLE' | translate}}</span>
|
||||||
|
<button mat-icon-button disabled>
|
||||||
|
<i class="icon las la-check-circle"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="desc">
|
||||||
|
{{'POLICY.IAM_POLICY.DESCRIPTION' | translate}}</p>
|
||||||
|
|
||||||
|
<span class="fill-space"></span>
|
||||||
|
<div class="btn-wrapper">
|
||||||
|
<ng-template appHasRole [appHasRole]="['iam.policy.write']">
|
||||||
|
<button [routerLink]="[ 'policy', PolicyComponentType.IAM ]"
|
||||||
|
mat-stroked-button>{{'POLICY.BTN_EDIT' | translate}}</button>
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template appHasRole
|
||||||
|
[appHasRole]="PolicyGridType.IAM ? ['iam.policy.read'] : PolicyGridType.ORG ? ['policy.read'] : []">
|
||||||
|
<div class="p-item card">
|
||||||
|
<div class="avatar">
|
||||||
|
<i class="icon las la-sign-in-alt"></i>
|
||||||
|
</div>
|
||||||
|
<div class="title">
|
||||||
|
<span>{{'POLICY.LOGIN_POLICY.TITLE' | translate}}</span>
|
||||||
|
<button mat-icon-button disabled>
|
||||||
|
<i class="icon las la-check-circle"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-template #showDescIAM>
|
||||||
|
<p class="desc">
|
||||||
|
{{'POLICY.LOGIN_POLICY.DESCRIPTION' | translate}}</p>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<span class="fill-space"></span>
|
||||||
|
<div class="btn-wrapper">
|
||||||
|
<ng-template appHasRole [appHasRole]="['policy.write']">
|
||||||
|
<button [routerLink]="[ 'policy', PolicyComponentType.LOGIN ]"
|
||||||
|
mat-stroked-button>{{'POLICY.BTN_EDIT' | translate}}</button>
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-container *ngIf="type === PolicyGridType.IAM">
|
||||||
|
<ng-template appHasRole [appHasRole]="['iam.policy.read']">
|
||||||
|
<div class="p-item card">
|
||||||
|
<div class="avatar">
|
||||||
|
<i class="icon las la-envelope"></i>
|
||||||
|
</div>
|
||||||
|
<div class="title">
|
||||||
|
<span>{{'POLICY.LABEL.TITLE' | translate}}</span>
|
||||||
|
<button mat-icon-button disabled>
|
||||||
|
<i class="icon las la-check-circle"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="desc">
|
||||||
|
{{'POLICY.LABEL.DESCRIPTION' | translate}}</p>
|
||||||
|
|
||||||
|
<span class="fill-space"></span>
|
||||||
|
<div class="btn-wrapper">
|
||||||
|
<ng-template appHasRole [appHasRole]="['iam.policy.write']">
|
||||||
|
<button [routerLink]="[ 'policy', PolicyComponentType.LABEL ]"
|
||||||
|
mat-stroked-button>{{'POLICY.BTN_EDIT' | translate}}</button>
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
@@ -3,17 +3,17 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.top-desc {
|
.top-desc {
|
||||||
color: #8795a1;
|
color: var(--grey);
|
||||||
}
|
}
|
||||||
|
|
||||||
.row-lyt {
|
.row-lyt {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
margin: 0 -1rem;
|
margin: 0 -.5rem;
|
||||||
|
|
||||||
.p-item {
|
.p-item {
|
||||||
flex-basis: 300px;
|
flex-basis: 290px;
|
||||||
margin: 1rem;
|
margin: .5rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
@@ -38,6 +38,7 @@ h1 {
|
|||||||
font-size: 2.5rem;
|
font-size: 2.5rem;
|
||||||
height: 2.5rem;
|
height: 2.5rem;
|
||||||
line-height: 2.5rem;
|
line-height: 2.5rem;
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +47,7 @@ h1 {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
font-size: 1.2rem;
|
font-size: 1.1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
@@ -56,8 +57,8 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.desc {
|
.desc {
|
||||||
font-size: .9rem;
|
font-size: 14px;
|
||||||
color: #8795a1;
|
color: var(--grey);
|
||||||
}
|
}
|
||||||
|
|
||||||
.fill-space {
|
.fill-space {
|
||||||
@@ -70,7 +71,6 @@ h1 {
|
|||||||
|
|
||||||
button {
|
button {
|
||||||
margin-right: 1rem;
|
margin-right: 1rem;
|
||||||
border-radius: .5rem;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
19
console/src/app/modules/policy-grid/policy-grid.component.ts
Normal file
19
console/src/app/modules/policy-grid/policy-grid.component.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
import { PolicyComponentType } from 'src/app/modules/policies/policy-component-types.enum';
|
||||||
|
|
||||||
|
export enum PolicyGridType {
|
||||||
|
ORG,
|
||||||
|
IAM,
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-policy-grid',
|
||||||
|
templateUrl: './policy-grid.component.html',
|
||||||
|
styleUrls: ['./policy-grid.component.scss'],
|
||||||
|
})
|
||||||
|
export class PolicyGridComponent {
|
||||||
|
@Input() public type!: PolicyGridType;
|
||||||
|
public PolicyComponentType: any = PolicyComponentType;
|
||||||
|
public PolicyGridType: any = PolicyGridType;
|
||||||
|
constructor() { }
|
||||||
|
}
|
27
console/src/app/modules/policy-grid/policy-grid.module.ts
Normal file
27
console/src/app/modules/policy-grid/policy-grid.module.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
||||||
|
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
|
||||||
|
|
||||||
|
import { PolicyGridComponent } from './policy-grid.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [PolicyGridComponent],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
HasRolePipeModule,
|
||||||
|
HasRoleModule,
|
||||||
|
TranslateModule,
|
||||||
|
RouterModule,
|
||||||
|
MatButtonModule,
|
||||||
|
MatIconModule,
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
PolicyGridComponent,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class PolicyGridModule { }
|
@@ -1,96 +1,26 @@
|
|||||||
<app-detail-layout *ngIf="project" [backRouterLink]="[ '/projects', project?.projectId]"
|
<app-detail-layout *ngIf="project" [backRouterLink]="[ '/projects', project?.projectId]"
|
||||||
title="{{projectName}} {{ 'PROJECT.MEMBER.TITLE' | translate }}"
|
title="{{projectName}} {{ 'PROJECT.MEMBER.TITLE' | translate }}"
|
||||||
description="{{ 'PROJECT.MEMBER.DESCRIPTION' | translate }}">
|
description="{{ 'PROJECT.MEMBER.DESCRIPTION' | translate }}">
|
||||||
<app-refresh-table *ngIf="project" (refreshed)="changePage()" [dataSize]="dataSource.totalResult"
|
<app-members-table *ngIf="project" [dataSource]="dataSource" [memberRoleOptions]="memberRoleOptions"
|
||||||
[timestamp]="dataSource.viewTimestamp" [selection]="selection" [loading]="dataSource?.loading$ | async">
|
(updateRoles)="updateRoles($event.member, $event.change)" [factoryLoadFunc]="changePageFactory"
|
||||||
<ng-template appHasRole actions
|
(changedSelection)="selection = $event" [refreshTrigger]="changePage"
|
||||||
|
[canWrite]="['project.member.write$', 'project.member.write:'+ project.projectId] | hasRole | async"
|
||||||
|
[canDelete]="['project.member.delete$', 'project.member.delete:'+project.projectId] | hasRole | async"
|
||||||
|
(deleteMember)="removeProjectMember($event)">
|
||||||
|
<ng-template appHasRole selectactions
|
||||||
[appHasRole]="['project.member.delete:' + project.projectId, 'project.member.delete']">
|
[appHasRole]="['project.member.delete:' + project.projectId, 'project.member.delete']">
|
||||||
<button (click)="removeProjectMemberSelection()" color="warn"
|
<button (click)="removeProjectMemberSelection()" color="warn"
|
||||||
matTooltip="{{'ORG_DETAIL.TABLE.DELETE' | translate}}" class="icon-button" mat-icon-button
|
matTooltip="{{'ORG_DETAIL.TABLE.DELETE' | translate}}" class="del-button" mat-raised-button>
|
||||||
*ngIf="selection.hasValue()">
|
|
||||||
<i class="las la-trash"></i>
|
<i class="las la-trash"></i>
|
||||||
|
{{'ACTIONS.SELECTIONDELETE' | translate}}
|
||||||
</button>
|
</button>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template appHasRole actions
|
<ng-template appHasRole writeactions
|
||||||
[appHasRole]="['project.member.write:'+project.projectId,'project.member.write']">
|
[appHasRole]="['project.member.write:'+project.projectId,'project.member.write']">
|
||||||
<a color="primary" [disabled]="disabled" class="add-button" (click)="openAddMember()" color="primary"
|
<a color="primary" (click)="openAddMember()" color="primary" mat-raised-button>
|
||||||
mat-raised-button>
|
|
||||||
<mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }}
|
<mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }}
|
||||||
</a>
|
</a>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
</app-members-table>
|
||||||
<div class="table-wrapper">
|
</app-detail-layout>
|
||||||
<table mat-table class="table" aria-label="Elements" [dataSource]="dataSource">
|
<!-- TODO: check for both project.member and project.grant.member permissions -->
|
||||||
<ng-container matColumnDef="select">
|
|
||||||
<th class="selection" mat-header-cell *matHeaderCellDef>
|
|
||||||
<mat-checkbox 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 color="primary" (click)="$event.stopPropagation()"
|
|
||||||
(change)="$event ? selection.toggle(row) : null" [checked]="selection.isSelected(row)">
|
|
||||||
</mat-checkbox>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
|
|
||||||
<ng-container matColumnDef="userId">
|
|
||||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.USERID' | translate }} </th>
|
|
||||||
<td class="pointer" [routerLink]="['/user', member.userId]" mat-cell *matCellDef="let member">
|
|
||||||
{{member.userId}} </td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="firstname">
|
|
||||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.FIRSTNAME' | translate }} </th>
|
|
||||||
<td class="pointer" [routerLink]="['/user', member.userId]" mat-cell *matCellDef="let member">
|
|
||||||
{{member.firstName}} </td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="lastname">
|
|
||||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.LASTNAME' | translate }} </th>
|
|
||||||
<td class="pointer" [routerLink]="['/user', member.userId]" mat-cell *matCellDef="let member">
|
|
||||||
{{member.lastName}} </td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="username">
|
|
||||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.USERNAME' | translate }} </th>
|
|
||||||
<td class="pointer" [routerLink]="['/user', member.userId]" mat-cell *matCellDef="let member">
|
|
||||||
{{member.userName}} </td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="email">
|
|
||||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.EMAIL' | translate }} </th>
|
|
||||||
<td class="pointer" [routerLink]="['/user', member.userId]" mat-cell *matCellDef="let member">
|
|
||||||
{{member.email}}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="roles">
|
|
||||||
<th mat-header-cell *matHeaderCellDef> {{ 'ROLESLABEL' | translate }} </th>
|
|
||||||
<td mat-cell *matCellDef="let member">
|
|
||||||
<mat-form-field class="form-field" appearance="outline" *ngIf="project">
|
|
||||||
<mat-label>{{ 'ROLESLABEL' | translate }}</mat-label>
|
|
||||||
<mat-select [(ngModel)]="member.rolesList" multiple
|
|
||||||
[disabled]="([('project.member.write:' + project.projectId), 'project.member.write'] | hasRole | async) == false"
|
|
||||||
(selectionChange)="updateRoles(member, $event)">
|
|
||||||
<mat-option *ngFor="let role of memberRoleOptions" [value]="role">
|
|
||||||
{{ role }}
|
|
||||||
</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
|
||||||
<tr class="data-row" mat-row *matRowDef="let row; columns: displayedColumns;">
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<mat-paginator *ngIf="dataSource" class="paginator" #paginator [pageSize]="INITIALPAGESIZE"
|
|
||||||
[length]="dataSource.totalResult" [pageSizeOptions]="[25, 50, 100, 250]" (page)="changePage($event)">
|
|
||||||
</mat-paginator>
|
|
||||||
</div>
|
|
||||||
</app-refresh-table>
|
|
||||||
</app-detail-layout>
|
|
@@ -1,50 +1,7 @@
|
|||||||
.icon-button {
|
.del-button {
|
||||||
margin-right: .5rem;
|
margin-right: .5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-button {
|
:root {
|
||||||
border-radius: .5rem;
|
width: 100%;
|
||||||
}
|
|
||||||
|
|
||||||
.table-wrapper {
|
|
||||||
overflow-x: auto;
|
|
||||||
|
|
||||||
.table,
|
|
||||||
.paginator {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
td,
|
|
||||||
th {
|
|
||||||
padding: .5rem;
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
padding-left: 0;
|
|
||||||
padding-right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.action {
|
|
||||||
width: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-row {
|
|
||||||
&:hover {
|
|
||||||
background-color: #ffffff05;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.selection {
|
|
||||||
width: 50px;
|
|
||||||
max-width: 50px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.pointer {
|
|
||||||
outline: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user