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>
</button>
<a *ngIf="(isHandset$ | async) == false" class="title" [routerLink]="['/']">
<img class="logo" *ngIf="componentCssClass == 'dark-theme'; else lighttheme"
src="./assets/images/zitadel-logo-oneline-darkdesign.svg" />
<img class="logo" alt="zitadel logo" *ngIf="componentCssClass == 'dark-theme'; else lighttheme"
src="../assets/images/zitadel-logo-oneline-darkdesign.svg" />
<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>
</a>
@ -126,4 +126,4 @@
</div>
</mat-drawer-content>
</mat-drawer-container>
</ng-container>
</ng-container>

View File

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

View File

@ -1,5 +1,5 @@
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 localeDe from '@angular/common/locales/de';
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 { AuthUserService } from './services/auth-user.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 { 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 { StatehandlerService, StatehandlerServiceImpl } from './services/statehandler.service';
import { StorageService } from './services/storage.service';
@ -42,7 +42,7 @@ registerLocaleData(localeDe);
// AoT requires an exported function for factories
export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
return new TranslateHttpLoader(http, './assets/i18n/');
return new TranslateHttpLoader(http, './assets/i18n/');
}
const appInitializerFn = (grpcServ: GrpcService) => {
@ -151,7 +151,5 @@ const authConfig: AuthConfig = {
})
export class AppModule {
constructor() {
console.log(window.location.href);
}
constructor() { }
}

View File

