mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-14 18:05:12 +00:00
fix(console): migrate from tslint to eslint, project delete from table (#2490)
* es lint * modify tsconfig, auto lint some stuff * lint * lint * lint * lint * html ts lint * lint * lint, tsconfig * fix project delete, state table * eslint config, remove cnslHint directive * mfa selector, info row fixes * lint * fix login policy, granted orgs table state, lint Co-authored-by: Florian Forster <[email protected]>
This commit is contained in:
co-authored by
Florian Forster
parent
bdf63800f7
commit
b1caef81da
@@ -1,4 +1,4 @@
|
||||
<app-detail-layout [backRouterLink]="[ '/users/list/machines']" title="{{ 'USER.CREATE.TITLE' | translate }}"
|
||||
<cnsl-detail-layout [backRouterLink]="[ '/users/list/machines']" title="{{ 'USER.CREATE.TITLE' | translate }}"
|
||||
description="{{ 'USER.CREATE.DESCRIPTION' | translate }}">
|
||||
<mat-progress-bar *ngIf="loading" color="primary" mode="indeterminate"></mat-progress-bar>
|
||||
|
||||
@@ -31,4 +31,4 @@
|
||||
translate }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</app-detail-layout>
|
||||
</cnsl-detail-layout>
|
||||
@@ -7,76 +7,76 @@ import { ManagementService } from 'src/app/services/mgmt.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-create-machine',
|
||||
templateUrl: './user-create-machine.component.html',
|
||||
styleUrls: ['./user-create-machine.component.scss'],
|
||||
selector: 'cnsl-user-create-machine',
|
||||
templateUrl: './user-create-machine.component.html',
|
||||
styleUrls: ['./user-create-machine.component.scss'],
|
||||
})
|
||||
export class UserCreateMachineComponent implements OnDestroy {
|
||||
public user: AddMachineUserRequest.AsObject = new AddMachineUserRequest().toObject();
|
||||
public userForm!: FormGroup;
|
||||
public user: AddMachineUserRequest.AsObject = new AddMachineUserRequest().toObject();
|
||||
public userForm!: FormGroup;
|
||||
|
||||
private sub: Subscription = new Subscription();
|
||||
public loading: boolean = false;
|
||||
private sub: Subscription = new Subscription();
|
||||
public loading: boolean = false;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private toast: ToastService,
|
||||
public userService: ManagementService,
|
||||
private fb: FormBuilder,
|
||||
) {
|
||||
this.initForm();
|
||||
}
|
||||
constructor(
|
||||
private router: Router,
|
||||
private toast: ToastService,
|
||||
public userService: ManagementService,
|
||||
private fb: FormBuilder,
|
||||
) {
|
||||
this.initForm();
|
||||
}
|
||||
|
||||
private initForm(): void {
|
||||
this.userForm = this.fb.group({
|
||||
userName: ['',
|
||||
[
|
||||
Validators.required,
|
||||
Validators.minLength(2),
|
||||
],
|
||||
],
|
||||
name: ['', [Validators.required]],
|
||||
description: ['', []],
|
||||
});
|
||||
}
|
||||
private initForm(): void {
|
||||
this.userForm = this.fb.group({
|
||||
userName: ['',
|
||||
[
|
||||
Validators.required,
|
||||
Validators.minLength(2),
|
||||
],
|
||||
],
|
||||
name: ['', [Validators.required]],
|
||||
description: ['', []],
|
||||
});
|
||||
}
|
||||
|
||||
public createUser(): void {
|
||||
this.user = this.userForm.value;
|
||||
public createUser(): void {
|
||||
this.user = this.userForm.value;
|
||||
|
||||
this.loading = true;
|
||||
this.loading = true;
|
||||
|
||||
const machineReq = new AddMachineUserRequest();
|
||||
machineReq.setDescription(this.description?.value);
|
||||
machineReq.setName(this.name?.value);
|
||||
machineReq.setUserName(this.userName?.value);
|
||||
const machineReq = new AddMachineUserRequest();
|
||||
machineReq.setDescription(this.description?.value);
|
||||
machineReq.setName(this.name?.value);
|
||||
machineReq.setUserName(this.userName?.value);
|
||||
|
||||
this.userService
|
||||
.addMachineUser(machineReq)
|
||||
.then((data) => {
|
||||
this.loading = false;
|
||||
this.toast.showInfo('USER.TOAST.CREATED', true);
|
||||
const id = data.userId;
|
||||
if (id) {
|
||||
this.router.navigate(['users', id]);
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
this.loading = false;
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
this.userService
|
||||
.addMachineUser(machineReq)
|
||||
.then((data) => {
|
||||
this.loading = false;
|
||||
this.toast.showInfo('USER.TOAST.CREATED', true);
|
||||
const id = data.userId;
|
||||
if (id) {
|
||||
this.router.navigate(['users', id]);
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
this.loading = false;
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.sub.unsubscribe();
|
||||
}
|
||||
ngOnDestroy(): void {
|
||||
this.sub.unsubscribe();
|
||||
}
|
||||
|
||||
public get name(): AbstractControl | null {
|
||||
return this.userForm.get('name');
|
||||
}
|
||||
public get description(): AbstractControl | null {
|
||||
return this.userForm.get('description');
|
||||
}
|
||||
public get userName(): AbstractControl | null {
|
||||
return this.userForm.get('userName');
|
||||
}
|
||||
public get name(): AbstractControl | null {
|
||||
return this.userForm.get('name');
|
||||
}
|
||||
public get description(): AbstractControl | null {
|
||||
return this.userForm.get('description');
|
||||
}
|
||||
public get userName(): AbstractControl | null {
|
||||
return this.userForm.get('userName');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<app-detail-layout [backRouterLink]="[ '/users/list/humans']" title="{{ 'USER.CREATE.TITLE' | translate }}"
|
||||
<cnsl-detail-layout [backRouterLink]="[ '/users/list/humans']" title="{{ 'USER.CREATE.TITLE' | translate }}"
|
||||
description="{{ 'USER.CREATE.DESCRIPTION' | translate }}">
|
||||
<mat-progress-bar *ngIf="loading" color="primary" mode="indeterminate"></mat-progress-bar>
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'USER.PROFILE.EMAIL' | translate }}*</cnsl-label>
|
||||
<input cnslInput matRipple formControlName="email" required />
|
||||
<span cnsl-error *ngIf="email?.invalid && !email?.errors?.required">
|
||||
<span cnslError *ngIf="email?.invalid && !email?.errors?.required">
|
||||
{{ 'USER.VALIDATION.NOTANEMAIL' | translate }}
|
||||
</span>
|
||||
<span cnsl-error *ngIf="email?.invalid && email?.errors?.required">
|
||||
<span cnslError *ngIf="email?.invalid && email?.errors?.required">
|
||||
{{ 'USER.VALIDATION.REQUIRED' | translate }}
|
||||
</span>
|
||||
</cnsl-form-field>
|
||||
@@ -21,10 +21,10 @@
|
||||
[ngStyle]="{'padding-right': suffixPadding ? suffixPadding : '10px'}" />
|
||||
<span #suffix *ngIf="envSuffixLabel" cnslSuffix>{{envSuffixLabel}}</span>
|
||||
|
||||
<span cnsl-error *ngIf="userName?.invalid && userName?.errors?.required">
|
||||
<span cnslError *ngIf="userName?.invalid && userName?.errors?.required">
|
||||
{{ 'USER.VALIDATION.REQUIRED' | translate }}
|
||||
</span>
|
||||
<span cnsl-error *ngIf="userName?.invalid && userName?.errors?.noEmailValidator">
|
||||
<span cnslError *ngIf="userName?.invalid && userName?.errors?.noEmailValidator">
|
||||
{{ 'USER.VALIDATION.NOEMAIL' | translate }}
|
||||
</span>
|
||||
</cnsl-form-field>
|
||||
@@ -33,21 +33,21 @@
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'USER.PROFILE.FIRSTNAME' | translate }}*</cnsl-label>
|
||||
<input cnslInput formControlName="firstName" required />
|
||||
<span cnsl-error *ngIf="firstName?.invalid && firstName?.errors?.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 cnsl-error *ngIf="lastName?.invalid && lastName?.errors?.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 cnsl-error *ngIf="nickName?.invalid && nickName?.errors?.required">
|
||||
<span cnslError *ngIf="nickName?.invalid && nickName?.errors?.required">
|
||||
{{ 'USER.VALIDATION.REQUIRED' | translate }}
|
||||
</span>
|
||||
</cnsl-form-field>
|
||||
@@ -61,7 +61,7 @@
|
||||
{{ 'GENDERS.'+gender | translate }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<span cnsl-error *ngIf="gender?.invalid && gender?.errors?.required">
|
||||
<span cnslError *ngIf="gender?.invalid && gender?.errors?.required">
|
||||
{{ 'USER.VALIDATION.REQUIRED' | translate }}
|
||||
</span>
|
||||
</cnsl-form-field>
|
||||
@@ -71,7 +71,7 @@
|
||||
<mat-option *ngFor="let language of languages" [value]="language">
|
||||
{{ 'LANGUAGES.'+language | translate }}
|
||||
</mat-option>
|
||||
<span cnsl-error *ngIf="preferredLanguage?.invalid && preferredLanguage?.errors?.required">
|
||||
<span cnslError *ngIf="preferredLanguage?.invalid && preferredLanguage?.errors?.required">
|
||||
{{ 'USER.VALIDATION.REQUIRED' | translate }}
|
||||
</span>
|
||||
</mat-select>
|
||||
@@ -82,7 +82,7 @@
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'USER.PROFILE.PHONE' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="phone" />
|
||||
<span cnsl-error *ngIf="phone?.invalid && phone?.errors?.required">
|
||||
<span cnslError *ngIf="phone?.invalid && phone?.errors?.required">
|
||||
{{ 'USER.VALIDATION.REQUIRED' | translate }}
|
||||
</span>
|
||||
</cnsl-form-field>
|
||||
@@ -92,4 +92,4 @@
|
||||
translate }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</app-detail-layout>
|
||||
</cnsl-detail-layout>
|
||||
@@ -11,165 +11,167 @@ import { ManagementService } from 'src/app/services/mgmt.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-create',
|
||||
templateUrl: './user-create.component.html',
|
||||
styleUrls: ['./user-create.component.scss'],
|
||||
selector: 'cnsl-user-create',
|
||||
templateUrl: './user-create.component.html',
|
||||
styleUrls: ['./user-create.component.scss'],
|
||||
})
|
||||
export class UserCreateComponent implements OnDestroy {
|
||||
public user: AddHumanUserRequest.AsObject = new AddHumanUserRequest().toObject();
|
||||
public genders: Gender[] = [Gender.GENDER_FEMALE, Gender.GENDER_MALE, Gender.GENDER_UNSPECIFIED];
|
||||
public languages: string[] = ['de', 'en'];
|
||||
public userForm!: FormGroup;
|
||||
public envSuffixLabel: string = '';
|
||||
private destroyed$: Subject<void> = new Subject();
|
||||
public user: AddHumanUserRequest.AsObject = new AddHumanUserRequest().toObject();
|
||||
public genders: Gender[] = [Gender.GENDER_FEMALE, Gender.GENDER_MALE, Gender.GENDER_UNSPECIFIED];
|
||||
public languages: string[] = ['de', 'en'];
|
||||
public userForm!: FormGroup;
|
||||
public envSuffixLabel: string = '';
|
||||
private destroyed$: Subject<void> = new Subject();
|
||||
|
||||
public userLoginMustBeDomain: boolean = false;
|
||||
public loading: boolean = false;
|
||||
public userLoginMustBeDomain: boolean = false;
|
||||
public loading: boolean = false;
|
||||
|
||||
@ViewChild('suffix') public suffix!: any;
|
||||
private primaryDomain!: Domain.AsObject;
|
||||
@ViewChild('suffix') public suffix!: any;
|
||||
private primaryDomain!: Domain.AsObject;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private toast: ToastService,
|
||||
private fb: FormBuilder,
|
||||
private mgmtService: ManagementService,
|
||||
private changeDetRef: ChangeDetectorRef,
|
||||
) {
|
||||
this.loading = true;
|
||||
this.loadOrg();
|
||||
this.mgmtService.getOrgIAMPolicy().then((resp) => {
|
||||
if (resp.policy?.userLoginMustBeDomain) {
|
||||
this.userLoginMustBeDomain = resp.policy.userLoginMustBeDomain;
|
||||
}
|
||||
this.initForm();
|
||||
this.loading = false;
|
||||
this.envSuffixLabel = this.envSuffix();
|
||||
this.changeDetRef.detectChanges();
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
this.initForm();
|
||||
this.loading = false;
|
||||
this.envSuffixLabel = this.envSuffix();
|
||||
this.changeDetRef.detectChanges();
|
||||
});
|
||||
constructor(
|
||||
private router: Router,
|
||||
private toast: ToastService,
|
||||
private fb: FormBuilder,
|
||||
private mgmtService: ManagementService,
|
||||
private changeDetRef: ChangeDetectorRef,
|
||||
) {
|
||||
this.loading = true;
|
||||
this.loadOrg();
|
||||
this.mgmtService.getOrgIAMPolicy().then((resp) => {
|
||||
if (resp.policy?.userLoginMustBeDomain) {
|
||||
this.userLoginMustBeDomain = resp.policy.userLoginMustBeDomain;
|
||||
}
|
||||
this.initForm();
|
||||
this.loading = false;
|
||||
this.envSuffixLabel = this.envSuffix();
|
||||
this.changeDetRef.detectChanges();
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
this.initForm();
|
||||
this.loading = false;
|
||||
this.envSuffixLabel = this.envSuffix();
|
||||
this.changeDetRef.detectChanges();
|
||||
});
|
||||
}
|
||||
|
||||
private async loadOrg(): Promise<void> {
|
||||
const domains = (await this.mgmtService.listOrgDomains());
|
||||
const found = domains.resultList.find(resp => resp.isPrimary);
|
||||
if (found) {
|
||||
this.primaryDomain = found;
|
||||
}
|
||||
}
|
||||
|
||||
private async loadOrg(): Promise<void> {
|
||||
const domains = (await this.mgmtService.listOrgDomains());
|
||||
const found = domains.resultList.find(resp => resp.isPrimary);
|
||||
if (found) {
|
||||
this.primaryDomain = found;
|
||||
private initForm(): void {
|
||||
this.userForm = this.fb.group({
|
||||
email: ['', [Validators.required, Validators.email]],
|
||||
userName: ['',
|
||||
[
|
||||
Validators.required,
|
||||
Validators.minLength(2),
|
||||
],
|
||||
],
|
||||
firstName: ['', Validators.required],
|
||||
lastName: ['', Validators.required],
|
||||
nickName: [''],
|
||||
gender: [],
|
||||
preferredLanguage: [''],
|
||||
phone: [''],
|
||||
});
|
||||
|
||||
this.userForm.controls['phone'].valueChanges.pipe(
|
||||
takeUntil(this.destroyed$),
|
||||
debounceTime(300)).subscribe(value => {
|
||||
const phoneNumber = parsePhoneNumber(value ?? '', 'CH');
|
||||
if (phoneNumber) {
|
||||
const formmatted = phoneNumber.formatInternational();
|
||||
const country = phoneNumber.country;
|
||||
if (this.phone && country && this.phone.value && this.phone.value !== formmatted) {
|
||||
this.phone.setValue(formmatted);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public createUser(): void {
|
||||
this.user = this.userForm.value;
|
||||
|
||||
this.loading = true;
|
||||
|
||||
const profileReq = new AddHumanUserRequest.Profile();
|
||||
profileReq.setFirstName(this.firstName?.value);
|
||||
profileReq.setLastName(this.lastName?.value);
|
||||
profileReq.setNickName(this.nickName?.value);
|
||||
profileReq.setPreferredLanguage(this.preferredLanguage?.value);
|
||||
profileReq.setGender(this.gender?.value);
|
||||
|
||||
const humanReq = new AddHumanUserRequest();
|
||||
humanReq.setUserName(this.userName?.value);
|
||||
humanReq.setProfile(profileReq);
|
||||
|
||||
humanReq.setEmail(new AddHumanUserRequest.Email().setEmail(this.email?.value));
|
||||
|
||||
if (this.phone && this.phone.value) {
|
||||
humanReq.setPhone(new AddHumanUserRequest.Phone().setPhone(this.phone.value));
|
||||
}
|
||||
|
||||
private initForm(): void {
|
||||
this.userForm = this.fb.group({
|
||||
email: ['', [Validators.required, Validators.email]],
|
||||
userName: ['',
|
||||
[
|
||||
Validators.required,
|
||||
Validators.minLength(2),
|
||||
],
|
||||
],
|
||||
firstName: ['', Validators.required],
|
||||
lastName: ['', Validators.required],
|
||||
nickName: [''],
|
||||
gender: [],
|
||||
preferredLanguage: [''],
|
||||
phone: [''],
|
||||
});
|
||||
this.mgmtService
|
||||
.addHumanUser(humanReq)
|
||||
.then((data) => {
|
||||
this.loading = false;
|
||||
this.toast.showInfo('USER.TOAST.CREATED', true);
|
||||
this.router.navigate(['users', data.userId]);
|
||||
})
|
||||
.catch(error => {
|
||||
this.loading = false;
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
|
||||
this.userForm.controls['phone'].valueChanges.pipe(
|
||||
takeUntil(this.destroyed$),
|
||||
debounceTime(300)).subscribe(value => {
|
||||
const phoneNumber = parsePhoneNumber(value ?? '', 'CH');
|
||||
if (phoneNumber) {
|
||||
const formmatted = phoneNumber.formatInternational();
|
||||
const country = phoneNumber.country;
|
||||
if (this.phone && country && this.phone.value && this.phone.value !== formmatted) {
|
||||
this.phone.setValue(formmatted);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
ngOnDestroy(): void {
|
||||
this.destroyed$.next();
|
||||
this.destroyed$.complete();
|
||||
}
|
||||
|
||||
public createUser(): void {
|
||||
this.user = this.userForm.value;
|
||||
public get email(): AbstractControl | null {
|
||||
return this.userForm.get('email');
|
||||
}
|
||||
public get userName(): AbstractControl | null {
|
||||
return this.userForm.get('userName');
|
||||
}
|
||||
public get firstName(): AbstractControl | null {
|
||||
return this.userForm.get('firstName');
|
||||
}
|
||||
public get lastName(): AbstractControl | null {
|
||||
return this.userForm.get('lastName');
|
||||
}
|
||||
public get nickName(): AbstractControl | null {
|
||||
return this.userForm.get('nickName');
|
||||
}
|
||||
public get gender(): AbstractControl | null {
|
||||
return this.userForm.get('gender');
|
||||
}
|
||||
public get preferredLanguage(): AbstractControl | null {
|
||||
return this.userForm.get('preferredLanguage');
|
||||
}
|
||||
public get phone(): AbstractControl | null {
|
||||
return this.userForm.get('phone');
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
private envSuffix(): string {
|
||||
if (this.userLoginMustBeDomain && this.primaryDomain?.domainName) {
|
||||
return `@${this.primaryDomain.domainName}`;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
const profileReq = new AddHumanUserRequest.Profile();
|
||||
profileReq.setFirstName(this.firstName?.value);
|
||||
profileReq.setLastName(this.lastName?.value);
|
||||
profileReq.setNickName(this.nickName?.value);
|
||||
profileReq.setPreferredLanguage(this.preferredLanguage?.value);
|
||||
profileReq.setGender(this.gender?.value);
|
||||
|
||||
const humanReq = new AddHumanUserRequest();
|
||||
humanReq.setUserName(this.userName?.value);
|
||||
humanReq.setProfile(profileReq);
|
||||
|
||||
humanReq.setEmail(new AddHumanUserRequest.Email().setEmail(this.email?.value));
|
||||
|
||||
if (this.phone && this.phone.value) {
|
||||
humanReq.setPhone(new AddHumanUserRequest.Phone().setPhone(this.phone.value));
|
||||
}
|
||||
|
||||
this.mgmtService
|
||||
.addHumanUser(humanReq)
|
||||
.then((data) => {
|
||||
this.loading = false;
|
||||
this.toast.showInfo('USER.TOAST.CREATED', true);
|
||||
this.router.navigate(['users', data.userId]);
|
||||
})
|
||||
.catch(error => {
|
||||
this.loading = false;
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroyed$.next();
|
||||
this.destroyed$.complete();
|
||||
}
|
||||
|
||||
public get email(): AbstractControl | null {
|
||||
return this.userForm.get('email');
|
||||
}
|
||||
public get userName(): AbstractControl | null {
|
||||
return this.userForm.get('userName');
|
||||
}
|
||||
public get firstName(): AbstractControl | null {
|
||||
return this.userForm.get('firstName');
|
||||
}
|
||||
public get lastName(): AbstractControl | null {
|
||||
return this.userForm.get('lastName');
|
||||
}
|
||||
public get nickName(): AbstractControl | null {
|
||||
return this.userForm.get('nickName');
|
||||
}
|
||||
public get gender(): AbstractControl | null {
|
||||
return this.userForm.get('gender');
|
||||
}
|
||||
public get preferredLanguage(): AbstractControl | null {
|
||||
return this.userForm.get('preferredLanguage');
|
||||
}
|
||||
public get phone(): AbstractControl | null {
|
||||
return this.userForm.get('phone');
|
||||
}
|
||||
|
||||
private envSuffix(): string {
|
||||
if (this.userLoginMustBeDomain && this.primaryDomain?.domainName) {
|
||||
return `@${this.primaryDomain.domainName}`;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
public get suffixPadding(): string | undefined {
|
||||
if (this.suffix?.nativeElement.offsetWidth) {
|
||||
return `${(this.suffix.nativeElement as HTMLElement).offsetWidth + 10}px`;
|
||||
}
|
||||
public get suffixPadding(): string | undefined {
|
||||
if (this.suffix?.nativeElement.offsetWidth) {
|
||||
return `${(this.suffix.nativeElement as HTMLElement).offsetWidth + 10}px`;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -2,7 +2,7 @@
|
||||
<span class="title">{{'USER.MFA.DIALOG.ADD_MFA_TITLE' | translate}} {{data?.number}}</span>
|
||||
</h1>
|
||||
<div mat-dialog-content>
|
||||
<ng-container *ngIf="selectedType == undefined">
|
||||
<ng-container *ngIf="selectedType === undefined">
|
||||
<p class="desc">{{'USER.MFA.DIALOG.ADD_MFA_DESCRIPTION' | translate}}</p>
|
||||
|
||||
<div class="type-selection">
|
||||
@@ -25,7 +25,7 @@
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div class="otp" *ngIf="selectedType == AuthFactorType.OTP">
|
||||
<div class="otp" *ngIf="selectedType === AuthFactorType.OTP">
|
||||
<p class="desc">{{'USER.MFA.OTP_DIALOG_DESCRIPTION' | translate}}</p>
|
||||
|
||||
<div class="qrcode-wrapper">
|
||||
@@ -38,7 +38,7 @@
|
||||
</cnsl-form-field>
|
||||
</div>
|
||||
|
||||
<div class="u2f" *ngIf="selectedType == AuthFactorType.U2F">
|
||||
<div class="u2f" *ngIf="selectedType === AuthFactorType.U2F">
|
||||
<p>{{'USER.MFA.U2F_DIALOG_DESCRIPTION' | translate}}</p>
|
||||
|
||||
<cnsl-form-field class="form-field" label="Name" required="true">
|
||||
|
||||
+110
-110
@@ -9,133 +9,133 @@ import { _base64ToArrayBuffer } from '../../u2f-util';
|
||||
import { _arrayBufferToBase64 } from '../u2f_util';
|
||||
|
||||
export enum AuthFactorType {
|
||||
OTP,
|
||||
U2F,
|
||||
OTP,
|
||||
U2F,
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-auth-factor-dialog',
|
||||
templateUrl: './auth-factor-dialog.component.html',
|
||||
styleUrls: ['./auth-factor-dialog.component.scss'],
|
||||
selector: 'cnsl-auth-factor-dialog',
|
||||
templateUrl: './auth-factor-dialog.component.html',
|
||||
styleUrls: ['./auth-factor-dialog.component.scss'],
|
||||
})
|
||||
export class AuthFactorDialogComponent {
|
||||
public otpurl: string = '';
|
||||
public otpcode: string = '';
|
||||
public otpurl: string = '';
|
||||
public otpcode: string = '';
|
||||
|
||||
public u2fname: string = '';
|
||||
public u2fCredentialOptions!: CredentialCreationOptions;
|
||||
public u2fLoading: boolean = false;
|
||||
public u2fError: string = '';
|
||||
public u2fname: string = '';
|
||||
public u2fCredentialOptions!: CredentialCreationOptions;
|
||||
public u2fLoading: boolean = false;
|
||||
public u2fError: string = '';
|
||||
|
||||
AuthFactorType: any = AuthFactorType;
|
||||
selectedType!: AuthFactorType;
|
||||
constructor(
|
||||
private authService: GrpcAuthService,
|
||||
private toast: ToastService,
|
||||
private translate: TranslateService,
|
||||
public dialogRef: MatDialogRef<AuthFactorDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any,
|
||||
) { }
|
||||
AuthFactorType: any = AuthFactorType;
|
||||
selectedType!: AuthFactorType;
|
||||
constructor(
|
||||
private authService: GrpcAuthService,
|
||||
private toast: ToastService,
|
||||
private translate: TranslateService,
|
||||
public dialogRef: MatDialogRef<AuthFactorDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any,
|
||||
) { }
|
||||
|
||||
closeDialog(code: string = ''): void {
|
||||
this.dialogRef.close(code);
|
||||
}
|
||||
closeDialog(code: string = ''): void {
|
||||
this.dialogRef.close(code);
|
||||
}
|
||||
|
||||
public selectType(type: AuthFactorType): void {
|
||||
this.selectedType = type;
|
||||
public selectType(type: AuthFactorType): void {
|
||||
this.selectedType = type;
|
||||
|
||||
if (type === AuthFactorType.OTP) {
|
||||
this.authService.addMyMultiFactorOTP().then((otpresp) => {
|
||||
this.otpurl = otpresp.url;
|
||||
}, error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
} else if (type === AuthFactorType.U2F) {
|
||||
this.authService.addMyMultiFactorU2F().then((u2fresp) => {
|
||||
if (u2fresp.key) {
|
||||
const credOptions: CredentialCreationOptions = JSON.parse(atob(u2fresp.key?.publicKey as string));
|
||||
if (type === AuthFactorType.OTP) {
|
||||
this.authService.addMyMultiFactorOTP().then((otpresp) => {
|
||||
this.otpurl = otpresp.url;
|
||||
}, error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
} else if (type === AuthFactorType.U2F) {
|
||||
this.authService.addMyMultiFactorU2F().then((u2fresp) => {
|
||||
if (u2fresp.key) {
|
||||
const credOptions: CredentialCreationOptions = JSON.parse(atob(u2fresp.key?.publicKey as string));
|
||||
|
||||
if (credOptions.publicKey?.challenge) {
|
||||
credOptions.publicKey.challenge = _base64ToArrayBuffer(credOptions.publicKey.challenge as any);
|
||||
credOptions.publicKey.user.id = _base64ToArrayBuffer(credOptions.publicKey.user.id as any);
|
||||
if (credOptions.publicKey.excludeCredentials) {
|
||||
credOptions.publicKey.excludeCredentials.map(cred => {
|
||||
cred.id = _base64ToArrayBuffer(cred.id as any);
|
||||
return cred;
|
||||
});
|
||||
}
|
||||
this.u2fCredentialOptions = credOptions;
|
||||
}
|
||||
}
|
||||
}, error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
if (credOptions.publicKey?.challenge) {
|
||||
credOptions.publicKey.challenge = _base64ToArrayBuffer(credOptions.publicKey.challenge as any);
|
||||
credOptions.publicKey.user.id = _base64ToArrayBuffer(credOptions.publicKey.user.id as any);
|
||||
if (credOptions.publicKey.excludeCredentials) {
|
||||
credOptions.publicKey.excludeCredentials.map(cred => {
|
||||
cred.id = _base64ToArrayBuffer(cred.id as any);
|
||||
return cred;
|
||||
});
|
||||
}
|
||||
this.u2fCredentialOptions = credOptions;
|
||||
}
|
||||
}
|
||||
}, error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public submitAuth(): void {
|
||||
if (this.selectedType === AuthFactorType.OTP) {
|
||||
this.submitOTP();
|
||||
} else if (this.selectedType === AuthFactorType.U2F) {
|
||||
this.submitU2F();
|
||||
}
|
||||
public submitAuth(): void {
|
||||
if (this.selectedType === AuthFactorType.OTP) {
|
||||
this.submitOTP();
|
||||
} else if (this.selectedType === AuthFactorType.U2F) {
|
||||
this.submitU2F();
|
||||
}
|
||||
}
|
||||
|
||||
public submitOTP(): void {
|
||||
this.authService.verifyMyMultiFactorOTP(this.otpcode).then(() => {
|
||||
public submitOTP(): void {
|
||||
this.authService.verifyMyMultiFactorOTP(this.otpcode).then(() => {
|
||||
this.dialogRef.close(true);
|
||||
}, error => {
|
||||
this.dialogRef.close(false);
|
||||
});
|
||||
}
|
||||
|
||||
public submitU2F(): void {
|
||||
if (this.u2fname && this.u2fCredentialOptions.publicKey) {
|
||||
// this.data.credOptions.publicKey.rp.id = 'localhost';
|
||||
navigator.credentials.create(this.u2fCredentialOptions).then((resp) => {
|
||||
if (resp &&
|
||||
(resp as any).response.attestationObject &&
|
||||
(resp as any).response.clientDataJSON &&
|
||||
(resp as any).rawId) {
|
||||
|
||||
const attestationObject = (resp as any).response.attestationObject;
|
||||
const clientDataJSON = (resp as any).response.clientDataJSON;
|
||||
const rawId = (resp as any).rawId;
|
||||
|
||||
const data = JSON.stringify({
|
||||
id: resp.id,
|
||||
rawId: _arrayBufferToBase64(rawId),
|
||||
type: resp.type,
|
||||
response: {
|
||||
attestationObject: _arrayBufferToBase64(attestationObject),
|
||||
clientDataJSON: _arrayBufferToBase64(clientDataJSON),
|
||||
},
|
||||
});
|
||||
|
||||
const base64 = btoa(data);
|
||||
|
||||
this.authService.verifyMyMultiFactorU2F(base64, this.u2fname).then(() => {
|
||||
this.translate.get('USER.MFA.U2F_SUCCESS').pipe(take(1)).subscribe(msg => {
|
||||
this.toast.showInfo(msg);
|
||||
});
|
||||
this.dialogRef.close(true);
|
||||
}, error => {
|
||||
this.dialogRef.close(false);
|
||||
});
|
||||
}
|
||||
|
||||
public submitU2F(): void {
|
||||
if (this.u2fname && this.u2fCredentialOptions.publicKey) {
|
||||
// this.data.credOptions.publicKey.rp.id = 'localhost';
|
||||
navigator.credentials.create(this.u2fCredentialOptions).then((resp) => {
|
||||
if (resp &&
|
||||
(resp as any).response.attestationObject &&
|
||||
(resp as any).response.clientDataJSON &&
|
||||
(resp as any).rawId) {
|
||||
|
||||
const attestationObject = (resp as any).response.attestationObject;
|
||||
const clientDataJSON = (resp as any).response.clientDataJSON;
|
||||
const rawId = (resp as any).rawId;
|
||||
|
||||
const data = JSON.stringify({
|
||||
id: resp.id,
|
||||
rawId: _arrayBufferToBase64(rawId),
|
||||
type: resp.type,
|
||||
response: {
|
||||
attestationObject: _arrayBufferToBase64(attestationObject),
|
||||
clientDataJSON: _arrayBufferToBase64(clientDataJSON),
|
||||
},
|
||||
});
|
||||
|
||||
const base64 = btoa(data);
|
||||
|
||||
this.authService.verifyMyMultiFactorU2F(base64, this.u2fname).then(() => {
|
||||
this.translate.get('USER.MFA.U2F_SUCCESS').pipe(take(1)).subscribe(msg => {
|
||||
this.toast.showInfo(msg);
|
||||
});
|
||||
this.dialogRef.close(true);
|
||||
this.u2fLoading = false;
|
||||
}).catch(error => {
|
||||
this.u2fLoading = false;
|
||||
this.toast.showError(error);
|
||||
});
|
||||
} else {
|
||||
this.u2fLoading = false;
|
||||
this.translate.get('USER.MFA.U2F_ERROR').pipe(take(1)).subscribe(msg => {
|
||||
this.toast.showInfo(msg);
|
||||
});
|
||||
this.dialogRef.close(true);
|
||||
}
|
||||
}).catch(error => {
|
||||
this.u2fLoading = false;
|
||||
this.u2fError = error;
|
||||
this.toast.showInfo(error.message);
|
||||
});
|
||||
this.u2fLoading = false;
|
||||
}).catch(error => {
|
||||
this.u2fLoading = false;
|
||||
this.toast.showError(error);
|
||||
});
|
||||
} else {
|
||||
this.u2fLoading = false;
|
||||
this.translate.get('USER.MFA.U2F_ERROR').pipe(take(1)).subscribe(msg => {
|
||||
this.toast.showInfo(msg);
|
||||
});
|
||||
this.dialogRef.close(true);
|
||||
}
|
||||
}).catch(error => {
|
||||
this.u2fLoading = false;
|
||||
this.u2fError = error;
|
||||
this.toast.showInfo(error.message);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -1,10 +1,10 @@
|
||||
<app-card title="{{'USER.PASSWORDLESS.TITLE' | translate}}"
|
||||
<cnsl-card title="{{'USER.PASSWORDLESS.TITLE' | translate}}"
|
||||
description="{{'USER.PASSWORDLESS.DESCRIPTION' | translate}}">
|
||||
<button card-actions mat-icon-button (click)="getPasswordless()" class="icon-button" matTooltip="{{'ACTIONS.REFRESH' | translate}}">
|
||||
<mat-icon class="icon">refresh</mat-icon>
|
||||
</button>
|
||||
<app-refresh-table [hideRefresh]="true" [loading]="loading$ | async" (refreshed)="getPasswordless()"
|
||||
[dataSize]="dataSource?.data?.length">
|
||||
<cnsl-refresh-table [hideRefresh]="true" [loading]="loading$ | async" (refreshed)="getPasswordless()"
|
||||
[dataSize]="dataSource?.data?.length ?? 0">
|
||||
<table class="table" mat-table [dataSource]="dataSource">
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'USER.PASSWORDLESS.NAME' | translate }} </th>
|
||||
@@ -37,7 +37,7 @@
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
</app-refresh-table>
|
||||
</cnsl-refresh-table>
|
||||
<div class="add-row">
|
||||
<button class="button" (click)="addPasswordless()" mat-raised-button color="primary"
|
||||
matTooltip="{{'ACTIONS.NEW' | translate}}">
|
||||
@@ -50,4 +50,4 @@
|
||||
<mat-spinner diameter="50"></mat-spinner>
|
||||
</div>
|
||||
</div>
|
||||
</app-card>
|
||||
</cnsl-card>
|
||||
+1
-1
@@ -23,7 +23,7 @@ export interface WebAuthNOptions {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-auth-passwordless',
|
||||
selector: 'cnsl-auth-passwordless',
|
||||
templateUrl: './auth-passwordless.component.html',
|
||||
styleUrls: ['./auth-passwordless.component.scss'],
|
||||
})
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ export enum U2FComponentDestination {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-dialog-passwordless',
|
||||
selector: 'cnsl-dialog-passwordless',
|
||||
templateUrl: './dialog-passwordless.component.html',
|
||||
styleUrls: ['./dialog-passwordless.component.scss'],
|
||||
})
|
||||
|
||||
+26
-26
@@ -1,4 +1,4 @@
|
||||
<app-meta-layout>
|
||||
<cnsl-meta-layout>
|
||||
<div class="max-width-container">
|
||||
<div class="header-row">
|
||||
<div class="text">
|
||||
@@ -7,7 +7,7 @@
|
||||
</div>
|
||||
|
||||
<div class="theme">
|
||||
<app-theme-setting></app-theme-setting>
|
||||
<cnsl-theme-setting></cnsl-theme-setting>
|
||||
</div>
|
||||
</div>
|
||||
<mat-progress-bar *ngIf="loading" color="primary" mode="indeterminate"></mat-progress-bar>
|
||||
@@ -16,40 +16,40 @@
|
||||
|
||||
<cnsl-info-row *ngIf="user" [user]="user"></cnsl-info-row>
|
||||
|
||||
<app-card *ngIf="user && user.human?.profile" class=" app-card" title="{{ 'USER.PROFILE.TITLE' | translate }}">
|
||||
<app-detail-form [showEditImage]="true" [preferredLoginName]="user.preferredLoginName" [genders]="genders" [languages]="languages" [username]="user.userName" [user]="user.human"
|
||||
<cnsl-card *ngIf="user && user.human && user.human.profile" class=" app-card" title="{{ 'USER.PROFILE.TITLE' | translate }}">
|
||||
<cnsl-detail-form [showEditImage]="true" [preferredLoginName]="user.preferredLoginName" [genders]="genders" [languages]="languages" [username]="user.userName" [user]="user.human"
|
||||
(changedLanguage)="changedLanguage($event)" (submitData)="saveProfile($event)">
|
||||
</app-detail-form>
|
||||
</app-card>
|
||||
</cnsl-detail-form>
|
||||
</cnsl-card>
|
||||
|
||||
<app-card *ngIf="user" title="{{ 'USER.LOGINMETHODS.TITLE' | translate }}"
|
||||
<cnsl-card *ngIf="user" title="{{ 'USER.LOGINMETHODS.TITLE' | translate }}"
|
||||
description="{{ 'USER.LOGINMETHODS.DESCRIPTION' | translate }}">
|
||||
<button class="icon-button" card-actions mat-icon-button (click)="refreshUser()" matTooltip="{{'ACTIONS.REFRESH' | translate}}">
|
||||
<mat-icon class="icon">refresh</mat-icon>
|
||||
</button>
|
||||
<app-contact *ngIf="user?.human" [human]="user.human" [state]="user.state" canWrite="true"
|
||||
<cnsl-contact *ngIf="user.human" [human]="user.human" [state]="user.state" [canWrite]="true"
|
||||
(editType)="openEditDialog($event)" (enteredPhoneCode)="enteredPhoneCode($event)"
|
||||
(deletedPhone)="deletePhone()" (resendEmailVerification)="resendEmailVerification()"
|
||||
(resendPhoneVerification)="resendPhoneVerification()">
|
||||
</app-contact>
|
||||
</app-card>
|
||||
</cnsl-contact>
|
||||
</cnsl-card>
|
||||
|
||||
<app-external-idps [userId]="user?.id" [service]="userService"></app-external-idps>
|
||||
<cnsl-external-idps *ngIf="user && user.id" [userId]="user.id" [service]="userService"></cnsl-external-idps>
|
||||
|
||||
<app-auth-passwordless *ngIf="user" #mfaComponent></app-auth-passwordless>
|
||||
<cnsl-auth-passwordless *ngIf="user" #mfaComponent></cnsl-auth-passwordless>
|
||||
|
||||
<app-auth-user-mfa *ngIf="user" #mfaComponent></app-auth-user-mfa>
|
||||
<cnsl-auth-user-mfa *ngIf="user" #mfaComponent></cnsl-auth-user-mfa>
|
||||
|
||||
<app-card *ngIf="user?.id" title="{{ 'GRANTS.USER.TITLE' | translate }}"
|
||||
<cnsl-card *ngIf="user?.id" title="{{ 'GRANTS.USER.TITLE' | translate }}"
|
||||
description="{{'GRANTS.USER.DESCRIPTION' | translate }}">
|
||||
<app-user-grants [userId]="user.id" [context]="USERGRANTCONTEXT"
|
||||
<cnsl-user-grants [userId]="user.id" [context]="USERGRANTCONTEXT"
|
||||
[displayedColumns]="['select', 'projectId', 'dates', 'roleNamesList']"
|
||||
[disableWrite]="((['user.grant.write$'] | hasRole) | async) == false"
|
||||
[disableDelete]="((['user.grant.delete$'] | hasRole) | async) == false">
|
||||
</app-user-grants>
|
||||
</app-card>
|
||||
[disableWrite]="((['user.grant.write$'] | hasRole) | async) === false"
|
||||
[disableDelete]="((['user.grant.delete$'] | hasRole) | async) === false">
|
||||
</cnsl-user-grants>
|
||||
</cnsl-card>
|
||||
|
||||
<ng-template appHasFeature [appHasFeature]="['metadata.user']">
|
||||
<ng-template cnslHasFeature [hasFeature]="['metadata.user']">
|
||||
<cnsl-metadata *ngIf="user?.id" [userId]="user.id"></cnsl-metadata>
|
||||
</ng-template>
|
||||
</div>
|
||||
@@ -65,18 +65,18 @@
|
||||
<span class="second"><span style="display: block;">{{user.preferredLoginName}}</span></span>
|
||||
</div>
|
||||
</div>
|
||||
<mat-tab-group mat-stretch-tabs class="tab-group" disablePagination="true">
|
||||
<mat-tab-group mat-stretch-tabs class="tab-group" [disablePagination]="true">
|
||||
<mat-tab label="Details">
|
||||
<div class="side-padding">
|
||||
<ng-template appHasRole [appHasRole]="['user.membership.read']">
|
||||
<app-memberships [auth]="true" [user]="user"></app-memberships>
|
||||
<ng-template cnslHasRole [hasRole]="['user.membership.read']">
|
||||
<cnsl-memberships [auth]="true" [user]="user"></cnsl-memberships>
|
||||
</ng-template>
|
||||
</div>
|
||||
</mat-tab>
|
||||
<mat-tab label="{{ 'CHANGES.PROJECT.TITLE' | translate }}" class="meta-flex-col">
|
||||
<app-changes class="changes" [refresh]="refreshChanges$" [changeType]="ChangeType.MYUSER" [id]="user.id">
|
||||
</app-changes>
|
||||
<cnsl-changes class="changes" [refresh]="refreshChanges$" [changeType]="ChangeType.MYUSER" [id]="user.id">
|
||||
</cnsl-changes>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</div>
|
||||
</app-meta-layout>
|
||||
</cnsl-meta-layout>
|
||||
+1
-1
@@ -11,7 +11,7 @@ import { ToastService } from 'src/app/services/toast.service';
|
||||
import { EditDialogComponent, EditDialogType } from './edit-dialog/edit-dialog.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-auth-user-detail',
|
||||
selector: 'cnsl-auth-user-detail',
|
||||
templateUrl: './auth-user-detail.component.html',
|
||||
styleUrls: ['./auth-user-detail.component.scss'],
|
||||
})
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
<app-card title="{{'USER.MFA.TITLE' | translate}}" description="{{'USER.MFA.DESCRIPTION' | translate}}">
|
||||
<cnsl-card title="{{'USER.MFA.TITLE' | translate}}" description="{{'USER.MFA.DESCRIPTION' | translate}}">
|
||||
<button card-actions mat-icon-button (click)="getMFAs()" class="icon-button" matTooltip="{{'ACTIONS.REFRESH' | translate}}">
|
||||
<mat-icon class="icon">refresh</mat-icon>
|
||||
</button>
|
||||
<app-refresh-table [hideRefresh]="true" [loading]="loading$ | async" (refreshed)="getMFAs()" [dataSize]="dataSource?.data?.length">
|
||||
<cnsl-refresh-table [hideRefresh]="true" [loading]="loading$ | async" (refreshed)="getMFAs()" [dataSize]="dataSource?.data?.length ?? 0">
|
||||
<table class="table" mat-table [dataSource]="dataSource">
|
||||
<ng-container matColumnDef="type">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'USER.MFA.TABLETYPE' | translate }} </th>
|
||||
@@ -44,7 +44,7 @@
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
</app-refresh-table>
|
||||
</cnsl-refresh-table>
|
||||
<div class="add-row">
|
||||
<button class="button" (click)="addAuthFactor()" mat-raised-button color="primary"
|
||||
matTooltip="{{'ACTIONS.NEW' | translate}}">
|
||||
@@ -57,4 +57,4 @@
|
||||
<mat-spinner diameter="50"></mat-spinner>
|
||||
</div>
|
||||
</div>
|
||||
</app-card>
|
||||
</cnsl-card>
|
||||
+99
-99
@@ -11,121 +11,121 @@ import { ToastService } from 'src/app/services/toast.service';
|
||||
import { AuthFactorDialogComponent } from '../auth-factor-dialog/auth-factor-dialog.component';
|
||||
|
||||
export interface WebAuthNOptions {
|
||||
challenge: string;
|
||||
rp: { name: string, id: string; };
|
||||
user: { name: string, id: string, displayName: string; };
|
||||
pubKeyCredParams: any;
|
||||
authenticatorSelection: { userVerification: string; };
|
||||
timeout: number;
|
||||
attestation: string;
|
||||
challenge: string;
|
||||
rp: { name: string, id: string; };
|
||||
user: { name: string, id: string, displayName: string; };
|
||||
pubKeyCredParams: any;
|
||||
authenticatorSelection: { userVerification: string; };
|
||||
timeout: number;
|
||||
attestation: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-auth-user-mfa',
|
||||
templateUrl: './auth-user-mfa.component.html',
|
||||
styleUrls: ['./auth-user-mfa.component.scss'],
|
||||
selector: 'cnsl-auth-user-mfa',
|
||||
templateUrl: './auth-user-mfa.component.html',
|
||||
styleUrls: ['./auth-user-mfa.component.scss'],
|
||||
})
|
||||
export class AuthUserMfaComponent implements OnInit, OnDestroy {
|
||||
public displayedColumns: string[] = ['type', 'name', 'state', 'actions'];
|
||||
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
|
||||
public displayedColumns: string[] = ['type', 'name', 'state', 'actions'];
|
||||
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
|
||||
|
||||
@ViewChild(MatTable) public table!: MatTable<AuthFactor.AsObject>;
|
||||
@ViewChild(MatSort) public sort!: MatSort;
|
||||
public dataSource!: MatTableDataSource<AuthFactor.AsObject>;
|
||||
@ViewChild(MatTable) public table!: MatTable<AuthFactor.AsObject>;
|
||||
@ViewChild(MatSort) public sort!: MatSort;
|
||||
public dataSource!: MatTableDataSource<AuthFactor.AsObject>;
|
||||
|
||||
public AuthFactorState: any = AuthFactorState;
|
||||
public AuthFactorState: any = AuthFactorState;
|
||||
|
||||
public error: string = '';
|
||||
public otpAvailable: boolean = false;
|
||||
public error: string = '';
|
||||
public otpAvailable: boolean = false;
|
||||
|
||||
constructor(
|
||||
private service: GrpcAuthService,
|
||||
private toast: ToastService,
|
||||
private dialog: MatDialog,
|
||||
) { }
|
||||
constructor(
|
||||
private service: GrpcAuthService,
|
||||
private toast: ToastService,
|
||||
private dialog: MatDialog,
|
||||
) { }
|
||||
|
||||
public ngOnInit(): void {
|
||||
this.getMFAs();
|
||||
}
|
||||
public ngOnInit(): void {
|
||||
this.getMFAs();
|
||||
}
|
||||
|
||||
public ngOnDestroy(): void {
|
||||
this.loadingSubject.complete();
|
||||
}
|
||||
public ngOnDestroy(): void {
|
||||
this.loadingSubject.complete();
|
||||
}
|
||||
|
||||
public addAuthFactor(): void {
|
||||
const dialogRef = this.dialog.open(AuthFactorDialogComponent, {
|
||||
data: {
|
||||
otpDisabled: !this.otpAvailable,
|
||||
},
|
||||
public addAuthFactor(): void {
|
||||
const dialogRef = this.dialog.open(AuthFactorDialogComponent, {
|
||||
data: {
|
||||
otpDisabled: !this.otpAvailable,
|
||||
},
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((code) => {
|
||||
if (code) {
|
||||
this.service.verifyMyMultiFactorOTP(code).then(() => {
|
||||
this.getMFAs();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
dialogRef.afterClosed().subscribe((code) => {
|
||||
if (code) {
|
||||
this.service.verifyMyMultiFactorOTP(code).then(() => {
|
||||
this.getMFAs();
|
||||
});
|
||||
public getMFAs(): void {
|
||||
this.service.listMyMultiFactors().then(mfas => {
|
||||
const list = mfas.resultList;
|
||||
this.dataSource = new MatTableDataSource(list);
|
||||
this.dataSource.sort = this.sort;
|
||||
|
||||
const index = list.findIndex(mfa => mfa.otp);
|
||||
if (index === -1) {
|
||||
this.otpAvailable = true;
|
||||
}
|
||||
}).catch(error => {
|
||||
this.error = error.message;
|
||||
});
|
||||
}
|
||||
|
||||
public deleteMFA(factor: AuthFactor.AsObject): void {
|
||||
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
||||
data: {
|
||||
confirmKey: 'ACTIONS.DELETE',
|
||||
cancelKey: 'ACTIONS.CANCEL',
|
||||
titleKey: 'USER.MFA.DIALOG.MFA_DELETE_TITLE',
|
||||
descriptionKey: 'USER.MFA.DIALOG.MFA_DELETE_DESCRIPTION',
|
||||
},
|
||||
width: '400px',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(resp => {
|
||||
if (resp) {
|
||||
if (factor.otp) {
|
||||
this.service.removeMyMultiFactorOTP().then(() => {
|
||||
this.toast.showInfo('USER.TOAST.OTPREMOVED', true);
|
||||
|
||||
const index = this.dataSource.data.findIndex(mfa => !!mfa.otp);
|
||||
if (index > -1) {
|
||||
this.dataSource.data.splice(index, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
this.getMFAs();
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
} else
|
||||
if (factor.u2f) {
|
||||
this.service.removeMyMultiFactorU2F(factor.u2f.id).then(() => {
|
||||
this.toast.showInfo('USER.TOAST.U2FREMOVED', true);
|
||||
|
||||
public getMFAs(): void {
|
||||
this.service.listMyMultiFactors().then(mfas => {
|
||||
const list = mfas.resultList;
|
||||
this.dataSource = new MatTableDataSource(list);
|
||||
this.dataSource.sort = this.sort;
|
||||
|
||||
const index = list.findIndex(mfa => mfa.otp);
|
||||
if (index === -1) {
|
||||
this.otpAvailable = true;
|
||||
}
|
||||
}).catch(error => {
|
||||
this.error = error.message;
|
||||
});
|
||||
}
|
||||
|
||||
public deleteMFA(factor: AuthFactor.AsObject): void {
|
||||
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
||||
data: {
|
||||
confirmKey: 'ACTIONS.DELETE',
|
||||
cancelKey: 'ACTIONS.CANCEL',
|
||||
titleKey: 'USER.MFA.DIALOG.MFA_DELETE_TITLE',
|
||||
descriptionKey: 'USER.MFA.DIALOG.MFA_DELETE_DESCRIPTION',
|
||||
},
|
||||
width: '400px',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(resp => {
|
||||
if (resp) {
|
||||
if (factor.otp) {
|
||||
this.service.removeMyMultiFactorOTP().then(() => {
|
||||
this.toast.showInfo('USER.TOAST.OTPREMOVED', true);
|
||||
|
||||
const index = this.dataSource.data.findIndex(mfa => !!mfa.otp);
|
||||
if (index > -1) {
|
||||
this.dataSource.data.splice(index, 1);
|
||||
}
|
||||
this.getMFAs();
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
} else
|
||||
if (factor.u2f) {
|
||||
this.service.removeMyMultiFactorU2F(factor.u2f.id).then(() => {
|
||||
this.toast.showInfo('USER.TOAST.U2FREMOVED', true);
|
||||
|
||||
const index = this.dataSource.data.findIndex(mfa => !!mfa.u2f);
|
||||
if (index > -1) {
|
||||
this.dataSource.data.splice(index, 1);
|
||||
}
|
||||
this.getMFAs();
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
const index = this.dataSource.data.findIndex(mfa => !!mfa.u2f);
|
||||
if (index > -1) {
|
||||
this.dataSource.data.splice(index, 1);
|
||||
}
|
||||
this.getMFAs();
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+9
-9
@@ -2,17 +2,17 @@ import { Component, Inject } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
|
||||
@Component({
|
||||
selector: 'app-code-dialog',
|
||||
templateUrl: './code-dialog.component.html',
|
||||
styleUrls: ['./code-dialog.component.scss'],
|
||||
selector: 'cnsl-code-dialog',
|
||||
templateUrl: './code-dialog.component.html',
|
||||
styleUrls: ['./code-dialog.component.scss'],
|
||||
})
|
||||
export class CodeDialogComponent {
|
||||
public code: string = '';
|
||||
constructor(public dialogRef: MatDialogRef<CodeDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any) { }
|
||||
public code: string = '';
|
||||
constructor(public dialogRef: MatDialogRef<CodeDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any) { }
|
||||
|
||||
closeDialog(code: string = ''): void {
|
||||
this.dialogRef.close(code);
|
||||
}
|
||||
closeDialog(code: string = ''): void {
|
||||
this.dialogRef.close(code);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+74
-74
@@ -8,93 +8,93 @@ import { ToastService } from 'src/app/services/toast.service';
|
||||
import { _arrayBufferToBase64 } from '../u2f_util';
|
||||
|
||||
export enum U2FComponentDestination {
|
||||
MFA = 'mfa',
|
||||
PASSWORDLESS = 'passwordless',
|
||||
MFA = 'mfa',
|
||||
PASSWORDLESS = 'passwordless',
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-dialog-u2f',
|
||||
templateUrl: './dialog-u2f.component.html',
|
||||
styleUrls: ['./dialog-u2f.component.scss'],
|
||||
selector: 'cnsl-dialog-u2f',
|
||||
templateUrl: './dialog-u2f.component.html',
|
||||
styleUrls: ['./dialog-u2f.component.scss'],
|
||||
})
|
||||
export class DialogU2FComponent {
|
||||
private type!: U2FComponentDestination;
|
||||
public name: string = '';
|
||||
public error: string = '';
|
||||
public loading: boolean = false;
|
||||
private type!: U2FComponentDestination;
|
||||
public name: string = '';
|
||||
public error: string = '';
|
||||
public loading: boolean = false;
|
||||
|
||||
constructor(public dialogRef: MatDialogRef<DialogU2FComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: { credOptions: any; type: U2FComponentDestination; },
|
||||
private service: GrpcAuthService, private translate: TranslateService, private toast: ToastService) {
|
||||
this.type = data.type;
|
||||
}
|
||||
constructor(public dialogRef: MatDialogRef<DialogU2FComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: { credOptions: any; type: U2FComponentDestination; },
|
||||
private service: GrpcAuthService, private translate: TranslateService, private toast: ToastService) {
|
||||
this.type = data.type;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
public closeDialog(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
public closeDialogWithCode(): void {
|
||||
this.error = '';
|
||||
this.loading = true;
|
||||
if (this.name && this.data.credOptions.publicKey) {
|
||||
// this.data.credOptions.publicKey.rp.id = 'localhost';
|
||||
navigator.credentials.create(this.data.credOptions).then((resp) => {
|
||||
if (resp &&
|
||||
(resp as any).response.attestationObject &&
|
||||
(resp as any).response.clientDataJSON &&
|
||||
(resp as any).rawId) {
|
||||
public closeDialogWithCode(): void {
|
||||
this.error = '';
|
||||
this.loading = true;
|
||||
if (this.name && this.data.credOptions.publicKey) {
|
||||
// this.data.credOptions.publicKey.rp.id = 'localhost';
|
||||
navigator.credentials.create(this.data.credOptions).then((resp) => {
|
||||
if (resp &&
|
||||
(resp as any).response.attestationObject &&
|
||||
(resp as any).response.clientDataJSON &&
|
||||
(resp as any).rawId) {
|
||||
|
||||
const attestationObject = (resp as any).response.attestationObject;
|
||||
const clientDataJSON = (resp as any).response.clientDataJSON;
|
||||
const rawId = (resp as any).rawId;
|
||||
const attestationObject = (resp as any).response.attestationObject;
|
||||
const clientDataJSON = (resp as any).response.clientDataJSON;
|
||||
const rawId = (resp as any).rawId;
|
||||
|
||||
const data = JSON.stringify({
|
||||
id: resp.id,
|
||||
rawId: _arrayBufferToBase64(rawId),
|
||||
type: resp.type,
|
||||
response: {
|
||||
attestationObject: _arrayBufferToBase64(attestationObject),
|
||||
clientDataJSON: _arrayBufferToBase64(clientDataJSON),
|
||||
},
|
||||
});
|
||||
const data = JSON.stringify({
|
||||
id: resp.id,
|
||||
rawId: _arrayBufferToBase64(rawId),
|
||||
type: resp.type,
|
||||
response: {
|
||||
attestationObject: _arrayBufferToBase64(attestationObject),
|
||||
clientDataJSON: _arrayBufferToBase64(clientDataJSON),
|
||||
},
|
||||
});
|
||||
|
||||
const base64 = btoa(data);
|
||||
if (this.type === U2FComponentDestination.MFA) {
|
||||
this.service.verifyMyMultiFactorU2F(base64, this.name).then(() => {
|
||||
this.translate.get('USER.MFA.U2F_SUCCESS').pipe(take(1)).subscribe(msg => {
|
||||
this.toast.showInfo(msg);
|
||||
});
|
||||
this.dialogRef.close(true);
|
||||
this.loading = false;
|
||||
}).catch(error => {
|
||||
this.loading = false;
|
||||
this.toast.showError(error);
|
||||
});
|
||||
} else if (this.type === U2FComponentDestination.PASSWORDLESS) {
|
||||
this.service.verifyMyPasswordless(base64, this.name).then(() => {
|
||||
this.translate.get('USER.PASSWORDLESS.U2F_SUCCESS').pipe(take(1)).subscribe(msg => {
|
||||
this.toast.showInfo(msg);
|
||||
});
|
||||
this.dialogRef.close(true);
|
||||
this.loading = false;
|
||||
}).catch(error => {
|
||||
this.loading = false;
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.loading = false;
|
||||
this.translate.get('USER.MFA.U2F_ERROR').pipe(take(1)).subscribe(msg => {
|
||||
this.toast.showInfo(msg);
|
||||
});
|
||||
this.dialogRef.close(true);
|
||||
}
|
||||
const base64 = btoa(data);
|
||||
if (this.type === U2FComponentDestination.MFA) {
|
||||
this.service.verifyMyMultiFactorU2F(base64, this.name).then(() => {
|
||||
this.translate.get('USER.MFA.U2F_SUCCESS').pipe(take(1)).subscribe(msg => {
|
||||
this.toast.showInfo(msg);
|
||||
});
|
||||
this.dialogRef.close(true);
|
||||
this.loading = false;
|
||||
}).catch(error => {
|
||||
this.loading = false;
|
||||
this.error = error;
|
||||
this.toast.showInfo(error.message);
|
||||
this.loading = false;
|
||||
this.toast.showError(error);
|
||||
});
|
||||
} else if (this.type === U2FComponentDestination.PASSWORDLESS) {
|
||||
this.service.verifyMyPasswordless(base64, this.name).then(() => {
|
||||
this.translate.get('USER.PASSWORDLESS.U2F_SUCCESS').pipe(take(1)).subscribe(msg => {
|
||||
this.toast.showInfo(msg);
|
||||
});
|
||||
this.dialogRef.close(true);
|
||||
this.loading = false;
|
||||
}).catch(error => {
|
||||
this.loading = false;
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.loading = false;
|
||||
this.translate.get('USER.MFA.U2F_ERROR').pipe(take(1)).subscribe(msg => {
|
||||
this.toast.showInfo(msg);
|
||||
});
|
||||
this.dialogRef.close(true);
|
||||
}
|
||||
|
||||
}).catch(error => {
|
||||
this.loading = false;
|
||||
this.error = error;
|
||||
this.toast.showInfo(error.message);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+42
-42
@@ -4,55 +4,55 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { parsePhoneNumber } from 'libphonenumber-js';
|
||||
|
||||
export enum EditDialogType {
|
||||
PHONE = 1,
|
||||
EMAIL = 2,
|
||||
PHONE = 1,
|
||||
EMAIL = 2,
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-edit-email-dialog',
|
||||
templateUrl: './edit-dialog.component.html',
|
||||
styleUrls: ['./edit-dialog.component.scss'],
|
||||
selector: 'cnsl-edit-email-dialog',
|
||||
templateUrl: './edit-dialog.component.html',
|
||||
styleUrls: ['./edit-dialog.component.scss'],
|
||||
})
|
||||
export class EditDialogComponent {
|
||||
public isPhone: boolean = false;
|
||||
public phoneCountry: string = 'CH';
|
||||
public valueControl: FormControl = new FormControl(['', [Validators.required]]);
|
||||
constructor(public dialogRef: MatDialogRef<EditDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any) {
|
||||
this.valueControl.setValue(data.value);
|
||||
if (data.type === EditDialogType.PHONE) {
|
||||
this.isPhone = true;
|
||||
public isPhone: boolean = false;
|
||||
public phoneCountry: string = 'CH';
|
||||
public valueControl: FormControl = new FormControl(['', [Validators.required]]);
|
||||
constructor(public dialogRef: MatDialogRef<EditDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any) {
|
||||
this.valueControl.setValue(data.value);
|
||||
if (data.type === EditDialogType.PHONE) {
|
||||
this.isPhone = true;
|
||||
}
|
||||
|
||||
this.valueControl.valueChanges.subscribe(value => {
|
||||
if (value && value.length > 1) {
|
||||
this.changeValue(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private changeValue(changedValue: string): void {
|
||||
if (this.isPhone && changedValue) {
|
||||
try {
|
||||
const phoneNumber = parsePhoneNumber(changedValue ?? '', 'CH');
|
||||
if (phoneNumber) {
|
||||
const formmatted = phoneNumber.formatInternational();
|
||||
this.phoneCountry = phoneNumber.country || '';
|
||||
if (formmatted !== this.valueControl.value) {
|
||||
this.valueControl.setValue(formmatted);
|
||||
}
|
||||
}
|
||||
|
||||
this.valueControl.valueChanges.subscribe(value => {
|
||||
if (value && value.length > 1) {
|
||||
this.changeValue(value);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private changeValue(changedValue: string): void {
|
||||
if (this.isPhone && changedValue) {
|
||||
try {
|
||||
const phoneNumber = parsePhoneNumber(changedValue ?? '', 'CH');
|
||||
if (phoneNumber) {
|
||||
const formmatted = phoneNumber.formatInternational();
|
||||
this.phoneCountry = phoneNumber.country || '';
|
||||
if (formmatted !== this.valueControl.value) {
|
||||
this.valueControl.setValue(formmatted);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
closeDialog(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
closeDialog(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
closeDialogWithValue(): void {
|
||||
this.dialogRef.close(this.valueControl.value);
|
||||
}
|
||||
closeDialogWithValue(): void {
|
||||
this.dialogRef.close(this.valueControl.value);
|
||||
}
|
||||
}
|
||||
|
||||
+12
-12
@@ -2,20 +2,20 @@ import { Component, Inject } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
|
||||
@Component({
|
||||
selector: 'app-resend-email-dialog',
|
||||
templateUrl: './resend-email-dialog.component.html',
|
||||
styleUrls: ['./resend-email-dialog.component.scss'],
|
||||
selector: 'cnsl-resend-email-dialog',
|
||||
templateUrl: './resend-email-dialog.component.html',
|
||||
styleUrls: ['./resend-email-dialog.component.scss'],
|
||||
})
|
||||
export class ResendEmailDialogComponent {
|
||||
public email: string = '';
|
||||
constructor(public dialogRef: MatDialogRef<ResendEmailDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any) { }
|
||||
public email: string = '';
|
||||
constructor(public dialogRef: MatDialogRef<ResendEmailDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any) { }
|
||||
|
||||
closeDialog(email: string = ''): void {
|
||||
this.dialogRef.close(email);
|
||||
}
|
||||
closeDialog(email: string = ''): void {
|
||||
this.dialogRef.close(email);
|
||||
}
|
||||
|
||||
closeDialogWithSend(email: string = ''): void {
|
||||
this.dialogRef.close({ send: true, email });
|
||||
}
|
||||
closeDialogWithSend(email: string = ''): void {
|
||||
this.dialogRef.close({ send: true, email });
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
<input type="checkbox" id="switch" (change)="change($event.target.checked)" [(checked)]="isDarkTheme">
|
||||
<input type="checkbox" id="switch" (change)="change($event)" [checked]="isDarkTheme"/>
|
||||
<div class="theme-app">
|
||||
<div class="theme-content">
|
||||
<label for="switch">
|
||||
|
||||
+20
-17
@@ -3,27 +3,30 @@ import { Subscription } from 'rxjs';
|
||||
import { ThemeService } from 'src/app/services/theme.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-theme-setting',
|
||||
templateUrl: './theme-setting.component.html',
|
||||
styleUrls: ['./theme-setting.component.scss'],
|
||||
selector: 'cnsl-theme-setting',
|
||||
templateUrl: './theme-setting.component.html',
|
||||
styleUrls: ['./theme-setting.component.scss'],
|
||||
})
|
||||
export class ThemeSettingComponent implements OnInit, OnDestroy {
|
||||
public isDarkTheme: boolean = true;
|
||||
private sub: Subscription = new Subscription();
|
||||
constructor(private themeService: ThemeService) {
|
||||
const theme = localStorage.getItem('theme');
|
||||
this.isDarkTheme = theme === 'dark-theme' ? true : theme === 'light-theme' ? false : true;
|
||||
}
|
||||
public isDarkTheme: boolean = true;
|
||||
private sub: Subscription = new Subscription();
|
||||
constructor(private themeService: ThemeService) {
|
||||
const theme = localStorage.getItem('theme');
|
||||
this.isDarkTheme = theme === 'dark-theme' ? true : theme === 'light-theme' ? false : true;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.sub = this.themeService.isDarkTheme.subscribe(d => this.isDarkTheme = d);
|
||||
}
|
||||
ngOnInit(): void {
|
||||
this.sub = this.themeService.isDarkTheme.subscribe(d => this.isDarkTheme = d);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.sub.unsubscribe();
|
||||
}
|
||||
ngOnDestroy(): void {
|
||||
this.sub.unsubscribe();
|
||||
}
|
||||
|
||||
public change(checked: boolean): void {
|
||||
this.themeService.setDarkTheme(checked);
|
||||
public change(event: any): void {
|
||||
if (event.target) {
|
||||
const checked = event.target.checked;
|
||||
this.themeService.setDarkTheme(checked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<span class="name">{{human?.email?.email}}</span>
|
||||
<span *ngIf="human?.email?.isEmailVerified" class="contact-state verified">{{'USER.EMAILVERIFIED' |
|
||||
translate}}</span>
|
||||
<div *ngIf="!human?.email.isEmailVerified" class="block">
|
||||
<div *ngIf="!human?.email?.isEmailVerified" class="block">
|
||||
<span class="contact-state notverified">{{'USER.NOTVERIFIED' | translate}}</span>
|
||||
|
||||
<ng-container *ngIf="human?.email">
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
<div class="right">
|
||||
<button matTooltip="{{'ACTIONS.EDIT' | translate}}"
|
||||
[disabled]="!canWrite || state == UserState.USER_STATE_INITIAL"
|
||||
[disabled]="!canWrite || state === UserState.USER_STATE_INITIAL"
|
||||
(click)="openEditDialog(EditDialogType.EMAIL)" mat-icon-button>
|
||||
<i class="las la-edit"></i>
|
||||
</button>
|
||||
@@ -46,9 +46,9 @@
|
||||
<div class="left">
|
||||
<span class="label">{{ 'USER.PHONE' | translate }}</span>
|
||||
<span class="name">{{human?.phone?.phone ? human.phone?.phone : ('USER.PHONEEMPTY' | translate)}}</span>
|
||||
<span *ngIf="human?.phone.isPhoneVerified" class="contact-state verified">{{'USER.PHONEVERIFIED' |
|
||||
<span *ngIf="human?.phone?.isPhoneVerified" class="contact-state verified">{{'USER.PHONEVERIFIED' |
|
||||
translate}}</span>
|
||||
<div *ngIf="human.phone?.phone && !human?.phone.isPhoneVerified" class="block">
|
||||
<div *ngIf="human.phone?.phone && !human?.phone?.isPhoneVerified" class="block">
|
||||
<span class="contact-state notverified">{{'USER.NOTVERIFIED' | translate}}</span>
|
||||
|
||||
<ng-container *ngIf="human?.phone?.phone">
|
||||
|
||||
@@ -7,69 +7,69 @@ import { CodeDialogComponent } from '../auth-user-detail/code-dialog/code-dialog
|
||||
import { EditDialogType } from '../auth-user-detail/edit-dialog/edit-dialog.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-contact',
|
||||
templateUrl: './contact.component.html',
|
||||
styleUrls: ['./contact.component.scss'],
|
||||
selector: 'cnsl-contact',
|
||||
templateUrl: './contact.component.html',
|
||||
styleUrls: ['./contact.component.scss'],
|
||||
})
|
||||
export class ContactComponent {
|
||||
@Input() disablePhoneCode: boolean = false;
|
||||
@Input() canWrite: boolean = false;
|
||||
@Input() human!: Human.AsObject;
|
||||
@Input() state!: UserState;
|
||||
@Output() editType: EventEmitter<EditDialogType> = new EventEmitter();
|
||||
@Output() resendEmailVerification: EventEmitter<void> = new EventEmitter();
|
||||
@Output() resendPhoneVerification: EventEmitter<void> = new EventEmitter();
|
||||
@Output() enteredPhoneCode: EventEmitter<string> = new EventEmitter();
|
||||
@Output() deletedPhone: EventEmitter<void> = new EventEmitter();
|
||||
public UserState: any = UserState;
|
||||
@Input() disablePhoneCode: boolean = false;
|
||||
@Input() canWrite: boolean | null = false;
|
||||
@Input() human!: Human.AsObject;
|
||||
@Input() state!: UserState;
|
||||
@Output() editType: EventEmitter<EditDialogType> = new EventEmitter();
|
||||
@Output() resendEmailVerification: EventEmitter<void> = new EventEmitter();
|
||||
@Output() resendPhoneVerification: EventEmitter<void> = new EventEmitter();
|
||||
@Output() enteredPhoneCode: EventEmitter<string> = new EventEmitter();
|
||||
@Output() deletedPhone: EventEmitter<void> = new EventEmitter();
|
||||
public UserState: any = UserState;
|
||||
|
||||
public EditDialogType: any = EditDialogType;
|
||||
constructor(private dialog: MatDialog) { }
|
||||
public EditDialogType: any = EditDialogType;
|
||||
constructor(private dialog: MatDialog) { }
|
||||
|
||||
emitDeletePhone(): void {
|
||||
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
||||
data: {
|
||||
confirmKey: 'ACTIONS.DELETE',
|
||||
cancelKey: 'ACTIONS.CANCEL',
|
||||
titleKey: 'USER.LOGINMETHODS.PHONE.DELETETITLE',
|
||||
descriptionKey: 'USER.LOGINMETHODS.PHONE.DELETEDESC',
|
||||
},
|
||||
width: '400px',
|
||||
});
|
||||
emitDeletePhone(): void {
|
||||
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
||||
data: {
|
||||
confirmKey: 'ACTIONS.DELETE',
|
||||
cancelKey: 'ACTIONS.CANCEL',
|
||||
titleKey: 'USER.LOGINMETHODS.PHONE.DELETETITLE',
|
||||
descriptionKey: 'USER.LOGINMETHODS.PHONE.DELETEDESC',
|
||||
},
|
||||
width: '400px',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(resp => {
|
||||
if (resp) {
|
||||
this.deletedPhone.emit();
|
||||
}
|
||||
});
|
||||
}
|
||||
dialogRef.afterClosed().subscribe(resp => {
|
||||
if (resp) {
|
||||
this.deletedPhone.emit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
emitEmailVerification(): void {
|
||||
this.resendEmailVerification.emit();
|
||||
}
|
||||
emitEmailVerification(): void {
|
||||
this.resendEmailVerification.emit();
|
||||
}
|
||||
|
||||
emitPhoneVerification(): void {
|
||||
this.resendPhoneVerification.emit();
|
||||
}
|
||||
emitPhoneVerification(): void {
|
||||
this.resendPhoneVerification.emit();
|
||||
}
|
||||
|
||||
public enterCode(): void {
|
||||
if (this.human) {
|
||||
const dialogRef = this.dialog.open(CodeDialogComponent, {
|
||||
data: {
|
||||
number: this.human.phone,
|
||||
},
|
||||
width: '400px',
|
||||
});
|
||||
public enterCode(): void {
|
||||
if (this.human) {
|
||||
const dialogRef = this.dialog.open(CodeDialogComponent, {
|
||||
data: {
|
||||
number: this.human.phone,
|
||||
},
|
||||
width: '400px',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(code => {
|
||||
if (code) {
|
||||
this.enteredPhoneCode.emit(code);
|
||||
}
|
||||
});
|
||||
dialogRef.afterClosed().subscribe(code => {
|
||||
if (code) {
|
||||
this.enteredPhoneCode.emit(code);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public openEditDialog(type: EditDialogType): void {
|
||||
this.editType.emit(type);
|
||||
}
|
||||
public openEditDialog(type: EditDialogType): void {
|
||||
this.editType.emit(type);
|
||||
}
|
||||
}
|
||||
|
||||
+37
-37
@@ -1,54 +1,54 @@
|
||||
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
|
||||
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { User } from 'src/app/proto/generated/zitadel/user_pb';
|
||||
import { Human, Machine } from 'src/app/proto/generated/zitadel/user_pb';
|
||||
|
||||
@Component({
|
||||
selector: 'app-detail-form-machine',
|
||||
templateUrl: './detail-form-machine.component.html',
|
||||
styleUrls: ['./detail-form-machine.component.scss'],
|
||||
selector: 'cnsl-detail-form-machine',
|
||||
templateUrl: './detail-form-machine.component.html',
|
||||
styleUrls: ['./detail-form-machine.component.scss'],
|
||||
})
|
||||
export class DetailFormMachineComponent implements OnInit, OnDestroy {
|
||||
@Input() public username!: string;
|
||||
@Input() public user!: User;
|
||||
@Input() public disabled: boolean = false;
|
||||
@Output() public submitData: EventEmitter<any> = new EventEmitter<any>();
|
||||
@Input() public username!: string;
|
||||
@Input() public user!: Human.AsObject | Machine.AsObject;
|
||||
@Input() public disabled: boolean = false;
|
||||
@Output() public submitData: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
public machineForm!: FormGroup;
|
||||
public machineForm!: FormGroup;
|
||||
|
||||
private sub: Subscription = new Subscription();
|
||||
private sub: Subscription = new Subscription();
|
||||
|
||||
constructor(private fb: FormBuilder) {
|
||||
this.machineForm = this.fb.group({
|
||||
userName: [{ value: '', disabled: true }, [
|
||||
Validators.required,
|
||||
]],
|
||||
name: [{ value: '', disabled: this.disabled }, Validators.required],
|
||||
description: [{ value: '', disabled: this.disabled }],
|
||||
});
|
||||
}
|
||||
constructor(private fb: FormBuilder) {
|
||||
this.machineForm = this.fb.group({
|
||||
userName: [{ value: '', disabled: true }, [
|
||||
Validators.required,
|
||||
]],
|
||||
name: [{ value: '', disabled: this.disabled }, Validators.required],
|
||||
description: [{ value: '', disabled: this.disabled }],
|
||||
});
|
||||
}
|
||||
|
||||
public ngOnInit(): void {
|
||||
this.machineForm.patchValue({ userName: this.username, ...this.user });
|
||||
}
|
||||
public ngOnInit(): void {
|
||||
this.machineForm.patchValue({ ...this.user, userName: this.username });
|
||||
}
|
||||
|
||||
public ngOnDestroy(): void {
|
||||
this.sub.unsubscribe();
|
||||
}
|
||||
public ngOnDestroy(): void {
|
||||
this.sub.unsubscribe();
|
||||
}
|
||||
|
||||
public submitForm(): void {
|
||||
this.submitData.emit(this.machineForm.value);
|
||||
}
|
||||
public submitForm(): void {
|
||||
this.submitData.emit(this.machineForm.value);
|
||||
}
|
||||
|
||||
public get name(): AbstractControl | null {
|
||||
return this.machineForm.get('name');
|
||||
}
|
||||
public get name(): AbstractControl | null {
|
||||
return this.machineForm.get('name');
|
||||
}
|
||||
|
||||
public get description(): AbstractControl | null {
|
||||
return this.machineForm.get('description');
|
||||
}
|
||||
public get description(): AbstractControl | null {
|
||||
return this.machineForm.get('description');
|
||||
}
|
||||
|
||||
public get userName(): AbstractControl | null {
|
||||
return this.machineForm.get('userName');
|
||||
}
|
||||
public get userName(): AbstractControl | null {
|
||||
return this.machineForm.get('userName');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
<div class="i-wrapper" *ngIf="showEditImage">
|
||||
<i class="las la-camera"></i>
|
||||
</div>
|
||||
<app-avatar
|
||||
*ngIf="user && user.profile?.displayName && user.profile?.firstName && user?.profile.lastName"
|
||||
class="avatar" [name]="user.profile?.displayName" [avatarUrl]="user?.profile?.avatarUrl || ''" [forColor]="preferredLoginName" [size]="80">
|
||||
</app-avatar>
|
||||
<cnsl-avatar
|
||||
*ngIf="user && user.profile?.displayName && user.profile?.firstName && user.profile?.lastName"
|
||||
class="avatar" [name]="user.profile?.displayName ?? ''" [avatarUrl]="user?.profile?.avatarUrl || ''" [forColor]="preferredLoginName" [size]="80">
|
||||
</cnsl-avatar>
|
||||
</button>
|
||||
|
||||
<cnsl-form-field class="formfield">
|
||||
|
||||
@@ -2,12 +2,12 @@ import { Component, EventEmitter, Input, OnChanges, OnDestroy, Output } from '@a
|
||||
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { Gender, Human, User } from 'src/app/proto/generated/zitadel/user_pb';
|
||||
import { Gender, Human, Profile } from 'src/app/proto/generated/zitadel/user_pb';
|
||||
|
||||
import { ProfilePictureComponent } from './profile-picture/profile-picture.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-detail-form',
|
||||
selector: 'cnsl-detail-form',
|
||||
templateUrl: './detail-form.component.html',
|
||||
styleUrls: ['./detail-form.component.scss'],
|
||||
})
|
||||
@@ -19,7 +19,7 @@ export class DetailFormComponent implements OnDestroy, OnChanges {
|
||||
@Input() public disabled: boolean = false;
|
||||
@Input() public genders: Gender[] = [];
|
||||
@Input() public languages: string[] = ['de', 'en'];
|
||||
@Output() public submitData: EventEmitter<User> = new EventEmitter<User>();
|
||||
@Output() public submitData: EventEmitter<Profile.AsObject> = new EventEmitter<Profile.AsObject>();
|
||||
@Output() public changedLanguage: EventEmitter<string> = new EventEmitter<string>();
|
||||
|
||||
public profileForm!: FormGroup;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<app-card title="{{ 'USER.EXTERNALIDP.TITLE' | translate }}" description="{{ 'USER.EXTERNALIDP.DESC' | translate }}">
|
||||
<cnsl-card title="{{ 'USER.EXTERNALIDP.TITLE' | translate }}" description="{{ 'USER.EXTERNALIDP.DESC' | translate }}">
|
||||
<button card-actions mat-icon-button (click)="refreshPage()" class="icon-button" matTooltip="{{'ACTIONS.REFRESH' | translate}}">
|
||||
<mat-icon class="icon">refresh</mat-icon>
|
||||
</button>
|
||||
<app-refresh-table [hideRefresh]="true" [loading]="loading$ | async" [dataSize]="dataSource.data.length"
|
||||
<cnsl-refresh-table [hideRefresh]="true" [loading]="loading$ | async" [dataSize]="dataSource.data.length"
|
||||
[timestamp]="viewTimestamp" [selection]="selection">
|
||||
|
||||
<div class="table-wrapper">
|
||||
@@ -59,6 +59,5 @@
|
||||
<cnsl-paginator #paginator class="paginator" [timestamp]="viewTimestamp" [length]="totalResult || 0" [pageSize]="10"
|
||||
[pageSizeOptions]="[5, 10, 20]" (page)="changePage($event)"></cnsl-paginator>
|
||||
</div>
|
||||
|
||||
</app-refresh-table>
|
||||
</app-card>
|
||||
</cnsl-refresh-table>
|
||||
</cnsl-card>
|
||||
@@ -13,7 +13,7 @@ import { ManagementService } from '../../../../services/mgmt.service';
|
||||
import { ToastService } from '../../../../services/toast.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-external-idps',
|
||||
selector: 'cnsl-external-idps',
|
||||
templateUrl: './external-idps.component.html',
|
||||
styleUrls: ['./external-idps.component.scss'],
|
||||
})
|
||||
|
||||
+6
-6
@@ -1,12 +1,12 @@
|
||||
<app-detail-layout [backRouterLink]="[ '/users', user?.id]"
|
||||
title="{{user?.human?.displayName}} {{ 'USER.MEMBERSHIPS.TITLE' | translate }}"
|
||||
<cnsl-detail-layout [backRouterLink]="[ '/users', user?.id]"
|
||||
title="{{ 'USER.MEMBERSHIPS.TITLE' | translate }}"
|
||||
description="{{ 'USER.MEMBERSHIPS.DESCRIPTION' | translate }}">
|
||||
<cnsl-info-section class="context-wrapper">
|
||||
<span *ngIf="isMe">{{'USER.MEMBERSHIPS.USERCONTEXT' | translate}}</span>
|
||||
<span *ngIf="!isMe">{{'USER.MEMBERSHIPS.ORGCONTEXT' | translate}}</span>
|
||||
</cnsl-info-section>
|
||||
|
||||
<app-refresh-table class="refresh-table" (refreshed)="refreshPage()" [dataSize]="dataSource?.totalResult"
|
||||
<cnsl-refresh-table class="refresh-table" (refreshed)="refreshPage()" [dataSize]="dataSource?.totalResult ?? 0"
|
||||
[timestamp]="dataSource?.viewTimestamp" [selection]="selection" [loading]="dataSource?.loading$ | async">
|
||||
|
||||
<a actions color="primary" (click)="addMember()" color="primary" mat-raised-button>
|
||||
@@ -70,7 +70,7 @@
|
||||
<ng-container matColumnDef="actions" stickyEnd>
|
||||
<th mat-header-cell *matHeaderCellDef></th>
|
||||
<td mat-cell *matCellDef="let member">
|
||||
<button [disabled]="member.projectId && member.projectGrantId ? (['project.grant.member.delete:' + member.projectGrantId, 'project.grant.member.delete'] | hasRole | async) == false : member.projectId ? (['project.member.delete:' + member.projectGrantId, 'project.member.delete'] | hasRole | async) == false : member.orgId ? (['org.member.delete:' + member.orgId, 'org.member.delete'] | hasRole | async) == false : (['iam.member.delete'] | hasRole | async) == false"
|
||||
<button [disabled]="member.projectId && member.projectGrantId ? (['project.grant.member.delete:' + member.projectGrantId, 'project.grant.member.delete'] | hasRole | async) === false : member.projectId ? (['project.member.delete:' + member.projectGrantId, 'project.member.delete'] | hasRole | async) === false : member.orgId ? (['org.member.delete:' + member.orgId, 'org.member.delete'] | hasRole | async) === false : (['iam.member.delete'] | hasRole | async) === false"
|
||||
mat-icon-button color="warn" matTooltip="{{'ACTIONS.REMOVE' | translate}}"
|
||||
(click)="removeMembership(member)">
|
||||
<i class="las la-minus-circle"></i>
|
||||
@@ -86,5 +86,5 @@
|
||||
<cnsl-paginator class="paginator" #paginator [timestamp]="dataSource?.viewTimestamp" [pageSize]="50" [pageSizeOptions]="[25, 50, 100, 250]">
|
||||
</cnsl-paginator>
|
||||
</div>
|
||||
</app-refresh-table>
|
||||
</app-detail-layout>
|
||||
</cnsl-refresh-table>
|
||||
</cnsl-detail-layout>
|
||||
+1
-1
@@ -15,7 +15,7 @@ import { ToastService } from 'src/app/services/toast.service';
|
||||
import { MembershipDetailDataSource } from './membership-detail-datasource';
|
||||
|
||||
@Component({
|
||||
selector: 'app-membership-detail',
|
||||
selector: 'cnsl-membership-detail',
|
||||
templateUrl: './membership-detail.component.html',
|
||||
styleUrls: ['./membership-detail.component.scss'],
|
||||
})
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="people" *ngIf="memberships">
|
||||
<div class="img-list" [@cardAnimation]="totalResult">
|
||||
<mat-spinner class="spinner" diameter="20" *ngIf="loading"></mat-spinner>
|
||||
<ng-container *ngIf="totalResult < 8; else compact">
|
||||
<ng-container *ngIf="totalResult < 8 && memberships && memberships.length; else compact">
|
||||
<ng-container *ngFor="let membership of memberships; index as i">
|
||||
<div @animate class="avatar-circle" (click)="navigateToObject()"
|
||||
matTooltip="{{ membership.displayName }} | {{membership.rolesList?.join(' ')}}"
|
||||
|
||||
@@ -11,7 +11,7 @@ import { ManagementService } from 'src/app/services/mgmt.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-memberships',
|
||||
selector: 'cnsl-memberships',
|
||||
templateUrl: './memberships.component.html',
|
||||
styleUrls: ['./memberships.component.scss'],
|
||||
animations: [
|
||||
@@ -33,7 +33,7 @@ import { ToastService } from 'src/app/services/toast.service';
|
||||
})
|
||||
export class MembershipsComponent implements OnInit {
|
||||
public loading: boolean = false;
|
||||
public memberships!: Membership.AsObject[] | UserGrant.AsObject[];
|
||||
public memberships: Membership.AsObject[] = [];
|
||||
public totalResult: number = 0;
|
||||
|
||||
@Input() public auth: boolean = false;
|
||||
@@ -181,7 +181,7 @@ export class MembershipsComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
getColor(type: Membership.AsObject[] | UserGrant.AsObject[]): string {
|
||||
getColor(type: Membership.AsObject | UserGrant.AsObject): string {
|
||||
const gen = type.toString();
|
||||
const colors = [
|
||||
'rgb(201, 115, 88)',
|
||||
@@ -194,9 +194,9 @@ export class MembershipsComponent implements OnInit {
|
||||
return colors[hash];
|
||||
}
|
||||
for (let i = 0; i < gen.length; i++) {
|
||||
// tslint:disable-next-line: no-bitwise
|
||||
// eslint-disable-next-line no-bitwise
|
||||
hash = gen.charCodeAt(i) + ((hash << 5) - hash);
|
||||
// tslint:disable-next-line: no-bitwise
|
||||
// eslint-disable-next-line no-bitwise
|
||||
hash = hash & hash;
|
||||
}
|
||||
hash = ((hash % colors.length) + colors.length) % colors.length;
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-metadata-dialog',
|
||||
selector: 'cnsl-metadata-dialog',
|
||||
templateUrl: './metadata-dialog.component.html',
|
||||
styleUrls: ['./metadata-dialog.component.scss'],
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<app-card class="metadata-details" title="{{ 'USER.METADATA.TITLE' | translate }}">
|
||||
<cnsl-card class="metadata-details" title="{{ 'USER.METADATA.TITLE' | translate }}">
|
||||
<div card-actions class="actions">
|
||||
<mat-spinner class="spinner" diameter="20" *ngIf="loading"></mat-spinner>
|
||||
<button mat-raised-button color="primary" class="edit" (click)="editMetadata()">{{'ACTIONS.EDIT' | translate}}</button>
|
||||
@@ -18,4 +18,4 @@
|
||||
<ng-template #emptyList>
|
||||
<p class="empty-desc">{{'USER.METADATA.EMPTY' | translate}}</p>
|
||||
</ng-template>
|
||||
</app-card>
|
||||
</cnsl-card>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<app-detail-layout [backRouterLink]="userId ? [ '/users',userId] : [ '/users/me']"
|
||||
<cnsl-detail-layout [backRouterLink]="userId ? [ '/users',userId] : [ '/users/me']"
|
||||
title="{{ 'USER.PASSWORD.TITLE' | translate }}" description="{{ 'USER.PASSWORD.DESCRIPTION' | translate }}">
|
||||
|
||||
<ng-container *ngIf="userId; else authUser">
|
||||
<div class="validation" *ngIf="this.policy">
|
||||
<app-password-complexity-view [policy]="this.policy" [password]="password">
|
||||
</app-password-complexity-view>
|
||||
<cnsl-password-complexity-view [policy]="this.policy" [password]="password">
|
||||
</cnsl-password-complexity-view>
|
||||
</div>
|
||||
|
||||
<form *ngIf="passwordForm" autocomplete="new-password" [formGroup]="passwordForm"
|
||||
@@ -47,8 +47,8 @@
|
||||
</cnsl-form-field>
|
||||
|
||||
<div class="validation between" *ngIf="this.policy">
|
||||
<app-password-complexity-view [policy]="this.policy" [password]="newPassword">
|
||||
</app-password-complexity-view>
|
||||
<cnsl-password-complexity-view [policy]="this.policy" [password]="newPassword">
|
||||
</cnsl-password-complexity-view>
|
||||
</div>
|
||||
|
||||
<cnsl-form-field class="formfield" appearance="outline">
|
||||
@@ -73,4 +73,4 @@
|
||||
color="primary">{{ 'USER.PASSWORD.RESET' | translate }}</button>
|
||||
</form>
|
||||
</ng-template>
|
||||
</app-detail-layout>
|
||||
</cnsl-detail-layout>
|
||||
@@ -9,131 +9,131 @@ import { ManagementService } from 'src/app/services/mgmt.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
function passwordConfirmValidator(c: AbstractControl): any {
|
||||
if (!c.parent || !c) {
|
||||
return;
|
||||
}
|
||||
const pwd = c.parent.get('password');
|
||||
const cpwd = c.parent.get('confirmPassword');
|
||||
if (!c.parent || !c) {
|
||||
return;
|
||||
}
|
||||
const pwd = c.parent.get('password');
|
||||
const cpwd = c.parent.get('confirmPassword');
|
||||
|
||||
if (!pwd || !cpwd) {
|
||||
return;
|
||||
}
|
||||
if (pwd.value !== cpwd.value) {
|
||||
return { invalid: true, notequal: 'Password is not equal' };
|
||||
}
|
||||
if (!pwd || !cpwd) {
|
||||
return;
|
||||
}
|
||||
if (pwd.value !== cpwd.value) {
|
||||
return { invalid: true, notequal: 'Password is not equal' };
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-password',
|
||||
templateUrl: './password.component.html',
|
||||
styleUrls: ['./password.component.scss'],
|
||||
selector: 'cnsl-password',
|
||||
templateUrl: './password.component.html',
|
||||
styleUrls: ['./password.component.scss'],
|
||||
})
|
||||
export class PasswordComponent implements OnDestroy {
|
||||
userId: string = '';
|
||||
userId: string = '';
|
||||
|
||||
public policy!: PasswordComplexityPolicy.AsObject;
|
||||
public passwordForm!: FormGroup;
|
||||
public policy!: PasswordComplexityPolicy.AsObject;
|
||||
public passwordForm!: FormGroup;
|
||||
|
||||
private formSub: Subscription = new Subscription();
|
||||
private formSub: Subscription = new Subscription();
|
||||
|
||||
constructor(
|
||||
activatedRoute: ActivatedRoute,
|
||||
private fb: FormBuilder,
|
||||
private authService: GrpcAuthService,
|
||||
private mgmtUserService: ManagementService,
|
||||
private toast: ToastService,
|
||||
) {
|
||||
activatedRoute.params.subscribe(data => {
|
||||
const { id } = data;
|
||||
if (id) {
|
||||
this.userId = id;
|
||||
}
|
||||
constructor(
|
||||
activatedRoute: ActivatedRoute,
|
||||
private fb: FormBuilder,
|
||||
private authService: GrpcAuthService,
|
||||
private mgmtUserService: ManagementService,
|
||||
private toast: ToastService,
|
||||
) {
|
||||
activatedRoute.params.subscribe(data => {
|
||||
const { id } = data;
|
||||
if (id) {
|
||||
this.userId = id;
|
||||
}
|
||||
|
||||
const validators: Validators[] = [Validators.required];
|
||||
this.authService.getMyPasswordComplexityPolicy().then(resp => {
|
||||
if (resp.policy) {
|
||||
this.policy = resp.policy;
|
||||
}
|
||||
if (this.policy.minLength) {
|
||||
validators.push(Validators.minLength(this.policy.minLength));
|
||||
}
|
||||
if (this.policy.hasLowercase) {
|
||||
validators.push(lowerCaseValidator);
|
||||
}
|
||||
if (this.policy.hasUppercase) {
|
||||
validators.push(upperCaseValidator);
|
||||
}
|
||||
if (this.policy.hasNumber) {
|
||||
validators.push(numberValidator);
|
||||
}
|
||||
if (this.policy.hasSymbol) {
|
||||
validators.push(symbolValidator);
|
||||
}
|
||||
const validators: Validators[] = [Validators.required];
|
||||
this.authService.getMyPasswordComplexityPolicy().then(resp => {
|
||||
if (resp.policy) {
|
||||
this.policy = resp.policy;
|
||||
}
|
||||
if (this.policy.minLength) {
|
||||
validators.push(Validators.minLength(this.policy.minLength));
|
||||
}
|
||||
if (this.policy.hasLowercase) {
|
||||
validators.push(lowerCaseValidator);
|
||||
}
|
||||
if (this.policy.hasUppercase) {
|
||||
validators.push(upperCaseValidator);
|
||||
}
|
||||
if (this.policy.hasNumber) {
|
||||
validators.push(numberValidator);
|
||||
}
|
||||
if (this.policy.hasSymbol) {
|
||||
validators.push(symbolValidator);
|
||||
}
|
||||
|
||||
this.setupForm(validators);
|
||||
}).catch(error => {
|
||||
this.setupForm(validators);
|
||||
});
|
||||
this.setupForm(validators);
|
||||
}).catch(error => {
|
||||
this.setupForm(validators);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public ngOnDestroy(): void {
|
||||
this.formSub.unsubscribe();
|
||||
}
|
||||
|
||||
setupForm(validators: Validators[]): void {
|
||||
if (this.userId) {
|
||||
this.passwordForm = this.fb.group({
|
||||
password: ['', validators],
|
||||
confirmPassword: ['', [...validators, passwordConfirmValidator]],
|
||||
});
|
||||
} else {
|
||||
this.passwordForm = this.fb.group({
|
||||
currentPassword: ['', Validators.required],
|
||||
newPassword: ['', validators],
|
||||
confirmPassword: ['', [...validators, passwordConfirmValidator]],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public setInitialPassword(userId: string): void {
|
||||
if (this.passwordForm.valid && this.password && this.password.value) {
|
||||
this.mgmtUserService.setHumanInitialPassword(userId, this.password.value).then((data: any) => {
|
||||
this.toast.showInfo('USER.TOAST.INITIALPASSWORDSET', true);
|
||||
window.history.back();
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public setPassword(): void {
|
||||
if (this.passwordForm.valid && this.currentPassword &&
|
||||
this.currentPassword.value &&
|
||||
this.newPassword && this.newPassword.value && this.newPassword.valid) {
|
||||
this.authService.updateMyPassword(this.currentPassword.value, this.newPassword.value)
|
||||
.then((data: any) => {
|
||||
this.toast.showInfo('USER.TOAST.PASSWORDCHANGED', true);
|
||||
window.history.back();
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public ngOnDestroy(): void {
|
||||
this.formSub.unsubscribe();
|
||||
}
|
||||
public get password(): AbstractControl | null {
|
||||
return this.passwordForm.get('password');
|
||||
}
|
||||
|
||||
setupForm(validators: Validators[]): void {
|
||||
if (this.userId) {
|
||||
this.passwordForm = this.fb.group({
|
||||
password: ['', validators],
|
||||
confirmPassword: ['', [...validators, passwordConfirmValidator]],
|
||||
});
|
||||
} else {
|
||||
this.passwordForm = this.fb.group({
|
||||
currentPassword: ['', Validators.required],
|
||||
newPassword: ['', validators],
|
||||
confirmPassword: ['', [...validators, passwordConfirmValidator]],
|
||||
});
|
||||
}
|
||||
}
|
||||
public get newPassword(): AbstractControl | null {
|
||||
return this.passwordForm.get('newPassword');
|
||||
}
|
||||
|
||||
public setInitialPassword(userId: string): void {
|
||||
if (this.passwordForm.valid && this.password && this.password.value) {
|
||||
this.mgmtUserService.setHumanInitialPassword(userId, this.password.value).then((data: any) => {
|
||||
this.toast.showInfo('USER.TOAST.INITIALPASSWORDSET', true);
|
||||
window.history.back();
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
public get currentPassword(): AbstractControl | null {
|
||||
return this.passwordForm.get('currentPassword');
|
||||
}
|
||||
|
||||
public setPassword(): void {
|
||||
if (this.passwordForm.valid && this.currentPassword &&
|
||||
this.currentPassword.value &&
|
||||
this.newPassword && this.newPassword.value && this.newPassword.valid) {
|
||||
this.authService.updateMyPassword(this.currentPassword.value, this.newPassword.value)
|
||||
.then((data: any) => {
|
||||
this.toast.showInfo('USER.TOAST.PASSWORDCHANGED', true);
|
||||
window.history.back();
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public get password(): AbstractControl | null {
|
||||
return this.passwordForm.get('password');
|
||||
}
|
||||
|
||||
public get newPassword(): AbstractControl | null {
|
||||
return this.passwordForm.get('newPassword');
|
||||
}
|
||||
|
||||
public get currentPassword(): AbstractControl | null {
|
||||
return this.passwordForm.get('currentPassword');
|
||||
}
|
||||
|
||||
public get confirmPassword(): AbstractControl | null {
|
||||
return this.passwordForm.get('confirmPassword');
|
||||
}
|
||||
public get confirmPassword(): AbstractControl | null {
|
||||
return this.passwordForm.get('confirmPassword');
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -1,10 +1,10 @@
|
||||
<app-card title="{{'USER.PASSWORDLESS.TITLE' | translate}}"
|
||||
<cnsl-card title="{{'USER.PASSWORDLESS.TITLE' | translate}}"
|
||||
description="{{'USER.PASSWORDLESS.DESCRIPTION' | translate}}">
|
||||
<button card-actions mat-icon-button (click)="getPasswordless()" class="icon-button" matTooltip="{{'ACTIONS.REFRESH' | translate}}">
|
||||
<mat-icon class="icon">refresh</mat-icon>
|
||||
</button>
|
||||
<app-refresh-table [hideRefresh]="true" [loading]="loading$ | async"
|
||||
[dataSize]="dataSource?.data?.length">
|
||||
<cnsl-refresh-table [hideRefresh]="true" [loading]="loading$ | async"
|
||||
[dataSize]="dataSource?.data?.length ?? 0">
|
||||
<table class="table" mat-table [dataSource]="dataSource">
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'USER.PASSWORDLESS.NAME' | translate }} </th>
|
||||
@@ -37,7 +37,7 @@
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
</app-refresh-table>
|
||||
</cnsl-refresh-table>
|
||||
<div class="add-row">
|
||||
<button class="button" (click)="sendPasswordlessRegistration()" mat-raised-button color="primary"
|
||||
matTooltip="{{'ACTIONS.NEW' | translate}}">
|
||||
@@ -50,4 +50,4 @@
|
||||
<mat-spinner diameter="50"></mat-spinner>
|
||||
</div>
|
||||
</div>
|
||||
</app-card>
|
||||
</cnsl-card>
|
||||
+2
-2
@@ -19,12 +19,12 @@ export interface WebAuthNOptions {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-passwordless',
|
||||
selector: 'cnsl-passwordless',
|
||||
templateUrl: './passwordless.component.html',
|
||||
styleUrls: ['./passwordless.component.scss'],
|
||||
})
|
||||
export class PasswordlessComponent implements OnInit, OnDestroy {
|
||||
@Input() private user!: User.AsObject;
|
||||
@Input() public user!: User.AsObject;
|
||||
public displayedColumns: string[] = ['name', 'state', 'actions'];
|
||||
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<div class="max-width-container" *ngIf="error">{{error}}</div>
|
||||
<div *ngIf="error" class="max-width-container">
|
||||
<p>{{error}}</p>
|
||||
</div>
|
||||
|
||||
<app-meta-layout *ngIf="user && (['user.write$','user.write:' + user.id] | hasRole) as canWrite$">
|
||||
<cnsl-meta-layout *ngIf="user && (['user.write$','user.write:' + user.id] | hasRole) as canWrite$">
|
||||
<div class="max-width-container">
|
||||
<div class="head">
|
||||
<a (click)="navigateBack()" mat-icon-button>
|
||||
@@ -12,7 +14,7 @@
|
||||
</div>
|
||||
|
||||
<span class="fill-space"></span>
|
||||
<ng-template appHasRole [appHasRole]="['user.write$', 'user.write:'+user?.id]">
|
||||
<ng-template cnslHasRole [hasRole]="['user.write$', 'user.write:'+user?.id]">
|
||||
<button class="actions-trigger" mat-raised-button color="primary" [matMenuTriggerFor]="actions">
|
||||
<span>{{'ACTIONS.ACTIONS' | translate}}</span>
|
||||
<mat-icon class="icon">keyboard_arrow_down</mat-icon>
|
||||
@@ -24,9 +26,9 @@
|
||||
<button mat-menu-item *ngIf="user?.state !== UserState.USER_STATE_INACTIVE"
|
||||
(click)="changeState(UserState.USER_STATE_INACTIVE)">{{'USER.PAGES.DEACTIVATE' |
|
||||
translate}}</button>
|
||||
<button mat-menu-item *ngIf="user?.state == UserState.USER_STATE_INACTIVE"
|
||||
<button mat-menu-item *ngIf="user?.state === UserState.USER_STATE_INACTIVE"
|
||||
(click)="changeState(UserState.USER_STATE_ACTIVE)">{{'USER.PAGES.REACTIVATE' | translate}}</button>
|
||||
<ng-template appHasRole [appHasRole]="['user.delete$', 'user.delete:'+user?.id]">
|
||||
<ng-template cnslHasRole [hasRole]="['user.delete$', 'user.delete:'+user?.id]">
|
||||
<button mat-menu-item matTooltip="{{'USER.PAGES.DELETE' | translate}}" (click)="deleteUser()"><span
|
||||
[style.color]="'var(--warn)'">{{'USER.PAGES.DELETE' | translate}}</span></button>
|
||||
</ng-template>
|
||||
@@ -36,67 +38,68 @@
|
||||
|
||||
<mat-progress-bar *ngIf="loading" color="primary" mode="indeterminate"></mat-progress-bar>
|
||||
|
||||
<cnsl-info-section class="locked" *ngIf="user?.state === UserState.USER_STATE_LOCKED" type="WARN">
|
||||
<cnsl-info-section class="locked" *ngIf="user?.state === UserState.USER_STATE_LOCKED" [type]="InfoSectionType.WARN">
|
||||
{{'USER.PAGES.LOCKEDDESCRIPTION' | translate}}</cnsl-info-section>
|
||||
<span *ngIf="!loading && !user">{{ 'USER.PAGES.NOUSER' | translate }}</span>
|
||||
|
||||
<cnsl-info-row *ngIf="user" [user]="user"></cnsl-info-row>
|
||||
|
||||
<ng-template appHasRole [appHasRole]="['user.read$', 'user.read:'+user?.id]">
|
||||
<app-card *ngIf="user.human" title="{{ 'USER.PROFILE.TITLE' | translate }}">
|
||||
<app-detail-form [preferredLoginName]="user.preferredLoginName" [disabled]="(canWrite$ | async) == false"
|
||||
<ng-template cnslHasRole [hasRole]="['user.read$', 'user.read:'+user?.id]">
|
||||
<cnsl-card *ngIf="user.human" title="{{ 'USER.PROFILE.TITLE' | translate }}">
|
||||
<cnsl-detail-form [preferredLoginName]="user.preferredLoginName" [disabled]="(canWrite$ | async) === false"
|
||||
[genders]="genders" [languages]="languages" [username]="user.userName" [user]="user.human"
|
||||
(submitData)="saveProfile($event)">
|
||||
</app-detail-form>
|
||||
</app-card>
|
||||
</cnsl-detail-form>
|
||||
</cnsl-card>
|
||||
|
||||
<app-card *ngIf="user.human" title="{{ 'USER.LOGINMETHODS.TITLE' | translate }}"
|
||||
<cnsl-card *ngIf="user.human" title="{{ 'USER.LOGINMETHODS.TITLE' | translate }}"
|
||||
description="{{ 'USER.LOGINMETHODS.DESCRIPTION' | translate }}">
|
||||
<button card-actions class="icon-button" mat-icon-button (click)="refreshUser()"
|
||||
matTooltip="{{'ACTIONS.REFRESH' | translate}}">
|
||||
<mat-icon class="icon">refresh</mat-icon>
|
||||
</button>
|
||||
<app-contact disablePhoneCode="true" [canWrite]="(['user.write:' + user?.id, 'user.write$'] | hasRole | async)"
|
||||
*ngIf="user?.human" [human]="user.human" (editType)="openEditDialog($event)" (deletedPhone)="deletePhone()"
|
||||
<cnsl-contact [disablePhoneCode]="true"
|
||||
[canWrite]="(['user.write:' + user?.id, 'user.write$'] | hasRole | async)" *ngIf="user?.human"
|
||||
[human]="user.human" (editType)="openEditDialog($event)" (deletedPhone)="deletePhone()"
|
||||
(resendEmailVerification)="resendEmailVerification()" (resendPhoneVerification)="resendPhoneVerification()">
|
||||
<button pwdAction [disabled]="(canWrite$ | async) == false" (click)="sendSetPasswordNotification()"
|
||||
<button pwdAction [disabled]="(canWrite$ | async) === false" (click)="sendSetPasswordNotification()"
|
||||
mat-stroked-button color="primary" *ngIf="user.state === UserState.USER_STATE_INITIAL">{{
|
||||
'USER.PASSWORD.RESENDNOTIFICATION' | translate }}</button>
|
||||
<button emailAction class="resendemail" *ngIf="user.state == UserState.USER_STATE_INITIAL" mat-stroked-button
|
||||
<button emailAction class="resendemail" *ngIf="user.state === UserState.USER_STATE_INITIAL" mat-stroked-button
|
||||
color="primary" (click)="resendInitEmail()">{{'USER.RESENDINITIALEMAIL' |
|
||||
translate}}</button>
|
||||
</app-contact>
|
||||
</app-card>
|
||||
</cnsl-contact>
|
||||
</cnsl-card>
|
||||
|
||||
<app-external-idps *ngIf="user && user.human && user.id" [userId]="user.id" [service]="mgmtUserService">
|
||||
</app-external-idps>
|
||||
<cnsl-external-idps *ngIf="user && user.human && user.id" [userId]="user.id" [service]="mgmtUserService">
|
||||
</cnsl-external-idps>
|
||||
|
||||
<app-card *ngIf="user.machine" title="{{ 'USER.MACHINE.TITLE' | translate }}">
|
||||
<app-detail-form-machine [disabled]="(canWrite$ | async) == false" [username]="user.userName"
|
||||
<cnsl-card *ngIf="user.machine" title="{{ 'USER.MACHINE.TITLE' | translate }}">
|
||||
<cnsl-detail-form-machine [disabled]="(canWrite$ | async) === false" [username]="user.userName"
|
||||
[user]="user.machine" (submitData)="saveMachine($event)">
|
||||
</app-detail-form-machine>
|
||||
</app-card>
|
||||
</cnsl-detail-form-machine>
|
||||
</cnsl-card>
|
||||
|
||||
<app-card *ngIf="user.machine && user.id" title="{{ 'USER.MACHINE.KEYSTITLE' | translate }}"
|
||||
<cnsl-card *ngIf="user.machine && user.id" title="{{ 'USER.MACHINE.KEYSTITLE' | translate }}"
|
||||
description="{{ 'USER.MACHINE.KEYSDESC' | translate }}">
|
||||
<app-machine-keys [userId]="user.id"></app-machine-keys>
|
||||
</app-card>
|
||||
<cnsl-machine-keys [userId]="user.id"></cnsl-machine-keys>
|
||||
</cnsl-card>
|
||||
</ng-template>
|
||||
|
||||
<app-passwordless *ngIf="user && user.human" [user]="user"></app-passwordless>
|
||||
<cnsl-passwordless *ngIf="user && user.human" [user]="user"></cnsl-passwordless>
|
||||
|
||||
<app-user-mfa *ngIf="user && user.human" [user]="user"></app-user-mfa>
|
||||
<cnsl-user-mfa *ngIf="user && user.human" [user]="user"></cnsl-user-mfa>
|
||||
|
||||
<app-card *ngIf="user?.id" title="{{ 'GRANTS.USER.TITLE' | translate }}"
|
||||
<cnsl-card *ngIf="user?.id" title="{{ 'GRANTS.USER.TITLE' | translate }}"
|
||||
description="{{'GRANTS.USER.DESCRIPTION' | translate }}">
|
||||
<app-user-grants [userId]="user.id" [context]="USERGRANTCONTEXT"
|
||||
<cnsl-user-grants [userId]="user.id" [context]="USERGRANTCONTEXT"
|
||||
[displayedColumns]="['select', 'projectId', 'dates', 'roleNamesList']"
|
||||
[disableWrite]="((['user.grant.write$'] | hasRole) | async) == false"
|
||||
[disableDelete]="((['user.grant.delete$'] | hasRole) | async) == false">
|
||||
</app-user-grants>
|
||||
</app-card>
|
||||
[disableWrite]="((['user.grant.write$'] | hasRole) | async) === false"
|
||||
[disableDelete]="((['user.grant.delete$'] | hasRole) | async) === false">
|
||||
</cnsl-user-grants>
|
||||
</cnsl-card>
|
||||
|
||||
<ng-template appHasFeature [appHasFeature]="['metadata.user']">
|
||||
<ng-template cnslHasFeature [hasFeature]="['metadata.user']">
|
||||
<cnsl-metadata *ngIf="user" [userId]="user.id"></cnsl-metadata>
|
||||
</ng-template>
|
||||
</div>
|
||||
@@ -119,18 +122,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-tab-group mat-stretch-tabs class="tab-group" disablePagination="true">
|
||||
<mat-tab-group mat-stretch-tabs class="tab-group" [disablePagination]="true">
|
||||
<mat-tab label="Details">
|
||||
<div class="side-padding">
|
||||
<ng-template appHasRole [appHasRole]="['user.membership.read']">
|
||||
<app-memberships [user]="user" [disabled]="(canWrite$ | async) == false"></app-memberships>
|
||||
<ng-template cnslHasRole [hasRole]="['user.membership.read']">
|
||||
<cnsl-memberships [user]="user" [disabled]="(canWrite$ | async) === false"></cnsl-memberships>
|
||||
</ng-template>
|
||||
</div>
|
||||
</mat-tab>
|
||||
<mat-tab label="{{ 'CHANGES.PROJECT.TITLE' | translate }}" class="meta-flex-col">
|
||||
<app-changes class="changes" [refresh]="refreshChanges$" [changeType]="ChangeType.USER" [id]="user.id">
|
||||
</app-changes>
|
||||
<cnsl-changes class="changes" [refresh]="refreshChanges$" [changeType]="ChangeType.USER" [id]="user.id">
|
||||
</cnsl-changes>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</div>
|
||||
</app-meta-layout>
|
||||
</cnsl-meta-layout>
|
||||
@@ -5,6 +5,7 @@ import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { ChangeType } from 'src/app/modules/changes/changes.component';
|
||||
import { InfoSectionType } from 'src/app/modules/info-section/info-section.component';
|
||||
import { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource';
|
||||
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
|
||||
import { SendHumanResetPasswordNotificationRequest, UnlockUserRequest } from 'src/app/proto/generated/zitadel/management_pb';
|
||||
@@ -17,7 +18,7 @@ import { EditDialogComponent, EditDialogType } from '../auth-user-detail/edit-di
|
||||
import { ResendEmailDialogComponent } from '../auth-user-detail/resend-email-dialog/resend-email-dialog.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-detail',
|
||||
selector: 'cnsl-user-detail',
|
||||
templateUrl: './user-detail.component.html',
|
||||
styleUrls: ['./user-detail.component.scss'],
|
||||
})
|
||||
@@ -36,6 +37,7 @@ export class UserDetailComponent implements OnInit {
|
||||
|
||||
public EditDialogType: any = EditDialogType;
|
||||
public refreshChanges$: EventEmitter<void> = new EventEmitter();
|
||||
public InfoSectionType: any = InfoSectionType;
|
||||
|
||||
public error: string = '';
|
||||
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
<app-card title="{{'USER.MFA.TITLE' | translate}}" description="{{'USER.MFA.DESCRIPTION' | translate}}">
|
||||
<cnsl-card title="{{'USER.MFA.TITLE' | translate}}" description="{{'USER.MFA.DESCRIPTION' | translate}}">
|
||||
<button card-actions mat-icon-button (click)="getMFAs()" class="icon-button" matTooltip="{{'ACTIONS.REFRESH' | translate}}">
|
||||
<mat-icon class="icon">refresh</mat-icon>
|
||||
</button>
|
||||
<app-refresh-table [hideRefresh]="true" [loading]="loading$ | async" (refreshed)="getMFAs()" [dataSize]="dataSource?.data?.length">
|
||||
<cnsl-refresh-table [hideRefresh]="true" [loading]="loading$ | async" (refreshed)="getMFAs()" [dataSize]="dataSource?.data?.length ?? 0">
|
||||
<table class="table" mat-table [dataSource]="dataSource">
|
||||
<ng-container matColumnDef="type">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'USER.MFA.TABLETYPE' | translate }} </th>
|
||||
@@ -44,10 +44,10 @@
|
||||
<tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns;">
|
||||
</tr>
|
||||
</table>
|
||||
</app-refresh-table>
|
||||
</cnsl-refresh-table>
|
||||
<div class="table-wrapper">
|
||||
<div class="spinner-container" *ngIf="loading$ | async">
|
||||
<mat-spinner diameter="50"></mat-spinner>
|
||||
</div>
|
||||
</div>
|
||||
</app-card>
|
||||
</cnsl-card>
|
||||
+69
-69
@@ -10,88 +10,88 @@ import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
|
||||
export interface MFAItem {
|
||||
name: string;
|
||||
verified: boolean;
|
||||
name: string;
|
||||
verified: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-mfa',
|
||||
templateUrl: './user-mfa.component.html',
|
||||
styleUrls: ['./user-mfa.component.scss'],
|
||||
selector: 'cnsl-user-mfa',
|
||||
templateUrl: './user-mfa.component.html',
|
||||
styleUrls: ['./user-mfa.component.scss'],
|
||||
})
|
||||
export class UserMfaComponent implements OnInit, OnDestroy {
|
||||
public displayedColumns: string[] = ['type', 'name', 'state', 'actions'];
|
||||
@Input() private user!: User.AsObject;
|
||||
public mfaSubject: BehaviorSubject<AuthFactor.AsObject[]> = new BehaviorSubject<AuthFactor.AsObject[]>([]);
|
||||
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
|
||||
public displayedColumns: string[] = ['type', 'name', 'state', 'actions'];
|
||||
@Input() public user!: User.AsObject;
|
||||
public mfaSubject: BehaviorSubject<AuthFactor.AsObject[]> = new BehaviorSubject<AuthFactor.AsObject[]>([]);
|
||||
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
|
||||
|
||||
@ViewChild(MatTable) public table!: MatTable<AuthFactor.AsObject>;
|
||||
@ViewChild(MatSort) public sort!: MatSort;
|
||||
public dataSource!: MatTableDataSource<AuthFactor.AsObject>;
|
||||
@ViewChild(MatTable) public table!: MatTable<AuthFactor.AsObject>;
|
||||
@ViewChild(MatSort) public sort!: MatSort;
|
||||
public dataSource!: MatTableDataSource<AuthFactor.AsObject>;
|
||||
|
||||
public AuthFactorState: any = AuthFactorState;
|
||||
public AuthFactorState: any = AuthFactorState;
|
||||
|
||||
public error: string = '';
|
||||
constructor(private mgmtUserService: ManagementService, private dialog: MatDialog, private toast: ToastService) { }
|
||||
public error: string = '';
|
||||
constructor(private mgmtUserService: ManagementService, private dialog: MatDialog, private toast: ToastService) { }
|
||||
|
||||
public ngOnInit(): void {
|
||||
this.getMFAs();
|
||||
}
|
||||
public ngOnInit(): void {
|
||||
this.getMFAs();
|
||||
}
|
||||
|
||||
public ngOnDestroy(): void {
|
||||
this.mfaSubject.complete();
|
||||
this.loadingSubject.complete();
|
||||
}
|
||||
public ngOnDestroy(): void {
|
||||
this.mfaSubject.complete();
|
||||
this.loadingSubject.complete();
|
||||
}
|
||||
|
||||
public getMFAs(): void {
|
||||
this.mgmtUserService.listHumanMultiFactors(this.user.id).then(mfas => {
|
||||
this.dataSource = new MatTableDataSource(mfas.resultList);
|
||||
this.dataSource.sort = this.sort;
|
||||
}).catch(error => {
|
||||
this.error = error.message;
|
||||
});
|
||||
}
|
||||
public getMFAs(): void {
|
||||
this.mgmtUserService.listHumanMultiFactors(this.user.id).then(mfas => {
|
||||
this.dataSource = new MatTableDataSource(mfas.resultList);
|
||||
this.dataSource.sort = this.sort;
|
||||
}).catch(error => {
|
||||
this.error = error.message;
|
||||
});
|
||||
}
|
||||
|
||||
public deleteMFA(factor: AuthFactor.AsObject): void {
|
||||
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
||||
data: {
|
||||
confirmKey: 'ACTIONS.DELETE',
|
||||
cancelKey: 'ACTIONS.CANCEL',
|
||||
titleKey: 'USER.MFA.DIALOG.MFA_DELETE_TITLE',
|
||||
descriptionKey: 'USER.MFA.DIALOG.MFA_DELETE_DESCRIPTION',
|
||||
},
|
||||
width: '400px',
|
||||
});
|
||||
public deleteMFA(factor: AuthFactor.AsObject): void {
|
||||
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
||||
data: {
|
||||
confirmKey: 'ACTIONS.DELETE',
|
||||
cancelKey: 'ACTIONS.CANCEL',
|
||||
titleKey: 'USER.MFA.DIALOG.MFA_DELETE_TITLE',
|
||||
descriptionKey: 'USER.MFA.DIALOG.MFA_DELETE_DESCRIPTION',
|
||||
},
|
||||
width: '400px',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(resp => {
|
||||
if (resp) {
|
||||
if (factor.otp) {
|
||||
this.mgmtUserService.removeHumanMultiFactorOTP(this.user.id).then(() => {
|
||||
this.toast.showInfo('USER.TOAST.OTPREMOVED', true);
|
||||
dialogRef.afterClosed().subscribe(resp => {
|
||||
if (resp) {
|
||||
if (factor.otp) {
|
||||
this.mgmtUserService.removeHumanMultiFactorOTP(this.user.id).then(() => {
|
||||
this.toast.showInfo('USER.TOAST.OTPREMOVED', true);
|
||||
|
||||
const index = this.dataSource.data.findIndex(mfa => !!mfa.otp);
|
||||
if (index > -1) {
|
||||
this.dataSource.data.splice(index, 1);
|
||||
}
|
||||
this.getMFAs();
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
} else if (factor.u2f) {
|
||||
this.mgmtUserService.removeHumanAuthFactorU2F(this.user.id, factor.u2f.id).then(() => {
|
||||
this.toast.showInfo('USER.TOAST.U2FREMOVED', true);
|
||||
|
||||
const index = this.dataSource.data.findIndex(mfa => !!mfa.u2f);
|
||||
if (index > -1) {
|
||||
this.dataSource.data.splice(index, 1);
|
||||
}
|
||||
this.getMFAs();
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
const index = this.dataSource.data.findIndex(mfa => !!mfa.otp);
|
||||
if (index > -1) {
|
||||
this.dataSource.data.splice(index, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
this.getMFAs();
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
} else if (factor.u2f) {
|
||||
this.mgmtUserService.removeHumanAuthFactorU2F(this.user.id, factor.u2f.id).then(() => {
|
||||
this.toast.showInfo('USER.TOAST.U2FREMOVED', true);
|
||||
|
||||
const index = this.dataSource.data.findIndex(mfa => !!mfa.u2f);
|
||||
if (index > -1) {
|
||||
this.dataSource.data.splice(index, 1);
|
||||
}
|
||||
this.getMFAs();
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,17 +3,17 @@
|
||||
<h1>{{ 'USER.PAGES.LIST' | translate }}</h1>
|
||||
<p class="sub">{{ 'USER.PAGES.DESCRIPTION' | translate }}</p>
|
||||
|
||||
<app-user-table [type]="Type.TYPE_HUMAN" [disabled]="(['user.write$'] | hasRole | async) == false">
|
||||
</app-user-table>
|
||||
<cnsl-user-table [type]="Type.TYPE_HUMAN" [disabled]="(['user.write$'] | hasRole | async) === false">
|
||||
</cnsl-user-table>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchCase="Type.TYPE_MACHINE">
|
||||
<h1>{{ 'USER.PAGES.LISTMACHINE' | translate }}</h1>
|
||||
<p class="sub">{{ 'USER.PAGES.DESCRIPTIONMACHINE' | translate }}</p>
|
||||
|
||||
<app-user-table [type]="Type.TYPE_MACHINE"
|
||||
<cnsl-user-table [type]="Type.TYPE_MACHINE"
|
||||
[displayedColumns]="['select','name', 'username', 'description','state', 'actions']"
|
||||
[disabled]="(['user.write$'] | hasRole | async) == false">
|
||||
</app-user-table>
|
||||
[disabled]="(['user.write$'] | hasRole | async) === false">
|
||||
</cnsl-user-table>
|
||||
</ng-container>
|
||||
</div>
|
||||
@@ -5,18 +5,18 @@ import { take } from 'rxjs/operators';
|
||||
import { Type } from 'src/app/proto/generated/zitadel/user_pb';
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-list',
|
||||
templateUrl: './user-list.component.html',
|
||||
styleUrls: ['./user-list.component.scss'],
|
||||
selector: 'cnsl-user-list',
|
||||
templateUrl: './user-list.component.html',
|
||||
styleUrls: ['./user-list.component.scss'],
|
||||
})
|
||||
export class UserListComponent {
|
||||
public Type: any = Type;
|
||||
public type: Type = Type.TYPE_HUMAN;
|
||||
public Type: any = Type;
|
||||
public type: Type = Type.TYPE_HUMAN;
|
||||
|
||||
constructor(public translate: TranslateService, activatedRoute: ActivatedRoute) {
|
||||
activatedRoute.data.pipe(take(1)).subscribe(params => {
|
||||
const { type } = params;
|
||||
this.type = type;
|
||||
});
|
||||
}
|
||||
constructor(public translate: TranslateService, activatedRoute: ActivatedRoute) {
|
||||
activatedRoute.data.pipe(take(1)).subscribe(params => {
|
||||
const { type } = params;
|
||||
this.type = type;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<app-refresh-table [loading]="loading$ | async" (refreshed)="refreshPage()" [dataSize]="totalResult"
|
||||
<cnsl-refresh-table [loading]="loading$ | async" (refreshed)="refreshPage()" [dataSize]="totalResult"
|
||||
[timestamp]="viewTimestamp" [selection]="selection" [emitRefreshOnPreviousRoutes]="refreshOnPreviousRoutes">
|
||||
<cnsl-form-field @appearfade *ngIf="userSearchKey != undefined" actions class="filtername">
|
||||
<cnsl-form-field @appearfade *ngIf="userSearchKey !== undefined" actions class="filtername">
|
||||
<input class="filterinput" cnslInput (keyup)="applyFilter($event)"
|
||||
[placeholder]="('USER.TABLE.FILTER.' + userSearchKey.toString()) | translate" #input>
|
||||
</cnsl-form-field>
|
||||
|
||||
<ng-template appHasRole [appHasRole]="['user.write']" actions>
|
||||
<ng-template cnslHasRole [hasRole]="['user.write']" actions>
|
||||
<button (click)="deactivateSelectedUsers()" matTooltip="{{'USER.TABLE.DEACTIVATE' | translate}}"
|
||||
class="icon-button" mat-icon-button *ngIf="selection.hasValue()" [disabled]="disabled">
|
||||
<mat-icon svgIcon="mdi_account_cancel"></mat-icon>
|
||||
@@ -14,7 +14,7 @@
|
||||
class="icon-button" mat-icon-button *ngIf="selection.hasValue()" [disabled]="disabled">
|
||||
<mat-icon svgIcon="mdi_account_check_outline"></mat-icon>
|
||||
</button>
|
||||
<a [routerLink]="[ '/users',type == Type.TYPE_HUMAN ? 'create' : 'create-machine']" color="primary"
|
||||
<a [routerLink]="[ '/users',type === Type.TYPE_HUMAN ? 'create' : 'create-machine']" color="primary"
|
||||
mat-raised-button [disabled]="disabled">
|
||||
<mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }}
|
||||
</a>
|
||||
@@ -33,10 +33,10 @@
|
||||
<mat-checkbox [disabled]="disabled" color="primary" (click)="$event.stopPropagation()"
|
||||
(change)="$event ? selection.toggle(user) : null" [checked]="selection.isSelected(user)">
|
||||
|
||||
<app-avatar
|
||||
<cnsl-avatar
|
||||
*ngIf="user.human && user.human.profile.displayName && user.human?.profile.firstName && user.human?.profile.lastName; else cog"
|
||||
class="avatar" [name]="user.human.profile.displayName" [avatarUrl]="user.human?.profile?.avatarUrl || ''" [forColor]="user?.preferredLoginName" [size]="32">
|
||||
</app-avatar>
|
||||
</cnsl-avatar>
|
||||
<ng-template #cog>
|
||||
<div class="sa-icon" *ngIf="user.machine">
|
||||
<i class="las la-user-cog"></i>
|
||||
@@ -48,7 +48,7 @@
|
||||
|
||||
<ng-container matColumnDef="displayName">
|
||||
<th mat-header-cell *matHeaderCellDef
|
||||
[ngClass]="{'search-active': this.userSearchKey == UserListSearchKey.DISPLAY_NAME}">
|
||||
[ngClass]="{'search-active': this.userSearchKey === UserListSearchKey.DISPLAY_NAME}">
|
||||
{{ 'USER.PROFILE.DISPLAYNAME' | translate }}
|
||||
<template [ngTemplateOutlet]="templateRef"
|
||||
[ngTemplateOutletContext]="{key: UserListSearchKey.DISPLAY_NAME}"></template>
|
||||
@@ -78,7 +78,7 @@
|
||||
|
||||
<ng-container matColumnDef="username">
|
||||
<th mat-header-cell *matHeaderCellDef
|
||||
[ngClass]="{'search-active': this.userSearchKey == UserListSearchKey.USER_NAME}">
|
||||
[ngClass]="{'search-active': this.userSearchKey === UserListSearchKey.USER_NAME}">
|
||||
{{ 'USER.PROFILE.USERNAME' | translate }}
|
||||
<template [ngTemplateOutlet]="templateRef"
|
||||
[ngTemplateOutletContext]="{key: UserListSearchKey.USER_NAME}"></template>
|
||||
@@ -89,7 +89,7 @@
|
||||
|
||||
<ng-container matColumnDef="email">
|
||||
<th mat-header-cell *matHeaderCellDef
|
||||
[ngClass]="{'search-active': this.UserListSearchKey == UserListSearchKey.EMAIL}">
|
||||
[ngClass]="{'search-active': this.UserListSearchKey === UserListSearchKey.EMAIL}">
|
||||
{{ 'USER.EMAIL' | translate }}
|
||||
<template [ngTemplateOutlet]="templateRef"
|
||||
[ngTemplateOutletContext]="{key: UserListSearchKey.EMAIL}"></template>
|
||||
@@ -113,7 +113,7 @@
|
||||
<th mat-header-cell *matHeaderCellDef></th>
|
||||
<td mat-cell *matCellDef="let user">
|
||||
<button class="dlt-button"
|
||||
[disabled]="(['user.delete$', 'user.delete:'+user.id] | hasRole | async) == false" color="warn"
|
||||
[disabled]="(['user.delete$', 'user.delete:'+user.id] | hasRole | async) === false" color="warn"
|
||||
mat-icon-button matTooltip="{{'USER.PAGES.DELETE' | translate}}" (click)="deleteUser(user)">
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
@@ -124,18 +124,18 @@
|
||||
<tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns;"> </tr>
|
||||
|
||||
</table>
|
||||
<div *ngIf="(loading$ | async) == false && !dataSource?.data?.length" class="no-content-row">
|
||||
<div *ngIf="(loading$ | async) === false && !dataSource?.data?.length" class="no-content-row">
|
||||
<i class="las la-exclamation"></i>
|
||||
<span>{{'USER.TABLE.EMPTY' | translate}}</span>
|
||||
</div>
|
||||
<cnsl-paginator #paginator class="paginator" [timestamp]="viewTimestamp" [length]="totalResult || 0" [pageSize]="10" [timestamp]="viewTimestamp"
|
||||
[pageSizeOptions]="[5, 10, 20]" (page)="changePage($event)"></cnsl-paginator>
|
||||
</div>
|
||||
</app-refresh-table>
|
||||
</cnsl-refresh-table>
|
||||
|
||||
<ng-template #templateRef let-key="key">
|
||||
<button class="search-button" mat-icon-button (click)="setFilter(key)">
|
||||
<mat-icon class="icon" *ngIf="this.userSearchKey != key">search</mat-icon>
|
||||
<mat-icon class="icon" *ngIf="this.userSearchKey == key">search_off</mat-icon>
|
||||
<mat-icon class="icon" *ngIf="this.userSearchKey !== key">search</mat-icon>
|
||||
<mat-icon class="icon" *ngIf="this.userSearchKey === key">search_off</mat-icon>
|
||||
</button>
|
||||
</ng-template>
|
||||
@@ -35,7 +35,7 @@ enum UserListSearchKey {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-table',
|
||||
selector: 'cnsl-user-table',
|
||||
templateUrl: './user-table.component.html',
|
||||
styleUrls: ['./user-table.component.scss'],
|
||||
animations: [
|
||||
|
||||
Reference in New Issue
Block a user