feat(console): user metadata, rehaul detail pages (#2209)

* service, sidenav, i18n, dialog

* detail layout, user detail

* metadata dialog from

* dialog

* features

* formarray

* metadata component

* comp

* user metadata refresh

* use formarray, control, bulk save

* metadata revert, has feature directive

* lint

* lint

* typo

* info row user, warn color optim

* card cleanup, actions for user detail

* project, org, user, app rehaul

* lint

* scss

* digit fix

* features and project grid rehaul

* info-section layout, org domain info

* readd palette scss

* add svg email warn

* missing translation

* rm unused ts

* lockoutpolicy

* check for lockout feature
This commit is contained in:
Max Peintner
2021-09-13 13:38:57 +02:00
committed by GitHub
parent e4bdaf26b0
commit 490cafa538
111 changed files with 2437 additions and 1063 deletions
@@ -1,3 +1,9 @@
h3 {
font-weight: 400;
font-size: 16px;
text-transform: uppercase;
letter-spacing: .05em;
}
.app-grid-header {
display: flex;
@@ -5,48 +5,44 @@
<mat-icon class="icon">arrow_back</mat-icon>
</a>
<h1>{{ 'PROJECT.PAGES.TITLE' | translate }} {{project?.name}}</h1>
<ng-template appHasRole [appHasRole]="['project.write:'+projectId, 'project.write']">
<button matTooltip="{{'ACTIONS.EDIT' | translate}}" mat-icon-button (click)="editstate = !editstate"
aria-label="Edit project name" *ngIf="isZitadel === false">
<i *ngIf="!editstate" class="las la-edit"></i>
<mat-icon *ngIf="editstate">close</mat-icon>
</button>
</ng-template>
<ng-template appHasRole [appHasRole]="['project.delete:'+projectId, 'project.delete']">
<button matTooltip="{{'ACTIONS.DELETE' | translate}}" color="warn" mat-icon-button
(click)="deleteProject()" aria-label="Edit project name" *ngIf="isZitadel === false">
<i class="las la-trash"></i>
</button>
</ng-template>
<span class="fill-space"></span>
<button mat-stroked-button color="warn"
[disabled]="isZitadel || (['project.write$', 'project.write:'+ project.id]| hasRole | async) == false"
*ngIf="project?.state === ProjectState.PROJECT_STATE_ACTIVE"
(click)="changeState(ProjectState.PROJECT_STATE_INACTIVE)">{{'PROJECT.TABLE.DEACTIVATE' |
translate}}</button>
<button mat-stroked-button color="warn"
[disabled]="isZitadel || (['project.write$', 'project.write:'+ project.id]| hasRole | async) == false"
*ngIf="project?.state === ProjectState.PROJECT_STATE_INACTIVE"
(click)="changeState(ProjectState.PROJECT_STATE_ACTIVE)">{{'PROJECT.TABLE.ACTIVATE' |
translate}}</button>
<ng-template appHasRole [appHasRole]="['project.write:'+projectId, 'project.write']">
<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>
</button>
<mat-menu #actions="matMenu" xPosition="before">
<button mat-menu-item (click)="openNameDialog()"
aria-label="Edit project name" *ngIf="isZitadel === false">
{{'ACTIONS.RENAME' | translate}}
</button>
<button mat-menu-item
[disabled]="isZitadel || (['project.write$', 'project.write:'+ project.id]| hasRole | async) == false"
*ngIf="project?.state === ProjectState.PROJECT_STATE_ACTIVE"
(click)="changeState(ProjectState.PROJECT_STATE_INACTIVE)">
{{'PROJECT.TABLE.DEACTIVATE' | translate}}
</button>
<button mat-menu-item
[disabled]="isZitadel || (['project.write$', 'project.write:'+ project.id]| hasRole | async) == false"
*ngIf="project?.state === ProjectState.PROJECT_STATE_INACTIVE"
(click)="changeState(ProjectState.PROJECT_STATE_ACTIVE)">
{{'PROJECT.TABLE.ACTIVATE' | translate}}
</button>
<ng-template appHasRole [appHasRole]="['project.delete$', 'project.delete:'+projectId]">
<button mat-menu-item matTooltip="{{'ACTIONS.DELETE' | translate}}"
(click)="deleteProject()" aria-label="Edit project name" *ngIf="isZitadel === false">
<span [style.color]="'var(--warn)'">{{'PROJECT.PAGES.DELETE' | translate}}</span>
</button>
</ng-template>
</mat-menu>
</ng-template>
<div class="full-width">
<div class="line">
<ng-container *ngIf="editstate">
<cnsl-form-field *ngIf="editstate && project?.name" class="formfield"
hintLabel="The name is required!">
<cnsl-label>{{'PROJECT.NAME' | translate}}</cnsl-label>
<input cnslInput [(ngModel)]="project.name" name="name" />
</cnsl-form-field>
<button matTooltip="{{'ACTIONS.SAVE' | translate}}" class="icon-button" *ngIf="editstate"
mat-icon-button (click)="updateName()">
<mat-icon>check</mat-icon>
</button>
</ng-container>
<span class="fill-space"></span>
</div>
<p class="desc">{{ 'PROJECT.PAGES.DESCRIPTION' | translate }}</p>
<p *ngIf="isZitadel" class="zitadel-warning">{{'PROJECT.PAGES.ZITADELPROJECT' | translate}}</p>
</div>
@@ -14,12 +14,22 @@
}
h1 {
font-size: 1.2rem;
margin: 0 1rem;
margin-left: 2rem;
font-weight: normal;
}
.actions-trigger {
margin-top: .25rem;
display: flex;
align-items: center;
.icon {
margin-left: .5rem;
margin-right: -.5rem;
}
}
.full-width {
padding-top: 1rem;
width: 100%;
@@ -21,6 +21,8 @@ import { User } from 'src/app/proto/generated/zitadel/user_pb';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
import { NameDialogComponent } from '../../../../modules/name-dialog/name-dialog.component';
@Component({
selector: 'app-owned-project-detail',
templateUrl: './owned-project-detail.component.html',
@@ -41,7 +43,6 @@ export class OwnedProjectDetailComponent implements OnInit, OnDestroy {
public grid: boolean = true;
private subscription?: Subscription;
public editstate: boolean = false;
public isZitadel: boolean = false;
@@ -73,6 +74,25 @@ export class OwnedProjectDetailComponent implements OnInit, OnDestroy {
this.subscription?.unsubscribe();
}
public openNameDialog(): void {
const dialogRef = this.dialog.open(NameDialogComponent, {
data: {
name: this.project.name,
titleKey: 'PROJECT.NAMEDIALOG.TITLE',
descKey: 'PROJECT.NAMEDIALOG.DESCRIPTION',
labelKey: 'PROJECT.NAMEDIALOG.NAME',
},
width: '400px',
});
dialogRef.afterClosed().subscribe(name => {
if (name) {
this.project.name = name;
this.updateName();
}
});
}
public openPrivateLabelingDialog(): void {
const dialogRef = this.dialog.open(ProjectPrivateLabelingDialogComponent, {
data: {
@@ -221,7 +241,6 @@ export class OwnedProjectDetailComponent implements OnInit, OnDestroy {
public updateName(): void {
this.saveProject();
this.editstate = false;
}
public openAddMember(): void {
@@ -5,6 +5,7 @@ import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatRippleModule } from '@angular/material/core';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select';
import { MatTableModule } from '@angular/material/table';
@@ -35,43 +36,44 @@ import { OwnedProjectDetailComponent } from './owned-project-detail.component';
import { ProjectGrantsComponent } from './project-grants/project-grants.component';
@NgModule({
declarations: [
OwnedProjectDetailComponent,
ApplicationGridComponent,
ApplicationsComponent,
ProjectGrantsComponent,
],
imports: [
CommonModule,
FormsModule,
AppCardModule,
OwnedProjectDetailRoutingModule,
TranslateModule,
ReactiveFormsModule,
HasRoleModule,
MatButtonModule,
MatIconModule,
ContributorsModule,
MatTabsModule,
WarnDialogModule,
MatTooltipModule,
ProjectRolesModule,
HasRolePipeModule,
UserGrantsModule,
TimestampToDatePipeModule,
MatTableModule,
InputModule,
CardModule,
PaginatorModule,
MatRippleModule,
MatCheckboxModule,
MatSelectModule,
MatProgressSpinnerModule,
ChangesModule,
MetaLayoutModule,
RefreshTableModule,
MemberCreateDialogModule,
LocalizedDatePipeModule,
],
declarations: [
OwnedProjectDetailComponent,
ApplicationGridComponent,
ApplicationsComponent,
ProjectGrantsComponent,
],
imports: [
CommonModule,
FormsModule,
AppCardModule,
OwnedProjectDetailRoutingModule,
TranslateModule,
ReactiveFormsModule,
HasRoleModule,
MatButtonModule,
MatIconModule,
ContributorsModule,
MatTabsModule,
WarnDialogModule,
MatTooltipModule,
ProjectRolesModule,
HasRolePipeModule,
UserGrantsModule,
TimestampToDatePipeModule,
MatTableModule,
InputModule,
CardModule,
PaginatorModule,
MatRippleModule,
MatCheckboxModule,
MatSelectModule,
MatMenuModule,
MatProgressSpinnerModule,
ChangesModule,
MetaLayoutModule,
RefreshTableModule,
MemberCreateDialogModule,
LocalizedDatePipeModule,
],
})
export class OwnedProjectDetailModule { }
@@ -17,7 +17,10 @@
{{
item.details.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'
}}</span>
<span class="name" *ngIf="item.name">{{ item.name }}</span>
<div class="name-row">
<span class="name" *ngIf="item.name">{{ item.name }}</span>
<div class="state-dot" [ngClass]="{'active': item.state === ProjectState.PROJECT_STATE_ACTIVE, 'inactive': item.state === ProjectState.PROJECT_STATE_INACTIVE}"></div>
</div>
<span *ngIf="item.details.changeDate" class="created">{{'PROJECT.PAGES.CREATEDON' | translate}}
{{
@@ -41,7 +44,10 @@
{{
item.details.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'
}}</span>
<span class="name" *ngIf="item.name">{{ item.name }}</span>
<div class="name-row">
<span class="name" *ngIf="item.name">{{ item.name }}</span>
<div class="state-dot" [ngClass]="{'active': item.state === ProjectState.PROJECT_STATE_ACTIVE, 'inactive': item.state === ProjectState.PROJECT_STATE_INACTIVE}"></div>
</div>
<span *ngIf="item.details.creationDate" class="created">{{'PROJECT.PAGES.CREATEDON' | translate}}
{{
@@ -44,7 +44,7 @@
position: relative;
z-index: 100;
margin: 1rem;
flex-basis: 260px;
flex-basis: 270px;
display: flex;
text-decoration: none;
cursor: pointer;
@@ -57,10 +57,6 @@
min-height: 166px;
transition: box-shadow .1s ease-in;
&.inactive {
color: var(--grey);
}
img {
height: 50px;
width: 50px;
@@ -81,10 +77,29 @@
color: var(--grey);
}
.name {
margin-top: 1rem;
font-size: 1.2rem;
margin-bottom: .5rem;
.name-row {
display: flex;
align-items: center;
margin: 1rem 0 .5rem 0;
.name {
font-size: 1.2rem;
margin-right: .5rem;
}
.state-dot {
height: 8px;
width: 8px;
border-radius: 50%;
&.active {
background-color: var(--success);
}
&.inactive {
background-color: var(--warn);
}
}
}
.description {
@@ -121,6 +121,7 @@ export class OwnedProjectListComponent implements OnInit, OnDestroy {
if (resp.details?.viewTimestamp) {
this.viewTimestamp = resp.details?.viewTimestamp;
}
console.log(resp.resultList);
this.dataSource.data = this.ownedProjectList;
this.loadingSubject.next(false);
}).catch(error => {
@@ -1,5 +1,5 @@
h1 {
margin-top: 0;
margin: 0;
}
.sub {
@@ -24,6 +24,7 @@ import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.mod
import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe/localized-date-pipe.module';
import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe/timestamp-to-date-pipe.module';
import { NameDialogModule } from '../../../modules/name-dialog/name-dialog.module';
import {
ProjectPrivateLabelingDialogModule,
} from '../../../modules/project-private-labeling-dialog/project-private-labeling-dialog.module';
@@ -43,6 +44,7 @@ import { OwnedProjectsComponent } from './owned-projects.component';
OwnedProjectsRoutingModule,
UserGrantsModule,
FormsModule,
NameDialogModule,
ReactiveFormsModule,
TranslateModule,
AvatarModule,