fix: part console ui (#1430)

* home, user create, list, app list

* org create fix

* fix user bindings, usertable, changes, projects

* fix app grid type, domains

* e.164 international phonenumber validation, create user, phone email dialog

* single authmethod create dialog

* fix timestamp to date pipe, app create secret dialog

* validate e164 number edit dialog

* fix machine keys, timestamp pipe

* rm comment

* projecttype circular deps

* downgrade protoc to 3.13.0 due to deserializer error, fix circular dep

* apptype controlname
This commit is contained in:
Max Peintner 2021-03-18 10:46:22 +01:00 committed by GitHub
parent 3cdaa8193d
commit bc7e650089
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 530 additions and 355 deletions

View File

@ -18,7 +18,7 @@ ENV PROTOC_ARCH x86_64
## protoc and protoc-gen-grpc-web for later use
#######################
FROM ${BUILDARCH}-base AS base
ARG PROTOC_VERSION=3.14.0
ARG PROTOC_VERSION=3.13.0
ARG PROTOC_ZIP=protoc-${PROTOC_VERSION}-linux-${PROTOC_ARCH}.zip
ARG GRPC_WEB_VERSION=1.2.1
# no arm specific version available and x86 works fine at the moment:

View File

@ -10721,6 +10721,11 @@
}
}
},
"libphonenumber-js": {
"version": "1.9.13",
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.9.13.tgz",
"integrity": "sha512-DOvAj9Now6KqP+L1Q3JrM3iNhH/mXiOPTj6kxb9OnJbYsVYRlVdvRY1kCpU3Tz9VegIEi6MgDrviBaAnvB3aSw=="
},
"license-webpack-plugin": {
"version": "2.3.11",
"resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-2.3.11.tgz",

View File

@ -35,6 +35,7 @@
"google-protobuf": "^3.13.0",
"grpc": "^1.24.5",
"grpc-web": "^1.2.1",
"libphonenumber-js": "^1.9.13",
"moment": "^2.29.1",
"ngx-quicklink": "^0.2.6",
"rxjs": "~6.6.3",

View File

@ -58,9 +58,11 @@
<a class="doc-link" href="https://docs.zitadel.ch" mat-stroked-button target="_blank">{{'MENU.DOCUMENTATION'
| translate}}</a>
<div (clickOutside)="closeAccountCard()" class="icon-container">
<app-avatar *ngIf="user && (user.displayName || (user.firstName && user.lastName))"
<app-avatar
*ngIf="user && (user.human?.profile?.displayName || (user.human?.profile?.firstName && user.human?.profile?.lastName))"
class="avatar dontcloseonclick" (click)="showAccount = !showAccount" [active]="showAccount"
[name]="user.displayName ? user.displayName : (user.firstName + ' '+ user.lastName)" [size]="38">
[name]="user.human.profile.displayName ? user.human.profile.displayName : (user.human.profile.firstName + ' '+ user.human.profile.lastName)"
[size]="38">
</app-avatar>
<app-accounts-card @accounts class="a_card mat-elevation-z1" *ngIf="showAccount"
(close)="showAccount = false" [user]="user" [iamuser]="iamuser$ | async">

View File

@ -1,5 +1,5 @@
<div class="cnsl-app-card" [ngClass]="{'web': type == OIDCApplicationType.OIDCAPPLICATIONTYPE_WEB,
'useragent': type == OIDCApplicationType.OIDCAPPLICATIONTYPE_USER_AGENT,
'native': type == OIDCApplicationType.OIDCAPPLICATIONTYPE_NATIVE, 'api': isApiApp}">
<div class="cnsl-app-card" [ngClass]="{'web': type == OIDCAppType.OIDC_APP_TYPE_WEB,
'useragent': type == OIDCAppType.OIDC_APP_TYPE_USER_AGENT,
'native': type == OIDCAppType.OIDC_APP_TYPE_NATIVE, 'api': isApiApp}">
<ng-content></ng-content>
</div>

View File

@ -10,5 +10,5 @@ export class AppCardComponent {
@Input() public outline: boolean = false;
@Input() public type!: OIDCAppType;
@Input() public isApiApp: boolean = false;
public OIDCApplicationType: any = OIDCAppType;
public OIDCAppType: any = OIDCAppType;
}

View File

