fix(console): intercept navigator.language, set browser lang as default for user without explicit setting, user table outline, member create dialog import (#820)

* i18n interceptor, set language to browser lang

* nullcheck

* rm external idp log

* fix module imports, rm user displayname from i18n

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

Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com>
This commit is contained in:
Max Peintner 2020-10-08 14:49:47 +02:00 committed by GitHub
parent 0bbc9c7c49
commit 286bba1c70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 54 additions and 13 deletions

View File

@ -220,7 +220,9 @@ export class AppComponent implements OnDestroy {
this.authService.user.subscribe(userprofile => { this.authService.user.subscribe(userprofile => {
this.profile = userprofile; this.profile = userprofile;
const lang = userprofile.preferredLanguage.match(/en|de/) ? userprofile.preferredLanguage : 'en'; const cropped = navigator.language.split('-')[0] ?? 'en';
const fallbackLang = cropped.match(/en|de/) ? cropped : 'en';
const lang = userprofile.preferredLanguage.match(/en|de/) ? userprofile.preferredLanguage : fallbackLang;
this.translate.use(lang); this.translate.use(lang);
this.document.documentElement.lang = lang; this.document.documentElement.lang = lang;
}); });

View File

@ -40,6 +40,7 @@ import { GrpcAuthService } from './services/grpc-auth.service';
import { GrpcService } from './services/grpc.service'; import { GrpcService } from './services/grpc.service';
import { AuthInterceptor } from './services/interceptors/auth.interceptor'; import { AuthInterceptor } from './services/interceptors/auth.interceptor';
import { GRPC_INTERCEPTORS } from './services/interceptors/grpc-interceptor'; import { GRPC_INTERCEPTORS } from './services/interceptors/grpc-interceptor';
import { I18nInterceptor } from './services/interceptors/i18n.interceptor';
import { OrgInterceptor } from './services/interceptors/org.interceptor'; import { OrgInterceptor } from './services/interceptors/org.interceptor';
import { RefreshService } from './services/refresh.service'; import { RefreshService } from './services/refresh.service';
import { StatehandlerProcessorService, StatehandlerProcessorServiceImpl } from './services/statehandler-processor.service'; import { StatehandlerProcessorService, StatehandlerProcessorServiceImpl } from './services/statehandler-processor.service';
@ -156,6 +157,11 @@ const authConfig: AuthConfig = {
multi: true, multi: true,
useClass: AuthInterceptor, useClass: AuthInterceptor,
}, },
{
provide: GRPC_INTERCEPTORS,
multi: true,
useClass: I18nInterceptor,
},
{ {
provide: GRPC_INTERCEPTORS, provide: GRPC_INTERCEPTORS,
multi: true, multi: true,

View File

@ -2,8 +2,10 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatChipsModule } from '@angular/material/chips';
import { MatDialogModule } from '@angular/material/dialog'; import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select'; import { MatSelectModule } from '@angular/material/select';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { SearchUserAutocompleteModule } from 'src/app/modules/search-user-autocomplete/search-user-autocomplete.module'; import { SearchUserAutocompleteModule } from 'src/app/modules/search-user-autocomplete/search-user-autocomplete.module';
@ -21,6 +23,8 @@ import { MemberCreateDialogComponent } from './member-create-dialog.component';
CommonModule, CommonModule,
MatDialogModule, MatDialogModule,
MatButtonModule, MatButtonModule,
MatChipsModule,
MatInputModule,
TranslateModule, TranslateModule,
MatFormFieldModule, MatFormFieldModule,
MatSelectModule, MatSelectModule,

View File

@ -4,12 +4,12 @@
<button (click)="deactivateSelectedIdps()" matTooltip="{{'IDP.DEACTIVATE' | translate}}" class="icon-button" <button (click)="deactivateSelectedIdps()" matTooltip="{{'IDP.DEACTIVATE' | translate}}" class="icon-button"
mat-icon-button *ngIf="selection.hasValue() && serviceType!=PolicyComponentServiceType.MGMT" mat-icon-button *ngIf="selection.hasValue() && serviceType!=PolicyComponentServiceType.MGMT"
[disabled]="disabled"> [disabled]="disabled">
<mat-icon svgIcon="mdi_account_cancel"></mat-icon> <mat-icon>block</mat-icon>
</button> </button>
<button (click)="reactivateSelectedIdps()" matTooltip="{{'IDP.ACTIVATE' | translate}}" class="icon-button" <button (click)="reactivateSelectedIdps()" matTooltip="{{'IDP.ACTIVATE' | translate}}" class="icon-button"
mat-icon-button *ngIf="selection.hasValue() && serviceType!=PolicyComponentServiceType.MGMT" mat-icon-button *ngIf="selection.hasValue() && serviceType!=PolicyComponentServiceType.MGMT"
[disabled]="disabled"> [disabled]="disabled">
<mat-icon svgIcon="mdi_account_check_outline"></mat-icon> <mat-icon>play_circle_outline</mat-icon>
</button> </button>
<button color="warn" (click)="removeSelectedIdps()" matTooltip="{{'IDP.DELETE' | translate}}" <button color="warn" (click)="removeSelectedIdps()" matTooltip="{{'IDP.DELETE' | translate}}"
class="icon-button" mat-icon-button class="icon-button" mat-icon-button

View File

@ -10,6 +10,7 @@ import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.
import { MembersTableModule } from 'src/app/modules/members-table/members-table.module'; import { MembersTableModule } from 'src/app/modules/members-table/members-table.module';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module'; import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
import { MemberCreateDialogModule } from '../add-member-dialog/member-create-dialog.module';
import { ProjectMembersRoutingModule } from './project-members-routing.module'; import { ProjectMembersRoutingModule } from './project-members-routing.module';
import { ProjectMembersComponent } from './project-members.component'; import { ProjectMembersComponent } from './project-members.component';
@ -27,6 +28,7 @@ import { ProjectMembersComponent } from './project-members.component';
MatDialogModule, MatDialogModule,
MembersTableModule, MembersTableModule,
HasRolePipeModule, HasRolePipeModule,
MemberCreateDialogModule,
], ],
}) })
export class ProjectMembersModule { } export class ProjectMembersModule { }