@ -1,5 +1,5 @@
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 { AuthService } from '../services/auth.service';
@ -9,7 +9,7 @@ import { AuthService } from '../services/auth.service';
providedIn: 'root',
})
export class AuthGuard implements CanActivate {
constructor(private auth: AuthService, private router: Router) { }
constructor(private auth: AuthService) { }
public canActivate(
_: ActivatedRouteSnapshot,

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Router } from '@angular/router';
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 { AuthService } from 'src/app/services/auth.service';
@ -11,7 +11,7 @@ import { AuthService } from 'src/app/services/auth.service';
styleUrls: ['./accounts-card.component.scss'],
})
export class AccountsCardComponent implements OnInit {
@Input() public profile!: UserProfile.AsObject;
@Input() public profile!: UserProfileView.AsObject;
@Input() public iamuser: boolean = false;
@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) {
this.userService.getMyUserSessions().then(sessions => {
this.users = sessions.toObject().userSessionsList;
const index = this.users.findIndex(user => user.userName === this.profile.userName);
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> = {
customQueryParams: {
// prompt: 'select_account',
} as any,
};
if (loginName) {
(configWithPrompt as any).customQueryParams['login_hint'] = loginName;
if (loginHint) {
(configWithPrompt as any).customQueryParams['login_hint'] = loginHint;
}
this.authService.authenticate(configWithPrompt);
}

View File

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

View File

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

View File

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

View File

@ -42,9 +42,6 @@ export class ProjectMembersComponent implements AfterViewInit {
this.getRoleOptions();
this.route.params.subscribe(params => {
console.log(params);
console.log(this.projectType);
this.projectService.GetProjectById(params.projectid).then(project => {
this.project = project.toObject();
this.dataSource = new ProjectMembersDataSource(this.projectService);
@ -69,13 +66,13 @@ export class ProjectMembersComponent implements AfterViewInit {
this.projectService.GetProjectGrantMemberRoles().then(resp => {
this.memberRoleOptions = resp.toObject().rolesList;
}).catch(error => {
this.toast.showError(error.message);
this.toast.showError(error);
});
} else if (this.projectType === ProjectType.PROJECTTYPE_OWNED) {
this.projectService.GetProjectMemberRoles().then(resp => {
this.memberRoleOptions = resp.toObject().rolesList;
}).catch(error => {
this.toast.showError(error.message);
this.toast.showError(error);
});
}
}
@ -92,18 +89,18 @@ export class ProjectMembersComponent implements AfterViewInit {
public removeProjectMemberSelection(): void {
Promise.all(this.selection.selected.map(member => {
return this.projectService.RemoveProjectMember(this.project.projectId, member.userId).then(() => {
this.toast.showInfo('Removed successfully');
this.toast.showInfo('PROJECT.TOAST.MEMBERREMOVED', true);
}).catch(error => {
this.toast.showError(error.message);
this.toast.showError(error);
});
}));
}
public removeMember(member: ProjectMember.AsObject): void {
this.projectService.RemoveProjectMember(this.project.projectId, member.userId).then(() => {
this.toast.showInfo('Member removed successfully');
this.toast.showInfo('PROJECT.TOAST.MEMBERREMOVED', true);
}).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 => {
return this.projectService.AddProjectMember(this.project.projectId, user.id, roles);
})).then(() => {
this.toast.showError('members added');
this.toast.showInfo('PROJECT.TOAST.MEMBERSADDED', true);
}).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 {
console.log(member, selectionChange.value);
this.projectService.ChangeProjectMember(this.project.projectId, member.userId, selectionChange.value)
.then((newmember: ProjectMember) => {
console.log(newmember.toObject());
this.toast.showInfo('Member updated!');
this.toast.showInfo('PROJECT.TOAST.MEMBERADDED', true);
}).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(''),
group: new FormControl(''),
});
console.log(data);
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) {
this.projectService.ChangeProjectRole(this.projectId, this.key.value, this.key.value, this.group.value)
.then(() => {
this.toast.showInfo('Role updated');
this.toast.showInfo('PROJECT.TOAST.ROLECHANGED', true);
this.dialogRef.close(true);
}).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) { }
public ngOnInit(): void {
console.log(this.projectId);
this.dataSource = new ProjectRolesDataSource(this.projectService);
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 this.projectService.RemoveProjectRole(role.projectId, role.key);
})).then(() => {
this.toast.showInfo('Deleted');
this.toast.showInfo('PROJECT.TOAST.ROLEREMOVED', true);
indexes.forEach(index => {
if (index > -1) {
oldState.splice(index, 1);
@ -102,7 +101,7 @@ export class ProjectRolesComponent implements AfterViewInit, OnInit {
this.projectService
.RemoveProjectRole(role.projectId, role.key)
.then(() => {
this.toast.showInfo('Role removed');
this.toast.showInfo('PROJECT.TOAST.ROLEREMOVED', true);
this.dataSource.rolesSubject.value.splice(index, 1);
this.dataSource.rolesSubject.next(this.dataSource.rolesSubject.value);
})

View File

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

View File

@ -140,7 +140,7 @@ export class SearchUserAutocompleteComponent {
this.users = [user.toObject()];
this.selectionChanged.emit(this.users);
}).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(
map(resp => {
this.totalResult = resp.toObject().totalResult;
console.log(resp.toObject().resultList);
return resp.toObject().resultList;
}),
catchError(() => of([])),

View File

@ -56,7 +56,6 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
grantId: this.grantId,
userId: this.userId,
};
console.log(this.context);
switch (this.context) {
case UserGrantContext.OWNED_PROJECT:
@ -78,7 +77,6 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
default:
this.routerLink = ['/grant-create'];
}
console.log(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 {
this.userService.UpdateUserGrant(grant.id, grant.userId, selectionChange.value)
.then((newmember: UserGrant) => {
this.toast.showInfo('Grant updated!');
.then(() => {
this.toast.showInfo('GRANTS.TOAST.UPDATED', true);
}).catch(error => {
this.toast.showError(error.message);
this.toast.showError(error);
});
}
deleteGrantSelection(): void {
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();
console.log(data);
this.selection.selected.forEach((item) => {
console.log(item);
const index = data.findIndex(i => i.id === item.id);
if (index > -1) {
data.splice(index, 1);
@ -151,7 +147,7 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
});
this.selection.clear();
}).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 => {
console.error(error);
this.toast.showError(error.message);
this.toast.showError(error);
this.errorMessage = error.message;
});
}
@ -145,15 +145,15 @@ export class AppDetailComponent implements OnInit, OnDestroy {
public changeState(event: MatButtonToggleChange): void {
if (event.value === AppState.APPSTATE_ACTIVE) {
this.projectService.ReactivateApplication(this.projectId, this.app.id).then(() => {
this.toast.showInfo('Reactivated Application');
this.toast.showInfo('APP.TOAST.REACTIVATED', true);
}).catch((error: any) => {
this.toast.showError(error.message);
this.toast.showError(error);
});
} else if (event.value === AppState.APPSTATE_INACTIVE) {
this.projectService.DeactivateApplication(this.projectId, this.app.id).then(() => {
this.toast.showInfo('Deactivated Application');
this.toast.showInfo('APP.TOAST.REACTIVATED', true);
}).catch((error: any) => {
this.toast.showError(error.message);
this.toast.showError(error);
});
}
@ -222,7 +222,7 @@ export class AppDetailComponent implements OnInit, OnDestroy {
this.projectService
.UpdateOIDCAppConfig(this.projectId, this.app.id, this.app.oidcConfig)
.then((data: OIDCConfig) => {
this.toast.showInfo('OIDC Config saved');
this.toast.showInfo('APP.TOAST.OIDCUPDATED', true);
})
.catch(data => {
this.toast.showError(data.message);
@ -233,7 +233,7 @@ export class AppDetailComponent implements OnInit, OnDestroy {
public regenerateOIDCClientSecret(): void {
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, {
data: {
clientId: data.toObject().clientId,

View File

@ -91,9 +91,8 @@ export class GrantedProjectDetailComponent implements OnInit, OnDestroy {
if (this.projectId && this.grantId) {
this.projectService.GetGrantedProjectByID(this.projectId, this.grantId).then(proj => {
this.project = proj.toObject();
console.log(this.project);
}).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 {
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.reorganizeItems();
}

View File

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

View File

@ -1,8 +1,9 @@
<div class="wrapper">
<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>
<img src="./assets/images/zitadel-logo-oneline-lightdesign.svg" />
<img alt="zitadel logo" src="../assets/images/zitadel-logo-oneline-lightdesign.svg" />
</ng-template>
</div>
@ -79,4 +80,4 @@
</div>
</ng-template>
</div>
</div>
</div>

View File

@ -76,7 +76,7 @@ export class IamContributorsComponent implements OnInit {
})).then(() => {
this.toast.showError('members added');
}).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(() => {
this.toast.showInfo('Removed successfully');
}).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.toast.showInfo('Member removed successfully');
}).catch(error => {
this.toast.showError(error.message);
this.toast.showError(error);
});
}
@ -99,7 +99,7 @@ export class IamMembersComponent implements AfterViewInit {
})).then(() => {
this.toast.showError('members added');
}).catch(error => {
this.toast.showError(error.message);
this.toast.showError(error);
});
}
}

View File

@ -77,7 +77,7 @@ export class OrgContributorsComponent implements OnInit {
})).then(() => {
this.toast.showError('members added');
}).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 { Router } from '@angular/router';
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 { 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 { lowerCaseValidator, numberValidator, symbolValidator, upperCaseValidator } from '../../user-detail/validators';
@ -62,7 +62,7 @@ export class OrgCreateComponent {
private adminService: AdminService,
private _location: Location,
private fb: FormBuilder,
private orgService: OrgService,
private authUserService: AuthUserService,
) {
const validators: Validators[] = [Validators.required];
@ -70,7 +70,7 @@ export class OrgCreateComponent {
name: ['', [Validators.required]],
domain: [''],
});
this.orgService.GetPasswordComplexityPolicy().then(data => {
this.authUserService.GetMyPasswordComplexityPolicy().then(data => {
this.policy = data.toObject();
if (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.org = org.toObject();
}).catch(error => {
this.toast.showError(error.message);
this.toast.showError(error);
});
this.orgService.SearchMyOrgDomains(0, 100).then(result => {
@ -66,13 +66,13 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
this.orgService.ReactivateMyOrg().then(() => {
this.toast.showInfo('Reactivated Org');
}).catch((error) => {
this.toast.showError(error.message);
this.toast.showError(error);
});
} else if (event.value === OrgState.ORGSTATE_INACTIVE) {
this.orgService.DeactivateMyOrg().then(() => {
this.toast.showInfo('Deactivated Org');
}).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);
}
}).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;
}).catch(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.allRoles = resp.toObject().rolesList;
}).catch(error => {
this.toast.showError(error.message);
this.toast.showError(error);
});
this.myControl.valueChanges.subscribe(change => {

View File

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

View File

@ -35,7 +35,7 @@
<div class="row">
<span class="left-desc">{{'ORG.POLICY.DATA.SHOWLOCKOUTFAILURES' | translate}}</span>
<span class="fill-space"></span>
<mat-slide-toggle name="showLockOutFailures" ngDefaultControl
<mat-slide-toggle color="primary" name="showLockOutFailures" ngDefaultControl
[(ngModel)]="complexityData.showLockOutFailures">
</mat-slide-toggle>
</div>
@ -63,26 +63,28 @@
<div class="row">
<span class="left-desc">{{'ORG.POLICY.DATA.HASNUMBER' | translate}}</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>
</div>
<div class="row">
<span class="left-desc">{{'ORG.POLICY.DATA.HASSYMBOL' | translate}}</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>
</div>
<div class="row">
<span class="left-desc">{{'ORG.POLICY.DATA.HASLOWERCASE' | translate}}</span>
<span class="fill-space"></span>
<mat-slide-toggle name="hasLowercase" ngDefaultControl
<mat-slide-toggle color="primary" name="hasLowercase" ngDefaultControl
[(ngModel)]="complexityData.hasLowercase">
</mat-slide-toggle>
</div>
<div class="row">
<span class="left-desc">{{'ORG.POLICY.DATA.HASUPPERCASE' | translate}}</span>
<span class="fill-space"></span>
<mat-slide-toggle name="hasUppercase" ngDefaultControl
<mat-slide-toggle color="primary" name="hasUppercase" ngDefaultControl
[(ngModel)]="complexityData.hasUppercase">
</mat-slide-toggle>
</div>
@ -132,13 +134,14 @@
<div class="row">
<span class="left-desc">{{'ORG.POLICY.DATA.USERLOGINMUSTBEDOMAIN' | translate}}</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>
</div>
</div>
<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>
</div>
</div>

View File

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

View File

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

View File

@ -36,7 +36,6 @@ export class SearchOrgAutocompleteComponent {
}
}).catch(error => {
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> {
this.projectId = id;
console.log(this.projectId);
this.orgService.GetIam().then(iam => {
this.isZitadel = iam.toObject().iamProjectId === this.projectId;
@ -93,7 +92,7 @@ export class OwnedProjectDetailComponent implements OnInit, OnDestroy {
this.project = proj.toObject();
}).catch(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.toast.showInfo('Reactivated Project');
}).catch(error => {
this.toast.showError(error.message);
this.toast.showError(error);
});
} else if (newState === ProjectState.PROJECTSTATE_INACTIVE) {
this.projectService.DeactivateProject(this.projectId).then(() => {
this.toast.showInfo('Deactivated Project');
}).catch(error => {
this.toast.showError(error.message);
this.toast.showError(error);
});
}
}

View File

@ -1,5 +1,5 @@
<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>
</button>
</div>

View File

@ -115,4 +115,8 @@ export class OwnedProjectGridComponent implements OnChanges {
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);
}).catch(error => {
console.error(error);
this.toast.showError(error.message);
this.toast.showError(error);
this.loadingSubject.next(false);
});

View File

@ -35,7 +35,6 @@ export class ProjectGrantDetailDataSource extends DataSource<ProjectMemberView.A
finalize(() => this.loadingSubject.next(false)),
).subscribe(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);
console.log(projectId, grantId);
from(this.projectService.SearchProjectGrantMembers(projectId,
grantId, pageSize, offset)).pipe(
map(resp => {

View File

@ -63,15 +63,14 @@ export class ProjectGrantMembersComponent implements AfterViewInit, OnInit {
if (this.type === ProjectType.PROJECTTYPE_GRANTED) {
this.projectService.GetProjectGrantMemberRoles().then(resp => {
this.memberRoleOptions = resp.toObject().rolesList;
console.log(this.memberRoleOptions);
}).catch(error => {
this.toast.showError(error.message);
this.toast.showError(error);
});
} else if (this.type === ProjectType.PROJECTTYPE_OWNED) {
this.projectService.GetProjectMemberRoles().then(resp => {
this.memberRoleOptions = resp.toObject().rolesList;
}).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(() => {
this.toast.showInfo('Removed successfully');
}).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.toast.showInfo('Member removed successfully');
}).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> {
const keysList = (await this.projectService.GetProjectGrantMemberRoles()).toObject();
console.log(keysList);
const dialogRef = this.dialog.open(ProjectGrantMembersCreateDialogComponent, {
data: {
@ -127,7 +125,6 @@ export class ProjectGrantMembersComponent implements AfterViewInit, OnInit {
});
dialogRef.afterClosed().subscribe((dataToAdd: ProjectGrantMembersCreateDialogExportType) => {
console.log(dataToAdd);
if (dataToAdd) {
dataToAdd.userIds.forEach((userid: string) => {
this.projectService.AddProjectGrantMember(
@ -138,7 +135,7 @@ export class ProjectGrantMembersComponent implements AfterViewInit, OnInit {
).then(() => {
this.toast.showInfo('Project Grant Member successfully added!');
}).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 {
console.log(this.projectId, this.grantId, member.userId, selectionChange.value);
this.projectService.ChangeProjectGrantMember(this.projectId, this.grantId, member.userId, selectionChange.value)
.then((newmember: ProjectMember) => {
console.log(newmember.toObject());
this.toast.showInfo('Member updated!');
}).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 {
console.log(domain);
this.orgService.getOrgByDomainGlobal(domain).then((ret) => {
const tmp = ret.toObject();
console.log(ret.toObject());
this.authService.GetActiveOrg().then((org) => {
console.log(org);
if (tmp !== org) {
this.org = tmp;
}
});
this.org = ret.toObject();
console.log(this.org);
}).catch(error => {
this.toast.showError(error.message);
this.toast.showError(error);
});
}
@ -71,7 +67,7 @@ export class ProjectGrantCreateComponent implements OnInit, OnDestroy {
this.close();
})
.catch(error => {
this.toast.showError(error.message);
this.toast.showError(error);
});
}

View File

@ -1,9 +1,10 @@
<div class="wrap">
<div class="block">
<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>
<img src="./assets/images/zitadel-logo-oneline-lightdesign.svg" />
<img alt="zitadel logo" src="../../../assets/images/zitadel-logo-oneline-lightdesign.svg" />
</ng-template>
<p>{{'USER.SIGNEDOUT' | translate}}</p>
@ -11,4 +12,4 @@
[routerLink]="[ '/users/me' ]">{{'USER.SIGNEDOUT_BTN' | translate}}</button>
</div>
</div>
</div>
</div>

View File

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

View File

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

View File

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

View File

@ -48,7 +48,7 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
}
});
}, 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 {
if (type === MfaType.MFATYPE_OTP) {
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);
if (index > -1) {
@ -74,7 +74,7 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
}
}).catch(error => {
this.toast.showError(error.message);
this.toast.showError(error);
});
}
}

View File

@ -1,10 +1,9 @@
import { Component, OnInit } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
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 { 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 { lowerCaseValidator, numberValidator, symbolValidator, upperCaseValidator } from '../validators';
@ -36,7 +35,6 @@ export class PasswordComponent implements OnInit {
public passwordForm!: FormGroup;
constructor(
private orgService: OrgService,
activatedRoute: ActivatedRoute,
private fb: FormBuilder,
private userService: AuthUserService,
@ -51,7 +49,7 @@ export class PasswordComponent implements OnInit {
}
const validators: Validators[] = [Validators.required];
this.orgService.GetPasswordComplexityPolicy().then(complexity => {
this.userService.GetMyPasswordComplexityPolicy().then(complexity => {
this.policy = complexity.toObject();
if (this.policy.minLength) {
validators.push(Validators.minLength(this.policy.minLength));
@ -97,7 +95,7 @@ export class PasswordComponent implements OnInit {
public setInitialPassword(userId: string): void {
if (this.passwordForm.valid && this.password && this.password.value) {
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();
}).catch(data => {
this.toast.showError(data.message);
@ -111,7 +109,7 @@ export class PasswordComponent implements OnInit {
this.newPassword && this.newPassword.value && this.newPassword.valid) {
this.userService
.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();
}).catch(data => {
this.toast.showError(data.message);

View File

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

View File

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

View File

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

View File

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

View File

@ -79,7 +79,7 @@ export class UserListComponent implements OnDestroy {
this.dataSource.data = resp.toObject().resultList;
this.loadingSubject.next(false);
}).catch(error => {
this.toast.showError(error.message);
this.toast.showError(error);
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 { Metadata } from 'grpc-web';
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 {
@ -15,6 +15,7 @@ import {
MyProjectOrgSearchRequest,
MyProjectOrgSearchResponse,
PasswordChange,
PasswordComplexityPolicy,
UpdateUserAddressRequest,
UpdateUserEmailRequest,
UpdateUserPhoneRequest,
@ -23,6 +24,7 @@ import {
UserEmail,
UserPhone,
UserProfile,
UserProfileView,
UserSessionViews,
UserView,
VerifyMfaOtp,
@ -57,7 +59,7 @@ export class AuthUserService {
return responseMapper(response);
}
public async GetMyUserProfile(): Promise<UserProfile> {
public async GetMyUserProfile(): Promise<UserProfileView> {
return await this.request(
c => c.getMyUserProfile,
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> {
return await this.request(
c => c.getMyUser,
@ -319,6 +330,9 @@ export class AuthUserService {
this._roleCache = userRoles;
return of(this.hasRoles(userRoles, roles, each));
}),
catchError((err) => {
return of(false);
}),
);
} else {
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 { 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 { GrpcService } from './grpc.service';
import { StatehandlerService } from './statehandler.service';
@ -16,7 +16,7 @@ import { StorageKey, StorageService } from './storage.service';
export class AuthService {
private cachedOrgs: Org.AsObject[] = [];
private _activeOrgChanged: Subject<Org.AsObject> = new Subject();
public user!: Observable<UserProfile.AsObject>;
public user!: Observable<UserProfileView.AsObject>;
private _authenticated: boolean = false;
private readonly _authenticationChanged: BehaviorSubject<
boolean
@ -61,7 +61,11 @@ export class AuthService {
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.clientId = config?.clientId || this.grpcService.clientid;
this.config.redirectUri = config?.redirectUri || this.grpcService.redirectUri;
@ -73,7 +77,8 @@ export class AuthService {
await this.oauthService.loadDiscoveryDocumentAndTryLogin();
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;
this.oauthService.initCodeFlow(newState);
}
@ -89,7 +94,6 @@ export class AuthService {
this.router.navigate(['/']);
}
public get activeOrgChanged(): Observable<Org.AsObject> {
return this._activeOrgChanged;
}

View File

@ -1,31 +1,27 @@
import { Inject, Injectable } from '@angular/core';
import { Metadata } from 'grpc-web';
import {
GrpcDefaultHandler,
GrpcHandler,
GrpcInterceptorHandler,
GrpcRequestFn,
} from './grpc-handler';
import { GRPC_INTERCEPTORS, GrpcInterceptor } from './grpc-interceptor';
import { GrpcDefaultHandler, GrpcHandler, GrpcInterceptorHandler, GrpcRequestFn } from './grpc-handler';
import { GRPC_INTERCEPTORS, GrpcInterceptor } from './interceptors/grpc-interceptor';
@Injectable({
providedIn: 'root',
providedIn: 'root',
})
export class GrpcBackendService {
constructor(
@Inject(GRPC_INTERCEPTORS) private readonly interceptors: GrpcInterceptor[],
) {}
constructor(
@Inject(GRPC_INTERCEPTORS) private readonly interceptors: GrpcInterceptor[],
) { }
public async runRequest<TReq, TResp>(
requestFn: GrpcRequestFn<TReq, TResp>,
request: TReq,
metadata: Metadata = {},
): Promise<TResp> {
const defaultHandler: GrpcHandler = new GrpcDefaultHandler(requestFn);
const chain = this.interceptors.reduceRight(
(next, interceptor) => new GrpcInterceptorHandler(next, interceptor),
defaultHandler,
);
return chain.handle(request, metadata);
}
public async runRequest<TReq, TResp>(
requestFn: GrpcRequestFn<TReq, TResp>,
request: TReq,
metadata: Metadata = {},
): Promise<TResp> {
const defaultHandler: GrpcHandler = new GrpcDefaultHandler(requestFn);
const chain = this.interceptors.reduceRight(
(next, interceptor) => new GrpcInterceptorHandler(next, interceptor),
defaultHandler,
);
return chain.handle(request, metadata);
}
}

View File

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

View File

@ -1,9 +1,9 @@
import { Injectable } from '@angular/core';
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 { StorageService } from './storage.service';
const authorizationKey = 'Authorization';
const bearerPrefix = 'Bearer ';

View File

@ -1,11 +1,12 @@
import { InjectionToken } from '@angular/core';
import { Metadata } from 'grpc-web';
import { GrpcHandler } from './grpc-handler';
import { GrpcHandler } from '../grpc-handler';
export const GRPC_INTERCEPTORS = new InjectionToken<GrpcInterceptor[]>(
'GRPC_INTERCEPTORS',
'GRPC_INTERCEPTORS',
);
export interface GrpcInterceptor {
intercept(req: unknown, metadata: Metadata, next: GrpcHandler): Promise<any>;
intercept(req: unknown, metadata: Metadata, next: GrpcHandler): Promise<any>;
}

View File

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

View File

@ -1,23 +1,46 @@
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({
providedIn: 'root',
})
export class ToastService {
constructor(private snackBar: MatSnackBar) { }
constructor(private snackBar: MatSnackBar, private translate: TranslateService, private authService: AuthService) { }
public showInfo(message: string): void {
this.showMessage(message, 'close');
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');
}
}
public showError(message: string): void {
this.showMessage(decodeURI(message), 'close');
public showError(grpcError: any): void {
const { message, code, metadata } = grpcError;
// TODO: remove check for code === 7
if (code === 16 || (code === 7 && message === 'invalid token')) {
const actionObserver$ = this.showMessage(decodeURI(message), 'Login');
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): void {
this.snackBar.open(message, action, {
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 {
constructor(private swUpdate: SwUpdate, snackbar: MatSnackBar) {
this.swUpdate.checkForUpdate();
this.swUpdate.available.subscribe((evt) => {
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_BTN":"Anmelden",
"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": {
"DETAIL": {
@ -437,6 +453,13 @@
"CREATIONDATE":"Erstelldatum",
"CHANGEDATE":"Letzte Änderung",
"RESOURCEOWNER":"Besitzer"
},
"TOAST":{
"MEMBERREMOVED":"Manager entfernt!",
"MEMBERSADDED": "Manager hinzugefügt!",
"MEMBERADDED": "Manager hinzugefügt!",
"ROLEREMOVED":"Rolle entfernt!",
"ROLECHANGED":"Rolle verändert!"
}
},
"APP": {
@ -480,6 +503,12 @@
"AUTHMETHOD0":"Basic",
"AUTHMETHOD1":"Post",
"AUTHMETHOD2":"None"
},
"TOAST": {
"REACTIVATED":"App reaktiviert!",
"DEACTIVATED":"App deaktiviert!",
"OIDCUPDATED":"OIDC Konfiguration geändert!",
"OIDCCLIENTSECRETREGENERATED":"OIDC Client Secret generiert!"
}
},
"GENDERS": {
@ -555,6 +584,10 @@
"DETAIL": {
"TITLE":"Authorisierung Detail",
"DESCRIPTION":""
},
"TOAST": {
"UPDATED":"Berechtigung geändert!",
"BULKREMOVED":"Berechtigungen entfernt!"
}
},
"CHANGES": {

View File

@ -191,7 +191,23 @@
"SIGNEDOUT":"You are signed out. Click the button below to sign in again!",
"SIGNEDOUT_BTN":"Sign In",
"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": {
"DETAIL": {
@ -437,6 +453,13 @@
"CREATIONDATE":"Created At",
"CHANGEDATE":"Last Modified",
"RESOURCEOWNER":"Owner"
},
"TOAST":{
"MEMBERREMOVED":"Manager removed!",
"MEMBERSADDED": "Managers added!",
"MEMBERADDED": "Manager added!",
"ROLEREMOVED":"Role removed!",
"ROLECHANGED":"Role changed!"
}
},
"APP": {
@ -480,6 +503,12 @@
"AUTHMETHOD0":"Basic",
"AUTHMETHOD1":"Post",
"AUTHMETHOD2":"None"
},
"TOAST": {
"REACTIVATED":"App reactivated!",
"DEACTIVATED":"App deactivated!",
"OIDCUPDATED":"OIDC Config updated!",
"OIDCCLIENTSECRETREGENERATED":"OIDC Client Secret generated!"
}
},
"GENDERS": {
@ -555,6 +584,10 @@
"DETAIL": {
"TITLE":"Authorization Detail",
"DESCRIPTION":""
},
"TOAST": {
"UPDATED":"Grant updated!",
"BULKREMOVED":"Grants removed!"
}
},
"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">
<link rel="manifest" href="manifest.webmanifest">
<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>
<body>

View File

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

View File

@ -1,451 +1,508 @@
<!doctype html>
<html>
<head>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>{{.Title}}</title>
<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-family: Ailerons;
src: url("https://www.caos.ch/fonts/Ailerons-Typeface.otf") format("opentype");
}
@font-face {
font-family: Ailerons;
src: url("https://www.caos.ch/fonts/Ailerons-Typeface.otf") format("opentype");
}
/* -------------------------------------
/* -------------------------------------
GLOBAL RESETS
------------------------------------- */
img {
border: none;
-ms-interpolation-mode: bicubic;
max-width: 100%;
}
body {
background: #222324 url('waves-bottomleft.png') bottom left no-repeat;
background-size: 150px;
font-family: 'Roboto', sans-serif;
font-weight: 300;
-webkit-font-smoothing: antialiased;
font-size: 14px;
line-height: 1.4;
margin: 0;
padding: 0;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
table {
border-collapse: separate;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
width: 100%; }
img {
border: none;
-ms-interpolation-mode: bicubic;
max-width: 100%;
}
body {
background: #222324 url('waves-bottomleft.png') bottom left no-repeat;
background-size: 150px;
font-family: 'Roboto', sans-serif;
font-weight: 300;
-webkit-font-smoothing: antialiased;
font-size: 14px;
line-height: 1.4;
margin: 0;
padding: 0;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
table {
border-collapse: separate;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
width: 100%;
}
table td {
font-family: 'Roboto', sans-serif;
font-size: 14px;
font-weight: 300;
vertical-align: top;
color: #fff;
}
/* -------------------------------------
font-family: 'Roboto', sans-serif;
font-size: 14px;
font-weight: 300;
vertical-align: top;
color: #fff;
}
/* -------------------------------------
BODY & CONTAINER
------------------------------------- */
.body {
background: url('waves-topright.png') top right no-repeat;
background-size: 150px;
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 */
.container {
display: block;
margin: 0 auto !important;
/* makes it centered */
max-width: 580px;
padding: 40px;
width: 580px;
}
/* This should also be a block element, so that it will fill 100% of the .container */
.content {
box-sizing: border-box;
display: block;
margin: 0 auto;
max-width: 580px;
padding: 10px;
}
/* -------------------------------------
.body {
background: url('waves-topright.png') top right no-repeat;
background-size: 150px;
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 */
.container {
display: block;
margin: 0 auto !important;
/* makes it centered */
max-width: 580px;
padding: 40px;
width: 580px;
}
/* This should also be a block element, so that it will fill 100% of the .container */
.content {
box-sizing: border-box;
display: block;
margin: 0 auto;
max-width: 580px;
padding: 10px;
}
/* -------------------------------------
HEADER, FOOTER, MAIN
------------------------------------- */
.main {
background: #191919;
width: 100%;
border-radius: 16px;
}
.wrapper {
box-sizing: border-box;
padding: 50px;
}
.content-block {
padding-bottom: 10px;
padding-top: 10px;
}
.footer {
clear: both;
margin-top: 10px;
text-align: center;
width: 100%;
}
.main {
background: #191919;
width: 100%;
border-radius: 16px;
}
.wrapper {
box-sizing: border-box;
padding: 50px;
}
.content-block {
padding-bottom: 10px;
padding-top: 10px;
}
.footer {
clear: both;
margin-top: 10px;
text-align: center;
width: 100%;
}
.footer td,
.footer p,
.footer span,
.footer a {
color: #999999;
font-size: 12px;
text-align: center;
}
.footer a{
color: #FE00FF;
text-decoration: none;
font-size: 14px;
font-weight: 400;
color: #999999;
font-size: 12px;
text-align: center;
}
.footer a:hover{
text-decoration: underline;
.footer a {
color: #FE00FF;
text-decoration: none;
font-size: 14px;
font-weight: 400;
}
.apple-link{
margin-bottom: 10px;
display: inline-block;
.footer a:hover {
text-decoration: underline;
}
/* -------------------------------------
.apple-link {
margin-bottom: 10px;
display: inline-block;
}
/* -------------------------------------
TYPOGRAPHY
------------------------------------- */
h1,
h2,
h3,
h4 {
color: #fff;
font-family: 'Roboto', sans-serif;
font-weight: 300;
line-height: 1.4;
margin: 0;
margin-bottom: 30px;
}
h1 {
font-size: 35px;
font-weight: 300;
text-align: center;
text-transform: capitalize;
}
p,
ul,
ol {
font-family: 'Roboto', sans-serif;
font-size: 14px;
font-weight: 300;
margin: 0;
margin-bottom: 15px;
}
h1,
h2,
h3,
h4 {
color: #fff;
font-family: 'Roboto', sans-serif;
font-weight: 300;
line-height: 1.4;
margin: 0;
margin-bottom: 30px;
}
h1 {
font-size: 35px;
font-weight: 300;
text-align: center;
text-transform: capitalize;
}
p,
ul,
ol {
font-family: 'Roboto', sans-serif;
font-size: 14px;
font-weight: 300;
margin: 0;
margin-bottom: 15px;
}
p li,
ul li,
ol li {
list-style-position: inside;
margin-left: 5px;
}
a {
color: #0FBDA6;
text-decoration: underline;
}
/* -------------------------------------
list-style-position: inside;
margin-left: 5px;
}
a {
color: #0FBDA6;
text-decoration: underline;
}
/* -------------------------------------
BUTTONS
------------------------------------- */
.btn {
box-sizing: border-box;
width: 100%; }
.btn > tbody > tr > td {
padding-bottom: 15px; }
.btn {
box-sizing: border-box;
width: 100%;
}
.btn>tbody>tr>td {
padding-bottom: 15px;
}
.btn table {
width: auto;
}
width: auto;
}
.btn table td {
background-color: #ffffff;
border-radius: 5px;
text-align: center;
}
background-color: #ffffff;
border-radius: 5px;
text-align: center;
}
.btn a {
background-color: #ffffff;
border: solid 1px #0FBDA6;
border-radius: 5px;
box-sizing: border-box;
color: #0FBDA6;
cursor: pointer;
display: inline-block;
font-size: 14px;
font-weight: 400;
margin: 0;
padding: 20px 60px;
text-decoration: none;
}
background-color: #ffffff;
border: solid 1px #0FBDA6;
border-radius: 5px;
box-sizing: border-box;
color: #0FBDA6;
cursor: pointer;
display: inline-block;
font-size: 14px;
font-weight: 400;
margin: 0;
padding: 20px 60px;
text-decoration: none;
}
.btn-primary {
margin-top: 50px;
}
.btn-primary {
margin-top: 50px;
}
.btn-primary table td {
background-color: #0FBDA6;
}
.btn-primary table td {
background-color: #0FBDA6;
}
.btn-primary a {
background-color: #0FBDA6;
border-color: #0FBDA6;
color: #ffffff;
}
/* -------------------------------------
.btn-primary a {
background-color: #0FBDA6;
border-color: #0FBDA6;
color: #ffffff;
}
/* -------------------------------------
OTHER STYLES THAT MIGHT BE USEFUL
------------------------------------- */
.mheader {
background-color: #131313;
}
.mheader {
background-color: #131313;
}
.headercell {
color: #FFFFFF;
}
.headercell {
color: #FFFFFF;
}
.headertitle{
padding: 30px 30px 70px;
margin: 0;
font-size: 42px;
font-family: 'Ailerons', sans-serif;
text-align: center;
}
.headertitle {
padding: 30px 30px 70px;
margin: 0;
font-size: 42px;
font-family: 'Ailerons', sans-serif;
text-align: center;
}
.logo{
height: 50px;
margin: 10px;
margin-left: 30px;
}
.logo {
height: 50px;
margin: 10px;
margin-left: 30px;
}
.hello {
font-size: 22px;
}
.hello {
font-size: 22px;
}
.last {
margin-bottom: 0;
}
.first {
margin-top: 0;
}
.align-center {
text-align: center;
}
.align-right {
text-align: right;
}
.align-left {
text-align: left;
}
.clear {
clear: both;
}
.mt0 {
margin-top: 0;
}
.mb0 {
margin-bottom: 0;
}
.preheader {
color: transparent;
display: none;
height: 0;
max-height: 0;
max-width: 0;
opacity: 0;
overflow: hidden;
mso-hide: all;
visibility: hidden;
width: 0;
}
.subject {
color: transparent;
display: none;
height: 0;
max-height: 0;
max-width: 0;
opacity: 0;
overflow: hidden;
mso-hide: all;
visibility: hidden;
width: 0;
}
.powered-by a {
text-decoration: none;
}
hr {
border: 0;
border-bottom: 1px solid #f6f6f6;
margin: 20px 0;
}
/* -------------------------------------
.last {
margin-bottom: 0;
}
.first {
margin-top: 0;
}
.align-center {
text-align: center;
}
.align-right {
text-align: right;
}
.align-left {
text-align: left;
}
.clear {
clear: both;
}
.mt0 {
margin-top: 0;
}
.mb0 {
margin-bottom: 0;
}
.preheader {
color: transparent;
display: none;
height: 0;
max-height: 0;
max-width: 0;
opacity: 0;
overflow: hidden;
mso-hide: all;
visibility: hidden;
width: 0;
}
.subject {
color: transparent;
display: none;
height: 0;
max-height: 0;
max-width: 0;
opacity: 0;
overflow: hidden;
mso-hide: all;
visibility: hidden;
width: 0;
}
.powered-by a {
text-decoration: none;
}
hr {
border: 0;
border-bottom: 1px solid #f6f6f6;
margin: 20px 0;
}
/* -------------------------------------
RESPONSIVE AND MOBILE FRIENDLY STYLES
------------------------------------- */
@media only screen and (max-width: 620px) {
table[class=body] h1 {
font-size: 28px !important;
margin-bottom: 10px !important;
@media only screen and (max-width: 620px) {
table[class=body] h1 {
font-size: 28px !important;
margin-bottom: 10px !important;
}
table[class=body] p,
table[class=body] ul,
table[class=body] ol,
table[class=body] td,
table[class=body] span,
table[class=body] a {
font-size: 16px !important;
}
table[class=body] .wrapper,
table[class=body] .article {
padding: 10px !important;
}
table[class=body] .content {
padding: 0 !important;
}
table[class=body] .container {
padding: 0 !important;
width: 100% !important;
}
table[class=body] .main {
border-left-width: 0 !important;
border-radius: 0 !important;
border-right-width: 0 !important;
}
table[class=body] .btn table {
width: 100% !important;
}
table[class=body] .btn a {
width: 100% !important;
}
table[class=body] .img-responsive {
height: auto !important;
max-width: 100% !important;
width: auto !important;
}
}
table[class=body] p,
table[class=body] ul,
table[class=body] ol,
table[class=body] td,
table[class=body] span,
table[class=body] a {
font-size: 16px !important;
}
table[class=body] .wrapper,
table[class=body] .article {
padding: 10px !important;
}
table[class=body] .content {
padding: 0 !important;
}
table[class=body] .container {
padding: 0 !important;
width: 100% !important;
}
table[class=body] .main {
border-left-width: 0 !important;
border-radius: 0 !important;
border-right-width: 0 !important;
}
table[class=body] .btn table {
width: 100% !important;
}
table[class=body] .btn a {
width: 100% !important;
}
table[class=body] .img-responsive {
height: auto !important;
max-width: 100% !important;
width: auto !important;
}
}
/* -------------------------------------
/* -------------------------------------
PRESERVE THESE STYLES IN THE HEAD
------------------------------------- */
@media all {
.ExternalClass {
width: 100%;
@media all {
.ExternalClass {
width: 100%;
}
.ExternalClass,
.ExternalClass p,
.ExternalClass span,
.ExternalClass font,
.ExternalClass td,
.ExternalClass div {
line-height: 100%;
}
.apple-link a {
color: inherit !important;
font-family: inherit !important;
font-size: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
text-decoration: none !important;
}
.btn-primary table td:hover {
background-color: #1ADFC5 !important;
}
.btn-primary a:hover {
background-color: #1ADFC5 !important;
border-color: #1ADFC5 !important;
}
}
.ExternalClass,
.ExternalClass p,
.ExternalClass span,
.ExternalClass font,
.ExternalClass td,
.ExternalClass div {
line-height: 100%;
}
.apple-link a {
color: inherit !important;
font-family: inherit !important;
font-size: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
text-decoration: none !important;
}
.btn-primary table td:hover {
background-color: #1ADFC5 !important;
}
.btn-primary a:hover {
background-color: #1ADFC5 !important;
border-color: #1ADFC5 !important;
}
}
</style>
</head>
<body class="">
</head>
<body class="">
<span class="preheader">{{.PreHeader}}</span>
<span class="subject">{{.Subject}}</span>
<table role="pheader" class="mheader" border="0" cellpadding="0" cellspacing="0">
<tr>
<td class="mheadercell">
</td>
<td class="logo">
<img class="logo" src="https://caos.ch/images/LogoCaos.png" alt="CAOS AG - Logo">
</td>
</tr>
</table>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="body">
<tr>
<td></td>
<td class="container">
<div class="content">
<tr>
<td class="mheadercell">
<!-- START CENTERED WHITE CONTAINER -->
<table role="pheader" class="tableheader" border="0" cellpadding="0" cellspacing="0">
<tr>
<td class="headercell">
<p class="headertitle">Zitadel</p>
</td>
</tr>
</table>
<table role="presentation" class="main">
<!-- START MAIN CONTENT AREA -->
<tr>
<td class="wrapper">
<table role="presentation" class="maincontent" border="0" cellpadding="0" cellspacing="0">
<tr>
<td>
<p class="hello">{{.Greeting}}</p>
<p>{{.Text}}</p>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="btn btn-primary">
<tbody>
<tr>
<td align="center">
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td> <a href="{{.URL}}" target="_blank" rel="noopener noreferrer">{{.ButtonText}}</a> </td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!-- END MAIN CONTENT AREA -->
</table>
<!-- END CENTERED WHITE CONTAINER -->
<!-- START FOOTER -->
<div class="footer">
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<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>
<br> <a href="http://www.caos.ch">caos.ch</a>.
</td>
</tr>
</table>
</div>
<!-- END FOOTER -->
</div>
</td>
<td>&nbsp;</td>
</tr>
</td>
<td class="logo">
<img class="logo" src="https://caos.ch/images/LogoCaos.png" alt="CAOS AG - Logo">
</td>
</tr>
</table>
</body>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="body">
<tr>
<td></td>
<td class="container">
<div class="content">
<!-- START CENTERED WHITE CONTAINER -->
<table role="pheader" class="tableheader" border="0" cellpadding="0" cellspacing="0">
<tr>
<td class="headercell">
<p class="headertitle">Zitadel</p>
</td>
</tr>
</table>
<table role="presentation" class="main">
<!-- START MAIN CONTENT AREA -->
<tr>
<td class="wrapper">
<table role="presentation" class="maincontent" border="0" cellpadding="0"
cellspacing="0">
<tr>
<td>
<p class="hello">{{.Greeting}}</p>
<p>{{.Text}}</p>
<table role="presentation" border="0" cellpadding="0" cellspacing="0"
class="btn btn-primary">
<tbody>
<tr>
<td align="center">
<table role="presentation" border="0" cellpadding="0"
cellspacing="0">
<tbody>
<tr>
<td> <a href="{{.URL}}" target="_blank"
rel="noopener noreferrer">{{.ButtonText}}</a>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!-- END MAIN CONTENT AREA -->
</table>
<!-- END CENTERED WHITE CONTAINER -->
<!-- START FOOTER -->
<div class="footer">
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<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>
<br> <a href="http://www.caos.ch">caos.ch</a>.
</td>
</tr>
</table>
</div>
<!-- END FOOTER -->
</div>
</td>
<td>&nbsp;</td>
</tr>
</table>
</body>
</html>