feat(console): replace wget with curl, user login prompt, warn dialog (#387)

* remove evil wget, add warn-dialog, login hint

* i18n

* reference ngsw.js

* update clientid

* fix warn dialog

* lint

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

Co-authored-by: Florian Forster <florian@caos.ch>
This commit is contained in:
Max Peintner 2020-07-08 10:00:15 +02:00 committed by GitHub
parent a5bfd085a1
commit c621fdee41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 187 additions and 31 deletions

View File

@ -10,10 +10,14 @@ rm -rf $GEN_PATH
echo "Create folders" echo "Create folders"
mkdir -p $GEN_PATH mkdir -p $GEN_PATH
targetcurl () {
mkdir -p $1 && cd $1 && { curl -O $2; cd -; }
}
echo "Download additional protofiles" echo "Download additional protofiles"
wget -P tmp/validate https://raw.githubusercontent.com/envoyproxy/protoc-gen-validate/v0.4.0/validate/validate.proto targetcurl tmp/validate https://raw.githubusercontent.com/envoyproxy/protoc-gen-validate/v0.4.0/validate/validate.proto
wget -P tmp/protoc-gen-swagger/options https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/v1.14.6/protoc-gen-swagger/options/annotations.proto targetcurl tmp/protoc-gen-swagger/options https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/v1.14.6/protoc-gen-swagger/options/annotations.proto
wget -P tmp/protoc-gen-swagger/options https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/v1.14.6/protoc-gen-swagger/options/openapiv2.proto targetcurl tmp/protoc-gen-swagger/options https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/v1.14.6/protoc-gen-swagger/options/openapiv2.proto
echo "Generate grpc" echo "Generate grpc"

View File

@ -103,7 +103,7 @@ export let authConfig = {
MatMenuModule, MatMenuModule,
MatSnackBarModule, MatSnackBarModule,
AvatarModule, AvatarModule,
ServiceWorkerModule.register('ngsw-config.json', { enabled: environment.production }), ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
], ],
providers: [ providers: [
ThemeService, ThemeService,

View File

@ -10,7 +10,7 @@
<button (click)="editUserProfile()" mat-stroked-button>{{'USER.EDITACCOUNT' | translate}}</button> <button (click)="editUserProfile()" mat-stroked-button>{{'USER.EDITACCOUNT' | translate}}</button>
<div class="l-accounts"> <div class="l-accounts">
<mat-progress-bar *ngIf="loadingUsers" color="primary" mode="indeterminate"></mat-progress-bar> <mat-progress-bar *ngIf="loadingUsers" color="primary" mode="indeterminate"></mat-progress-bar>
<a class="row" *ngFor="let user of users" (click)="selectAccount(user.userName)"> <a class="row" *ngFor="let user of users" (click)="selectAccount(user.loginName)">
<app-avatar *ngIf="user && user.displayName" class="small-avatar" <app-avatar *ngIf="user && user.displayName" class="small-avatar"
[name]="user.displayName ? user.displayName : ''" [size]="32"> [name]="user.displayName ? user.displayName : ''" [size]="32">
</app-avatar> </app-avatar>
@ -22,7 +22,7 @@
<span class="fill-space"></span> <span class="fill-space"></span>
<mat-icon>keyboard_arrow_right</mat-icon> <mat-icon>keyboard_arrow_right</mat-icon>
</a> </a>
<a class="row" (click)="selectAccount()"> <a class="row" (click)="selectNewAccount()">
<div class="icon-wrapper"> <div class="icon-wrapper">
<i class="las la-user-plus"></i> <i class="las la-user-plus"></i>
</div> </div>

View File

@ -45,18 +45,24 @@ export class AccountsCardComponent implements OnInit {
} }
} }
public selectAccount(loginHint?: string, idToken?: string): void { public selectAccount(loginName?: string): void {
const configWithPrompt: Partial<AuthConfig> = { const configWithPrompt: Partial<AuthConfig> = {
customQueryParams: { customQueryParams: {
prompt: 'select_account', // prompt: 'select_account',
} as any, } as any,
}; };
if (loginHint) { if (loginName) {
(configWithPrompt as any).customQueryParams['login_hint'] = loginHint; (configWithPrompt as any).customQueryParams['login_hint'] = loginName;
} }
if (idToken) { this.authService.authenticate(configWithPrompt);
(configWithPrompt as any).customQueryParams['id_token_hint'] = idToken;
} }
public selectNewAccount(): void {
const configWithPrompt: Partial<AuthConfig> = {
customQueryParams: {
prompt: 'login',
} as any,
};
this.authService.authenticate(configWithPrompt); this.authService.authenticate(configWithPrompt);
} }

