fix(console): user create loading state and i18n, improved app create stepper, invalid token dialog, cleanup, new home (#509)

* fix iam member model

* fix org member model

* fix auth user loading

* copytoclipboard directive

* directive logs, load bar on init, create user

* typo

* welcome section, contributor spinner

* fix home link

* fix stepper flow

* show dialog on invalid token

* fix app table refresh, pin icons light theme

* cleanup contributor

* Update console/src/assets/i18n/en.json

Co-authored-by: Florian Forster <florian@caos.ch>

* Update console/src/assets/i18n/de.json

Co-authored-by: Florian Forster <florian@caos.ch>

* Update console/src/assets/i18n/de.json

Co-authored-by: Florian Forster <florian@caos.ch>

* Update console/src/assets/i18n/de.json

Co-authored-by: Florian Forster <florian@caos.ch>

Co-authored-by: Florian Forster <florian@caos.ch>
This commit is contained in:
Max Peintner 2020-07-24 09:48:58 +02:00 committed by GitHub
parent c105bf483b
commit af60b88997
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
73 changed files with 581 additions and 487 deletions

View File

@ -204,6 +204,21 @@ export class AppComponent implements OnDestroy {
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/lock-reset.svg'), this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/lock-reset.svg'),
); );
this.matIconRegistry.addSvgIcon(
'mdi_broom',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/broom.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_pin_outline',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/pin-outline.svg'),
);
this.matIconRegistry.addSvgIcon(
'mdi_pin',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/pin.svg'),
);
this.orgSub = this.authService.activeOrgChanged.subscribe(org => { this.orgSub = this.authService.activeOrgChanged.subscribe(org => {
this.org = org; this.org = org;
}); });

View File

@ -5,6 +5,7 @@ import localeDe from '@angular/common/locales/de';
import { APP_INITIALIZER, NgModule } from '@angular/core'; import { APP_INITIALIZER, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card'; import { MatCardModule } from '@angular/material/card';
import { MatDialogModule } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu';
import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatProgressBarModule } from '@angular/material/progress-bar';
@ -27,6 +28,7 @@ import { HasRoleModule } from './directives/has-role/has-role.module';
import { OutsideClickModule } from './directives/outside-click/outside-click.module'; import { OutsideClickModule } from './directives/outside-click/outside-click.module';
import { AccountsCardModule } from './modules/accounts-card/accounts-card.module'; import { AccountsCardModule } from './modules/accounts-card/accounts-card.module';
import { AvatarModule } from './modules/avatar/avatar.module'; import { AvatarModule } from './modules/avatar/avatar.module';
import { WarnDialogModule } from './modules/warn-dialog/warn-dialog.module';
import { SignedoutComponent } from './pages/signedout/signedout.component'; import { SignedoutComponent } from './pages/signedout/signedout.component';
import { HasRolePipeModule } from './pipes/has-role-pipe.module'; import { HasRolePipeModule } from './pipes/has-role-pipe.module';
import { AuthUserService } from './services/auth-user.service'; import { AuthUserService } from './services/auth-user.service';
@ -105,6 +107,8 @@ const authConfig: AuthConfig = {
MatMenuModule, MatMenuModule,
MatSnackBarModule, MatSnackBarModule,
AvatarModule, AvatarModule,
WarnDialogModule,
MatDialogModule,
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }), ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
], ],
providers: [ providers: [

View File

@ -0,0 +1,31 @@
import { Directive, EventEmitter, HostListener, Input, Output } from '@angular/core';
@Directive({
selector: '[appCopyToClipboard]',
})
export class CopyToClipboardDirective {
@Input() valueToCopy: string = '';
@Output() copiedValue: EventEmitter<string> = new EventEmitter();
@HostListener('document:click', ['$event.target']) onMouseEnter(): void {
this.copytoclipboard(this.valueToCopy);
}
public copytoclipboard(value: string): void {
const selBox = document.createElement('textarea');
selBox.style.position = 'fixed';
selBox.style.left = '0';
selBox.style.top = '0';
selBox.style.opacity = '0';
selBox.value = value;
document.body.appendChild(selBox);
selBox.focus();
selBox.select();
document.execCommand('copy');
document.body.removeChild(selBox);
this.copiedValue.emit(value);
setTimeout(() => {
this.copiedValue.emit('');
}, 3000);
}
}

View File

@ -0,0 +1,19 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { CopyToClipboardDirective } from './copy-to-clipboard.directive';
@NgModule({
declarations: [
CopyToClipboardDirective,
],
imports: [
CommonModule,
],
exports: [
CopyToClipboardDirective,
],
})
export class CopyToClipboardModule { }

View File

@ -7,7 +7,7 @@
<span class="u-email">{{profile?.preferredLoginName}}</span> <span class="u-email">{{profile?.preferredLoginName}}</span>
<span class="iamuser" *ngIf="iamuser">IAM USER</span> <span class="iamuser" *ngIf="iamuser">IAM USER</span>
<button (click)="editUserProfile()" mat-stroked-button>{{'USER.EDITACCOUNT' | translate}}</button> <button color="primary" (click)="editUserProfile()" mat-stroked-button>{{'USER.EDITACCOUNT' | translate}}</button>
<div class="l-accounts"> <div class="l-accounts">
<mat-progress-bar *ngIf="loadingUsers" color="primary" mode="indeterminate"></mat-progress-bar> <mat-progress-bar *ngIf="loadingUsers" color="primary" mode="indeterminate"></mat-progress-bar>
<a class="row" *ngFor="let user of users" (click)="selectAccount(user.loginName)"> <a class="row" *ngFor="let user of users" (click)="selectAccount(user.loginName)">
@ -35,5 +35,5 @@
</a> </a>
</div> </div>
<button (click)="logout()" mat-stroked-button>{{'MENU.LOGOUT' | translate}}</button> <button (click)="logout()" color="primary" mat-stroked-button>{{'MENU.LOGOUT' | translate}}</button>
</div> </div>

View File

@ -1,4 +1,5 @@
<div class="avatar-circle dontcloseonclick" <div class="avatar-circle dontcloseonclick" matRipple [matRippleColor]="'#ffffff20'" matRippleUnbounded="true"
matRippleCentered="true"
[ngStyle]="{'height': size+'px', 'width': size+'px', 'fontSize': (fontSize-1)+'px', 'background-color': color}" [ngStyle]="{'height': size+'px', 'width': size+'px', 'fontSize': (fontSize-1)+'px', 'background-color': color}"
[ngClass]="{'active': active}"> [ngClass]="{'active': active}">
{{credentials}} {{credentials}}

View File

@ -16,8 +16,8 @@
outline: none; outline: none;
color: white; color: white;
&.active:hover { // &.active:hover {
border: 2px solid #8795a1; // border: 2px solid #8795a1;
} // }
} }
} }

View File

@ -1,5 +1,6 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { MatRippleModule } from '@angular/material/core';
import { AvatarComponent } from './avatar.component'; import { AvatarComponent } from './avatar.component';
@ -9,6 +10,7 @@ import { AvatarComponent } from './avatar.component';
declarations: [AvatarComponent], declarations: [AvatarComponent],
imports: [ imports: [
CommonModule, CommonModule,
MatRippleModule,
], ],
exports: [ exports: [
AvatarComponent, AvatarComponent,

View File

@ -3,6 +3,8 @@
<span class="sub-header">{{ description }}</span> <span class="sub-header">{{ description }}</span>
<div class="people"> <div class="people">
<div class="img-list"> <div class="img-list">
<mat-spinner diameter="20" *ngIf="loading"></mat-spinner>
<ng-container *ngIf="totalResult < 10; else compact"> <ng-container *ngIf="totalResult < 10; else compact">
<ng-container *ngFor="let member of membersSubject | async; index as i"> <ng-container *ngFor="let member of membersSubject | async; index as i">
<div (click)="emitShowDetail()" class="avatar-circle" <div (click)="emitShowDetail()" class="avatar-circle"

View File

@ -28,10 +28,13 @@
margin-left: 1rem; margin-left: 1rem;
display: flex; display: flex;
align-items: center; align-items: center;
transition: all .15s ease-in-out;
mat-spinner {
margin-left: -15px;
margin-right: 20px;
}
.avatar-circle { .avatar-circle {
transition: all .3s ease-in-out;
float: left; float: left;
margin: 0 8px 0 -15px; margin: 0 8px 0 -15px;
height: 32px; height: 32px;

View File

@ -1,3 +1,4 @@
import { animate, animateChild, query, stagger, style, transition, trigger } from '@angular/animations';
import { Component, EventEmitter, Input, Output } from '@angular/core'; import { Component, EventEmitter, Input, Output } from '@angular/core';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
@ -5,12 +6,28 @@ import { BehaviorSubject } from 'rxjs';
selector: 'app-contributors', selector: 'app-contributors',
templateUrl: './contributors.component.html', templateUrl: './contributors.component.html',
styleUrls: ['./contributors.component.scss'], styleUrls: ['./contributors.component.scss'],
animations: [
trigger('list', [
transition(':enter', [
query('@animate',
stagger(80, animateChild()),
),
]),
]),
trigger('animate', [
transition(':enter', [
style({ opacity: 0, transform: 'translateX(100%)' }),
animate('100ms', style({ opacity: 1, transform: 'translateX(0)' })),
]),
]),
],
}) })
export class ContributorsComponent { export class ContributorsComponent {
@Input() title: string = ''; @Input() title: string = '';
@Input() description: string = ''; @Input() description: string = '';
@Input() disabled: boolean = false; @Input() disabled: boolean = false;
@Input() totalResult: number = 0; @Input() totalResult: number = 0;
@Input() loading: boolean = false;
@Input() membersSubject!: BehaviorSubject<any[]>; @Input() membersSubject!: BehaviorSubject<any[]>;
@Output() addClicked: EventEmitter<void> = new EventEmitter(); @Output() addClicked: EventEmitter<void> = new EventEmitter();
@Output() showDetailClicked: EventEmitter<void> = new EventEmitter(); @Output() showDetailClicked: EventEmitter<void> = new EventEmitter();

View File

@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { AvatarModule } from '../avatar/avatar.module'; import { AvatarModule } from '../avatar/avatar.module';
@ -17,6 +18,7 @@ import { ContributorsComponent } from './contributors.component';
MatIconModule, MatIconModule,
MatTooltipModule, MatTooltipModule,
MatButtonModule, MatButtonModule,
MatProgressSpinnerModule,
], ],
exports: [ exports: [
ContributorsComponent, ContributorsComponent,

View File

@ -3,10 +3,10 @@
<p class="desc"> {{data.descriptionKey | translate}}</p> <p class="desc"> {{data.descriptionKey | translate}}</p>
</div> </div>
<div mat-dialog-actions class="action"> <div mat-dialog-actions class="action">
<button mat-button (click)="closeDialog()"> <button *ngIf="data.cancelKey" mat-button (click)="closeDialog()">
{{data.cancelKey | translate}} {{data.cancelKey | translate}}
</button> </button>
<span class="fill-space"></span>
<button color="warn" mat-raised-button class="ok-button" (click)="closeDialogWithSuccess()"> <button color="warn" mat-raised-button class="ok-button" (click)="closeDialogWithSuccess()">
{{data.confirmKey | translate}} {{data.confirmKey | translate}}
</button> </button>

View File

@ -10,12 +10,15 @@
.action { .action {
display: flex; display: flex;
justify-content: flex-end;
.ok-button { .ok-button {
margin-left: 0.5rem; margin-left: 0.5rem;
} }
.fill-space {
flex: 1;
}
button { button {
border-radius: 0.5rem; border-radius: 0.5rem;
} }

View File

@ -1,9 +1,17 @@
<div class="wrapper"> <div class="wrapper max-width-container">
<div class="header"> <div class="header" *ngIf="(authService.user | async) || {} as user">
<img alt="zitadel logo" *ngIf="dark; else lighttheme" <app-avatar [routerLink]="['/users/me']" *ngIf="user && (user.displayName || (user.firstName && user.lastName))"
src="../assets/images/zitadel-logo-oneline-darkdesign.svg" /> class="avatar" [name]="user.displayName ? user.displayName : (user.firstName + ' '+ user.lastName)"
<ng-template #lighttheme> [size]="100">
<img alt="zitadel logo" src="../assets/images/zitadel-logo-oneline-lightdesign.svg" /> </app-avatar>
<h3 *ngIf="(user.displayName || user.firstName || user.lastName); else loader">
{{'HOME.WELCOME' | translate}},
{{user?.displayName ? user.displayName : (user.firstName + ' '+ user.lastName)}}</h3>
<p>{{user?.userName}}</p>
<p class="wlc_stnce">{{'HOME.WELCOMESENTENCE' | translate}}</p>
<ng-template #loader>
<mat-spinner diameter="50"></mat-spinner>
</ng-template> </ng-template>
</div> </div>
@ -77,9 +85,14 @@
</div> </div>
<span class="fill-space"></span> <span class="fill-space"></span>
<div class="footer"> <div class="footer">
<a color="primary" mat-button [routerLink]="['/users/me']">{{'HOME.USERS_BUTTON' | translate}}</a> <a color="primary" mat-button [routerLink]="['/users/all']">{{'HOME.USERS_BUTTON' | translate}}</a>
</div> </div>
</app-card> </app-card>
</ng-template> </ng-template>
</div> </div>
<p class="disclaimer">{{'HOME.DISCLAIMER' | translate}}
<!-- TODO: Add doc link to security here -->
<!-- <a href="https://caos.github.io/site#security"></a> -->
</p>
</div> </div>

View File

@ -1,39 +1,25 @@
.wrapper { .wrapper {
width: 80%; padding-bottom: 100px;
max-width: 1350px;
@media only screen and (max-width: 700px) {
width: 90%;
}
margin: auto;
.header { .header {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin: 4rem 0; margin: 4rem 0;
align-items: center; align-items: center;
.avatar { h3 {
width: 100px; font-size: 24px;
height: 100px; margin-bottom: 1rem;
font-size: 100px;
line-height: 100px;
margin-top: 1rem;
}
h1 {
font-size: 3rem;
}
p {
color: #8795a1;
text-align: center;
font-size: 1rem;
margin: 0;
} }
img { .wlc_stnce {
height: 60px; color: #8795a1;
align-self: flex-end; font-size: 16px;
}
.avatar {
outline: none;
cursor: pointer;
} }
} }
@ -84,5 +70,11 @@
} }
} }
} }
} }
.disclaimer {
color: #8795a1;
font-size: 14px;
margin-top: 3rem;
}
} }

