feat(console-v2): secret generator settings (#3619)

* secret generator, fix project grant

* secret generators
This commit is contained in:
Max Peintner 2022-05-13 16:12:58 +02:00 committed by GitHub
parent 024eedc1b5
commit a674f99c2d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 636 additions and 69 deletions

View File

@ -109,12 +109,6 @@ export class NotificationSettingsComponent implements OnInit {
public addSMSProvider(): void {
const dialogRef = this.dialog.open(DialogAddSMSProviderComponent, {
data: {
confirmKey: 'ACTIONS.DELETE',
cancelKey: 'ACTIONS.CANCEL',
titleKey: 'IDP.DELETE_TITLE',
descriptionKey: 'IDP.DELETE_DESCRIPTION',
},
width: '400px',
});

View File

@ -0,0 +1,81 @@
<h1 mat-dialog-title class="title">
<span>{{ 'SETTING.SECRETS.ADDGENERATOR' | translate }}</span>
</h1>
<div mat-dialog-content>
<form *ngIf="specsForm" (ngSubmit)="closeDialogWithRequest()" [formGroup]="specsForm">
<cnsl-form-field class="type-form-field" label="Access Code" required="true">
<cnsl-label>{{ 'SETTING.SECRETS.GENERATORTYPE' | translate }}</cnsl-label>
<mat-select formControlName="generatorType">
<mat-option *ngFor="let gen of availableGenerators" [value]="gen">
<span>{{ 'SETTING.SECRETS.TYPE.' + gen | translate }}</span>
</mat-option>
</mat-select>
</cnsl-form-field>
<h2 class="generator-type">{{ 'SETTING.SECRETS.TYPE.' + generatorType?.value | translate }}</h2>
<cnsl-form-field class="generator-form-field" label="Expiration">
<cnsl-label>{{ 'SETTING.SECRETS.EXPIRY' | translate }}</cnsl-label>
<input cnslInput name="expiry" type="number" formControlName="expiry" />
</cnsl-form-field>
<cnsl-form-field class="generator-form-field" label="Length">
<cnsl-label>{{ 'SETTING.SECRETS.LENGTH' | translate }}</cnsl-label>
<input cnslInput name="length" type="number" formControlName="length" />
</cnsl-form-field>
<div class="slide-toggle-wrapper">
<mat-slide-toggle class="slide-toggle" color="primary" name="includeDigits" formControlName="includeDigits">
<div class="slide-toggle-row">
<mat-icon class="icon" svgIcon="mdi_numeric"></mat-icon>
<span class="left-desc">{{ 'SETTING.SECRETS.INCLUDEDIGITS' | translate }}</span>
</div>
</mat-slide-toggle>
<mat-slide-toggle class="slide-toggle" color="primary" name="includeSymbols" formControlName="includeSymbols">
<div class="slide-toggle-row">
<mat-icon class="icon" svgIcon="mdi_symbol"></mat-icon>
<span class="left-desc">{{ 'SETTING.SECRETS.INCLUDESYMBOLS' | translate }}</span>
</div>
</mat-slide-toggle>
<mat-slide-toggle
class="slide-toggle"
color="primary"
name="includeLowerLetters"
formControlName="includeLowerLetters"
>
<div class="slide-toggle-row">
<mat-icon class="icon" svgIcon="mdi_format-letter-case-lower"></mat-icon>
<span class="left-desc">{{ 'SETTING.SECRETS.INCLUDELOWERLETTERS' | translate }}</span>
</div>
</mat-slide-toggle>
<mat-slide-toggle
class="slide-toggle"
color="primary"
name="includeUpperLetters"
formControlName="includeUpperLetters"
>
<div class="slide-toggle-row">
<mat-icon class="icon" svgIcon="mdi_format-letter-case-upper"></mat-icon>
<span class="left-desc">{{ 'SETTING.SECRETS.INCLUDEUPPERLETTERS' | translate }}</span>
</div>
</mat-slide-toggle>
</div>
</form>
</div>
<div mat-dialog-actions class="action">
<button mat-stroked-button (click)="closeDialog()">
<span>{{ 'ACTIONS.CLOSE' | translate }}</span>
</button>
<button
[disabled]="!specsForm.valid"
mat-raised-button
class="ok-button"
color="primary"
(click)="closeDialogWithRequest()"
>
<span>{{ 'ACTIONS.SAVE' | translate }}</span>
</button>
</div>

View File

@ -0,0 +1,43 @@
.title {
font-size: 1.5rem;
}
.generator-type {
font-size: 1rem;
}
.type-form-field {
display: none;
}
.generator-form-field {
width: 100%;
}
.slide-toggle-wrapper {
display: grid;
grid-template-columns: 1fr;
column-gap: 1rem;
.slide-toggle {
margin: 0.5rem 0;
.slide-toggle-row {
display: flex;
align-items: center;
.icon {
margin: 0 0.5rem;
}
}
}
}
.action {
display: flex;
justify-content: space-between;
button {
border-radius: 0.5rem;
}
}

View File

@ -0,0 +1,91 @@
import { Component, Inject } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Duration } from 'google-protobuf/google/protobuf/duration_pb';
import { UpdateSecretGeneratorRequest } from 'src/app/proto/generated/zitadel/admin_pb';
import { SecretGeneratorType } from 'src/app/proto/generated/zitadel/settings_pb';
@Component({
selector: 'cnsl-dialog-add-secret-generator',
templateUrl: './dialog-add-secret-generator.component.html',
styleUrls: ['./dialog-add-secret-generator.component.scss'],
})
export class DialogAddSecretGeneratorComponent {
public SecretGeneratorType: any = SecretGeneratorType;
public availableGenerators: SecretGeneratorType[] = [
SecretGeneratorType.SECRET_GENERATOR_TYPE_INIT_CODE,
SecretGeneratorType.SECRET_GENERATOR_TYPE_VERIFY_EMAIL_CODE,
SecretGeneratorType.SECRET_GENERATOR_TYPE_VERIFY_PHONE_CODE,
SecretGeneratorType.SECRET_GENERATOR_TYPE_PASSWORD_RESET_CODE,
SecretGeneratorType.SECRET_GENERATOR_TYPE_PASSWORDLESS_INIT_CODE,
SecretGeneratorType.SECRET_GENERATOR_TYPE_APP_SECRET,
];
public req: UpdateSecretGeneratorRequest = new UpdateSecretGeneratorRequest();
public specsForm!: FormGroup;
constructor(
private fb: FormBuilder,
public dialogRef: MatDialogRef<DialogAddSecretGeneratorComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
) {
this.specsForm = this.fb.group({
generatorType: [SecretGeneratorType.SECRET_GENERATOR_TYPE_APP_SECRET, [Validators.required]],
expiry: [1, [Validators.required]],
includeDigits: [true, [Validators.required]],
includeLowerLetters: [true, [Validators.required]],
includeSymbols: [true, [Validators.required]],
includeUpperLetters: [true, [Validators.required]],
length: [6, [Validators.required]],
});
this.generatorType?.setValue(data.type);
}
public closeDialog(): void {
this.dialogRef.close();
}
public closeDialogWithRequest(): void {
this.req.setGeneratorType(this.generatorType?.value);
const expiry = new Duration().setSeconds((this.expiry?.value ?? 1) * 60 * 60);
this.req.setExpiry(expiry);
this.req.setIncludeDigits(this.includeDigits?.value);
this.req.setIncludeLowerLetters(this.includeLowerLetters?.value);
this.req.setIncludeSymbols(this.includeSymbols?.value);
this.req.setIncludeUpperLetters(this.includeUpperLetters?.value);
this.req.setLength(this.length?.value);
this.dialogRef.close(this.req);
}
public get generatorType(): AbstractControl | null {
return this.specsForm.get('generatorType');
}
public get expiry(): AbstractControl | null {
return this.specsForm.get('expiry');
}
public get includeDigits(): AbstractControl | null {
return this.specsForm.get('includeDigits');
}
public get includeLowerLetters(): AbstractControl | null {
return this.specsForm.get('includeLowerLetters');
}
public get includeSymbols(): AbstractControl | null {
return this.specsForm.get('includeSymbols');
}
public get includeUpperLetters(): AbstractControl | null {
return this.specsForm.get('includeUpperLetters');
}
public get length(): AbstractControl | null {
return this.specsForm.get('length');
}
}

View File

@ -0,0 +1,15 @@
<div class="spinner-wr">
<mat-spinner diameter="30" *ngIf="loading" color="primary"></mat-spinner>
</div>
<h2>{{ 'SETTING.SECRETS.TITLE' | translate }}</h2>
<div class="generators">
<cnsl-card class="generator" [nomargin]="true" *ngFor="let gen of AVAILABLEGENERATORS">
<div class="row">
<h3 class="title">{{ 'SETTING.SECRETS.TYPE.' + gen | translate }}</h3>
<button mat-icon-button (click)="openGeneratorDialog(gen)">
<i class="las la-pen"></i>
</button>
</div>
</cnsl-card>
</div>

View File

@ -0,0 +1,29 @@
.spinner-wr {
margin: 0.5rem 0;
}
.generators {
display: grid;
grid-template-columns: 1fr 1fr;
column-gap: 1rem;
row-gap: 1rem;
margin-bottom: 2rem;
margin-top: 1.5rem;
@media only screen and (max-width: 600px) {
grid-template-columns: 1fr;
}
.generator {
.row {
display: flex;
align-items: center;
justify-content: space-between;
.title {
font-size: 1rem;
margin: 0;
}
}
}
}

View File

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

View File

@ -0,0 +1,107 @@
import { Component, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { UpdateSecretGeneratorRequest, UpdateSecretGeneratorResponse } from 'src/app/proto/generated/zitadel/admin_pb';
import { OIDCSettings, SecretGenerator, SecretGeneratorType } from 'src/app/proto/generated/zitadel/settings_pb';
import { AdminService } from 'src/app/services/admin.service';
import { ToastService } from 'src/app/services/toast.service';
import { DialogAddSecretGeneratorComponent } from './dialog-add-secret-generator/dialog-add-secret-generator.component';
@Component({
selector: 'cnsl-secret-generator',
templateUrl: './secret-generator.component.html',
styleUrls: ['./secret-generator.component.scss'],
})
export class SecretGeneratorComponent implements OnInit {
public generators: SecretGenerator.AsObject[] = [];
public oidcSettings!: OIDCSettings.AsObject;
public loading: boolean = false;
public readonly AVAILABLEGENERATORS: SecretGeneratorType[] = [
SecretGeneratorType.SECRET_GENERATOR_TYPE_INIT_CODE,
SecretGeneratorType.SECRET_GENERATOR_TYPE_VERIFY_EMAIL_CODE,
SecretGeneratorType.SECRET_GENERATOR_TYPE_VERIFY_PHONE_CODE,
SecretGeneratorType.SECRET_GENERATOR_TYPE_PASSWORD_RESET_CODE,
SecretGeneratorType.SECRET_GENERATOR_TYPE_PASSWORDLESS_INIT_CODE,
SecretGeneratorType.SECRET_GENERATOR_TYPE_APP_SECRET,
];
constructor(private service: AdminService, private toast: ToastService, private dialog: MatDialog) {}
ngOnInit(): void {
this.fetchData();
}
private fetchData(): void {
this.service
.listSecretGenerators()
.then((generators) => {
if (generators.resultList) {
this.generators = generators.resultList;
console.log(this.generators);
}
})
.catch((error) => {
if (error.code === 5) {
} else {
this.toast.showError(error);
}
});
}
private updateData(): Promise<UpdateSecretGeneratorResponse.AsObject> | void {
const dialogRef = this.dialog.open(DialogAddSecretGeneratorComponent, {
data: {},
width: '400px',
});
dialogRef.afterClosed().subscribe((req: UpdateSecretGeneratorRequest) => {
if (req) {
return (this.service as AdminService).updateSecretGenerator(req);
} else {
return;
}
});
}
public openGeneratorDialog(generatorType: SecretGeneratorType): void {
const dialogRef = this.dialog.open(DialogAddSecretGeneratorComponent, {
data: {
type: generatorType,
},
width: '400px',
});
dialogRef.afterClosed().subscribe((req: UpdateSecretGeneratorRequest) => {
if (req) {
return (this.service as AdminService)
.updateSecretGenerator(req)
.then(() => {
this.toast.showInfo('SETTING.SECRETS.UPDATED', true);
})
.catch((error) => {
this.toast.showError(error);
});
} else {
return;
}
});
}
public savePolicy(): void {
const prom = this.updateData();
if (prom) {
prom
.then(() => {
this.toast.showInfo('SETTING.SMTP.SAVED', true);
this.loading = true;
setTimeout(() => {
this.fetchData();
}, 2000);
})
.catch((error) => {
this.toast.showError(error);
});
}
}
}

View File

@ -0,0 +1,35 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { TranslateModule } from '@ngx-translate/core';
import { CardModule } from '../../card/card.module';
import { FormFieldModule } from '../../form-field/form-field.module';
import { InputModule } from '../../input/input.module';
import { DialogAddSecretGeneratorComponent } from './dialog-add-secret-generator/dialog-add-secret-generator.component';
import { SecretGeneratorComponent } from './secret-generator.component';
@NgModule({
declarations: [SecretGeneratorComponent, DialogAddSecretGeneratorComponent],
imports: [
CommonModule,
MatIconModule,
CardModule,
FormsModule,
MatButtonModule,
FormFieldModule,
ReactiveFormsModule,
MatSlideToggleModule,
InputModule,
MatProgressSpinnerModule,
MatSelectModule,
TranslateModule,
],
exports: [SecretGeneratorComponent, DialogAddSecretGeneratorComponent],
})
export class SecretGeneratorModule {}

View File

@ -77,11 +77,9 @@ export class ProjectMembersComponent {
}),
new Breadcrumb({
type: BreadcrumbType.PROJECT,
// name: this.project.name,
param: { key: 'projectid', value: (this.project as Project.AsObject).id },
routerLink: ['/projects', (this.project as Project.AsObject).id],
isZitadel: isZitadel,
hideNav: true,
}),
];
breadcrumbService.setBreadcrumb(breadcrumbs);

View File

@ -30,6 +30,10 @@
<cnsl-oidc-configuration></cnsl-oidc-configuration>
</ng-container>
<ng-container *ngIf="currentSetting === 'secrets' && serviceType === PolicyComponentServiceType.ADMIN">
<cnsl-secret-generator></cnsl-secret-generator>
</ng-container>
<ng-container *ngIf="currentSetting === 'branding'">
<cnsl-private-labeling-policy [serviceType]="serviceType"></cnsl-private-labeling-policy>
</ng-container>

View File

@ -17,6 +17,7 @@ import { PasswordComplexityPolicyModule } from '../policies/password-complexity-
import { PasswordLockoutPolicyModule } from '../policies/password-lockout-policy/password-lockout-policy.module';
import { PrivacyPolicyModule } from '../policies/privacy-policy/privacy-policy.module';
import { PrivateLabelingPolicyModule } from '../policies/private-labeling-policy/private-labeling-policy.module';
import { SecretGeneratorModule } from '../policies/secret-generator/secret-generator.module';
import { SidenavModule } from '../sidenav/sidenav.module';
import { SettingsListComponent } from './settings-list.component';
@ -41,6 +42,7 @@ import { SettingsListComponent } from './settings-list.component';
HasRolePipeModule,
NotificationSettingsModule,
OIDCConfigurationModule,
SecretGeneratorModule,
],
exports: [SettingsListComponent],
})

