feat(console-v2): login policy extension, domain policy, filter and UI fixes (#3644)

* show filter count when set

* toast contrast color

* fix notification settings, password dialog

* app-create, user-create layout

* domain policy

* login-policy, project grid loader, i18n

* login policy

* login policy save lifetimes

* private labeling optim

* granted project grantId

* smtp address matching

* i18n

* i18n

* i18n

* replace url strategy

* fix privatelabeling color picker saving

* stylelint

Co-authored-by: Livio Amstutz <livio.a@gmail.com>
This commit is contained in:
Max Peintner
2022-05-17 16:18:37 +02:00
committed by GitHub
parent 8d0cf9f368
commit 8baf0fe08c
83 changed files with 1967 additions and 1212 deletions

View File

@@ -8,6 +8,7 @@ import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/
import {
BRANDING,
COMPLEXITY,
DOMAIN,
GENERAL,
IDP,
LOCKOUT,
@@ -31,12 +32,14 @@ export class InstanceSettingsComponent {
public settingsList: SidenavSetting[] = [
GENERAL,
// notifications
{ showWarn: true, ...NOTIFICATIONS },
// { showWarn: true, ...NOTIFICATIONS },
NOTIFICATIONS,
// login
LOGIN,
COMPLEXITY,
LOCKOUT,
IDP,
DOMAIN,
// appearance
BRANDING,
MESSAGETEXTS,

View File

@@ -8,6 +8,7 @@ import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/
import {
BRANDING,
COMPLEXITY,
DOMAIN,
IDP,
LOCKOUT,
LOGIN,
@@ -29,6 +30,7 @@ export class OrgSettingsComponent {
COMPLEXITY,
LOCKOUT,
IDP,
DOMAIN,
BRANDING,
MESSAGETEXTS,
LOGINTEXTS,

View File

@@ -1,275 +1,349 @@
<div class="app-create-container">
<div class="abort-container">
<button (click)="close()" mat-icon-button>
<mat-icon>close</mat-icon>
</button>
<span class="abort">{{ 'APP.PAGES.CREATE_OIDC' | translate }}</span>
</div>
<div class="max-width-container">
<div class="enlarged-container">
<div class="abort-container">
<button (click)="close()" mat-icon-button>
<mat-icon>close</mat-icon>
</button>
<span class="abort">{{ 'APP.PAGES.CREATE_OIDC' | translate }}</span
><span class="abort-2">Step {{ currentCreateStep }} of {{ createSteps }}</span>
</div>
<h1>{{'APP.PAGES.CREATE_OIDC_DESC_TITLE' | translate}}</h1>
<p class="desc cnsl-secondary-text">{{'APP.PAGES.CREATE_OIDC_DESC_SUB' | translate}}</p>
<div class="app-create-content">
<h1>{{ 'APP.PAGES.CREATE_OIDC_DESC_TITLE' | translate }}</h1>
<mat-progress-bar class="progress-bar" color="primary" *ngIf="loading" mode="indeterminate"></mat-progress-bar>
<mat-progress-bar class="progress-bar" color="primary" *ngIf="loading" mode="indeterminate"></mat-progress-bar>
<mat-checkbox class="proswitch" color="primary" [(ngModel)]="devmode">
{{ 'APP.OIDC.PROSWITCH' | translate }}
</mat-checkbox>
<mat-checkbox class="proswitch" color="primary" [(ngModel)]="devmode">
{{'APP.OIDC.PROSWITCH' | translate}}
</mat-checkbox>
<mat-horizontal-stepper
class="stepper"
*ngIf="!devmode"
linear
#stepper
labelPosition="bottom"
(selectionChange)="changeStep($event)"
>
<mat-step [stepControl]="firstFormGroup" [editable]="true">
<form [formGroup]="firstFormGroup">
<ng-template matStepLabel>{{ 'APP.OIDC.NAMEANDTYPESECTION' | translate }}</ng-template>
<mat-horizontal-stepper class="stepper" *ngIf="!devmode" linear #stepper labelPosition="bottom"
(selectionChange)="changeStep($event)">
<mat-step [stepControl]="firstFormGroup" [editable]="true">
<form [formGroup]="firstFormGroup">
<ng-template matStepLabel>{{'APP.OIDC.NAMEANDTYPESECTION' | translate}}</ng-template>
<p class="step-title">{{ 'APP.OIDC.TITLEFIRST' | translate }}</p>
<cnsl-form-field appearance="outline" class="name-formfield">
<cnsl-label>{{ 'APP.NAME' | translate }}</cnsl-label>
<input cnslInput cdkFocusInitial formControlName="name" />
<span cnslError *ngIf="name?.errors?.required">{{ 'PROJECT.APP.NAMEREQUIRED' | translate }}</span>
</cnsl-form-field>
<p class="step-title">{{'APP.OIDC.TITLEFIRST' | translate}}</p>
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'APP.NAME' | translate }}</cnsl-label>
<input cnslInput cdkFocusInitial formControlName="name" />
<span cnslError *ngIf="name?.errors?.required">{{'PROJECT.APP.NAMEREQUIRED' | translate}}</span>
</cnsl-form-field>
<p class="step-title">{{ 'APP.OIDC.TYPETITLE' | translate }}</p>
<p class="step-title">{{'APP.OIDC.TYPETITLE' | translate}}</p>
<cnsl-type-radio [types]="appTypes" (selectedType)="appType?.setValue($event)" [selected]="appType?.value">
</cnsl-type-radio>
<div class="app-create-actions">
<button
mat-raised-button
[disabled]="firstFormGroup.invalid"
color="primary"
matStepperNext
[attr.data-e2e]="'continue-button-nameandtype'"
>
{{ 'ACTIONS.CONTINUE' | translate }}
</button>
</div>
</form>
</mat-step>
<cnsl-type-radio [types]="appTypes" (selectedType)="appType?.setValue($event)" [selected]="appType?.value">
</cnsl-type-radio>
<div class="app-create-actions">
<span class="fill-space"></span>
<button mat-raised-button [disabled]="firstFormGroup.invalid" color="primary" matStepperNext
[attr.data-e2e]="'continue-button-nameandtype'">{{'ACTIONS.CONTINUE' | translate}}</button>
</div>
</form>
</mat-step>
<!-- skip for native applications -->
<mat-step
*ngIf="oidcAppRequest.appType !== OIDCAppType.OIDC_APP_TYPE_NATIVE"
[stepControl]="secondFormGroup"
[editable]="true"
>
<form [formGroup]="secondFormGroup">
<ng-template matStepLabel>{{ 'APP.AUTHMETHODSECTION' | translate }}</ng-template>
<!-- skip for native applications -->
<mat-step *ngIf="oidcAppRequest.appType !== OIDCAppType.OIDC_APP_TYPE_NATIVE" [stepControl]="secondFormGroup"
[editable]="true">
<form [formGroup]="secondFormGroup">
<ng-template matStepLabel>{{'APP.AUTHMETHODSECTION' | translate}}</ng-template>
<cnsl-auth-method-radio
[authMethods]="authMethods"
[selected]="authMethod?.value"
[isOIDC]="appType?.value?.createType === AppCreateType.OIDC"
(selectedMethod)="authMethod?.setValue($event)"
>
</cnsl-auth-method-radio>
<cnsl-auth-method-radio [authMethods]="authMethods" [selected]="authMethod?.value"
[isOIDC]="appType?.value?.createType === AppCreateType.OIDC" (selectedMethod)="authMethod?.setValue($event)">
</cnsl-auth-method-radio>
<div class="app-create-actions">
<button class="bck-button" mat-stroked-button matStepperPrevious>{{ 'ACTIONS.BACK' | translate }}</button>
<button
mat-raised-button
color="primary"
[disabled]="secondFormGroup.invalid"
matStepperNext
[attr.data-e2e]="'continue-button-authmethod'"
>
{{ 'ACTIONS.CONTINUE' | translate }}
</button>
</div>
</form>
</mat-step>
<div class="app-create-actions">
<button mat-stroked-button color="primary" matStepperPrevious>{{'ACTIONS.BACK' |
translate}}</button>
<span class="fill-space"></span>
<button mat-raised-button color="primary" [disabled]="secondFormGroup.invalid" matStepperNext
[attr.data-e2e]="'continue-button-authmethod'">{{'ACTIONS.CONTINUE' | translate}}</button>
</div>
</form>
</mat-step>
<!-- show redirect step only for OIDC apps -->
<mat-step *ngIf="appType?.value?.createType === AppCreateType.OIDC" [editable]="true">
<ng-template matStepLabel>{{ 'APP.OIDC.REDIRECTSECTION' | translate }}</ng-template>
<!-- show redirect step only for OIDC apps -->
<mat-step *ngIf="appType?.value?.createType === AppCreateType.OIDC" [editable]="true">
<ng-template matStepLabel>{{'APP.OIDC.REDIRECTSECTION' | translate}}</ng-template>
<p class="step-title">{{ 'APP.OIDC.REDIRECTTITLE' | translate }}</p>
<p
class="step-description cnsl-secondary-text"
*ngIf="oidcAppRequest.appType === OIDCAppType.OIDC_APP_TYPE_NATIVE"
>
{{ 'APP.OIDC.REDIRECTDESCRIPTIONNATIVE' | translate }}
</p>
<p class="step-description cnsl-secondary-text" *ngIf="oidcAppRequest.appType === OIDCAppType.OIDC_APP_TYPE_WEB">
{{ 'APP.OIDC.REDIRECTDESCRIPTIONWEB' | translate }}
</p>
<p class="step-title">{{'APP.OIDC.REDIRECTTITLE' | translate}}</p>
<p class="step-description cnsl-secondary-text"
*ngIf="oidcAppRequest.appType === OIDCAppType.OIDC_APP_TYPE_NATIVE">
{{'APP.OIDC.REDIRECTDESCRIPTIONNATIVE' | translate}}</p>
<p class="step-description cnsl-secondary-text" *ngIf="oidcAppRequest.appType === OIDCAppType.OIDC_APP_TYPE_WEB">
{{'APP.OIDC.REDIRECTDESCRIPTIONWEB' | translate}}</p>
<cnsl-redirect-uris class="redirect-section" [canWrite]="true"
[isNative]="oidcAppRequest.appType === OIDCAppType.OIDC_APP_TYPE_NATIVE"
(changedUris)="oidcAppRequest.redirectUrisList = $any($event)" [urisList]="oidcAppRequest.redirectUrisList"
[getValues]="requestRedirectValuesSubject$" title="{{ 'APP.OIDC.REDIRECT' | translate }}">
</cnsl-redirect-uris>
<p class="step-title">{{'APP.OIDC.POSTREDIRECTTITLE' | translate}}</p>
<p class="step-description cnsl-secondary-text"
*ngIf="oidcAppRequest.appType === OIDCAppType.OIDC_APP_TYPE_NATIVE">
{{'APP.OIDC.REDIRECTDESCRIPTIONNATIVE' | translate}}</p>
<p class="step-description cnsl-secondary-text"
*ngIf="oidcAppRequest.appType === OIDCAppType.OIDC_APP_TYPE_WEB || oidcAppRequest.appType === OIDCAppType.OIDC_APP_TYPE_USER_AGENT">
{{'APP.OIDC.REDIRECTDESCRIPTIONWEB' | translate}}</p>
<cnsl-redirect-uris class="redirect-section" [canWrite]="true"
(changedUris)="oidcAppRequest.postLogoutRedirectUrisList = $any($event)"
[urisList]="oidcAppRequest.postLogoutRedirectUrisList" title="{{ 'APP.OIDC.POSTLOGOUTREDIRECT' | translate }}"
[getValues]="requestRedirectValuesSubject$"
[isNative]="oidcAppRequest.appType === OIDCAppType.OIDC_APP_TYPE_NATIVE">
</cnsl-redirect-uris>
<div class="app-create-actions">
<button mat-stroked-button color="primary" matStepperPrevious>{{'ACTIONS.BACK' | translate}}</button>
<span class="fill-space"></span>
<button mat-raised-button color="primary" matStepperNext
[attr.data-e2e]="'continue-button-redirecturis'">{{'ACTIONS.CONTINUE' | translate}}</button>
</div>
</mat-step>
<mat-step>
<ng-template matStepLabel>{{'APP.OIDC.OVERVIEWSECTION'| translate}}</ng-template>
<p class="step-title">{{'APP.OIDC.OVERVIEWTITLE' | translate}}</p>
<div class="row cnsl-secondary-text">
<span class="left">
{{ 'APP.NAME' | translate }}
</span>
<span class="right">
{{oidcAppRequest.name}}
</span>
</div>
<ng-container *ngIf="appType?.value?.createType === AppCreateType.OIDC">
<div class="row cnsl-secondary-text">
<span class="left">
{{ 'APP.TYPE' | translate }}
</span>
<span class="right">
{{'APP.OIDC.APPTYPE.'+oidcAppRequest.appType | translate}}
</span>
</div>
<div class="row cnsl-secondary-text">
<span class="left">
{{ 'APP.GRANT' | translate }}
</span>
<span class="right" *ngIf="oidcAppRequest.grantTypesList && oidcAppRequest.grantTypesList.length > 0">
[<span *ngFor="let element of oidcAppRequest.grantTypesList; index as i">
{{'APP.OIDC.GRANT.'+element | translate}}
{{i < oidcAppRequest.grantTypesList.length - 1 ? ', ' : '' }} </span>]
</span>
</div>
<div class="row cnsl-secondary-text">
<span class="left">
{{ 'APP.OIDC.RESPONSETYPE' | translate }}
</span>
<span class="right" *ngIf="oidcAppRequest.responseTypesList && oidcAppRequest.responseTypesList.length > 0">
[<span *ngFor="let element of oidcAppRequest.responseTypesList; index as i">
{{('APP.OIDC.RESPONSE.'+element | translate)}}
{{i < oidcAppRequest.responseTypesList.length - 1 ? ', ' : '' }} </span>]
</span>
</div>
<div class="row cnsl-secondary-text">
<span class="left">
{{ 'APP.AUTHMETHOD' | translate }}
</span>
<span class="right">
<span>
{{'APP.OIDC.AUTHMETHOD.'+oidcAppRequest?.authMethodType | translate}}
</span>
</span>
</div>
<div class="row cnsl-secondary-text">
<span class="left">
{{ 'APP.OIDC.REDIRECT' | translate }}
</span>
<span class="right" *ngIf="oidcAppRequest.redirectUrisList && oidcAppRequest.redirectUrisList.length > 0">
[<span *ngFor="let redirect of oidcAppRequest.redirectUrisList; index as i">
{{redirect}}
{{i < oidcAppRequest.redirectUrisList.length - 1 ? ', ' : '' }} </span>]
</span>
</div>
<div class="row cnsl-secondary-text">
<span class="left">
{{ 'APP.OIDC.POSTLOGOUTREDIRECT' | translate }}
</span>
<span class="right"
*ngIf="oidcAppRequest.postLogoutRedirectUrisList && oidcAppRequest.postLogoutRedirectUrisList.length > 0">
[<span *ngFor="let redirect of oidcAppRequest.postLogoutRedirectUrisList; index as i">
{{redirect}}
{{i < oidcAppRequest.postLogoutRedirectUrisList.length - 1 ? ', ' : '' }} </span>]
</span>
</div>
</ng-container>
<ng-container *ngIf="appType?.value?.createType === AppCreateType.API">
<div class="row cnsl-secondary-text">
<span class="left">
{{ 'APP.AUTHMETHOD' | translate }}
</span>
<span class="right">
<span>
{{'APP.API.AUTHMETHOD.'+apiAppRequest?.authMethodType | translate}}
</span>
</span>
</div>
</ng-container>
<div class="app-create-actions">
<button mat-stroked-button color="primary" matStepperPrevious>{{'ACTIONS.BACK' | translate}}</button>
<span class="fill-space"></span>
<button mat-raised-button color="primary" (click)="createApp()"
[attr.data-e2e]="'create-button'">{{'ACTIONS.CREATE' |
translate}}</button>
</div>
</mat-step>
</mat-horizontal-stepper>
<div *ngIf="devmode" class="dev">
<form [formGroup]="form" (ngSubmit)="createApp()" [attr.data-e2e]="'create-app-wizzard-3'">
<div class="content">
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'APP.NAME' | translate }}</cnsl-label>
<input cnslInput formControlName="name" />
</cnsl-form-field>
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'APP.TYPE' | translate }}</cnsl-label>
<mat-select formControlName="appType">
<mat-option *ngFor="let appType of appTypes" [value]="appType">
{{ appType.titleI18nKey | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
<ng-container *ngIf="formappType?.value?.createType === AppCreateType.OIDC">
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'APP.OIDC.GRANTTYPE' | translate }}</cnsl-label>
<mat-select formControlName="grantTypesList" multiple>
<mat-option *ngFor="let grant of oidcGrantTypes" [value]="grant.type">
{{ ('APP.OIDC.GRANT.' + grant.type) | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'APP.OIDC.RESPONSETYPE' | translate }}</cnsl-label>
<mat-select formControlName="responseTypesList" multiple>
<mat-option *ngFor="let type of oidcResponseTypes" [value]="type.type">
{{ 'APP.OIDC.RESPONSE.'+type.type | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
</ng-container>
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'APP.AUTHMETHOD' | translate }}</cnsl-label>
<mat-select formControlName="authMethodType">
<mat-option *ngFor="let type of authMethodTypes" [value]="type.type">
<span *ngIf="type.oidc">{{ 'APP.OIDC.AUTHMETHOD.'+type.type | translate }}</span>
<span *ngIf="type.api">{{ 'APP.API.AUTHMETHOD.'+type.type | translate }}</span>
</mat-option>
</mat-select>
</cnsl-form-field>
</div>
<div class="content" *ngIf="formappType?.value?.createType === AppCreateType.OIDC">
<div class="formfield full-width">
<cnsl-redirect-uris class="redirect-section" [canWrite]="true"
(changedUris)="oidcAppRequest.redirectUrisList = $any($event)" [urisList]="oidcAppRequest.redirectUrisList"
title="{{ 'APP.OIDC.REDIRECT' | translate }}" [getValues]="requestRedirectValuesSubject$"
[isNative]="oidcAppRequest.appType === OIDCAppType.OIDC_APP_TYPE_NATIVE">
<cnsl-redirect-uris
class="redirect-section"
[canWrite]="true"
[isNative]="oidcAppRequest.appType === OIDCAppType.OIDC_APP_TYPE_NATIVE"
(changedUris)="oidcAppRequest.redirectUrisList = $any($event)"
[urisList]="oidcAppRequest.redirectUrisList"
[getValues]="requestRedirectValuesSubject$"
title="{{ 'APP.OIDC.REDIRECT' | translate }}"
>
</cnsl-redirect-uris>
<cnsl-redirect-uris class="redirect-section" [canWrite]="true"
<p class="step-title">{{ 'APP.OIDC.POSTREDIRECTTITLE' | translate }}</p>
<p
class="step-description cnsl-secondary-text"
*ngIf="oidcAppRequest.appType === OIDCAppType.OIDC_APP_TYPE_NATIVE"
>
{{ 'APP.OIDC.REDIRECTDESCRIPTIONNATIVE' | translate }}
</p>
<p
class="step-description cnsl-secondary-text"
*ngIf="
oidcAppRequest.appType === OIDCAppType.OIDC_APP_TYPE_WEB ||
oidcAppRequest.appType === OIDCAppType.OIDC_APP_TYPE_USER_AGENT
"
>
{{ 'APP.OIDC.REDIRECTDESCRIPTIONWEB' | translate }}
</p>
<cnsl-redirect-uris
class="redirect-section"
[canWrite]="true"
(changedUris)="oidcAppRequest.postLogoutRedirectUrisList = $any($event)"
[urisList]="oidcAppRequest.postLogoutRedirectUrisList"
title="{{ 'APP.OIDC.POSTLOGOUTREDIRECT' | translate }}" [getValues]="requestRedirectValuesSubject$"
[isNative]="oidcAppRequest.appType === OIDCAppType.OIDC_APP_TYPE_NATIVE">
title="{{ 'APP.OIDC.POSTLOGOUTREDIRECT' | translate }}"
[getValues]="requestRedirectValuesSubject$"
[isNative]="oidcAppRequest.appType === OIDCAppType.OIDC_APP_TYPE_NATIVE"
>
</cnsl-redirect-uris>
</div>
</div>
<button color="primary" mat-raised-button class="continue-button" [disabled]="form.invalid" cdkFocusInitial
type="submit">
{{ 'ACTIONS.SAVE' | translate }}
</button>
</form>
<div class="app-create-actions">
<button mat-stroked-button class="bck-button" matStepperPrevious>{{ 'ACTIONS.BACK' | translate }}</button>
<button mat-raised-button color="primary" matStepperNext [attr.data-e2e]="'continue-button-redirecturis'">
{{ 'ACTIONS.CONTINUE' | translate }}
</button>
</div>
</mat-step>
<mat-step>
<ng-template matStepLabel>{{ 'APP.OIDC.OVERVIEWSECTION' | translate }}</ng-template>
<p class="step-title">{{ 'APP.OIDC.OVERVIEWTITLE' | translate }}</p>
<div class="row cnsl-secondary-text">
<span class="left">
{{ 'APP.NAME' | translate }}
</span>
<span class="right">
{{ oidcAppRequest.name }}
</span>
</div>
<ng-container *ngIf="appType?.value?.createType === AppCreateType.OIDC">
<div class="row cnsl-secondary-text">
<span class="left">
{{ 'APP.TYPE' | translate }}
</span>
<span class="right">
{{ 'APP.OIDC.APPTYPE.' + oidcAppRequest.appType | translate }}
</span>
</div>
<div class="row cnsl-secondary-text">
<span class="left">
{{ 'APP.GRANT' | translate }}
</span>
<span class="right" *ngIf="oidcAppRequest.grantTypesList && oidcAppRequest.grantTypesList.length > 0">
[<span *ngFor="let element of oidcAppRequest.grantTypesList; index as i">
{{ 'APP.OIDC.GRANT.' + element | translate }}
{{ i < oidcAppRequest.grantTypesList.length - 1 ? ', ' : '' }} </span
>]
</span>
</div>
<div class="row cnsl-secondary-text">
<span class="left">
{{ 'APP.OIDC.RESPONSETYPE' | translate }}
</span>
<span class="right" *ngIf="oidcAppRequest.responseTypesList && oidcAppRequest.responseTypesList.length > 0">
[<span *ngFor="let element of oidcAppRequest.responseTypesList; index as i">
{{ 'APP.OIDC.RESPONSE.' + element | translate }}
{{ i < oidcAppRequest.responseTypesList.length - 1 ? ', ' : '' }} </span
>]
</span>
</div>
<div class="row cnsl-secondary-text">
<span class="left">
{{ 'APP.AUTHMETHOD' | translate }}
</span>
<span class="right">
<span>
{{ 'APP.OIDC.AUTHMETHOD.' + oidcAppRequest?.authMethodType | translate }}
</span>
</span>
</div>
<div class="row cnsl-secondary-text">
<span class="left">
{{ 'APP.OIDC.REDIRECT' | translate }}
</span>
<span class="right" *ngIf="oidcAppRequest.redirectUrisList && oidcAppRequest.redirectUrisList.length > 0">
[<span *ngFor="let redirect of oidcAppRequest.redirectUrisList; index as i">
{{ redirect }}
{{ i < oidcAppRequest.redirectUrisList.length - 1 ? ', ' : '' }} </span
>]
</span>
</div>
<div class="row cnsl-secondary-text">
<span class="left">
{{ 'APP.OIDC.POSTLOGOUTREDIRECT' | translate }}
</span>
<span
class="right"
*ngIf="oidcAppRequest.postLogoutRedirectUrisList && oidcAppRequest.postLogoutRedirectUrisList.length > 0"
>
[<span *ngFor="let redirect of oidcAppRequest.postLogoutRedirectUrisList; index as i">
{{ redirect }}
{{ i < oidcAppRequest.postLogoutRedirectUrisList.length - 1 ? ', ' : '' }} </span
>]
</span>
</div>
</ng-container>
<ng-container *ngIf="appType?.value?.createType === AppCreateType.API">
<div class="row cnsl-secondary-text">
<span class="left">
{{ 'APP.AUTHMETHOD' | translate }}
</span>
<span class="right">
<span>
{{ 'APP.API.AUTHMETHOD.' + apiAppRequest?.authMethodType | translate }}
</span>
</span>
</div>
</ng-container>
<div class="app-create-actions">
<button mat-stroked-button matStepperPrevious class="bck-button">{{ 'ACTIONS.BACK' | translate }}</button>
<button
mat-raised-button
class="create-button"
color="primary"
(click)="createApp()"
[attr.data-e2e]="'create-button'"
>
{{ 'ACTIONS.CREATE' | translate }}
</button>
</div>
</mat-step>
</mat-horizontal-stepper>
<div *ngIf="devmode" class="dev">
<form [formGroup]="form" (ngSubmit)="createApp()" [attr.data-e2e]="'create-app-wizzard-3'">
<div class="content">
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'APP.NAME' | translate }}</cnsl-label>
<input cnslInput formControlName="name" />
</cnsl-form-field>
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'APP.TYPE' | translate }}</cnsl-label>
<mat-select formControlName="appType">
<mat-option *ngFor="let appType of appTypes" [value]="appType">
{{ appType.titleI18nKey | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
<ng-container *ngIf="formappType?.value?.createType === AppCreateType.OIDC">
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'APP.OIDC.GRANTTYPE' | translate }}</cnsl-label>
<mat-select formControlName="grantTypesList" multiple>
<mat-option *ngFor="let grant of oidcGrantTypes" [value]="grant.type">
{{ 'APP.OIDC.GRANT.' + grant.type | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'APP.OIDC.RESPONSETYPE' | translate }}</cnsl-label>
<mat-select formControlName="responseTypesList" multiple>
<mat-option *ngFor="let type of oidcResponseTypes" [value]="type.type">
{{ 'APP.OIDC.RESPONSE.' + type.type | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
</ng-container>
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'APP.AUTHMETHOD' | translate }}</cnsl-label>
<mat-select formControlName="authMethodType">
<mat-option *ngFor="let type of authMethodTypes" [value]="type.type">
<span *ngIf="type.oidc">{{ 'APP.OIDC.AUTHMETHOD.' + type.type | translate }}</span>
<span *ngIf="type.api">{{ 'APP.API.AUTHMETHOD.' + type.type | translate }}</span>
</mat-option>
</mat-select>
</cnsl-form-field>
</div>
<div class="content" *ngIf="formappType?.value?.createType === AppCreateType.OIDC">
<div class="formfield full-width">
<cnsl-redirect-uris
class="redirect-section"
[canWrite]="true"
(changedUris)="oidcAppRequest.redirectUrisList = $any($event)"
[urisList]="oidcAppRequest.redirectUrisList"
title="{{ 'APP.OIDC.REDIRECT' | translate }}"
[getValues]="requestRedirectValuesSubject$"
[isNative]="oidcAppRequest.appType === OIDCAppType.OIDC_APP_TYPE_NATIVE"
>
</cnsl-redirect-uris>
<cnsl-redirect-uris
class="redirect-section"
[canWrite]="true"
(changedUris)="oidcAppRequest.postLogoutRedirectUrisList = $any($event)"
[urisList]="oidcAppRequest.postLogoutRedirectUrisList"
title="{{ 'APP.OIDC.POSTLOGOUTREDIRECT' | translate }}"
[getValues]="requestRedirectValuesSubject$"
[isNative]="oidcAppRequest.appType === OIDCAppType.OIDC_APP_TYPE_NATIVE"
>
</cnsl-redirect-uris>
</div>
</div>
<button
color="primary"
mat-raised-button
class="continue-button"
[disabled]="form.invalid"
cdkFocusInitial
type="submit"
>
{{ 'ACTIONS.CREATE' | translate }}
</button>
</form>
</div>
</div>
</div>
</div>
</div>