View File

@ -1,4 +1,5 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { AuthService } from 'src/app/services/auth.service';
@Component({ @Component({
selector: 'app-home', selector: 'app-home',
@ -7,9 +8,9 @@ import { Component } from '@angular/core';
}) })
export class HomeComponent { export class HomeComponent {
public dark: boolean = true; public dark: boolean = true;
constructor(public authService: AuthService) {
constructor() {
const theme = localStorage.getItem('theme'); const theme = localStorage.getItem('theme');
this.dark = theme === 'dark-theme' ? true : theme === 'light-theme' ? false : true; this.dark = theme === 'dark-theme' ? true : theme === 'light-theme' ? false : true;
} }
} }

View File

@ -2,8 +2,10 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module'; import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { AvatarModule } from 'src/app/modules/avatar/avatar.module';
import { CardModule } from 'src/app/modules/card/card.module'; import { CardModule } from 'src/app/modules/card/card.module';
import { SharedModule } from 'src/app/modules/shared/shared.module'; import { SharedModule } from 'src/app/modules/shared/shared.module';
@ -21,7 +23,9 @@ import { HomeComponent } from './home.component';
HomeRoutingModule, HomeRoutingModule,
MatButtonModule, MatButtonModule,
TranslateModule, TranslateModule,
AvatarModule,
SharedModule, SharedModule,
MatProgressSpinnerModule,
CardModule, CardModule,
], ],
}) })

View File

