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.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.org = org;
});

View File

@ -5,6 +5,7 @@ import localeDe from '@angular/common/locales/de';
import { APP_INITIALIZER, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatDialogModule } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
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 { AccountsCardModule } from './modules/accounts-card/accounts-card.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 { HasRolePipeModule } from './pipes/has-role-pipe.module';
import { AuthUserService } from './services/auth-user.service';
@ -105,6 +107,8 @@ const authConfig: AuthConfig = {
MatMenuModule,
MatSnackBarModule,
AvatarModule,
WarnDialogModule,
MatDialogModule,
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
],
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="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">
<mat-progress-bar *ngIf="loadingUsers" color="primary" mode="indeterminate"></mat-progress-bar>
<a class="row" *ngFor="let user of users" (click)="selectAccount(user.loginName)">
@ -35,5 +35,5 @@
</a>
</div>
<button (click)="logout()" mat-stroked-button>{{'MENU.LOGOUT' | translate}}</button>
<button (click)="logout()" color="primary" mat-stroked-button>{{'MENU.LOGOUT' | translate}}</button>
</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}"
[ngClass]="{'active': active}">
{{credentials}}

View File

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

View File

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

View File

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

View File

@ -28,10 +28,13 @@
margin-left: 1rem;
display: flex;
align-items: center;
transition: all .15s ease-in-out;
mat-spinner {
margin-left: -15px;
margin-right: 20px;
}
.avatar-circle {
transition: all .3s ease-in-out;
float: left;
margin: 0 8px 0 -15px;
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 { BehaviorSubject } from 'rxjs';
@ -5,12 +6,28 @@ import { BehaviorSubject } from 'rxjs';
selector: 'app-contributors',
templateUrl: './contributors.component.html',
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 {
@Input() title: string = '';
@Input() description: string = '';
@Input() disabled: boolean = false;
@Input() totalResult: number = 0;
@Input() loading: boolean = false;
@Input() membersSubject!: BehaviorSubject<any[]>;
@Output() addClicked: 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 { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatTooltipModule } from '@angular/material/tooltip';
import { AvatarModule } from '../avatar/avatar.module';
@ -17,6 +18,7 @@ import { ContributorsComponent } from './contributors.component';
MatIconModule,
MatTooltipModule,
MatButtonModule,
MatProgressSpinnerModule,
],
exports: [
ContributorsComponent,

View File

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

View File

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

View File

@ -1,9 +1,17 @@
<div class="wrapper">
<div class="header">
<img alt="zitadel logo" *ngIf="dark; else lighttheme"
src="../assets/images/zitadel-logo-oneline-darkdesign.svg" />
<ng-template #lighttheme>
<img alt="zitadel logo" src="../assets/images/zitadel-logo-oneline-lightdesign.svg" />
<div class="wrapper max-width-container">
<div class="header" *ngIf="(authService.user | async) || {} as user">
<app-avatar [routerLink]="['/users/me']" *ngIf="user && (user.displayName || (user.firstName && user.lastName))"
class="avatar" [name]="user.displayName ? user.displayName : (user.firstName + ' '+ user.lastName)"
[size]="100">
</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>
</div>
@ -77,9 +85,14 @@
</div>
<span class="fill-space"></span>
<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>
</app-card>
</ng-template>
</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>

View File

@ -1,39 +1,25 @@
.wrapper {
width: 80%;
max-width: 1350px;
@media only screen and (max-width: 700px) {
width: 90%;
}
margin: auto;
padding-bottom: 100px;
.header {
display: flex;
flex-direction: column;
margin: 4rem 0;
align-items: center;
align-items: center;
.avatar {
width: 100px;
height: 100px;
font-size: 100px;
line-height: 100px;
margin-top: 1rem;
}
h1 {
font-size: 3rem;
}
p {
color: #8795a1;
text-align: center;
font-size: 1rem;
margin: 0;
h3 {
font-size: 24px;
margin-bottom: 1rem;
}
img {
height: 60px;
align-self: flex-end;
.wlc_stnce {
color: #8795a1;
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 { AuthService } from 'src/app/services/auth.service';
@Component({
selector: 'app-home',
@ -7,9 +8,9 @@ import { Component } from '@angular/core';
})
export class HomeComponent {
public dark: boolean = true;
constructor() {
constructor(public authService: AuthService) {
const theme = localStorage.getItem('theme');
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 { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { TranslateModule } from '@ngx-translate/core';
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 { SharedModule } from 'src/app/modules/shared/shared.module';
@ -21,7 +23,9 @@ import { HomeComponent } from './home.component';
HomeRoutingModule,
MatButtonModule,
TranslateModule,
AvatarModule,
SharedModule,
MatProgressSpinnerModule,
CardModule,
],
})

View File

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

View File

@ -5,6 +5,7 @@ import { MatPaginator } from '@angular/material/paginator';
import { MatTable } from '@angular/material/table';
import { tap } from 'rxjs/operators';
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 { AdminService } from 'src/app/services/admin.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 disabled: boolean = false;
@ViewChild(MatPaginator) public paginator!: MatPaginator;
@ViewChild(MatTable) public table!: MatTable<ProjectMember.AsObject>;
@ViewChild(MatTable) public table!: MatTable<IamMemberView.AsObject>;
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. */
public displayedColumns: string[] = ['select', 'firstname', 'lastname', 'username', 'email', 'roles'];
@ -32,7 +33,7 @@ export class IamMembersComponent implements AfterViewInit {
private toast: ToastService) {
this.dataSource = new IamMembersDataSource(this.adminService);
this.dataSource.loadMembers(0, 25, 'asc');
this.dataSource.loadMembers(0, 25);
}
public ngAfterViewInit(): void {

View File

@ -41,7 +41,9 @@
<th mat-header-cell *matHeaderCellDef> {{ 'IAM.VIEWS.ACTIONS' | translate }} </th>
<td mat-cell *matCellDef="let role">
<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>
</ng-container>

View File

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

View File

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

View File

@ -1,17 +1,12 @@
import { DataSource } from '@angular/cdk/collections';
import { BehaviorSubject, from, Observable, of } from 'rxjs';
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';
/**
* 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> {
export class OrgMembersDataSource extends DataSource<OrgMemberView.AsObject> {
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);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
@ -19,26 +14,20 @@ export class ProjectMembersDataSource extends DataSource<ProjectMember.AsObject>
super();
}
public loadMembers(
pageIndex: number, pageSize: number, grantId?: string, sortDirection?: string): void {
public loadMembers(pageIndex: number, pageSize: number): void {
const offset = pageIndex * pageSize;
this.loadingSubject.next(true);
// TODO
const promise: Promise<ProjectMemberSearchResponse> =
this.orgService.SearchMyOrgMembers(pageSize, offset);
if (promise) {
from(promise).pipe(
map(resp => {
this.totalResult = resp.toObject().totalResult;
return resp.toObject().resultList;
}),
catchError(() => of([])),
finalize(() => this.loadingSubject.next(false)),
).subscribe(members => {
this.membersSubject.next(members);
});
}
from(this.orgService.SearchMyOrgMembers(pageSize, offset)).pipe(
map(resp => {
this.totalResult = resp.toObject().totalResult;
return resp.toObject().resultList;
}),
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.
* @returns A stream of the items to be rendered.
*/
public connect(): Observable<ProjectMember.AsObject[]> {
public connect(): Observable<OrgMemberView.AsObject[]> {
return this.membersSubject.asObservable();
}

View File

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

View File

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

View File

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

View File

@ -32,11 +32,36 @@ export class AppCreateComponent implements OnInit, OnDestroy {
public loading: boolean = false;
public oidcApp: OIDCApplicationCreate.AsObject = new OIDCApplicationCreate().toObject();
public oidcResponseTypes: { type: OIDCResponseType, checked: boolean; }[] = [
{ type: OIDCResponseType.OIDCRESPONSETYPE_CODE, checked: false },
{ type: OIDCResponseType.OIDCRESPONSETYPE_ID_TOKEN, checked: false },
{ type: OIDCResponseType.OIDCRESPONSETYPE_ID_TOKEN_TOKEN, checked: false },
public oidcResponseTypes: { type: OIDCResponseType, checked: boolean; disabled: boolean; }[] = [
{ type: OIDCResponseType.OIDCRESPONSETYPE_CODE, checked: false, disabled: false },
{ type: OIDCResponseType.OIDCRESPONSETYPE_ID_TOKEN, checked: false, disabled: 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: {
type: OIDCGrantType,
checked: boolean,
@ -47,28 +72,6 @@ export class AppCreateComponent implements OnInit, OnDestroy {
// { type: OIDCGrantType.OIDCGRANTTYPE_REFRESH_TOKEN, checked: false, disabled: true },
// 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];
@ -92,8 +95,8 @@ export class AppCreateComponent implements OnInit, OnDestroy {
this.form.valueChanges.pipe(debounceTime(300)).subscribe((value) => {
this.oidcApp.name = this.formname?.value;
this.oidcApp.applicationType = this.formapplicationType?.value;
this.oidcApp.grantTypesList = this.formgrantTypesList?.value;
this.oidcApp.responseTypesList = this.formresponseTypesList?.value;
this.oidcApp.grantTypesList = this.formgrantTypesList?.value;
this.oidcApp.authMethodType = this.formauthMethodType?.value;
console.log(this.oidcApp);
@ -103,48 +106,60 @@ export class AppCreateComponent implements OnInit, OnDestroy {
name: ['', [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({
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.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 {
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 {
return this.firstFormGroup.get('name');
}

View File

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

View File

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

View File

@ -7,7 +7,7 @@
<div *ngIf="data.clientSecret" class="flex">
<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-check"></i>
</button>

View File

@ -14,22 +14,4 @@ export class AppSecretDialogComponent {
public closeDialog(): void {
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 { MatTooltipModule } from '@angular/material/tooltip';
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 { CardModule } from 'src/app/modules/card/card.module';
@ -55,6 +56,7 @@ import { AppsRoutingModule } from './apps-routing.module';
TranslateModule,
MatStepperModule,
MatRadioModule,
CopyToClipboardModule,
],
exports: [TranslateModule],
})

View File

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

View File

@ -5,7 +5,7 @@ import { MatDialog } from '@angular/material/dialog';
import { MatTableDataSource } from '@angular/material/table';
import { ActivatedRoute, Params, Router } from '@angular/router';
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 { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.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 membersSubject: 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(
public translate: TranslateService,

View File

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

View File

@ -6,16 +6,21 @@
</button>
</div>
<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"
*ngFor="let app of appsSubject | async">
<div class="morph-card">
<div class="morph-card" matRipple>
{{ app.name.charAt(0)}}
</div>
<span class="name">{{app.name}}</span>
</div>
<ng-template appHasRole [appHasRole]="['project.app.write']">
<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>
</div>
<span class="name">{{ 'ACTIONS.NEW' | translate }}</span>

View File

@ -21,6 +21,14 @@
margin: 0 -1rem;
padding-bottom: 2rem;
.sp-container {
display: flex;
justify-content: center;
align-items: center;
width: calc(82px + 2rem);
height: calc(82px + 2rem);
}
.app-wrap {
outline: none;
display: flex;
@ -40,11 +48,24 @@
margin: 1rem;
border-radius: .5rem;
border: 1px solid $accent-color;
font-weight: 800;
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 {
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;
@Output() public changeView: EventEmitter<void> = new EventEmitter();
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();
constructor(private projectService: ProjectService) { }

View File

@ -19,7 +19,7 @@ export class ProjectApplicationsDataSource extends DataSource<Application.AsObje
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;
this.loadingSubject.next(true);

View File

@ -1,54 +1,41 @@
<div class="table-header-row">
<div class="col">
<ng-container *ngIf="!selection.hasValue()">
<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']">
<app-refresh-table [loading]="dataSource.loading$ | async" [selection]="selection" (refreshed)="refreshPage()"
[dataSize]="dataSource.totalResult">
<ng-template appHasRole [appHasRole]="['project.app.write']" actions>
<a [disabled]="disabled" class="add-button" [routerLink]="[ '/projects', projectId, 'apps', 'create']"
color="primary" mat-raised-button>
<mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }}
</a>
</ng-template>
</div>
<div class="table-wrapper">
<div class="spinner-container" *ngIf="dataSource.loading$ | async">
<mat-spinner diameter="50"></mat-spinner>
<div class="table-wrapper">
<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]="25"
[pageSizeOptions]="[25, 50, 100, 250]">
</mat-paginator>
</div>
<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>
</app-refresh-table>

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 {
overflow: auto;
.spinner-container {
display: flex;
align-items: center;
justify-content: center;
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;
}
}
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 {

View File

@ -33,7 +33,7 @@ export class ApplicationsComponent implements AfterViewInit, OnInit {
public ngOnInit(): void {
this.dataSource = new ProjectApplicationsDataSource(this.projectService);
this.dataSource.loadApps(this.projectId, 0, 25, 'asc');
this.dataSource.loadApps(this.projectId, 0, 25);
}
public ngAfterViewInit(): void {
@ -51,7 +51,6 @@ export class ApplicationsComponent implements AfterViewInit, OnInit {
this.projectId,
this.paginator.pageIndex,
this.paginator.pageSize,
this.sort.direction,
);
}
@ -66,4 +65,8 @@ export class ApplicationsComponent implements AfterViewInit, OnInit {
this.selection.clear() :
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 label="Details">
<app-contributors [totalResult]="totalMemberResult" [membersSubject]="membersSubject"
title="{{ 'PROJECT.MEMBER.TITLE' | translate }}"
<app-contributors [loading]="loading$ | async" [totalResult]="totalMemberResult"
[membersSubject]="membersSubject" title="{{ 'PROJECT.MEMBER.TITLE' | translate }}"
description="{{ 'PROJECT.MEMBER.TITLEDESC' | translate }}" (addClicked)="openAddMember()"
(showDetailClicked)="showDetail()" [disabled]="false">
</app-contributors>

View File

@ -5,7 +5,7 @@ import { MatDialog } from '@angular/material/dialog';
import { MatTableDataSource } from '@angular/material/table';
import { ActivatedRoute, Params, Router } from '@angular/router';
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 { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.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 membersSubject: 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(
public translate: TranslateService,

View File

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

View File

@ -29,7 +29,8 @@
</div>
<button [ngClass]="{ selected: selection.isSelected(item)}" (click)="selection.toggle(item)" class="edit-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>
</div>
@ -57,7 +58,8 @@
</div>
<button [ngClass]="{ selected: selection.isSelected(item)}" (click)="selection.toggle(item)" class="edit-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>
</div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,7 +13,7 @@
<span>{{login}}</span>
<button color="primary" [disabled]="copied == login"
[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-check"></i>
</button>

View File

@ -39,9 +39,11 @@ export class AuthUserDetailComponent implements OnDestroy {
private dialog: MatDialog,
) {
this.loading = true;
this.getData().then(() => {
this.userService.GetMyUser().then(user => {
this.user = user.toObject();
this.loading = false;
}).catch(error => {
this.toast.showError(error);
this.loading = false;
});
}
@ -149,30 +151,4 @@ export class AuthUserDetailComponent implements OnDestroy {
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 {
this.service.GetMyMfas().then(mfas => {
console.log(mfas.toObject().mfasList);
this.dataSource = new MatTableDataSource(mfas.toObject().mfasList);
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;
$light-background: #fafafa;
:root {
transition: none;
@ -21,6 +22,7 @@ $dark-background: #2d2e30;
height: 6rem;
background: linear-gradient(40deg, #FF0080,#FF8C00 70%);
margin: auto;
box-shadow: 0 30px 60px rgba(0,0,0,0.12);
}
.crescent {
@ -29,10 +31,10 @@ $dark-background: #2d2e30;
right: 0;
width: 4rem;
height: 4rem;
background: #fafafa;
background: $light-background;
transform: scale(0);
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 {
@ -66,7 +68,7 @@ label {
.toggle {
position: absolute;
background-color: #fafafa;
background-color: $light-background;
box-shadow: 0 2px 15px rgba(0,0,0,.15);
}
@ -101,7 +103,7 @@ label {
[type="checkbox"]:checked + .theme-app{
background-color: $dark-background;
color: #fafafa;
color: $light-background;
}
[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 { TranslateModule } from '@ngx-translate/core';
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 { CardModule } from 'src/app/modules/card/card.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,
SharedModule,
RefreshTableModule,
CopyToClipboardModule,
],
})
export class UserDetailModule { }

View File

@ -25,12 +25,13 @@
<app-card title="{{ 'USER.PAGES.LOGINNAMES' | translate }}"
description="{{ 'USER.PAGES.LOGINNAMESDESC' | translate }}" *ngIf="user">
<div class="login-name-row" *ngFor="let login of user?.loginNamesList">
<span>{{login}}</span>
<span>{{login}} </span>
<button color="primary" [disabled]="copied == login"
[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-check"></i>
</button>
</div>
</app-card>

View File

@ -1,6 +1,6 @@
import { Location } from '@angular/common';
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 { Subscription } from 'rxjs';
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 languages: string[] = ['de', 'en'];
public isMgmt: boolean = false;
private subscription: Subscription = new Subscription();
public emailEditState: boolean = false;
public phoneEditState: boolean = false;
@ -49,11 +48,11 @@ export class UserDetailComponent implements OnInit, OnDestroy {
public ngOnInit(): void {
this.subscription = this.route.params.subscribe(params => {
this.loading = true;
this.getData(params).then(() => {
this.loading = false;
}).catch(error => {
this.loading = false;
const { id } = params;
this.mgmtUserService.GetUserByID(id).then(user => {
this.user = user.toObject();
}).catch(err => {
console.error(err);
});
});
}
@ -157,15 +156,6 @@ export class UserDetailComponent implements OnInit, OnDestroy {
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 {
this.mgmtUserService.SendSetPasswordNotification(this.user.id, NotificationType.NOTIFICATIONTYPE_EMAIL)
.then(() => {
@ -174,22 +164,4 @@ export class UserDetailComponent implements OnInit, OnDestroy {
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 { MatDialog } from '@angular/material/dialog';
import { MatSnackBar, MatSnackBarConfig } from '@angular/material/snack-bar';
import { TranslateService } from '@ngx-translate/core';
import { Observable } from 'rxjs';
import { take } from 'rxjs/operators';
import { WarnDialogComponent } from '../modules/warn-dialog/warn-dialog.component';
import { AuthService } from './auth.service';
@Injectable({
providedIn: 'root',
})
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 {
if (i18nkey) {
@ -28,10 +34,19 @@ export class ToastService {
const { message, code, metadata } = grpcError;
// TODO: remove check for code === 7
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(() => {
this.authService.authenticate(undefined, true, true);
dialogRef.afterClosed().pipe(take(1)).subscribe(resp => {
if (resp) {
this.authService.authenticate(undefined, true, true);
}
});
} else {
this.showMessage(decodeURI(message), 'close', { duration: 3000 });

View File

@ -16,7 +16,11 @@
"USERS_BUTTON": "Benutzer anzeigen",
"IAM": "Identity and Access Management",
"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": {
"PERSONAL_INFO": "Persönliche Informationen",
@ -51,10 +55,15 @@
"CHANGE":"Ändern",
"REACTIVATE":"Aktivieren",
"DEACTIVATE":"Deaktivieren",
"REFRESH":"Aktualisieren"
"REFRESH":"Aktualisieren",
"LOGIN":"Login"
},
"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": {
"TITLE": "Persönliche Informationen",
@ -102,7 +111,8 @@
"NAMEANDEMAILSECTION":"Name und Email",
"GENDERLANGSECTION":"Geschlecht und Sprache",
"PHONESECTION":"Telefonnummer",
"PASSWORDSECTION":"Setze ein initiales Passwort."
"PASSWORDSECTION":"Setze ein initiales Passwort.",
"ADDRESSANDPHONESECTION":"Telefonnummer"
},
"CODEDIALOG": {
"TITLE":"Telefonnummer verifizieren",

View File

@ -16,7 +16,11 @@
"USERS_BUTTON": "Show users",
"IAM": "Identity and Access Management",
"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": {
"PERSONAL_INFO": "Personal Information",
@ -51,10 +55,15 @@
"CHANGE":"Change",
"REACTIVATE":"Reactivate",
"DEACTIVATE":"Deactivate",
"REFRESH":"Refresh"
"REFRESH":"Refresh",
"LOGIN":"Login"
},
"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": {
"TITLE": "Personal Information",
@ -102,7 +111,8 @@
"NAMEANDEMAILSECTION":"Name and Email",
"GENDERLANGSECTION":"Gender and Language",
"PHONESECTION":"Phonenumbers",
"PASSWORDSECTION":"Set the initial Password."
"PASSWORDSECTION":"Set the initial Password.",
"ADDRESSANDPHONESECTION":"Phonenumber"
},
"CODEDIALOG": {
"TITLE":"Verify Phone Number",

View File

@ -61,29 +61,29 @@
<path d="M1496.17,759.473L1555.54,720.014" style="fill:none;"/>
</g>
<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 transform="matrix(1.299,0,0,1.08306,-3394.18,-2084.88)">
<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 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 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 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 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 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 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>

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 'src/app/pages/projects/owned-projects/owned-project-detail/application-grid/application-grid.component';
@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) {
@include avatar-theme($theme);

View File

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

View File

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

View File

@ -48,7 +48,7 @@
}
.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;
transition: background-color .4s ease-in-out;
}
@ -56,6 +56,9 @@
.admin-line {
background: $accent-color;
color: white;
margin-right: 1rem;
border-top-right-radius: 50vw;
border-bottom-right-radius: 50vw;
// &::after {
// 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;
}
}