mirror of
https://github.com/zitadel/zitadel.git
synced 2025-01-06 14:37:41 +00:00
fix(console): LDAP UI optimization for better required field recognition, improve onboarding all done visibility (#5659)
* fix: onboarding all done styles * ldap UI change * rm log * set required marker to formfield, max width of string-list comp * seperate formfields * formarray * clear action * validator * hide pwd field * rm dead code * lint
This commit is contained in:
parent
0ed2906b5d
commit
c420de1533
@ -1,5 +1,14 @@
|
||||
<ng-template #labelTemplate>
|
||||
<label class="cnsl-label-wrapper" [attr.for]="_control.id" [attr.aria-owns]="_control.id">
|
||||
<ng-content select="cnsl-label"></ng-content>
|
||||
<span *ngIf="_control.required && !hideRequiredMarker" aria-hidden="true" class="cnsl-form-field-required-marker"
|
||||
>*</span
|
||||
>
|
||||
</label>
|
||||
</ng-template>
|
||||
|
||||
<div class="cnsl-form-field-wrapper" (click)="_control.onContainerClick && _control.onContainerClick($event)">
|
||||
<ng-content select="cnsl-label"></ng-content>
|
||||
<ng-template [ngTemplateOutlet]="labelTemplate"></ng-template>
|
||||
<div class="cnsl-rel" #inputContainer>
|
||||
<ng-content></ng-content>
|
||||
<ng-content select="cnslSuffix"></ng-content>
|
||||
|
@ -51,6 +51,7 @@ interface ValidationError {
|
||||
'[class.ng-valid]': '_shouldForward("valid")',
|
||||
'[class.ng-invalid]': '_shouldForward("invalid")',
|
||||
'[class.ng-pending]': '_shouldForward("pending")',
|
||||
'[class.ng-required]': '_control.required',
|
||||
'[class.cnsl-form-field-disabled]': '_control.disabled',
|
||||
'[class.cnsl-form-field-autofilled]': '_control.autofilled',
|
||||
'[class.cnsl-focused]': '_control.focused',
|
||||
@ -69,6 +70,7 @@ export class CnslFormFieldComponent extends CnslFormFieldBase implements OnDestr
|
||||
@ContentChild(MatFormFieldControl) _controlNonStatic!: MatFormFieldControl<any>;
|
||||
@ContentChild(MatFormFieldControl, { static: true }) _controlStatic!: MatFormFieldControl<any>;
|
||||
@Input() public disableValidationErrors = false;
|
||||
@Input() public hideRequiredMarker = false;
|
||||
|
||||
get _control(): MatFormFieldControl<any> {
|
||||
return this._explicitFormFieldControl || this._controlNonStatic || this._controlStatic;
|
||||
|
@ -24,6 +24,12 @@ export function requiredValidator(c: AbstractControl): ValidationErrors | null {
|
||||
return i18nErr(Validators.required(c), 'ERRORS.REQUIRED');
|
||||
}
|
||||
|
||||
export function minArrayLengthValidator(minArrLength: number): ValidatorFn {
|
||||
return (c: AbstractControl): ValidationErrors | null => {
|
||||
return arrayLengthValidator(c, minArrLength, 'ERRORS.ATLEASTONE');
|
||||
};
|
||||
}
|
||||
|
||||
export function emailValidator(c: AbstractControl): ValidationErrors | null {
|
||||
return i18nErr(Validators.email(c), 'ERRORS.NOTANEMAIL');
|
||||
}
|
||||
@ -56,6 +62,12 @@ function regexpValidator(c: AbstractControl, regexp: RegExp, i18nKey: string): V
|
||||
return !c.value || regexp.test(c.value) ? null : i18nErr({ invalid: true }, i18nKey, { regexp: regexp });
|
||||
}
|
||||
|
||||
function arrayLengthValidator(c: AbstractControl, length: number, i18nKey: string): ValidationErrors | null {
|
||||
const arr: string[] = c.value;
|
||||
const invalidStrings: string[] = arr.filter((val: string) => val.trim() === '');
|
||||
return arr && invalidStrings.length === 0 && arr.length >= length ? null : i18nErr({ invalid: true }, i18nKey);
|
||||
}
|
||||
|
||||
function i18nErr(err: ValidationErrors | null | undefined, i18nKey: string, params?: any): ValidationErrors | null {
|
||||
if (err === null) {
|
||||
return null;
|
||||
|
@ -9,23 +9,31 @@
|
||||
$foreground: map-get($theme, foreground);
|
||||
$secondary-text: map-get($foreground, secondary-text);
|
||||
|
||||
.cnsl-label {
|
||||
display: block;
|
||||
.cnsl-label-wrapper {
|
||||
display: flex;
|
||||
font-size: 12px;
|
||||
color: $secondary-text;
|
||||
transition: color 0.2s ease;
|
||||
margin-bottom: 4px;
|
||||
font-weight: 400;
|
||||
|
||||
.cnsl-label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.cnsl-form-field-required-marker {
|
||||
margin-left: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.cnsl-form-field-disabled {
|
||||
.cnsl-label {
|
||||
.cnsl-label-wrapper {
|
||||
color: if($is-dark-theme, #ffffff80, #00000061);
|
||||
}
|
||||
}
|
||||
|
||||
.cnsl-form-field-invalid {
|
||||
.cnsl-label {
|
||||
.cnsl-label-wrapper {
|
||||
color: $warn-color;
|
||||
}
|
||||
}
|
||||
|
@ -198,6 +198,12 @@
|
||||
.state-circle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.action-card {
|
||||
.action-content {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,54 +1,65 @@
|
||||
<form [formGroup]="form" class="attribute-form">
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.LDAPIDATTRIBUTE' | translate }}*</cnsl-label>
|
||||
<input cnslInput formControlName="idAttribute" />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.AVATARURLATTRIBUTE' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="avatarUrlAttribute" />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.DISPLAYNAMEATTRIBUTE' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="displayNameAttribute" />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.EMAILATTRIBUTEATTRIBUTE' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="emailAttribute" />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.EMAILVERIFIEDATTRIBUTE' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="emailVerifiedAttribute" />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.FIRSTNAMEATTRIBUTE' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="firstNameAttribute" />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.LASTNAMEATTRIBUTE' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="lastNameAttribute" />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.NICKNAMEATTRIBUTE' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="nickNameAttribute" />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.PHONEATTRIBUTE' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="phoneAttribute" />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.PHONEVERIFIEDATTRIBUTE' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="phoneVerifiedAttribute" />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.PREFERREDLANGUAGEATTRIBUTE' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="preferredLanguageAttribute" />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.PREFERREDUSERNAMEATTRIBUTE' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="preferredUsernameAttribute" />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.PROFILEATTRIBUTE' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="profileAttribute" />
|
||||
<cnsl-label>{{ 'IDP.LDAPIDATTRIBUTE' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="idAttribute" required />
|
||||
</cnsl-form-field>
|
||||
|
||||
<div class="attribute-more-row">
|
||||
<span>{{ 'ACTIONS.MORE' | translate }}</span>
|
||||
<button (click)="showMore = !showMore" type="button" mat-icon-button>
|
||||
<mat-icon *ngIf="showMore">keyboard_arrow_up</mat-icon>
|
||||
<mat-icon *ngIf="!showMore">keyboard_arrow_down</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="showMore">
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.AVATARURLATTRIBUTE' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="avatarUrlAttribute" />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.DISPLAYNAMEATTRIBUTE' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="displayNameAttribute" />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.EMAILATTRIBUTEATTRIBUTE' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="emailAttribute" />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.EMAILVERIFIEDATTRIBUTE' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="emailVerifiedAttribute" />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.FIRSTNAMEATTRIBUTE' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="firstNameAttribute" />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.LASTNAMEATTRIBUTE' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="lastNameAttribute" />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.NICKNAMEATTRIBUTE' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="nickNameAttribute" />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.PHONEATTRIBUTE' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="phoneAttribute" />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.PHONEVERIFIEDATTRIBUTE' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="phoneVerifiedAttribute" />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.PREFERREDLANGUAGEATTRIBUTE' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="preferredLanguageAttribute" />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.PREFERREDUSERNAMEATTRIBUTE' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="preferredUsernameAttribute" />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.PROFILEATTRIBUTE' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="profileAttribute" />
|
||||
</cnsl-form-field>
|
||||
</ng-container>
|
||||
</form>
|
||||
|
@ -4,3 +4,8 @@
|
||||
max-width: 400px;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.attribute-more-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ export class LDAPAttributesComponent implements OnChanges, OnDestroy {
|
||||
profileAttribute: new FormControl('', []),
|
||||
});
|
||||
|
||||
public showMore: boolean = false;
|
||||
constructor() {
|
||||
this.form.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((value) => {
|
||||
if (value) {
|
||||
|
@ -17,12 +17,13 @@
|
||||
<div class="identity-provider-content">
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.NAME' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="name" />
|
||||
<input cnslInput formControlName="name" required />
|
||||
</cnsl-form-field>
|
||||
|
||||
<h2 class="subheader">{{ 'IDP.LDAPCONNECTION' | translate }}</h2>
|
||||
|
||||
<cnsl-string-list
|
||||
class="string-list-component-wrapper"
|
||||
title="{{ 'IDP.SERVERS' | translate }}"
|
||||
formControlName="serversList"
|
||||
[required]="true"
|
||||
@ -30,13 +31,13 @@
|
||||
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.BASEDN' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="baseDn" />
|
||||
<input cnslInput formControlName="baseDn" required />
|
||||
</cnsl-form-field>
|
||||
|
||||
<div [ngClass]="{ 'identity-provider-2-col': !provider }">
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.BINDDN' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="bindDn" />
|
||||
<input cnslInput formControlName="bindDn" required />
|
||||
</cnsl-form-field>
|
||||
|
||||
<mat-checkbox
|
||||
@ -46,14 +47,14 @@
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
>{{ 'IDP.UPDATEBINDPASSWORD' | translate }}</mat-checkbox
|
||||
>
|
||||
<cnsl-form-field *ngIf="!provider || (provider && updateBindPassword)" class="formfield">
|
||||
<cnsl-form-field class="formfield pwd" [ngClass]="{ show: !provider || (provider && updateBindPassword) }">
|
||||
<cnsl-label>{{ 'IDP.BINDPASSWORD' | translate }}</cnsl-label>
|
||||
<input
|
||||
cnslInput
|
||||
name="bindpassword"
|
||||
formControlName="bindPassword"
|
||||
type="password"
|
||||
autocomplete="new-password"
|
||||
[required]="!provider"
|
||||
/>
|
||||
</cnsl-form-field>
|
||||
</div>
|
||||
@ -62,16 +63,18 @@
|
||||
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.USERBASE' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="userBase" />
|
||||
<input cnslInput formControlName="userBase" required />
|
||||
</cnsl-form-field>
|
||||
|
||||
<cnsl-string-list
|
||||
class="string-list-component-wrapper"
|
||||
title="{{ 'IDP.USERFILTERS' | translate }}"
|
||||
formControlName="userFiltersList"
|
||||
[required]="true"
|
||||
></cnsl-string-list>
|
||||
|
||||
<cnsl-string-list
|
||||
class="string-list-component-wrapper"
|
||||
title="{{ 'IDP.USEROBJECTCLASSES' | translate }}"
|
||||
formControlName="userObjectClassesList"
|
||||
[required]="true"
|
||||
@ -79,22 +82,11 @@
|
||||
|
||||
<div class="identity-provider-optional-h-wrapper">
|
||||
<h2>{{ 'IDP.LDAPATTRIBUTES' | translate }}</h2>
|
||||
|
||||
<button (click)="showAttributes = !showAttributes" type="button" mat-icon-button>
|
||||
<mat-icon *ngIf="showAttributes">keyboard_arrow_up</mat-icon>
|
||||
<mat-icon *ngIf="!showAttributes">keyboard_arrow_down</mat-icon>
|
||||
</button>
|
||||
|
||||
<span *ngIf="!provider?.config?.ldap?.attributes?.idAttribute" class="state error">{{
|
||||
'IDP.REQUIRED' | translate
|
||||
}}</span>
|
||||
</div>
|
||||
<div *ngIf="showAttributes">
|
||||
<cnsl-ldap-attributes
|
||||
[initialAttributes]="provider?.config?.ldap?.attributes"
|
||||
(attributesChanged)="attributes = $event"
|
||||
></cnsl-ldap-attributes>
|
||||
</div>
|
||||
<cnsl-ldap-attributes
|
||||
[initialAttributes]="provider?.config?.ldap?.attributes"
|
||||
(attributesChanged)="attributes = $event"
|
||||
></cnsl-ldap-attributes>
|
||||
|
||||
<div class="identity-provider-optional-h-wrapper">
|
||||
<h2>{{ 'IDP.OPTIONAL' | translate }}</h2>
|
||||
@ -123,7 +115,7 @@
|
||||
color="primary"
|
||||
mat-raised-button
|
||||
class="continue-button"
|
||||
[disabled]="form.invalid || attributes.toObject().idAttribute === '' || form.disabled"
|
||||
[disabled]="!form.valid || !attributes.toObject().idAttribute || form.disabled"
|
||||
type="submit"
|
||||
>
|
||||
<span *ngIf="id">{{ 'ACTIONS.SAVE' | translate }}</span>
|
||||
|
@ -20,7 +20,7 @@ import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/
|
||||
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
import { requiredValidator } from '../../form-field/validators/validators';
|
||||
import { minArrayLengthValidator, requiredValidator } from '../../form-field/validators/validators';
|
||||
|
||||
import { PolicyComponentServiceType } from '../../policies/policy-component-types.enum';
|
||||
|
||||
@ -30,7 +30,6 @@ import { PolicyComponentServiceType } from '../../policies/policy-component-type
|
||||
})
|
||||
export class ProviderLDAPComponent {
|
||||
public updateBindPassword: boolean = false;
|
||||
public showAttributes: boolean = false;
|
||||
public showOptional: boolean = false;
|
||||
public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true);
|
||||
public attributes: LDAPAttributes = new LDAPAttributes();
|
||||
@ -54,15 +53,15 @@ export class ProviderLDAPComponent {
|
||||
) {
|
||||
this.form = new FormGroup({
|
||||
name: new FormControl('', [requiredValidator]),
|
||||
serversList: new FormControl('', [requiredValidator]),
|
||||
serversList: new FormControl<string[]>([''], [minArrayLengthValidator(1)]),
|
||||
baseDn: new FormControl('', [requiredValidator]),
|
||||
bindDn: new FormControl('', [requiredValidator]),
|
||||
bindPassword: new FormControl('', [requiredValidator]),
|
||||
userBase: new FormControl('', [requiredValidator]),
|
||||
userFiltersList: new FormControl('', [requiredValidator]),
|
||||
userObjectClassesList: new FormControl('', [requiredValidator]),
|
||||
userFiltersList: new FormControl<string[]>([''], [minArrayLengthValidator(1)]),
|
||||
userObjectClassesList: new FormControl<string[]>([''], [minArrayLengthValidator(1)]),
|
||||
timeout: new FormControl<number>(0),
|
||||
startTls: new FormControl(false),
|
||||
startTls: new FormControl<boolean>(false),
|
||||
});
|
||||
|
||||
this.authService
|
||||
@ -112,6 +111,7 @@ export class ProviderLDAPComponent {
|
||||
if (this.id) {
|
||||
this.getData(this.id);
|
||||
this.bindPassword?.setValidators([]);
|
||||
this.bindPassword?.updateValueAndValidity();
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -125,12 +125,25 @@ export class ProviderLDAPComponent {
|
||||
this.service
|
||||
.getProviderByID(req)
|
||||
.then((resp) => {
|
||||
this.provider = resp.idp;
|
||||
this.loading = false;
|
||||
if (this.provider?.config?.ldap) {
|
||||
this.form.patchValue(this.provider.config.ldap);
|
||||
if (resp.idp) {
|
||||
this.provider = resp.idp;
|
||||
this.loading = false;
|
||||
|
||||
this.name?.setValue(this.provider.name);
|
||||
this.timeout?.setValue(this.provider.config.ldap.timeout?.seconds);
|
||||
|
||||
const config = this.provider?.config?.ldap;
|
||||
if (config) {
|
||||
this.serversList?.setValue(config.serversList);
|
||||
this.startTls?.setValue(config.startTls);
|
||||
this.baseDn?.setValue(config.baseDn);
|
||||
this.bindDn?.setValue(config.bindDn);
|
||||
this.userBase?.setValue(config.userBase);
|
||||
this.userObjectClassesList?.setValue(config.userObjectClassesList);
|
||||
this.userFiltersList?.setValue(config.userFiltersList);
|
||||
if (this.provider?.config?.ldap?.timeout?.seconds) {
|
||||
this.timeout?.setValue(this.provider?.config?.ldap?.timeout?.seconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
|
@ -1,5 +1,8 @@
|
||||
@use '@angular/material' as mat;
|
||||
|
||||
@mixin identity-provider-theme($theme) {
|
||||
$is-dark-theme: map-get($theme, is-dark);
|
||||
$background: map-get($theme, background);
|
||||
|
||||
.identity-provider-desc {
|
||||
font-size: 14px;
|
||||
@ -44,6 +47,14 @@
|
||||
display: block;
|
||||
max-width: 400px;
|
||||
|
||||
&.pwd {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.pwd.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.name-hint {
|
||||
font-size: 12px;
|
||||
}
|
||||
@ -63,6 +74,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.string-list-component-wrapper {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.identity-provider-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -1,30 +1,43 @@
|
||||
<form class="string-list-form" (ngSubmit)="add(redInput)">
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ title }}</cnsl-label>
|
||||
<input #redInput cnslInput [formControl]="control" />
|
||||
</cnsl-form-field>
|
||||
<button
|
||||
matTooltip="{{ 'ACTIONS.ADD' | translate }}"
|
||||
type="submit"
|
||||
mat-icon-button
|
||||
[disabled]="control.invalid || control.disabled"
|
||||
>
|
||||
<mat-icon>add</mat-icon>
|
||||
</button>
|
||||
</form>
|
||||
<div class="form-array-list">
|
||||
<div class="form-field-list">
|
||||
<div class="list-header-wrapper">
|
||||
<p class="list-header cnsl-secondary-text">{{ title }}*</p>
|
||||
<button
|
||||
class="add-element-btn"
|
||||
matTooltip="{{ 'ACTIONS.ADD' | translate }}"
|
||||
type="button"
|
||||
mat-icon-button
|
||||
(click)="addArrayEntry()"
|
||||
>
|
||||
<mat-icon>add</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<ng-container *ngFor="let formControl of formArray.controls; index as i">
|
||||
<div class="element-row">
|
||||
<cnsl-form-field class="formfield" [hideRequiredMarker]="true">
|
||||
<input cnslInput title="{{ 'IDP.SERVERS' | translate }}" [formControl]="$any(formControl)" required />
|
||||
</cnsl-form-field>
|
||||
|
||||
<div class="string-list">
|
||||
<div *ngFor="let str of value" class="value-line">
|
||||
<span>{{ str }}</span>
|
||||
<span class="fill-space"></span>
|
||||
<button
|
||||
type="button"
|
||||
matTooltip="{{ 'ACTIONS.DELETE' | translate }}"
|
||||
mat-icon-button
|
||||
(click)="remove(str)"
|
||||
class="icon-button"
|
||||
>
|
||||
<mat-icon class="icon">cancel</mat-icon>
|
||||
</button>
|
||||
<button
|
||||
class="add-element-btn"
|
||||
[disabled]="i === 0 && formArray.controls.length === 1 && formControl.value === ''"
|
||||
[matTooltip]="
|
||||
i === 0 && formArray.controls.length === 1 ? ('ACTIONS.CLEAR' | translate) : ('ACTIONS.REMOVE' | translate)
|
||||
"
|
||||
type="button"
|
||||
mat-icon-button
|
||||
color="warn"
|
||||
(click)="i === 0 && formArray.controls.length === 1 ? clearEntryAtIndex(i) : removeEntryAtIndex(i)"
|
||||
>
|
||||
<i *ngIf="i === 0 && formArray.controls.length === 1; else removeIcon" class="las la-times-circle"></i>
|
||||
<ng-template #removeIcon>
|
||||
<i class="las la-minus-circle"></i>
|
||||
</ng-template>
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
<span class="control-error" *ngIf="control.touched && control.errors && control.errors['errorsatleastone']">{{
|
||||
control.errors['errorsatleastone'].i18nKey | translate
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -5,56 +5,50 @@
|
||||
$background: map-get($theme, background);
|
||||
$is-dark-theme: map-get($theme, is-dark);
|
||||
$warn: map-get($theme, warn);
|
||||
$warn-color: map-get($warn, 500);
|
||||
$button-text-color: map-get($foreground, text);
|
||||
$button-disabled-text-color: map-get($foreground, disabled-button);
|
||||
$divider-color: map-get($foreground, dividers);
|
||||
$secondary-text: map-get($foreground, secondary-text);
|
||||
$warncolor: map-get($warn, 500);
|
||||
|
||||
.string-list {
|
||||
width: 100%;
|
||||
.form-array-list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
max-width: 400px;
|
||||
background: if($is-dark-theme, #00000020, mat.get-color-from-palette($background, cards));
|
||||
margin-left: -1rem;
|
||||
margin-right: -1rem;
|
||||
padding: 0 1rem 0.5rem 1rem;
|
||||
margin-top: 0.5rem;
|
||||
|
||||
.value-line {
|
||||
.list-header-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0.5rem 0;
|
||||
padding: 0 0 0 0.75rem;
|
||||
border-radius: 4px;
|
||||
background: map-get($background, infosection);
|
||||
margin: 0.5rem -0.5rem 0 0;
|
||||
|
||||
.fill-space {
|
||||
flex: 1;
|
||||
.list-header {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
.form-field-list {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.icon {
|
||||
font-size: 1rem;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
.element-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:not(:hover) {
|
||||
color: $secondary-text;
|
||||
.formfield {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.control-error {
|
||||
font-size: 12px;
|
||||
color: $warncolor;
|
||||
}
|
||||
}
|
||||
|
||||
.add-element-btn {
|
||||
margin-bottom: 0rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.string-list-form {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
min-width: 320px;
|
||||
|
||||
.formfield {
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-bottom: 0.9rem;
|
||||
margin-right: -0.5rem;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Component, forwardRef, Input, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||
import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { Observable, Subject, takeUntil } from 'rxjs';
|
||||
import { requiredValidator } from '../form-field/validators/validators';
|
||||
import { Component, forwardRef, Input, OnDestroy, ViewChildren, ViewEncapsulation } from '@angular/core';
|
||||
import { ControlValueAccessor, FormArray, FormControl, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { distinctUntilChanged, Subject, takeUntil } from 'rxjs';
|
||||
import { minArrayLengthValidator, requiredValidator } from '../form-field/validators/validators';
|
||||
|
||||
@Component({
|
||||
selector: 'cnsl-string-list',
|
||||
@ -16,22 +16,23 @@ import { requiredValidator } from '../form-field/validators/validators';
|
||||
},
|
||||
],
|
||||
})
|
||||
export class StringListComponent implements ControlValueAccessor, OnInit, OnDestroy {
|
||||
export class StringListComponent implements ControlValueAccessor, OnDestroy {
|
||||
@Input() title: string = '';
|
||||
@Input() required: boolean = false;
|
||||
@Input() public getValues: Observable<void> = new Observable(); // adds formfieldinput to array on emission
|
||||
|
||||
@Input() public control: FormControl = new FormControl<string>({ value: '', disabled: true });
|
||||
@Input() public control: FormControl = new FormControl<string[]>({ value: [], disabled: true });
|
||||
|
||||
private destroy$: Subject<void> = new Subject();
|
||||
@ViewChild('redInput') input!: any;
|
||||
private val: string[] = [];
|
||||
@ViewChildren('stringInput') input!: any[];
|
||||
public val: string[] = [];
|
||||
|
||||
ngOnInit(): void {
|
||||
this.getValues.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
||||
this.add(this.input.nativeElement);
|
||||
public formArray: FormArray = new FormArray([new FormControl('', [requiredValidator])]);
|
||||
|
||||
constructor() {
|
||||
this.control.setValidators([minArrayLengthValidator(1)]);
|
||||
this.formArray.valueChanges.pipe(takeUntil(this.destroy$), distinctUntilChanged()).subscribe((value) => {
|
||||
this.value = value;
|
||||
});
|
||||
|
||||
this.required ? this.control.setValidators([requiredValidator]) : this.control.setValidators([]);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
@ -50,12 +51,24 @@ export class StringListComponent implements ControlValueAccessor, OnInit, OnDest
|
||||
}
|
||||
}
|
||||
|
||||
addArrayEntry() {
|
||||
this.formArray.push(new FormControl('', [requiredValidator]));
|
||||
}
|
||||
|
||||
removeEntryAtIndex(index: number) {
|
||||
this.formArray.removeAt(index);
|
||||
}
|
||||
|
||||
clearEntryAtIndex(index: number) {
|
||||
this.formArray.controls[index].setValue('');
|
||||
}
|
||||
get value() {
|
||||
return this.val;
|
||||
}
|
||||
|
||||
writeValue(value: string[]) {
|
||||
this.value = value;
|
||||
value.map((v, i) => this.formArray.setControl(i, new FormControl(v, [requiredValidator])));
|
||||
}
|
||||
|
||||
registerOnChange(fn: any) {
|
||||
@ -73,28 +86,4 @@ export class StringListComponent implements ControlValueAccessor, OnInit, OnDest
|
||||
this.control.enable();
|
||||
}
|
||||
}
|
||||
|
||||
public add(input: any): void {
|
||||
if (this.control.valid) {
|
||||
const trimmed = input.value.trim();
|
||||
if (trimmed) {
|
||||
this.val ? this.val.push(input.value) : (this.val = [input.value]);
|
||||
this.onChange(this.val);
|
||||
this.onTouch(this.val);
|
||||
}
|
||||
if (input) {
|
||||
input.value = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public remove(str: string): void {
|
||||
const index = this.value.indexOf(str);
|
||||
|
||||
if (index >= 0) {
|
||||
this.value.splice(index, 1);
|
||||
this.onChange(this.value);
|
||||
this.onTouch(this.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatLegacyButtonModule } from '@angular/material/legacy-button';
|
||||
import { MatLegacyChipsModule } from '@angular/material/legacy-chips';
|
||||
import { MatLegacyTooltipModule } from '@angular/material/legacy-tooltip';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { InputModule } from '../input/input.module';
|
||||
@ -15,6 +16,7 @@ import { StringListComponent } from './string-list.component';
|
||||
InputModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
MatLegacyChipsModule,
|
||||
TranslateModule,
|
||||
MatIconModule,
|
||||
MatLegacyTooltipModule,
|
||||
|
@ -10,11 +10,11 @@
|
||||
<form *ngIf="userForm" [formGroup]="userForm" (ngSubmit)="createUser()" class="machine-create-form">
|
||||
<div class="machine-create-content">
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'USER.MACHINE.USERNAME' | translate }}*</cnsl-label>
|
||||
<cnsl-label>{{ 'USER.MACHINE.USERNAME' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="userName" required />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'USER.MACHINE.NAME' | translate }}*</cnsl-label>
|
||||
<cnsl-label>{{ 'USER.MACHINE.NAME' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="name" required />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
|
@ -13,11 +13,11 @@
|
||||
|
||||
<div class="user-create-grid">
|
||||
<cnsl-form-field>
|
||||
<cnsl-label>{{ 'USER.PROFILE.EMAIL' | translate }}*</cnsl-label>
|
||||
<cnsl-label>{{ 'USER.PROFILE.EMAIL' | translate }}</cnsl-label>
|
||||
<input cnslInput matRipple formControlName="email" required />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field>
|
||||
<cnsl-label>{{ 'USER.PROFILE.USERNAME' | translate }}*</cnsl-label>
|
||||
<cnsl-label>{{ 'USER.PROFILE.USERNAME' | translate }}</cnsl-label>
|
||||
<input
|
||||
cnslInput
|
||||
formControlName="userName"
|
||||
@ -28,11 +28,11 @@
|
||||
</cnsl-form-field>
|
||||
|
||||
<cnsl-form-field>
|
||||
<cnsl-label>{{ 'USER.PROFILE.FIRSTNAME' | translate }}*</cnsl-label>
|
||||
<cnsl-label>{{ 'USER.PROFILE.FIRSTNAME' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="firstName" required />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field>
|
||||
<cnsl-label>{{ 'USER.PROFILE.LASTNAME' | translate }}*</cnsl-label>
|
||||
<cnsl-label>{{ 'USER.PROFILE.LASTNAME' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="lastName" required />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field>
|
||||
|
@ -247,6 +247,7 @@
|
||||
},
|
||||
"ERRORS": {
|
||||
"REQUIRED": "Bitte fülle dieses Feld aus.",
|
||||
"ATLEASTONE": "Geben Sie mindestens einen Wert an.",
|
||||
"TOKENINVALID": {
|
||||
"TITLE": "Du bist abgemeldet",
|
||||
"DESCRIPTION": "Klicke auf \"Einloggen\", um Dich erneut anzumelden."
|
||||
|
@ -248,6 +248,7 @@
|
||||
},
|
||||
"ERRORS": {
|
||||
"REQUIRED": "Please fill in this field.",
|
||||
"ATLEASTONE": "Provide at least one value.",
|
||||
"TOKENINVALID": {
|
||||
"TITLE": "Your authorization token has expired.",
|
||||
"DESCRIPTION": "Click the button below to log in again."
|
||||
|
@ -248,6 +248,7 @@
|
||||
},
|
||||
"ERRORS": {
|
||||
"REQUIRED": "Por favor rellena este campo.",
|
||||
"ATLEASTONE": "Proporcione al menos un valor.",
|
||||
"TOKENINVALID": {
|
||||
"TITLE": "Tu token de autorización token ha caducado.",
|
||||
"DESCRIPTION": "Haz clic en el botón más abajo para iniciar sesión otra vez."
|
||||
|
@ -247,6 +247,7 @@
|
||||
},
|
||||
"ERRORS": {
|
||||
"REQUIRED": "Remplis ce champ s'il te plaît.",
|
||||
"ATLEASTONE": "Indiquez au moins une valeur.",
|
||||
"TOKENINVALID": {
|
||||
"TITLE": "Votre jeton d'autorisation a expiré.",
|
||||
"DESCRIPTION": "Cliquez sur le bouton ci-dessous pour vous reconnecter."
|
||||
|
@ -247,6 +247,7 @@
|
||||
},
|
||||
"ERRORS": {
|
||||
"REQUIRED": "Compilare questo campo.",
|
||||
"ATLEASTONE": "Inserisci almeno un valore.",
|
||||
"TOKENINVALID": {
|
||||
"TITLE": "Il tuo Access Token \u00e8 scaduto.",
|
||||
"DESCRIPTION": "Clicca il pulsante per richiedere una nuova sessione."
|
||||
|
@ -248,6 +248,7 @@
|
||||
},
|
||||
"ERRORS": {
|
||||
"REQUIRED": "一部の必須項目が不足しています。",
|
||||
"ATLEASTONE": "少なくとも 1 つの値を指定してください。",
|
||||
"TOKENINVALID": {
|
||||
"TITLE": "トークンが期限切れになりました。",
|
||||
"DESCRIPTION": "下のボタンをクリックして、もう一度ログインする。"
|
||||
|
@ -247,6 +247,7 @@
|
||||
},
|
||||
"ERRORS": {
|
||||
"REQUIRED": "Proszę wypełnić to pole.",
|
||||
"ATLEASTONE": "Podaj co najmniej jedną wartość.",
|
||||
"TOKENINVALID": {
|
||||
"TITLE": "Twój token autoryzacji wygasł.",
|
||||
"DESCRIPTION": "Kliknij przycisk poniżej, aby ponownie się zalogować."
|
||||
|
@ -247,6 +247,7 @@
|
||||
},
|
||||
"ERRORS": {
|
||||
"REQUIRED": "请填写此栏",
|
||||
"ATLEASTONE": "P至少提供一个值。",
|
||||
"TOKENINVALID": {
|
||||
"TITLE": "您的授权令牌已过期。",
|
||||
"DESCRIPTION": "点击下方按钮再次登录。"
|
||||
|
Loading…
x
Reference in New Issue
Block a user