@ -1,8 +1,7 @@
import { DataSource } from '@angular/cdk/collections'; import { DataSource } from '@angular/cdk/collections';
import { BehaviorSubject, from, Observable, of } from 'rxjs'; import { BehaviorSubject, from, Observable, of } from 'rxjs';
import { catchError, finalize, map } from 'rxjs/operators'; import { catchError, finalize, map } from 'rxjs/operators';
import { IamMemberSearchResponse } from 'src/app/proto/generated/admin_pb'; import { IamMemberView } from 'src/app/proto/generated/admin_pb';
import { ProjectMember } from 'src/app/proto/generated/management_pb';
import { AdminService } from 'src/app/services/admin.service'; import { AdminService } from 'src/app/services/admin.service';
/** /**
@ -10,9 +9,9 @@ import { AdminService } from 'src/app/services/admin.service';
* encapsulate all logic for fetching and manipulating the displayed data * encapsulate all logic for fetching and manipulating the displayed data
* (including sorting, pagination, and filtering). * (including sorting, pagination, and filtering).
*/ */
export class IamMembersDataSource extends DataSource<ProjectMember.AsObject> { export class IamMembersDataSource extends DataSource<IamMemberView.AsObject> {
public totalResult: number = 0; public totalResult: number = 0;
public membersSubject: BehaviorSubject<ProjectMember.AsObject[]> = new BehaviorSubject<ProjectMember.AsObject[]>([]); public membersSubject: BehaviorSubject<IamMemberView.AsObject[]> = new BehaviorSubject<IamMemberView.AsObject[]>([]);
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false); private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable(); public loading$: Observable<boolean> = this.loadingSubject.asObservable();
@ -21,25 +20,21 @@ export class IamMembersDataSource extends DataSource<ProjectMember.AsObject> {
} }
public loadMembers( public loadMembers(
pageIndex: number, pageSize: number, grantId?: string, sortDirection?: string): void { pageIndex: number, pageSize: number): void {
const offset = pageIndex * pageSize; const offset = pageIndex * pageSize;
this.loadingSubject.next(true); this.loadingSubject.next(true);
// TODO
const promise: Promise<IamMemberSearchResponse> = from(this.adminService.SearchIamMembers(pageSize, offset)).pipe(
this.adminService.SearchIamMembers(pageSize, offset); map(resp => {
if (promise) { this.totalResult = resp.toObject().totalResult;
from(promise).pipe( return resp.toObject().resultList;
map(resp => { }),
this.totalResult = resp.toObject().totalResult; catchError(() => of([])),
return resp.toObject().resultList; finalize(() => this.loadingSubject.next(false)),
}), ).subscribe(members => {
catchError(() => of([])), this.membersSubject.next(members);
finalize(() => this.loadingSubject.next(false)), });
).subscribe(members => {
this.membersSubject.next(members);
});
}
} }
@ -48,7 +43,7 @@ export class IamMembersDataSource extends DataSource<ProjectMember.AsObject> {
* the returned stream emits new items. * the returned stream emits new items.
* @returns A stream of the items to be rendered. * @returns A stream of the items to be rendered.
*/ */
public connect(): Observable<ProjectMember.AsObject[]> { public connect(): Observable<IamMemberView.AsObject[]> {
return this.membersSubject.asObservable(); return this.membersSubject.asObservable();
} }

View File

@ -5,6 +5,7 @@ import { MatPaginator } from '@angular/material/paginator';
import { MatTable } from '@angular/material/table'; import { MatTable } from '@angular/material/table';
import { tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component'; import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component';
import { IamMemberView } from 'src/app/proto/generated/admin_pb';
import { ProjectMember, ProjectType, User } from 'src/app/proto/generated/management_pb'; import { ProjectMember, ProjectType, User } from 'src/app/proto/generated/management_pb';
import { AdminService } from 'src/app/services/admin.service'; import { AdminService } from 'src/app/services/admin.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
@ -20,9 +21,9 @@ export class IamMembersComponent implements AfterViewInit {
public projectType: ProjectType = ProjectType.PROJECTTYPE_OWNED; public projectType: ProjectType = ProjectType.PROJECTTYPE_OWNED;
public disabled: boolean = false; public disabled: boolean = false;
@ViewChild(MatPaginator) public paginator!: MatPaginator; @ViewChild(MatPaginator) public paginator!: MatPaginator;
@ViewChild(MatTable) public table!: MatTable<ProjectMember.AsObject>; @ViewChild(MatTable) public table!: MatTable<IamMemberView.AsObject>;
public dataSource!: IamMembersDataSource; public dataSource!: IamMembersDataSource;
public selection: SelectionModel<ProjectMember.AsObject> = new SelectionModel<ProjectMember.AsObject>(true, []); public selection: SelectionModel<IamMemberView.AsObject> = new SelectionModel<IamMemberView.AsObject>(true, []);
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
public displayedColumns: string[] = ['select', 'firstname', 'lastname', 'username', 'email', 'roles']; public displayedColumns: string[] = ['select', 'firstname', 'lastname', 'username', 'email', 'roles'];
@ -32,7 +33,7 @@ export class IamMembersComponent implements AfterViewInit {
private toast: ToastService) { private toast: ToastService) {
this.dataSource = new IamMembersDataSource(this.adminService); this.dataSource = new IamMembersDataSource(this.adminService);
this.dataSource.loadMembers(0, 25, 'asc'); this.dataSource.loadMembers(0, 25);
} }
public ngAfterViewInit(): void { public ngAfterViewInit(): void {

View File

@ -41,7 +41,9 @@
<th mat-header-cell *matHeaderCellDef> {{ 'IAM.VIEWS.ACTIONS' | translate }} </th> <th mat-header-cell *matHeaderCellDef> {{ 'IAM.VIEWS.ACTIONS' | translate }} </th>
<td mat-cell *matCellDef="let role"> <td mat-cell *matCellDef="let role">
<button mat-icon-button matTooltip="{{'IAM.VIEWS.CLEAR' | translate}}" <button mat-icon-button matTooltip="{{'IAM.VIEWS.CLEAR' | translate}}"
(click)="cancelView(role.viewName, role.database)"><i class="las la-redo-alt"></i></button> (click)="cancelView(role.viewName, role.database)">
<mat-icon svgIcon="mdi_broom"></mat-icon>
</button>
</td> </td>
</ng-container> </ng-container>

View File

@ -9,9 +9,10 @@
</div> </div>
<div class="side" metainfo> <div class="side" metainfo>
<app-contributors [totalResult]="totalMemberResult" [membersSubject]="membersSubject" <app-contributors [totalResult]="totalMemberResult" [loading]="loading$ | async"
title="{{ 'PROJECT.MEMBER.TITLE' | translate }}" description="{{ 'PROJECT.MEMBER.TITLEDESC' | translate }}" [membersSubject]="membersSubject" title="{{ 'PROJECT.MEMBER.TITLE' | translate }}"
(addClicked)="openAddMember()" (showDetailClicked)="showDetail()" [disabled]="false"> description="{{ 'PROJECT.MEMBER.TITLEDESC' | translate }}" (addClicked)="openAddMember()"
(showDetailClicked)="showDetail()" [disabled]="false">
</app-contributors> </app-contributors>
</div> </div>
</app-meta-layout> </app-meta-layout>

View File

@ -39,8 +39,8 @@
<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"> <mat-tab label="Details">
<app-contributors [totalResult]="totalMemberResult" [membersSubject]="membersSubject" <app-contributors [totalResult]="totalMemberResult" [loading]="loading$ | async"
title="{{ 'PROJECT.MEMBER.TITLE' | translate }}" [membersSubject]="membersSubject" title="{{ 'PROJECT.MEMBER.TITLE' | translate }}"
description="{{ 'PROJECT.MEMBER.TITLEDESC' | translate }}" (addClicked)="openAddMember()" description="{{ 'PROJECT.MEMBER.TITLEDESC' | translate }}" (addClicked)="openAddMember()"
(showDetailClicked)="showDetail()" [disabled]="false"> (showDetailClicked)="showDetail()" [disabled]="false">
</app-contributors> </app-contributors>

View File

@ -1,17 +1,12 @@
import { DataSource } from '@angular/cdk/collections'; import { DataSource } from '@angular/cdk/collections';
import { BehaviorSubject, from, Observable, of } from 'rxjs'; import { BehaviorSubject, from, Observable, of } from 'rxjs';
import { catchError, finalize, map } from 'rxjs/operators'; import { catchError, finalize, map } from 'rxjs/operators';
import { ProjectMember, ProjectMemberSearchResponse } from 'src/app/proto/generated/management_pb'; import { OrgMemberView } from 'src/app/proto/generated/management_pb';
import { OrgService } from 'src/app/services/org.service'; import { OrgService } from 'src/app/services/org.service';
/** export class OrgMembersDataSource extends DataSource<OrgMemberView.AsObject> {
* Data source for the ProjectMembers view. This class should
* encapsulate all logic for fetching and manipulating the displayed data
* (including sorting, pagination, and filtering).
*/
export class ProjectMembersDataSource extends DataSource<ProjectMember.AsObject> {
public totalResult: number = 0; public totalResult: number = 0;
public membersSubject: BehaviorSubject<ProjectMember.AsObject[]> = new BehaviorSubject<ProjectMember.AsObject[]>([]); public membersSubject: BehaviorSubject<OrgMemberView.AsObject[]> = new BehaviorSubject<OrgMemberView.AsObject[]>([]);
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false); private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable(); public loading$: Observable<boolean> = this.loadingSubject.asObservable();
@ -19,26 +14,20 @@ export class ProjectMembersDataSource extends DataSource<ProjectMember.AsObject>
super(); super();
} }
public loadMembers( public loadMembers(pageIndex: number, pageSize: number): void {
pageIndex: number, pageSize: number, grantId?: string, sortDirection?: string): void {
const offset = pageIndex * pageSize; const offset = pageIndex * pageSize;
this.loadingSubject.next(true); this.loadingSubject.next(true);
// TODO from(this.orgService.SearchMyOrgMembers(pageSize, offset)).pipe(
const promise: Promise<ProjectMemberSearchResponse> = map(resp => {
this.orgService.SearchMyOrgMembers(pageSize, offset); this.totalResult = resp.toObject().totalResult;
if (promise) { return resp.toObject().resultList;
from(promise).pipe( }),
map(resp => { catchError(() => of([])),
this.totalResult = resp.toObject().totalResult; finalize(() => this.loadingSubject.next(false)),
return resp.toObject().resultList; ).subscribe(members => {
}), this.membersSubject.next(members);
catchError(() => of([])), });
finalize(() => this.loadingSubject.next(false)),
).subscribe(members => {
this.membersSubject.next(members);
});
}
} }
@ -47,7 +36,7 @@ export class ProjectMembersDataSource extends DataSource<ProjectMember.AsObject>
* the returned stream emits new items. * the returned stream emits new items.
* @returns A stream of the items to be rendered. * @returns A stream of the items to be rendered.
*/ */
public connect(): Observable<ProjectMember.AsObject[]> { public connect(): Observable<OrgMemberView.AsObject[]> {
return this.membersSubject.asObservable(); return this.membersSubject.asObservable();
} }

View File

@ -5,11 +5,11 @@ import { MatPaginator } from '@angular/material/paginator';
import { MatTable } from '@angular/material/table'; import { MatTable } from '@angular/material/table';
import { tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component'; import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component';
import { Org, ProjectMember, ProjectType, User } from 'src/app/proto/generated/management_pb'; import { Org, OrgMemberView, ProjectMember, ProjectType, User } from 'src/app/proto/generated/management_pb';
import { OrgService } from 'src/app/services/org.service'; import { OrgService } from 'src/app/services/org.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
import { ProjectMembersDataSource } from './org-members-datasource'; import { OrgMembersDataSource } from './org-members-datasource';
@Component({ @Component({
selector: 'app-org-members', selector: 'app-org-members',
@ -21,9 +21,9 @@ export class OrgMembersComponent implements AfterViewInit {
public projectType: ProjectType = ProjectType.PROJECTTYPE_OWNED; public projectType: ProjectType = ProjectType.PROJECTTYPE_OWNED;
public disabled: boolean = false; public disabled: boolean = false;
@ViewChild(MatPaginator) public paginator!: MatPaginator; @ViewChild(MatPaginator) public paginator!: MatPaginator;
@ViewChild(MatTable) public table!: MatTable<ProjectMember.AsObject>; @ViewChild(MatTable) public table!: MatTable<OrgMemberView.AsObject>;
public dataSource!: ProjectMembersDataSource; public dataSource!: OrgMembersDataSource;
public selection: SelectionModel<ProjectMember.AsObject> = new SelectionModel<ProjectMember.AsObject>(true, []); public selection: SelectionModel<OrgMemberView.AsObject> = new SelectionModel<OrgMemberView.AsObject>(true, []);
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
public displayedColumns: string[] = ['select', 'firstname', 'lastname', 'username', 'email', 'roles']; public displayedColumns: string[] = ['select', 'firstname', 'lastname', 'username', 'email', 'roles'];
@ -33,8 +33,8 @@ export class OrgMembersComponent implements AfterViewInit {
private toast: ToastService) { private toast: ToastService) {
this.orgService.GetMyOrg().then(org => { this.orgService.GetMyOrg().then(org => {
this.org = org.toObject(); this.org = org.toObject();
this.dataSource = new ProjectMembersDataSource(this.orgService); this.dataSource = new OrgMembersDataSource(this.orgService);
this.dataSource.loadMembers(0, 25, 'asc'); this.dataSource.loadMembers(0, 25);
}); });
} }

View File

@ -41,29 +41,13 @@
</form> </form>
</mat-step> </mat-step>
<mat-step [editable]="true"> <mat-step [editable]="true"
<ng-template matStepLabel>{{'APP.OIDC.GRANTSECTION' | translate}}</ng-template> *ngIf="oidcApp.applicationType === OIDCApplicationType.OIDCAPPLICATIONTYPE_USER_AGENT">
<p class="step-title">{{'APP.OIDC.GRANTTITLE' | translate}}</p>
<div class="checkbox-container">
<mat-checkbox (change)="changeGrant()" *ngFor="let granttype of oidcGrantTypes" color="primary"
[(ngModel)]="granttype.checked" [disabled]="granttype.disabled">
{{'APP.OIDC.GRANT'+granttype.type | translate}}
</mat-checkbox>
</div>
<div class="actions">
<button mat-stroked-button color="primary" matStepperPrevious>{{'ACTIONS.BACK' | translate}}</button>
<button mat-raised-button color="primary" matStepperNext>{{'ACTIONS.CONTINUE' | translate}}</button>
</div>
</mat-step>
<mat-step [editable]="true" *ngIf="!grantTypeChecked(OIDCGrantType.OIDCGRANTTYPE_AUTHORIZATION_CODE)">
<ng-template matStepLabel>{{'APP.OIDC.RESPONSESECTION' | translate}}</ng-template> <ng-template matStepLabel>{{'APP.OIDC.RESPONSESECTION' | translate}}</ng-template>
<div class="checkbox-container"> <div class="checkbox-container">
<mat-checkbox *ngFor="let responsetype of oidcResponseTypes | keyvalue" (change)="changeResponseType()" <mat-checkbox *ngFor="let responsetype of oidcResponseTypes" (change)="changeResponseType()"
color="primary" [(ngModel)]="responsetype.checked"> color="primary" [(ngModel)]="responsetype.checked" [disabled]="responsetype.disabled">
{{'APP.OIDC.RESPONSE'+responsetype.key | translate}} {{'APP.OIDC.RESPONSE'+responsetype.type | translate}}
</mat-checkbox> </mat-checkbox>
</div> </div>
<div class="actions"> <div class="actions">
@ -72,8 +56,8 @@
</div> </div>
</mat-step> </mat-step>
<mat-step [stepControl]="secondFormGroup" [editable]="true" <mat-step *ngIf="oidcApp.applicationType === OIDCApplicationType.OIDCAPPLICATIONTYPE_WEB"
*ngIf="applicationType?.value !== OIDCApplicationType.OIDCAPPLICATIONTYPE_NATIVE"> [stepControl]="secondFormGroup" [editable]="true">
<form [formGroup]="secondFormGroup"> <form [formGroup]="secondFormGroup">
<ng-template matStepLabel>{{'APP.OIDC.AUTHMETHODSECTION' | translate}}</ng-template> <ng-template matStepLabel>{{'APP.OIDC.AUTHMETHODSECTION' | translate}}</ng-template>
@ -157,19 +141,32 @@
<span class="left"> <span class="left">
{{ 'APP.GRANT' | translate }} {{ 'APP.GRANT' | translate }}
</span> </span>
<span class="right"> <span class="right" *ngIf="oidcApp.grantTypesList?.length > 0">
<span *ngFor="let element of oidcApp.grantTypesList"> [<span *ngFor="let element of oidcApp.grantTypesList; index as i">
{{'APP.OIDC.GRANT'+element | translate}} {{'APP.OIDC.GRANT'+element | translate}}
</span> {{i < oidcApp.grantTypesList.length - 1 ? ', ': ''}}
</span>]
</span> </span>
</div> </div>
<div class="row"> <div class="row">
<span class="left"> <span class="left">
{{ 'APP.OIDC.RESPONSE' | translate }} {{ 'APP.OIDC.RESPONSE' | translate }}
</span> </span>
<span class="right" *ngIf="oidcApp.responseTypesList?.length > 0">
[<span *ngFor="let element of oidcApp.responseTypesList; index as i">
{{('APP.OIDC.RESPONSE'+element | translate)}}
{{i < oidcApp.responseTypesList.length - 1 ? ', ': ''}}
</span>]
</span>
</div>
<div class="row">
<span class="left">
{{ 'APP.OIDC.AUTHMETHOD' | translate }}
</span>
<span class="right"> <span class="right">
<span *ngFor="let element of oidcApp.responseTypesList"> <span>
{{'APP.OIDC.RESPONSE'+element | translate}} {{'APP.OIDC.AUTHMETHOD'+oidcApp?.authMethodType | translate}}
</span> </span>
</span> </span>
</div> </div>
@ -178,10 +175,11 @@
<span class="left"> <span class="left">
{{ 'APP.OIDC.REDIRECT' | translate }} {{ 'APP.OIDC.REDIRECT' | translate }}
</span> </span>
<span class="right"> <span class="right" *ngIf="oidcApp.redirectUrisList?.length > 0">
<span *ngFor="let redirect of oidcApp.redirectUrisList"> [<span *ngFor="let redirect of oidcApp.redirectUrisList; index as i">
{{redirect}} {{redirect}}
</span> {{i < oidcApp.redirectUrisList.length - 1 ? ', ': ''}}
</span>]
</span> </span>
</div> </div>
@ -189,10 +187,11 @@
<span class="left"> <span class="left">
{{ 'APP.OIDC.POSTLOGOUTREDIRECT' | translate }} {{ 'APP.OIDC.POSTLOGOUTREDIRECT' | translate }}
</span> </span>
<span class="right"> <span class="right" *ngIf="oidcApp.postLogoutRedirectUrisList?.length > 0">
<span *ngFor="let redirect of oidcApp.postLogoutRedirectUrisList"> [<span *ngFor="let redirect of oidcApp.postLogoutRedirectUrisList; index as i">
{{redirect}} {{redirect}}
</span> {{i < oidcApp.postLogoutRedirectUrisList.length - 1 ? ', ': ''}}
</span>]
</span> </span>
</div> </div>

View File

@ -14,7 +14,11 @@ p.desc {
} }
.container { .container {
padding: 4rem 4rem 2rem 4rem; padding: 4rem 4rem 2rem 4rem;
mat-progress-bar {
margin-bottom: 1rem;
}
.abort-container { .abort-container {
display: flex; display: flex;

View File

@ -32,11 +32,36 @@ export class AppCreateComponent implements OnInit, OnDestroy {
public loading: boolean = false; public loading: boolean = false;
public oidcApp: OIDCApplicationCreate.AsObject = new OIDCApplicationCreate().toObject(); public oidcApp: OIDCApplicationCreate.AsObject = new OIDCApplicationCreate().toObject();
public oidcResponseTypes: { type: OIDCResponseType, checked: boolean; }[] = [ public oidcResponseTypes: { type: OIDCResponseType, checked: boolean; disabled: boolean; }[] = [
{ type: OIDCResponseType.OIDCRESPONSETYPE_CODE, checked: false }, { type: OIDCResponseType.OIDCRESPONSETYPE_CODE, checked: false, disabled: false },
{ type: OIDCResponseType.OIDCRESPONSETYPE_ID_TOKEN, checked: false }, { type: OIDCResponseType.OIDCRESPONSETYPE_ID_TOKEN, checked: false, disabled: false },
{ type: OIDCResponseType.OIDCRESPONSETYPE_ID_TOKEN_TOKEN, checked: false }, { type: OIDCResponseType.OIDCRESPONSETYPE_ID_TOKEN_TOKEN, checked: false, disabled: false },
]; ];
public oidcAppTypes: OIDCApplicationType[] = [
OIDCApplicationType.OIDCAPPLICATIONTYPE_WEB,
OIDCApplicationType.OIDCAPPLICATIONTYPE_USER_AGENT,
OIDCApplicationType.OIDCAPPLICATIONTYPE_NATIVE,
];
public oidcAuthMethodType: { type: OIDCAuthMethodType, checked: boolean, disabled: boolean; }[] = [
{ type: OIDCAuthMethodType.OIDCAUTHMETHODTYPE_BASIC, checked: false, disabled: false },
{ type: OIDCAuthMethodType.OIDCAUTHMETHODTYPE_NONE, checked: false, disabled: false },
{ type: OIDCAuthMethodType.OIDCAUTHMETHODTYPE_POST, checked: false, disabled: false },
];
// stepper
firstFormGroup!: FormGroup;
secondFormGroup!: FormGroup;
// thirdFormGroup!: FormGroup;
// devmode
public form!: FormGroup;
public OIDCApplicationType: any = OIDCApplicationType;
public OIDCGrantType: any = OIDCGrantType;
public OIDCAuthMethodType: any = OIDCAuthMethodType;
public oidcGrantTypes: { public oidcGrantTypes: {
type: OIDCGrantType, type: OIDCGrantType,
checked: boolean, checked: boolean,
@ -47,28 +72,6 @@ export class AppCreateComponent implements OnInit, OnDestroy {
// { type: OIDCGrantType.OIDCGRANTTYPE_REFRESH_TOKEN, checked: false, disabled: true }, // { type: OIDCGrantType.OIDCGRANTTYPE_REFRESH_TOKEN, checked: false, disabled: true },
// TODO show when implemented // TODO show when implemented
]; ];
public oidcAppTypes: OIDCApplicationType[] = [
OIDCApplicationType.OIDCAPPLICATIONTYPE_WEB,
OIDCApplicationType.OIDCAPPLICATIONTYPE_USER_AGENT,
OIDCApplicationType.OIDCAPPLICATIONTYPE_NATIVE,
];
public oidcAuthMethodType: { type: OIDCAuthMethodType, checked: boolean, disabled: boolean; }[] = [
{ type: OIDCAuthMethodType.OIDCAUTHMETHODTYPE_BASIC, checked: false, disabled: false },
{ type: OIDCAuthMethodType.OIDCAUTHMETHODTYPE_NONE, checked: false, disabled: false },
{ type: OIDCAuthMethodType.OIDCAUTHMETHODTYPE_POST, checked: false, disabled: false },
];
// stepper
firstFormGroup!: FormGroup;
secondFormGroup!: FormGroup;
thirdFormGroup!: FormGroup;
// devmode
public form!: FormGroup;
public OIDCApplicationType: any = OIDCApplicationType;
public OIDCGrantType: any = OIDCGrantType;
public OIDCAuthMethodType: any = OIDCAuthMethodType;
public readonly separatorKeysCodes: number[] = [ENTER, COMMA, SPACE]; public readonly separatorKeysCodes: number[] = [ENTER, COMMA, SPACE];
@ -92,8 +95,8 @@ export class AppCreateComponent implements OnInit, OnDestroy {
this.form.valueChanges.pipe(debounceTime(300)).subscribe((value) => { this.form.valueChanges.pipe(debounceTime(300)).subscribe((value) => {
this.oidcApp.name = this.formname?.value; this.oidcApp.name = this.formname?.value;
this.oidcApp.applicationType = this.formapplicationType?.value; this.oidcApp.applicationType = this.formapplicationType?.value;
this.oidcApp.grantTypesList = this.formgrantTypesList?.value;
this.oidcApp.responseTypesList = this.formresponseTypesList?.value; this.oidcApp.responseTypesList = this.formresponseTypesList?.value;
this.oidcApp.grantTypesList = this.formgrantTypesList?.value;
this.oidcApp.authMethodType = this.formauthMethodType?.value; this.oidcApp.authMethodType = this.formauthMethodType?.value;
console.log(this.oidcApp); console.log(this.oidcApp);
@ -103,48 +106,60 @@ export class AppCreateComponent implements OnInit, OnDestroy {
name: ['', [Validators.required]], name: ['', [Validators.required]],
applicationType: ['', [Validators.required]], applicationType: ['', [Validators.required]],
}); });
this.firstFormGroup.valueChanges.subscribe(value => {
if (this.firstFormGroup.valid) {
console.log('change firstform', value);
switch (value.applicationType) {
case OIDCApplicationType.OIDCAPPLICATIONTYPE_NATIVE:
console.log('NATIVE');
this.oidcResponseTypes[0].checked = true;
this.oidcApp.responseTypesList = [OIDCResponseType.OIDCRESPONSETYPE_CODE];
this.oidcApp.grantTypesList =
[OIDCGrantType.OIDCGRANTTYPE_AUTHORIZATION_CODE];
this.oidcApp.authMethodType = OIDCAuthMethodType.OIDCAUTHMETHODTYPE_NONE;
break;
case OIDCApplicationType.OIDCAPPLICATIONTYPE_WEB:
console.log('WEB');
this.oidcAuthMethodType[0].disabled = false;
this.oidcAuthMethodType[1].disabled = true; // NONE DISABLED
this.oidcAuthMethodType[2].disabled = false; // POST POSSIBLE
this.authMethodType?.setValue(OIDCAuthMethodType.OIDCAUTHMETHODTYPE_BASIC); // BASIC DEFAULT
this.oidcApp.authMethodType = OIDCAuthMethodType.OIDCAUTHMETHODTYPE_BASIC;
this.oidcResponseTypes[0].checked = true;
this.oidcApp.responseTypesList = [OIDCResponseType.OIDCRESPONSETYPE_CODE];
this.changeResponseType();
this.oidcApp.grantTypesList =
[OIDCGrantType.OIDCGRANTTYPE_AUTHORIZATION_CODE];
break;
case OIDCApplicationType.OIDCAPPLICATIONTYPE_USER_AGENT:
console.log('USERAGENT');
this.oidcResponseTypes[0].checked = true;
this.oidcApp.responseTypesList = [OIDCResponseType.OIDCRESPONSETYPE_CODE];
this.oidcApp.grantTypesList =
[OIDCGrantType.OIDCGRANTTYPE_AUTHORIZATION_CODE, OIDCGrantType.OIDCGRANTTYPE_IMPLICIT];
this.oidcApp.authMethodType = OIDCAuthMethodType.OIDCAUTHMETHODTYPE_NONE;
break;
}
this.oidcApp.name = this.name?.value;
this.oidcApp.applicationType = this.applicationType?.value;
}
});
this.secondFormGroup = this.fb.group({ this.secondFormGroup = this.fb.group({
authMethodType: [OIDCAuthMethodType.OIDCAUTHMETHODTYPE_BASIC, [Validators.required]], authMethodType: [OIDCAuthMethodType.OIDCAUTHMETHODTYPE_BASIC, [Validators.required]],
}); });
this.oidcApp.authMethodType = OIDCAuthMethodType.OIDCAUTHMETHODTYPE_BASIC;
this.firstFormGroup.valueChanges.subscribe(value => {
if (this.applicationType?.value === OIDCApplicationType.OIDCAPPLICATIONTYPE_NATIVE) {
this.oidcResponseTypes[0].checked = true;
// this.oidcApp.responseTypesList = [this.oidcResponseTypes[0].type];
this.authMethodType?.setValue(OIDCAuthMethodType.OIDCAUTHMETHODTYPE_NONE);
this.oidcAuthMethodType[1].disabled = true;
}
if (this.applicationType?.value === OIDCApplicationType.OIDCAPPLICATIONTYPE_WEB) {
this.oidcResponseTypes[0].checked = true;
this.oidcApp.responseTypesList = [this.oidcResponseTypes[0].type];
this.authMethodType?.setValue(OIDCAuthMethodType.OIDCAUTHMETHODTYPE_NONE);
this.oidcAuthMethodType[0].disabled = true;
this.oidcAuthMethodType[2].disabled = true;
}
if (this.applicationType?.value === OIDCApplicationType.OIDCAPPLICATIONTYPE_USER_AGENT) {
this.oidcResponseTypes[0].checked = true;
this.oidcApp.responseTypesList = [this.oidcResponseTypes[0].type];
this.authMethodType?.setValue(OIDCAuthMethodType.OIDCAUTHMETHODTYPE_NONE);
this.oidcAuthMethodType[0].disabled = true;
this.oidcAuthMethodType[2].disabled = true;
this.oidcGrantTypes[1].disabled = false;
}
this.changeResponseType();
this.changeGrant();
this.oidcApp.name = this.name?.value;
this.oidcApp.applicationType = this.applicationType?.value;
});
this.secondFormGroup.valueChanges.subscribe(value => { this.secondFormGroup.valueChanges.subscribe(value => {
this.oidcApp.authMethodType = this.authMethodType?.value; this.oidcApp.authMethodType = value.authMethodType;
}); });
} }
@ -226,14 +241,14 @@ export class AppCreateComponent implements OnInit, OnDestroy {
} }
} }
changeGrant(): void {
this.oidcApp.grantTypesList = this.oidcGrantTypes.filter(gt => gt.checked).map(gt => gt.type);
}
changeResponseType(): void { changeResponseType(): void {
this.oidcApp.responseTypesList = this.oidcResponseTypes.filter(gt => gt.checked).map(gt => gt.type); this.oidcApp.responseTypesList = this.oidcResponseTypes.filter(gt => gt.checked).map(gt => gt.type);
} }
moreThanOneOption(options: Array<{ type: OIDCGrantType, checked: boolean, disabled: boolean; }>): boolean {
return options.filter(option => option.disabled === false).length > 1;
}
get name(): AbstractControl | null { get name(): AbstractControl | null {
return this.firstFormGroup.get('name'); return this.firstFormGroup.get('name');
} }

View File

@ -86,7 +86,9 @@
<mat-form-field class="full-width" appearance="outline"> <mat-form-field class="full-width" appearance="outline">
<mat-label>{{ 'APP.OIDC.REDIRECT' | translate }}</mat-label> <mat-label>{{ 'APP.OIDC.REDIRECT' | translate }}</mat-label>
<mat-chip-list #chipRedirectList> <mat-chip-list #chipRedirectList>
<mat-chip *ngFor="let redirect of redirectUrisList" [selectable]="selectable" <mat-chip *ngFor="let redirect of redirectUrisList" selected
[matTooltip]="!redirect.startsWith('https://') ? ('APP.OIDC.UNSECUREREDIRECT' | translate): ''"
[color]="!redirect.startsWith('https://') ? 'warn': 'green'"
(removed)="remove(redirect, RedirectType.REDIRECT)"> (removed)="remove(redirect, RedirectType.REDIRECT)">
{{redirect}} {{redirect}}
<mat-icon matChipRemove *ngIf="removable">cancel</mat-icon> <mat-icon matChipRemove *ngIf="removable">cancel</mat-icon>
@ -100,8 +102,10 @@
<mat-form-field class="full-width" appearance="outline"> <mat-form-field class="full-width" appearance="outline">
<mat-label>{{ 'APP.OIDC.POSTLOGOUTREDIRECT' | translate }}</mat-label> <mat-label>{{ 'APP.OIDC.POSTLOGOUTREDIRECT' | translate }}</mat-label>
<mat-chip-list #chipPostRedirectList> <mat-chip-list #chipPostRedirectList>
<mat-chip *ngFor="let redirect of postLogoutRedirectUrisList" [selectable]="selectable" <mat-chip *ngFor="let redirect of postLogoutRedirectUrisList" selected
(removed)="remove(redirect, RedirectType.POSTREDIRECT)"> (removed)="remove(redirect, RedirectType.POSTREDIRECT)"
[matTooltip]="!redirect.startsWith('https://') ? ('APP.OIDC.UNSECUREREDIRECT' | translate): ''"
[color]="!redirect.startsWith('https://') ? 'warn': 'green'">
{{redirect}} {{redirect}}
<mat-icon matChipRemove *ngIf="removable">cancel</mat-icon> <mat-icon matChipRemove *ngIf="removable">cancel</mat-icon>
</mat-chip> </mat-chip>

View File

@ -84,4 +84,8 @@
border-radius: .5rem; border-radius: .5rem;
margin: 0 .5rem; margin: 0 .5rem;
} }
}
mat-chip[color='green'] {
background-color: #56a392 !important;
} }

View File

@ -35,7 +35,6 @@ enum RedirectType {
}) })
export class AppDetailComponent implements OnInit, OnDestroy { export class AppDetailComponent implements OnInit, OnDestroy {
public errorMessage: string = ''; public errorMessage: string = '';
public selectable: boolean = false;
public removable: boolean = true; public removable: boolean = true;
public addOnBlur: boolean = true; public addOnBlur: boolean = true;
public readonly separatorKeysCodes: number[] = [ENTER, COMMA, SPACE]; public readonly separatorKeysCodes: number[] = [ENTER, COMMA, SPACE];
@ -219,7 +218,7 @@ export class AppDetailComponent implements OnInit, OnDestroy {
this.projectService this.projectService
.UpdateOIDCAppConfig(this.projectId, this.app.id, this.app.oidcConfig) .UpdateOIDCAppConfig(this.projectId, this.app.id, this.app.oidcConfig)
.then((data: OIDCConfig) => { .then(() => {
this.toast.showInfo('APP.TOAST.OIDCUPDATED', true); this.toast.showInfo('APP.TOAST.OIDCUPDATED', true);
}) })
.catch(error => { .catch(error => {

View File

@ -7,7 +7,7 @@
<div *ngIf="data.clientSecret" class="flex"> <div *ngIf="data.clientSecret" class="flex">
<button color="primary" [disabled]="copied == data.clientSecret" matTooltip="copy to clipboard" <button color="primary" [disabled]="copied == data.clientSecret" matTooltip="copy to clipboard"
(click)="copytoclipboard(data.clientSecret)" mat-icon-button> appCopyToClipboard [valueToCopy]="data.clientSecret" (copiedValue)="this.copied = $event" mat-icon-button>
<i *ngIf="copied != data.clientSecret" class="las la-clipboard"></i> <i *ngIf="copied != data.clientSecret" class="las la-clipboard"></i>
<i *ngIf="copied == data.clientSecret" class="las la-clipboard-check"></i> <i *ngIf="copied == data.clientSecret" class="las la-clipboard-check"></i>
</button> </button>

View File

@ -14,22 +14,4 @@ export class AppSecretDialogComponent {
public closeDialog(): void { public closeDialog(): void {
this.dialogRef.close(false); this.dialogRef.close(false);
} }
public copytoclipboard(value: string): void {
const selBox = document.createElement('textarea');
selBox.style.position = 'fixed';
selBox.style.left = '0';
selBox.style.top = '0';
selBox.style.opacity = '0';
selBox.value = value;
document.body.appendChild(selBox);
selBox.focus();
selBox.select();
document.execCommand('copy');
document.body.removeChild(selBox);
this.copied = value;
setTimeout(() => {
this.copied = '';
}, 3000);
}
} }

View File

@ -17,6 +17,7 @@ import { MatSelectModule } from '@angular/material/select';
import { MatStepperModule } from '@angular/material/stepper'; import { MatStepperModule } from '@angular/material/stepper';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { CopyToClipboardModule } from 'src/app/directives/copy-to-clipboard/copy-to-clipboard.module';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module'; import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { CardModule } from 'src/app/modules/card/card.module'; import { CardModule } from 'src/app/modules/card/card.module';
@ -55,6 +56,7 @@ import { AppsRoutingModule } from './apps-routing.module';
TranslateModule, TranslateModule,
MatStepperModule, MatStepperModule,
MatRadioModule, MatRadioModule,
CopyToClipboardModule,
], ],
exports: [TranslateModule], exports: [TranslateModule],
}) })

View File

@ -34,8 +34,8 @@
<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"> <mat-tab label="Details">
<app-contributors [totalResult]="totalMemberResult" [membersSubject]="membersSubject" <app-contributors [totalResult]="totalMemberResult" [loading]="loading$ | async"
title="{{ 'PROJECT.MEMBER.TITLE' | translate }}" [membersSubject]="membersSubject" title="{{ 'PROJECT.MEMBER.TITLE' | translate }}"
description="{{ 'PROJECT.MEMBER.TITLEDESC' | translate }}" (addClicked)="openAddMember()" description="{{ 'PROJECT.MEMBER.TITLEDESC' | translate }}" (addClicked)="openAddMember()"
(showDetailClicked)="showDetail()" [disabled]="false"> (showDetailClicked)="showDetail()" [disabled]="false">
</app-contributors> </app-contributors>

View File

@ -5,7 +5,7 @@ import { MatDialog } from '@angular/material/dialog';
import { MatTableDataSource } from '@angular/material/table'; import { MatTableDataSource } from '@angular/material/table';
import { ActivatedRoute, Params, Router } from '@angular/router'; import { ActivatedRoute, Params, Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { BehaviorSubject, from, of, Subscription } from 'rxjs'; import { BehaviorSubject, from, Observable, of, Subscription } from 'rxjs';
import { catchError, finalize, map } from 'rxjs/operators'; import { catchError, finalize, map } from 'rxjs/operators';
import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component'; import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component';
import { ChangeType } from 'src/app/modules/changes/changes.component'; import { ChangeType } from 'src/app/modules/changes/changes.component';
@ -72,7 +72,8 @@ export class GrantedProjectDetailComponent implements OnInit, OnDestroy {
public totalMemberResult: number = 0; public totalMemberResult: number = 0;
public membersSubject: BehaviorSubject<ProjectMemberView.AsObject[]> public membersSubject: BehaviorSubject<ProjectMemberView.AsObject[]>
= new BehaviorSubject<ProjectMemberView.AsObject[]>([]); = new BehaviorSubject<ProjectMemberView.AsObject[]>([]);
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false); private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
constructor( constructor(
public translate: TranslateService, public translate: TranslateService,

View File

@ -26,7 +26,8 @@
</div> </div>
<button [ngClass]="{ selected: selection.isSelected(item)}" (click)="selection.toggle(item)" class="edit-button" <button [ngClass]="{ selected: selection.isSelected(item)}" (click)="selection.toggle(item)" class="edit-button"
mat-icon-button> mat-icon-button>
<mat-icon>push_pin_outline</mat-icon> <mat-icon *ngIf="selection.isSelected(item)" svgIcon="mdi_pin"></mat-icon>
<mat-icon svgIcon="mdi_pin_outline" *ngIf="!selection.isSelected(item)"></mat-icon>
</button> </button>
</div> </div>
@ -52,7 +53,8 @@
</div> </div>
<button [ngClass]="{ selected: selection.isSelected(item)}" (click)="selection.toggle(item)" class="edit-button" <button [ngClass]="{ selected: selection.isSelected(item)}" (click)="selection.toggle(item)" class="edit-button"
mat-icon-button> mat-icon-button>
<mat-icon>push_pin_outline</mat-icon> <mat-icon *ngIf="selection.isSelected(item)" svgIcon="mdi_pin"></mat-icon>
<mat-icon svgIcon="mdi_pin_outline" *ngIf="!selection.isSelected(item)"></mat-icon>
</button> </button>
</div> </div>
<p class="n-items" *ngIf="!loading && items.length === 0 && selection.selected.length === 0"> <p class="n-items" *ngIf="!loading && items.length === 0 && selection.selected.length === 0">

View File

@ -6,16 +6,21 @@
</button> </button>
</div> </div>
<div class="app-container"> <div class="app-container">
<div class="sp-container" *ngIf="(loading$ | async)">
<mat-spinner color="accent" diameter="50"></mat-spinner>
</div>
<div [routerLink]="['/projects', projectId, 'apps', app.id ]" class="app-wrap" <div [routerLink]="['/projects', projectId, 'apps', app.id ]" class="app-wrap"
*ngFor="let app of appsSubject | async"> *ngFor="let app of appsSubject | async">
<div class="morph-card"> <div class="morph-card" matRipple>
{{ app.name.charAt(0)}} {{ app.name.charAt(0)}}
</div> </div>
<span class="name">{{app.name}}</span> <span class="name">{{app.name}}</span>
</div> </div>
<ng-template appHasRole [appHasRole]="['project.app.write']"> <ng-template appHasRole [appHasRole]="['project.app.write']">
<div class="app-wrap" *ngIf="!disabled" [routerLink]="[ '/projects', projectId, 'apps', 'create']"> <div class="app-wrap" *ngIf="!disabled" [routerLink]="[ '/projects', projectId, 'apps', 'create']">
<div class="morph-card add"> <div class="morph-card add" matRipple>
<mat-icon>add</mat-icon> <mat-icon>add</mat-icon>
</div> </div>
<span class="name">{{ 'ACTIONS.NEW' | translate }}</span> <span class="name">{{ 'ACTIONS.NEW' | translate }}</span>

View File

@ -21,6 +21,14 @@
margin: 0 -1rem; margin: 0 -1rem;
padding-bottom: 2rem; padding-bottom: 2rem;
.sp-container {
display: flex;
justify-content: center;
align-items: center;
width: calc(82px + 2rem);
height: calc(82px + 2rem);
}
.app-wrap { .app-wrap {
outline: none; outline: none;
display: flex; display: flex;
@ -40,11 +48,24 @@
margin: 1rem; margin: 1rem;
border-radius: .5rem; border-radius: .5rem;
border: 1px solid $accent-color; border: 1px solid $accent-color;
font-weight: 800;
background-color: $primary-dark; background-color: $primary-dark;
transition: background-color .4s ease-in-out; transition: background-color .2s ease-in-out;
background-image:
linear-gradient(transparent 11px, rgba($accent-color,.5) 12px, transparent 12px),
linear-gradient(90deg, transparent 11px, rgba($accent-color,.5) 12px, transparent 12px);
background-size: 100% 12px, 12px 100%;
&:hover {
background-color: rgba($accent-color,.2);
}
&.add { &.add {
background: $accent-color; background: $accent-color;
&:hover {
background-color: rgba($accent-color,.8);
}
} }
} }

View File

@ -14,7 +14,7 @@ export class ApplicationGridComponent implements OnInit {
@Input() public disabled: boolean = false; @Input() public disabled: boolean = false;
@Output() public changeView: EventEmitter<void> = new EventEmitter(); @Output() public changeView: EventEmitter<void> = new EventEmitter();
public appsSubject: BehaviorSubject<Application.AsObject[]> = new BehaviorSubject<Application.AsObject[]>([]); public appsSubject: BehaviorSubject<Application.AsObject[]> = new BehaviorSubject<Application.AsObject[]>([]);
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false); private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
public loading$: Observable<boolean> = this.loadingSubject.asObservable(); public loading$: Observable<boolean> = this.loadingSubject.asObservable();
constructor(private projectService: ProjectService) { } constructor(private projectService: ProjectService) { }

View File

@ -19,7 +19,7 @@ export class ProjectApplicationsDataSource extends DataSource<Application.AsObje
super(); super();
} }
public loadApps(projectId: string, pageIndex: number, pageSize: number, sortDirection?: string): void { public loadApps(projectId: string, pageIndex: number, pageSize: number): void {
const offset = pageIndex * pageSize; const offset = pageIndex * pageSize;
this.loadingSubject.next(true); this.loadingSubject.next(true);

View File

@ -1,54 +1,41 @@
<div class="table-header-row"> <app-refresh-table [loading]="dataSource.loading$ | async" [selection]="selection" (refreshed)="refreshPage()"
<div class="col"> [dataSize]="dataSource.totalResult">
<ng-container *ngIf="!selection.hasValue()"> <ng-template appHasRole [appHasRole]="['project.app.write']" actions>
<span class="desc">{{'ORG_DETAIL.TABLE.TOTAL' | translate}}</span>
<span class="count">{{dataSource?.appsSubject.value.length}}</span>
</ng-container>
<ng-container *ngIf="selection.hasValue()">
<span class="desc">{{'ORG_DETAIL.TABLE.SELECTION' | translate}}</span>
<span class="count">{{selection?.selected?.length}}</span>
</ng-container>
</div>
<span class="fill-space"></span>
<ng-template appHasRole [appHasRole]="['project.app.write']">
<a [disabled]="disabled" class="add-button" [routerLink]="[ '/projects', projectId, 'apps', 'create']" <a [disabled]="disabled" class="add-button" [routerLink]="[ '/projects', projectId, 'apps', 'create']"
color="primary" mat-raised-button> color="primary" mat-raised-button>
<mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }} <mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }}
</a> </a>
</ng-template> </ng-template>
</div>
<div class="table-wrapper"> <div class="table-wrapper">
<div class="spinner-container" *ngIf="dataSource.loading$ | async"> <table [dataSource]="dataSource" mat-table class="full-width-table" matSort aria-label="Elements">
<mat-spinner diameter="50"></mat-spinner> <ng-container matColumnDef="select">
<th class="selection" mat-header-cell *matHeaderCellDef>
<mat-checkbox color="primary" (change)="$event ? masterToggle() : null"
[checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()">
</mat-checkbox>
</th>
<td class="selection" mat-cell *matCellDef="let row">
<mat-checkbox color="primary" (click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(row) : null" [checked]="selection.isSelected(row)">
</mat-checkbox>
</td>
</ng-container>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.ROLE.NAME' | translate }} </th>
<td class="pointer" [routerLink]="['/projects', projectId, 'apps', role.id ]" mat-cell
*matCellDef="let role">
{{role.name}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr class="data-row" mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<mat-paginator #paginator [length]="dataSource.totalResult" [pageSize]="25"
[pageSizeOptions]="[25, 50, 100, 250]">
</mat-paginator>
</div> </div>
</app-refresh-table>
<table [dataSource]="dataSource" mat-table class="full-width-table" matSort aria-label="Elements">
<ng-container matColumnDef="select">
<th class="selection" mat-header-cell *matHeaderCellDef>
<mat-checkbox color="primary" (change)="$event ? masterToggle() : null"
[checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()">
</mat-checkbox>
</th>
<td class="selection" mat-cell *matCellDef="let row">
<mat-checkbox color="primary" (click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(row) : null" [checked]="selection.isSelected(row)">
</mat-checkbox>
</td>
</ng-container>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.ROLE.NAME' | translate }} </th>
<td class="pointer" [routerLink]="['/projects', projectId, 'apps', role.id ]" mat-cell
*matCellDef="let role">
{{role.name}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr class="data-row" mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<mat-paginator #paginator [length]="dataSource.totalResult" [pageSize]="50" [pageSizeOptions]="[25, 50, 100, 250]">
</mat-paginator>
</div>

View File

@ -1,73 +1,39 @@
.table-header-row {
display: flex;
align-items: center;
.col {
display: flex;
flex-direction: column;
.desc {
font-size: .8rem;
color: #8795a1;
}
.count {
font-size: 2rem;
}
}
.fill-space {
flex: 1;
}
.icon-button {
margin-right: .5rem;
}
.add-button {
border-radius: .5rem;
}
}
.table-wrapper { .table-wrapper {
overflow: auto; overflow: auto;
.spinner-container { table, mat-paginator {
display: flex; width: 100%;
align-items: center; background-color: #2d2e30;
justify-content: center;
td, th {
&:first-child {
padding-left: 0;
padding-right: 1rem;
}
&:last-child {
padding-right: 0;
}
}
.data-row {
cursor: pointer;
&:hover {
background-color: #ffffff05;
}
}
.selection {
width: 50px;
max-width: 50px;
}
.margin-neg {
margin-left: -1rem;
}
} }
table, mat-paginator {
width: 100%;
background-color: #2d2e30;
td, th {
&:first-child {
padding-left: 0;
padding-right: 1rem;
}
&:last-child {
padding-right: 0;
}
}
.data-row {
cursor: pointer;
&:hover {
background-color: #ffffff05;
}
}
.selection {
width: 50px;
max-width: 50px;
}
.margin-neg {
margin-left: -1rem;
}
}
} }
.pointer { .pointer {

View File

@ -33,7 +33,7 @@ export class ApplicationsComponent implements AfterViewInit, OnInit {
public ngOnInit(): void { public ngOnInit(): void {
this.dataSource = new ProjectApplicationsDataSource(this.projectService); this.dataSource = new ProjectApplicationsDataSource(this.projectService);
this.dataSource.loadApps(this.projectId, 0, 25, 'asc'); this.dataSource.loadApps(this.projectId, 0, 25);
} }
public ngAfterViewInit(): void { public ngAfterViewInit(): void {
@ -51,7 +51,6 @@ export class ApplicationsComponent implements AfterViewInit, OnInit {
this.projectId, this.projectId,
this.paginator.pageIndex, this.paginator.pageIndex,
this.paginator.pageSize, this.paginator.pageSize,
this.sort.direction,
); );
} }
@ -66,4 +65,8 @@ export class ApplicationsComponent implements AfterViewInit, OnInit {
this.selection.clear() : this.selection.clear() :
this.dataSource.appsSubject.value.forEach((row: Application.AsObject) => this.selection.select(row)); this.dataSource.appsSubject.value.forEach((row: Application.AsObject) => this.selection.select(row));
} }
public refreshPage(): void {
this.dataSource.loadApps(this.projectId, this.paginator.pageIndex, this.paginator.pageSize);
}
} }

View File

@ -101,8 +101,8 @@
<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"> <mat-tab label="Details">
<app-contributors [totalResult]="totalMemberResult" [membersSubject]="membersSubject" <app-contributors [loading]="loading$ | async" [totalResult]="totalMemberResult"
title="{{ 'PROJECT.MEMBER.TITLE' | translate }}" [membersSubject]="membersSubject" title="{{ 'PROJECT.MEMBER.TITLE' | translate }}"
description="{{ 'PROJECT.MEMBER.TITLEDESC' | translate }}" (addClicked)="openAddMember()" description="{{ 'PROJECT.MEMBER.TITLEDESC' | translate }}" (addClicked)="openAddMember()"
(showDetailClicked)="showDetail()" [disabled]="false"> (showDetailClicked)="showDetail()" [disabled]="false">
</app-contributors> </app-contributors>

View File

@ -5,7 +5,7 @@ import { MatDialog } from '@angular/material/dialog';
import { MatTableDataSource } from '@angular/material/table'; import { MatTableDataSource } from '@angular/material/table';
import { ActivatedRoute, Params, Router } from '@angular/router'; import { ActivatedRoute, Params, Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { BehaviorSubject, from, of, Subscription } from 'rxjs'; import { BehaviorSubject, from, Observable, of, Subscription } from 'rxjs';
import { catchError, finalize, map } from 'rxjs/operators'; import { catchError, finalize, map } from 'rxjs/operators';
import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component'; import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component';
import { ChangeType } from 'src/app/modules/changes/changes.component'; import { ChangeType } from 'src/app/modules/changes/changes.component';
@ -72,7 +72,8 @@ export class OwnedProjectDetailComponent implements OnInit, OnDestroy {
public totalMemberResult: number = 0; public totalMemberResult: number = 0;
public membersSubject: BehaviorSubject<ProjectMemberView.AsObject[]> public membersSubject: BehaviorSubject<ProjectMemberView.AsObject[]>
= new BehaviorSubject<ProjectMemberView.AsObject[]>([]); = new BehaviorSubject<ProjectMemberView.AsObject[]>([]);
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false); private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
constructor( constructor(
public translate: TranslateService, public translate: TranslateService,

View File

@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatRippleModule } from '@angular/material/core';
import { MatFormFieldModule } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatPaginatorModule } from '@angular/material/paginator'; import { MatPaginatorModule } from '@angular/material/paginator';
@ -56,6 +57,7 @@ import { ProjectGrantsComponent } from './project-grants/project-grants.componen
MatFormFieldModule, MatFormFieldModule,
CardModule, CardModule,
MatPaginatorModule, MatPaginatorModule,
MatRippleModule,
MatCheckboxModule, MatCheckboxModule,
MatSelectModule, MatSelectModule,
MatProgressSpinnerModule, MatProgressSpinnerModule,

View File

@ -29,7 +29,8 @@
</div> </div>
<button [ngClass]="{ selected: selection.isSelected(item)}" (click)="selection.toggle(item)" class="edit-button" <button [ngClass]="{ selected: selection.isSelected(item)}" (click)="selection.toggle(item)" class="edit-button"
mat-icon-button> mat-icon-button>
<mat-icon>push_pin</mat-icon> <mat-icon *ngIf="selection.isSelected(item)" svgIcon="mdi_pin"></mat-icon>
<mat-icon svgIcon="mdi_pin_outline" *ngIf="!selection.isSelected(item)"></mat-icon>
</button> </button>
</div> </div>
@ -57,7 +58,8 @@
</div> </div>
<button [ngClass]="{ selected: selection.isSelected(item)}" (click)="selection.toggle(item)" class="edit-button" <button [ngClass]="{ selected: selection.isSelected(item)}" (click)="selection.toggle(item)" class="edit-button"
mat-icon-button> mat-icon-button>
<mat-icon>push_pin</mat-icon> <mat-icon *ngIf="selection.isSelected(item)" svgIcon="mdi_pin"></mat-icon>
<mat-icon svgIcon="mdi_pin_outline" *ngIf="!selection.isSelected(item)"></mat-icon>
</button> </button>
</div> </div>

View File

@ -133,10 +133,6 @@
margin-bottom: 0.25rem; margin-bottom: 0.25rem;
color: #8795a1; color: #8795a1;
&:hover {
color: white;
}
&.selected { &.selected {
opacity: 1; opacity: 1;
} }

View File

@ -9,6 +9,7 @@ import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatPaginatorModule } from '@angular/material/paginator'; import { MatPaginatorModule } from '@angular/material/paginator';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select';
import { MatTableModule } from '@angular/material/table'; import { MatTableModule } from '@angular/material/table';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
@ -39,6 +40,7 @@ import { ProjectGrantMembersModule } from './project-grant-members/project-grant
MatProgressSpinnerModule, MatProgressSpinnerModule,
FormsModule, FormsModule,
TranslateModule, TranslateModule,
MatSelectModule,
], ],
}) })
export class ProjectGrantDetailModule { } export class ProjectGrantDetailModule { }