View File

@ -39,11 +39,10 @@ export class UserGrantsDataSource extends DataSource<UserGrant.AsObject> {
): void { ): void {
const offset = pageIndex * pageSize; const offset = pageIndex * pageSize;
this.loadingSubject.next(true);
switch (context) { switch (context) {
case UserGrantContext.USER: case UserGrantContext.USER:
if (data && data.userId) { if (data && data.userId) {
this.loadingSubject.next(true);
const userfilter = new UserGrantSearchQuery(); const userfilter = new UserGrantSearchQuery();
userfilter.setKey(UserGrantSearchKey.USERGRANTSEARCHKEY_USER_ID); userfilter.setKey(UserGrantSearchKey.USERGRANTSEARCHKEY_USER_ID);
userfilter.setValue(data.userId); userfilter.setValue(data.userId);
@ -59,12 +58,14 @@ export class UserGrantsDataSource extends DataSource<UserGrant.AsObject> {
break; break;
case UserGrantContext.OWNED_PROJECT: case UserGrantContext.OWNED_PROJECT:
if (data && data.projectId) { if (data && data.projectId) {
this.loadingSubject.next(true);
const promise1 = this.userService.SearchProjectUserGrants(data.projectId, 10, 0, queries); const promise1 = this.userService.SearchProjectUserGrants(data.projectId, 10, 0, queries);
this.loadResponse(promise1); this.loadResponse(promise1);
} }
break; break;
case UserGrantContext.GRANTED_PROJECT: case UserGrantContext.GRANTED_PROJECT:
if (data && data.grantId) { if (data && data.grantId) {
this.loadingSubject.next(true);
const promise2 = this.userService.SearchProjectGrantUserGrants(data.grantId, 10, 0, queries); const promise2 = this.userService.SearchProjectGrantUserGrants(data.grantId, 10, 0, queries);
this.loadResponse(promise2); this.loadResponse(promise2);
} }

View File

@ -139,7 +139,17 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
deleteGrantSelection(): void { deleteGrantSelection(): void {
this.userService.BulkRemoveUserGrant(this.selection.selected.map(grant => grant.id)).then(() => { this.userService.BulkRemoveUserGrant(this.selection.selected.map(grant => grant.id)).then(() => {
this.toast.showInfo('Grants deleted'); this.toast.showInfo('Grants deleted');
this.loadGrantsPage(); 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);
this.dataSource.grantsSubject.next(data);
}
});
this.selection.clear();
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error.message);
}); });

View File

@ -0,0 +1,13 @@
<span class="title" mat-dialog-title>{{data.titleKey | translate}}</span>
<div mat-dialog-content>
<p class="desc"> {{data.descriptionKey | translate}}</p>
</div>
<div mat-dialog-actions class="action">
<button mat-button (click)="closeDialog()">
{{data.cancelKey | translate}}
</button>
<button color="warn" mat-raised-button class="ok-button" (click)="closeDialogWithSuccess()">
{{data.confirmKey | translate}}
</button>
</div>

View File

@ -0,0 +1,22 @@
.title {
font-size: 1.2rem;
margin-top: 0;
}
.desc {
color: #8795a1;
font-size: .9rem;
}
.action {
display: flex;
justify-content: flex-end;
.ok-button {
margin-left: 0.5rem;
}
button {
border-radius: 0.5rem;
}
}

View File

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

View File

@ -0,0 +1,26 @@
import { Component, Inject, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
@Component({
selector: 'app-warn-dialog',
templateUrl: './warn-dialog.component.html',
styleUrls: ['./warn-dialog.component.scss'],
})
export class WarnDialogComponent implements OnInit {
constructor(
public dialogRef: MatDialogRef<WarnDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
) { }
ngOnInit(): void {
}
public closeDialog(): void {
this.dialogRef.close(false);
}
public closeDialogWithSuccess(): void {
this.dialogRef.close(true);
}
}

View File

@ -0,0 +1,21 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { TranslateModule } from '@ngx-translate/core';
import { WarnDialogComponent } from './warn-dialog.component';
@NgModule({
declarations: [WarnDialogComponent],
imports: [
CommonModule,
TranslateModule,
MatButtonModule,
],
entryComponents: [
WarnDialogComponent,
],
})
export class WarnDialogModule { }

View File

@ -1,10 +1,12 @@
import { SelectionModel } from '@angular/cdk/collections'; import { SelectionModel } from '@angular/cdk/collections';
import { Component, OnDestroy, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { MatButtonToggleChange } from '@angular/material/button-toggle'; import { MatButtonToggleChange } from '@angular/material/button-toggle';
import { MatDialog } from '@angular/material/dialog';
import { MatTableDataSource } from '@angular/material/table'; import { MatTableDataSource } from '@angular/material/table';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { ChangeType } from 'src/app/modules/changes/changes.component'; import { ChangeType } from 'src/app/modules/changes/changes.component';
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
import { Org, OrgDomainView, OrgMember, OrgMemberSearchResponse, OrgState } from 'src/app/proto/generated/management_pb'; import { Org, OrgDomainView, OrgMember, OrgMemberSearchResponse, OrgState } from 'src/app/proto/generated/management_pb';
import { OrgService } from 'src/app/services/org.service'; import { OrgService } from 'src/app/services/org.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
@ -32,6 +34,7 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
public newDomain: string = ''; public newDomain: string = '';
constructor( constructor(
private dialog: MatDialog,
public translate: TranslateService, public translate: TranslateService,
private orgService: OrgService, private orgService: OrgService,
private toast: ToastService, private toast: ToastService,
@ -82,6 +85,18 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
} }
public removeDomain(domain: string): void { public removeDomain(domain: string): void {
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.DELETE',
cancelKey: 'ACTIONS.CANCEL',
titleKey: 'ORG.DOMAINS.DELETE.TITLE',
descriptionKey: 'ORG.DOMAINS.DELETE.DESCRIPTION',
},
width: '400px',
});
dialogRef.afterClosed().subscribe(resp => {
if (resp) {
this.orgService.RemoveMyOrgDomain(domain).then(() => { this.orgService.RemoveMyOrgDomain(domain).then(() => {
this.toast.showInfo('Removed'); this.toast.showInfo('Removed');
const index = this.domains.findIndex(d => d.domain === domain); const index = this.domains.findIndex(d => d.domain === domain);
@ -92,4 +107,6 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
this.toast.showError(error.message); this.toast.showError(error.message);
}); });
} }
});
}
} }