View File

@ -5,6 +5,7 @@ import { MatIconModule } from '@angular/material/icon';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module'; import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { MemberCreateDialogModule } from 'src/app/modules/add-member-dialog/member-create-dialog.module';
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module'; import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module';
import { MembersTableModule } from 'src/app/modules/members-table/members-table.module'; import { MembersTableModule } from 'src/app/modules/members-table/members-table.module';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module'; import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
@ -26,6 +27,7 @@ import { IamMembersComponent } from './iam-members.component';
TranslateModule, TranslateModule,
MembersTableModule, MembersTableModule,
HasRolePipeModule, HasRolePipeModule,
MemberCreateDialogModule,
], ],
}) })
export class IamMembersModule { } export class IamMembersModule { }

View File

@ -6,6 +6,7 @@ import { MatIconModule } from '@angular/material/icon';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module'; import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { MemberCreateDialogModule } from 'src/app/modules/add-member-dialog/member-create-dialog.module';
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module'; import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module';
import { MembersTableModule } from 'src/app/modules/members-table/members-table.module'; import { MembersTableModule } from 'src/app/modules/members-table/members-table.module';
import { RefreshTableModule } from 'src/app/modules/refresh-table/refresh-table.module'; import { RefreshTableModule } from 'src/app/modules/refresh-table/refresh-table.module';
@ -30,6 +31,7 @@ import { OrgMembersComponent } from './org-members.component';
RefreshTableModule, RefreshTableModule,
MembersTableModule, MembersTableModule,
HasRolePipeModule, HasRolePipeModule,
MemberCreateDialogModule,
], ],
}) })
export class OrgMembersModule { } export class OrgMembersModule { }

View File

@ -47,7 +47,6 @@ import { PolicyGridComponent } from './policy-grid/policy-grid.component';
MetaLayoutModule, MetaLayoutModule,
MatTabsModule, MatTabsModule,
MatTooltipModule, MatTooltipModule,
MatDialogModule,
WarnDialogModule, WarnDialogModule,
MemberCreateDialogModule, MemberCreateDialogModule,
MatMenuModule, MatMenuModule,

View File

