fix(console): toast login handler, show user session loginname, policy value incrementation, accessibility (#413)

* get auth policy, fix increment from 0

* seo, accessibility

* ngsw rem check for update

* organize interceptors

* toast i18n part1

* show loginname

* use primary color

* toast login handler, fix user session type

* 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>

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

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

* Update console/src/index.html

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

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

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

* Update console/src/assets/i18n/en.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-09 10:54:55 +02:00 committed by GitHub
parent fa57cc48c1
commit 0721acf605
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
69 changed files with 796 additions and 675 deletions

View File

@ -4,10 +4,10 @@
<mat-icon aria-label="Side nav toggle icon">menu</mat-icon> <mat-icon aria-label="Side nav toggle icon">menu</mat-icon>
</button> </button>
<a *ngIf="(isHandset$ | async) == false" class="title" [routerLink]="['/']"> <a *ngIf="(isHandset$ | async) == false" class="title" [routerLink]="['/']">
<img class="logo" *ngIf="componentCssClass == 'dark-theme'; else lighttheme" <img class="logo" alt="zitadel logo" *ngIf="componentCssClass == 'dark-theme'; else lighttheme"
src="./assets/images/zitadel-logo-oneline-darkdesign.svg" /> src="../assets/images/zitadel-logo-oneline-darkdesign.svg" />
<ng-template #lighttheme> <ng-template #lighttheme>
<img class="logo" src="./assets/images/zitadel-logo-oneline-lightdesign.svg" /> <img alt="zitadel logo" class="logo" src="../assets/images/zitadel-logo-oneline-lightdesign.svg" />
</ng-template> </ng-template>
</a> </a>

View File

@ -10,7 +10,7 @@ import { TranslateService } from '@ngx-translate/core';
import { Observable, of, Subscription } from 'rxjs'; import { Observable, of, Subscription } from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { Org, UserProfile } from './proto/generated/auth_pb'; import { Org, UserProfileView } from './proto/generated/auth_pb';
import { AuthUserService } from './services/auth-user.service'; import { AuthUserService } from './services/auth-user.service';
import { AuthService } from './services/auth.service'; import { AuthService } from './services/auth.service';
import { ThemeService } from './services/theme.service'; import { ThemeService } from './services/theme.service';
@ -128,7 +128,7 @@ export class AppComponent implements OnDestroy {
public showAccount: boolean = false; public showAccount: boolean = false;
public org!: Org.AsObject; public org!: Org.AsObject;
public orgs: Org.AsObject[] = []; public orgs: Org.AsObject[] = [];
public profile!: UserProfile.AsObject; public profile!: UserProfileView.AsObject;
public isDarkTheme: Observable<boolean> = of(true); public isDarkTheme: Observable<boolean> = of(true);
public orgLoading: boolean = false; public orgLoading: boolean = false;
@ -248,7 +248,7 @@ export class AppComponent implements OnDestroy {
this.orgs = res.toObject().resultList; this.orgs = res.toObject().resultList;
this.orgLoading = false; this.orgLoading = false;
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
this.orgLoading = false; this.orgLoading = false;
}); });
} }

View File

@ -1,5 +1,5 @@
import { OverlayModule } from '@angular/cdk/overlay'; import { OverlayModule } from '@angular/cdk/overlay';
import { APP_BASE_HREF, CommonModule, registerLocaleData, PlatformLocation } from '@angular/common'; import { CommonModule, registerLocaleData } from '@angular/common';
import { HttpClient, HttpClientModule } from '@angular/common/http'; import { HttpClient, HttpClientModule } from '@angular/common/http';
import localeDe from '@angular/common/locales/de'; import localeDe from '@angular/common/locales/de';
import { APP_INITIALIZER, NgModule } from '@angular/core'; import { APP_INITIALIZER, NgModule } from '@angular/core';
@ -29,10 +29,10 @@ import { AvatarModule } from './modules/avatar/avatar.module';
import { SignedoutComponent } from './pages/signedout/signedout.component'; import { SignedoutComponent } from './pages/signedout/signedout.component';
import { AuthUserService } from './services/auth-user.service'; import { AuthUserService } from './services/auth-user.service';
import { AuthService } from './services/auth.service'; import { AuthService } from './services/auth.service';
import { GrpcAuthInterceptor } from './services/grpc-auth.interceptor';
import { GRPC_INTERCEPTORS } from './services/grpc-interceptor';
import { GrpcOrgInterceptor } from './services/grpc-org.interceptor';
import { GrpcService } from './services/grpc.service'; import { GrpcService } from './services/grpc.service';
import { GrpcAuthInterceptor } from './services/interceptors/grpc-auth.interceptor';
import { GRPC_INTERCEPTORS } from './services/interceptors/grpc-interceptor';
import { GrpcOrgInterceptor } from './services/interceptors/grpc-org.interceptor';
import { StatehandlerProcessorService, StatehandlerProcessorServiceImpl } from './services/statehandler-processor.service'; import { StatehandlerProcessorService, StatehandlerProcessorServiceImpl } from './services/statehandler-processor.service';
import { StatehandlerService, StatehandlerServiceImpl } from './services/statehandler.service'; import { StatehandlerService, StatehandlerServiceImpl } from './services/statehandler.service';
import { StorageService } from './services/storage.service'; import { StorageService } from './services/storage.service';
@ -151,7 +151,5 @@ const authConfig: AuthConfig = {
}) })
export class AppModule { export class AppModule {
constructor() { constructor() { }
console.log(window.location.href);
}
} }

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { AuthService } from '../services/auth.service'; import { AuthService } from '../services/auth.service';
@ -9,7 +9,7 @@ import { AuthService } from '../services/auth.service';
providedIn: 'root', providedIn: 'root',
}) })
export class AuthGuard implements CanActivate { export class AuthGuard implements CanActivate {
constructor(private auth: AuthService, private router: Router) { } constructor(private auth: AuthService) { }
public canActivate( public canActivate(
_: ActivatedRouteSnapshot, _: ActivatedRouteSnapshot,

View File

@ -3,8 +3,8 @@
[name]="profile.displayName ? profile.displayName : (profile.firstName + ' '+ profile.lastName)" [size]="80"> [name]="profile.displayName ? profile.displayName : (profile.firstName + ' '+ profile.lastName)" [size]="80">
</app-avatar> </app-avatar>
<span class="u-name">{{profile?.firstName}} {{profile?.lastName}}</span> <span class="u-name">{{profile.displayName ? profile.displayName : profile.userName}}</span>
<span class="u-email">{{profile?.userName}}</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 (click)="editUserProfile()" mat-stroked-button>{{'USER.EDITACCOUNT' | translate}}</button>
@ -16,7 +16,8 @@
</app-avatar> </app-avatar>
<div class="col"> <div class="col">
<span class="title">{{user.userName}}</span> <span class="title">{{user.displayName ? user.displayName : user.userName}} </span>
<span class="loginname">{{user.loginName}}</span>
<span class="email">{{'USER.STATE.'+user.authState | translate}}</span> <span class="email">{{'USER.STATE.'+user.authState | translate}}</span>
</div> </div>
<span class="fill-space"></span> <span class="fill-space"></span>

View File

@ -90,7 +90,7 @@
line-height: 1rem; line-height: 1rem;
} }
.email { .email, .loginname {
color: #8795a1; color: #8795a1;
font-size: .8rem; font-size: .8rem;
line-height: 1rem; line-height: 1rem;

View File

@ -1,7 +1,7 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { AuthConfig } from 'angular-oauth2-oidc'; import { AuthConfig } from 'angular-oauth2-oidc';
import { UserProfile, UserSessionView } from 'src/app/proto/generated/auth_pb'; import { UserProfileView, UserSessionView } from 'src/app/proto/generated/auth_pb';
import { AuthUserService } from 'src/app/services/auth-user.service'; import { AuthUserService } from 'src/app/services/auth-user.service';
import { AuthService } from 'src/app/services/auth.service'; import { AuthService } from 'src/app/services/auth.service';
@ -11,7 +11,7 @@ import { AuthService } from 'src/app/services/auth.service';
styleUrls: ['./accounts-card.component.scss'], styleUrls: ['./accounts-card.component.scss'],
}) })
export class AccountsCardComponent implements OnInit { export class AccountsCardComponent implements OnInit {
@Input() public profile!: UserProfile.AsObject; @Input() public profile!: UserProfileView.AsObject;
@Input() public iamuser: boolean = false; @Input() public iamuser: boolean = false;
@Output() public close: EventEmitter<void> = new EventEmitter(); @Output() public close: EventEmitter<void> = new EventEmitter();
@ -20,7 +20,6 @@ export class AccountsCardComponent implements OnInit {
constructor(public authService: AuthService, private router: Router, private userService: AuthUserService) { constructor(public authService: AuthService, private router: Router, private userService: AuthUserService) {
this.userService.getMyUserSessions().then(sessions => { this.userService.getMyUserSessions().then(sessions => {
this.users = sessions.toObject().userSessionsList; this.users = sessions.toObject().userSessionsList;
const index = this.users.findIndex(user => user.userName === this.profile.userName); const index = this.users.findIndex(user => user.userName === this.profile.userName);
this.users.splice(index, 1); this.users.splice(index, 1);
@ -45,14 +44,14 @@ export class AccountsCardComponent implements OnInit {
} }
} }
public selectAccount(loginName?: string): void { public selectAccount(loginHint?: string): void {
const configWithPrompt: Partial<AuthConfig> = { const configWithPrompt: Partial<AuthConfig> = {
customQueryParams: { customQueryParams: {
// prompt: 'select_account', // prompt: 'select_account',
} as any, } as any,
}; };
if (loginName) { if (loginHint) {
(configWithPrompt as any).customQueryParams['login_hint'] = loginName; (configWithPrompt as any).customQueryParams['login_hint'] = loginHint;
} }
this.authService.authenticate(configWithPrompt); this.authService.authenticate(configWithPrompt);
} }

View File

@ -37,19 +37,19 @@ export class MemberCreateDialogComponent {
this.projectService.GetProjectGrantMemberRoles().then(resp => { this.projectService.GetProjectGrantMemberRoles().then(resp => {
this.memberRoleOptions = resp.toObject().rolesList; this.memberRoleOptions = resp.toObject().rolesList;
}).catch(error => { }).catch(error => {
toastService.showError(error.message); toastService.showError(error);
}); });
} else if (this.creationType === CreationType.PROJECT_OWNED) { } else if (this.creationType === CreationType.PROJECT_OWNED) {
this.projectService.GetProjectMemberRoles().then(resp => { this.projectService.GetProjectMemberRoles().then(resp => {
this.memberRoleOptions = resp.toObject().rolesList; this.memberRoleOptions = resp.toObject().rolesList;
}).catch(error => { }).catch(error => {
toastService.showError(error.message); toastService.showError(error);
}); });
} else if (this.creationType === CreationType.IAM) { } else if (this.creationType === CreationType.IAM) {
this.adminService.GetIamMemberRoles().then(resp => { this.adminService.GetIamMemberRoles().then(resp => {
this.memberRoleOptions = resp.toObject().rolesList; this.memberRoleOptions = resp.toObject().rolesList;
}).catch(error => { }).catch(error => {
toastService.showError(error.message); toastService.showError(error);
}); });
} }
} }

View File

@ -105,7 +105,6 @@ export class ChangesComponent implements OnInit {
return from(col).pipe( return from(col).pipe(
tap((res: Changes) => { tap((res: Changes) => {
let values = res.toObject().changesList; let values = res.toObject().changesList;
console.log(values);
// If prepending, reverse the batch order // If prepending, reverse the batch order
values = false ? values.reverse() : values; values = false ? values.reverse() : values;

View File

@ -58,7 +58,6 @@ export class ProjectContributorsComponent implements OnInit {
finalize(() => this.loadingSubject.next(false)), finalize(() => this.loadingSubject.next(false)),
).subscribe(members => { ).subscribe(members => {
this.membersSubject.next(members); this.membersSubject.next(members);
console.log(members);
}); });
} }
} }
@ -83,7 +82,7 @@ export class ProjectContributorsComponent implements OnInit {
})).then(() => { })).then(() => {
this.toast.showError('members added'); this.toast.showError('members added');
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }
} }

View File

@ -42,7 +42,6 @@ export class ProjectMembersDataSource extends DataSource<ProjectMember.AsObject>
finalize(() => this.loadingSubject.next(false)), finalize(() => this.loadingSubject.next(false)),
).subscribe(members => { ).subscribe(members => {
this.membersSubject.next(members); this.membersSubject.next(members);
console.log(members);
}); });
} }
} }

View File