View File

@ -13,6 +13,8 @@
<p class="desc">{{ 'USER.CREATE.DESCRIPTION' | translate }}</p> <p class="desc">{{ 'USER.CREATE.DESCRIPTION' | translate }}</p>
</div> </div>
<mat-progress-bar *ngIf="loading" color="accent" mode="indeterminate"></mat-progress-bar>
<form *ngIf="userForm" [formGroup]="userForm" (ngSubmit)="createUser()" class="form"> <form *ngIf="userForm" [formGroup]="userForm" (ngSubmit)="createUser()" class="form">
<!-- <h2>{{ 'USER.PAGES.CREATE' | translate}}</h2> --> <!-- <h2>{{ 'USER.PAGES.CREATE' | translate}}</h2> -->
<div class="content"> <div class="content">

View File

@ -39,6 +39,7 @@ export class UserCreateComponent implements OnDestroy {
private sub: Subscription = new Subscription(); private sub: Subscription = new Subscription();
public userLoginMustBeDomain: boolean = false; public userLoginMustBeDomain: boolean = false;
public loading: boolean = false;
constructor( constructor(
private router: Router, private router: Router,
@ -47,12 +48,15 @@ export class UserCreateComponent implements OnDestroy {
private fb: FormBuilder, private fb: FormBuilder,
private orgService: OrgService, private orgService: OrgService,
) { ) {
this.loading = true;
this.orgService.GetMyOrgIamPolicy().then((iampolicy) => { this.orgService.GetMyOrgIamPolicy().then((iampolicy) => {
this.userLoginMustBeDomain = iampolicy.toObject().userLoginMustBeDomain; this.userLoginMustBeDomain = iampolicy.toObject().userLoginMustBeDomain;
this.initForm(); this.initForm();
this.loading = false;
}).catch(error => { }).catch(error => {
console.error(error); console.error(error);
this.initForm(); this.initForm();
this.loading = false;
}); });
} }
@ -83,13 +87,16 @@ export class UserCreateComponent implements OnDestroy {
public createUser(): void { public createUser(): void {
this.user = this.userForm.value; this.user = this.userForm.value;
this.loading = true;
this.userService this.userService
.CreateUser(this.user) .CreateUser(this.user)
.then((data: User) => { .then((data: User) => {
this.loading = false;
this.toast.showInfo('USER.TOAST.CREATED', true); this.toast.showInfo('USER.TOAST.CREATED', true);
this.router.navigate(['users', data.getId()]); this.router.navigate(['users', data.getId()]);
}) })
.catch(error => { .catch(error => {
this.loading = false;
this.toast.showError(error); this.toast.showError(error);
}); });
} }

View File

@ -6,6 +6,7 @@ import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatFormFieldModule } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select'; import { MatSelectModule } from '@angular/material/select';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
@ -29,6 +30,7 @@ import { UserCreateComponent } from './user-create.component';
MatButtonModule, MatButtonModule,
MatIconModule, MatIconModule,
MatProgressSpinnerModule, MatProgressSpinnerModule,
MatProgressBarModule,
MatCheckboxModule, MatCheckboxModule,
MatTooltipModule, MatTooltipModule,
TranslateModule, TranslateModule,

View File

@ -13,7 +13,7 @@
<span>{{login}}</span> <span>{{login}}</span>
<button color="primary" [disabled]="copied == login" <button color="primary" [disabled]="copied == login"
[matTooltip]="(copied != login ? 'USER.PAGES.COPY' : 'USER.PAGES.COPIED' ) | translate" [matTooltip]="(copied != login ? 'USER.PAGES.COPY' : 'USER.PAGES.COPIED' ) | translate"
(click)="copytoclipboard(login)" mat-icon-button> appCopyToClipboard [valueToCopy]="login" (copiedValue)="copied = $event" mat-icon-button>
<i *ngIf="copied != login" class="las la-clipboard"></i> <i *ngIf="copied != login" class="las la-clipboard"></i>
<i *ngIf="copied == login" class="las la-clipboard-check"></i> <i *ngIf="copied == login" class="las la-clipboard-check"></i>
</button> </button>

View File

@ -39,9 +39,11 @@ export class AuthUserDetailComponent implements OnDestroy {
private dialog: MatDialog, private dialog: MatDialog,
) { ) {
this.loading = true; this.loading = true;
this.getData().then(() => { this.userService.GetMyUser().then(user => {
this.user = user.toObject();
this.loading = false; this.loading = false;
}).catch(error => { }).catch(error => {
this.toast.showError(error);
this.loading = false; this.loading = false;
}); });
} }
@ -149,30 +151,4 @@ export class AuthUserDetailComponent implements OnDestroy {
this.phoneEditState = false; this.phoneEditState = false;
}); });
} }
private async getData(): Promise<void> {
this.userService.GetMyUser().then(user => {
this.user = user.toObject();
}).catch(error => {
this.toast.showError(error);
});
}
public copytoclipboard(value: string): void {
const selBox = document.createElement('textarea');
selBox.style.position = 'fixed';
selBox.style.left = '0';
selBox.style.top = '0';
selBox.style.opacity = '0';
selBox.value = value;
document.body.appendChild(selBox);
selBox.focus();
selBox.select();
document.execCommand('copy');
document.body.removeChild(selBox);
this.copied = value;
setTimeout(() => {
this.copied = '';
}, 3000);
}
} }