@ -67,7 +67,6 @@ export class ExternalIdpsComponent implements OnInit {
promise.then(resp => { promise.then(resp => {
this.externalIdpResult = resp.toObject(); this.externalIdpResult = resp.toObject();
this.dataSource.data = this.externalIdpResult.resultList; this.dataSource.data = this.externalIdpResult.resultList;
console.log(this.externalIdpResult.resultList);
this.loadingSubject.next(false); this.loadingSubject.next(false);
}).catch((error: any) => { }).catch((error: any) => {
this.toast.showError(error); this.toast.showError(error);

View File

@ -199,8 +199,6 @@ export class UserDetailComponent implements OnInit, OnDestroy {
confirmKey: 'ACTIONS.DELETE', confirmKey: 'ACTIONS.DELETE',
cancelKey: 'ACTIONS.CANCEL', cancelKey: 'ACTIONS.CANCEL',
titleKey: 'USER.DIALOG.DELETE_TITLE', titleKey: 'USER.DIALOG.DELETE_TITLE',
descriptionParam: this.user.human ??
this.user.machine ? { displayName: this.user.machine?.name } : { displayName: '' },
descriptionKey: 'USER.DIALOG.DELETE_DESCRIPTION', descriptionKey: 'USER.DIALOG.DELETE_DESCRIPTION',
}, },
width: '400px', width: '400px',

View File

@ -126,7 +126,7 @@
<th mat-header-cell *matHeaderCellDef></th> <th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let user"> <td mat-cell *matCellDef="let user">
<button [disabled]="(['user.delete$', 'user.delete:'+user.id] | hasRole | async) == false" <button [disabled]="(['user.delete$', 'user.delete:'+user.id] | hasRole | async) == false"
color="warn" mat-icon-button matTooltip="{{'IAM.VIEWS.DELETE' | translate}}" color="warn" mat-icon-button matTooltip="{{'USER.PAGES.DELETE' | translate}}"
(click)="deleteUser(user)"> (click)="deleteUser(user)">
<i class="las la-trash"></i> <i class="las la-trash"></i>
</button> </button>

View File

@ -8,6 +8,7 @@
td, td,
th { th {
padding: .5rem; padding: .5rem;
outline: none;
&:first-child { &:first-child {
padding-left: 0; padding-left: 0;
@ -27,8 +28,6 @@
} }
tr { tr {
outline: none;
button { button {
visibility: hidden; visibility: hidden;
} }

View File

@ -152,7 +152,6 @@ export class UserTableComponent implements OnInit {
confirmKey: 'ACTIONS.DELETE', confirmKey: 'ACTIONS.DELETE',
cancelKey: 'ACTIONS.CANCEL', cancelKey: 'ACTIONS.CANCEL',
titleKey: 'USER.DIALOG.DELETE_TITLE', titleKey: 'USER.DIALOG.DELETE_TITLE',
descriptionParam: user.human ?? user.machine ? { displayName: user.machine?.name } : { displayName: '' },
descriptionKey: 'USER.DIALOG.DELETE_DESCRIPTION', descriptionKey: 'USER.DIALOG.DELETE_DESCRIPTION',
}, },
width: '400px', width: '400px',

View File

@ -9,6 +9,7 @@ import { AuthServicePromiseClient } from '../proto/generated/auth_grpc_web_pb';
import { ManagementServicePromiseClient } from '../proto/generated/management_grpc_web_pb'; import { ManagementServicePromiseClient } from '../proto/generated/management_grpc_web_pb';
import { AuthenticationService } from './authentication.service'; import { AuthenticationService } from './authentication.service';
import { AuthInterceptor } from './interceptors/auth.interceptor'; import { AuthInterceptor } from './interceptors/auth.interceptor';
import { I18nInterceptor } from './interceptors/i18n.interceptor';
import { OrgInterceptor } from './interceptors/org.interceptor'; import { OrgInterceptor } from './interceptors/org.interceptor';
import { StorageService } from './storage.service'; import { StorageService } from './storage.service';
@ -37,6 +38,7 @@ export class GrpcService {
unaryInterceptors: [ unaryInterceptors: [
new OrgInterceptor(this.storageService), new OrgInterceptor(this.storageService),
new AuthInterceptor(this.authenticationService, this.storageService, this.dialog), new AuthInterceptor(this.authenticationService, this.storageService, this.dialog),
new I18nInterceptor(),
], ],
}; };

View File

@ -0,0 +1,27 @@
import { Injectable } from '@angular/core';
import { Request, UnaryInterceptor, UnaryResponse } from 'grpc-web';
const i18nHeader = 'Accept-Language';
@Injectable({ providedIn: 'root' })
/**
* Set the navigator language as header to all grpc requests
*/
export class I18nInterceptor<TReq = unknown, TResp = unknown> implements UnaryInterceptor<TReq, TResp> {
constructor() { }
public async intercept(request: Request<TReq, TResp>, invoker: any): Promise<UnaryResponse<TReq, TResp>> {
const metadata = request.getMetadata();
const navLang = navigator.language;
if (navLang) {
metadata[i18nHeader] = navLang;
}
return invoker(request).then((response: any) => {
return response;
}).catch((error: any) => {
return Promise.reject(error);
});
}
}

View File

@ -94,7 +94,7 @@
}, },
"DIALOG": { "DIALOG": {
"DELETE_TITLE":"User löschen", "DELETE_TITLE":"User löschen",
"DELETE_DESCRIPTION":"Sie sind im Begriff den Benutzer {{displayName}} entgültig zu löschen. Wollen Sie dies wirklich tun?" "DELETE_DESCRIPTION":"Sie sind im Begriff einen Benutzer endgültig zu löschen. Wollen Sie dies wirklich tun?"
}, },
"TABLE":{ "TABLE":{
"DEACTIVATE":"Deaktivieren", "DEACTIVATE":"Deaktivieren",

View File

@ -94,7 +94,7 @@
}, },
"DIALOG": { "DIALOG": {
"DELETE_TITLE":"Delete User", "DELETE_TITLE":"Delete User",
"DELETE_DESCRIPTION":"You are about to permanently delete the user {{displayName}}. Are you sure?" "DELETE_DESCRIPTION":"You are about to permanently delete a user. Are you sure?"
}, },
"TABLE":{ "TABLE":{
"DEACTIVATE":"Deactivate", "DEACTIVATE":"Deactivate",