mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 23:07:22 +00:00
feat(console): profile initializers, contributors for grants (#229)
* resourceowner header, i18n * password policy validators, roles required field * use angular pattern instead of custom validator * user detail fixes, mfa qr code, add org * use mgmt for mfa list * fetch owned projects * search project * seperate owned from granted projects * lint * fix granted project grid * refactor project detail * disable zitadel apps * refactor project contributors * changed i18n * hide meta nav * mobile meta layout * refactor contributor name * refactor org contributors * org i18n, org member detail view * fix uninitialized phone, email * fix: create role, add contributors to granted pjs * initialize address * contributor 18n * add tab module
This commit is contained in:
parent
6fa62ccd0a
commit
dfe6d0deb4
@ -9,6 +9,7 @@ import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
import { HttpLoaderFactory } from 'src/app/app.module';
|
||||
@ -40,6 +41,7 @@ import { PolicyGridComponent } from './policy-grid/policy-grid.component';
|
||||
ReactiveFormsModule,
|
||||
MatButtonToggleModule,
|
||||
MetaLayoutModule,
|
||||
MatTabsModule,
|
||||
MatTooltipModule,
|
||||
MatMenuModule,
|
||||
ChangesModule,
|
||||
|
@ -18,8 +18,8 @@
|
||||
<mat-form-field appearance="outline" class="formfield">
|
||||
<mat-label>{{ 'PROJECT.ROLE.KEY' | translate }}</mat-label>
|
||||
<input matInput formControlName="key" />
|
||||
<mat-error *ngIf="formGroup.get('key')?.errors?.required">{{'ERRORS.REQUIRED' | translate}}
|
||||
</mat-error>
|
||||
<!-- <mat-error *ngIf="formGroup.get('key')?.errors?.required">{{'ERRORS.REQUIRED' | translate}}
|
||||
</mat-error> -->
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="formfield">
|
||||
<mat-label>{{ 'PROJECT.ROLE.DISPLAY_NAME' | translate }}</mat-label>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { animate, animateChild, query, stagger, style, transition, trigger } from '@angular/animations';
|
||||
import { Location } from '@angular/common';
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
import { FormArray, FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { ProjectRoleAdd } from 'src/app/proto/generated/management_pb';
|
||||
@ -47,11 +47,9 @@ export class ProjectRoleCreateComponent implements OnInit, OnDestroy {
|
||||
private toast: ToastService,
|
||||
private projectService: ProjectService,
|
||||
private _location: Location,
|
||||
private fb: FormBuilder,
|
||||
) {
|
||||
this.formGroup = new FormGroup({
|
||||
key: new FormControl('', [Validators.required]),
|
||||
// name: new FormControl(''),
|
||||
displayName: new FormControl(''),
|
||||
group: new FormControl('', [Validators.required]),
|
||||
});
|
||||
@ -61,7 +59,7 @@ export class ProjectRoleCreateComponent implements OnInit, OnDestroy {
|
||||
|
||||
public addEntry(): void {
|
||||
const newGroup = new FormGroup({
|
||||
name: new FormControl(''),
|
||||
key: new FormControl(''),
|
||||
displayName: new FormControl(''),
|
||||
group: new FormControl('', [Validators.required]),
|
||||
});
|
||||
@ -86,11 +84,13 @@ export class ProjectRoleCreateComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
public addRole(): void {
|
||||
Promise.all(this.formArray.value.map((role: ProjectRoleAdd.AsObject) => {
|
||||
const promises = this.formArray.value.map((role: ProjectRoleAdd.AsObject) => {
|
||||
role.id = this.projectId;
|
||||
console.log(role);
|
||||
return this.projectService.AddProjectRole(role);
|
||||
})).then(() => {
|
||||
});
|
||||
|
||||
Promise.all(promises).then(() => {
|
||||
this.router.navigate(['projects', this.projectId]);
|
||||
}).catch(data => {
|
||||
console.log(data);
|
||||
|
@ -31,6 +31,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<app-changes *ngIf="project" [changeType]="ChangeType.PROJECT" [id]="project.id"></app-changes>
|
||||
<mat-tab-group mat-stretch-tabs class="tab-group" disablePagination="true">
|
||||
<mat-tab label="Details">
|
||||
<app-project-contributors *ngIf="project"
|
||||
[disabled]="project?.state !== ProjectState.PROJECTSTATE_ACTIVE"
|
||||
[projectType]="ProjectType.PROJECTTYPE_GRANTED" [project]="project">
|
||||
</app-project-contributors>
|
||||
</mat-tab>
|
||||
<mat-tab label="{{ 'CHANGES.PROJECT.TITLE' | translate }}" class="flex-col">
|
||||
<app-changes *ngIf="project" [changeType]="ChangeType.PROJECT" [id]="project.id"></app-changes>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</metainfo>
|
||||
</app-meta-layout>
|
@ -88,10 +88,10 @@
|
||||
|
||||
<mat-tab-group mat-stretch-tabs class="tab-group" disablePagination="true">
|
||||
<mat-tab label="Details">
|
||||
<app-owned-project-contributors *ngIf="project"
|
||||
<app-project-contributors *ngIf="project"
|
||||
[disabled]="project?.state !== ProjectState.PROJECTSTATE_ACTIVE"
|
||||
[projectType]="ProjectType.PROJECTTYPE_OWNED" [project]="project">
|
||||
</app-owned-project-contributors>
|
||||
</app-project-contributors>
|
||||
</mat-tab>
|
||||
<mat-tab label="{{ 'CHANGES.PROJECT.TITLE' | translate }}" class="flex-col">
|
||||
<app-changes *ngIf="project" [changeType]="ChangeType.PROJECT" [id]="project.id"></app-changes>
|
||||
|
@ -1,20 +1,20 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { OwnedProjectContributorsComponent } from './owned-project-contributors.component';
|
||||
import { ProjectContributorsComponent } from './project-contributors.component';
|
||||
|
||||
describe('ProjectContributorsComponent', () => {
|
||||
let component: OwnedProjectContributorsComponent;
|
||||
let fixture: ComponentFixture<OwnedProjectContributorsComponent>;
|
||||
let component: ProjectContributorsComponent;
|
||||
let fixture: ComponentFixture<ProjectContributorsComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [OwnedProjectContributorsComponent],
|
||||
declarations: [ProjectContributorsComponent],
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(OwnedProjectContributorsComponent);
|
||||
fixture = TestBed.createComponent(ProjectContributorsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
@ -5,6 +5,7 @@ import { BehaviorSubject, from, of } from 'rxjs';
|
||||
import { catchError, finalize, map } from 'rxjs/operators';
|
||||
import { User } from 'src/app/proto/generated/auth_pb';
|
||||
import {
|
||||
ProjectGrantView,
|
||||
ProjectMemberSearchResponse,
|
||||
ProjectMemberView,
|
||||
ProjectState,
|
||||
@ -20,12 +21,12 @@ import {
|
||||
} from '../../../modules/add-member-dialog/member-create-dialog.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-owned-project-contributors',
|
||||
templateUrl: './owned-project-contributors.component.html',
|
||||
styleUrls: ['./owned-project-contributors.component.scss'],
|
||||
selector: 'app-project-contributors',
|
||||
templateUrl: './project-contributors.component.html',
|
||||
styleUrls: ['./project-contributors.component.scss'],
|
||||
})
|
||||
export class OwnedProjectContributorsComponent implements OnInit {
|
||||
@Input() public project!: ProjectView.AsObject;
|
||||
export class ProjectContributorsComponent implements OnInit {
|
||||
@Input() public project!: ProjectView.AsObject | ProjectGrantView.AsObject;
|
||||
@Input() public projectType!: ProjectType;
|
||||
|
||||
@Input() public disabled: boolean = false;
|
@ -33,12 +33,12 @@ import { UserListModule } from '../user-list/user-list.module';
|
||||
import { GrantedProjectDetailComponent } from './granted-project-detail/granted-project-detail.component';
|
||||
import { GrantedProjectGridComponent } from './granted-project-grid/granted-project-grid.component';
|
||||
import { GrantedProjectListComponent } from './granted-project-list/granted-project-list.component';
|
||||
import { OwnedProjectContributorsComponent } from './owned-project-contributors/owned-project-contributors.component';
|
||||
import { OwnedProjectDetailComponent } from './owned-project-detail/owned-project-detail.component';
|
||||
import { OwnedProjectGridComponent } from './owned-project-grid/owned-project-grid.component';
|
||||
import { OwnedProjectListComponent } from './owned-project-list/owned-project-list.component';
|
||||
import { ProjectApplicationGridComponent } from './project-application-grid/project-application-grid.component';
|
||||
import { ProjectApplicationsComponent } from './project-applications/project-applications.component';
|
||||
import { ProjectContributorsComponent } from './project-contributors/project-contributors.component';
|
||||
import {
|
||||
ProjectGrantMembersCreateDialogComponent,
|
||||
} from './project-grant-members-create-dialog/project-grant-members-create-dialog.component';
|
||||
@ -62,7 +62,7 @@ import { ProjectsComponent } from './projects.component';
|
||||
ProjectGrantsComponent,
|
||||
ProjectGrantMembersComponent,
|
||||
ProjectGrantMembersCreateDialogComponent,
|
||||
OwnedProjectContributorsComponent,
|
||||
ProjectContributorsComponent,
|
||||
ProjectsComponent,
|
||||
],
|
||||
imports: [
|
||||
|
@ -38,11 +38,6 @@ export class UserCreateComponent implements OnDestroy {
|
||||
region: [''],
|
||||
country: [''],
|
||||
});
|
||||
if (this.email) {
|
||||
this.sub = this.email?.valueChanges.subscribe(value => {
|
||||
this.userName?.setValue(value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public createUser(): void {
|
||||
|
@ -68,16 +68,16 @@
|
||||
<app-card *ngIf="profile" title="{{ 'USER.LOGINMETHODS.TITLE' | translate }}"
|
||||
description="{{ 'USER.LOGINMETHODS.DESCRIPTION' | translate }}">
|
||||
<div class="method-col">
|
||||
<div *ngIf="email" class="method-row">
|
||||
<div class="method-row">
|
||||
<span class="label">{{ 'USER.EMAIL' | translate }}</span>
|
||||
|
||||
<ng-container *ngIf="!emailEditState; else emailEdit">
|
||||
<div class="actions">
|
||||
<span class="name">{{email.email}}</span>
|
||||
<mat-icon *ngIf="email.isemailverified" color="primary" aria-hidden="false"
|
||||
<span class="name">{{email?.email}}</span>
|
||||
<mat-icon *ngIf="email?.isemailverified" color="primary" aria-hidden="false"
|
||||
aria-label="verified icon">
|
||||
check_circle_outline</mat-icon>
|
||||
<ng-container *ngIf="email.email && !email.isemailverified">
|
||||
<ng-container *ngIf="email?.email && !email?.isemailverified">
|
||||
<mat-icon color="warn" aria-hidden="false" aria-label="not verified icon">highlight_off
|
||||
</mat-icon>
|
||||
<a class="verify" matTooltip="{{'USER.LOGINMETHODS.EMAIL.RESEND' | translate}}"
|
||||
@ -104,16 +104,16 @@
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<div *ngIf="phone" class="method-row">
|
||||
<div class="method-row">
|
||||
<span class="label">{{ 'USER.PHONE' | translate }}</span>
|
||||
|
||||
<ng-container *ngIf="!phoneEditState; else phoneEdit">
|
||||
<div class="actions">
|
||||
<span class="name">{{phone.phone}}</span>
|
||||
<mat-icon *ngIf="phone.isPhoneVerified" color="primary" aria-hidden="false"
|
||||
<span class="name">{{phone?.phone}}</span>
|
||||
<mat-icon *ngIf="phone?.isPhoneVerified" color="primary" aria-hidden="false"
|
||||
aria-label="verified icon">
|
||||
check_circle_outline</mat-icon>
|
||||
<ng-container *ngIf="phone.phone && !phone.isPhoneVerified">
|
||||
<ng-container *ngIf="phone?.phone && !phone?.isPhoneVerified">
|
||||
<mat-icon color="warn" aria-hidden="false" aria-label="not verified icon">highlight_off
|
||||
</mat-icon>
|
||||
<a class="verify" matTooltip="{{'USER.LOGINMETHODS.ENTERCODE_DESC' | translate}}"
|
||||
@ -127,7 +127,7 @@
|
||||
<button (click)="phoneEditState = true" mat-icon-button>
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
<button *ngIf="phone.phone" (click)="deletePhone()" mat-icon-button>
|
||||
<button *ngIf="phone?.phone" (click)="deletePhone()" mat-icon-button>
|
||||
<mat-icon>delete_outline</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
@ -150,7 +150,7 @@
|
||||
|
||||
<app-auth-user-mfa *ngIf="profile" [profile]="profile"></app-auth-user-mfa>
|
||||
|
||||
<app-card title="{{ 'USER.ADDRESS.TITLE' | translate }}" *ngIf="address && profile">
|
||||
<app-card title="{{ 'USER.ADDRESS.TITLE' | translate }}" *ngIf="profile">
|
||||
<form [formGroup]="addressForm" (ngSubmit)="saveAddress()">
|
||||
<div class="content">
|
||||
<mat-form-field class="formfield">
|
||||
|
@ -33,9 +33,9 @@ function passwordConfirmValidator(c: AbstractControl): any {
|
||||
})
|
||||
export class AuthUserDetailComponent implements OnDestroy {
|
||||
public profile!: UserProfile.AsObject;
|
||||
public email!: UserEmail.AsObject;
|
||||
public phone!: UserPhone.AsObject;
|
||||
public address!: UserAddress.AsObject;
|
||||
public email: UserEmail.AsObject = { email: '' } as any;
|
||||
public phone: UserPhone.AsObject = { phone: '' } as any;
|
||||
public address: UserAddress.AsObject = { id: '' } as any;
|
||||
public genders: Gender[] = [Gender.GENDER_MALE, Gender.GENDER_FEMALE, Gender.GENDER_DIVERSE];
|
||||
public languages: string[] = ['de', 'en'];
|
||||
|
||||
@ -150,6 +150,8 @@ export class AuthUserDetailComponent implements OnDestroy {
|
||||
}
|
||||
|
||||
public saveEmail(): void {
|
||||
this.emailEditState = false;
|
||||
|
||||
this.userService
|
||||
.SaveMyUserEmail(this.email).then((data: UserEmail) => {
|
||||
this.toast.showInfo('Saved Email');
|
||||
@ -211,6 +213,10 @@ export class AuthUserDetailComponent implements OnDestroy {
|
||||
}
|
||||
|
||||
public savePhone(): void {
|
||||
this.phoneEditState = false;
|
||||
if (!this.phone.id) {
|
||||
this.phone.id = this.profile.id;
|
||||
}
|
||||
this.userService
|
||||
.SaveMyUserPhone(this.phone).then((data: UserPhone) => {
|
||||
this.toast.showInfo('Saved Phone');
|
||||
|
@ -67,16 +67,16 @@
|
||||
<app-card title="{{ 'USER.LOGINMETHODS.TITLE' | translate }}"
|
||||
description="{{ 'USER.LOGINMETHODS.DESCRIPTION' | translate }}">
|
||||
<div class="method-col">
|
||||
<div *ngIf="email" class="method-row">
|
||||
<div class="method-row">
|
||||
<span class="label">{{ 'USER.EMAIL' | translate }}</span>
|
||||
|
||||
<ng-container *ngIf="!emailEditState; else emailEdit">
|
||||
<div class="actions">
|
||||
<span class="name">{{email.email}}</span>
|
||||
<mat-icon *ngIf="email.isEmailVerified" color="primary" aria-hidden="false"
|
||||
<span class="name">{{email?.email}}</span>
|
||||
<mat-icon *ngIf="email?.isEmailVerified" color="primary" aria-hidden="false"
|
||||
aria-label="verified icon">
|
||||
check_circle_outline</mat-icon>
|
||||
<ng-container *ngIf="email.email && !email.isEmailVerified">
|
||||
<ng-container *ngIf="email?.email && !email?.isEmailVerified">
|
||||
<mat-icon color="warn" aria-hidden="false" aria-label="not verified icon">highlight_off
|
||||
</mat-icon>
|
||||
<a class="verify" matTooltip="{{'USER.LOGINMETHODS.EMAIL.RESEND' | translate}}"
|
||||
@ -103,16 +103,16 @@
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<div *ngIf="phone" class="method-row">
|
||||
<div class="method-row">
|
||||
<span class="label">{{ 'USER.PHONE' | translate }}</span>
|
||||
|
||||
<ng-container *ngIf="!phoneEditState; else phoneEdit">
|
||||
<div class="actions">
|
||||
<span class="name">{{phone.phone}}</span>
|
||||
<mat-icon *ngIf="phone.isPhoneVerified" color="primary" aria-hidden="false"
|
||||
<span class="name">{{phone?.phone}}</span>
|
||||
<mat-icon *ngIf="phone?.isPhoneVerified" color="primary" aria-hidden="false"
|
||||
aria-label="verified icon">
|
||||
check_circle_outline</mat-icon>
|
||||
<ng-container *ngIf="phone.phone && !phone.isPhoneVerified">
|
||||
<ng-container *ngIf="phone?.phone && !phone?.isPhoneVerified">
|
||||
<mat-icon color="warn" aria-hidden="false" aria-label="not verified icon">highlight_off
|
||||
</mat-icon>
|
||||
|
||||
@ -125,7 +125,7 @@
|
||||
<button (click)="phoneEditState = true" mat-icon-button>
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
<button *ngIf="phone.phone" (click)="deletePhone()" mat-icon-button>
|
||||
<button *ngIf="phone?.phone" (click)="deletePhone()" mat-icon-button>
|
||||
<mat-icon>delete_outline</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
@ -148,7 +148,7 @@
|
||||
|
||||
<app-auth-user-mfa *ngIf="profile" [profile]="profile"></app-auth-user-mfa>
|
||||
|
||||
<app-card title="{{ 'USER.ADDRESS.TITLE' | translate }}" *ngIf="address">
|
||||
<app-card title="{{ 'USER.ADDRESS.TITLE' | translate }}">
|
||||
<form [formGroup]="addressForm" (ngSubmit)="saveAddress()">
|
||||
<div class="content">
|
||||
<mat-form-field class="formfield">
|
||||
|
@ -44,9 +44,9 @@ function passwordConfirmValidator(c: AbstractControl): any {
|
||||
})
|
||||
export class UserDetailComponent implements OnInit, OnDestroy {
|
||||
public profile!: UserProfile.AsObject;
|
||||
public email!: UserEmail.AsObject;
|
||||
public phone!: UserPhone.AsObject;
|
||||
public address!: UserAddress.AsObject;
|
||||
public email: UserEmail.AsObject = { email: '' } as any;
|
||||
public phone: UserPhone.AsObject = { phone: '' } as any;
|
||||
public address: UserAddress.AsObject = { id: '' } as any;
|
||||
public genders: Gender[] = [Gender.GENDER_MALE, Gender.GENDER_FEMALE, Gender.GENDER_DIVERSE];
|
||||
public languages: string[] = ['de', 'en'];
|
||||
|
||||
@ -222,6 +222,9 @@ export class UserDetailComponent implements OnInit, OnDestroy {
|
||||
|
||||
public savePhone(): void {
|
||||
this.phoneEditState = false;
|
||||
if (!this.phone.id) {
|
||||
this.phone.id = this.profile.id;
|
||||
}
|
||||
this.mgmtUserService
|
||||
.SaveUserPhone(this.phone).then((data: UserPhone) => {
|
||||
this.toast.showInfo('Saved Phone');
|
||||
@ -232,6 +235,10 @@ export class UserDetailComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
public saveAddress(): void {
|
||||
if (!this.address.id) {
|
||||
this.address.id = this.profile.id;
|
||||
}
|
||||
|
||||
this.address.streetAddress = this.streetAddress?.value;
|
||||
this.address.postalCode = this.postalCode?.value;
|
||||
this.address.locality = this.locality?.value;
|
||||
|
@ -296,8 +296,8 @@
|
||||
},
|
||||
"NAME": "Name",
|
||||
"MEMBER": {
|
||||
"TITLE": "Mitwirkende",
|
||||
"TITLEDESC":"Mitwirkende können Änderungen an dieser Entität vornehmen",
|
||||
"TITLE": "Manager",
|
||||
"TITLEDESC":"Manager können Änderungen an dieser Entität vornehmen",
|
||||
"DESCRIPTION":"Hier finden Sie alle Mitwirkenden dieses Projekts. Sie können ein neues Mitglied hinzufügen und bestehende verwalten.",
|
||||
"USERNAME": "Benutzername",
|
||||
"FIRSTNAME": "Vorname",
|
||||
|
@ -296,9 +296,9 @@
|
||||
},
|
||||
"NAME": "Name",
|
||||
"MEMBER": {
|
||||
"TITLE": "Contributors",
|
||||
"TITLEDESC":"Contributors can make changes on this very entity",
|
||||
"DESCRIPTION":"Here you can find all contributors of this project. You can add a new member and manage persisting ones.",
|
||||
"TITLE": "Managers",
|
||||
"TITLEDESC":"Managers can make changes on this very entity, based on their given role in zitadel.",
|
||||
"DESCRIPTION":"Here you can find all managers of this project. You can add a new member and manage persisting ones.",
|
||||
"USERNAME": "Username",
|
||||
"FIRSTNAME": "Firstname",
|
||||
"LASTNAME": "Lastname",
|
||||
|
Loading…
x
Reference in New Issue
Block a user