@ -42,9 +42,6 @@ export class ProjectMembersComponent implements AfterViewInit {
this.getRoleOptions(); this.getRoleOptions();
this.route.params.subscribe(params => { this.route.params.subscribe(params => {
console.log(params);
console.log(this.projectType);
this.projectService.GetProjectById(params.projectid).then(project => { this.projectService.GetProjectById(params.projectid).then(project => {
this.project = project.toObject(); this.project = project.toObject();
this.dataSource = new ProjectMembersDataSource(this.projectService); this.dataSource = new ProjectMembersDataSource(this.projectService);
@ -69,13 +66,13 @@ export class ProjectMembersComponent implements AfterViewInit {
this.projectService.GetProjectGrantMemberRoles().then(resp => { this.projectService.GetProjectGrantMemberRoles().then(resp => {
this.memberRoleOptions = resp.toObject().rolesList; this.memberRoleOptions = resp.toObject().rolesList;
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} else if (this.projectType === ProjectType.PROJECTTYPE_OWNED) { } else if (this.projectType === ProjectType.PROJECTTYPE_OWNED) {
this.projectService.GetProjectMemberRoles().then(resp => { this.projectService.GetProjectMemberRoles().then(resp => {
this.memberRoleOptions = resp.toObject().rolesList; this.memberRoleOptions = resp.toObject().rolesList;
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }
} }
@ -92,18 +89,18 @@ export class ProjectMembersComponent implements AfterViewInit {
public removeProjectMemberSelection(): void { public removeProjectMemberSelection(): void {
Promise.all(this.selection.selected.map(member => { Promise.all(this.selection.selected.map(member => {
return this.projectService.RemoveProjectMember(this.project.projectId, member.userId).then(() => { return this.projectService.RemoveProjectMember(this.project.projectId, member.userId).then(() => {
this.toast.showInfo('Removed successfully'); this.toast.showInfo('PROJECT.TOAST.MEMBERREMOVED', true);
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
})); }));
} }
public removeMember(member: ProjectMember.AsObject): void { public removeMember(member: ProjectMember.AsObject): void {
this.projectService.RemoveProjectMember(this.project.projectId, member.userId).then(() => { this.projectService.RemoveProjectMember(this.project.projectId, member.userId).then(() => {
this.toast.showInfo('Member removed successfully'); this.toast.showInfo('PROJECT.TOAST.MEMBERREMOVED', true);
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }
@ -137,9 +134,9 @@ export class ProjectMembersComponent implements AfterViewInit {
Promise.all(users.map(user => { Promise.all(users.map(user => {
return this.projectService.AddProjectMember(this.project.projectId, user.id, roles); return this.projectService.AddProjectMember(this.project.projectId, user.id, roles);
})).then(() => { })).then(() => {
this.toast.showError('members added'); this.toast.showInfo('PROJECT.TOAST.MEMBERSADDED', true);
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }
} }
@ -147,13 +144,11 @@ export class ProjectMembersComponent implements AfterViewInit {
} }
updateRoles(member: ProjectMember.AsObject, selectionChange: MatSelectChange): void { updateRoles(member: ProjectMember.AsObject, selectionChange: MatSelectChange): void {
console.log(member, selectionChange.value);
this.projectService.ChangeProjectMember(this.project.projectId, member.userId, selectionChange.value) this.projectService.ChangeProjectMember(this.project.projectId, member.userId, selectionChange.value)
.then((newmember: ProjectMember) => { .then((newmember: ProjectMember) => {
console.log(newmember.toObject()); this.toast.showInfo('PROJECT.TOAST.MEMBERADDED', true);
this.toast.showInfo('Member updated!');
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }
} }

View File

@ -23,7 +23,6 @@ export class ProjectRoleDetailComponent implements OnInit {
displayName: new FormControl(''), displayName: new FormControl(''),
group: new FormControl(''), group: new FormControl(''),
}); });
console.log(data);
this.formGroup.patchValue(data.role); this.formGroup.patchValue(data.role);
} }
@ -35,10 +34,10 @@ export class ProjectRoleDetailComponent implements OnInit {
if (this.formGroup.valid && this.key?.value && this.group?.value && this.displayName?.value) { if (this.formGroup.valid && this.key?.value && this.group?.value && this.displayName?.value) {
this.projectService.ChangeProjectRole(this.projectId, this.key.value, this.key.value, this.group.value) this.projectService.ChangeProjectRole(this.projectId, this.key.value, this.key.value, this.group.value)
.then(() => { .then(() => {
this.toast.showInfo('Role updated'); this.toast.showInfo('PROJECT.TOAST.ROLECHANGED', true);
this.dialogRef.close(true); this.dialogRef.close(true);
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }
} }

View File

@ -33,7 +33,6 @@ export class ProjectRolesComponent implements AfterViewInit, OnInit {
constructor(private projectService: ProjectService, private toast: ToastService, private dialog: MatDialog) { } constructor(private projectService: ProjectService, private toast: ToastService, private dialog: MatDialog) { }
public ngOnInit(): void { public ngOnInit(): void {
console.log(this.projectId);
this.dataSource = new ProjectRolesDataSource(this.projectService); this.dataSource = new ProjectRolesDataSource(this.projectService);
this.dataSource.loadRoles(this.projectId, 0, 25, 'asc'); this.dataSource.loadRoles(this.projectId, 0, 25, 'asc');
@ -85,7 +84,7 @@ export class ProjectRolesComponent implements AfterViewInit, OnInit {
return Promise.all(this.selection.selected.map(role => { return Promise.all(this.selection.selected.map(role => {
return this.projectService.RemoveProjectRole(role.projectId, role.key); return this.projectService.RemoveProjectRole(role.projectId, role.key);
})).then(() => { })).then(() => {
this.toast.showInfo('Deleted'); this.toast.showInfo('PROJECT.TOAST.ROLEREMOVED', true);
indexes.forEach(index => { indexes.forEach(index => {
if (index > -1) { if (index > -1) {
oldState.splice(index, 1); oldState.splice(index, 1);
@ -102,7 +101,7 @@ export class ProjectRolesComponent implements AfterViewInit, OnInit {
this.projectService this.projectService
.RemoveProjectRole(role.projectId, role.key) .RemoveProjectRole(role.projectId, role.key)
.then(() => { .then(() => {
this.toast.showInfo('Role removed'); this.toast.showInfo('PROJECT.TOAST.ROLEREMOVED', true);
this.dataSource.rolesSubject.value.splice(index, 1); this.dataSource.rolesSubject.value.splice(index, 1);
this.dataSource.rolesSubject.next(this.dataSource.rolesSubject.value); this.dataSource.rolesSubject.next(this.dataSource.rolesSubject.value);
}) })

View File

@ -56,7 +56,6 @@ export class SearchProjectAutocompleteComponent {
).subscribe(([granted, owned]) => { ).subscribe(([granted, owned]) => {
this.isLoading = false; this.isLoading = false;
this.filteredProjects = [...owned.toObject().resultList, ...granted.toObject().resultList]; this.filteredProjects = [...owned.toObject().resultList, ...granted.toObject().resultList];
console.log(this.filteredProjects);
}); });
} }
@ -102,7 +101,6 @@ export class SearchProjectAutocompleteComponent {
} }
public selected(event: MatAutocompleteSelectedEvent): void { public selected(event: MatAutocompleteSelectedEvent): void {
console.log(event.option.value);
if (this.singleOutput) { if (this.singleOutput) {
this.selectionChanged.emit(event.option.value); this.selectionChanged.emit(event.option.value);
} else { } else {

View File

@ -140,7 +140,7 @@ export class SearchUserAutocompleteComponent {
this.users = [user.toObject()]; this.users = [user.toObject()];
this.selectionChanged.emit(this.users); this.selectionChanged.emit(this.users);
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }
} }

View File

@ -77,7 +77,6 @@ export class UserGrantsDataSource extends DataSource<UserGrant.AsObject> {
from(promise).pipe( from(promise).pipe(
map(resp => { map(resp => {
this.totalResult = resp.toObject().totalResult; this.totalResult = resp.toObject().totalResult;
console.log(resp.toObject().resultList);
return resp.toObject().resultList; return resp.toObject().resultList;
}), }),
catchError(() => of([])), catchError(() => of([])),

View File

@ -56,7 +56,6 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
grantId: this.grantId, grantId: this.grantId,
userId: this.userId, userId: this.userId,
}; };
console.log(this.context);
switch (this.context) { switch (this.context) {
case UserGrantContext.OWNED_PROJECT: case UserGrantContext.OWNED_PROJECT:
@ -78,7 +77,6 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
default: default:
this.routerLink = ['/grant-create']; this.routerLink = ['/grant-create'];
} }
console.log(data);
this.dataSource.loadGrants(this.context, 0, 25, data); this.dataSource.loadGrants(this.context, 0, 25, data);
} }
@ -129,20 +127,18 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
updateRoles(grant: UserGrant.AsObject, selectionChange: MatSelectChange): void { updateRoles(grant: UserGrant.AsObject, selectionChange: MatSelectChange): void {
this.userService.UpdateUserGrant(grant.id, grant.userId, selectionChange.value) this.userService.UpdateUserGrant(grant.id, grant.userId, selectionChange.value)
.then((newmember: UserGrant) => { .then(() => {
this.toast.showInfo('Grant updated!'); this.toast.showInfo('GRANTS.TOAST.UPDATED', true);
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }
deleteGrantSelection(): void { deleteGrantSelection(): void {
this.userService.BulkRemoveUserGrant(this.selection.selected.map(grant => grant.id)).then(() => { this.userService.BulkRemoveUserGrant(this.selection.selected.map(grant => grant.id)).then(() => {
this.toast.showInfo('Grants deleted'); this.toast.showInfo('GRANTS.TOAST.BULKREMOVED', true);
const data = this.dataSource.grantsSubject.getValue(); const data = this.dataSource.grantsSubject.getValue();
console.log(data);
this.selection.selected.forEach((item) => { this.selection.selected.forEach((item) => {
console.log(item);
const index = data.findIndex(i => i.id === item.id); const index = data.findIndex(i => i.id === item.id);
if (index > -1) { if (index > -1) {
data.splice(index, 1); data.splice(index, 1);
@ -151,7 +147,7 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
}); });
this.selection.clear(); this.selection.clear();
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }
} }

View File

@ -137,7 +137,7 @@ export class AppDetailComponent implements OnInit, OnDestroy {
} }
}).catch(error => { }).catch(error => {
console.error(error); console.error(error);
this.toast.showError(error.message); this.toast.showError(error);
this.errorMessage = error.message; this.errorMessage = error.message;
}); });
} }
@ -145,15 +145,15 @@ export class AppDetailComponent implements OnInit, OnDestroy {
public changeState(event: MatButtonToggleChange): void { public changeState(event: MatButtonToggleChange): void {
if (event.value === AppState.APPSTATE_ACTIVE) { if (event.value === AppState.APPSTATE_ACTIVE) {
this.projectService.ReactivateApplication(this.projectId, this.app.id).then(() => { this.projectService.ReactivateApplication(this.projectId, this.app.id).then(() => {
this.toast.showInfo('Reactivated Application'); this.toast.showInfo('APP.TOAST.REACTIVATED', true);
}).catch((error: any) => { }).catch((error: any) => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} else if (event.value === AppState.APPSTATE_INACTIVE) { } else if (event.value === AppState.APPSTATE_INACTIVE) {
this.projectService.DeactivateApplication(this.projectId, this.app.id).then(() => { this.projectService.DeactivateApplication(this.projectId, this.app.id).then(() => {
this.toast.showInfo('Deactivated Application'); this.toast.showInfo('APP.TOAST.REACTIVATED', true);
}).catch((error: any) => { }).catch((error: any) => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }
@ -222,7 +222,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((data: OIDCConfig) => {
this.toast.showInfo('OIDC Config saved'); this.toast.showInfo('APP.TOAST.OIDCUPDATED', true);
}) })
.catch(data => { .catch(data => {
this.toast.showError(data.message); this.toast.showError(data.message);
@ -233,7 +233,7 @@ export class AppDetailComponent implements OnInit, OnDestroy {
public regenerateOIDCClientSecret(): void { public regenerateOIDCClientSecret(): void {
this.projectService.RegenerateOIDCClientSecret(this.app.id, this.projectId).then((data: OIDCConfig) => { this.projectService.RegenerateOIDCClientSecret(this.app.id, this.projectId).then((data: OIDCConfig) => {
this.toast.showInfo('OIDC Secret Regenerated'); this.toast.showInfo('APP.TOAST.OIDCCLIENTSECRETREGENERATED', true);
this.dialog.open(AppSecretDialogComponent, { this.dialog.open(AppSecretDialogComponent, {
data: { data: {
clientId: data.toObject().clientId, clientId: data.toObject().clientId,

View File

@ -91,9 +91,8 @@ export class GrantedProjectDetailComponent implements OnInit, OnDestroy {
if (this.projectId && this.grantId) { if (this.projectId && this.grantId) {
this.projectService.GetGrantedProjectByID(this.projectId, this.grantId).then(proj => { this.projectService.GetGrantedProjectByID(this.projectId, this.grantId).then(proj => {
this.project = proj.toObject(); this.project = proj.toObject();
console.log(this.project);
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }
} }

View File

@ -61,7 +61,7 @@ export class GrantedProjectGridComponent implements OnChanges {
} }
public ngOnChanges(changes: SimpleChanges): void { public ngOnChanges(changes: SimpleChanges): void {
if (changes.items.currentValue && changes.items.currentValue.length > 0) { if (changes.items?.currentValue && changes.items.currentValue.length > 0) {
this.notPinned = Object.assign([], this.items); this.notPinned = Object.assign([], this.items);
this.reorganizeItems(); this.reorganizeItems();
} }

View File

@ -95,7 +95,7 @@ export class GrantedProjectListComponent implements OnInit, OnDestroy {
this.loadingSubject.next(false); this.loadingSubject.next(false);
}).catch(error => { }).catch(error => {
console.error(error); console.error(error);
this.toast.showError(error.message); this.toast.showError(error);
this.loadingSubject.next(false); this.loadingSubject.next(false);
}); });
} }

View File

@ -1,8 +1,9 @@
<div class="wrapper"> <div class="wrapper">
<div class="header"> <div class="header">
<img *ngIf="dark; else lighttheme" src="./assets/images/zitadel-logo-oneline-darkdesign.svg" /> <img alt="zitadel logo" *ngIf="dark; else lighttheme"
src="../assets/images/zitadel-logo-oneline-darkdesign.svg" />
<ng-template #lighttheme> <ng-template #lighttheme>
<img src="./assets/images/zitadel-logo-oneline-lightdesign.svg" /> <img alt="zitadel logo" src="../assets/images/zitadel-logo-oneline-lightdesign.svg" />
</ng-template> </ng-template>
</div> </div>

View File

@ -76,7 +76,7 @@ export class IamContributorsComponent implements OnInit {
})).then(() => { })).then(() => {
this.toast.showError('members added'); this.toast.showError('members added');
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }
} }

View File

@ -55,7 +55,7 @@ export class IamMembersComponent implements AfterViewInit {
return this.adminService.RemoveIamMember(member.userId).then(() => { return this.adminService.RemoveIamMember(member.userId).then(() => {
this.toast.showInfo('Removed successfully'); this.toast.showInfo('Removed successfully');
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
})); }));
} }
@ -64,7 +64,7 @@ export class IamMembersComponent implements AfterViewInit {
this.adminService.RemoveIamMember(member.userId).then(() => { this.adminService.RemoveIamMember(member.userId).then(() => {
this.toast.showInfo('Member removed successfully'); this.toast.showInfo('Member removed successfully');
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }
@ -99,7 +99,7 @@ export class IamMembersComponent implements AfterViewInit {
})).then(() => { })).then(() => {
this.toast.showError('members added'); this.toast.showError('members added');
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }
} }

View File

@ -77,7 +77,7 @@ export class OrgContributorsComponent implements OnInit {
})).then(() => { })).then(() => {
this.toast.showError('members added'); this.toast.showError('members added');
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }
} }

View File

@ -4,9 +4,9 @@ import { Component } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms'; import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { CreateOrgRequest, CreateUserRequest, Gender, OrgSetUpResponse } from 'src/app/proto/generated/admin_pb'; import { CreateOrgRequest, CreateUserRequest, Gender, OrgSetUpResponse } from 'src/app/proto/generated/admin_pb';
import { PasswordComplexityPolicy } from 'src/app/proto/generated/management_pb'; import { PasswordComplexityPolicy } from 'src/app/proto/generated/auth_pb';
import { AdminService } from 'src/app/services/admin.service'; import { AdminService } from 'src/app/services/admin.service';
import { OrgService } from 'src/app/services/org.service'; import { AuthUserService } from 'src/app/services/auth-user.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
import { lowerCaseValidator, numberValidator, symbolValidator, upperCaseValidator } from '../../user-detail/validators'; import { lowerCaseValidator, numberValidator, symbolValidator, upperCaseValidator } from '../../user-detail/validators';
@ -62,7 +62,7 @@ export class OrgCreateComponent {
private adminService: AdminService, private adminService: AdminService,
private _location: Location, private _location: Location,
private fb: FormBuilder, private fb: FormBuilder,
private orgService: OrgService, private authUserService: AuthUserService,
) { ) {
const validators: Validators[] = [Validators.required]; const validators: Validators[] = [Validators.required];
@ -70,7 +70,7 @@ export class OrgCreateComponent {
name: ['', [Validators.required]], name: ['', [Validators.required]],
domain: [''], domain: [''],
}); });
this.orgService.GetPasswordComplexityPolicy().then(data => { this.authUserService.GetMyPasswordComplexityPolicy().then(data => {
this.policy = data.toObject(); this.policy = data.toObject();
if (this.policy.minLength) { if (this.policy.minLength) {
validators.push(Validators.minLength(this.policy.minLength)); validators.push(Validators.minLength(this.policy.minLength));

View File

@ -52,7 +52,7 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
this.orgService.GetMyOrg().then((org: Org) => { this.orgService.GetMyOrg().then((org: Org) => {
this.org = org.toObject(); this.org = org.toObject();
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
this.orgService.SearchMyOrgDomains(0, 100).then(result => { this.orgService.SearchMyOrgDomains(0, 100).then(result => {
@ -66,13 +66,13 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
this.orgService.ReactivateMyOrg().then(() => { this.orgService.ReactivateMyOrg().then(() => {
this.toast.showInfo('Reactivated Org'); this.toast.showInfo('Reactivated Org');
}).catch((error) => { }).catch((error) => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} else if (event.value === OrgState.ORGSTATE_INACTIVE) { } else if (event.value === OrgState.ORGSTATE_INACTIVE) {
this.orgService.DeactivateMyOrg().then(() => { this.orgService.DeactivateMyOrg().then(() => {
this.toast.showInfo('Deactivated Org'); this.toast.showInfo('Deactivated Org');
}).catch((error) => { }).catch((error) => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }
} }
@ -104,7 +104,7 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
this.domains.splice(index, 1); this.domains.splice(index, 1);
} }
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }
}); });

View File

@ -33,7 +33,7 @@ export class OrgGridComponent {
this.orgList = res.toObject().resultList; this.orgList = res.toObject().resultList;
}).catch(error => { }).catch(error => {
console.error(error); console.error(error);
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }

View File

@ -22,7 +22,7 @@ export class OrgMemberRolesAutocompleteComponent {
this.orgService.GetOrgMemberRoles().then(resp => { this.orgService.GetOrgMemberRoles().then(resp => {
this.allRoles = resp.toObject().rolesList; this.allRoles = resp.toObject().rolesList;
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
this.myControl.valueChanges.subscribe(change => { this.myControl.valueChanges.subscribe(change => {

View File

@ -59,7 +59,7 @@ export class OrgMembersComponent implements AfterViewInit {
return this.orgService.RemoveMyOrgMember(member.userId).then(() => { return this.orgService.RemoveMyOrgMember(member.userId).then(() => {
this.toast.showInfo('Removed successfully'); this.toast.showInfo('Removed successfully');
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
})); }));
} }
@ -68,7 +68,7 @@ export class OrgMembersComponent implements AfterViewInit {
this.orgService.RemoveMyOrgMember(member.userId).then(() => { this.orgService.RemoveMyOrgMember(member.userId).then(() => {
this.toast.showInfo('Member removed successfully'); this.toast.showInfo('Member removed successfully');
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }
@ -103,7 +103,7 @@ export class OrgMembersComponent implements AfterViewInit {
})).then(() => { })).then(() => {
this.toast.showError('members added'); this.toast.showError('members added');
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }
} }

View File

@ -35,7 +35,7 @@
<div class="row"> <div class="row">
<span class="left-desc">{{'ORG.POLICY.DATA.SHOWLOCKOUTFAILURES' | translate}}</span> <span class="left-desc">{{'ORG.POLICY.DATA.SHOWLOCKOUTFAILURES' | translate}}</span>
<span class="fill-space"></span> <span class="fill-space"></span>
<mat-slide-toggle name="showLockOutFailures" ngDefaultControl <mat-slide-toggle color="primary" name="showLockOutFailures" ngDefaultControl
[(ngModel)]="complexityData.showLockOutFailures"> [(ngModel)]="complexityData.showLockOutFailures">
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
@ -63,26 +63,28 @@
<div class="row"> <div class="row">
<span class="left-desc">{{'ORG.POLICY.DATA.HASNUMBER' | translate}}</span> <span class="left-desc">{{'ORG.POLICY.DATA.HASNUMBER' | translate}}</span>
<span class="fill-space"></span> <span class="fill-space"></span>
<mat-slide-toggle name="hasNumber" ngDefaultControl [(ngModel)]="complexityData.hasNumber"> <mat-slide-toggle color="primary" name="hasNumber" ngDefaultControl
[(ngModel)]="complexityData.hasNumber">
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
<div class="row"> <div class="row">
<span class="left-desc">{{'ORG.POLICY.DATA.HASSYMBOL' | translate}}</span> <span class="left-desc">{{'ORG.POLICY.DATA.HASSYMBOL' | translate}}</span>
<span class="fill-space"></span> <span class="fill-space"></span>
<mat-slide-toggle name="hasSymbol" ngDefaultControl [(ngModel)]="complexityData.hasSymbol"> <mat-slide-toggle color="primary" name="hasSymbol" ngDefaultControl
[(ngModel)]="complexityData.hasSymbol">
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
<div class="row"> <div class="row">
<span class="left-desc">{{'ORG.POLICY.DATA.HASLOWERCASE' | translate}}</span> <span class="left-desc">{{'ORG.POLICY.DATA.HASLOWERCASE' | translate}}</span>
<span class="fill-space"></span> <span class="fill-space"></span>
<mat-slide-toggle name="hasLowercase" ngDefaultControl <mat-slide-toggle color="primary" name="hasLowercase" ngDefaultControl
[(ngModel)]="complexityData.hasLowercase"> [(ngModel)]="complexityData.hasLowercase">
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
<div class="row"> <div class="row">
<span class="left-desc">{{'ORG.POLICY.DATA.HASUPPERCASE' | translate}}</span> <span class="left-desc">{{'ORG.POLICY.DATA.HASUPPERCASE' | translate}}</span>
<span class="fill-space"></span> <span class="fill-space"></span>
<mat-slide-toggle name="hasUppercase" ngDefaultControl <mat-slide-toggle color="primary" name="hasUppercase" ngDefaultControl
[(ngModel)]="complexityData.hasUppercase"> [(ngModel)]="complexityData.hasUppercase">
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
@ -132,13 +134,14 @@
<div class="row"> <div class="row">
<span class="left-desc">{{'ORG.POLICY.DATA.USERLOGINMUSTBEDOMAIN' | translate}}</span> <span class="left-desc">{{'ORG.POLICY.DATA.USERLOGINMUSTBEDOMAIN' | translate}}</span>
<span class="fill-space"></span> <span class="fill-space"></span>
<mat-slide-toggle name="hasNumber" ngDefaultControl [(ngModel)]="iamData.userLoginMustBeDomain"> <mat-slide-toggle color="primary" name="hasNumber" ngDefaultControl
[(ngModel)]="iamData.userLoginMustBeDomain">
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
</div> </div>
<div class="btn-container"> <div class="btn-container">
<button (click)="savePolicy()" color="accent" type="submit" <button (click)="savePolicy()" color="primary" type="submit"
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button> mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
</div> </div>
</div> </div>

View File

@ -160,7 +160,7 @@ export class PasswordPolicyComponent implements OnInit, OnDestroy {
} }
public incrementLength(): void { public incrementLength(): void {
if (this.complexityData?.minLength) { if (this.complexityData?.minLength !== undefined) {
this.complexityData.minLength++; this.complexityData.minLength++;
} }
} }
@ -172,7 +172,7 @@ export class PasswordPolicyComponent implements OnInit, OnDestroy {
} }
public incrementMaxAttempts(): void { public incrementMaxAttempts(): void {
if (this.lockoutData?.maxAttempts) { if (this.lockoutData?.maxAttempts !== undefined) {
this.lockoutData.maxAttempts++; this.lockoutData.maxAttempts++;
} }
} }
@ -184,7 +184,7 @@ export class PasswordPolicyComponent implements OnInit, OnDestroy {
} }
public incrementExpireWarnDays(): void { public incrementExpireWarnDays(): void {
if (this.ageData?.expireWarnDays) { if (this.ageData?.expireWarnDays !== undefined) {
this.ageData.expireWarnDays++; this.ageData.expireWarnDays++;
} }
} }
@ -196,7 +196,7 @@ export class PasswordPolicyComponent implements OnInit, OnDestroy {
} }
public incrementMaxAgeDays(): void { public incrementMaxAgeDays(): void {
if (this.ageData?.maxAgeDays) { if (this.ageData?.maxAgeDays !== undefined) {
this.ageData.maxAgeDays++; this.ageData.maxAgeDays++;
} }
} }
@ -218,7 +218,7 @@ export class PasswordPolicyComponent implements OnInit, OnDestroy {
).then(() => { ).then(() => {
this.router.navigate(['org']); this.router.navigate(['org']);
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
break; break;
@ -230,7 +230,7 @@ export class PasswordPolicyComponent implements OnInit, OnDestroy {
).then(() => { ).then(() => {
this.router.navigate(['org']); this.router.navigate(['org']);
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
break; break;
@ -245,7 +245,7 @@ export class PasswordPolicyComponent implements OnInit, OnDestroy {
).then(() => { ).then(() => {
this.router.navigate(['org']); this.router.navigate(['org']);
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
break; break;
@ -259,7 +259,7 @@ export class PasswordPolicyComponent implements OnInit, OnDestroy {
).then(() => { ).then(() => {
this.router.navigate(['org']); this.router.navigate(['org']);
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }
break; break;
@ -274,7 +274,7 @@ export class PasswordPolicyComponent implements OnInit, OnDestroy {
).then(() => { ).then(() => {
this.router.navigate(['org']); this.router.navigate(['org']);
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
break; break;
@ -286,7 +286,7 @@ export class PasswordPolicyComponent implements OnInit, OnDestroy {
).then(() => { ).then(() => {
this.router.navigate(['org']); this.router.navigate(['org']);
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
break; break;
@ -301,7 +301,7 @@ export class PasswordPolicyComponent implements OnInit, OnDestroy {
).then(() => { ).then(() => {
this.router.navigate(['org']); this.router.navigate(['org']);
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
break; break;
@ -315,7 +315,7 @@ export class PasswordPolicyComponent implements OnInit, OnDestroy {
).then(() => { ).then(() => {
this.router.navigate(['org']); this.router.navigate(['org']);
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }
break; break;

View File

@ -54,21 +54,21 @@ export class PolicyGridComponent implements OnInit {
this.orgService.DeletePasswordLockoutPolicy(this.lockoutPolicy.id).then(() => { this.orgService.DeletePasswordLockoutPolicy(this.lockoutPolicy.id).then(() => {
this.toast.showInfo('Successfully deleted'); this.toast.showInfo('Successfully deleted');
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
break; break;
case PolicyComponentType.AGE: case PolicyComponentType.AGE:
this.orgService.DeletePasswordAgePolicy(this.agePolicy.id).then(() => { this.orgService.DeletePasswordAgePolicy(this.agePolicy.id).then(() => {
this.toast.showInfo('Successfully deleted'); this.toast.showInfo('Successfully deleted');
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
break; break;
case PolicyComponentType.COMPLEXITY: case PolicyComponentType.COMPLEXITY:
this.orgService.DeletePasswordLockoutPolicy(this.lockoutPolicy.id).then(() => { this.orgService.DeletePasswordLockoutPolicy(this.lockoutPolicy.id).then(() => {
this.toast.showInfo('Successfully deleted'); this.toast.showInfo('Successfully deleted');
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
break; break;
} }

View File

@ -36,7 +36,6 @@ export class SearchOrgAutocompleteComponent {
} }
}).catch(error => { }).catch(error => {
this.isLoading = false; this.isLoading = false;
// this.toast.showInfo(error.message);
}); });
}); });
} }

View File

@ -82,7 +82,6 @@ export class OwnedProjectDetailComponent implements OnInit, OnDestroy {
private async getData({ id }: Params): Promise<void> { private async getData({ id }: Params): Promise<void> {
this.projectId = id; this.projectId = id;
console.log(this.projectId);
this.orgService.GetIam().then(iam => { this.orgService.GetIam().then(iam => {
this.isZitadel = iam.toObject().iamProjectId === this.projectId; this.isZitadel = iam.toObject().iamProjectId === this.projectId;
@ -93,7 +92,7 @@ export class OwnedProjectDetailComponent implements OnInit, OnDestroy {
this.project = proj.toObject(); this.project = proj.toObject();
}).catch(error => { }).catch(error => {
console.error(error); console.error(error);
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }
} }
@ -103,13 +102,13 @@ export class OwnedProjectDetailComponent implements OnInit, OnDestroy {
this.projectService.ReactivateProject(this.projectId).then(() => { this.projectService.ReactivateProject(this.projectId).then(() => {
this.toast.showInfo('Reactivated Project'); this.toast.showInfo('Reactivated Project');
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} else if (newState === ProjectState.PROJECTSTATE_INACTIVE) { } else if (newState === ProjectState.PROJECTSTATE_INACTIVE) {
this.projectService.DeactivateProject(this.projectId).then(() => { this.projectService.DeactivateProject(this.projectId).then(() => {
this.toast.showInfo('Deactivated Project'); this.toast.showInfo('Deactivated Project');
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }
} }

View File

@ -1,5 +1,5 @@
<div class="view-toggle"> <div class="view-toggle">
<button [disabled]="selection.selected.length > 0" (click)="changedView.emit(true)" mat-icon-button> <button (click)="closeGridView()" mat-icon-button>
<i class="show list view las la-th-list"></i> <i class="show list view las la-th-list"></i>
</button> </button>
</div> </div>

View File

@ -115,4 +115,8 @@ export class OwnedProjectGridComponent implements OnChanges {
this.router.navigate(['/projects', id]); this.router.navigate(['/projects', id]);
} }
} }
public closeGridView(): void {
this.changedView.emit(true);
}
} }

View File

@ -95,7 +95,7 @@ export class OwnedProjectListComponent implements OnInit, OnDestroy {
this.loadingSubject.next(false); this.loadingSubject.next(false);
}).catch(error => { }).catch(error => {
console.error(error); console.error(error);
this.toast.showError(error.message); this.toast.showError(error);
this.loadingSubject.next(false); this.loadingSubject.next(false);
}); });

View File

@ -35,7 +35,6 @@ export class ProjectGrantDetailDataSource extends DataSource<ProjectMemberView.A
finalize(() => this.loadingSubject.next(false)), finalize(() => this.loadingSubject.next(false)),
).subscribe(members => { ).subscribe(members => {
this.membersSubject.next(members); this.membersSubject.next(members);
console.log(members);
}); });
} }

View File

@ -25,8 +25,6 @@ export class ProjectGrantMembersDataSource extends DataSource<ProjectMember.AsOb
this.loadingSubject.next(true); this.loadingSubject.next(true);
console.log(projectId, grantId);
from(this.projectService.SearchProjectGrantMembers(projectId, from(this.projectService.SearchProjectGrantMembers(projectId,
grantId, pageSize, offset)).pipe( grantId, pageSize, offset)).pipe(
map(resp => { map(resp => {

View File

@ -63,15 +63,14 @@ export class ProjectGrantMembersComponent implements AfterViewInit, OnInit {
if (this.type === ProjectType.PROJECTTYPE_GRANTED) { if (this.type === ProjectType.PROJECTTYPE_GRANTED) {
this.projectService.GetProjectGrantMemberRoles().then(resp => { this.projectService.GetProjectGrantMemberRoles().then(resp => {
this.memberRoleOptions = resp.toObject().rolesList; this.memberRoleOptions = resp.toObject().rolesList;
console.log(this.memberRoleOptions);
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} else if (this.type === ProjectType.PROJECTTYPE_OWNED) { } else if (this.type === ProjectType.PROJECTTYPE_OWNED) {
this.projectService.GetProjectMemberRoles().then(resp => { this.projectService.GetProjectMemberRoles().then(resp => {
this.memberRoleOptions = resp.toObject().rolesList; this.memberRoleOptions = resp.toObject().rolesList;
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }
} }
@ -90,7 +89,7 @@ export class ProjectGrantMembersComponent implements AfterViewInit, OnInit {
return this.projectService.RemoveProjectGrantMember(this.projectId, this.grantId, member.userId).then(() => { return this.projectService.RemoveProjectGrantMember(this.projectId, this.grantId, member.userId).then(() => {
this.toast.showInfo('Removed successfully'); this.toast.showInfo('Removed successfully');
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
})); }));
} }
@ -99,7 +98,7 @@ export class ProjectGrantMembersComponent implements AfterViewInit, OnInit {
this.projectService.RemoveProjectGrantMember(this.projectId, this.grantId, member.userId).then(() => { this.projectService.RemoveProjectGrantMember(this.projectId, this.grantId, member.userId).then(() => {
this.toast.showInfo('Member removed successfully'); this.toast.showInfo('Member removed successfully');
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }
@ -117,7 +116,6 @@ export class ProjectGrantMembersComponent implements AfterViewInit, OnInit {
public async openAddMember(): Promise<any> { public async openAddMember(): Promise<any> {
const keysList = (await this.projectService.GetProjectGrantMemberRoles()).toObject(); const keysList = (await this.projectService.GetProjectGrantMemberRoles()).toObject();
console.log(keysList);
const dialogRef = this.dialog.open(ProjectGrantMembersCreateDialogComponent, { const dialogRef = this.dialog.open(ProjectGrantMembersCreateDialogComponent, {
data: { data: {
@ -127,7 +125,6 @@ export class ProjectGrantMembersComponent implements AfterViewInit, OnInit {
}); });
dialogRef.afterClosed().subscribe((dataToAdd: ProjectGrantMembersCreateDialogExportType) => { dialogRef.afterClosed().subscribe((dataToAdd: ProjectGrantMembersCreateDialogExportType) => {
console.log(dataToAdd);
if (dataToAdd) { if (dataToAdd) {
dataToAdd.userIds.forEach((userid: string) => { dataToAdd.userIds.forEach((userid: string) => {
this.projectService.AddProjectGrantMember( this.projectService.AddProjectGrantMember(
@ -138,7 +135,7 @@ export class ProjectGrantMembersComponent implements AfterViewInit, OnInit {
).then(() => { ).then(() => {
this.toast.showInfo('Project Grant Member successfully added!'); this.toast.showInfo('Project Grant Member successfully added!');
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
}); });
@ -147,13 +144,11 @@ export class ProjectGrantMembersComponent implements AfterViewInit, OnInit {
} }
updateRoles(member: ProjectMember.AsObject, selectionChange: MatSelectChange): void { updateRoles(member: ProjectMember.AsObject, selectionChange: MatSelectChange): void {
console.log(this.projectId, this.grantId, member.userId, selectionChange.value);
this.projectService.ChangeProjectGrantMember(this.projectId, this.grantId, member.userId, selectionChange.value) this.projectService.ChangeProjectGrantMember(this.projectId, this.grantId, member.userId, selectionChange.value)
.then((newmember: ProjectMember) => { .then((newmember: ProjectMember) => {
console.log(newmember.toObject());
this.toast.showInfo('Member updated!'); this.toast.showInfo('Member updated!');
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }
} }

View File

@ -43,20 +43,16 @@ export class ProjectGrantCreateComponent implements OnInit, OnDestroy {
} }
public searchOrg(domain: string): void { public searchOrg(domain: string): void {
console.log(domain);
this.orgService.getOrgByDomainGlobal(domain).then((ret) => { this.orgService.getOrgByDomainGlobal(domain).then((ret) => {
const tmp = ret.toObject(); const tmp = ret.toObject();
console.log(ret.toObject());
this.authService.GetActiveOrg().then((org) => { this.authService.GetActiveOrg().then((org) => {
console.log(org);
if (tmp !== org) { if (tmp !== org) {
this.org = tmp; this.org = tmp;
} }
}); });
this.org = ret.toObject(); this.org = ret.toObject();
console.log(this.org);
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }
@ -71,7 +67,7 @@ export class ProjectGrantCreateComponent implements OnInit, OnDestroy {
this.close(); this.close();
}) })
.catch(error => { .catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }

View File

@ -1,9 +1,10 @@
<div class="wrap"> <div class="wrap">
<div class="block"> <div class="block">
<div class="header"> <div class="header">
<img *ngIf="dark; else lighttheme" src="./assets/images/zitadel-logo-oneline-darkdesign.svg" /> <img alt="zitadel logo" *ngIf="dark; else lighttheme"
src="../../../assets/images/zitadel-logo-oneline-darkdesign.svg" />
<ng-template #lighttheme> <ng-template #lighttheme>
<img src="./assets/images/zitadel-logo-oneline-lightdesign.svg" /> <img alt="zitadel logo" src="../../../assets/images/zitadel-logo-oneline-lightdesign.svg" />
</ng-template> </ng-template>
<p>{{'USER.SIGNEDOUT' | translate}}</p> <p>{{'USER.SIGNEDOUT' | translate}}</p>

View File

@ -86,7 +86,7 @@ export class UserCreateComponent implements OnDestroy {
this.userService this.userService
.CreateUser(this.user) .CreateUser(this.user)
.then((data: User) => { .then((data: User) => {
this.toast.showInfo('User created'); this.toast.showInfo('USER.TOAST.CREATED', true);
this.router.navigate(['users', data.getId()]); this.router.navigate(['users', data.getId()]);
}) })
.catch(data => { .catch(data => {

View File

@ -21,8 +21,7 @@
</app-card> </app-card>
<div class="col" *ngIf="user"> <div class="col" *ngIf="user">
<app-card title="{{ 'USER.PROFILE.TITLE' | translate }}" <app-card title="{{ 'USER.PROFILE.TITLE' | translate }}">
description="{{'USER.PROFILE.DESCRIPTION' | translate}}">
<app-detail-form [genders]="genders" [languages]="languages" [profile]="user" <app-detail-form [genders]="genders" [languages]="languages" [profile]="user"
(changedLanguage)="changedLanguage($event)" (submitData)="saveProfile($event)"> (changedLanguage)="changedLanguage($event)" (submitData)="saveProfile($event)">
</app-detail-form> </app-detail-form>

View File

@ -66,7 +66,7 @@ export class AuthUserDetailComponent implements OnDestroy {
this.user.gender, this.user.gender,
) )
.then((data: UserProfile) => { .then((data: UserProfile) => {
this.toast.showInfo('Saved Profile'); this.toast.showInfo('USER.TOAST.SAVED', true);
this.user = Object.assign(this.user, data.toObject()); this.user = Object.assign(this.user, data.toObject());
}) })
.catch(data => { .catch(data => {
@ -79,7 +79,7 @@ export class AuthUserDetailComponent implements OnDestroy {
this.userService this.userService
.SaveMyUserEmail(this.user.email).then((data: UserEmail) => { .SaveMyUserEmail(this.user.email).then((data: UserEmail) => {
this.toast.showInfo('Saved Email'); this.toast.showInfo('USER.TOAST.EMAILSAVED', true);
this.user.email = data.toObject().email; this.user.email = data.toObject().email;
this.emailEditState = false; this.emailEditState = false;
}).catch(data => { }).catch(data => {
@ -99,9 +99,9 @@ export class AuthUserDetailComponent implements OnDestroy {
dialogRef.afterClosed().subscribe(code => { dialogRef.afterClosed().subscribe(code => {
if (code) { if (code) {
this.userService.VerifyMyUserPhone(code).then(() => { this.userService.VerifyMyUserPhone(code).then(() => {
this.toast.showInfo('Verified Phone'); this.toast.showInfo('USER.TOAST.PHONESAVED', true);
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }
}); });
@ -113,7 +113,7 @@ export class AuthUserDetailComponent implements OnDestroy {
public resendVerification(): void { public resendVerification(): void {
this.userService.ResendEmailVerification().then(() => { this.userService.ResendEmailVerification().then(() => {
this.toast.showInfo('Saved Email'); this.toast.showInfo('USER.TOAST.EMAILSAVED', true);
}).catch(data => { }).catch(data => {
this.toast.showError(data.message); this.toast.showError(data.message);
}); });
@ -121,7 +121,7 @@ export class AuthUserDetailComponent implements OnDestroy {
public resendPhoneVerification(): void { public resendPhoneVerification(): void {
this.userService.ResendPhoneVerification().then(() => { this.userService.ResendPhoneVerification().then(() => {
this.toast.showInfo('Phoneverification was successfully sent!'); this.toast.showInfo('USER.TOAST.PHONEVERIFICATIONSENT', true);
}).catch(data => { }).catch(data => {
this.toast.showError(data.message); this.toast.showError(data.message);
}); });
@ -129,7 +129,7 @@ export class AuthUserDetailComponent implements OnDestroy {
public deletePhone(): void { public deletePhone(): void {
this.userService.RemoveMyUserPhone().then(() => { this.userService.RemoveMyUserPhone().then(() => {
this.toast.showInfo('Phone removed with success!'); this.toast.showInfo('USER.TOAST.PHONEREMOVED', true);
this.user.phone = ''; this.user.phone = '';
this.phoneEditState = false; this.phoneEditState = false;
}).catch(data => { }).catch(data => {
@ -141,7 +141,7 @@ export class AuthUserDetailComponent implements OnDestroy {
this.phoneEditState = false; this.phoneEditState = false;
this.userService this.userService
.SaveMyUserPhone(this.user.phone).then((data: UserPhone) => { .SaveMyUserPhone(this.user.phone).then((data: UserPhone) => {
this.toast.showInfo('Saved Phone'); this.toast.showInfo('USER.TOAST.PHONESAVED', true);
this.user.phone = data.toObject().phone; this.user.phone = data.toObject().phone;
this.phoneEditState = false; this.phoneEditState = false;
}).catch(data => { }).catch(data => {
@ -154,7 +154,7 @@ export class AuthUserDetailComponent implements OnDestroy {
this.userService.GetMyUser().then(user => { this.userService.GetMyUser().then(user => {
this.user = user.toObject(); this.user = user.toObject();
}).catch(err => { }).catch(err => {
console.error(err); this.toast.showError(err);
}); });
} }

View File

@ -48,7 +48,7 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
} }
}); });
}, error => { }, error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }
@ -64,7 +64,7 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
public deleteMFA(type: MfaType): void { public deleteMFA(type: MfaType): void {
if (type === MfaType.MFATYPE_OTP) { if (type === MfaType.MFATYPE_OTP) {
this.userService.RemoveMfaOTP().then(() => { this.userService.RemoveMfaOTP().then(() => {
this.toast.showInfo('OTP Deleted'); this.toast.showInfo('USER.TOAST.OTPREMOVED', true);
const index = this.mfaSubject.value.findIndex(mfa => mfa.type === type); const index = this.mfaSubject.value.findIndex(mfa => mfa.type === type);
if (index > -1) { if (index > -1) {
@ -74,7 +74,7 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
} }
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }
} }

View File

@ -1,10 +1,9 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms'; import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { PasswordComplexityPolicy } from 'src/app/proto/generated/management_pb'; import { PasswordComplexityPolicy } from 'src/app/proto/generated/auth_pb';
import { AuthUserService } from 'src/app/services/auth-user.service'; import { AuthUserService } from 'src/app/services/auth-user.service';
import { MgmtUserService } from 'src/app/services/mgmt-user.service'; import { MgmtUserService } from 'src/app/services/mgmt-user.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 { lowerCaseValidator, numberValidator, symbolValidator, upperCaseValidator } from '../validators'; import { lowerCaseValidator, numberValidator, symbolValidator, upperCaseValidator } from '../validators';
@ -36,7 +35,6 @@ export class PasswordComponent implements OnInit {
public passwordForm!: FormGroup; public passwordForm!: FormGroup;
constructor( constructor(
private orgService: OrgService,
activatedRoute: ActivatedRoute, activatedRoute: ActivatedRoute,
private fb: FormBuilder, private fb: FormBuilder,
private userService: AuthUserService, private userService: AuthUserService,
@ -51,7 +49,7 @@ export class PasswordComponent implements OnInit {
} }
const validators: Validators[] = [Validators.required]; const validators: Validators[] = [Validators.required];
this.orgService.GetPasswordComplexityPolicy().then(complexity => { this.userService.GetMyPasswordComplexityPolicy().then(complexity => {
this.policy = complexity.toObject(); this.policy = complexity.toObject();
if (this.policy.minLength) { if (this.policy.minLength) {
validators.push(Validators.minLength(this.policy.minLength)); validators.push(Validators.minLength(this.policy.minLength));
@ -97,7 +95,7 @@ export class PasswordComponent implements OnInit {
public setInitialPassword(userId: string): void { public setInitialPassword(userId: string): void {
if (this.passwordForm.valid && this.password && this.password.value) { if (this.passwordForm.valid && this.password && this.password.value) {
this.mgmtUserService.SetInitialPassword(userId, this.password.value).then((data: any) => { this.mgmtUserService.SetInitialPassword(userId, this.password.value).then((data: any) => {
this.toast.showInfo('Set initial Password'); this.toast.showInfo('USER.TOAST.INITIALPASSWORDSET', true);
window.history.back(); window.history.back();
}).catch(data => { }).catch(data => {
this.toast.showError(data.message); this.toast.showError(data.message);
@ -111,7 +109,7 @@ export class PasswordComponent implements OnInit {
this.newPassword && this.newPassword.value && this.newPassword.valid) { this.newPassword && this.newPassword.value && this.newPassword.valid) {
this.userService this.userService
.ChangeMyPassword(this.currentPassword.value, this.newPassword.value).then((data: any) => { .ChangeMyPassword(this.currentPassword.value, this.newPassword.value).then((data: any) => {
this.toast.showInfo('Password Set'); this.toast.showInfo('USER.TOAST.PASSWORDCHANGED', true);
window.history.back(); window.history.back();
}).catch(data => { }).catch(data => {
this.toast.showError(data.message); this.toast.showError(data.message);

View File

@ -16,8 +16,6 @@
class="state-button" class="state-button"
(click)="changeState(UserState.USERSTATE_ACTIVE)">{{'USER.PAGES.REACTIVATE' | translate}}</button> (click)="changeState(UserState.USERSTATE_ACTIVE)">{{'USER.PAGES.REACTIVATE' | translate}}</button>
</ng-template> </ng-template>
<p class="desc">{{ 'USER.PROFILE.DESCRIPTION' | translate }}</p>
</div> </div>
<mat-progress-bar *ngIf="loading" color="accent" mode="indeterminate"></mat-progress-bar> <mat-progress-bar *ngIf="loading" color="accent" mode="indeterminate"></mat-progress-bar>

View File

@ -3,23 +3,18 @@
align-items: center; align-items: center;
border-bottom: 1px solid #ffffff20; border-bottom: 1px solid #ffffff20;
flex-wrap: wrap; flex-wrap: wrap;
padding-bottom: .5rem;
a { a {
display: block; display: block;
margin-right: 2rem;
} }
h1 { h1 {
font-size: 2rem; font-size: 2rem;
margin: 0 1rem; margin: 0;
margin-left: 2rem;
font-weight: normal; font-weight: normal;
} margin-right: 1rem;
.desc {
width: 100%;
display: block;
font-size: .9rem;
color: #8795a1;
} }
.fill-space { .fill-space {

View File

@ -65,15 +65,15 @@ export class UserDetailComponent implements OnInit, OnDestroy {
public changeState(newState: UserState): void { public changeState(newState: UserState): void {
if (newState === UserState.USERSTATE_ACTIVE) { if (newState === UserState.USERSTATE_ACTIVE) {
this.mgmtUserService.ReactivateUser(this.user.id).then(() => { this.mgmtUserService.ReactivateUser(this.user.id).then(() => {
this.toast.showInfo('reactivated User'); this.toast.showInfo('USER.TOAST.REACTIVATED', true);
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} else if (newState === UserState.USERSTATE_INACTIVE) { } else if (newState === UserState.USERSTATE_INACTIVE) {
this.mgmtUserService.DeactivateUser(this.user.id).then(() => { this.mgmtUserService.DeactivateUser(this.user.id).then(() => {
this.toast.showInfo('deactivated User'); this.toast.showInfo('USER.TOAST.DEACTIVATED', true);
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
} }
} }
@ -94,7 +94,7 @@ export class UserDetailComponent implements OnInit, OnDestroy {
this.user.preferredLanguage, this.user.preferredLanguage,
this.user.gender) this.user.gender)
.then((data: UserProfile) => { .then((data: UserProfile) => {
this.toast.showInfo('Saved Profile'); this.toast.showInfo('USER.TOAST.SAVED', true);
this.user = Object.assign(this.user, data.toObject()); this.user = Object.assign(this.user, data.toObject());
}) })
.catch(data => { .catch(data => {
@ -104,7 +104,7 @@ export class UserDetailComponent implements OnInit, OnDestroy {
public resendVerification(): void { public resendVerification(): void {
this.mgmtUserService.ResendEmailVerification(this.user.id).then(() => { this.mgmtUserService.ResendEmailVerification(this.user.id).then(() => {
this.toast.showInfo('Email was successfully sent!'); this.toast.showInfo('USER.TOAST.EMAILVERIFICATIONSENT', true);
}).catch(data => { }).catch(data => {
this.toast.showError(data.message); this.toast.showError(data.message);
}); });
@ -112,7 +112,7 @@ export class UserDetailComponent implements OnInit, OnDestroy {
public resendPhoneVerification(): void { public resendPhoneVerification(): void {
this.mgmtUserService.ResendPhoneVerification(this.user.id).then(() => { this.mgmtUserService.ResendPhoneVerification(this.user.id).then(() => {
this.toast.showInfo('Phoneverification was successfully sent!'); this.toast.showInfo('USER.TOAST.PHONEVERIFICATIONSENT', true);
}).catch(data => { }).catch(data => {
this.toast.showError(data.message); this.toast.showError(data.message);
}); });
@ -120,7 +120,7 @@ export class UserDetailComponent implements OnInit, OnDestroy {
public deletePhone(): void { public deletePhone(): void {
this.mgmtUserService.RemoveUserPhone(this.user.id).then(() => { this.mgmtUserService.RemoveUserPhone(this.user.id).then(() => {
this.toast.showInfo('Phone removed with success!'); this.toast.showInfo('USER.TOAST.PHONEREMOVED', true);
this.user.phone = ''; this.user.phone = '';
this.phoneEditState = false; this.phoneEditState = false;
}).catch(data => { }).catch(data => {
@ -132,7 +132,7 @@ export class UserDetailComponent implements OnInit, OnDestroy {
this.emailEditState = false; this.emailEditState = false;
this.mgmtUserService this.mgmtUserService
.SaveUserEmail(this.user.id, this.user.email).then((data: UserEmail) => { .SaveUserEmail(this.user.id, this.user.email).then((data: UserEmail) => {
this.toast.showInfo('Saved Email'); this.toast.showInfo('USER.TOAST.EMAILSENT', true);
this.user.email = data.toObject().email; this.user.email = data.toObject().email;
}).catch(data => { }).catch(data => {
this.toast.showError(data.message); this.toast.showError(data.message);
@ -143,7 +143,7 @@ export class UserDetailComponent implements OnInit, OnDestroy {
this.phoneEditState = false; this.phoneEditState = false;
this.mgmtUserService this.mgmtUserService
.SaveUserPhone(this.user.id, this.user.phone).then((data: UserPhone) => { .SaveUserPhone(this.user.id, this.user.phone).then((data: UserPhone) => {
this.toast.showInfo('Saved Phone'); this.toast.showInfo('USER.TOAST.PHONESAVED', true);
this.user.phone = data.toObject().phone; this.user.phone = data.toObject().phone;
this.phoneEditState = false; this.phoneEditState = false;
}).catch(data => { }).catch(data => {
@ -167,7 +167,7 @@ export class UserDetailComponent implements OnInit, OnDestroy {
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(() => {
this.toast.showInfo('Set initial Password'); this.toast.showInfo('USER.TOAST.PASSWORDNOTIFICATIONSENT', true);
}).catch(data => { }).catch(data => {
this.toast.showError(data.message); this.toast.showError(data.message);
}); });

View File

@ -39,7 +39,6 @@ export class UserGrantCreateComponent implements OnDestroy {
private route: ActivatedRoute, private route: ActivatedRoute,
) { ) {
this.subscription = this.route.params.subscribe((params: Params) => { this.subscription = this.route.params.subscribe((params: Params) => {
console.log(params);
const { context, projectid, grantid, userid } = params; const { context, projectid, grantid, userid } = params;
this.context = context; this.context = context;
@ -76,7 +75,7 @@ export class UserGrantCreateComponent implements OnDestroy {
this.toast.showInfo('User Grant added'); this.toast.showInfo('User Grant added');
this.close(); this.close();
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
break; break;
case UserGrantContext.OWNED_PROJECT: case UserGrantContext.OWNED_PROJECT:
@ -88,7 +87,7 @@ export class UserGrantCreateComponent implements OnDestroy {
this.toast.showInfo('Project User Grant added'); this.toast.showInfo('Project User Grant added');
this.close(); this.close();
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
break; break;
case UserGrantContext.GRANTED_PROJECT: case UserGrantContext.GRANTED_PROJECT:
@ -102,7 +101,7 @@ export class UserGrantCreateComponent implements OnDestroy {
this.toast.showInfo('Project Grant User Grant added'); this.toast.showInfo('Project Grant User Grant added');
this.close(); this.close();
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
}); });
break; break;
@ -111,12 +110,10 @@ export class UserGrantCreateComponent implements OnDestroy {
} }
public selectProject(project: ProjectView.AsObject | ProjectGrantView.AsObject | any): void { public selectProject(project: ProjectView.AsObject | ProjectGrantView.AsObject | any): void {
console.log(project);
this.projectId = project.projectId; this.projectId = project.projectId;
} }
public selectUser(user: User.AsObject): void { public selectUser(user: User.AsObject): void {
console.log(user);
this.userId = user.id; this.userId = user.id;
} }

View File

@ -79,7 +79,7 @@ export class UserListComponent implements OnDestroy {
this.dataSource.data = resp.toObject().resultList; this.dataSource.data = resp.toObject().resultList;
this.loadingSubject.next(false); this.loadingSubject.next(false);
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error);
this.loadingSubject.next(false); this.loadingSubject.next(false);
}); });
} }

View File

@ -1,15 +0,0 @@
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class AuthInterceptorService implements HttpInterceptor {
constructor() { }
public intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req);
}
}

View File

@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
import { Empty } from 'google-protobuf/google/protobuf/empty_pb'; import { Empty } from 'google-protobuf/google/protobuf/empty_pb';
import { Metadata } from 'grpc-web'; import { Metadata } from 'grpc-web';
import { from, Observable, of } from 'rxjs'; import { from, Observable, of } from 'rxjs';
import { switchMap } from 'rxjs/operators'; import { catchError, switchMap } from 'rxjs/operators';
import { AuthServicePromiseClient } from '../proto/generated/auth_grpc_web_pb'; import { AuthServicePromiseClient } from '../proto/generated/auth_grpc_web_pb';
import { import {
@ -15,6 +15,7 @@ import {
MyProjectOrgSearchRequest, MyProjectOrgSearchRequest,
MyProjectOrgSearchResponse, MyProjectOrgSearchResponse,
PasswordChange, PasswordChange,
PasswordComplexityPolicy,
UpdateUserAddressRequest, UpdateUserAddressRequest,
UpdateUserEmailRequest, UpdateUserEmailRequest,
UpdateUserPhoneRequest, UpdateUserPhoneRequest,
@ -23,6 +24,7 @@ import {
UserEmail, UserEmail,
UserPhone, UserPhone,
UserProfile, UserProfile,
UserProfileView,
UserSessionViews, UserSessionViews,
UserView, UserView,
VerifyMfaOtp, VerifyMfaOtp,
@ -57,7 +59,7 @@ export class AuthUserService {
return responseMapper(response); return responseMapper(response);
} }
public async GetMyUserProfile(): Promise<UserProfile> { public async GetMyUserProfile(): Promise<UserProfileView> {
return await this.request( return await this.request(
c => c.getMyUserProfile, c => c.getMyUserProfile,
new Empty(), new Empty(),
@ -65,6 +67,15 @@ export class AuthUserService {
); );
} }
public async GetMyPasswordComplexityPolicy(): Promise<PasswordComplexityPolicy> {
return await this.request(
c => c.getMyPasswordComplexityPolicy,
new Empty(),
f => f,
);
}
public async GetMyUser(): Promise<UserView> { public async GetMyUser(): Promise<UserView> {
return await this.request( return await this.request(
c => c.getMyUser, c => c.getMyUser,
@ -319,6 +330,9 @@ export class AuthUserService {
this._roleCache = userRoles; this._roleCache = userRoles;
return of(this.hasRoles(userRoles, roles, each)); return of(this.hasRoles(userRoles, roles, each));
}), }),
catchError((err) => {
return of(false);
}),
); );
} else { } else {
return of(false); return of(false);

View File

@ -4,7 +4,7 @@ import { AuthConfig, OAuthService } from 'angular-oauth2-oidc';
import { BehaviorSubject, from, merge, Observable, of, Subject } from 'rxjs'; import { BehaviorSubject, from, merge, Observable, of, Subject } from 'rxjs';
import { catchError, filter, map, mergeMap, take, timeout } from 'rxjs/operators'; import { catchError, filter, map, mergeMap, take, timeout } from 'rxjs/operators';
import { Org, UserProfile } from '../proto/generated/auth_pb'; import { Org, UserProfileView } from '../proto/generated/auth_pb';
import { AuthUserService } from './auth-user.service'; import { AuthUserService } from './auth-user.service';
import { GrpcService } from './grpc.service'; import { GrpcService } from './grpc.service';
import { StatehandlerService } from './statehandler.service'; import { StatehandlerService } from './statehandler.service';
@ -16,7 +16,7 @@ import { StorageKey, StorageService } from './storage.service';
export class AuthService { export class AuthService {
private cachedOrgs: Org.AsObject[] = []; private cachedOrgs: Org.AsObject[] = [];
private _activeOrgChanged: Subject<Org.AsObject> = new Subject(); private _activeOrgChanged: Subject<Org.AsObject> = new Subject();
public user!: Observable<UserProfile.AsObject>; public user!: Observable<UserProfileView.AsObject>;
private _authenticated: boolean = false; private _authenticated: boolean = false;
private readonly _authenticationChanged: BehaviorSubject< private readonly _authenticationChanged: BehaviorSubject<
boolean boolean
@ -61,7 +61,11 @@ export class AuthService {
return from(this.oauthService.loadUserProfile()); return from(this.oauthService.loadUserProfile());
} }
public async authenticate(config?: Partial<AuthConfig>, setState: boolean = true): Promise<boolean> { public async authenticate(
config?: Partial<AuthConfig>,
setState: boolean = true,
force: boolean = false,
): Promise<boolean> {
this.config.issuer = config?.issuer || this.grpcService.issuer; this.config.issuer = config?.issuer || this.grpcService.issuer;
this.config.clientId = config?.clientId || this.grpcService.clientid; this.config.clientId = config?.clientId || this.grpcService.clientid;
this.config.redirectUri = config?.redirectUri || this.grpcService.redirectUri; this.config.redirectUri = config?.redirectUri || this.grpcService.redirectUri;
@ -73,7 +77,8 @@ export class AuthService {
await this.oauthService.loadDiscoveryDocumentAndTryLogin(); await this.oauthService.loadDiscoveryDocumentAndTryLogin();
this._authenticated = this.oauthService.hasValidAccessToken(); this._authenticated = this.oauthService.hasValidAccessToken();
if (!this.oauthService.hasValidIdToken() || !this.authenticated || config) {
if (!this.oauthService.hasValidIdToken() || !this.authenticated || config || force) {
const newState = setState ? await this.statehandler.createState().toPromise() : undefined; const newState = setState ? await this.statehandler.createState().toPromise() : undefined;
this.oauthService.initCodeFlow(newState); this.oauthService.initCodeFlow(newState);
} }
@ -89,7 +94,6 @@ export class AuthService {
this.router.navigate(['/']); this.router.navigate(['/']);
} }
public get activeOrgChanged(): Observable<Org.AsObject> { public get activeOrgChanged(): Observable<Org.AsObject> {
return this._activeOrgChanged; return this._activeOrgChanged;
} }

View File

@ -1,12 +1,8 @@
import { Inject, Injectable } from '@angular/core'; import { Inject, Injectable } from '@angular/core';
import { Metadata } from 'grpc-web'; import { Metadata } from 'grpc-web';
import {
GrpcDefaultHandler, import { GrpcDefaultHandler, GrpcHandler, GrpcInterceptorHandler, GrpcRequestFn } from './grpc-handler';
GrpcHandler, import { GRPC_INTERCEPTORS, GrpcInterceptor } from './interceptors/grpc-interceptor';
GrpcInterceptorHandler,
GrpcRequestFn,
} from './grpc-handler';
import { GRPC_INTERCEPTORS, GrpcInterceptor } from './grpc-interceptor';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@ -14,7 +10,7 @@ import { GRPC_INTERCEPTORS, GrpcInterceptor } from './grpc-interceptor';
export class GrpcBackendService { export class GrpcBackendService {
constructor( constructor(
@Inject(GRPC_INTERCEPTORS) private readonly interceptors: GrpcInterceptor[], @Inject(GRPC_INTERCEPTORS) private readonly interceptors: GrpcInterceptor[],
) {} ) { }
public async runRequest<TReq, TResp>( public async runRequest<TReq, TResp>(
requestFn: GrpcRequestFn<TReq, TResp>, requestFn: GrpcRequestFn<TReq, TResp>,

View File

@ -1,6 +1,6 @@
import { Metadata } from 'grpc-web'; import { Metadata } from 'grpc-web';
import { GrpcInterceptor } from './grpc-interceptor'; import { GrpcInterceptor } from './interceptors/grpc-interceptor';
export type GrpcRequestFn<TReq, TResp> = ( export type GrpcRequestFn<TReq, TResp> = (
request: TReq, request: TReq,
@ -15,7 +15,7 @@ export class GrpcInterceptorHandler implements GrpcHandler {
constructor( constructor(
private readonly next: GrpcHandler, private readonly next: GrpcHandler,
private interceptor: GrpcInterceptor, private interceptor: GrpcInterceptor,
) {} ) { }
public handle(req: unknown, metadata: Metadata): Promise<unknown> { public handle(req: unknown, metadata: Metadata): Promise<unknown> {
return this.interceptor.intercept(req, metadata, this.next); return this.interceptor.intercept(req, metadata, this.next);
@ -23,7 +23,7 @@ export class GrpcInterceptorHandler implements GrpcHandler {
} }
export class GrpcDefaultHandler<TReq, TResp> implements GrpcHandler { export class GrpcDefaultHandler<TReq, TResp> implements GrpcHandler {
constructor(private readonly requestFn: GrpcRequestFn<TReq, TResp>) {} constructor(private readonly requestFn: GrpcRequestFn<TReq, TResp>) { }
public handle(req: TReq, metadata: Metadata): Promise<TResp> { public handle(req: TReq, metadata: Metadata): Promise<TResp> {
return this.requestFn(req, metadata); return this.requestFn(req, metadata);

View File

@ -1,9 +1,9 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Metadata } from 'grpc-web'; import { Metadata } from 'grpc-web';
import { GrpcHandler } from './grpc-handler'; import { GrpcHandler } from '../grpc-handler';
import { StorageService } from '../storage.service';
import { GrpcInterceptor } from './grpc-interceptor'; import { GrpcInterceptor } from './grpc-interceptor';
import { StorageService } from './storage.service';
const authorizationKey = 'Authorization'; const authorizationKey = 'Authorization';
const bearerPrefix = 'Bearer '; const bearerPrefix = 'Bearer ';

View File

@ -1,6 +1,7 @@
import { InjectionToken } from '@angular/core'; import { InjectionToken } from '@angular/core';
import { Metadata } from 'grpc-web'; import { Metadata } from 'grpc-web';
import { GrpcHandler } from './grpc-handler';
import { GrpcHandler } from '../grpc-handler';
export const GRPC_INTERCEPTORS = new InjectionToken<GrpcInterceptor[]>( export const GRPC_INTERCEPTORS = new InjectionToken<GrpcInterceptor[]>(
'GRPC_INTERCEPTORS', 'GRPC_INTERCEPTORS',

View File

@ -1,10 +1,10 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Metadata } from 'grpc-web'; import { Metadata } from 'grpc-web';
import { Org } from '../proto/generated/auth_pb'; import { Org } from '../../proto/generated/auth_pb';
import { GrpcHandler } from './grpc-handler'; import { GrpcHandler } from '../grpc-handler';
import { StorageService } from '../storage.service';
import { GrpcInterceptor } from './grpc-interceptor'; import { GrpcInterceptor } from './grpc-interceptor';
import { StorageService } from './storage.service';
const orgKey = 'x-zitadel-orgid'; const orgKey = 'x-zitadel-orgid';

View File

@ -1,23 +1,46 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar'; import { MatSnackBar, MatSnackBarConfig } from '@angular/material/snack-bar';
import { TranslateService } from '@ngx-translate/core';
import { Observable } from 'rxjs';
import { take } from 'rxjs/operators';
import { AuthService } from './auth.service';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class ToastService { export class ToastService {
constructor(private snackBar: MatSnackBar) { } constructor(private snackBar: MatSnackBar, private translate: TranslateService, private authService: AuthService) { }
public showInfo(message: string): void { public showInfo(message: string, i18nkey: boolean = false): void {
if (i18nkey) {
this.translate
.get(message)
.subscribe(data => {
this.showMessage(data, 'close');
});
} else {
this.showMessage(message, 'close'); this.showMessage(message, 'close');
} }
public showError(message: string): void {
this.showMessage(decodeURI(message), 'close');
} }
private showMessage(message: string, action: string): void { public showError(grpcError: any): void {
this.snackBar.open(message, action, { const { message, code, metadata } = grpcError;
duration: 5000, // TODO: remove check for code === 7
if (code === 16 || (code === 7 && message === 'invalid token')) {
const actionObserver$ = this.showMessage(decodeURI(message), 'Login');
actionObserver$.pipe(take(1)).subscribe(() => {
this.authService.authenticate(undefined, true, true);
}); });
} else {
const actionObserver$ = this.showMessage(decodeURI(message), 'close', { duration: 5000 });
}
}
private showMessage(message: string, action: string, config?: MatSnackBarConfig): Observable<void> {
const ref = this.snackBar.open(message, action, config);
return ref.onAction();
} }
} }

View File

@ -7,8 +7,6 @@ import { SwUpdate } from '@angular/service-worker';
}) })
export class UpdateService { export class UpdateService {
constructor(private swUpdate: SwUpdate, snackbar: MatSnackBar) { constructor(private swUpdate: SwUpdate, snackbar: MatSnackBar) {
this.swUpdate.checkForUpdate();
this.swUpdate.available.subscribe((evt) => { this.swUpdate.available.subscribe((evt) => {
const snack = snackbar.open('Update Available', 'Reload'); const snack = snackbar.open('Update Available', 'Reload');

View File

@ -191,7 +191,23 @@
"SIGNEDOUT":"Du bist abgemeldet. Klicke auf den Button um dich erneut anzumelden!", "SIGNEDOUT":"Du bist abgemeldet. Klicke auf den Button um dich erneut anzumelden!",
"SIGNEDOUT_BTN":"Anmelden", "SIGNEDOUT_BTN":"Anmelden",
"EDITACCOUNT":"Account bearbeiten", "EDITACCOUNT":"Account bearbeiten",
"ADDACCOUNT":"Account hinzufügen" "ADDACCOUNT":"Account hinzufügen",
"TOAST": {
"CREATED":"User erfolgreich erstellt",
"SAVED":"Profile gespeichert!",
"EMAILSAVED":"Email gespeichert!",
"PHONESAVED":"Telefonnummer gespeichert!",
"PHONEREMOVED":"Telefonnummer gelöscht!",
"PHONEVERIFIED":"Telefonnummer bestätigt!",
"PHONEVERIFICATIONSENT":"Tel. Bestätigungscode gesendet!",
"EMAILVERIFICATIONSENT":"Email Bestätigungscode gesendet!",
"OTPREMOVED":"OTP entfernt!",
"INITIALPASSWORDSET":"Initiales Passwort gesetzt!",
"PASSWORDNOTIFICATIONSENT":"Passwortänderung mittgeteilt!",
"PASSWORDCHANGED":"Passwort geändert!",
"REACTIVATED":"User reaktiviert!",
"DEACTIVATED":"User deaktiviert!"
}
}, },
"IAM": { "IAM": {
"DETAIL": { "DETAIL": {
@ -437,6 +453,13 @@
"CREATIONDATE":"Erstelldatum", "CREATIONDATE":"Erstelldatum",
"CHANGEDATE":"Letzte Änderung", "CHANGEDATE":"Letzte Änderung",
"RESOURCEOWNER":"Besitzer" "RESOURCEOWNER":"Besitzer"
},
"TOAST":{
"MEMBERREMOVED":"Manager entfernt!",
"MEMBERSADDED": "Manager hinzugefügt!",
"MEMBERADDED": "Manager hinzugefügt!",
"ROLEREMOVED":"Rolle entfernt!",
"ROLECHANGED":"Rolle verändert!"
} }
}, },
"APP": { "APP": {
@ -480,6 +503,12 @@
"AUTHMETHOD0":"Basic", "AUTHMETHOD0":"Basic",
"AUTHMETHOD1":"Post", "AUTHMETHOD1":"Post",
"AUTHMETHOD2":"None" "AUTHMETHOD2":"None"
},
"TOAST": {
"REACTIVATED":"App reaktiviert!",
"DEACTIVATED":"App deaktiviert!",
"OIDCUPDATED":"OIDC Konfiguration geändert!",
"OIDCCLIENTSECRETREGENERATED":"OIDC Client Secret generiert!"
} }
}, },
"GENDERS": { "GENDERS": {
@ -555,6 +584,10 @@
"DETAIL": { "DETAIL": {
"TITLE":"Authorisierung Detail", "TITLE":"Authorisierung Detail",
"DESCRIPTION":"" "DESCRIPTION":""
},
"TOAST": {
"UPDATED":"Berechtigung geändert!",
"BULKREMOVED":"Berechtigungen entfernt!"
} }
}, },
"CHANGES": { "CHANGES": {

View File

@ -191,7 +191,23 @@
"SIGNEDOUT":"You are signed out. Click the button below to sign in again!", "SIGNEDOUT":"You are signed out. Click the button below to sign in again!",
"SIGNEDOUT_BTN":"Sign In", "SIGNEDOUT_BTN":"Sign In",
"EDITACCOUNT":"Edit Account", "EDITACCOUNT":"Edit Account",
"ADDACCOUNT":"log in with another account" "ADDACCOUNT":"Log in with another account",
"TOAST": {
"CREATED":"User created successful!",
"SAVED":"Profile saved successful!",
"EMAILSAVED":"Email saved successful!",
"PHONESAVED":"Phone saved successful!",
"PHONEREMOVED":"Phone has been removed!",
"PHONEVERIFIED":"Phone verified successful!",
"PHONEVERIFICATIONSENT":"Phone verification code sent!",
"EMAILVERIFICATIONSENT":"Email verification code sent!",
"OTPREMOVED":"OTP removed!",
"INITIALPASSWORDSET":"Initial password set!",
"PASSWORDNOTIFICATIONSENT":"Password Change Notification sent",
"PASSWORDCHANGED":"Password changed successful!",
"REACTIVATED":"User reactivated",
"DEACTIVATED":"User deactivated"
}
}, },
"IAM": { "IAM": {
"DETAIL": { "DETAIL": {
@ -437,6 +453,13 @@
"CREATIONDATE":"Created At", "CREATIONDATE":"Created At",
"CHANGEDATE":"Last Modified", "CHANGEDATE":"Last Modified",
"RESOURCEOWNER":"Owner" "RESOURCEOWNER":"Owner"
},
"TOAST":{
"MEMBERREMOVED":"Manager removed!",
"MEMBERSADDED": "Managers added!",
"MEMBERADDED": "Manager added!",
"ROLEREMOVED":"Role removed!",
"ROLECHANGED":"Role changed!"
} }
}, },
"APP": { "APP": {
@ -480,6 +503,12 @@
"AUTHMETHOD0":"Basic", "AUTHMETHOD0":"Basic",
"AUTHMETHOD1":"Post", "AUTHMETHOD1":"Post",
"AUTHMETHOD2":"None" "AUTHMETHOD2":"None"
},
"TOAST": {
"REACTIVATED":"App reactivated!",
"DEACTIVATED":"App deactivated!",
"OIDCUPDATED":"OIDC Config updated!",
"OIDCCLIENTSECRETREGENERATED":"OIDC Client Secret generated!"
} }
}, },
"GENDERS": { "GENDERS": {
@ -555,6 +584,10 @@
"DETAIL": { "DETAIL": {
"TITLE":"Authorization Detail", "TITLE":"Authorization Detail",
"DESCRIPTION":"" "DESCRIPTION":""
},
"TOAST": {
"UPDATED":"Grant updated!",
"BULKREMOVED":"Grants removed!"
} }
}, },
"CHANGES": { "CHANGES": {

View File

@ -15,6 +15,16 @@
href="https://maxst.icons8.com/vue-static/landings/line-awesome/line-awesome/1.3.0/css/line-awesome.min.css"> href="https://maxst.icons8.com/vue-static/landings/line-awesome/line-awesome/1.3.0/css/line-awesome.min.css">
<link rel="manifest" href="manifest.webmanifest"> <link rel="manifest" href="manifest.webmanifest">
<meta name="theme-color" content="#e6768b"> <meta name="theme-color" content="#e6768b">
<meta property="og:url" content="https://console.zitadel.dev" />
<meta property="og:type" content="website" />
<meta property="og:title" content="ZITADEL Console - Identity and Access Management" />
<meta property="og:description" content="Console Management Platform for ZITADEL IAM" />
<meta property="description" content="Console Management Platform for ZITADEL IAM" />
<meta property="og:image"
content="https://console.zitadel.dev/assets/images/zitadel-logo-oneline-lightdesign.svg" />
</head> </head>
<body> <body>

View File

@ -16,6 +16,11 @@
"src": "assets/icons/android-chrome-192x192.png", "src": "assets/icons/android-chrome-192x192.png",
"sizes": "192x192", "sizes": "192x192",
"type": "image/png" "type": "image/png"
},
{
"src": "assets/icons/apple-touch-icon.png",
"sizes": "180x180",
"type": "image/png"
} }
] ]
} }

View File

@ -1,17 +1,17 @@
<!doctype html> <!doctype html>
<html> <html>
<head>
<head>
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>{{.Title}}</title> <title>{{.Title}}</title>
<style> <style>
@import url('https://fonts.googleapis.com/css?family=Roboto:300,400&display=swap'); @import url('https://fonts.googleapis.com/css?family=Roboto:300,400&display=swap');
@font-face { @font-face {
font-family: Ailerons; font-family: Ailerons;
src: url("https://www.caos.ch/fonts/Ailerons-Typeface.otf") format("opentype"); src: url("https://www.caos.ch/fonts/Ailerons-Typeface.otf") format("opentype");
} }
/* ------------------------------------- /* -------------------------------------
@ -23,6 +23,7 @@
-ms-interpolation-mode: bicubic; -ms-interpolation-mode: bicubic;
max-width: 100%; max-width: 100%;
} }
body { body {
background: #222324 url('waves-bottomleft.png') bottom left no-repeat; background: #222324 url('waves-bottomleft.png') bottom left no-repeat;
background-size: 150px; background-size: 150px;
@ -36,11 +37,14 @@
-ms-text-size-adjust: 100%; -ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;
} }
table { table {
border-collapse: separate; border-collapse: separate;
mso-table-lspace: 0pt; mso-table-lspace: 0pt;
mso-table-rspace: 0pt; mso-table-rspace: 0pt;
width: 100%; } width: 100%;
}
table td { table td {
font-family: 'Roboto', sans-serif; font-family: 'Roboto', sans-serif;
font-size: 14px; font-size: 14px;
@ -48,6 +52,7 @@
vertical-align: top; vertical-align: top;
color: #fff; color: #fff;
} }
/* ------------------------------------- /* -------------------------------------
BODY & CONTAINER BODY & CONTAINER
------------------------------------- */ ------------------------------------- */
@ -56,6 +61,7 @@
background-size: 150px; background-size: 150px;
width: 100%; width: 100%;
} }
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */ /* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
.container { .container {
display: block; display: block;
@ -65,6 +71,7 @@
padding: 40px; padding: 40px;
width: 580px; width: 580px;
} }
/* This should also be a block element, so that it will fill 100% of the .container */ /* This should also be a block element, so that it will fill 100% of the .container */
.content { .content {
box-sizing: border-box; box-sizing: border-box;
@ -73,6 +80,7 @@
max-width: 580px; max-width: 580px;
padding: 10px; padding: 10px;
} }
/* ------------------------------------- /* -------------------------------------
HEADER, FOOTER, MAIN HEADER, FOOTER, MAIN
------------------------------------- */ ------------------------------------- */
@ -81,20 +89,24 @@
width: 100%; width: 100%;
border-radius: 16px; border-radius: 16px;
} }
.wrapper { .wrapper {
box-sizing: border-box; box-sizing: border-box;
padding: 50px; padding: 50px;
} }
.content-block { .content-block {
padding-bottom: 10px; padding-bottom: 10px;
padding-top: 10px; padding-top: 10px;
} }
.footer { .footer {
clear: both; clear: both;
margin-top: 10px; margin-top: 10px;
text-align: center; text-align: center;
width: 100%; width: 100%;
} }
.footer td, .footer td,
.footer p, .footer p,
.footer span, .footer span,
@ -104,18 +116,18 @@
text-align: center; text-align: center;
} }
.footer a{ .footer a {
color: #FE00FF; color: #FE00FF;
text-decoration: none; text-decoration: none;
font-size: 14px; font-size: 14px;
font-weight: 400; font-weight: 400;
} }
.footer a:hover{ .footer a:hover {
text-decoration: underline; text-decoration: underline;
} }
.apple-link{ .apple-link {
margin-bottom: 10px; margin-bottom: 10px;
display: inline-block; display: inline-block;
} }
@ -134,12 +146,14 @@
margin: 0; margin: 0;
margin-bottom: 30px; margin-bottom: 30px;
} }
h1 { h1 {
font-size: 35px; font-size: 35px;
font-weight: 300; font-weight: 300;
text-align: center; text-align: center;
text-transform: capitalize; text-transform: capitalize;
} }
p, p,
ul, ul,
ol { ol {
@ -149,32 +163,41 @@
margin: 0; margin: 0;
margin-bottom: 15px; margin-bottom: 15px;
} }
p li, p li,
ul li, ul li,
ol li { ol li {
list-style-position: inside; list-style-position: inside;
margin-left: 5px; margin-left: 5px;
} }
a { a {
color: #0FBDA6; color: #0FBDA6;
text-decoration: underline; text-decoration: underline;
} }
/* ------------------------------------- /* -------------------------------------
BUTTONS BUTTONS
------------------------------------- */ ------------------------------------- */
.btn { .btn {
box-sizing: border-box; box-sizing: border-box;
width: 100%; } width: 100%;
.btn > tbody > tr > td { }
padding-bottom: 15px; }
.btn>tbody>tr>td {
padding-bottom: 15px;
}
.btn table { .btn table {
width: auto; width: auto;
} }
.btn table td { .btn table td {
background-color: #ffffff; background-color: #ffffff;
border-radius: 5px; border-radius: 5px;
text-align: center; text-align: center;
} }
.btn a { .btn a {
background-color: #ffffff; background-color: #ffffff;
border: solid 1px #0FBDA6; border: solid 1px #0FBDA6;
@ -203,6 +226,7 @@
border-color: #0FBDA6; border-color: #0FBDA6;
color: #ffffff; color: #ffffff;
} }
/* ------------------------------------- /* -------------------------------------
OTHER STYLES THAT MIGHT BE USEFUL OTHER STYLES THAT MIGHT BE USEFUL
------------------------------------- */ ------------------------------------- */
@ -214,7 +238,7 @@
color: #FFFFFF; color: #FFFFFF;
} }
.headertitle{ .headertitle {
padding: 30px 30px 70px; padding: 30px 30px 70px;
margin: 0; margin: 0;
font-size: 42px; font-size: 42px;
@ -222,7 +246,7 @@
text-align: center; text-align: center;
} }
.logo{ .logo {
height: 50px; height: 50px;
margin: 10px; margin: 10px;
margin-left: 30px; margin-left: 30px;
@ -237,27 +261,35 @@
.last { .last {
margin-bottom: 0; margin-bottom: 0;
} }
.first { .first {
margin-top: 0; margin-top: 0;
} }
.align-center { .align-center {
text-align: center; text-align: center;
} }
.align-right { .align-right {
text-align: right; text-align: right;
} }
.align-left { .align-left {
text-align: left; text-align: left;
} }
.clear { .clear {
clear: both; clear: both;
} }
.mt0 { .mt0 {
margin-top: 0; margin-top: 0;
} }
.mb0 { .mb0 {
margin-bottom: 0; margin-bottom: 0;
} }
.preheader { .preheader {
color: transparent; color: transparent;
display: none; display: none;
@ -270,6 +302,7 @@
visibility: hidden; visibility: hidden;
width: 0; width: 0;
} }
.subject { .subject {
color: transparent; color: transparent;
display: none; display: none;
@ -282,14 +315,17 @@
visibility: hidden; visibility: hidden;
width: 0; width: 0;
} }
.powered-by a { .powered-by a {
text-decoration: none; text-decoration: none;
} }
hr { hr {
border: 0; border: 0;
border-bottom: 1px solid #f6f6f6; border-bottom: 1px solid #f6f6f6;
margin: 20px 0; margin: 20px 0;
} }
/* ------------------------------------- /* -------------------------------------
RESPONSIVE AND MOBILE FRIENDLY STYLES RESPONSIVE AND MOBILE FRIENDLY STYLES
------------------------------------- */ ------------------------------------- */
@ -298,6 +334,7 @@
font-size: 28px !important; font-size: 28px !important;
margin-bottom: 10px !important; margin-bottom: 10px !important;
} }
table[class=body] p, table[class=body] p,
table[class=body] ul, table[class=body] ul,
table[class=body] ol, table[class=body] ol,
@ -306,34 +343,42 @@
table[class=body] a { table[class=body] a {
font-size: 16px !important; font-size: 16px !important;
} }
table[class=body] .wrapper, table[class=body] .wrapper,
table[class=body] .article { table[class=body] .article {
padding: 10px !important; padding: 10px !important;
} }
table[class=body] .content { table[class=body] .content {
padding: 0 !important; padding: 0 !important;
} }
table[class=body] .container { table[class=body] .container {
padding: 0 !important; padding: 0 !important;
width: 100% !important; width: 100% !important;
} }
table[class=body] .main { table[class=body] .main {
border-left-width: 0 !important; border-left-width: 0 !important;
border-radius: 0 !important; border-radius: 0 !important;
border-right-width: 0 !important; border-right-width: 0 !important;
} }
table[class=body] .btn table { table[class=body] .btn table {
width: 100% !important; width: 100% !important;
} }
table[class=body] .btn a { table[class=body] .btn a {
width: 100% !important; width: 100% !important;
} }
table[class=body] .img-responsive { table[class=body] .img-responsive {
height: auto !important; height: auto !important;
max-width: 100% !important; max-width: 100% !important;
width: auto !important; width: auto !important;
} }
} }
/* ------------------------------------- /* -------------------------------------
PRESERVE THESE STYLES IN THE HEAD PRESERVE THESE STYLES IN THE HEAD
------------------------------------- */ ------------------------------------- */
@ -341,6 +386,7 @@
.ExternalClass { .ExternalClass {
width: 100%; width: 100%;
} }
.ExternalClass, .ExternalClass,
.ExternalClass p, .ExternalClass p,
.ExternalClass span, .ExternalClass span,
@ -349,6 +395,7 @@
.ExternalClass div { .ExternalClass div {
line-height: 100%; line-height: 100%;
} }
.apple-link a { .apple-link a {
color: inherit !important; color: inherit !important;
font-family: inherit !important; font-family: inherit !important;
@ -357,17 +404,20 @@
line-height: inherit !important; line-height: inherit !important;
text-decoration: none !important; text-decoration: none !important;
} }
.btn-primary table td:hover { .btn-primary table td:hover {
background-color: #1ADFC5 !important; background-color: #1ADFC5 !important;
} }
.btn-primary a:hover { .btn-primary a:hover {
background-color: #1ADFC5 !important; background-color: #1ADFC5 !important;
border-color: #1ADFC5 !important; border-color: #1ADFC5 !important;
} }
} }
</style> </style>
</head> </head>
<body class="">
<body class="">
<span class="preheader">{{.PreHeader}}</span> <span class="preheader">{{.PreHeader}}</span>
<span class="subject">{{.Subject}}</span> <span class="subject">{{.Subject}}</span>
<table role="pheader" class="mheader" border="0" cellpadding="0" cellspacing="0"> <table role="pheader" class="mheader" border="0" cellpadding="0" cellspacing="0">
@ -399,19 +449,24 @@
<!-- START MAIN CONTENT AREA --> <!-- START MAIN CONTENT AREA -->
<tr> <tr>
<td class="wrapper"> <td class="wrapper">
<table role="presentation" class="maincontent" border="0" cellpadding="0" cellspacing="0"> <table role="presentation" class="maincontent" border="0" cellpadding="0"
cellspacing="0">
<tr> <tr>
<td> <td>
<p class="hello">{{.Greeting}}</p> <p class="hello">{{.Greeting}}</p>
<p>{{.Text}}</p> <p>{{.Text}}</p>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="btn btn-primary"> <table role="presentation" border="0" cellpadding="0" cellspacing="0"
class="btn btn-primary">
<tbody> <tbody>
<tr> <tr>
<td align="center"> <td align="center">
<table role="presentation" border="0" cellpadding="0" cellspacing="0"> <table role="presentation" border="0" cellpadding="0"
cellspacing="0">
<tbody> <tbody>
<tr> <tr>
<td> <a href="{{.URL}}" target="_blank" rel="noopener noreferrer">{{.ButtonText}}</a> </td> <td> <a href="{{.URL}}" target="_blank"
rel="noopener noreferrer">{{.ButtonText}}</a>
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -434,7 +489,8 @@
<table role="presentation" border="0" cellpadding="0" cellspacing="0"> <table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr> <tr>
<td class="content-block"> <td class="content-block">
<span class="apple-link">CAOS AG &nbsp;&nbsp; | &nbsp;&nbsp; Teufener Strasse 19 &nbsp;&nbsp; | &nbsp;&nbsp; CH-9000 St.Gallen</span> <span class="apple-link">CAOS AG &nbsp;&nbsp; | &nbsp;&nbsp; Teufener Strasse 19
&nbsp;&nbsp; | &nbsp;&nbsp; CH-9000 St.Gallen</span>
<br> <a href="http://www.caos.ch">caos.ch</a>. <br> <a href="http://www.caos.ch">caos.ch</a>.
</td> </td>
</tr> </tr>
@ -447,5 +503,6 @@
<td>&nbsp;</td> <td>&nbsp;</td>
</tr> </tr>
</table> </table>
</body> </body>
</html> </html>