View File

@ -60,7 +60,6 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
public getOTP(): void { public getOTP(): void {
this.service.GetMyMfas().then(mfas => { this.service.GetMyMfas().then(mfas => {
console.log(mfas.toObject().mfasList);
this.dataSource = new MatTableDataSource(mfas.toObject().mfasList); this.dataSource = new MatTableDataSource(mfas.toObject().mfasList);
this.dataSource.sort = this.sort; this.dataSource.sort = this.sort;

View File

@ -0,0 +1,11 @@
@import '~@angular/material/theming';
@mixin theme-card($theme) {
$primary: map-get($theme, primary);
$primary-dark: mat-color($primary, A800);
.theme-conent, .theme-app , .crescent {
background-color: $primary-dark;
transition: background-color .4s cubic-bezier(0.645, 0.045, 0.355, 1);
}
}

View File

@ -1,4 +1,5 @@
$dark-background: #2d2e30; $dark-background: #2d2e30;
$light-background: #fafafa;
:root { :root {
transition: none; transition: none;
@ -21,6 +22,7 @@ $dark-background: #2d2e30;
height: 6rem; height: 6rem;
background: linear-gradient(40deg, #FF0080,#FF8C00 70%); background: linear-gradient(40deg, #FF0080,#FF8C00 70%);
margin: auto; margin: auto;
box-shadow: 0 30px 60px rgba(0,0,0,0.12);
} }
.crescent { .crescent {
@ -29,10 +31,10 @@ $dark-background: #2d2e30;
right: 0; right: 0;
width: 4rem; width: 4rem;
height: 4rem; height: 4rem;
background: #fafafa; background: $light-background;
transform: scale(0); transform: scale(0);
transform-origin: top right; transform-origin: top right;
transition: transform .6s cubic-bezier(0.645, 0.045, 0.355, 1); transition: transform .4s cubic-bezier(0.645, 0.045, 0.355, 1);
} }
p { p {
@ -66,7 +68,7 @@ label {
.toggle { .toggle {
position: absolute; position: absolute;
background-color: #fafafa; background-color: $light-background;
box-shadow: 0 2px 15px rgba(0,0,0,.15); box-shadow: 0 2px 15px rgba(0,0,0,.15);
} }
@ -101,7 +103,7 @@ label {
[type="checkbox"]:checked + .theme-app{ [type="checkbox"]:checked + .theme-app{
background-color: $dark-background; background-color: $dark-background;
color: #fafafa; color: $light-background;
} }
[type="checkbox"]:checked + .theme-app .crescent{ [type="checkbox"]:checked + .theme-app .crescent{

View File

@ -13,6 +13,7 @@ import { MatTableModule } from '@angular/material/table';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { QRCodeModule } from 'angularx-qrcode'; import { QRCodeModule } from 'angularx-qrcode';
import { CopyToClipboardModule } from 'src/app/directives/copy-to-clipboard/copy-to-clipboard.module';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module'; import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { CardModule } from 'src/app/modules/card/card.module'; import { CardModule } from 'src/app/modules/card/card.module';
import { ChangesModule } from 'src/app/modules/changes/changes.module'; import { ChangesModule } from 'src/app/modules/changes/changes.module';
@ -70,6 +71,7 @@ import { UserMfaComponent } from './user-detail/user-mfa/user-mfa.component';
MatPaginatorModule, MatPaginatorModule,
SharedModule, SharedModule,
RefreshTableModule, RefreshTableModule,
CopyToClipboardModule,
], ],
}) })
export class UserDetailModule { } export class UserDetailModule { }

View File

@ -25,12 +25,13 @@
<app-card title="{{ 'USER.PAGES.LOGINNAMES' | translate }}" <app-card title="{{ 'USER.PAGES.LOGINNAMES' | translate }}"
description="{{ 'USER.PAGES.LOGINNAMESDESC' | translate }}" *ngIf="user"> description="{{ 'USER.PAGES.LOGINNAMESDESC' | translate }}" *ngIf="user">
<div class="login-name-row" *ngFor="let login of user?.loginNamesList"> <div class="login-name-row" *ngFor="let login of user?.loginNamesList">
<span>{{login}}</span> <span>{{login}} </span>
<button color="primary" [disabled]="copied == login" <button color="primary" [disabled]="copied == login"
[matTooltip]="(copied != login ? 'USER.PAGES.COPY' : 'USER.PAGES.COPIED' ) | translate" [matTooltip]="(copied != login ? 'USER.PAGES.COPY' : 'USER.PAGES.COPIED' ) | translate"
(click)="copytoclipboard(login)" mat-icon-button> appCopyToClipboard [valueToCopy]="login" (copiedValue)="copied = $event" mat-icon-button>
<i *ngIf="copied != login" class="las la-clipboard"></i> <i *ngIf="copied != login" class="las la-clipboard"></i>
<i *ngIf="copied == login" class="las la-clipboard-check"></i> <i *ngIf="copied == login" class="las la-clipboard-check"></i>
</button> </button>
</div> </div>
</app-card> </app-card>

View File

@ -1,6 +1,6 @@
import { Location } from '@angular/common'; import { Location } from '@angular/common';
import { Component, OnDestroy, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { ChangeType } from 'src/app/modules/changes/changes.component'; import { ChangeType } from 'src/app/modules/changes/changes.component';
@ -27,7 +27,6 @@ export class UserDetailComponent implements OnInit, OnDestroy {
public genders: Gender[] = [Gender.GENDER_MALE, Gender.GENDER_FEMALE, Gender.GENDER_DIVERSE]; public genders: Gender[] = [Gender.GENDER_MALE, Gender.GENDER_FEMALE, Gender.GENDER_DIVERSE];
public languages: string[] = ['de', 'en']; public languages: string[] = ['de', 'en'];
public isMgmt: boolean = false;
private subscription: Subscription = new Subscription(); private subscription: Subscription = new Subscription();
public emailEditState: boolean = false; public emailEditState: boolean = false;
public phoneEditState: boolean = false; public phoneEditState: boolean = false;
@ -49,11 +48,11 @@ export class UserDetailComponent implements OnInit, OnDestroy {
public ngOnInit(): void { public ngOnInit(): void {
this.subscription = this.route.params.subscribe(params => { this.subscription = this.route.params.subscribe(params => {
this.loading = true; const { id } = params;
this.getData(params).then(() => { this.mgmtUserService.GetUserByID(id).then(user => {
this.loading = false; this.user = user.toObject();
}).catch(error => { }).catch(err => {
this.loading = false; console.error(err);
}); });
}); });
} }
@ -157,15 +156,6 @@ export class UserDetailComponent implements OnInit, OnDestroy {
this._location.back(); this._location.back();
} }
private async getData({ id }: Params): Promise<void> {
this.isMgmt = true;
this.mgmtUserService.GetUserByID(id).then(user => {
this.user = user.toObject();
}).catch(err => {
console.error(err);
});
}
public sendSetPasswordNotification(): void { public sendSetPasswordNotification(): void {
this.mgmtUserService.SendSetPasswordNotification(this.user.id, NotificationType.NOTIFICATIONTYPE_EMAIL) this.mgmtUserService.SendSetPasswordNotification(this.user.id, NotificationType.NOTIFICATIONTYPE_EMAIL)
.then(() => { .then(() => {
@ -174,22 +164,4 @@ export class UserDetailComponent implements OnInit, OnDestroy {
this.toast.showError(error); this.toast.showError(error);
}); });
} }
public copytoclipboard(value: string): void {
const selBox = document.createElement('textarea');
selBox.style.position = 'fixed';
selBox.style.left = '0';
selBox.style.top = '0';
selBox.style.opacity = '0';
selBox.value = value;
document.body.appendChild(selBox);
selBox.focus();
selBox.select();
document.execCommand('copy');
document.body.removeChild(selBox);
this.copied = value;
setTimeout(() => {
this.copied = '';
}, 3000);
}
} }

View File

@ -1,16 +1,22 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatSnackBar, MatSnackBarConfig } from '@angular/material/snack-bar'; import { MatSnackBar, MatSnackBarConfig } from '@angular/material/snack-bar';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { take } from 'rxjs/operators'; import { take } from 'rxjs/operators';
import { WarnDialogComponent } from '../modules/warn-dialog/warn-dialog.component';
import { AuthService } from './auth.service'; import { AuthService } from './auth.service';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class ToastService { export class ToastService {
constructor(private snackBar: MatSnackBar, private translate: TranslateService, private authService: AuthService) { } constructor(private dialog: MatDialog,
private snackBar: MatSnackBar,
private translate: TranslateService,
private authService: AuthService,
) { }
public showInfo(message: string, i18nkey: boolean = false): void { public showInfo(message: string, i18nkey: boolean = false): void {
if (i18nkey) { if (i18nkey) {
@ -28,10 +34,19 @@ export class ToastService {
const { message, code, metadata } = grpcError; const { message, code, metadata } = grpcError;
// TODO: remove check for code === 7 // TODO: remove check for code === 7
if (code === 16 || (code === 7 && message === 'invalid token')) { if (code === 16 || (code === 7 && message === 'invalid token')) {
const actionObserver$ = this.showMessage(decodeURI(message), 'Login'); const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.LOGIN',
titleKey: 'ERRORS.TOKENINVALID.TITLE',
descriptionKey: 'ERRORS.TOKENINVALID.DESCRIPTION',
},
width: '400px',
});
actionObserver$.pipe(take(1)).subscribe(() => { dialogRef.afterClosed().pipe(take(1)).subscribe(resp => {
this.authService.authenticate(undefined, true, true); if (resp) {
this.authService.authenticate(undefined, true, true);
}
}); });
} else { } else {
this.showMessage(decodeURI(message), 'close', { duration: 3000 }); this.showMessage(decodeURI(message), 'close', { duration: 3000 });

View File

@ -16,7 +16,11 @@
"USERS_BUTTON": "Benutzer anzeigen", "USERS_BUTTON": "Benutzer anzeigen",
"IAM": "Identity and Access Management", "IAM": "Identity and Access Management",
"IAM_DESC": "Verwalte deine Organisationen und Administratoren.", "IAM_DESC": "Verwalte deine Organisationen und Administratoren.",
"IAM_BUTTON": "ZITADEL verwalten" "IAM_BUTTON": "ZITADEL verwalten",
"WELCOME":"Willkommen",
"WELCOMESENTENCE":"Hier findest du die empfohlenen Aktionen basierend auf deinen zuletzt erworbenen Berechtigungen. Beachte bitte, dass du möglicherweise deine Organisation in der Kopfzeile wechseln musst.",
"DISCLAIMER":"Du kannst nur die Einstellungen deiner aktuellen Organisation sehen, die in der Kopfzeile angegeben ist. ZITADEL behandelt deine Daten vertraulich und sicher.",
"DISCLAIMERLINK":"Mehr Informationen zur Sicherheit"
}, },
"MENU": { "MENU": {
"PERSONAL_INFO": "Persönliche Informationen", "PERSONAL_INFO": "Persönliche Informationen",
@ -51,10 +55,15 @@
"CHANGE":"Ändern", "CHANGE":"Ändern",
"REACTIVATE":"Aktivieren", "REACTIVATE":"Aktivieren",
"DEACTIVATE":"Deaktivieren", "DEACTIVATE":"Deaktivieren",
"REFRESH":"Aktualisieren" "REFRESH":"Aktualisieren",
"LOGIN":"Login"
}, },
"ERRORS": { "ERRORS": {
"REQUIRED": "Bitte fülle alle benötigten Felder aus!" "REQUIRED": "Bitte fülle alle benötigten Felder aus!",
"TOKENINVALID": {
"TITLE":"Du bist abgemeldet",
"DESCRIPTION":"Klicke auf Knopf Login um dich wieder einzuloggen!"
}
}, },
"USER": { "USER": {
"TITLE": "Persönliche Informationen", "TITLE": "Persönliche Informationen",
@ -102,7 +111,8 @@
"NAMEANDEMAILSECTION":"Name und Email", "NAMEANDEMAILSECTION":"Name und Email",
"GENDERLANGSECTION":"Geschlecht und Sprache", "GENDERLANGSECTION":"Geschlecht und Sprache",
"PHONESECTION":"Telefonnummer", "PHONESECTION":"Telefonnummer",
"PASSWORDSECTION":"Setze ein initiales Passwort." "PASSWORDSECTION":"Setze ein initiales Passwort.",
"ADDRESSANDPHONESECTION":"Telefonnummer"
}, },
"CODEDIALOG": { "CODEDIALOG": {
"TITLE":"Telefonnummer verifizieren", "TITLE":"Telefonnummer verifizieren",

View File

@ -16,7 +16,11 @@
"USERS_BUTTON": "Show users", "USERS_BUTTON": "Show users",
"IAM": "Identity and Access Management", "IAM": "Identity and Access Management",
"IAM_DESC": "Manage your organisations and administrators.", "IAM_DESC": "Manage your organisations and administrators.",
"IAM_BUTTON": "Manage ZITADEL" "IAM_BUTTON": "Manage ZITADEL",
"WELCOME":"Welcome",
"WELCOMESENTENCE":"Here you can find recommended Actions based on your last acquired permissions. Note that you may have to select your organisation in the header above.",
"DISCLAIMER":"You can only see settings of your current organisation specified in the header. ZITADEL treats your data confidentially and securely.",
"DISCLAIMERLINK":"Further information"
}, },
"MENU": { "MENU": {
"PERSONAL_INFO": "Personal Information", "PERSONAL_INFO": "Personal Information",
@ -51,10 +55,15 @@
"CHANGE":"Change", "CHANGE":"Change",
"REACTIVATE":"Reactivate", "REACTIVATE":"Reactivate",
"DEACTIVATE":"Deactivate", "DEACTIVATE":"Deactivate",
"REFRESH":"Refresh" "REFRESH":"Refresh",
"LOGIN":"Login"
}, },
"ERRORS": { "ERRORS": {
"REQUIRED": "Some required fields are missing!" "REQUIRED": "Some required fields are missing!",
"TOKENINVALID": {
"TITLE":"Your auth token has expired",
"DESCRIPTION":"Click the button below to login again!"
}
}, },
"USER": { "USER": {
"TITLE": "Personal Information", "TITLE": "Personal Information",
@ -102,7 +111,8 @@
"NAMEANDEMAILSECTION":"Name and Email", "NAMEANDEMAILSECTION":"Name and Email",
"GENDERLANGSECTION":"Gender and Language", "GENDERLANGSECTION":"Gender and Language",
"PHONESECTION":"Phonenumbers", "PHONESECTION":"Phonenumbers",
"PASSWORDSECTION":"Set the initial Password." "PASSWORDSECTION":"Set the initial Password.",
"ADDRESSANDPHONESECTION":"Phonenumber"
}, },
"CODEDIALOG": { "CODEDIALOG": {
"TITLE":"Verify Phone Number", "TITLE":"Verify Phone Number",

View File

@ -61,29 +61,29 @@
<path d="M1496.17,759.473L1555.54,720.014" style="fill:none;"/> <path d="M1496.17,759.473L1555.54,720.014" style="fill:none;"/>
</g> </g>
<g transform="matrix(1.32803,-0.355844,-0.311363,-1.16202,-1672.97,1561.28)"> <g transform="matrix(1.32803,-0.355844,-0.311363,-1.16202,-1672.97,1561.28)">
<path d="M1496.1,754.362C1496.1,754.362 1497.2,755.607 1501.13,753.598C1503.25,752.509 1505.74,751.023 1508.49,749.329C1513.52,746.234 1519.35,742.314 1525.19,738.438C1530.13,735.166 1534.94,731.832 1539.27,728.802C1542.87,726.279 1549.36,722.059 1549.81,721.75C1552.75,719.73 1552.18,718.196 1552.18,718.196L1555.28,724.152C1555.28,724.152 1553.77,722.905 1551.37,724.681C1550.93,725.006 1544.52,729.349 1540.82,731.68C1536.38,734.479 1531.45,737.766 1526.52,741.049C1520.68,744.94 1514.82,748.79 1509.98,752.255C1507.33,754.151 1504.89,755.771 1503.09,757.456C1499.47,760.841 1501.26,763.283 1501.26,763.283L1496.1,754.362Z" style="fill:rgb(35,35,35);"/> <path d="M1496.1,754.362C1496.1,754.362 1497.2,755.607 1501.13,753.598C1503.25,752.509 1505.74,751.023 1508.49,749.329C1513.52,746.234 1519.35,742.314 1525.19,738.438C1530.13,735.166 1534.94,731.832 1539.27,728.802C1542.87,726.279 1549.36,722.059 1549.81,721.75C1552.75,719.73 1552.18,718.196 1552.18,718.196L1555.28,724.152C1555.28,724.152 1553.77,722.905 1551.37,724.681C1550.93,725.006 1544.52,729.349 1540.82,731.68C1536.38,734.479 1531.45,737.766 1526.52,741.049C1520.68,744.94 1514.82,748.79 1509.98,752.255C1507.33,754.151 1504.89,755.771 1503.09,757.456C1499.47,760.841 1501.26,763.283 1501.26,763.283L1496.1,754.362Z" style="fill:#8795a1;"/>
</g> </g>
<g transform="matrix(1.299,0,0,1.08306,-3394.18,-2084.88)"> <g transform="matrix(1.299,0,0,1.08306,-3394.18,-2084.88)">
<g transform="matrix(94.2338,0,0,94.1776,2827.58,2063)"> <g transform="matrix(94.2338,0,0,94.1776,2827.58,2063)">
<path d="M0.449,-0.7L0.177,-0.7C0.185,-0.682 0.197,-0.654 0.2,-0.648C0.205,-0.639 0.216,-0.628 0.239,-0.628L0.32,-0.628C0.332,-0.628 0.336,-0.62 0.334,-0.611L0.128,0L0.389,0C0.412,0 0.422,-0.01 0.427,-0.02L0.45,-0.071L0.255,-0.071C0.245,-0.071 0.239,-0.078 0.242,-0.09L0.449,-0.7Z" style="fill:rgb(35,35,35);fill-rule:nonzero;"/> <path d="M0.449,-0.7L0.177,-0.7C0.185,-0.682 0.197,-0.654 0.2,-0.648C0.205,-0.639 0.216,-0.628 0.239,-0.628L0.32,-0.628C0.332,-0.628 0.336,-0.62 0.334,-0.611L0.128,0L0.389,0C0.412,0 0.422,-0.01 0.427,-0.02L0.45,-0.071L0.255,-0.071C0.245,-0.071 0.239,-0.078 0.242,-0.09L0.449,-0.7Z" style="fill:#8795a1;fill-rule:nonzero;"/>
</g> </g>
<g transform="matrix(94.2338,0,0,94.1776,2912.39,2063)"> <g transform="matrix(94.2338,0,0,94.1776,2912.39,2063)">
<path d="M0.214,-0.7L0.214,-0.015C0.215,-0.01 0.218,0 0.235,0L0.286,0L0.286,-0.672C0.286,-0.684 0.278,-0.7 0.257,-0.7L0.214,-0.7Z" style="fill:rgb(35,35,35);fill-rule:nonzero;"/> <path d="M0.214,-0.7L0.214,-0.015C0.215,-0.01 0.218,0 0.235,0L0.286,0L0.286,-0.672C0.286,-0.684 0.278,-0.7 0.257,-0.7L0.214,-0.7Z" style="fill:#8795a1;fill-rule:nonzero;"/>
</g> </g>
<g transform="matrix(94.2338,0,0,94.1776,2987.78,2063)"> <g transform="matrix(94.2338,0,0,94.1776,2987.78,2063)">
<path d="M0.441,-0.7L0.155,-0.7C0.143,-0.7 0.133,-0.69 0.133,-0.678L0.133,-0.629L0.234,-0.629L0.234,-0.015C0.234,-0.01 0.237,0 0.254,0L0.305,0L0.305,-0.612C0.306,-0.621 0.313,-0.629 0.323,-0.629L0.379,-0.629C0.402,-0.629 0.413,-0.639 0.417,-0.648L0.441,-0.7Z" style="fill:rgb(35,35,35);fill-rule:nonzero;"/> <path d="M0.441,-0.7L0.155,-0.7C0.143,-0.7 0.133,-0.69 0.133,-0.678L0.133,-0.629L0.234,-0.629L0.234,-0.015C0.234,-0.01 0.237,0 0.254,0L0.305,0L0.305,-0.612C0.306,-0.621 0.313,-0.629 0.323,-0.629L0.379,-0.629C0.402,-0.629 0.413,-0.639 0.417,-0.648L0.441,-0.7Z" style="fill:#8795a1;fill-rule:nonzero;"/>
</g> </g>
<g transform="matrix(94.2338,0,0,94.1776,3067.88,2063)"> <g transform="matrix(94.2338,0,0,94.1776,3067.88,2063)">
<path d="M0.422,0L0.343,0L0.28,-0.482L0.217,0L0.138,0L0.244,-0.7L0.283,-0.7C0.313,-0.7 0.318,-0.681 0.321,-0.662L0.422,0Z" style="fill:rgb(35,35,35);fill-rule:nonzero;"/> <path d="M0.422,0L0.343,0L0.28,-0.482L0.217,0L0.138,0L0.244,-0.7L0.283,-0.7C0.313,-0.7 0.318,-0.681 0.321,-0.662L0.422,0Z" style="fill:#8795a1;fill-rule:nonzero;"/>
</g> </g>
<g transform="matrix(94.2338,0,0,94.1776,3148.92,2063)"> <g transform="matrix(94.2338,0,0,94.1776,3148.92,2063)">
<path d="M0.186,-0.7L0.186,0L0.325,0C0.374,0 0.413,-0.039 0.414,-0.088L0.414,-0.612C0.413,-0.661 0.374,-0.7 0.325,-0.7L0.186,-0.7ZM0.343,-0.108C0.343,-0.081 0.325,-0.071 0.305,-0.071L0.258,-0.071L0.258,-0.628L0.305,-0.628C0.325,-0.628 0.343,-0.618 0.343,-0.592L0.343,-0.108Z" style="fill:rgb(35,35,35);fill-rule:nonzero;"/> <path d="M0.186,-0.7L0.186,0L0.325,0C0.374,0 0.413,-0.039 0.414,-0.088L0.414,-0.612C0.413,-0.661 0.374,-0.7 0.325,-0.7L0.186,-0.7ZM0.343,-0.108C0.343,-0.081 0.325,-0.071 0.305,-0.071L0.258,-0.071L0.258,-0.628L0.305,-0.628C0.325,-0.628 0.343,-0.618 0.343,-0.592L0.343,-0.108Z" style="fill:#8795a1;fill-rule:nonzero;"/>
</g> </g>
<g transform="matrix(94.2338,0,0,94.1776,3233.73,2063)"> <g transform="matrix(94.2338,0,0,94.1776,3233.73,2063)">
<path d="M0.291,-0.071L0.291,-0.314C0.291,-0.323 0.299,-0.331 0.308,-0.331L0.338,-0.331C0.361,-0.331 0.371,-0.341 0.376,-0.35C0.379,-0.356 0.391,-0.385 0.399,-0.403L0.291,-0.403L0.291,-0.611C0.291,-0.621 0.298,-0.628 0.308,-0.628L0.366,-0.628C0.389,-0.628 0.4,-0.639 0.404,-0.648L0.428,-0.7L0.241,-0.7C0.229,-0.7 0.22,-0.691 0.219,-0.68L0.219,0L0.379,0C0.402,0 0.413,-0.01 0.418,-0.019C0.421,-0.025 0.433,-0.053 0.441,-0.071L0.291,-0.071Z" style="fill:rgb(35,35,35);fill-rule:nonzero;"/> <path d="M0.291,-0.071L0.291,-0.314C0.291,-0.323 0.299,-0.331 0.308,-0.331L0.338,-0.331C0.361,-0.331 0.371,-0.341 0.376,-0.35C0.379,-0.356 0.391,-0.385 0.399,-0.403L0.291,-0.403L0.291,-0.611C0.291,-0.621 0.298,-0.628 0.308,-0.628L0.366,-0.628C0.389,-0.628 0.4,-0.639 0.404,-0.648L0.428,-0.7L0.241,-0.7C0.229,-0.7 0.22,-0.691 0.219,-0.68L0.219,0L0.379,0C0.402,0 0.413,-0.01 0.418,-0.019C0.421,-0.025 0.433,-0.053 0.441,-0.071L0.291,-0.071Z" style="fill:#8795a1;fill-rule:nonzero;"/>
</g> </g>
<g transform="matrix(94.2338,0,0,94.1776,3318.54,2063)"> <g transform="matrix(94.2338,0,0,94.1776,3318.54,2063)">
<path d="M0.283,-0.071L0.283,-0.678C0.283,-0.69 0.273,-0.699 0.261,-0.7L0.211,-0.7L0.211,0L0.383,0C0.406,0 0.417,-0.01 0.422,-0.019C0.425,-0.025 0.437,-0.053 0.445,-0.071L0.283,-0.071Z" style="fill:rgb(35,35,35);fill-rule:nonzero;"/> <path d="M0.283,-0.071L0.283,-0.678C0.283,-0.69 0.273,-0.699 0.261,-0.7L0.211,-0.7L0.211,0L0.383,0C0.406,0 0.417,-0.01 0.422,-0.019C0.425,-0.025 0.437,-0.053 0.445,-0.071L0.283,-0.071Z" style="fill:#8795a1;fill-rule:nonzero;"/>
</g> </g>
</g> </g>
</g> </g>

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M19.36,2.72L20.78,4.14L15.06,9.85C16.13,11.39 16.28,13.24 15.38,14.44L9.06,8.12C10.26,7.22 12.11,7.37 13.65,8.44L19.36,2.72M5.93,17.57C3.92,15.56 2.69,13.16 2.35,10.92L7.23,8.83L14.67,16.27L12.58,21.15C10.34,20.81 7.94,19.58 5.93,17.57Z" /></svg>

After

Width:  |  Height:  |  Size: 531 B

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12M8.8,14L10,12.8V4H14V12.8L15.2,14H8.8Z" /></svg>

After

Width:  |  Height:  |  Size: 391 B

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z" /></svg>

After

Width:  |  Height:  |  Size: 354 B

View File

@ -5,7 +5,7 @@
@import './styles/changes'; @import './styles/changes';
@import 'src/app/pages/projects/owned-projects/owned-project-detail/application-grid/application-grid.component'; @import 'src/app/pages/projects/owned-projects/owned-project-detail/application-grid/application-grid.component';
@import './styles/meta'; @import './styles/meta';
@import './styles/theme-card'; @import 'src/app/pages/users/user-detail/auth-user-detail/theme-setting/theme-card';
@mixin component-themes($theme) { @mixin component-themes($theme) {
@include avatar-theme($theme); @include avatar-theme($theme);

View File

@ -154,7 +154,7 @@ $custom-typography: mat-typography-config(
@include angular-material-theme($light-theme); @include angular-material-theme($light-theme);
color: #202124; color: #202124;
.sidenav, .main-container { .sidenav, .main-container, .mat-dialog-container {
background-color: #fafafa; background-color: #fafafa;
transition: background-color .4s ease-in-out; transition: background-color .4s ease-in-out;
} }
@ -183,7 +183,7 @@ $custom-typography: mat-typography-config(
@include component-themes($dark-theme); @include component-themes($dark-theme);
@include angular-material-theme($dark-theme); @include angular-material-theme($dark-theme);
.sidenav, .main-container { .sidenav, .main-container, .mat-dialog-container {
background-color: #212224; background-color: #212224;
transition: background-color .4s ease-in-out; transition: background-color .4s ease-in-out;
} }

View File

@ -45,5 +45,11 @@
.iamuser { .iamuser {
color: $primary-color; color: $primary-color;
} }
.edit-button {
&:hover {
color: $border-selected-color;
}
}
} }
} }

View File

@ -48,7 +48,7 @@
} }
.root-header { .root-header {
box-shadow: 0px 2px 1px -1px rgba(0, 0, 0, 0.1), 0px 1px 1px 0px rgba(0, 0, 0, 0.1), 0px 1px 3px 0px rgba(0, 0, 0, 0.1); box-shadow: 0 5px 10px rgba(0,0,0,0.12);
background-color: $primary-dark !important; background-color: $primary-dark !important;
transition: background-color .4s ease-in-out; transition: background-color .4s ease-in-out;
} }
@ -56,6 +56,9 @@
.admin-line { .admin-line {
background: $accent-color; background: $accent-color;
color: white; color: white;
margin-right: 1rem;
border-top-right-radius: 50vw;
border-bottom-right-radius: 50vw;
// &::after { // &::after {
// content: ''; // content: '';

View File

@ -1,21 +0,0 @@
@import '~@angular/material/theming';
@mixin theme-card($theme) {
$primary: map-get($theme, primary);
$primary-dark: mat-color($primary, A800);
.theme-conent {
background-color: $primary-dark;
transition: background-color .4s ease-in-out;
}
.theme-app {
background-color: $primary-dark;
transition: background-color .4s ease-in-out;
}
.crescent {
background-color: $primary-dark;
transition: background-color .4s ease-in-out;
}
}