View File

@@ -12,109 +12,122 @@ p.desc {
display: block;
}
.app-create-container {
padding-top: 2rem;
.abort-container {
display: flex;
align-items: center;
margin-bottom: 2rem;
.progress-bar {
margin-bottom: 1rem;
.abort {
font-size: 1.2rem;
margin-left: 2rem;
}
.abort-container {
display: flex;
align-items: center;
margin-bottom: 2rem;
.abort {
font-size: 1.2rem;
margin-left: 2rem;
}
.abort-2 {
font-size: 1.2rem;
margin-left: 2rem;
white-space: nowrap;
}
.abort-2 {
font-size: 1.2rem;
margin-left: 2rem;
white-space: nowrap;
}
}
.margin-right {
margin-right: 0.5rem;
}
.app-create-content {
padding-left: 4.5rem;
.full-width {
width: 100%;
}
.stepper {
background: inherit !important;
margin: 0 -1.5rem;
.formfield {
.name-formfield {
max-width: 400px;
display: block;
}
.step-title {
font-size: 1rem;
text-transform: uppercase;
letter-spacing: 0.05em;
.app-create-container {
padding-top: 2rem;
.progress-bar {
margin-bottom: 1rem;
}
}
.step-description {
font-size: 0.9rem;
.margin-right {
margin-right: 0.5rem;
}
}
.checkbox-container {
display: flex;
flex-direction: column;
.checkbox {
margin: 0.5rem 0;
.full-width {
width: 100%;
}
}
.row {
display: flex;
justify-content: space-between;
.left,
.right {
margin-bottom: 0.5rem;
font-size: 14px;
}
}
.app-create-actions {
margin-top: 1rem;
display: flex;
.fill-space {
flex: 1;
}
}
.dev {
.content {
display: flex;
margin: 0 -0.5rem;
flex-wrap: wrap;
flex-direction: row;
.stepper {
background: inherit !important;
margin: 0 -1.5rem;
.formfield {
flex: 1;
box-sizing: border-box;
margin: 0 0.5rem;
max-width: 400px;
}
&.full-width {
flex-basis: 80%;
}
.step-title {
font-size: 1rem;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.step-description {
font-size: 0.9rem;
}
}
.continue-button {
margin-top: 3rem;
display: block;
padding: 0.5rem 4rem;
float: right;
.checkbox-container {
display: flex;
flex-direction: column;
.checkbox {
margin: 0.5rem 0;
}
}
.row {
display: flex;
justify-content: space-between;
.left,
.right {
margin-bottom: 0.5rem;
font-size: 14px;
}
}
.app-create-actions {
margin-top: 1rem;
display: flex;
align-items: center;
.bck-button {
margin-right: 1rem;
}
.create-button {
padding: 0.5rem 4rem;
}
}
.dev {
.content {
display: flex;
margin: 0 -0.5rem;
flex-wrap: wrap;
flex-direction: row;
.formfield {
flex: 1;
box-sizing: border-box;
margin: 0 0.5rem;
&.full-width {
flex-basis: 80%;
}
}
}
.continue-button {
margin-top: 3rem;
display: block;
padding: 0.5rem 4rem;
}
}
}

View File

@@ -49,6 +49,9 @@ export class AppCreateComponent implements OnInit, OnDestroy {
public projectId: string = '';
public loading: boolean = false;
public createSteps: number = 4;
public currentCreateStep: number = 1;
public oidcAppRequest: AddOIDCAppRequest.AsObject = new AddOIDCAppRequest().toObject();
public apiAppRequest: AddAPIAppRequest.AsObject = new AddAPIAppRequest().toObject();
@@ -270,6 +273,8 @@ export class AppCreateComponent implements OnInit, OnDestroy {
}
public changeStep(event: StepperSelectionEvent): void {
this.currentCreateStep = event.selectedIndex + 1;
if (event.selectedIndex >= 2) {
this.requestRedirectValuesSubject$.next();
}

View File

@@ -1,32 +1,54 @@
<cnsl-top-view title="{{project?.projectName}}" [hasActions]="false"
<cnsl-top-view
title="{{ project?.projectName }}"
[hasActions]="false"
docLink="https://docs.zitadel.ch/docs/guides/basics/projects#what-is-a-granted-project"
sub="{{'PROJECT.PAGES.TYPE.GRANTED_SINGULAR' | translate}} {{'ACTIONS.OF' | translate}} <strong>{{project?.projectOwnerName}}</strong>"
sub="{{ 'PROJECT.PAGES.TYPE.GRANTED_SINGULAR' | translate }} {{ 'ACTIONS.OF' | translate }} <strong>{{
project?.projectOwnerName
}}</strong>"
[isActive]="project?.state === ProjectGrantState.PROJECT_GRANT_STATE_ACTIVE"
[isInactive]="project?.state === ProjectGrantState.PROJECT_GRANT_STATE_INACTIVE"
stateTooltip="{{'ORG.STATE.'+project.state | translate}}" [hasContributors]="true">
stateTooltip="{{ 'ORG.STATE.' + project?.state | translate }}"
[hasContributors]="true"
>
<p topContent *ngIf="isZitadel" class="granted-project-sub zitadel-warning">
{{'PROJECT.PAGES.ZITADELPROJECT' | translate}}
{{ 'PROJECT.PAGES.ZITADELPROJECT' | translate }}
</p>
<cnsl-contributors topContributors class="project-contributors" *ngIf="project" [loading]="loading$ | async"
[totalResult]="totalMemberResult" [membersSubject]="membersSubject" title="{{ 'PROJECT.MEMBER.TITLE' | translate }}"
description="{{ 'PROJECT.MEMBER.TITLEDESC' | translate }}" (addClicked)="openAddMember()"
(showDetailClicked)="showDetail()" (refreshClicked)="loadMembers()"
[disabled]="(['project.member.write$', 'project.member.write:'+ project.projectId]| hasRole | async) === false">
<cnsl-contributors
topContributors
class="project-contributors"
*ngIf="project"
[loading]="loading$ | async"
[totalResult]="totalMemberResult"
[membersSubject]="membersSubject"
title="{{ 'PROJECT.MEMBER.TITLE' | translate }}"
description="{{ 'PROJECT.MEMBER.TITLEDESC' | translate }}"
(addClicked)="openAddMember()"
(showDetailClicked)="showDetail()"
(refreshClicked)="loadMembers()"
[disabled]="(['project.member.write$', 'project.member.write:' + project.projectId] | hasRole | async) === false"
>
</cnsl-contributors>
<cnsl-info-row topContent *ngIf="project" [grantedProject]="project"></cnsl-info-row>
</cnsl-top-view>
<div class="max-width-container">
<cnsl-meta-layout>
<ng-template cnslHasRole [hasRole]="['user.grant.read', 'user.grant.read:'+grantId]">
<cnsl-card *ngIf="project?.projectId" title="{{ 'GRANTS.PROJECT.TITLE' | translate }}"
description="{{'GRANTS.PROJECT.DESCRIPTION' | translate }}">
<cnsl-user-grants *ngIf="projectId && grantId" [context]="UserGrantContext.GRANTED_PROJECT"
[projectId]="projectId" [grantId]="grantId"
[displayedColumns]="['select','user', 'projectId', 'creationDate','changeDate', 'roleNamesList','actions']"
[disableWrite]="(['user.grant.write$','user.grant.write:'+grantId] | hasRole | async) === false"
[disableDelete]="(['user.grant.delete$','user.grant.delete:'+grantId] | hasRole | async) === false"
[refreshOnPreviousRoutes]="['/grant-create/project/{{projectId}}/grant/{{grantId}}']">
<ng-template cnslHasRole [hasRole]="['user.grant.read', 'user.grant.read:' + grantId]">
<cnsl-card
*ngIf="project?.projectId"
title="{{ 'GRANTS.PROJECT.TITLE' | translate }}"
description="{{ 'GRANTS.PROJECT.DESCRIPTION' | translate }}"
>
<cnsl-user-grants
*ngIf="projectId && grantId"
[context]="UserGrantContext.GRANTED_PROJECT"
[projectId]="projectId"
[grantId]="grantId"
[displayedColumns]="['select', 'user', 'projectId', 'creationDate', 'changeDate', 'roleNamesList', 'actions']"
[disableWrite]="(['user.grant.write$', 'user.grant.write:' + grantId] | hasRole | async) === false"
[disableDelete]="(['user.grant.delete$', 'user.grant.delete:' + grantId] | hasRole | async) === false"
[refreshOnPreviousRoutes]="['/grant-create/project/{{projectId}}/grant/{{grantId}}']"
>
</cnsl-user-grants>
</cnsl-card>
</ng-template>
@@ -34,6 +56,5 @@
<div metainfo>
<cnsl-changes *ngIf="project" [changeType]="ChangeType.PROJECT" [id]="project.projectId"></cnsl-changes>
</div>
</cnsl-meta-layout>
</div>
</div>

View File

@@ -89,7 +89,7 @@ export class ProjectRoleCreateComponent implements OnInit, OnDestroy {
.bulkAddProjectRoles(this.projectId, rolesToAdd)
.then(() => {
this.toast.showInfo('PROJECT.TOAST.ROLESCREATED', true);
this.router.navigate(['projects', this.projectId, 'roles']);
this.router.navigate(['projects', this.projectId], { queryParams: { id: 'roles' } });
})
.catch((error) => {
this.toast.showError(error);

View File

@@ -4,27 +4,31 @@
<button (click)="close()" mat-icon-button>
<mat-icon>close</mat-icon>
</button>
<span class="abort">{{ 'PROJECT.PAGES.CREATE' | translate }}</span><span class="abort-2">Step
{{ currentCreateStep }} of
{{ createSteps }}</span>
<span class="abort">{{ 'PROJECT.PAGES.CREATE' | translate }}</span>
</div>
<div class="project-create-content">
<h1>{{'PROJECT.PAGES.CREATE_DESC' | translate}}</h1>
<h1>{{ 'PROJECT.PAGES.CREATE_DESC' | translate }}</h1>
<form cdkFocusRegionStart (ngSubmit)="saveProject()">
<div class="column">
<cnsl-form-field class="formfield" hintLabel="The name is required!">
<cnsl-label>{{'PROJECT.NAME' | translate}}</cnsl-label>
<input cnslInput cdkFocusInitial autofocus [(ngModel)]="project.name"
[ngModelOptions]="{ standalone: true }" />
<cnsl-label>{{ 'PROJECT.NAME' | translate }}</cnsl-label>
<input cnslInput cdkFocusInitial autofocus [(ngModel)]="project.name" [ngModelOptions]="{ standalone: true }" />
</cnsl-form-field>
</div>
<button color="primary" mat-raised-button class="continue-button" [disabled]="!project.name" cdkFocusInitial
type="submit" [attr.data-e2e]="'continue-button'">
{{'ACTIONS.CONTINUE' | translate}}
<button
color="primary"
mat-raised-button
class="continue-button"
[disabled]="!project.name"
cdkFocusInitial
type="submit"
[attr.data-e2e]="'continue-button'"
>
{{ 'ACTIONS.CONTINUE' | translate }}
</button>
</form>
</div>
</div>
</div>
</div>

View File

@@ -11,12 +11,6 @@ h1 {
font-size: 1.2rem;
margin-left: 2rem;
}
.abort-2 {
font-size: 1.2rem;
margin-left: 2rem;
white-space: nowrap;
}
}
.project-create-content {

View File

@@ -28,9 +28,6 @@ export class ProjectCreateComponent {
breadcrumbService.setBreadcrumb([bread]);
}
public createSteps: number = 1;
public currentCreateStep: number = 1;
public saveProject(): void {
this.mgmtService
.addProject(this.project)

View File

@@ -1,79 +1,95 @@
<div class="grid-main-container" *ngIf="projectType$ | async as type">
<div class="loading-sp-wrapper">
<mat-progress-spinner diameter="25" *ngIf="(loading$| async) === false" class="spinner" color="primary">
</mat-progress-spinner>
<div class="loading-sp-wrapper" *ngIf="loading$ | async">
<mat-spinner diameter="25" class="spinner" color="primary"> </mat-spinner>
</div>
<div class="owned-project-grid-container">
<div class="item card" matRipple *ngFor="let item of selection.selected; index as i"
<div
class="item card"
matRipple
*ngFor="let item of selection.selected; index as i"
(click)="navigateToProject(type, item, $event)"
[ngClass]="{ inactive: item.state !== ProjectState.PROJECT_STATE_ACTIVE}">
[ngClass]="{ inactive: item.state !== ProjectState.PROJECT_STATE_ACTIVE }"
>
<div class="text-part">
<span *ngIf="item.details && item.details.changeDate"
class="top cnsl-secondary-text">{{'PROJECT.PAGES.LASTMODIFIED' |
translate}}
{{ item.details.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }}</span>
<span *ngIf="item.details && item.details.changeDate" class="top cnsl-secondary-text"
>{{ 'PROJECT.PAGES.LASTMODIFIED' | translate }}
{{ item.details.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }}</span
>
<div class="name-row">
<span class="name" *ngIf="$any(item).name">{{ $any(item).name }}</span>
<span class="name" *ngIf="$any(item).projectName">{{ $any(item).projectName }}</span>
<div class="state-dot"
[ngClass]="{'active': item.state === ProjectState.PROJECT_STATE_ACTIVE, 'inactive': item.state === ProjectState.PROJECT_STATE_INACTIVE}">
</div>
<div
class="state-dot"
[ngClass]="{
active: item.state === ProjectState.PROJECT_STATE_ACTIVE,
inactive: item.state === ProjectState.PROJECT_STATE_INACTIVE
}"
></div>
</div>
<span *ngIf="item.details && item.details.creationDate" class="created">{{'PROJECT.PAGES.CREATEDON' |
translate}}
{{ item.details.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }}</span>
<span *ngIf="item.details && item.details.creationDate" class="created"
>{{ 'PROJECT.PAGES.CREATEDON' | translate }}
{{ item.details.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }}</span
>
<span class="fill-space"></span>
</div>
<template [ngTemplateOutlet]="deleteButton" [ngTemplateOutletContext]="{key: item}"></template>
<template [ngTemplateOutlet]="toggleButton" [ngTemplateOutletContext]="{key: item}"></template>
<template [ngTemplateOutlet]="deleteButton" [ngTemplateOutletContext]="{ key: item }"></template>
<template [ngTemplateOutlet]="toggleButton" [ngTemplateOutletContext]="{ key: item }"></template>
</div>
</div>
<div class="owned-project-grid-container">
<div class="item card" matRipple *ngFor="let item of notPinned; index as i"
<div
class="item card"
matRipple
*ngFor="let item of notPinned; index as i"
(click)="navigateToProject(type, $any(item), $event)"
[ngClass]="{ inactive: item.state !== ProjectState.PROJECT_STATE_ACTIVE}" [attr.data-e2e]="'grid-card'">
[ngClass]="{ inactive: item.state !== ProjectState.PROJECT_STATE_ACTIVE }"
[attr.data-e2e]="'grid-card'"
>
<div class="text-part">
<span *ngIf="item.details && item.details.changeDate"
class="top cnsl-secondary-text">{{'PROJECT.PAGES.LASTMODIFIED' | translate}}
{{ item.details.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }}</span>
<span *ngIf="item.details && item.details.changeDate" class="top cnsl-secondary-text"
>{{ 'PROJECT.PAGES.LASTMODIFIED' | translate }}
{{ item.details.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }}</span
>
<div class="name-row">
<span class="name" *ngIf="$any(item).name">{{ $any(item).name }}</span>
<span class="name" *ngIf="$any(item).projectName">{{ $any(item).projectName }}</span>
<div class="state-dot"
[ngClass]="{'active': item.state === ProjectState.PROJECT_STATE_ACTIVE, 'inactive': item.state === ProjectState.PROJECT_STATE_INACTIVE}">
</div>
<div
class="state-dot"
[ngClass]="{
active: item.state === ProjectState.PROJECT_STATE_ACTIVE,
inactive: item.state === ProjectState.PROJECT_STATE_INACTIVE
}"
></div>
</div>
<span class="owning-org" *ngIf="$any(item).projectOwnerName">{{$any(item).projectOwnerName}}</span>
<span *ngIf="item.details && item.details.creationDate"
class="created cnsl-secondary-text">{{'PROJECT.PAGES.CREATEDON' |
translate}}
{{
item.details.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'
}}</span>
<span class="owning-org" *ngIf="$any(item).projectOwnerName">{{ $any(item).projectOwnerName }}</span>
<span *ngIf="item.details && item.details.creationDate" class="created cnsl-secondary-text"
>{{ 'PROJECT.PAGES.CREATEDON' | translate }}
{{ item.details.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }}</span
>
<span class="fill-space"></span>
</div>
<ng-container *ngIf="type === ProjectType.PROJECTTYPE_OWNED">
<template [ngTemplateOutlet]="deleteButton" [ngTemplateOutletContext]="{key: item}"></template>
<template [ngTemplateOutlet]="deleteButton" [ngTemplateOutletContext]="{ key: item }"></template>
</ng-container>
<template [ngTemplateOutlet]="toggleButton" [ngTemplateOutletContext]="{key: item}"></template>
<template [ngTemplateOutlet]="toggleButton" [ngTemplateOutletContext]="{ key: item }"></template>
</div>
<p class="n-items cnsl-secondary-text" *ngIf="(loading$ | async) === false && projectList.length === 0">
{{'PROJECT.PAGES.NOITEMS' |
translate}}</p>
{{ 'PROJECT.PAGES.NOITEMS' | translate }}
</p>
<ng-container *ngIf="type === ProjectType.PROJECTTYPE_OWNED">
<ng-template cnslHasRole [hasRole]="['project.create']">
<div class="add-project-button card" matRipple (click)="addItem()">
<mat-icon class="icon">add</mat-icon>
<span>{{'PROJECT.PAGES.ADDNEW' | translate}}</span>
<span>{{ 'PROJECT.PAGES.ADDNEW' | translate }}</span>
<cnsl-action-keys [doNotUseContrast]="true" [withoutMargin]="true" (actionTriggered)="addItem()">
</cnsl-action-keys>
</div>
@@ -83,17 +99,28 @@
</div>
<ng-template #deleteButton let-key="key">
<button *ngIf="key.id !== zitadelProjectId" matTooltip="{{'ACTIONS.DELETE' | translate}}" color="warn"
(click)="deleteProject($event, key)" class="delete-button" mat-icon-button
[attr.data-e2e]="'delete-project-button'">
<button
*ngIf="key.id !== zitadelProjectId"
matTooltip="{{ 'ACTIONS.DELETE' | translate }}"
color="warn"
(click)="deleteProject($event, key)"
class="delete-button"
mat-icon-button
[attr.data-e2e]="'delete-project-button'"
>
<i class="las la-trash"></i>
</button>
</ng-template>
<ng-template #toggleButton let-key="key">
<button matTooltip="{{'ACTIONS.PIN' | translate}}" [ngClass]="{ selected: selection.isSelected(key)}"
(click)="toggle(key,$event)" class="edit-button" mat-icon-button>
<button
matTooltip="{{ 'ACTIONS.PIN' | translate }}"
[ngClass]="{ selected: selection.isSelected(key) }"
(click)="toggle(key, $event)"
class="edit-button"
mat-icon-button
>
<mat-icon *ngIf="selection.isSelected(key)" svgIcon="mdi_pin"></mat-icon>
<mat-icon svgIcon="mdi_pin_outline" *ngIf="!selection.isSelected(key)"></mat-icon>
</button>
</ng-template>
</ng-template>

View File

@@ -25,17 +25,12 @@
}
}
.spinner {
margin: 1rem;
}
.grid-main-container {
position: relative;
.loading-sp-wrapper {
position: absolute;
top: 0;
left: 0;
display: block;
margin: 1rem 0.5rem 0 0.5rem;
}
.owned-project-grid-container {

View File

@@ -144,7 +144,6 @@ export class ProjectGridComponent implements OnInit, OnDestroy {
this.loadingSubject.next(false);
})
.catch((error) => {
console.error(error);
this.toast.showError(error);
this.loadingSubject.next(false);
});

View File

@@ -10,12 +10,20 @@
<div class="projects-controls">
<div class="project-type-actions">
<button class="type-button" [ngClass]="{'active': (projectType$ | async) === ProjectType.PROJECTTYPE_OWNED}"
(click)="setType(ProjectType.PROJECTTYPE_OWNED)">{{'PROJECT.PAGES.TYPE.OWNED' | translate}}
({{((mgmtService?.ownedProjectsCount | async) ?? 0)}})</button>
<button class="type-button" [ngClass]="{'active': (projectType$ | async) === ProjectType.PROJECTTYPE_GRANTED}"
(click)="setType(ProjectType.PROJECTTYPE_GRANTED)">{{'PROJECT.PAGES.TYPE.GRANTED' | translate}}
({{((mgmtService?.grantedProjectsCount | async) ?? 0)}})</button>
<button
class="type-button"
[ngClass]="{ active: (projectType$ | async) === ProjectType.PROJECTTYPE_OWNED }"
(click)="setType(ProjectType.PROJECTTYPE_OWNED)"
>
{{ 'PROJECT.PAGES.TYPE.OWNED' | translate }} ({{ (mgmtService?.ownedProjectsCount | async) ?? 0 }})
</button>
<button
class="type-button"
[ngClass]="{ active: (projectType$ | async) === ProjectType.PROJECTTYPE_GRANTED }"
(click)="setType(ProjectType.PROJECTTYPE_GRANTED)"
>
{{ 'PROJECT.PAGES.TYPE.GRANTED' | translate }} ({{ (mgmtService?.grantedProjectsCount | async) ?? 0 }})
</button>
</div>
<span class="fill-space"></span>
<button class="grid-btn" (click)="grid = !grid" mat-icon-button [attr.data-e2e]="'toggle-grid'">
@@ -24,11 +32,15 @@
</button>
</div>
<cnsl-project-grid *ngIf="grid" [projectType$]="projectType$" [zitadelProjectId]="zitadelProjectId"
(emitAddProject)="addProject()">
<cnsl-project-grid
*ngIf="grid"
[projectType$]="projectType$"
[zitadelProjectId]="zitadelProjectId"
(emitAddProject)="addProject()"
>
</cnsl-project-grid>
<cnsl-project-list *ngIf="!grid" [projectType$]="projectType$" [zitadelProjectId]="zitadelProjectId">
</cnsl-project-list>
</div>
</div>
</div>

View File

@@ -53,6 +53,7 @@ export class ProjectsComponent {
type:
type === ProjectType.PROJECTTYPE_OWNED ? 'owned' : type === ProjectType.PROJECTTYPE_GRANTED ? 'granted' : 'owned',
},
replaceUrl: true,
queryParamsHandling: 'merge',
skipLocationChange: false,
});

View File

@@ -4,53 +4,64 @@
<button (click)="close()" mat-icon-button>
<mat-icon>close</mat-icon>
</button>
<h1 class="abort">{{ 'GRANTS.CREATE.TITLE' | translate }}</h1><span class="abort-2">Step
{{ currentCreateStep }} of
{{ STEPS }}</span>
<h1 class="abort">{{ 'GRANTS.CREATE.TITLE' | translate }}</h1>
<span class="abort-2">Step {{ currentCreateStep }} of {{ STEPS }}</span>
</div>
<div class="user-grant-create-content">
<ng-container *ngIf="currentCreateStep === 1">
<p class="user-grant-create-desc cnsl-secondary-text">
{{'PROJECT.GRANT.CREATE.ORG_DESCRIPTION' | translate: org}}
<br>
{{'PROJECT.GRANT.CREATE.ORG_DESCRIPTION_DESC' | translate}}
{{ 'PROJECT.GRANT.CREATE.ORG_DESCRIPTION' | translate: org }}
<br />
{{ 'PROJECT.GRANT.CREATE.ORG_DESCRIPTION_DESC' | translate }}
</p>
<ng-container>
<h2>{{'PROJECT.GRANT.CREATE.SEL_USER' | translate}}</h2>
<h2>{{ 'PROJECT.GRANT.CREATE.SEL_USER' | translate }}</h2>
<cnsl-search-user-autocomplete [editState]="context !== UserGrantContext.USER" class="block"
[users]="user ? [user] : []" (selectionChanged)="selectUsers($event)" [target]="UserTarget.SELF">
<cnsl-search-user-autocomplete
[editState]="context !== UserGrantContext.USER"
class="block"
[users]="user ? [user] : []"
(selectionChanged)="selectUsers($event)"
[target]="UserTarget.SELF"
>
</cnsl-search-user-autocomplete>
</ng-container>
<ng-container *ngIf="context && (context === UserGrantContext.USER || context === UserGrantContext.NONE)">
<h2 class="project-search">{{'PROJECT.GRANT.CREATE.SEL_PROJECT' | translate}}</h2>
<h2 class="project-search">{{ 'PROJECT.GRANT.CREATE.SEL_PROJECT' | translate }}</h2>
<cnsl-search-project-autocomplete class="block"
(selectionChanged)="selectProject($event.project, $event.type)">
<cnsl-search-project-autocomplete class="block" (selectionChanged)="selectProject($event.project, $event.type)">
</cnsl-search-project-autocomplete>
</ng-container>
</ng-container>
<ng-container *ngIf="currentCreateStep === STEPS">
<h1>{{'PROJECT.GRANT.CREATE.SEL_ROLES' | translate}}</h1>
<h1>{{ 'PROJECT.GRANT.CREATE.SEL_ROLES' | translate }}</h1>
<cnsl-card>
{{ $any(project)?.grantId }}
<cnsl-project-roles-table
[displayedColumns]="['select', 'key', 'displayname', 'group', 'creationDate', 'changeDate']"
(changedSelection)="selectRoles($event)"
[projectId]="project?.id ? project.id : grantedProject?.projectId ? grantedProject.projectId : ''"
[grantId]="$any(project)?.grantId ? $any(project)?.grantId : ''">
[grantId]="$any(grantedProject)?.grantId ? $any(grantedProject)?.grantId : ''"
>
</cnsl-project-roles-table>
</cnsl-card>
</ng-container>
<div class="btn-container">
<ng-container *ngIf="currentCreateStep === 1">
<button [disabled]="!org || !(project?.id || grantedProject?.projectId) || userIds.length < 1"
(click)="next()" color="primary" mat-raised-button class="big-button" cdkFocusInitial>
<button
[disabled]="!org || !(project?.id || grantedProject?.projectId) || userIds.length < 1"
(click)="next()"
color="primary"
mat-raised-button
class="big-button"
cdkFocusInitial
>
{{ 'ACTIONS.CONTINUE' | translate }}
</button>
</ng-container>
@@ -66,4 +77,4 @@
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,11 +1,10 @@
<div class="max-width-container">
<div class="enlarged-container">
<div class="abort-container">
<a [routerLink]="[ '/users']" mat-icon-button>
<a [routerLink]="['/users']" mat-icon-button>
<mat-icon>close</mat-icon>
</a>
<h1 class="abort">{{ 'USER.CREATE.TITLE' | translate }}</h1><span class="abort-2">Step
1 of 1</span>
<h1 class="abort">{{ 'USER.CREATE.TITLE' | translate }}</h1>
</div>
<div class="user-create-main-content">
@@ -13,63 +12,69 @@
<form *ngIf="userForm" [formGroup]="userForm" (ngSubmit)="createUser()" class="user-create-form">
<div class="user-create-content">
<p class="user-create-section cnsl-secondary-text">{{ 'USER.CREATE.NAMEANDEMAILSECTION' | translate }}</p>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PROFILE.EMAIL' | translate }}*</cnsl-label>
<input cnslInput matRipple formControlName="email" required />
<span cnslError *ngIf="email?.invalid && !email?.errors?.required">
{{ 'USER.VALIDATION.NOTANEMAIL' | translate }}
</span>
<span cnslError *ngIf="email?.invalid && email?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</span>
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PROFILE.USERNAME' | translate }}*</cnsl-label>
<input cnslInput formControlName="userName" required
[ngStyle]="{'padding-right': suffixPadding ? suffixPadding : '10px'}" />
<span #suffix *ngIf="envSuffixLabel" cnslSuffix>{{envSuffixLabel}}</span>
<p class="user-create-section">{{ 'USER.CREATE.NAMEANDEMAILSECTION' | translate }}</p>
<span cnslError *ngIf="userName?.invalid && userName?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</span>
<span cnslError *ngIf="userName?.invalid && userName?.errors?.noEmailValidator">
{{ 'USER.VALIDATION.NOEMAIL' | translate }}
</span>
</cnsl-form-field>
</div>
<div class="user-create-content">
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PROFILE.FIRSTNAME' | translate }}*</cnsl-label>
<input cnslInput formControlName="firstName" required />
<span cnslError *ngIf="firstName?.invalid && firstName?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</span>
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PROFILE.LASTNAME' | translate }}*</cnsl-label>
<input cnslInput formControlName="lastName" required />
<span cnslError *ngIf="lastName?.invalid && lastName?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</span>
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PROFILE.NICKNAME' | translate }}</cnsl-label>
<input cnslInput formControlName="nickName" />
<span cnslError *ngIf="nickName?.invalid && nickName?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</span>
</cnsl-form-field>
<div class="user-create-grid">
<cnsl-form-field>
<cnsl-label>{{ 'USER.PROFILE.EMAIL' | translate }}*</cnsl-label>
<input cnslInput matRipple formControlName="email" required />
<span cnslError *ngIf="email?.invalid && !email?.errors?.required">
{{ 'USER.VALIDATION.NOTANEMAIL' | translate }}
</span>
<span cnslError *ngIf="email?.invalid && email?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</span>
</cnsl-form-field>
<cnsl-form-field>
<cnsl-label>{{ 'USER.PROFILE.USERNAME' | translate }}*</cnsl-label>
<input
cnslInput
formControlName="userName"
required
[ngStyle]="{ 'padding-right': suffixPadding ? suffixPadding : '10px' }"
/>
<span #suffix *ngIf="envSuffixLabel" cnslSuffix>{{ envSuffixLabel }}</span>
<span cnslError *ngIf="userName?.invalid && userName?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</span>
<span cnslError *ngIf="userName?.invalid && userName?.errors?.noEmailValidator">
{{ 'USER.VALIDATION.NOEMAIL' | translate }}
</span>
</cnsl-form-field>
<cnsl-form-field>
<cnsl-label>{{ 'USER.PROFILE.FIRSTNAME' | translate }}*</cnsl-label>
<input cnslInput formControlName="firstName" required />
<span cnslError *ngIf="firstName?.invalid && firstName?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</span>
</cnsl-form-field>
<cnsl-form-field>
<cnsl-label>{{ 'USER.PROFILE.LASTNAME' | translate }}*</cnsl-label>
<input cnslInput formControlName="lastName" required />
<span cnslError *ngIf="lastName?.invalid && lastName?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</span>
</cnsl-form-field>
<cnsl-form-field>
<cnsl-label>{{ 'USER.PROFILE.NICKNAME' | translate }}</cnsl-label>
<input cnslInput formControlName="nickName" />
<span cnslError *ngIf="nickName?.invalid && nickName?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</span>
</cnsl-form-field>
</div>
<div class="email-is-verified">
<mat-checkbox class="block-checkbox" formControlName="isVerified">
{{'USER.LOGINMETHODS.EMAIL.ISVERIFIED' | translate}}
{{ 'USER.LOGINMETHODS.EMAIL.ISVERIFIED' | translate }}
</mat-checkbox>
<mat-checkbox class="block-checkbox" [(ngModel)]="usePassword" [ngModelOptions]="{standalone: true}">
{{'ORG.PAGES.USEPASSWORD' | translate}}
<mat-checkbox class="block-checkbox" [(ngModel)]="usePassword" [ngModelOptions]="{ standalone: true }">
{{ 'ORG.PAGES.USEPASSWORD' | translate }}
</mat-checkbox>
<cnsl-info-section class="full-width desc">
<span>{{'USER.CREATE.INITMAILDESCRIPTION' | translate}}</span>
<span>{{ 'USER.CREATE.INITMAILDESCRIPTION' | translate }}</span>
</cnsl-info-section>
</div>
@@ -77,59 +82,67 @@
<cnsl-password-complexity-view class="complexity-view" [policy]="this.policy" [password]="password">
</cnsl-password-complexity-view>
<form [formGroup]="pwdForm" class="user-create-pwd-form">
<cnsl-form-field class="pwd-field" *ngIf="password" appearance="outline">
<cnsl-label>{{ 'USER.PASSWORD.NEWINITIAL' | translate }}</cnsl-label>
<input cnslInput autocomplete="off" name="firstpassword" formControlName="password" type="password" />
<form [formGroup]="pwdForm">
<div class="user-create-grid">
<cnsl-form-field *ngIf="password">
<cnsl-label>{{ 'USER.PASSWORD.NEWINITIAL' | translate }}</cnsl-label>
<input cnslInput autocomplete="off" name="firstpassword" formControlName="password" type="password" />
<span cnslError *ngIf="password?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</span>
<span cnslError *ngIf="password?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</span>
</cnsl-form-field>
<cnsl-form-field *ngIf="confirmPassword">
<cnsl-label>{{ 'USER.PASSWORD.CONFIRMINITIAL' | translate }}</cnsl-label>
<input
cnslInput
autocomplete="off"
name="confirmPassword"
formControlName="confirmPassword"
type="password"
/>
</cnsl-form-field>
<cnsl-form-field class="pwd-field" *ngIf="confirmPassword" appearance="outline">
<cnsl-label>{{ 'USER.PASSWORD.CONFIRMINITIAL' | translate }}</cnsl-label>
<input cnslInput autocomplete="off" name="confirmPassword" formControlName="confirmPassword"
type="password" />
<span cnslError *ngIf="confirmPassword?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</span>
<span cnslError *ngIf="confirmPassword?.errors?.notequal">
{{ 'USER.PASSWORD.NOTEQUAL' | translate }}
</span>
</cnsl-form-field>
<span cnslError *ngIf="confirmPassword?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</span>
<span cnslError *ngIf="confirmPassword?.errors?.notequal">
{{ 'USER.PASSWORD.NOTEQUAL' | translate }}
</span>
</cnsl-form-field>
</div>
</form>
</div>
<p class="user-create-section cnsl-secondary-text">{{ 'USER.CREATE.GENDERLANGSECTION' | translate }}</p>
<p class="user-create-section">{{ 'USER.CREATE.GENDERLANGSECTION' | translate }}</p>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PROFILE.GENDER' | translate }}</cnsl-label>
<mat-select formControlName="gender">
<mat-option *ngFor="let gender of genders" [value]="gender">
{{ 'GENDERS.'+gender | translate }}
</mat-option>
</mat-select>
<span cnslError *ngIf="gender?.invalid && gender?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</span>
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PROFILE.PREFERRED_LANGUAGE' | translate }}</cnsl-label>
<mat-select formControlName="preferredLanguage">
<mat-option *ngFor="let language of languages" [value]="language">
{{ 'LANGUAGES.'+language | translate }}
</mat-option>
<span cnslError *ngIf="preferredLanguage?.invalid && preferredLanguage?.errors?.required">
<div class="user-create-grid">
<cnsl-form-field>
<cnsl-label>{{ 'USER.PROFILE.GENDER' | translate }}</cnsl-label>
<mat-select formControlName="gender">
<mat-option *ngFor="let gender of genders" [value]="gender">
{{ 'GENDERS.' + gender | translate }}
</mat-option>
</mat-select>
<span cnslError *ngIf="gender?.invalid && gender?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</span>
</mat-select>
</cnsl-form-field>
</cnsl-form-field>
<cnsl-form-field>
<cnsl-label>{{ 'USER.PROFILE.PREFERRED_LANGUAGE' | translate }}</cnsl-label>
<mat-select formControlName="preferredLanguage">
<mat-option *ngFor="let language of languages" [value]="language">
{{ 'LANGUAGES.' + language | translate }}
</mat-option>
<span cnslError *ngIf="preferredLanguage?.invalid && preferredLanguage?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</span>
</mat-select>
</cnsl-form-field>
</div>
<p class="user-create-section cnsl-secondary-text">{{ 'USER.CREATE.ADDRESSANDPHONESECTION' | translate }}</p>
<p class="user-create-section">{{ 'USER.CREATE.ADDRESSANDPHONESECTION' | translate }}</p>
<cnsl-form-field class="formfield">
<cnsl-form-field>
<cnsl-label>{{ 'USER.PROFILE.PHONE' | translate }}</cnsl-label>
<input cnslInput formControlName="phone" />
<span cnslError *ngIf="phone?.invalid && phone?.errors?.required">
@@ -138,12 +151,17 @@
</cnsl-form-field>
</div>
<div class="user-create-btn-container">
<button [attr.data-e2e]="'create-button'" color="primary"
[disabled]="userForm.invalid || (this.usePassword && this.pwdForm.invalid)" type="submit"
mat-raised-button>{{ 'ACTIONS.CREATE' |
translate }}</button>
<button
[attr.data-e2e]="'create-button'"
color="primary"
[disabled]="userForm.invalid || (this.usePassword && this.pwdForm.invalid)"
type="submit"
mat-raised-button
>
{{ 'ACTIONS.CREATE' | translate }}
</button>
</div>
</form>
</div>
</div>
</div>
</div>

View File

@@ -7,20 +7,47 @@
font-size: 1.2rem;
margin-left: 2rem;
}
.abort-2 {
font-size: 1.2rem;
margin-left: 2rem;
white-space: nowrap;
}
}
.user-create-main-content {
padding-left: 4.5rem;
max-width: 35rem;
@media only screen and (max-width: 500px) {
padding: 0 0.5rem;
}
.user-create-form {
padding-top: 1rem;
.user-create-content {
.user-create-section {
padding: 1rem 0 0 0;
flex-basis: 100%;
font-size: 14px;
letter-spacing: 0.05em;
text-transform: uppercase;
}
.user-create-grid {
display: grid;
grid-template-columns: 1fr 1fr;
column-gap: 1rem;
@media only screen and (max-width: 500px) {
grid-template-columns: 1fr;
}
}
.email-is-verified,
.use-password-block {
flex-basis: 100%;
margin-top: 1.5rem;
.block-checkbox {
display: block;
margin: 0.25rem 0;
}
}
}
.user-create-btn-container {
button {
@@ -32,49 +59,9 @@
}
}
.user-create-content {
display: flex;
flex-wrap: wrap;
flex-direction: row;
margin: 0 -0.5rem;
.user-create-section {
padding: 0.5rem;
flex-basis: 100%;
font-size: 0.9rem;
letter-spacing: 0.05em;
text-transform: uppercase;
}
.formfield {
flex: 1 0 33%;
margin: 0 0.5rem;
}
.email-is-verified,
.use-password-block {
margin: 0 0.5rem;
flex-basis: 100%;
margin-top: 1.5rem;
.block-checkbox {
display: block;
margin: 0.25rem 0;
}
}
}
.pwd-section {
margin: 0 0.5rem;
.section {
padding: 0.5rem 0;
}
.user-create-pwd-form {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 1rem;
}
}
}

View File

@@ -75,7 +75,7 @@ export class UserCreateComponent implements OnDestroy {
this.loading = true;
this.loadOrg();
this.mgmtService
.getOrgIAMPolicy()
.getDomainPolicy()
.then((resp) => {
if (resp.policy?.userLoginMustBeDomain) {
this.userLoginMustBeDomain = resp.policy.userLoginMustBeDomain;

View File

@@ -40,7 +40,9 @@
<mat-progress-spinner diameter="25" color="primary" mode="indeterminate"></mat-progress-spinner>
</div>
<p class="no-user-error" *ngIf="!loading && !user">{{ 'USER.PAGES.NOUSER' | translate }}</p>
<div *ngIf="!loading && !user" class="max-width-container">
<p class="no-user-error">{{ 'USER.PAGES.NOUSER' | translate }}</p>
</div>
<div class="max-width-container" *ngIf="user && (['user.write$', 'user.write:' + user.id] | hasRole) as canWrite$">
<cnsl-meta-layout>

View File

@@ -76,6 +76,7 @@ export class UserTableComponent implements OnInit {
public ActionKeysType: any = ActionKeysType;
public filterOpen: boolean = false;
private searchQueries: SearchQuery[] = [];
constructor(
private router: Router,
public translate: TranslateService,
@@ -109,6 +110,7 @@ export class UserTableComponent implements OnInit {
queryParams: {
type: type === Type.TYPE_HUMAN ? 'human' : type === Type.TYPE_MACHINE ? 'machine' : 'human',
},
replaceUrl: true,
queryParamsHandling: 'merge',
skipLocationChange: false,
});
@@ -225,14 +227,12 @@ export class UserTableComponent implements OnInit {
}
public refreshPage(): void {
this.getData(this.paginator.pageSize, this.paginator.pageIndex * this.paginator.pageSize, this.type);
this.getData(this.paginator.pageSize, this.paginator.pageIndex * this.paginator.pageSize, this.type, this.searchQueries);
}
public sortChange(sortState: Sort) {
console.log(sortState.active, sortState.direction);
if (sortState.direction && sortState.active) {
this._liveAnnouncer.announce(`Sorted ${sortState.direction}ending`);
this._liveAnnouncer.announce(`Sorted ${sortState.direction} ending`);
this.refreshPage();
} else {
this._liveAnnouncer.announce('Sorting cleared');
@@ -241,6 +241,7 @@ export class UserTableComponent implements OnInit {
public applySearchQuery(searchQueries: SearchQuery[]): void {
this.selection.clear();
this.searchQueries = searchQueries;
this.getData(
this.paginator ? this.paginator.pageSize : this.INITIAL_PAGE_SIZE,
this.paginator ? this.paginator.pageIndex * this.paginator.pageSize : 0,