View File

@ -10,6 +10,11 @@ export const OIDC: SidenavSetting = {
i18nKey: 'SETTINGS.LIST.OIDC',
};
export const SECRETS: SidenavSetting = {
id: 'secrets',
i18nKey: 'SETTINGS.LIST.SECRETS',
};
export const LOGIN: SidenavSetting = {
id: 'login',
i18nKey: 'SETTINGS.LIST.LOGIN',

View File

@ -38,7 +38,6 @@
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: -0.5rem;
.title {
font-size: 16px;

View File

@ -17,6 +17,7 @@ import {
NOTIFICATIONS,
OIDC,
PRIVACYPOLICY,
SECRETS,
} from '../../modules/settings-list/settings';
@Component({
@ -43,6 +44,7 @@ export class InstanceSettingsComponent {
// others
PRIVACYPOLICY,
OIDC,
SECRETS,
];
constructor(breadcrumbService: BreadcrumbService, activatedRoute: ActivatedRoute) {
const breadcrumbs = [

View File

@ -35,6 +35,7 @@
.domain-button {
margin-right: 1rem;
margin-bottom: 1rem;
display: block;
}
.btn-container {

View File

@ -1,13 +1,15 @@
import { Location } from '@angular/common';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';
import { Subject, takeUntil } from 'rxjs';
import { Org } from 'src/app/proto/generated/zitadel/org_pb';
import { Role } from 'src/app/proto/generated/zitadel/project_pb';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
const ROUTEPARAM = 'projectid';
@Component({
selector: 'cnsl-project-grant-create',
templateUrl: './project-grant-create.component.html',
@ -22,37 +24,50 @@ export class ProjectGrantCreateComponent implements OnInit, OnDestroy {
public STEPS: number = 2;
public currentCreateStep: number = 1;
private routeSubscription: Subscription = new Subscription();
private destroy$: Subject<void> = new Subject();
constructor(
private route: ActivatedRoute,
private toast: ToastService,
private mgmtService: ManagementService,
private authService: GrpcAuthService,
private _location: Location,
) { }
private breadcrumbService: BreadcrumbService,
) {}
public ngOnInit(): void {
this.routeSubscription = this.route.params.subscribe(params => {
this.route.params.pipe(takeUntil(this.destroy$)).subscribe((params) => {
this.projectId = params.projectid;
const breadcrumbs = [
new Breadcrumb({
type: BreadcrumbType.ORG,
routerLink: ['/org'],
}),
new Breadcrumb({
type: BreadcrumbType.PROJECT,
name: '',
param: { key: ROUTEPARAM, value: this.projectId },
routerLink: ['/projects', this.projectId],
}),
];
this.breadcrumbService.setBreadcrumb(breadcrumbs);
});
}
public ngOnDestroy(): void {
this.routeSubscription.unsubscribe();
this.destroy$.next();
this.destroy$.complete();
}
public searchOrg(domain: string): void {
this.mgmtService.getOrgByDomainGlobal(domain).then((ret) => {
this.mgmtService
.getOrgByDomainGlobal(domain)
.then((ret) => {
if (ret.org) {
const tmp = ret.org;
this.authService.getActiveOrg().then((org) => {
if (tmp !== org) {
this.org = tmp;
}
});
this.org = ret.org;
}
}).catch(error => {
})
.catch((error) => {
this.toast.showError(error);
});
}
@ -67,13 +82,13 @@ export class ProjectGrantCreateComponent implements OnInit, OnDestroy {
.then(() => {
this.close();
})
.catch(error => {
.catch((error) => {
this.toast.showError(error);
});
}
public selectRoles(roles: Role.AsObject[]): void {
this.rolesKeyList = roles.map(role => role.key);
this.rolesKeyList = roles.map((role) => role.key);
}
public next(): void {
@ -84,4 +99,3 @@ export class ProjectGrantCreateComponent implements OnInit, OnDestroy {
this.currentCreateStep--;
}
}

View File

@ -77,6 +77,8 @@ import {
GetPreviewLabelPolicyResponse,
GetPrivacyPolicyRequest,
GetPrivacyPolicyResponse,
GetSecretGeneratorRequest,
GetSecretGeneratorResponse,
GetSMSProviderRequest,
GetSMSProviderResponse,
GetSMTPConfigRequest,
@ -98,6 +100,8 @@ import {
ListLoginPolicyMultiFactorsResponse,
ListLoginPolicySecondFactorsRequest,
ListLoginPolicySecondFactorsResponse,
ListSecretGeneratorsRequest,
ListSecretGeneratorsResponse,
ListSMSProvidersRequest,
ListSMSProvidersResponse,
ListViewsRequest,
@ -174,6 +178,8 @@ import {
UpdatePasswordComplexityPolicyResponse,
UpdatePrivacyPolicyRequest,
UpdatePrivacyPolicyResponse,
UpdateSecretGeneratorRequest,
UpdateSecretGeneratorResponse,
UpdateSMTPConfigPasswordRequest,
UpdateSMTPConfigPasswordResponse,
UpdateSMTPConfigRequest,
@ -579,6 +585,21 @@ export class AdminService {
return this.grpcService.admin.getFileSystemNotificationProvider(req, null).then((resp) => resp.toObject());
}
/* secrets generator */
public listSecretGenerators(): Promise<ListSecretGeneratorsResponse.AsObject> {
const req = new ListSecretGeneratorsRequest();
return this.grpcService.admin.listSecretGenerators(req, null).then((resp) => resp.toObject());
}
public getSecretGenerator(req: GetSecretGeneratorRequest): Promise<GetSecretGeneratorResponse.AsObject> {
return this.grpcService.admin.getSecretGenerator(req, null).then((resp) => resp.toObject());
}
public updateSecretGenerator(req: UpdateSecretGeneratorRequest): Promise<UpdateSecretGeneratorResponse.AsObject> {
return this.grpcService.admin.updateSecretGenerator(req, null).then((resp) => resp.toObject());
}
/* org iam */
public getCustomOrgIAMPolicy(orgId: string): Promise<GetCustomOrgIAMPolicyResponse.AsObject> {

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { MatSnackBar, MatSnackBarHorizontalPosition, MatSnackBarVerticalPosition } from '@angular/material/snack-bar';
import { TranslateService } from '@ngx-translate/core';
import { Observable } from 'rxjs';
import { take } from 'rxjs/operators';
@ -8,22 +8,26 @@ import { take } from 'rxjs/operators';
providedIn: 'root',
})
export class ToastService {
constructor(
private snackBar: MatSnackBar,
private translate: TranslateService,
) {}
horizontalPosition: MatSnackBarHorizontalPosition = 'end';
verticalPosition: MatSnackBarVerticalPosition = 'top';
constructor(private snackBar: MatSnackBar, private translate: TranslateService) {}
public showInfo(message: string, i18nkey: boolean = false): void {
if (i18nkey) {
this.translate.get(message).subscribe((data) => {
this.translate
.get(message)
.subscribe(data => {
this.translate.get('ACTIONS.CLOSE').pipe(take(1)).subscribe(value => {
.get('ACTIONS.CLOSE')
.pipe(take(1))
.subscribe((value) => {
this.showMessage(data, value, true);
});
});
} else {
this.translate.get('ACTIONS.CLOSE').pipe(take(1)).subscribe(value => {
this.translate
.get('ACTIONS.CLOSE')
.pipe(take(1))
.subscribe((value) => {
this.showMessage(message, value, true);
});
}
@ -32,7 +36,10 @@ export class ToastService {
public showError(grpcError: any): void {
const { message, code, metadata } = grpcError;
if (code !== 16) {
this.translate.get('ACTIONS.CLOSE').pipe(take(1)).subscribe(value => {
this.translate
.get('ACTIONS.CLOSE')
.pipe(take(1))
.subscribe((value) => {
this.showMessage(decodeURI(message), value, false);
});
}
@ -40,8 +47,13 @@ export class ToastService {
private showMessage(message: string, action: string, success: boolean): Observable<void> {
const ref = this.snackBar.open(message, action, {
duration: 4000,
panelClass: success ? "data-e2e-success" : "data-e2e-failure"
data: {
message,
},
duration: success ? 4000 : 5000,
panelClass: success ? 'data-e2e-success' : 'data-e2e-failure',
horizontalPosition: this.horizontalPosition,
verticalPosition: this.verticalPosition,
});
return ref.onAction();

View File

@ -821,7 +821,8 @@
"LOGINTEXTS": "Login Interface Texte",
"BRANDING": "Branding",
"PRIVACYPOLICY": "Datenschutzrichtlinie",
"OIDC": "OIDC Konfiguration"
"OIDC": "OIDC Token Lifetime und Expiration",
"SECRETS": "Secret Erscheinungsbild"
},
"GROUPS": {
"NOTIFICATIONS": "Benachrichtigungen",
@ -869,6 +870,27 @@
"REFRESHTOKENIDLEEXPIRATION": "Refresh Token Idle Expiration",
"INHOURS": "Stunden",
"INDAYS": "Tage"
},
"SECRETS": {
"TITLE": "Secret Erscheinungsbild",
"TYPES": "Schlüsseltypen",
"TYPE": {
"1": "Email Initialisierungscode",
"2": "Email Verifikationscode",
"3": "Telefonnummer Verificationscode",
"4": "Passwort Zurücksetzen Code",
"5": "Passwordless Initialisierungscode",
"6": "Applicationssecret"
},
"ADDGENERATOR": "Secret Erscheinungsbild definieren",
"GENERATORTYPE": "Typ",
"EXPIRY": "Ablauf (in Stunden)",
"INCLUDEDIGITS": "Enthält Zahlen",
"INCLUDESYMBOLS": "Enthält Symbole",
"INCLUDELOWERLETTERS": "Enthält Kleinbuchstaben",
"INCLUDEUPPERLETTERS": "Enthält Grossbuchstaben",
"LENGTH": "Länge",
"UPDATED": "Einstellungen geändert"
}
},
"POLICY": {

View File

@ -821,7 +821,8 @@
"LOGINTEXTS": "Login Interface Texts",
"BRANDING": "Branding",
"PRIVACYPOLICY": "Privacy Policy",
"OIDC": "OIDC Configuration"
"OIDC": "OIDC Token lifetime and expiration",
"SECRETS": "Secret Appearance"
},
"GROUPS": {
"NOTIFICATIONS": "Notifications",
@ -869,6 +870,27 @@
"REFRESHTOKENIDLEEXPIRATION": "Refresh Token Idle Expiration",
"INHOURS": "hours",
"INDAYS": "Days"
},
"SECRETS": {
"TITLE": "Secret Appearance",
"TYPES": "Secret Types",
"TYPE": {
"1": "Initialization Mail",
"2": "Email verification",
"3": "Phone verification",
"4": "Password Reset",
"5": "Passwordless Initialization",
"6": "App Secret"
},
"ADDGENERATOR": "Define Secret Appearance",
"GENERATORTYPE": "Type",
"EXPIRY": "Expiration (in hours)",
"INCLUDEDIGITS": "Include Numbers",
"INCLUDESYMBOLS": "Include Symbols",
"INCLUDELOWERLETTERS": "Include Lower letters",
"INCLUDEUPPERLETTERS": "Include Upper letters",
"LENGTH": "Length",
"UPDATED": "Settings updated."
}
},
"POLICY": {

View File

@ -821,7 +821,8 @@
"LOGINTEXTS": "Testi dell'interfaccia login",
"BRANDING": "Branding",
"PRIVACYPOLICY": "Informativa sulla privacy e TOS",
"OIDC": "OIDC Configuration"
"OIDC": "OIDC Token lifetime e scadenza",
"SECRETS": "Aspetto dei segreti"
},
"GROUPS": {
"NOTIFICATIONS": "Notifiche",
@ -869,6 +870,27 @@
"REFRESHTOKENIDLEEXPIRATION": "Refresh Token Idle Expiration",
"INHOURS": "ore",
"INDAYS": "giorni"
},
"SECRETS": {
"TITLE": "Aspetto dei segreti",
"TYPES": "Tipi di segreti",
"TYPE": {
"1": "Initializzazione email",
"2": "Verificazione dell' email",
"3": "Verificazione del numero di telefono",
"4": "Ripristino Password",
"5": "Inizializzazione Passwordless",
"6": "Segreto dell'applicazione"
},
"ADDGENERATOR": "Definisci aspetto",
"GENERATORTYPE": "Tipo",
"EXPIRY": "Scadenza (in ore)",
"INCLUDEDIGITS": "Contiene numeri",
"INCLUDESYMBOLS": "Contiene simboli",
"INCLUDELOWERLETTERS": "Contiene lettere minuscole",
"INCLUDEUPPERLETTERS": "Contiene lettere maiuscole",
"LENGTH": "Lunghezza",
"UPDATED": "Impostazioni aggiornati"
}
},
"POLICY": {

View File

@ -12,6 +12,7 @@
@import 'src/app/modules/app-card/app-card.component';
@import 'src/app/modules/contributors/contributors.component';
@import 'src/app/modules/nav/nav.component';
@import './styles/toast.scss';
@import 'src/app/modules/table-actions/table-actions.component';
@import 'src/app/modules/org-context/org-context.component.scss';
@import 'src/app/modules/action-keys/action-keys.component.scss';
@ -95,6 +96,7 @@
@include user-detail-theme($theme);
@include instance-detail-theme($theme);
@include meta-theme($theme);
@include toast-theme($theme);
@include keyboard-shortcuts-theme($theme);
@include project-grant-illustration-theme($theme);
@include refresh-table-theme($theme);

View File

@ -0,0 +1,22 @@
@use '@angular/material' as mat;
@mixin toast-theme($theme) {
$primary: map-get($theme, primary);
$primary-color: mat.get-color-from-palette($primary, 500);
$primary-light-color: mat.get-color-from-palette($primary, 200);
$warn: map-get($theme, warn);
$warn-color: mat.get-color-from-palette($warn, 500);
$background: map-get($theme, background);
$foreground: map-get($theme, foreground);
$is-dark-theme: map-get($theme, is-dark);
// .data-e2e-success {
// background-color: map-get($background, cards) !important;
// color: var(--success) !important;
// }
.data-e2e-failure {
background-color: $warn-color !important;
color: map-get($foreground, text) !important;
}
}