View File

@ -16,6 +16,7 @@ import { HttpLoaderFactory } from 'src/app/app.module';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module'; import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { CardModule } from 'src/app/modules/card/card.module'; import { CardModule } from 'src/app/modules/card/card.module';
import { MetaLayoutModule } from 'src/app/modules/meta-layout/meta-layout.module'; import { MetaLayoutModule } from 'src/app/modules/meta-layout/meta-layout.module';
import { WarnDialogModule } from 'src/app/modules/warn-dialog/warn-dialog.module';
import { ChangesModule } from '../../modules/changes/changes.module'; import { ChangesModule } from '../../modules/changes/changes.module';
import { OrgContributorsModule } from './org-contributors/org-contributors.module'; import { OrgContributorsModule } from './org-contributors/org-contributors.module';
@ -43,6 +44,8 @@ import { PolicyGridComponent } from './policy-grid/policy-grid.component';
MetaLayoutModule, MetaLayoutModule,
MatTabsModule, MatTabsModule,
MatTooltipModule, MatTooltipModule,
MatDialogModule,
WarnDialogModule,
MatMenuModule, MatMenuModule,
ChangesModule, ChangesModule,
TranslateModule.forChild({ TranslateModule.forChild({

View File

@ -3,5 +3,5 @@
"mgmtServiceUrl": "https://api.zitadel.dev", "mgmtServiceUrl": "https://api.zitadel.dev",
"adminServiceUrl":"https://api.zitadel.dev", "adminServiceUrl":"https://api.zitadel.dev",
"issuer": "https://issuer.zitadel.dev", "issuer": "https://issuer.zitadel.dev",
"clientid": "61542534056307917@zitadel" "clientid": "63146698922323188@zitadel"
} }

View File

@ -22,10 +22,10 @@
"PERSONAL_INFO": "Persönliche Informationen", "PERSONAL_INFO": "Persönliche Informationen",
"IAM":"Administration", "IAM":"Administration",
"ORGANIZATION": "Organisation", "ORGANIZATION": "Organisation",
"PROJECTSSECTION":"Projekte", "PROJECTSSECTION":"Projektsektion",
"PROJECT": "Projekte", "PROJECT": "Projekte",
"GRANTEDPROJECT":"Berechtigte Projekte", "GRANTEDPROJECT":"Berechtigte Projekte",
"USERSECTION":"Benutzer", "USERSECTION":"Benutzersektion",
"USER": "Benutzer", "USER": "Benutzer",
"LOGOUT": "alle Benutzer abmelden", "LOGOUT": "alle Benutzer abmelden",
"NEWORG":"Neue Organisation", "NEWORG":"Neue Organisation",
@ -230,7 +230,11 @@
}, },
"DOMAINS": { "DOMAINS": {
"TITLE":"Domains", "TITLE":"Domains",
"DESCRIPTION":"Konfigurieren Sie Ihre Domains mit denen sich Ihre Nutzer einloggen können." "DESCRIPTION":"Konfigurieren Sie die Domains mit denen sich Ihre Nutzer einloggen können.",
"DELETE": {
"TITLE":"Domain löschen?",
"DESCRIPTION":"Sie sind im Begriff eine Domain aus Ihrer Organisation zu löschen. Ihre Benutzer können diese nach dem Löschen nicht mehr für den Login nutzen."
}
}, },
"STATE": { "STATE": {
"0": "nicht definiert", "0": "nicht definiert",

View File

@ -22,10 +22,10 @@
"PERSONAL_INFO": "Personal Information", "PERSONAL_INFO": "Personal Information",
"IAM":"Administration", "IAM":"Administration",
"ORGANIZATION": "Organization", "ORGANIZATION": "Organization",
"PROJECTSSECTION":"Projects", "PROJECTSSECTION":"projects section",
"PROJECT": "Projects", "PROJECT": "Projects",
"GRANTEDPROJECT":"Granted Projects", "GRANTEDPROJECT":"Granted Projects",
"USERSECTION":"Benutzer", "USERSECTION":"user section",
"USER": "Users", "USER": "Users",
"LOGOUT": "Logout all users", "LOGOUT": "Logout all users",
"NEWORG":"New Organization", "NEWORG":"New Organization",
@ -231,7 +231,11 @@
}, },
"DOMAINS": { "DOMAINS": {
"TITLE":"Domains", "TITLE":"Domains",
"DESCRIPTION":"Configure your domains on which your users will be registered." "DESCRIPTION":"Configure your domains on which your users will be registered.",
"DELETE": {
"TITLE":"Delete domain?",
"DESCRIPTION":"You are about to delete one of your domains. Note that your users can no longer use this domain for their logins."
}
}, },
"STATE": { "STATE": {
"0": "not defined", "0": "not defined",