@ -152,6 +152,7 @@ export class ChangesComponent implements OnInit, OnDestroy {
take(1),
tap((res: ListChanges) => {
const values = res.resultList;
console.log(values);
const mapped = this.mapChanges(values);
// update source with new values, done loading
// this._data.next(values);
@ -184,7 +185,7 @@ export class ChangesComponent implements OnInit, OnDestroy {
const userData: any = {
editor: change.editorDisplayName,
editorId: change.editorId,
editorName: change.editorDisplayName,
editorDisplayName: change.editorDisplayName,
dates: [change.changeDate],
// data: [change.data],
@ -205,7 +206,7 @@ export class ChangesComponent implements OnInit, OnDestroy {
{
editor: change.editorDisplayName,
editorId: change.editorId,
editorName: change.editorDisplayName,
editorDisplayName: change.editorDisplayName,
dates: [change.changeDate],
// data: [change.data],

View File

@ -41,7 +41,7 @@
<ng-container matColumnDef="creationDate">
<th mat-header-cell *matHeaderCellDef> {{ 'USER.MACHINE.CREATIONDATE' | translate }} </th>
<td mat-cell *matCellDef="let key">
{{key.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'}}
{{key.details?.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'}}
</td>
</ng-container>

View File

@ -126,6 +126,7 @@ export class MachineKeysComponent implements OnInit {
if (this.userId) {
this.mgmtService.listMachineKeys(this.userId, limit, offset).then(resp => {
this.keyResult = resp;
console.log(resp);
if (resp.resultList) {
this.dataSource.data = resp.resultList;
}

View File

@ -12,6 +12,7 @@ import { PasswordComplexityPolicy } from 'src/app/proto/generated/zitadel/policy
import { AdminService } from 'src/app/services/admin.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
import { CnslLinks } from '../../links/links.component';
import {
IAM_LABEL_LINK,
@ -76,6 +77,7 @@ export class PasswordComplexityPolicyComponent implements OnDestroy {
this.getData().then(data => {
if (data.policy) {
this.complexityData = data.policy;
console.log(data.policy);
this.loading = false;
}
});

View File

@ -6,7 +6,10 @@ import { ListProjectGrantMembersResponse, ListProjectMembersResponse } from 'src
import { Member } from 'src/app/proto/generated/zitadel/member_pb';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ProjectType } from './project-members.component';
export enum ProjectType {
PROJECTTYPE_OWNED = "OWNED",
PROJECTTYPE_GRANTED = "GRANTED"
}
/**
* Data source for the ProjectMembers view. This class should

View File

@ -11,12 +11,7 @@ import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
import { CreationType, MemberCreateDialogComponent } from '../add-member-dialog/member-create-dialog.component';
import { ProjectMembersDataSource } from './project-members-datasource';
export enum ProjectType {
PROJECTTYPE_OWNED = "OWNED",
PROJECTTYPE_GRANTED = "GRANTED"
}
import { ProjectMembersDataSource, ProjectType } from './project-members-datasource';
@Component({
selector: 'app-project-members',

View File

@ -52,7 +52,8 @@
<ng-container matColumnDef="creationDate">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.ROLE.CREATIONDATE' | translate }} </th>
<td (click)="openDetailDialog(role)" mat-cell *matCellDef="let role">
<span>{{role.creationDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}</span>
<span *ngIf="role?.details?.creationDate">{{role.details.creationDate | timestampToDate |
localizedDate: 'dd. MMM, HH:mm' }}</span>
</td>
</ng-container>

View File

@ -1,13 +1,17 @@
<div class="wrapper max-width-container">
<div class="header" *ngIf="(authService.user | async) || {} as user">
<app-avatar [routerLink]="['/users/me']" *ngIf="user && (user.displayName || (user.firstName && user.lastName))"
class="avatar" [name]="user.displayName ? user.displayName : (user.firstName + ' '+ user.lastName)"
<app-avatar [routerLink]="['/users/me']"
*ngIf="user && (user.human?.profile?.displayName || (user.human?.profile?.firstName && user.human?.profile?.lastName))"
class="avatar"
[name]="user.human?.profile?.displayName ? user.human?.profile?.displayName : (user.human?.profile?.firstName + ' '+ user.human?.profile?.lastName)"
[size]="100">
</app-avatar>
<h3 *ngIf="(user.displayName || user.firstName || user.lastName); else loader">
<h3
*ngIf="user && (user.human?.profile?.displayName || user.human?.profile?.firstName || user.human?.profile?.lastName); else loader">
{{'HOME.WELCOME' | translate}},
{{user?.displayName ? user.displayName : (user.firstName + ' '+ user.lastName)}}</h3>
<p>{{user?.userName}}</p>
{{user?.human?.profile?.displayName ? user.human?.profile?.displayName : (user.human?.profile?.firstName + '
'+ user.human?.profile?.lastName)}}</h3>
<p>{{user?.human?.profile?.userName}}</p>
<p class="wlc_stnce">{{'HOME.WELCOMESENTENCE' | translate}}</p>
<ng-template #loader>

View File

@ -3,7 +3,6 @@ import { MatDialog } from '@angular/material/dialog';
import { PageEvent } from '@angular/material/paginator';
import { MatSelectChange } from '@angular/material/select';
import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component';
import { ProjectType } from 'src/app/modules/project-members/project-members.component';
import { Member } from 'src/app/proto/generated/zitadel/member_pb';
import { User } from 'src/app/proto/generated/zitadel/user_pb';
import { AdminService } from 'src/app/services/admin.service';
@ -18,7 +17,6 @@ import { IamMembersDataSource } from './iam-members-datasource';
})
export class IamMembersComponent {
public INITIALPAGESIZE: number = 25;
public projectType: ProjectType = ProjectType.PROJECTTYPE_OWNED;
public dataSource!: IamMembersDataSource;
public memberRoleOptions: string[] = [];

View File

@ -116,8 +116,8 @@
[password]="password">
</app-password-complexity-view>
<form [formGroup]="pwdForm" class="form">
<cnsl-form-field class="formfield" *ngIf="password" appearance="outline">
<form [formGroup]="pwdForm" class="pwd-form">
<cnsl-form-field class="pwd" *ngIf="password" appearance="outline">
<cnsl-label>{{ 'USER.PASSWORD.NEW' | translate }}</cnsl-label>
<input cnslInput autocomplete="off" name="firstpassword"
formControlName="password" type="password" />
@ -127,7 +127,7 @@
</span>
</cnsl-form-field>
<cnsl-form-field class="formfield" *ngIf="confirmPassword" appearance="outline">
<cnsl-form-field class="pwd" *ngIf="confirmPassword" appearance="outline">
<cnsl-label>{{ 'USER.PASSWORD.CONFIRM' | translate }}</cnsl-label>
<input cnslInput autocomplete="off" name="confirmPassword"
formControlName="confirmPassword" type="password" />

View File

@ -110,6 +110,17 @@ h1 {
margin: 0 .5rem;
}
}
.pwd-form {
display: flex;
flex-direction: row;
flex-wrap: wrap;
.pwd {
flex: 1;
margin: 0 .5rem;
}
}
}
}

View File

@ -95,7 +95,7 @@ export class OrgCreateComponent {
createOrgRequest.setDomain(this.domain?.value);
const humanRequest: SetUpOrgRequest.Human = new SetUpOrgRequest.Human();
humanRequest.setEmail(this.email?.value);
humanRequest.setEmail(new SetUpOrgRequest.Human.Email().setEmail(this.email?.value));
humanRequest.setUserName(this.userName?.value);
const profile: SetUpOrgRequest.Human.Profile = new SetUpOrgRequest.Human.Profile();
@ -105,7 +105,7 @@ export class OrgCreateComponent {
profile.setGender(this.gender?.value);
profile.setPreferredLanguage(this.preferredLanguage?.value);
humanRequest.setProfile(this.firstName?.value);
humanRequest.setProfile(profile);
if (this.usePassword && this.password) {
humanRequest.setPassword(this.password?.value);
}

View File

@ -7,18 +7,18 @@
description="{{ 'ORG.DOMAINS.DESCRIPTION' | translate }}">
<div *ngFor="let domain of domains" class="domain">
<span *ngIf="canwrite$ | async" (click)="verifyDomain(domain )"
class="title">{{domain.domain}}</span>
<span *ngIf="(canwrite$ | async) == false" class="title disabled">{{domain.domain}}</span>
<span *ngIf="canwrite$ | async" (click)="verifyDomain(domain)"
class="title">{{domain.domainName}}</span>
<span *ngIf="(canwrite$ | async) == false" class="title disabled">{{domain?.domainName}}</span>
<i matTooltip="verified" *ngIf="domain.verified" class="verified las la-check-circle"></i>
<i matTooltip="primary" *ngIf="domain.primary" class="primary las la-star"></i>
<a *ngIf="!domain.primary && (canwrite$ | async)" class="primaryset"
<i matTooltip="verified" *ngIf="domain.isVerified" class="verified las la-check-circle"></i>
<i matTooltip="primary" *ngIf="domain.isPrimary" class="primary las la-star"></i>
<a *ngIf="!domain.isPrimary && (canwrite$ | async)" class="primaryset"
(click)="setPrimary(domain)">{{'ORG.DOMAINS.SETPRIMARY' | translate}}</a>
<span class="fill-space"></span>
<button class="rem-button" [disabled]="(canwrite$ | async) == false" matTooltip="Remove domain"
color="warn" mat-icon-button (click)="removeDomain(domain.domain)"><i
color="warn" mat-icon-button (click)="removeDomain(domain.domainName)"><i
class="las la-trash"></i></button>
</div>
<p class="new-desc">{{'ORG.PAGES.ORGDOMAIN.VERIFICATION' | translate}}</p>

View File

@ -10,13 +10,17 @@ import { debounceTime, takeUntil } from 'rxjs/operators';
import { RadioItemAuthType } from 'src/app/modules/app-radio/app-auth-method-radio/app-auth-method-radio.component';
import {
APIAuthMethodType,
App,
OIDCAppType,
OIDCAuthMethodType,
OIDCGrantType,
OIDCResponseType,
} from 'src/app/proto/generated/zitadel/app_pb';
import { AddAPIAppRequest, AddOIDCAppRequest } from 'src/app/proto/generated/zitadel/management_pb';
import {
AddAPIAppRequest,
AddAPIAppResponse,
AddOIDCAppRequest,
AddOIDCAppResponse,
} from 'src/app/proto/generated/zitadel/management_pb';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
@ -269,6 +273,7 @@ export class AppCreateComponent implements OnInit, OnDestroy {
private async getData({ projectid }: Params): Promise<void> {
this.projectId = projectid;
console.log(this.projectId);
this.oidcAppRequest.projectId = projectid;
this.apiAppRequest.projectId = projectid;
}
@ -289,11 +294,11 @@ export class AppCreateComponent implements OnInit, OnDestroy {
.addOIDCApp(this.oidcAppRequest)
.then((resp) => {
this.loading = false;
// if (resp.oidcConfig?.authMethodType !== OIDCAuthMethodType.OIDCAUTHMETHODTYPE_NONE) {
// this.showSavedDialog(resp);
// } else {
// this.router.navigate(['projects', this.projectId, 'apps', response.id]);
// }
if (resp.clientId || resp.clientSecret) {
this.showSavedDialog(resp);
} else {
this.router.navigate(['projects', this.projectId, 'apps', resp.appId]);
}
})
.catch(error => {
this.loading = false;
@ -305,12 +310,12 @@ export class AppCreateComponent implements OnInit, OnDestroy {
.addAPIApp(this.apiAppRequest)
.then((resp) => {
this.loading = false;
// const response = resp.toObject();
// if (response.apiConfig?.authMethodType == APIAuthMethodType.APIAUTHMETHODTYPE_BASIC) {
// this.showSavedDialog(resp);
// } else {
// this.router.navigate(['projects', this.projectId, 'apps', response.id]);
// }
if (resp.clientId || resp.clientSecret) {
this.showSavedDialog(resp);
} else {
this.router.navigate(['projects', this.projectId, 'apps', resp.appId]);
}
})
.catch(error => {
this.loading = false;
@ -319,27 +324,25 @@ export class AppCreateComponent implements OnInit, OnDestroy {
}
}
public showSavedDialog(app: App.AsObject): void {
if (app.oidcConfig?.clientSecret !== undefined) {
const dialogRef = this.dialog.open(AppSecretDialogComponent, {
data: app.oidcConfig,
});
dialogRef.afterClosed().subscribe(() => {
this.router.navigate(['projects', this.projectId, 'apps', app.id]);
});
public showSavedDialog(added: AddOIDCAppResponse.AsObject | AddAPIAppResponse.AsObject): void {
let clientSecret = '';
if (added.clientSecret) {
clientSecret = added.clientSecret;
}
else if (app.apiConfig?.clientSecret !== undefined) {
const dialogRef = this.dialog.open(AppSecretDialogComponent, {
data: app.apiConfig,
});
dialogRef.afterClosed().subscribe(() => {
this.router.navigate(['projects', this.projectId, 'apps', app.id]);
});
} else {
this.router.navigate(['projects', this.projectId, 'apps', app.id]);
let clientId = '';
if (added.clientId) {
clientId = added.clientId;
}
const dialogRef = this.dialog.open(AppSecretDialogComponent, {
data: {
clientSecret: clientSecret,
clientId: clientId
}
});
dialogRef.afterClosed().subscribe(() => {
this.router.navigate(['projects', this.projectId, 'apps', added.appId]);
});
}
get name(): AbstractControl | null {

View File

@ -180,7 +180,7 @@
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'APP.TYPE' | translate }}</cnsl-label>
<mat-select formControlName="applicationType">
<mat-select formControlName="appType">
<mat-option *ngFor="let type of oidcAppTypes" [value]="type">
{{ 'APP.OIDC.APPTYPE.'+type | translate }}
</mat-option>

View File

@ -7,7 +7,6 @@ import { BehaviorSubject, from, Observable, of, Subscription } from 'rxjs';
import { catchError, finalize, map } from 'rxjs/operators';
import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component';
import { ChangeType } from 'src/app/modules/changes/changes.component';
import { ProjectType } from 'src/app/modules/project-members/project-members.component';
import { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource';
import { Member } from 'src/app/proto/generated/zitadel/member_pb';
import { GrantedProject, ProjectState } from 'src/app/proto/generated/zitadel/project_pb';
@ -26,7 +25,6 @@ export class GrantedProjectDetailComponent implements OnInit, OnDestroy {
public project!: GrantedProject.AsObject;
public ProjectState: any = ProjectState;
public ProjectType: any = ProjectType;
public ChangeType: any = ChangeType;
private subscription?: Subscription;

View File

@ -2,7 +2,6 @@ import { animate, animateChild, query, stagger, style, transition, trigger } fro
import { SelectionModel } from '@angular/cdk/collections';
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { Router } from '@angular/router';
import { ProjectType } from 'src/app/modules/project-members/project-members.component';
import { Org } from 'src/app/proto/generated/zitadel/org_pb';
import { GrantedProject, ProjectState } from 'src/app/proto/generated/zitadel/project_pb';
import { StorageKey, StorageService } from 'src/app/services/storage.service';
@ -41,7 +40,6 @@ export class GrantedProjectGridComponent implements OnChanges {
public showNewProject: boolean = false;
public ProjectState: any = ProjectState;
public ProjectType: any = ProjectType;
constructor(private storage: StorageService, private router: Router) {
this.selection.changed.subscribe(selection => {

View File

@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthGuard } from 'src/app/guards/auth.guard';
import { RoleGuard } from 'src/app/guards/role.guard';
import { ProjectType } from 'src/app/modules/project-members/project-members.component';
import { ProjectType } from 'src/app/modules/project-members/project-members-datasource';
import { GrantedProjectDetailComponent } from './granted-project-detail/granted-project-detail.component';
import { GrantedProjectsComponent } from './granted-projects.component';

View File

@ -11,23 +11,20 @@
</div>
<div [routerLink]="['/projects', projectId, 'apps', app.id ]" class="app-wrap"
*ngFor="let app of appsSubject | async"
matTooltip="{{'ACTIONS.EDIT' | translate}}">
<cnsl-app-card class="grid-card" matRipple [type]="app.oidcConfig?.applicationType"
*ngFor="let app of appsSubject | async" matTooltip="{{'ACTIONS.EDIT' | translate}}">
<cnsl-app-card class="grid-card" matRipple [type]="app.oidcConfig?.appType"
[isApiApp]="app.apiConfig !== undefined">
{{ app.name.charAt(0)}}
<i *ngIf="app.oidcConfig?.applicationType == OIDCApplicationType.OIDCAPPLICATIONTYPE_NATIVE"
class="las la-mobile"></i>
<i *ngIf="app.oidcConfig?.applicationType == OIDCApplicationType.OIDCAPPLICATIONTYPE_WEB"
class="las la-code"></i>
<i *ngIf="app.oidcConfig?.applicationType == OIDCApplicationType.OIDCAPPLICATIONTYPE_USER_AGENT"
class="las la-code"></i>
<i *ngIf="app.apiConfig" class="las la-code"></i>
<ng-container *ngIf="app.oidcConfig?.appType !== undefined">
<i *ngIf="app.oidcConfig?.appType == OIDCAppType.OIDC_APP_TYPE_NATIVE" class="las la-mobile"></i>
<i *ngIf="app.oidcConfig?.appType == OIDCAppType.OIDC_APP_TYPE_WEB" class="las la-code"></i>
<i *ngIf="app.oidcConfig?.appType == OIDCAppType.OIDC_APP_TYPE_USER_AGENT" class="las la-code"></i>
<i *ngIf="app.apiConfig" class="las la-code"></i>
</ng-container>
</cnsl-app-card>
<span class="name">{{app.name}}</span>
<span *ngIf="app.oidcConfig?.applicationType !== undefined && app.oidcConfig?.applicationType !== null"
class="type">
{{'APP.OIDC.APPTYPE.'+app.oidcConfig?.applicationType | translate}}</span>
<span *ngIf="app.oidcConfig?.appType !== undefined && app.oidcConfig?.appType !== null" class="type">
{{'APP.OIDC.APPTYPE.'+app.oidcConfig?.appType | translate}}</span>
<span *ngIf="app.apiConfig !== undefined" class="type"> API</span>
</div>

View File

@ -1,11 +1,9 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { BehaviorSubject, from, Observable, of } from 'rxjs';
import { catchError, finalize, map } from 'rxjs/operators';
import { BehaviorSubject, from, Observable } from 'rxjs';
import { finalize, map } from 'rxjs/operators';
import { App, OIDCAppType } from 'src/app/proto/generated/zitadel/app_pb';
import { ManagementService } from 'src/app/services/mgmt.service';
import { NATIVE_TYPE, USER_AGENT_TYPE, WEB_TYPE } from '../../../apps/authtypes';
@Component({
selector: 'app-application-grid',
templateUrl: './application-grid.component.html',
@ -18,11 +16,7 @@ export class ApplicationGridComponent implements OnInit {
public appsSubject: BehaviorSubject<App.AsObject[]> = new BehaviorSubject<App.AsObject[]>([]);
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
public OIDCApplicationType: any = OIDCAppType;
public NATIVE_TYPE: any = NATIVE_TYPE;
public WEB_TYPE: any = WEB_TYPE;
public USER_AGENT_TYPE: any = USER_AGENT_TYPE;
public OIDCAppType: any = OIDCAppType;
constructor(private mgmtService: ManagementService) { }
@ -33,11 +27,13 @@ export class ApplicationGridComponent implements OnInit {
public loadApps(): void {
from(this.mgmtService.listApps(this.projectId, 100, 0)).pipe(
map(resp => {
console.log(resp.resultList);
return resp.resultList;
}),
catchError(() => of([])),
// catchError(() => of([])),
finalize(() => this.loadingSubject.next(false)),
).subscribe((apps) => {
console.log(apps);
this.appsSubject.next(apps as App.AsObject[]);
});
}

View File

@ -23,13 +23,13 @@
<button mat-stroked-button color="warn"
[disabled]="isZitadel || (['project.write$', 'project.write:'+ project.id]| hasRole | async) == false"
*ngIf="project?.state === ProjectState.PROJECTSTATE_ACTIVE"
(click)="changeState(ProjectState.PROJECTSTATE_INACTIVE)">{{'PROJECT.TABLE.DEACTIVATE' |
*ngIf="project?.state === ProjectState.PROJECT_STATE_ACTIVE"
(click)="changeState(ProjectState.PROJECT_STATE_INACTIVE)">{{'PROJECT.TABLE.DEACTIVATE' |
translate}}</button>
<button mat-stroked-button color="warn"
[disabled]="isZitadel || (['project.write$', 'project.write:'+ project.id]| hasRole | async) == false"
*ngIf="project?.state === ProjectState.PROJECTSTATE_INACTIVE"
(click)="changeState(ProjectState.PROJECTSTATE_ACTIVE)">{{'PROJECT.TABLE.ACTIVATE' |
*ngIf="project?.state === ProjectState.PROJECT_STATE_INACTIVE"
(click)="changeState(ProjectState.PROJECT_STATE_ACTIVE)">{{'PROJECT.TABLE.ACTIVATE' |
translate}}</button>
<div class="full-width">
@ -67,8 +67,7 @@
</ng-template>
<ng-container *ngIf="isZitadel == false">
<ng-template appHasRole
[appHasRole]="['project.grant.read:' + project.id, 'project.grant.read']">
<ng-template appHasRole [appHasRole]="['project.grant.read:' + project.id, 'project.grant.read']">
<app-card title="{{ 'PROJECT.GRANT.TITLE' | translate }}"
description="{{ 'PROJECT.GRANT.DESCRIPTION' | translate }}">
<app-project-grants
@ -120,7 +119,7 @@
<div class="meta-row">
<span class="first">{{'PROJECT.STATE.TITLE' | translate}}:</span>
<span *ngIf="project && project.state !== undefined" class="state"
[ngClass]="{'active': project.state === ProjectState.PROJECTSTATE_ACTIVE, 'inactive': project.state === ProjectState.PROJECTSTATE_INACTIVE}">{{'PROJECT.STATE.'+project.state
[ngClass]="{'active': project.state === ProjectState.PROJECT_STATE_ACTIVE, 'inactive': project.state === ProjectState.PROJECT_STATE_INACTIVE}">{{'PROJECT.STATE.'+project.state
| translate}}</span>
</div>
</div>
@ -139,4 +138,4 @@
</mat-tab>
</mat-tab-group>
</div>
</app-meta-layout>
</app-meta-layout>

View File

@ -8,7 +8,6 @@ import { BehaviorSubject, from, Observable, of, Subscription } from 'rxjs';
import { catchError, finalize, map } from 'rxjs/operators';
import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component';
import { ChangeType } from 'src/app/modules/changes/changes.component';
import { ProjectType } from 'src/app/modules/project-members/project-members.component';
import { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource';
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
import { App } from 'src/app/proto/generated/zitadel/app_pb';
@ -35,7 +34,6 @@ export class OwnedProjectDetailComponent implements OnInit, OnDestroy {
public appsColumns: string[] = ['name'];
public ProjectState: any = ProjectState;
public ProjectType: any = ProjectType;
public ChangeType: any = ChangeType;
public grid: boolean = true;

View File

@ -10,19 +10,19 @@
<p class="n-items" *ngIf="!loading && selection.selected.length > 0">{{'PROJECT.PAGES.PINNED' | translate}}</p>
<div class="item card" *ngFor="let item of selection.selected; index as i"
(click)="navigateToProject(item.projectId, $event)"
[ngClass]="{ inactive: item.state !== ProjectState.PROJECTSTATE_ACTIVE}">
(click)="navigateToProject(item.id, $event)"
[ngClass]="{ inactive: item.state !== ProjectState.PROJECT_STATE_ACTIVE}">
<div class="text-part">
<span *ngIf="item.changeDate" class="top">{{'PROJECT.PAGES.LASTMODIFIED' | translate}}
{{
item.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'
}}</span>
item.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'
}}</span>
<span class="name" *ngIf="item.name">{{ item.name }}</span>
<span *ngIf="item.changeDate" class="created">{{'PROJECT.PAGES.CREATEDON' | translate}}
{{
item.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'
}}</span>
item.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'
}}</span>
<span class="fill-space"></span>
</div>
@ -34,20 +34,19 @@
<div class="container">
<p class="n-items" *ngIf="!loading && notPinned.length > 0">{{'PROJECT.PAGES.ALL' | translate}}</p>
<div class="item card" *ngFor="let item of notPinned; index as i"
(click)="navigateToProject(item.projectId, $event)"
[ngClass]="{ inactive: item.state !== ProjectState.PROJECTSTATE_ACTIVE}">
<div class="item card" *ngFor="let item of notPinned; index as i" (click)="navigateToProject(item.id, $event)"
[ngClass]="{ inactive: item.state !== ProjectState.PROJECT_STATE_ACTIVE}">
<div class="text-part">
<span *ngIf="item.changeDate" class="top">{{'PROJECT.PAGES.LASTMODIFIED' | translate}}
<span *ngIf="item.details?.changeDate" class="top">{{'PROJECT.PAGES.LASTMODIFIED' | translate}}
{{
item.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'
}}</span>
item.details.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'
}}</span>
<span class="name" *ngIf="item.name">{{ item.name }}</span>
<span *ngIf="item.changeDate" class="created">{{'PROJECT.PAGES.CREATEDON' | translate}}
<span *ngIf="item.details.creationDate" class="created">{{'PROJECT.PAGES.CREATEDON' | translate}}
{{
item.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'
}}</span>
item.details.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'
}}</span>
<span class="fill-space"></span>
</div>
@ -66,7 +65,7 @@
</div>
<ng-template #deleteButton let-key="key">
<button *ngIf="key.projectId !== zitadelProjectId" matTooltip="{{'ACTIONS.DELETE' | translate}}" color="warn"
<button *ngIf="key.id !== zitadelProjectId" matTooltip="{{'ACTIONS.DELETE' | translate}}" color="warn"
(click)="deleteProject($event, key)" class="delete-button" mat-icon-button>
<i class="las la-trash"></i>
</button>

View File

@ -3,7 +3,6 @@ import { SelectionModel } from '@angular/cdk/collections';
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Router } from '@angular/router';
import { ProjectType } from 'src/app/modules/project-members/project-members.component';
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
import { Org } from 'src/app/proto/generated/zitadel/org_pb';
import { Project, ProjectState } from 'src/app/proto/generated/zitadel/project_pb';
@ -51,7 +50,6 @@ export class OwnedProjectGridComponent implements OnChanges {
public showNewProject: boolean = false;
public ProjectState: any = ProjectState;
public ProjectType: any = ProjectType;
@Input() public zitadelProjectId: string = '';
constructor(
private router: Router,

View File

@ -41,16 +41,16 @@
<ng-container matColumnDef="state">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.TABLE.STATE' | translate }} </th>
<td mat-cell *matCellDef="let project"><span
*ngIf="project.state">{{'PROJECT.STATE.'+project.state | translate}}</span></td>
<td mat-cell *matCellDef="let project"><span *ngIf="project.state">{{'PROJECT.STATE.'+project.state
| translate}}</span></td>
</ng-container>
<ng-container matColumnDef="creationDate">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.TABLE.CREATIONDATE' | translate }} </th>
<td mat-cell *matCellDef="let project">
<span
*ngIf="project.creationDate">{{project.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'}}</span>
<span *ngIf="project.details.creationDate">{{project.details.creationDate | timestampToDate |
localizedDate: 'EEE dd. MMM, HH:mm'}}</span>
</td>
</ng-container>
@ -58,8 +58,8 @@
<ng-container matColumnDef="changeDate">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.TABLE.CHANGEDATE' | translate }} </th>
<td mat-cell *matCellDef="let project">
<span
*ngIf="project.changeDate">{{project.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'}}</span>
<span *ngIf="project.details.changeDate">{{project.details.changeDate | timestampToDate |
localizedDate: 'EEE dd. MMM, HH:mm'}}</span>
</td>
</ng-container>

View File

@ -1,20 +1,20 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { GrantedProjectListComponent } from './granted-project-list.component';
import { OwnedProjectListComponent } from './owned-project-list.component';
describe('ProjectListComponent', () => {
let component: GrantedProjectListComponent;
let fixture: ComponentFixture<GrantedProjectListComponent>;
describe('OwnedProjectListComponent', () => {
let component: OwnedProjectListComponent;
let fixture: ComponentFixture<OwnedProjectListComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [GrantedProjectListComponent],
declarations: [OwnedProjectListComponent],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(GrantedProjectListComponent);
fixture = TestBed.createComponent(OwnedProjectListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

View File

@ -109,6 +109,7 @@ export class OwnedProjectListComponent implements OnInit, OnDestroy {
private async getData(limit?: number, offset?: number): Promise<void> {
this.loadingSubject.next(true);
this.mgmtService.listProjects(limit, offset).then(resp => {
console.log(resp.resultList);
this.ownedProjectList = resp.resultList;
if (resp.details?.totalResult) {
this.totalResult = resp.details.totalResult;

View File

@ -1,7 +1,7 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { RoleGuard } from 'src/app/guards/role.guard';
import { ProjectType } from 'src/app/modules/project-members/project-members.component';
import { ProjectType } from 'src/app/modules/project-members/project-members-datasource';
import { OwnedProjectsComponent } from './owned-projects.component';

View File

@ -3,7 +3,6 @@ import { MatDialog } from '@angular/material/dialog';
import { PageEvent } from '@angular/material/paginator';
import { MatSelectChange } from '@angular/material/select';
import { ActivatedRoute } from '@angular/router';
import { ProjectType } from 'src/app/modules/project-members/project-members.component';
import { Member } from 'src/app/proto/generated/zitadel/member_pb';
import { GrantedProject, ProjectGrantState, Role } from 'src/app/proto/generated/zitadel/project_pb';
import { ManagementService } from 'src/app/services/mgmt.service';
@ -27,7 +26,6 @@ export class ProjectGrantDetailComponent {
public projectid: string = '';
public grantid: string = '';
public projectType: ProjectType = ProjectType.PROJECTTYPE_OWNED;
public disabled: boolean = false;
public isZitadel: boolean = false;

View File

@ -19,7 +19,7 @@
<cnsl-label>{{ 'USER.PROFILE.USERNAME' | translate }}*</cnsl-label>
<input cnslInput formControlName="userName" required
[ngStyle]="{'padding-right': suffixPadding ? suffixPadding : '10px'}" />
<span #suffix *ngIf="envSuffixLabel" cnslSuffix (click)="logsuff()">{{envSuffixLabel}}</span>
<span #suffix *ngIf="envSuffixLabel" cnslSuffix>{{envSuffixLabel}}</span>
<span cnsl-error *ngIf="userName?.invalid && userName?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
@ -88,8 +88,8 @@
</cnsl-form-field>
</div>
<div class="btn-container">
<button color="primary" [disabled]="userForm.invalid" type="submit"
mat-raised-button>{{ 'ACTIONS.CREATE' | translate }}</button>
<button color="primary" [disabled]="userForm.invalid" type="submit" mat-raised-button>{{ 'ACTIONS.CREATE' |
translate }}</button>
</div>
</form>
</app-detail-layout>

View File

@ -1,7 +1,9 @@
import { ChangeDetectorRef, Component, OnDestroy, ViewChild } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { Subscription } from 'rxjs';
import parsePhoneNumber from 'libphonenumber-js';
import { Subject } from 'rxjs';
import { debounceTime, takeUntil } from 'rxjs/operators';
import { AddHumanUserRequest } from 'src/app/proto/generated/zitadel/management_pb';
import { Domain } from 'src/app/proto/generated/zitadel/org_pb';
import { Gender } from 'src/app/proto/generated/zitadel/user_pb';
@ -37,7 +39,7 @@ export class UserCreateComponent implements OnDestroy {
public languages: string[] = ['de', 'en'];
public userForm!: FormGroup;
public envSuffixLabel: string = '';
private sub: Subscription = new Subscription();
private destroyed$: Subject<void> = new Subject();
public userLoginMustBeDomain: boolean = false;
public loading: boolean = false;
@ -92,15 +94,23 @@ export class UserCreateComponent implements OnDestroy {
firstName: ['', Validators.required],
lastName: ['', Validators.required],
nickName: [''],
gender: [Gender.GENDER_UNSPECIFIED],
gender: [],
preferredLanguage: [''],
phone: [''],
});
}
public logsuff(): void {
console.log((this.suffix.nativeElement as HTMLElement), (this.suffix.nativeElement as HTMLElement).offsetWidth);
this.userForm.controls['phone'].valueChanges.pipe(
takeUntil(this.destroyed$),
debounceTime(300)).subscribe(value => {
const phoneNumber = parsePhoneNumber(value ?? '', 'CH');
if (phoneNumber) {
const formmatted = phoneNumber.formatInternational();
const country = phoneNumber.country;
if (this.phone && country && this.phone.value && this.phone.value !== formmatted) {
this.phone.setValue(formmatted);
}
}
});
}
public createUser(): void {
@ -119,8 +129,11 @@ export class UserCreateComponent implements OnDestroy {
humanReq.setUserName(this.userName?.value);
humanReq.setProfile(profileReq);
humanReq.setEmail(this.email?.value);
humanReq.setPhone(this.phone?.value);
humanReq.setEmail(new AddHumanUserRequest.Email().setEmail(this.email?.value));
if (this.phone && this.phone.value) {
humanReq.setPhone(new AddHumanUserRequest.Phone().setPhone(this.phone.value));
}
this.mgmtService
.addHumanUser(humanReq)
@ -136,7 +149,8 @@ export class UserCreateComponent implements OnDestroy {
}
ngOnDestroy(): void {
this.sub.unsubscribe();
this.destroyed$.next();
this.destroyed$.complete();
}
public get email(): AbstractControl | null {

View File

@ -0,0 +1,50 @@
<h1 mat-dialog-title>
<span class="title">{{'USER.CODEDIALOG.TITLE' | translate}} {{data?.number}}</span>
</h1>
<p class="desc">{{'USER.CODEDIALOG.DESCRIPTION' | translate}}</p>
<div mat-dialog-content>
<div class="type-selection">
<button class="otp" (click)="selectType(AuthFactorType.OTP)">
<mat-icon class="icon" svgIcon="mdi_radar"></mat-icon>
<span>{{'USER.MFA.OTP' | translate}}</span>
</button>
<button class="u2f" (click)="selectType(AuthFactorType.U2F)">
<i class="las la-fingerprint"></i>
<span>{{'USER.MFA.U2F' | translate}}</span>
</button>
</div>
<div class="otp" *ngIf="selectedType == AuthFactorType.OTP">
<p>{{'USER.MFA.OTP_DIALOG_DESCRIPTION' | translate}}</p>
<div class="qrcode-wrapper">
<qrcode *ngIf="otpurl" class="qrcode" [qrdata]="otpurl" [width]="150" [errorCorrectionLevel]="'M'"></qrcode>
</div>
<cnsl-form-field class="form-field" label="Access Code" required="true">
<cnsl-label>Code</cnsl-label>
<input cnslInput [(ngModel)]="otpcode" />
</cnsl-form-field>
</div>
<div class="u2f" *ngIf="selectedType == AuthFactorType.U2F">
<p>{{'USER.MFA.U2F_DIALOG_DESCRIPTION' | translate}}</p>
<cnsl-form-field class="form-field" label="Name" required="true">
<cnsl-label>{{'USER.MFA.U2F_NAME' | translate}}</cnsl-label>
<input cnslInput [(ngModel)]="u2fname" required (keydown.enter)="u2fname ? submitU2F() : null" />
</cnsl-form-field>
<mat-spinner diameter="30" *ngIf="u2fLoading"></mat-spinner>
<p class="error">{{u2fError}}</p>
</div>
</div>
<div mat-dialog-actions class="action">
<button cdkFocusInitial color="primary" mat-button class="ok-button" (click)="closeDialog()">
{{'ACTIONS.CLOSE' | translate}}
</button>
<button cdkFocusInitial color="primary" mat-raised-button class="ok-button" (click)="submitAuth()">
{{'ACTIONS.CREATE' | translate}}
</button>
</div>

View File

@ -0,0 +1,35 @@
.type-selection {
display: flex;
margin: 0 -0.5rem;
.otp,
.u2f {
flex: 1;
min-height: 100px;
border-radius: .5rem;
border: 1px solid var(--grey);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 1rem;
margin: .5rem;
}
}
.formfield {
width: 100%;
}
.action {
display: flex;
justify-content: flex-end;
.ok-button {
margin-left: .5rem;
}
button {
border-radius: .5rem;
}
}

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { AuthFactorDialogComponent } from './auth-factor-dialog.component';
describe('CodeDialogComponent', () => {
let component: AuthFactorDialogComponent;
let fixture: ComponentFixture<AuthFactorDialogComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [AuthFactorDialogComponent],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AuthFactorDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,138 @@
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { TranslateService } from '@ngx-translate/core';
import { take } from 'rxjs/operators';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ToastService } from 'src/app/services/toast.service';
import { _base64ToArrayBuffer } from '../../u2f-util';
import { _arrayBufferToBase64 } from '../u2f_util';
export enum AuthFactorType {
OTP,
U2F,
}
@Component({
selector: 'app-auth-factor-dialog',
templateUrl: './auth-factor-dialog.component.html',
styleUrls: ['./auth-factor-dialog.component.scss'],
})
export class AuthFactorDialogComponent {
public otpurl: string = '';
public otpcode: string = '';
public u2fname: string = '';
public u2fCredentialOptions!: CredentialCreationOptions;
public u2fLoading: boolean = false;
public u2fError: string = '';
AuthFactorType: any = AuthFactorType;
selectedType!: AuthFactorType;
constructor(
private authService: GrpcAuthService,
private toast: ToastService,
private translate: TranslateService,
public dialogRef: MatDialogRef<AuthFactorDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
) { }
closeDialog(code: string = ''): void {
this.dialogRef.close(code);
}
public selectType(type: AuthFactorType): void {
if (type == AuthFactorType.OTP) {
this.authService.addMyMultiFactorOTP().then((otpresp) => {
this.otpurl = otpresp.url;
}, error => {
this.toast.showError(error);
});
} else if (type == AuthFactorType.U2F) {
this.authService.addMyMultiFactorU2F().then((u2fresp) => {
const credOptions: CredentialCreationOptions = JSON.parse(atob(u2fresp.key?.publicKey as string));
if (credOptions.publicKey?.challenge) {
credOptions.publicKey.challenge = _base64ToArrayBuffer(credOptions.publicKey.challenge as any);
credOptions.publicKey.user.id = _base64ToArrayBuffer(credOptions.publicKey.user.id as any);
if (credOptions.publicKey.excludeCredentials) {
credOptions.publicKey.excludeCredentials.map(cred => {
cred.id = _base64ToArrayBuffer(cred.id as any);
return cred;
});
}
this.u2fCredentialOptions = credOptions;
}
}, error => {
this.toast.showError(error);
});
}
}
public submitAuth() {
if (this.selectedType == AuthFactorType.OTP) {
this.submitOTP();
} else if (this.selectedType == AuthFactorType.U2F) {
this.submitU2F();
}
}
public submitOTP(): void {
this.authService.verifyMyMultiFactorOTP(this.otpcode).then(() => {
this.dialogRef.close(true);
}, error => {
this.dialogRef.close(false);
});
}
public submitU2F(): void {
if (this.u2fname && this.u2fCredentialOptions.publicKey) {
// this.data.credOptions.publicKey.rp.id = 'localhost';
navigator.credentials.create(this.data.credOptions).then((resp) => {
if (resp &&
(resp as any).response.attestationObject &&
(resp as any).response.clientDataJSON &&
(resp as any).rawId) {
const attestationObject = (resp as any).response.attestationObject;
const clientDataJSON = (resp as any).response.clientDataJSON;
const rawId = (resp as any).rawId;
const data = JSON.stringify({
id: resp.id,
rawId: _arrayBufferToBase64(rawId),
type: resp.type,
response: {
attestationObject: _arrayBufferToBase64(attestationObject),
clientDataJSON: _arrayBufferToBase64(clientDataJSON),
},
});
const base64 = btoa(data);
this.authService.verifyMyMultiFactorU2F(base64, this.u2fname).then(() => {
this.translate.get('USER.MFA.U2F_SUCCESS').pipe(take(1)).subscribe(msg => {
this.toast.showInfo(msg);
});
this.dialogRef.close(true);
this.u2fLoading = false;
}).catch(error => {
this.u2fLoading = false;
this.toast.showError(error);
});
} else {
this.u2fLoading = false;
this.translate.get('USER.MFA.U2F_ERROR').pipe(take(1)).subscribe(msg => {
this.toast.showInfo(msg);
});
this.dialogRef.close(true);
}
}).catch(error => {
this.u2fLoading = false;
this.u2fError = error;
this.toast.showInfo(error.message);
});
}
}
}

View File

@ -27,9 +27,10 @@
</div>
</app-card>
<app-card *ngIf="user" class="app-card" title="{{ 'USER.PROFILE.TITLE' | translate }}">
<app-detail-form [genders]="genders" [languages]="languages" [username]="user.userName" [user]="user.human"
(changedLanguage)="changedLanguage($event)" (submitData)="saveProfile($event)">
<app-card *ngIf="user && user.human?.profile?.userName" class=" app-card"
title="{{ 'USER.PROFILE.TITLE' | translate }}">
<app-detail-form [genders]="genders" [languages]="languages" [username]="user.human?.profile?.userName"
[user]="user.human" (changedLanguage)="changedLanguage($event)" (submitData)="saveProfile($event)">
</app-detail-form>
</app-card>

View File

@ -8,8 +8,7 @@ import { Email, Gender, Phone, Profile, User, UserState } from 'src/app/proto/ge
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ToastService } from 'src/app/services/toast.service';
import { EditDialogType } from '../user-detail/user-detail.component';
import { EditDialogComponent } from './edit-dialog/edit-dialog.component';
import { EditDialogComponent, EditDialogType } from './edit-dialog/edit-dialog.component';
@Component({
selector: 'app-auth-user-detail',
@ -169,7 +168,7 @@ export class AuthUserDetailComponent implements OnDestroy {
labelKey: 'ACTIONS.NEWVALUE',
titleKey: 'USER.LOGINMETHODS.PHONE.EDITTITLE',
descriptionKey: 'USER.LOGINMETHODS.PHONE.EDITDESC',
value: this.user.human?.phone,
value: this.user.human?.phone?.phone,
},
width: '400px',
});
@ -188,7 +187,7 @@ export class AuthUserDetailComponent implements OnDestroy {
labelKey: 'ACTIONS.NEWVALUE',
titleKey: 'USER.LOGINMETHODS.EMAIL.EDITTITLE',
descriptionKey: 'USER.LOGINMETHODS.EMAIL.EDITDESC',
value: this.user.human?.email,
value: this.user.human?.email?.email,
},
width: '400px',
});

View File

@ -39,14 +39,10 @@
</table>
</app-refresh-table>
<div class="add-row">
<button class="button" *ngIf="otpAvailable" (click)="addOTP()" mat-stroked-button color="primary"
<button class="button" *ngIf="otpAvailable" (click)="addAuthFactor()" mat-raised-button color="primary"
matTooltip="{{'ACTIONS.NEW' | translate}}">
<mat-icon class="icon" svgIcon="mdi_radar"></mat-icon>{{'USER.MFA.OTP' | translate}}
</button>
<button class="button" (click)="addU2F()" mat-stroked-button color="primary"
matTooltip="{{'ACTIONS.NEW' | translate}}">
<i class="las la-fingerprint"></i>
{{'USER.MFA.U2F' | translate}}
<mat-icon class="icon">add</mat-icon>
{{'USER.MFA.OTP' | translate}}
</button>
</div>
<div class="table-wrapper">

View File

@ -8,9 +8,7 @@ import { AuthFactor, AuthFactorState } from 'src/app/proto/generated/zitadel/use
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ToastService } from 'src/app/services/toast.service';
import { _base64ToArrayBuffer } from '../../u2f-util';
import { DialogOtpComponent } from '../dialog-otp/dialog-otp.component';
import { DialogU2FComponent, U2FComponentDestination } from '../dialog-u2f/dialog-u2f.component';
import { AuthFactorDialogComponent } from '../auth-factor-dialog/auth-factor-dialog.component';
export interface WebAuthNOptions {
challenge: string;
@ -55,59 +53,17 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
this.loadingSubject.complete();
}
public addOTP(): void {
this.service.addMyMultiFactorOTP().then((otpresp) => {
const otp = otpresp;
const dialogRef = this.dialog.open(DialogOtpComponent, {
data: otp.url,
width: '400px',
});
dialogRef.afterClosed().subscribe((code) => {
if (code) {
this.service.verifyMyMultiFactorOTP(code).then(() => {
this.getMFAs();
});
}
});
}, error => {
this.toast.showError(error);
public addAuthFactor(): void {
const dialogRef = this.dialog.open(AuthFactorDialogComponent, {
width: '400px',
});
}
public addU2F(): void {
this.service.addMyMultiFactorU2F().then((u2fresp) => {
const credOptions: CredentialCreationOptions = JSON.parse(atob(u2fresp.key?.publicKey as string));
if (credOptions.publicKey?.challenge) {
credOptions.publicKey.challenge = _base64ToArrayBuffer(credOptions.publicKey.challenge as any);
credOptions.publicKey.user.id = _base64ToArrayBuffer(credOptions.publicKey.user.id as any);
if (credOptions.publicKey.excludeCredentials) {
credOptions.publicKey.excludeCredentials.map(cred => {
cred.id = _base64ToArrayBuffer(cred.id as any);
return cred;
});
}
console.log(credOptions);
const dialogRef = this.dialog.open(DialogU2FComponent, {
width: '400px',
data: {
credOptions,
type: U2FComponentDestination.MFA,
},
});
dialogRef.afterClosed().subscribe(done => {
if (done) {
this.getMFAs();
} else {
this.getMFAs();
}
dialogRef.afterClosed().subscribe((code) => {
if (code) {
this.service.verifyMyMultiFactorOTP(code).then(() => {
this.getMFAs();
});
}
}, error => {
this.toast.showError(error);
});
}

View File

@ -1,18 +0,0 @@
<h1 mat-dialog-title>{{'USER.MFA.OTP_DIALOG_TITLE' | translate}}</h1>
<div mat-dialog-content>
<p>{{'USER.MFA.OTP_DIALOG_DESCRIPTION' | translate}}</p>
<div class="qrcode-wrapper">
<qrcode *ngIf="data" class="qrcode" [qrdata]="data" [width]="150" [errorCorrectionLevel]="'M'"></qrcode>
</div>
<cnsl-form-field class="form-field" label="Access Code" required="true">
<cnsl-label>Code</cnsl-label>
<input cnslInput [(ngModel)]="code" />
</cnsl-form-field>
</div>
<div mat-dialog-actions class="action">
<button mat-button (click)="closeDialog()"><span translate>ACTIONS.CLOSE</span></button>
<button mat-raised-button class="ok-button" color="primary" (click)="closeDialogWithCode()"><span
translate>ACTIONS.OK</span>
</button>
</div>

View File

@ -1,22 +0,0 @@
.qrcode-wrapper {
display: flex;
align-content: center;
.qrcode {
margin: 1rem auto;
}
}
.form-field {
width: 100%;
}
.action {
display: flex;
justify-content: flex-end;
button {
margin-left: .5rem;
border-radius: .5rem;
}
}

View File

@ -1,21 +0,0 @@
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
@Component({
selector: 'app-dialog-otp',
templateUrl: './dialog-otp.component.html',
styleUrls: ['./dialog-otp.component.scss'],
})
export class DialogOtpComponent {
public code: string = '';
constructor(public dialogRef: MatDialogRef<DialogOtpComponent>,
@Inject(MAT_DIALOG_DATA) public data: string) { }
public closeDialog(): void {
this.dialogRef.close();
}
public closeDialogWithCode(): void {
this.dialogRef.close(this.code);
}
}

View File

@ -5,17 +5,7 @@ import { take } from 'rxjs/operators';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ToastService } from 'src/app/services/toast.service';
export function _arrayBufferToBase64(buffer: any): string {
let binary = '';
const bytes = new Uint8Array(buffer);
const len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary).replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}
import { _arrayBufferToBase64 } from '../u2f_util';
export enum U2FComponentDestination {
MFA = 'mfa',

View File

@ -4,8 +4,9 @@
<p class="desc">{{data.descriptionKey | translate}}</p>
<div mat-dialog-content>
<cnsl-form-field class="formfield">
<cnsl-label>{{data.labelKey | translate }}</cnsl-label>
<input cnslInput [(ngModel)]="value" (keydown.enter)="value ? closeDialogWithValue(value) : null" />
<cnsl-label>{{data.labelKey | translate }} <span *ngIf="phoneCountry">({{ phoneCountry }})</span></cnsl-label>
<input cnslInput [(ngModel)]="value" (change)="changeValue($event)"
(keydown.enter)="value ? closeDialogWithValue(value) : null" />
</cnsl-form-field>
</div>
<div mat-dialog-actions class="action">

View File

@ -1,5 +1,11 @@
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { parsePhoneNumber } from 'libphonenumber-js';
export enum EditDialogType {
PHONE = 1,
EMAIL = 2,
}
@Component({
selector: 'app-edit-email-dialog',
@ -8,9 +14,26 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
})
export class EditDialogComponent {
public value: string = '';
public isPhone: boolean = false;
public phoneCountry: string = 'CH';
constructor(public dialogRef: MatDialogRef<EditDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any) {
this.value = data.value;
if (data.type == EditDialogType.PHONE) {
this.isPhone = true;
}
}
changeValue(change: any) {
const value = change.target.value;
if (this.isPhone && value) {
const phoneNumber = parsePhoneNumber(value ?? '', 'CH');
if (phoneNumber) {
const formmatted = phoneNumber.formatInternational();
this.phoneCountry = phoneNumber.country || '';
this.value = formmatted;
}
}
}
closeDialog(email: string = ''): void {

View File

@ -0,0 +1,12 @@
export function _arrayBufferToBase64(buffer: any): string {
let binary = '';
const bytes = new Uint8Array(buffer);
const len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary).replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}

View File

@ -18,10 +18,10 @@
<div class="method-row">
<div class="left">
<span class="label">{{ 'USER.EMAIL' | translate }}</span>
<span class="name">{{human?.email}}</span>
<span *ngIf="human?.profile.email.isEmailVerified" class="contact-state verified">{{'USER.EMAILVERIFIED' |
<span class="name">{{human?.email?.email}}</span>
<span *ngIf="human?.email?.isEmailVerified" class="contact-state verified">{{'USER.EMAILVERIFIED' |
translate}}</span>
<div *ngIf="!human?.profile.email.isEmailVerified" class="block">
<div *ngIf="!human?.email.isEmailVerified" class="block">
<span class="contact-state notverified">{{'USER.NOTVERIFIED' | translate}}</span>
<ng-container *ngIf="human?.email">
@ -45,13 +45,13 @@
<div class="method-row">
<div class="left">
<span class="label">{{ 'USER.PHONE' | translate }}</span>
<span class="name">{{human?.phone ? human.phone : ('USER.PHONEEMPTY' | translate)}}</span>
<span *ngIf="human?.profile.phone.isPhoneVerified" class="contact-state verified">{{'USER.PHONEVERIFIED' |
<span class="name">{{human?.phone?.phone ? human.phone?.phone : ('USER.PHONEEMPTY' | translate)}}</span>
<span *ngIf="human?.phone.isPhoneVerified" class="contact-state verified">{{'USER.PHONEVERIFIED' |
translate}}</span>
<div *ngIf="!human?.profile.phone.isPhoneVerified" class="block">
<div *ngIf="human.phone?.phone && !human?.phone.isPhoneVerified" class="block">
<span class="contact-state notverified">{{'USER.NOTVERIFIED' | translate}}</span>
<ng-container *ngIf="human?.phone">
<ng-container *ngIf="human?.phone?.phone">
<a *ngIf="!disablePhoneCode && canWrite" class="verify"
matTooltip="{{'USER.LOGINMETHODS.ENTERCODE_DESC' | translate}}"
(click)="enterCode()">{{'USER.LOGINMETHODS.ENTERCODE' | translate}}</a>
@ -64,7 +64,7 @@
</div>
<div class="right">
<button matTooltip="{{'ACTIONS.DELETE' | translate}}" *ngIf="human && human.phone" color="warn"
<button matTooltip="{{'ACTIONS.DELETE' | translate}}" *ngIf="human && human.phone?.phone" color="warn"
(click)="emitDeletePhone()" mat-icon-button>
<i class="las la-trash"></i>
</button>

View File

@ -4,7 +4,7 @@ import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.com
import { Human, UserState } from 'src/app/proto/generated/zitadel/user_pb';
import { CodeDialogComponent } from '../auth-user-detail/code-dialog/code-dialog.component';
import { EditDialogType } from '../user-detail/user-detail.component';
import { EditDialogType } from '../auth-user-detail/edit-dialog/edit-dialog.component';
@Component({
selector: 'app-contact',

View File

@ -1,7 +1,7 @@
import { Component, EventEmitter, Input, OnChanges, OnDestroy, Output } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Subscription } from 'rxjs';
import { Gender, User } from 'src/app/proto/generated/zitadel/user_pb';
import { Gender, Human, User } from 'src/app/proto/generated/zitadel/user_pb';
@Component({
@ -11,7 +11,7 @@ import { Gender, User } from 'src/app/proto/generated/zitadel/user_pb';
})
export class DetailFormComponent implements OnDestroy, OnChanges {
@Input() public username!: string;
@Input() public user!: User;
@Input() public user!: Human.AsObject;
@Input() public disabled: boolean = false;
@Input() public genders: Gender[] = [];
@Input() public languages: string[] = ['de', 'en'];
@ -47,7 +47,7 @@ export class DetailFormComponent implements OnDestroy, OnChanges {
preferredLanguage: [{ value: '', disabled: this.disabled }],
});
this.profileForm.patchValue({ userName: this.username, ...this.user });
this.profileForm.patchValue({ userName: this.username, ...this.user.profile });
if (this.preferredLanguage) {
this.sub = this.preferredLanguage.valueChanges.subscribe(value => {

View File

@ -19,6 +19,7 @@ import { CardModule } from 'src/app/modules/card/card.module';
import { ChangesModule } from 'src/app/modules/changes/changes.module';
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module';
import { InputModule } from 'src/app/modules/input/input.module';
import { MachineKeysModule } from 'src/app/modules/machine-keys/machine-keys.module';
import { MetaLayoutModule } from 'src/app/modules/meta-layout/meta-layout.module';
import { PasswordComplexityViewModule } from 'src/app/modules/password-complexity-view/password-complexity-view.module';
import { RefreshTableModule } from 'src/app/modules/refresh-table/refresh-table.module';
@ -29,11 +30,11 @@ import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.mod
import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe/localized-date-pipe.module';
import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe/timestamp-to-date-pipe.module';
import { AuthFactorDialogComponent } from './auth-user-detail/auth-factor-dialog/auth-factor-dialog.component';
import { AuthPasswordlessComponent } from './auth-user-detail/auth-passwordless/auth-passwordless.component';
import { AuthUserDetailComponent } from './auth-user-detail/auth-user-detail.component';
import { AuthUserMfaComponent } from './auth-user-detail/auth-user-mfa/auth-user-mfa.component';
import { CodeDialogComponent } from './auth-user-detail/code-dialog/code-dialog.component';
import { DialogOtpComponent } from './auth-user-detail/dialog-otp/dialog-otp.component';
import { DialogU2FComponent } from './auth-user-detail/dialog-u2f/dialog-u2f.component';
import { EditDialogComponent } from './auth-user-detail/edit-dialog/edit-dialog.component';
import { ResendEmailDialogComponent } from './auth-user-detail/resend-email-dialog/resend-email-dialog.component';
@ -48,13 +49,11 @@ import { UserDetailRoutingModule } from './user-detail-routing.module';
import { PasswordlessComponent } from './user-detail/passwordless/passwordless.component';
import { UserDetailComponent } from './user-detail/user-detail.component';
import { UserMfaComponent } from './user-detail/user-mfa/user-mfa.component';
import { MachineKeysModule } from 'src/app/modules/machine-keys/machine-keys.module';
@NgModule({
declarations: [
AuthUserDetailComponent,
UserDetailComponent,
DialogOtpComponent,
EditDialogComponent,
AuthUserMfaComponent,
AuthPasswordlessComponent,
@ -68,6 +67,7 @@ import { MachineKeysModule } from 'src/app/modules/machine-keys/machine-keys.mod
ContactComponent,
ResendEmailDialogComponent,
DialogU2FComponent,
AuthFactorDialogComponent,
],
imports: [
UserDetailRoutingModule,

View File

@ -12,14 +12,9 @@ import { Email, Gender, Machine, Phone, Profile, User, UserState } from 'src/app
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
import { EditDialogComponent } from '../auth-user-detail/edit-dialog/edit-dialog.component';
import { EditDialogComponent, EditDialogType } from '../auth-user-detail/edit-dialog/edit-dialog.component';
import { ResendEmailDialogComponent } from '../auth-user-detail/resend-email-dialog/resend-email-dialog.component';
export enum EditDialogType {
PHONE = 1,
EMAIL = 2,
}
@Component({
selector: 'app-user-detail',
templateUrl: './user-detail.component.html',
@ -87,6 +82,7 @@ export class UserDetailComponent implements OnInit {
}
public saveProfile(profileData: Profile.AsObject): void {
console.log(profileData);
if (this.user.human) {
this.user.human.profile = profileData;
this.mgmtUserService
@ -252,7 +248,8 @@ export class UserDetailComponent implements OnInit {
labelKey: 'ACTIONS.NEWVALUE',
titleKey: 'USER.LOGINMETHODS.PHONE.EDITTITLE',
descriptionKey: 'USER.LOGINMETHODS.PHONE.EDITDESC',
value: this.user.human?.phone,
value: this.user.human?.phone?.phone,
type: EditDialogType.PHONE,
},
width: '400px',
});
@ -271,7 +268,8 @@ export class UserDetailComponent implements OnInit {
labelKey: 'ACTIONS.NEWVALUE',
titleKey: 'USER.LOGINMETHODS.EMAIL.EDITTITLE',
descriptionKey: 'USER.LOGINMETHODS.EMAIL.EDITDESC',
value: this.user.human?.email,
value: this.user.human?.email?.email,
type: EditDialogType.EMAIL,
},
width: '400px',
});

View File

@ -32,9 +32,10 @@
<td mat-cell *matCellDef="let user" class="selection">
<mat-checkbox [disabled]="disabled" color="primary" (click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(user) : null" [checked]="selection.isSelected(user)">
<app-avatar
*ngIf="user[type] && user[type].displayName && user[type]?.firstName && user[type]?.lastName; else cog"
class="avatar" [name]="user[type].displayName" [size]="32">
*ngIf="user.human && user.human.profile.displayName && user.human?.profile.firstName && user.human?.profile.lastName; else cog"
class="avatar" [name]="user.human.profile.displayName" [size]="32">
</app-avatar>
<ng-template #cog>
<div class="sa-icon">
@ -45,28 +46,6 @@
</td>
</ng-container>
<ng-container matColumnDef="firstname">
<th mat-header-cell *matHeaderCellDef
[ngClass]="{'search-active': this.userSearchKey == UserListSearchKey.FIRST_NAME}">
{{ 'USER.PROFILE.FIRSTNAME' | translate }}
<template [ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{key: UserListSearchKey.FIRST_NAME}"></template>
</th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id ]: null">
{{user[type]?.firstName}} </td>
</ng-container>
<ng-container matColumnDef="lastname">
<th mat-header-cell *matHeaderCellDef
[ngClass]="{'search-active': this.userSearchKey == UserListSearchKey.LAST_NAME}">
{{ 'USER.PROFILE.LASTNAME' | translate }}
<template [ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{key: UserListSearchKey.LAST_NAME}"></template>
</th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id ]: null">
{{user[type]?.lastName}} </td>
</ng-container>
<ng-container matColumnDef="displayName">
<th mat-header-cell *matHeaderCellDef
[ngClass]="{'search-active': this.userSearchKey == UserListSearchKey.USERSEARCHKEY_DISPLAY_NAME}">
@ -75,7 +54,8 @@
[ngTemplateOutletContext]="{key: UserListSearchKey.USERSEARCHKEY_DISPLAY_NAME}"></template>
</th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id ]: null">
{{user[type]?.displayName}} </td>
{{user.human?.profile?.displayName}}
</td>
</ng-container>
<ng-container matColumnDef="name">
@ -83,13 +63,17 @@
{{ 'USER.MACHINE.NAME' | translate }}
</th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id ]: null">
{{user[type]?.name}} </td>
<span *ngIf="user.human?.name">{{user.human?.name}}</span>
<span *ngIf="user.machine?.name">{{user.machine?.name}}</span>
</td>
</ng-container>
<ng-container matColumnDef="description">
<th mat-header-cell *matHeaderCellDef> {{ 'USER.MACHINE.DESCRIPTION' | translate }} </th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id ]: null">
{{user[type]?.description}} </td>
<span *ngIf="user.human?.description">{{user.human?.description}}</span>
<span *ngIf="user.machine?.description">{{user.machine?.description}}</span>
</td>
</ng-container>
<ng-container matColumnDef="username">
@ -111,15 +95,17 @@
[ngTemplateOutletContext]="{key: UserListSearchKey.EMAIL}"></template>
</th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id ]: null">
{{user[type]?.email}} </td>
<span *ngIf="user.human?.email?.email">{{user.human?.email.email}}</span>
</td>
</ng-container>
<ng-container matColumnDef="state">
<th mat-header-cell *matHeaderCellDef> {{ 'USER.DATA.STATE' | translate }} </th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id ]: null">
<span class="state"
[ngClass]="{'active': user.state === UserState.USERSTATE_ACTIVE, 'inactive': user.state === UserState.USERSTATE_INACTIVE}">{{
'USER.DATA.STATE'+user.state | translate }}</span>
[ngClass]="{'active': user.state === UserState.USER_STATE_ACTIVE, 'inactive': user.state === UserState.USER_STATE_INACTIVE}">
{{ 'USER.DATA.STATE'+user.state | translate }}
</span>
</td>
</ng-container>

View File

@ -180,6 +180,8 @@ export class UserTableComponent implements OnInit {
}
this.userService.listUsers(limit, offset, [query]).then(resp => {
console.log(resp);
if (resp.details?.totalResult) {
this.totalResult = resp.details?.totalResult;
}

View File

@ -11,8 +11,10 @@ export class TimestampToDatePipe implements PipeTransform {
}
private dateFromTimestamp(date: Timestamp.AsObject): any {
const ts: Date = new Date(date.seconds * 1000 + date.nanos / 1000 / 1000);
return ts;
if (date?.seconds !== undefined && date?.nanos !== undefined) {
const ts: Date = new Date(date.seconds * 1000 + date.nanos / 1000 / 1000);
return ts;
}
}
}

View File

@ -1556,7 +1556,6 @@ export class ManagementService {
if (offset) {
query.setOffset(offset);
}
req.setQuery(query);
if (queryList) {
req.setQueriesList(queryList);

View File

@ -1,7 +1,7 @@
{
"authServiceUrl": "https://api.zitadel.dev",
"mgmtServiceUrl": "https://api.zitadel.dev",
"adminServiceUrl":"https://api.zitadel.dev",
"issuer": "https://issuer.zitadel.dev",
"clientid": "70669160379706195@zitadel"
"authServiceUrl": "https://api.zitadel.io",
"mgmtServiceUrl": "https://api.zitadel.io",
"adminServiceUrl":"https://api.zitadel.io",
"issuer": "https://issuer.zitadel.io",
"clientid": "98804911164221523@zitadel"
}