mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-22 09:17:43 +00:00
feat: Web Keys management (#9526)
# Which Problems Are Solved Adds Web Keys managment to the Instance settings. # How the Problems Are Solved Added new page to the Instance settings.  # Additional Changes Removed dataSize input from refresh table as it's not actually used anywhere. # Additional Context It should always be **Web Keys** not Webkeys etc. as that is the official naming, if I missed it somewhere please mark it. At the moment the code won't compile because the @zitadel/client package is not released with the new functionality. In the code at someplaces the V3 Alpha proto is referenced because I wasn't able to npm link @zitadel/client and @zitadel/proto at the same time. I will fix this as soon as we have released the @zitadel/client. Translations are also missing at the moment as I'd like @skewis6 to take a look at the english texts first. - Closes #8033 Open Todo's - [ ] Fix V3 Alpha importants once @zitadel/client is released - [ ] Add Translations - [ ] Fix warn dialog with correct translations aswell - [ ] Remove all todo's - [ ] Merge main into this branch --------- Co-authored-by: Livio Spring <livio.a@gmail.com> Co-authored-by: Elio Bischof <elio@zitadel.com> Co-authored-by: Kenta Yamaguchi <56732734+KEY60228@users.noreply.github.com> Co-authored-by: Harsha Reddy <harsha.reddy@klaviyo.com> Co-authored-by: Silvan <27845747+adlerhurst@users.noreply.github.com> Co-authored-by: Livio Spring <livio@zitadel.com> Co-authored-by: Stefan Benz <46600784+stebenz@users.noreply.github.com> Co-authored-by: Max Peintner <max@caos.ch>
This commit is contained in:
@@ -34,7 +34,7 @@
|
||||
"@netlify/framework-info": "^9.8.13",
|
||||
"@ngx-translate/core": "^15.0.0",
|
||||
"@zitadel/client": "^1.0.6",
|
||||
"@zitadel/proto": "^1.0.3",
|
||||
"@zitadel/proto": "1.0.5-sha-47a2ab5",
|
||||
"angular-oauth2-oidc": "^15.0.1",
|
||||
"angularx-qrcode": "^16.0.0",
|
||||
"buffer": "^6.0.3",
|
||||
|
@@ -1,7 +1,6 @@
|
||||
<cnsl-refresh-table
|
||||
[loading]="loading$ | async"
|
||||
(refreshed)="refreshPage()"
|
||||
[dataSize]="dataSource.data.length"
|
||||
[timestamp]="keyResult?.details?.viewTimestamp"
|
||||
[selection]="selection"
|
||||
>
|
||||
|
@@ -6,12 +6,7 @@
|
||||
</div>
|
||||
<p class="events-desc cnsl-secondary-text">{{ 'DESCRIPTIONS.SETTINGS.IAM_EVENTS.DESCRIPTION' | translate }}</p>
|
||||
|
||||
<cnsl-refresh-table
|
||||
[hideRefresh]="true"
|
||||
(refreshed)="refresh()"
|
||||
[dataSize]="dataSource.data.length"
|
||||
[loading]="_loading | async"
|
||||
>
|
||||
<cnsl-refresh-table [hideRefresh]="true" (refreshed)="refresh()" [loading]="_loading | async">
|
||||
<div actions>
|
||||
<cnsl-filter-events (requestChanged)="filterChanged($event)"></cnsl-filter-events>
|
||||
</div>
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<p class="failed-events-desc cnsl-secondary-text">{{ 'DESCRIPTIONS.SETTINGS.IAM_FAILED_EVENTS.DESCRIPTION' | translate }}</p>
|
||||
|
||||
<div class="table-wrapper">
|
||||
<cnsl-refresh-table (refreshed)="loadEvents()" [dataSize]="eventDataSource.data.length" [loading]="loading$ | async">
|
||||
<cnsl-refresh-table (refreshed)="loadEvents()" [loading]="loading$ | async">
|
||||
<table [dataSource]="eventDataSource" mat-table class="table" aria-label="Elements">
|
||||
<ng-container matColumnDef="viewName">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.FAILEDEVENTS.VIEWNAME' | translate }}</th>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<h2>{{ 'DESCRIPTIONS.SETTINGS.IAM_VIEWS.TITLE' | translate }}</h2>
|
||||
<p class="views-desc cnsl-secondary-text">{{ 'DESCRIPTIONS.SETTINGS.IAM_VIEWS.DESCRIPTION' | translate }}</p>
|
||||
|
||||
<cnsl-refresh-table (refreshed)="loadViews()" [dataSize]="dataSource.data.length" [loading]="loading$ | async">
|
||||
<cnsl-refresh-table (refreshed)="loadViews()" [loading]="loading$ | async">
|
||||
<table [dataSource]="dataSource" mat-table class="table views-table" aria-label="Views" matSort>
|
||||
<ng-container matColumnDef="viewName">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'IAM.VIEWS.VIEWNAME' | translate }}</th>
|
||||
|
@@ -1,7 +1,6 @@
|
||||
<cnsl-refresh-table
|
||||
[loading]="loading$ | async"
|
||||
(refreshed)="refreshPage()"
|
||||
[dataSize]="dataSource.data.length"
|
||||
[emitRefreshOnPreviousRoutes]="['/instance/idp/create']"
|
||||
[timestamp]="idpResult?.details?.viewTimestamp"
|
||||
[selection]="selection"
|
||||
|
@@ -1,7 +1,6 @@
|
||||
<cnsl-refresh-table
|
||||
[loading]="loading$ | async"
|
||||
(refreshed)="refreshPage()"
|
||||
[dataSize]="dataSource.data.length"
|
||||
[timestamp]="keyResult?.details?.viewTimestamp"
|
||||
[selection]="selection"
|
||||
>
|
||||
|
@@ -1,7 +1,6 @@
|
||||
<cnsl-refresh-table
|
||||
*ngIf="dataSource"
|
||||
(refreshed)="changePage()"
|
||||
[dataSize]="dataSource.totalResult"
|
||||
[timestamp]="dataSource.viewTimestamp"
|
||||
[selection]="selection"
|
||||
[loading]="dataSource.loading$ | async"
|
||||
|
@@ -1,7 +1,6 @@
|
||||
<cnsl-refresh-table
|
||||
*ngIf="dataSource"
|
||||
(refreshed)="changePage()"
|
||||
[dataSize]="dataSource.totalResult"
|
||||
[timestamp]="dataSource.viewTimestamp"
|
||||
[hideRefresh]="true"
|
||||
[selection]="selection"
|
||||
|
@@ -1,12 +1,7 @@
|
||||
<cnsl-card class="metadata-details" [title]="'DESCRIPTIONS.METADATA_TITLE' | translate" [description]="description">
|
||||
<mat-spinner card-actions class="spinner" diameter="20" *ngIf="loading"></mat-spinner>
|
||||
|
||||
<cnsl-refresh-table
|
||||
*ngIf="dataSource$ | async as dataSource"
|
||||
[loading]="loading"
|
||||
(refreshed)="refresh.emit()"
|
||||
[dataSize]="dataSource.data.length"
|
||||
>
|
||||
<cnsl-refresh-table *ngIf="dataSource$ | async as dataSource" [loading]="loading" (refreshed)="refresh.emit()">
|
||||
<button actions [disabled]="disabled" mat-raised-button color="primary" class="edit" (click)="editClicked.emit()">
|
||||
{{ 'ACTIONS.EDIT' | translate }}
|
||||
</button>
|
||||
|
@@ -1,9 +1,4 @@
|
||||
<cnsl-refresh-table
|
||||
[hideRefresh]="true"
|
||||
(refreshed)="refresh()"
|
||||
[dataSize]="dataSource.data.length"
|
||||
[loading]="loading$ | async"
|
||||
>
|
||||
<cnsl-refresh-table [hideRefresh]="true" (refreshed)="refresh()" [loading]="loading$ | async">
|
||||
<cnsl-filter-org actions (filterChanged)="applySearchQuery($any($event))" (filterOpen)="filterOpen = $event">
|
||||
</cnsl-filter-org>
|
||||
|
||||
|
@@ -36,7 +36,6 @@ export class OrgTableComponent {
|
||||
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
|
||||
public activeOrg!: Org.AsObject;
|
||||
public OrgListSearchKey: any = OrgListSearchKey;
|
||||
public initialLimit: number = 20;
|
||||
public timestamp: Timestamp.AsObject | undefined = undefined;
|
||||
public totalResult: number = 0;
|
||||
|
@@ -1,7 +1,6 @@
|
||||
<cnsl-refresh-table
|
||||
[loading]="loading$ | async"
|
||||
(refreshed)="refreshPage()"
|
||||
[dataSize]="dataSource.data.length"
|
||||
[timestamp]="keyResult?.details?.viewTimestamp"
|
||||
[selection]="selection"
|
||||
>
|
||||
|
@@ -0,0 +1,60 @@
|
||||
<cnsl-card
|
||||
[title]="'DESCRIPTIONS.SETTINGS.WEB_KEYS.CREATE.TITLE' | translate"
|
||||
[description]="'DESCRIPTIONS.SETTINGS.WEB_KEYS.CREATE.DESCRIPTION' | translate"
|
||||
>
|
||||
<cnsl-form-field>
|
||||
<cnsl-label>{{ 'DESCRIPTIONS.SETTINGS.WEB_KEYS.CREATE.KEY_TYPE' | translate }}</cnsl-label>
|
||||
<mat-select [value]="keyType()" (valueChange)="keyType.set($event)">
|
||||
<mat-option value="rsa">RSA</mat-option>
|
||||
<mat-option value="ecdsa">ECDSA</mat-option>
|
||||
<mat-option value="ed25519">ED25519</mat-option>
|
||||
</mat-select>
|
||||
</cnsl-form-field>
|
||||
<ng-container [ngSwitch]="keyType()">
|
||||
<form *ngSwitchCase="'rsa'" [formGroup]="rsaForm" (ngSubmit)="ngSubmit.emit(rsaForm.getRawValue())">
|
||||
<cnsl-form-field>
|
||||
<cnsl-label>{{ 'DESCRIPTIONS.SETTINGS.WEB_KEYS.CREATE.BITS' | translate }}</cnsl-label>
|
||||
<mat-select formControlName="bits">
|
||||
<ng-container *ngFor="let bit of RSABits | keyvalue">
|
||||
<mat-option *ngIf="Number(bit.key) as key" [value]="key">{{
|
||||
$any(bit.value).replace('RSA_BITS_', '')
|
||||
}}</mat-option>
|
||||
</ng-container>
|
||||
</mat-select>
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field>
|
||||
<cnsl-label>{{ 'DESCRIPTIONS.SETTINGS.WEB_KEYS.CREATE.HASHER' | translate }}</cnsl-label>
|
||||
<mat-select formControlName="hasher">
|
||||
<ng-container *ngFor="let hasher of RSAHasher | keyvalue">
|
||||
<mat-option *ngIf="Number(hasher.key) as key" [value]="key">{{
|
||||
$any(hasher.value).replace('RSA_HASHER_', '')
|
||||
}}</mat-option>
|
||||
</ng-container>
|
||||
</mat-select>
|
||||
</cnsl-form-field>
|
||||
<ng-container *ngTemplateOutlet="submitButton; context: { form: rsaForm }" />
|
||||
</form>
|
||||
<form *ngSwitchCase="'ecdsa'" [formGroup]="ecdsaForm" (ngSubmit)="ngSubmit.emit(ecdsaForm.getRawValue())">
|
||||
<cnsl-form-field>
|
||||
<cnsl-label>{{ 'DESCRIPTIONS.SETTINGS.WEB_KEYS.CREATE.CURVE' | translate }}</cnsl-label>
|
||||
<mat-select formControlName="curve">
|
||||
<ng-container *ngFor="let curve of ECDSACurve | keyvalue">
|
||||
<mat-option *ngIf="Number(curve.key) as key" [value]="key">{{
|
||||
$any(curve.value).replace('ECDSA_CURVE_', '')
|
||||
}}</mat-option>
|
||||
</ng-container>
|
||||
</mat-select>
|
||||
</cnsl-form-field>
|
||||
<ng-container *ngTemplateOutlet="submitButton; context: { form: ecdsaForm }" />
|
||||
</form>
|
||||
<form *ngSwitchCase="'ed25519'" (submit)="emitEd25519($event)">
|
||||
<ng-container *ngTemplateOutlet="submitButton" />
|
||||
</form>
|
||||
<ng-template #submitButton let-form="form">
|
||||
<button [disabled]="(loading$ | async) || form?.invalid" mat-raised-button color="primary" type="submit">
|
||||
<mat-spinner diameter="20" *ngIf="loading$ | async"></mat-spinner>
|
||||
<span *ngIf="(loading$ | async) === false || (loading$ | async) === null">{{ 'ACTIONS.CREATE' | translate }}</span>
|
||||
</button>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</cnsl-card>
|
@@ -0,0 +1,60 @@
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, signal, WritableSignal } from '@angular/core';
|
||||
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
import { WebKey } from '@zitadel/proto/zitadel/webkey/v2beta/key_pb';
|
||||
import { ReplaySubject } from 'rxjs';
|
||||
import { RSAHasher, RSABits, ECDSACurve } from '@zitadel/proto/zitadel/webkey/v2beta/key_pb';
|
||||
|
||||
type RawValue<T extends FormGroup> = ReturnType<T['getRawValue']>;
|
||||
|
||||
@Component({
|
||||
selector: 'cnsl-oidc-webkeys-create',
|
||||
templateUrl: './oidc-webkeys-create.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class OidcWebKeysCreateComponent {
|
||||
protected readonly keyType: WritableSignal<NonNullable<WebKey['key']['case']>> = signal('rsa');
|
||||
protected readonly RSAHasher = RSAHasher;
|
||||
protected readonly RSABits = RSABits;
|
||||
protected readonly ECDSACurve = ECDSACurve;
|
||||
protected readonly Number = Number;
|
||||
protected readonly rsaForm = this.buildRsaForm();
|
||||
protected readonly ecdsaForm = this.buildEcdsaForm();
|
||||
protected readonly loading$ = new ReplaySubject<boolean>();
|
||||
|
||||
@Output()
|
||||
public readonly ngSubmit = new EventEmitter<RawValue<typeof this.rsaForm> | RawValue<typeof this.ecdsaForm> | void>();
|
||||
|
||||
@Input()
|
||||
public set loading(loading: boolean) {
|
||||
this.loading$.next(loading);
|
||||
}
|
||||
|
||||
constructor(private readonly fb: FormBuilder) {}
|
||||
|
||||
private buildRsaForm() {
|
||||
return this.fb.group({
|
||||
bits: new FormControl<RSABits>(RSABits.RSA_BITS_2048, {
|
||||
nonNullable: true,
|
||||
validators: [Validators.required],
|
||||
}),
|
||||
hasher: new FormControl<RSAHasher>(RSAHasher.RSA_HASHER_SHA256, {
|
||||
nonNullable: true,
|
||||
validators: [Validators.required],
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
private buildEcdsaForm() {
|
||||
return this.fb.group({
|
||||
curve: new FormControl<ECDSACurve>(ECDSACurve.ECDSA_CURVE_P256, {
|
||||
nonNullable: true,
|
||||
validators: [Validators.required],
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
protected emitEd25519(event: SubmitEvent) {
|
||||
event.preventDefault();
|
||||
this.ngSubmit.emit();
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
<cnsl-card
|
||||
[title]="'DESCRIPTIONS.SETTINGS.WEB_KEYS.PREVIOUS_TABLE.TITLE' | translate"
|
||||
[description]="'DESCRIPTIONS.SETTINGS.WEB_KEYS.PREVIOUS_TABLE.DESCRIPTION' | translate"
|
||||
>
|
||||
<cnsl-refresh-table [hideRefresh]="true" [loading]="(dataSource$ | async) === null">
|
||||
<div class="table-wrapper">
|
||||
<table
|
||||
*ngIf="dataSource$ | async as dataSource"
|
||||
mat-table
|
||||
class="table"
|
||||
aria-label="Elements"
|
||||
[dataSource]="dataSource"
|
||||
>
|
||||
<ng-container matColumnDef="timestamp">
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
{{ 'DESCRIPTIONS.SETTINGS.WEB_KEYS.PREVIOUS_TABLE.DEACTIVATED_ON' | translate }}
|
||||
</th>
|
||||
<td mat-cell *cnslCellDef="let row; dataSource: dataSource">
|
||||
{{ row.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="id">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'APP.PAGES.ID' | translate }}</th>
|
||||
<td mat-cell *cnslCellDef="let row; dataSource: dataSource">
|
||||
{{ row.id }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="type">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'PROJECT.TYPE.TITLE' | translate }}</th>
|
||||
<td mat-cell *cnslCellDef="let row; dataSource: dataSource">
|
||||
{{ row.key.case | uppercase }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="['timestamp', 'id', 'type']"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: ['timestamp', 'id', 'type']"></tr>
|
||||
</table>
|
||||
</div>
|
||||
</cnsl-refresh-table>
|
||||
</cnsl-card>
|
@@ -0,0 +1,23 @@
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||
import { ReplaySubject } from 'rxjs';
|
||||
import { filter, map } from 'rxjs/operators';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { WebKey } from '@zitadel/proto/zitadel/webkey/v2beta/key_pb';
|
||||
|
||||
@Component({
|
||||
selector: 'cnsl-oidc-webkeys-inactive-table',
|
||||
templateUrl: './oidc-webkeys-inactive-table.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class OidcWebKeysInactiveTableComponent {
|
||||
@Input({ required: true })
|
||||
public set InactiveWebKeys(webKeys: WebKey[] | null) {
|
||||
this.inactiveWebKeys$.next(webKeys);
|
||||
}
|
||||
|
||||
private inactiveWebKeys$ = new ReplaySubject<WebKey[] | null>(1);
|
||||
protected dataSource$ = this.inactiveWebKeys$.pipe(
|
||||
filter(Boolean),
|
||||
map((webKeys) => new MatTableDataSource(webKeys)),
|
||||
);
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
<cnsl-refresh-table (refreshed)="refresh.emit()" [loading]="(dataSource$ | async) === null">
|
||||
<div actions>
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
<div class="table-wrapper">
|
||||
<table *ngIf="dataSource$ | async as dataSource" mat-table class="table" aria-label="Elements" [dataSource]="dataSource">
|
||||
<ng-container matColumnDef="state">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'APP.PAGES.STATE' | translate }}</th>
|
||||
<td mat-cell *cnslCellDef="let row; let i = index; dataSource: dataSource">
|
||||
<span
|
||||
class="state"
|
||||
[ngClass]="{
|
||||
active: i === 0,
|
||||
neutral: i === 1,
|
||||
}"
|
||||
[ngSwitch]="i"
|
||||
>
|
||||
<ng-container *ngSwitchCase="0">{{ 'DESCRIPTIONS.SETTINGS.WEB_KEYS.TABLE.ACTIVE' | translate }}</ng-container>
|
||||
<ng-container *ngSwitchCase="1">{{ 'DESCRIPTIONS.SETTINGS.WEB_KEYS.TABLE.NEXT' | translate }}</ng-container>
|
||||
<ng-container *ngSwitchDefault>{{ 'DESCRIPTIONS.SETTINGS.WEB_KEYS.TABLE.FUTURE' | translate }}</ng-container>
|
||||
</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="id">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'APP.PAGES.ID' | translate }}</th>
|
||||
<td mat-cell *cnslCellDef="let row; dataSource: dataSource">
|
||||
{{ row.id }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="type">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'PROJECT.TYPE.TITLE' | translate }}</th>
|
||||
<td mat-cell *cnslCellDef="let row; dataSource: dataSource">
|
||||
{{ row.key.case | uppercase }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="actions" stickyEnd>
|
||||
<th mat-header-cell *matHeaderCellDef></th>
|
||||
<td mat-cell *cnslCellDef="let row; dataSource: dataSource">
|
||||
<cnsl-table-actions *ngIf="row.state === State.INITIAL">
|
||||
<button
|
||||
actions
|
||||
matTooltip="{{ 'ACTIONS.REMOVE' | translate }}"
|
||||
color="warn"
|
||||
(click)="delete.emit(row)"
|
||||
mat-icon-button
|
||||
>
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
</cnsl-table-actions>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="['state', 'id', 'type', 'actions']"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: ['state', 'id', 'type', 'actions']"></tr>
|
||||
</table>
|
||||
</div>
|
||||
</cnsl-refresh-table>
|
@@ -0,0 +1,4 @@
|
||||
.state.next {
|
||||
color: #0e6245;
|
||||
border: 1px solid #0e6245;
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { ReplaySubject } from 'rxjs';
|
||||
import { filter, map } from 'rxjs/operators';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { State, WebKey } from '@zitadel/proto/zitadel/webkey/v2beta/key_pb';
|
||||
|
||||
@Component({
|
||||
selector: 'cnsl-oidc-webkeys-table',
|
||||
templateUrl: './oidc-webkeys-table.component.html',
|
||||
styleUrls: ['./oidc-webkeys-table.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class OidcWebKeysTableComponent {
|
||||
@Output()
|
||||
public readonly refresh = new EventEmitter<void>();
|
||||
|
||||
@Output()
|
||||
public readonly delete = new EventEmitter<WebKey>();
|
||||
|
||||
@Input({ required: true })
|
||||
public set webKeys(webKeys: WebKey[] | null) {
|
||||
this.webKeys$.next(webKeys);
|
||||
}
|
||||
|
||||
private readonly webKeys$ = new ReplaySubject<WebKey[] | null>(1);
|
||||
protected readonly dataSource$ = this.webKeys$.pipe(
|
||||
filter(Boolean),
|
||||
map((keys) => new MatTableDataSource(keys)),
|
||||
);
|
||||
protected readonly State = State;
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
<h2>{{ 'SETTINGS.LIST.WEB_KEYS' | translate }}</h2>
|
||||
<p>{{ 'DESCRIPTIONS.SETTINGS.WEB_KEYS.DESCRIPTION' | translate }}</p>
|
||||
<h3>{{ 'DESCRIPTIONS.SETTINGS.WEB_KEYS.TABLE.TITLE' | translate }}</h3>
|
||||
<p>{{ 'DESCRIPTIONS.SETTINGS.WEB_KEYS.TABLE.DESCRIPTION' | translate }}</p>
|
||||
<p>{{ 'DESCRIPTIONS.SETTINGS.WEB_KEYS.TABLE.NOTE' | translate }}</p>
|
||||
<cnsl-oidc-webkeys-table (refresh)="refresh.next(true)" (delete)="deleteWebKey($event)" [webKeys]="webKeys$ | async">
|
||||
<button
|
||||
*ngIf="nextWebKeyCandidate$ | async as nextWebKeyCandidate"
|
||||
[disabled]="activateLoading()"
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
(click)="activateWebKey(nextWebKeyCandidate)"
|
||||
>
|
||||
<mat-spinner diameter="20" *ngIf="activateLoading()"></mat-spinner>
|
||||
<span *ngIf="!activateLoading()">{{ 'DESCRIPTIONS.SETTINGS.WEB_KEYS.TABLE.ACTIVATE' | translate }}</span>
|
||||
</button>
|
||||
</cnsl-oidc-webkeys-table>
|
||||
<cnsl-oidc-webkeys-create [loading]="createLoading()" (ngSubmit)="createWebKey($event)" />
|
||||
<cnsl-oidc-webkeys-inactive-table [InactiveWebKeys]="inactiveWebKeys$ | async" />
|
@@ -0,0 +1,217 @@
|
||||
import { ChangeDetectionStrategy, Component, DestroyRef, OnInit, signal } from '@angular/core';
|
||||
import { WebKeysService } from 'src/app/services/webkeys.service';
|
||||
import { defer, EMPTY, firstValueFrom, Observable, ObservedValueOf, of, shareReplay, Subject, switchMap } from 'rxjs';
|
||||
import { catchError, map, startWith, withLatestFrom } from 'rxjs/operators';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
import { MessageInitShape } from '@bufbuild/protobuf';
|
||||
import { OidcWebKeysCreateComponent } from './oidc-webkeys-create/oidc-webkeys-create.component';
|
||||
import { TimestampToDatePipe } from 'src/app/pipes/timestamp-to-date-pipe/timestamp-to-date.pipe';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { State, WebKey } from '@zitadel/proto/zitadel/webkey/v2beta/key_pb';
|
||||
import { CreateWebKeyRequestSchema } from '@zitadel/proto/zitadel/webkey/v2beta/webkey_service_pb';
|
||||
import { RSAHasher, RSABits, ECDSACurve } from '@zitadel/proto/zitadel/webkey/v2beta/key_pb';
|
||||
import { NewFeatureService } from 'src/app/services/new-feature.service';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
|
||||
const CACHE_WARNING_MS = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
@Component({
|
||||
selector: 'cnsl-oidc-webkeys',
|
||||
templateUrl: './oidc-webkeys.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class OidcWebKeysComponent implements OnInit {
|
||||
protected readonly refresh = new Subject<true>();
|
||||
protected readonly webKeysEnabled$: Observable<boolean>;
|
||||
protected readonly webKeys$: Observable<WebKey[]>;
|
||||
protected readonly inactiveWebKeys$: Observable<WebKey[]>;
|
||||
protected readonly nextWebKeyCandidate$: Observable<WebKey | undefined>;
|
||||
|
||||
protected readonly activateLoading = signal(false);
|
||||
protected readonly createLoading = signal(false);
|
||||
|
||||
constructor(
|
||||
private readonly webKeysService: WebKeysService,
|
||||
private readonly featureService: NewFeatureService,
|
||||
private readonly toast: ToastService,
|
||||
private readonly timestampToDatePipe: TimestampToDatePipe,
|
||||
private readonly dialog: MatDialog,
|
||||
private readonly destroyRef: DestroyRef,
|
||||
private readonly router: Router,
|
||||
private readonly route: ActivatedRoute,
|
||||
) {
|
||||
this.webKeysEnabled$ = this.getWebKeysEnabled().pipe(shareReplay({ refCount: true, bufferSize: 1 }));
|
||||
|
||||
const webKeys$ = this.getWebKeys(this.webKeysEnabled$).pipe(shareReplay({ refCount: true, bufferSize: 1 }));
|
||||
|
||||
this.webKeys$ = webKeys$.pipe(map((webKeys) => webKeys.filter((webKey) => webKey.state !== State.INACTIVE)));
|
||||
this.inactiveWebKeys$ = webKeys$.pipe(map((webKeys) => webKeys.filter((webKey) => webKey.state === State.INACTIVE)));
|
||||
|
||||
this.nextWebKeyCandidate$ = this.getNextWebKeyCandidate(this.webKeys$);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
// redirect away from this page if web keys are not enabled
|
||||
// this also preloads the web keys enabled state
|
||||
this.webKeysEnabled$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(async (webKeysEnabled) => {
|
||||
if (webKeysEnabled) {
|
||||
return;
|
||||
}
|
||||
await this.router.navigate([], {
|
||||
relativeTo: this.route,
|
||||
queryParamsHandling: 'merge',
|
||||
queryParams: {
|
||||
id: null,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private getWebKeysEnabled() {
|
||||
return defer(() => this.featureService.getInstanceFeatures()).pipe(
|
||||
map((features) => features.webKey?.enabled ?? false),
|
||||
catchError((err) => {
|
||||
this.toast.showError(err);
|
||||
return of(false);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private getWebKeys(webKeysEnabled$: Observable<boolean>) {
|
||||
return this.refresh.pipe(
|
||||
startWith(true),
|
||||
switchMap(() => {
|
||||
return this.webKeysService.ListWebKeys();
|
||||
}),
|
||||
map(({ webKeys }) => webKeys),
|
||||
catchError(async (err) => {
|
||||
const webKeysEnabled = await firstValueFrom(webKeysEnabled$);
|
||||
// suppress errors if web keys are not enabled
|
||||
if (!webKeysEnabled) {
|
||||
return [];
|
||||
}
|
||||
|
||||
this.toast.showError(err);
|
||||
return [];
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private getNextWebKeyCandidate(webKeys$: Observable<WebKey[]>) {
|
||||
return webKeys$.pipe(
|
||||
map((webKeys) => {
|
||||
if (webKeys.length < 2) {
|
||||
return undefined;
|
||||
}
|
||||
const [webKey, nextWebKey] = webKeys;
|
||||
if (webKey.state !== State.ACTIVE) {
|
||||
return undefined;
|
||||
}
|
||||
if (nextWebKey.state !== State.INITIAL) {
|
||||
return undefined;
|
||||
}
|
||||
return nextWebKey;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
protected async createWebKey(event: ObservedValueOf<OidcWebKeysCreateComponent['ngSubmit']>) {
|
||||
try {
|
||||
this.createLoading.set(true);
|
||||
|
||||
const req = !event
|
||||
? this.createEd25519()
|
||||
: 'curve' in event
|
||||
? this.createEcdsa(event.curve)
|
||||
: this.createRsa(event.bits, event.hasher);
|
||||
await this.webKeysService.CreateWebKey(req);
|
||||
|
||||
this.refresh.next(true);
|
||||
} catch (error) {
|
||||
this.toast.showError(error);
|
||||
} finally {
|
||||
this.createLoading.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
private createEd25519(): MessageInitShape<typeof CreateWebKeyRequestSchema> {
|
||||
return {
|
||||
key: {
|
||||
case: 'ed25519',
|
||||
value: {},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private createEcdsa(curve: ECDSACurve): MessageInitShape<typeof CreateWebKeyRequestSchema> {
|
||||
return {
|
||||
key: {
|
||||
case: 'ecdsa',
|
||||
value: {
|
||||
curve,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private createRsa(bits: RSABits, hasher: RSAHasher): MessageInitShape<typeof CreateWebKeyRequestSchema> {
|
||||
return {
|
||||
key: {
|
||||
case: 'rsa',
|
||||
value: {
|
||||
bits,
|
||||
hasher,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected async deleteWebKey(row: WebKey) {
|
||||
try {
|
||||
await this.webKeysService.DeleteWebKey(row.id);
|
||||
this.refresh.next(true);
|
||||
} catch (err) {
|
||||
this.toast.showError(err);
|
||||
}
|
||||
}
|
||||
|
||||
protected async activateWebKey(nextWebKey: WebKey) {
|
||||
try {
|
||||
this.activateLoading.set(true);
|
||||
const creationDate = this.timestampToDatePipe.transform(nextWebKey.creationDate);
|
||||
if (!creationDate) {
|
||||
// noinspection ExceptionCaughtLocallyJS
|
||||
throw new Error('Invalid creation date');
|
||||
}
|
||||
|
||||
const diffToCurrentTime = Date.now() - creationDate.getTime();
|
||||
if (diffToCurrentTime < CACHE_WARNING_MS && !(await this.openCacheWarnDialog())) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.webKeysService.ActivateWebKey(nextWebKey.id);
|
||||
this.refresh.next(true);
|
||||
} catch (error) {
|
||||
this.toast.showError(error);
|
||||
} finally {
|
||||
this.activateLoading.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
private openCacheWarnDialog() {
|
||||
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
||||
data: {
|
||||
confirmKey: 'DESCRIPTIONS.SETTINGS.WEB_KEYS.TABLE.ACTIVATE',
|
||||
cancelKey: 'ACTIONS.CANCEL',
|
||||
titleKey: 'Web Key is less then 5 min old',
|
||||
descriptionKey: 'DESCRIPTIONS.SETTINGS.WEB_KEYS.TABLE.NOTE',
|
||||
},
|
||||
width: '400px',
|
||||
});
|
||||
|
||||
const obs = dialogRef.afterClosed().pipe(map(Boolean), takeUntilDestroyed(this.destroyRef));
|
||||
return firstValueFrom(obs);
|
||||
}
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { OidcWebKeysComponent } from './oidc-webkeys.component';
|
||||
import { RefreshTableModule } from 'src/app/modules/refresh-table/refresh-table.module';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { TableActionsModule } from 'src/app/modules/table-actions/table-actions.module';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { ActionKeysModule } from 'src/app/modules/action-keys/action-keys.module';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { FormFieldModule } from 'src/app/modules/form-field/form-field.module';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { CardModule } from 'src/app/modules/card/card.module';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { OidcWebKeysCreateComponent } from './oidc-webkeys-create/oidc-webkeys-create.component';
|
||||
import { OidcWebKeysTableComponent } from './oidc-webkeys-table/oidc-webkeys-table.component';
|
||||
import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe/timestamp-to-date-pipe.module';
|
||||
import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe/localized-date-pipe.module';
|
||||
import { OidcWebKeysInactiveTableComponent } from './oidc-webkeys-inactive-table/oidc-webkeys-inactive-table.component';
|
||||
import { TypeSafeCellDefDirective } from './type-safe-cell-def.directive';
|
||||
import { TimestampToDatePipe } from '../../../pipes/timestamp-to-date-pipe/timestamp-to-date.pipe';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
OidcWebKeysComponent,
|
||||
OidcWebKeysCreateComponent,
|
||||
OidcWebKeysTableComponent,
|
||||
OidcWebKeysInactiveTableComponent,
|
||||
TypeSafeCellDefDirective,
|
||||
],
|
||||
providers: [TimestampToDatePipe],
|
||||
imports: [
|
||||
CommonModule,
|
||||
TranslateModule,
|
||||
RefreshTableModule,
|
||||
MatCheckboxModule,
|
||||
MatTableModule,
|
||||
MatMenuModule,
|
||||
TableActionsModule,
|
||||
MatButtonModule,
|
||||
ActionKeysModule,
|
||||
MatIconModule,
|
||||
FormFieldModule,
|
||||
MatSelectModule,
|
||||
ReactiveFormsModule,
|
||||
CardModule,
|
||||
MatProgressSpinnerModule,
|
||||
TimestampToDatePipeModule,
|
||||
LocalizedDatePipeModule,
|
||||
MatTooltipModule,
|
||||
],
|
||||
exports: [OidcWebKeysComponent],
|
||||
})
|
||||
export class OidcWebkeysModule {}
|
@@ -0,0 +1,16 @@
|
||||
import { Directive, Input } from '@angular/core';
|
||||
import { DataSource } from '@angular/cdk/collections';
|
||||
import { MatCellDef } from '@angular/material/table';
|
||||
import { CdkCellDef } from '@angular/cdk/table';
|
||||
|
||||
@Directive({
|
||||
selector: '[cnslCellDef]',
|
||||
providers: [{ provide: CdkCellDef, useExisting: TypeSafeCellDefDirective }],
|
||||
})
|
||||
export class TypeSafeCellDefDirective<T> extends MatCellDef {
|
||||
@Input({ required: true }) cnslCellDefDataSource!: DataSource<T>;
|
||||
|
||||
static ngTemplateContextGuard<T>(_dir: TypeSafeCellDefDirective<T>, _ctx: any): _ctx is { $implicit: T; index: number } {
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -2,7 +2,6 @@
|
||||
[showSelectionActionButton]="showSelectionActionButton"
|
||||
*ngIf="projectId"
|
||||
(refreshed)="refreshPage()"
|
||||
[dataSize]="dataSource.totalResult"
|
||||
[emitRefreshOnPreviousRoutes]="['/projects/' + projectId + '/roles/create']"
|
||||
[selection]="selection"
|
||||
[loading]="dataSource.loading$ | async"
|
||||
|
@@ -29,7 +29,6 @@ const rotate = animation([
|
||||
export class RefreshTableComponent implements OnInit {
|
||||
@Input() public selection: SelectionModel<any> = new SelectionModel<any>(true, []);
|
||||
@Input() public timestamp: Timestamp.AsObject | ConnectTimestamp | undefined = undefined;
|
||||
@Input() public dataSize: number = 0;
|
||||
@Input() public emitRefreshAfterTimeoutInMs: number = 0;
|
||||
@Input() public loading: boolean | null = false;
|
||||
@Input() public emitRefreshOnPreviousRoutes: string[] = [];
|
||||
|
@@ -49,6 +49,9 @@
|
||||
<ng-container *ngIf="currentSetting === 'oidc' && serviceType === PolicyComponentServiceType.ADMIN">
|
||||
<cnsl-oidc-configuration></cnsl-oidc-configuration>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="currentSetting === 'webkeys' && serviceType === PolicyComponentServiceType.ADMIN">
|
||||
<cnsl-oidc-webkeys />
|
||||
</ng-container>
|
||||
<ng-container *ngIf="currentSetting === 'secrets' && serviceType === PolicyComponentServiceType.ADMIN">
|
||||
<cnsl-secret-generator></cnsl-secret-generator>
|
||||
</ng-container>
|
||||
|
@@ -31,6 +31,7 @@ import { OrgTableModule } from '../org-table/org-table.module';
|
||||
import { NotificationSMTPProviderModule } from '../policies/notification-smtp-provider/notification-smtp-provider.module';
|
||||
import { FeaturesComponent } from 'src/app/components/features/features.component';
|
||||
import OrgListModule from 'src/app/pages/org-list/org-list.module';
|
||||
import { OidcWebkeysModule } from '../policies/oidc-webkeys/oidc-webkeys.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [SettingsListComponent],
|
||||
@@ -62,6 +63,7 @@ import OrgListModule from 'src/app/pages/org-list/org-list.module';
|
||||
NotificationSMTPProviderModule,
|
||||
NotificationSMSProviderModule,
|
||||
OIDCConfigurationModule,
|
||||
OidcWebkeysModule,
|
||||
SecretGeneratorModule,
|
||||
FailedEventsModule,
|
||||
IamViewsModule,
|
||||
|
@@ -35,6 +35,14 @@ export const OIDC: SidenavSetting = {
|
||||
},
|
||||
};
|
||||
|
||||
export const WEBKEYS: SidenavSetting = {
|
||||
id: 'webkeys',
|
||||
i18nKey: 'SETTINGS.LIST.WEB_KEYS',
|
||||
requiredRoles: {
|
||||
[PolicyComponentServiceType.ADMIN]: ['iam.policy.read'],
|
||||
},
|
||||
};
|
||||
|
||||
export const SECRETS: SidenavSetting = {
|
||||
id: 'secrets',
|
||||
i18nKey: 'SETTINGS.LIST.SECRETS',
|
||||
|
@@ -1,7 +1,6 @@
|
||||
<cnsl-refresh-table
|
||||
[loading]="loading$ | async"
|
||||
(refreshed)="refreshPage()"
|
||||
[dataSize]="dataSource.data.length"
|
||||
[emitRefreshOnPreviousRoutes]="[
|
||||
'/instance/smtpprovider/aws-ses/create',
|
||||
'/instance/smtpprovider/generic/create',
|
||||
|
@@ -4,7 +4,6 @@
|
||||
[hideRefresh]="true"
|
||||
[emitRefreshOnPreviousRoutes]="refreshOnPreviousRoutes"
|
||||
[timestamp]="dataSource.viewTimestamp"
|
||||
[dataSize]="dataSource.totalResult"
|
||||
[selection]="selection"
|
||||
>
|
||||
<button
|
||||
|
@@ -2,7 +2,6 @@
|
||||
[hideRefresh]="true"
|
||||
[loading]="loading$ | async"
|
||||
(refreshed)="refreshPage()"
|
||||
[dataSize]="dataSource.data.length"
|
||||
[timestamp]="actionsResult?.details?.viewTimestamp"
|
||||
[selection]="selection"
|
||||
>
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Component, DestroyRef } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||
import { BehaviorSubject, from, Observable, of, Subject } from 'rxjs';
|
||||
import { catchError, finalize, map, takeUntil } from 'rxjs/operators';
|
||||
import { BehaviorSubject, defer, from, Observable, of, TimeoutError } from 'rxjs';
|
||||
import { catchError, finalize, map, timeout } from 'rxjs/operators';
|
||||
import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component';
|
||||
import { PolicyComponentServiceType } from 'src/app/modules/policies/policy-component-types.enum';
|
||||
import { InstanceDetail, State } from 'src/app/proto/generated/zitadel/instance_pb';
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
MESSAGETEXTS,
|
||||
NOTIFICATIONS,
|
||||
OIDC,
|
||||
WEBKEYS,
|
||||
PRIVACYPOLICY,
|
||||
SECRETS,
|
||||
SECURITY,
|
||||
@@ -38,22 +39,25 @@ import {
|
||||
import { SidenavSetting } from 'src/app/modules/sidenav/sidenav.component';
|
||||
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||
import { EnvironmentService } from 'src/app/services/environment.service';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { NewFeatureService } from '../../services/new-feature.service';
|
||||
import { withLatestFromSynchronousFix } from '../../utils/withLatestFromSynchronousFix';
|
||||
@Component({
|
||||
selector: 'cnsl-instance',
|
||||
templateUrl: './instance.component.html',
|
||||
styleUrls: ['./instance.component.scss'],
|
||||
})
|
||||
export class InstanceComponent implements OnInit, OnDestroy {
|
||||
public instance?: InstanceDetail.AsObject;
|
||||
public PolicyComponentServiceType: any = PolicyComponentServiceType;
|
||||
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
|
||||
public totalMemberResult: number = 0;
|
||||
public membersSubject: BehaviorSubject<Member.AsObject[]> = new BehaviorSubject<Member.AsObject[]>([]);
|
||||
public State: any = State;
|
||||
export class InstanceComponent {
|
||||
protected instance?: InstanceDetail.AsObject;
|
||||
protected readonly PolicyComponentServiceType = PolicyComponentServiceType;
|
||||
private readonly loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
protected readonly loading$: Observable<boolean> = this.loadingSubject.asObservable();
|
||||
protected totalMemberResult: number = 0;
|
||||
protected readonly membersSubject: BehaviorSubject<Member.AsObject[]> = new BehaviorSubject<Member.AsObject[]>([]);
|
||||
protected readonly State = State;
|
||||
|
||||
public id: string = '';
|
||||
public defaultSettingsList: SidenavSetting[] = [
|
||||
protected id: string = '';
|
||||
protected readonly defaultSettingsList: SidenavSetting[] = [
|
||||
ORGANIZATIONS,
|
||||
FEATURESETTINGS,
|
||||
// notifications
|
||||
@@ -81,23 +85,25 @@ export class InstanceComponent implements OnInit, OnDestroy {
|
||||
PRIVACYPOLICY,
|
||||
LANGUAGES,
|
||||
OIDC,
|
||||
WEBKEYS,
|
||||
SECRETS,
|
||||
SECURITY,
|
||||
];
|
||||
|
||||
public settingsList: Observable<SidenavSetting[]> = of([]);
|
||||
public customerPortalLink$ = this.envService.env.pipe(map((env) => env.customer_portal));
|
||||
protected readonly settingsList: Observable<SidenavSetting[]>;
|
||||
protected readonly customerPortalLink$ = this.envService.env.pipe(map((env) => env.customer_portal));
|
||||
|
||||
private destroy$: Subject<void> = new Subject();
|
||||
constructor(
|
||||
public adminService: AdminService,
|
||||
private dialog: MatDialog,
|
||||
private toast: ToastService,
|
||||
protected readonly adminService: AdminService,
|
||||
private readonly dialog: MatDialog,
|
||||
private readonly toast: ToastService,
|
||||
breadcrumbService: BreadcrumbService,
|
||||
private router: Router,
|
||||
private authService: GrpcAuthService,
|
||||
private envService: EnvironmentService,
|
||||
private readonly router: Router,
|
||||
private readonly authService: GrpcAuthService,
|
||||
private readonly envService: EnvironmentService,
|
||||
activatedRoute: ActivatedRoute,
|
||||
private readonly destroyRef: DestroyRef,
|
||||
private readonly featureService: NewFeatureService,
|
||||
) {
|
||||
this.loadMembers();
|
||||
|
||||
@@ -120,12 +126,39 @@ export class InstanceComponent implements OnInit, OnDestroy {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
|
||||
activatedRoute.queryParams.pipe(takeUntil(this.destroy$)).subscribe((params: Params) => {
|
||||
activatedRoute.queryParams.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((params: Params) => {
|
||||
const { id } = params;
|
||||
if (id) {
|
||||
this.id = id;
|
||||
}
|
||||
});
|
||||
|
||||
this.settingsList = this.getSettingsList();
|
||||
}
|
||||
|
||||
public getSettingsList(): Observable<SidenavSetting[]> {
|
||||
const webKeysEnabled$ = defer(() => this.featureService.getInstanceFeatures()).pipe(
|
||||
map(({ webKey }) => webKey?.enabled ?? false),
|
||||
timeout(1000),
|
||||
catchError((error) => {
|
||||
if (!(error instanceof TimeoutError)) {
|
||||
this.toast.showError(error);
|
||||
}
|
||||
return of(false);
|
||||
}),
|
||||
);
|
||||
|
||||
return this.authService
|
||||
.isAllowedMapper(this.defaultSettingsList, (setting) => setting.requiredRoles.admin || [])
|
||||
.pipe(
|
||||
withLatestFromSynchronousFix(webKeysEnabled$),
|
||||
map(([settings, webKeysEnabled]) => {
|
||||
if (webKeysEnabled) {
|
||||
return settings;
|
||||
}
|
||||
return settings.filter((setting) => setting.id !== WEBKEYS.id);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
public loadMembers(): void {
|
||||
@@ -185,18 +218,6 @@ export class InstanceComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
public showDetail(): void {
|
||||
this.router.navigate(['/instance', 'members']);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.settingsList = this.authService.isAllowedMapper(
|
||||
this.defaultSettingsList,
|
||||
(setting) => setting.requiredRoles.admin || [],
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
this.router.navigate(['/instance', 'members']).then();
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@
|
||||
[loading]="dataSource.loading$ | async"
|
||||
[selection]="selection"
|
||||
(refreshed)="refreshPage()"
|
||||
[dataSize]="dataSource.totalResult"
|
||||
[timestamp]="dataSource.viewTimestamp"
|
||||
>
|
||||
<ng-template cnslHasRole [hasRole]="['project.app.write']" actions>
|
||||
|
@@ -5,7 +5,6 @@
|
||||
[loading]="dataSource.loading$ | async"
|
||||
*ngIf="projectId"
|
||||
(refreshed)="refreshPage()"
|
||||
[dataSize]="dataSource.totalResult"
|
||||
[selection]="selection"
|
||||
[timestamp]="dataSource.viewTimestamp"
|
||||
(refreshed)="getRoleOptions(projectId)"
|
||||
|
@@ -2,7 +2,6 @@
|
||||
<cnsl-refresh-table
|
||||
[hideRefresh]="true"
|
||||
(refreshed)="refreshPage(type)"
|
||||
[dataSize]="totalResult"
|
||||
[timestamp]="viewTimestamp"
|
||||
[selection]="selection"
|
||||
[loading]="loading$ | async"
|
||||
|
@@ -11,12 +11,7 @@
|
||||
>
|
||||
<mat-icon class="icon">refresh</mat-icon>
|
||||
</button>
|
||||
<cnsl-refresh-table
|
||||
[hideRefresh]="true"
|
||||
[loading]="loading$ | async"
|
||||
(refreshed)="getPasswordless()"
|
||||
[dataSize]="dataSource.data.length"
|
||||
>
|
||||
<cnsl-refresh-table [hideRefresh]="true" [loading]="loading$ | async" (refreshed)="getPasswordless()">
|
||||
<button
|
||||
actions
|
||||
class="button"
|
||||
|
@@ -9,12 +9,7 @@
|
||||
<mat-icon class="icon">refresh</mat-icon>
|
||||
</button>
|
||||
|
||||
<cnsl-refresh-table
|
||||
[hideRefresh]="true"
|
||||
[loading]="loading$ | async"
|
||||
(refreshed)="getMFAs()"
|
||||
[dataSize]="dataSource.data.length"
|
||||
>
|
||||
<cnsl-refresh-table [hideRefresh]="true" [loading]="loading$ | async" (refreshed)="getMFAs()">
|
||||
<button
|
||||
actions
|
||||
class="button"
|
||||
|
@@ -8,13 +8,7 @@
|
||||
>
|
||||
<mat-icon class="icon">refresh</mat-icon>
|
||||
</button>
|
||||
<cnsl-refresh-table
|
||||
[hideRefresh]="true"
|
||||
[loading]="loading$ | async"
|
||||
[dataSize]="dataSource.data.length"
|
||||
[timestamp]="viewTimestamp"
|
||||
[selection]="selection"
|
||||
>
|
||||
<cnsl-refresh-table [hideRefresh]="true" [loading]="loading$ | async" [timestamp]="viewTimestamp" [selection]="selection">
|
||||
<div class="table-wrapper">
|
||||
<table class="table" mat-table [dataSource]="dataSource">
|
||||
<ng-container matColumnDef="select">
|
||||
|
@@ -11,7 +11,7 @@
|
||||
>
|
||||
<mat-icon class="icon">refresh</mat-icon>
|
||||
</button>
|
||||
<cnsl-refresh-table [hideRefresh]="true" [loading]="loading$ | async" [dataSize]="dataSource.data.length">
|
||||
<cnsl-refresh-table [hideRefresh]="true" [loading]="loading$ | async">
|
||||
<button
|
||||
actions
|
||||
[disabled]="disabled"
|
||||
|
@@ -12,12 +12,7 @@
|
||||
>
|
||||
<mat-icon class="icon">refresh</mat-icon>
|
||||
</button>
|
||||
<cnsl-refresh-table
|
||||
[hideRefresh]="true"
|
||||
[loading]="mfaQuery.state === 'loading'"
|
||||
(refreshed)="refresh$.next(true)"
|
||||
[dataSize]="mfaQuery.value.data.length"
|
||||
>
|
||||
<cnsl-refresh-table [hideRefresh]="true" [loading]="mfaQuery.state === 'loading'" (refreshed)="refresh$.next(true)">
|
||||
<table class="table" mat-table [dataSource]="mfaQuery.value">
|
||||
<ng-container matColumnDef="type">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'USER.MFA.TABLETYPE' | translate }}</th>
|
||||
|
@@ -2,7 +2,6 @@
|
||||
*ngIf="type$ | async as type"
|
||||
[loading]="loading()"
|
||||
(refreshed)="this.refresh$.next(true)"
|
||||
[dataSize]="dataSize()"
|
||||
[hideRefresh]="true"
|
||||
[timestamp]="(users$ | async)?.details?.timestamp"
|
||||
[selection]="selection"
|
||||
|
@@ -1,18 +1,15 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { Timestamp as ConnectTimestamp } from '@bufbuild/protobuf/wkt';
|
||||
import { Timestamp as BufTimestamp } from '@bufbuild/protobuf/wkt';
|
||||
import { Timestamp } from 'src/app/proto/generated/google/protobuf/timestamp_pb';
|
||||
|
||||
@Pipe({
|
||||
name: 'timestampToDate',
|
||||
})
|
||||
export class TimestampToDatePipe implements PipeTransform {
|
||||
transform(value: ConnectTimestamp | Timestamp.AsObject, ...args: unknown[]): unknown {
|
||||
return this.dateFromTimestamp(value);
|
||||
}
|
||||
|
||||
private dateFromTimestamp(date: ConnectTimestamp | Timestamp.AsObject): any {
|
||||
if (date?.seconds !== undefined && date?.nanos !== undefined) {
|
||||
transform(date: BufTimestamp | Timestamp.AsObject | undefined): Date | undefined {
|
||||
if (date?.seconds && date.nanos) {
|
||||
return new Date(Number(date.seconds) * 1000 + date.nanos / 1000 / 1000);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
@@ -14,15 +14,20 @@ import { ExhaustedService } from './exhausted.service';
|
||||
import { AuthInterceptor, AuthInterceptorProvider, NewConnectWebAuthInterceptor } from './interceptors/auth.interceptor';
|
||||
import { ExhaustedGrpcInterceptor } from './interceptors/exhausted.grpc.interceptor';
|
||||
import { I18nInterceptor } from './interceptors/i18n.interceptor';
|
||||
import { OrgInterceptor } from './interceptors/org.interceptor';
|
||||
import { NewConnectWebOrgInterceptor, OrgInterceptor, OrgInterceptorProvider } from './interceptors/org.interceptor';
|
||||
import { StorageService } from './storage.service';
|
||||
import { UserServiceClient } from '../proto/generated/zitadel/user/v2/User_serviceServiceClientPb';
|
||||
//@ts-ignore
|
||||
import { createUserServiceClient } from '@zitadel/client/v2';
|
||||
import { createFeatureServiceClient, createUserServiceClient } from '@zitadel/client/v2';
|
||||
//@ts-ignore
|
||||
import { createAuthServiceClient, createManagementServiceClient } from '@zitadel/client/v1';
|
||||
import { createGrpcWebTransport } from '@connectrpc/connect-web';
|
||||
import { FeatureServiceClient } from '../proto/generated/zitadel/feature/v2/Feature_serviceServiceClientPb';
|
||||
import { WebKeyService } from '@zitadel/proto/zitadel/webkey/v2beta/webkey_service_pb';
|
||||
// @ts-ignore
|
||||
import { createClientFor } from '@zitadel/client';
|
||||
|
||||
const createWebKeyServiceClient = createClientFor(WebKeyService);
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@@ -36,6 +41,8 @@ export class GrpcService {
|
||||
public userNew!: ReturnType<typeof createUserServiceClient>;
|
||||
public mgmtNew!: ReturnType<typeof createManagementServiceClient>;
|
||||
public authNew!: ReturnType<typeof createAuthServiceClient>;
|
||||
public featureNew!: ReturnType<typeof createFeatureServiceClient>;
|
||||
public webKey!: ReturnType<typeof createWebKeyServiceClient>;
|
||||
|
||||
constructor(
|
||||
private readonly envService: EnvironmentService,
|
||||
@@ -46,6 +53,7 @@ export class GrpcService {
|
||||
private readonly exhaustedService: ExhaustedService,
|
||||
private readonly authInterceptor: AuthInterceptor,
|
||||
private readonly authInterceptorProvider: AuthInterceptorProvider,
|
||||
private readonly orgInterceptorProvider: OrgInterceptorProvider,
|
||||
) {}
|
||||
|
||||
public loadAppEnvironment(): Promise<any> {
|
||||
@@ -62,7 +70,7 @@ export class GrpcService {
|
||||
const interceptors = {
|
||||
unaryInterceptors: [
|
||||
new ExhaustedGrpcInterceptor(this.exhaustedService, this.envService),
|
||||
new OrgInterceptor(this.storageService),
|
||||
new OrgInterceptor(this.orgInterceptorProvider),
|
||||
this.authInterceptor,
|
||||
new I18nInterceptor(this.translate),
|
||||
],
|
||||
@@ -103,9 +111,18 @@ export class GrpcService {
|
||||
baseUrl: env.api,
|
||||
interceptors: [NewConnectWebAuthInterceptor(this.authInterceptorProvider)],
|
||||
});
|
||||
const transportOldAPIs = createGrpcWebTransport({
|
||||
baseUrl: env.api,
|
||||
interceptors: [
|
||||
NewConnectWebAuthInterceptor(this.authInterceptorProvider),
|
||||
NewConnectWebOrgInterceptor(this.orgInterceptorProvider),
|
||||
],
|
||||
});
|
||||
this.userNew = createUserServiceClient(transport);
|
||||
this.mgmtNew = createManagementServiceClient(transport);
|
||||
this.mgmtNew = createManagementServiceClient(transportOldAPIs);
|
||||
this.authNew = createAuthServiceClient(transport);
|
||||
this.featureNew = createFeatureServiceClient(transport);
|
||||
this.webKey = createWebKeyServiceClient(transport);
|
||||
|
||||
const authConfig: AuthConfig = {
|
||||
scope: 'openid profile email',
|
||||
|
@@ -3,32 +3,63 @@ import { Request, RpcError, StatusCode, UnaryInterceptor, UnaryResponse } from '
|
||||
import { Org } from 'src/app/proto/generated/zitadel/org_pb';
|
||||
|
||||
import { StorageKey, StorageLocation, StorageService } from '../storage.service';
|
||||
import { ConnectError, Interceptor } from '@connectrpc/connect';
|
||||
import { firstValueFrom, identity, Observable, Subject } from 'rxjs';
|
||||
import { debounceTime, filter, map } from 'rxjs/operators';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
|
||||
const ORG_HEADER_KEY = 'x-zitadel-orgid';
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class OrgInterceptor<TReq = unknown, TResp = unknown> implements UnaryInterceptor<TReq, TResp> {
|
||||
constructor(private readonly storageService: StorageService) {}
|
||||
constructor(private readonly orgInterceptorProvider: OrgInterceptorProvider) {}
|
||||
|
||||
public async intercept(request: Request<TReq, TResp>, invoker: any): Promise<UnaryResponse<TReq, TResp>> {
|
||||
const metadata = request.getMetadata();
|
||||
|
||||
const org: Org.AsObject | null = this.storageService.getItem(StorageKey.organization, StorageLocation.session);
|
||||
|
||||
if (org) {
|
||||
metadata[ORG_HEADER_KEY] = `${org.id}`;
|
||||
const orgId = this.orgInterceptorProvider.getOrgId();
|
||||
if (orgId) {
|
||||
metadata[ORG_HEADER_KEY] = orgId;
|
||||
}
|
||||
|
||||
try {
|
||||
return await invoker(request);
|
||||
} catch (error: any) {
|
||||
if (
|
||||
error instanceof RpcError &&
|
||||
error.code === StatusCode.PERMISSION_DENIED &&
|
||||
error.message.startsWith("Organisation doesn't exist")
|
||||
) {
|
||||
this.storageService.removeItem(StorageKey.organization, StorageLocation.session);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return invoker(request).catch(this.orgInterceptorProvider.handleError);
|
||||
}
|
||||
}
|
||||
|
||||
export function NewConnectWebOrgInterceptor(orgInterceptorProvider: OrgInterceptorProvider): Interceptor {
|
||||
return (next) => async (req) => {
|
||||
if (!req.header.get(ORG_HEADER_KEY)) {
|
||||
const orgId = orgInterceptorProvider.getOrgId();
|
||||
if (orgId) {
|
||||
req.header.set(ORG_HEADER_KEY, orgId);
|
||||
}
|
||||
}
|
||||
|
||||
return next(req).catch(orgInterceptorProvider.handleError);
|
||||
};
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class OrgInterceptorProvider {
|
||||
constructor(private storageService: StorageService) {}
|
||||
|
||||
getOrgId() {
|
||||
const org: Org.AsObject | null = this.storageService.getItem(StorageKey.organization, StorageLocation.session);
|
||||
return org?.id;
|
||||
}
|
||||
|
||||
handleError = (error: any): never => {
|
||||
if (!(error instanceof RpcError) && !(error instanceof ConnectError)) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (
|
||||
error instanceof RpcError &&
|
||||
error.code === StatusCode.PERMISSION_DENIED &&
|
||||
error.message.startsWith("Organisation doesn't exist")
|
||||
) {
|
||||
this.storageService.removeItem(StorageKey.organization, StorageLocation.session);
|
||||
}
|
||||
|
||||
throw error;
|
||||
};
|
||||
}
|
||||
|
14
console/src/app/services/new-feature.service.ts
Normal file
14
console/src/app/services/new-feature.service.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { GrpcService } from './grpc.service';
|
||||
import { GetInstanceFeaturesResponse } from '@zitadel/proto/zitadel/feature/v2/instance_pb';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class NewFeatureService {
|
||||
constructor(private readonly grpcService: GrpcService) {}
|
||||
|
||||
public getInstanceFeatures(): Promise<GetInstanceFeaturesResponse> {
|
||||
return this.grpcService.featureNew.getInstanceFeatures({});
|
||||
}
|
||||
}
|
33
console/src/app/services/webkeys.service.ts
Normal file
33
console/src/app/services/webkeys.service.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { GrpcService } from './grpc.service';
|
||||
import type { MessageInitShape } from '@bufbuild/protobuf';
|
||||
import {
|
||||
DeleteWebKeyResponse,
|
||||
ListWebKeysResponse,
|
||||
CreateWebKeyRequestSchema,
|
||||
CreateWebKeyResponse,
|
||||
ActivateWebKeyResponse,
|
||||
} from '@zitadel/proto/zitadel/webkey/v2beta/webkey_service_pb';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class WebKeysService {
|
||||
constructor(private readonly grpcService: GrpcService) {}
|
||||
|
||||
public ListWebKeys(): Promise<ListWebKeysResponse> {
|
||||
return this.grpcService.webKey.listWebKeys({});
|
||||
}
|
||||
|
||||
public DeleteWebKey(id: string): Promise<DeleteWebKeyResponse> {
|
||||
return this.grpcService.webKey.deleteWebKey({ id });
|
||||
}
|
||||
|
||||
public CreateWebKey(req: MessageInitShape<typeof CreateWebKeyRequestSchema>): Promise<CreateWebKeyResponse> {
|
||||
return this.grpcService.webKey.createWebKey(req);
|
||||
}
|
||||
|
||||
public ActivateWebKey(id: string): Promise<ActivateWebKeyResponse> {
|
||||
return this.grpcService.webKey.activateWebKey({ id });
|
||||
}
|
||||
}
|
@@ -185,6 +185,32 @@
|
||||
"DESCRIPTION": "Животът на неактивния refresh токен е максималното време, през което refresh токен може да не се използва."
|
||||
}
|
||||
},
|
||||
"WEB_KEYS": {
|
||||
"DESCRIPTION": "Управлявайте вашите OIDC уеб ключове, за да подписвате и валидирате токени сигурно за вашата ZITADEL инстанция.",
|
||||
"TABLE": {
|
||||
"TITLE": "Активни и бъдещи уеб ключове",
|
||||
"DESCRIPTION": "Вашите активни и предстоящи уеб ключове. Активирането на нов ключ ще деактивира текущия.",
|
||||
"NOTE": "Забележка: Крайна точка JWKs OIDC връща кешируем отговор (по подразбиране 5 минути). Избягвайте активирането на ключ твърде рано, тъй като той може да не е наличен в кеша и клиентите.",
|
||||
"ACTIVATE": "Активирайте следващия уеб ключ",
|
||||
"ACTIVE": "В момента активен",
|
||||
"NEXT": "Следващ в опашката",
|
||||
"FUTURE": "Бъдещ",
|
||||
"WARNING": "Уеб ключът е на по-малко от 5 минути"
|
||||
},
|
||||
"CREATE": {
|
||||
"TITLE": "Създаване на нов уеб ключ",
|
||||
"DESCRIPTION": "Създаването на нов уеб ключ го добавя към вашия списък. ZITADEL използва ключове RSA2048 с хеш SHA256 по подразбиране.",
|
||||
"KEY_TYPE": "Тип ключ",
|
||||
"BITS": "Битове",
|
||||
"HASHER": "Хешер",
|
||||
"CURVE": "Крива"
|
||||
},
|
||||
"PREVIOUS_TABLE": {
|
||||
"TITLE": "Предишни уеб ключове",
|
||||
"DESCRIPTION": "Това са вашите предишни уеб ключове, които вече не са активни.",
|
||||
"DEACTIVATED_ON": "Деактивиран на"
|
||||
}
|
||||
},
|
||||
"MESSAGE_TEXTS": {
|
||||
"TITLE": "Текстове на съобщенията",
|
||||
"DESCRIPTION": "Персонализирайте текстовете на вашите имейл или SMS уведомления. Ако искате да деактивирате някои от езиците, ограничете ги в настройките за език на вашите инстанции.",
|
||||
@@ -1352,6 +1378,7 @@
|
||||
"BRANDING": "Брандиране",
|
||||
"PRIVACYPOLICY": "Политика за бедност",
|
||||
"OIDC": "Живот и изтичане на OIDC Token",
|
||||
"WEB_KEYS": "OIDC Web Keys",
|
||||
"SECRETS": "Тайна поява",
|
||||
"SECURITY": "Настройки на сигурността",
|
||||
"EVENTS": "Събития",
|
||||
|
@@ -185,6 +185,32 @@
|
||||
"DESCRIPTION": "Životnost nečinného refresh tokenu je maximální doba, po kterou může být refresh token nepoužitý."
|
||||
}
|
||||
},
|
||||
"WEB_KEYS": {
|
||||
"DESCRIPTION": "Spravujte své OIDC webové klíče pro bezpečné podepisování a ověřování tokenů ve vaší instanci ZITADEL.",
|
||||
"TABLE": {
|
||||
"TITLE": "Aktivní a budoucí webové klíče",
|
||||
"DESCRIPTION": "Vaše aktivní a nadcházející webové klíče. Aktivací nového klíče dojde k deaktivaci aktuálního.",
|
||||
"NOTE": "Poznámka: Koncový bod JWKs OIDC vrací odpověď uložitelnou do mezipaměti (výchozí 5 minut). Vyhněte se příliš brzké aktivaci klíče, protože nemusí být dostupný v mezipaměti a klientům.",
|
||||
"ACTIVATE": "Aktivovat další webový klíč",
|
||||
"ACTIVE": "Aktuálně aktivní",
|
||||
"NEXT": "Další v řadě",
|
||||
"FUTURE": "Budoucí",
|
||||
"WARNING": "Webový klíč je starý méně než 5 minut"
|
||||
},
|
||||
"CREATE": {
|
||||
"TITLE": "Vytvořit nový webový klíč",
|
||||
"DESCRIPTION": "Vytvořením nového webového klíče jej přidáte do svého seznamu. ZITADEL používá klíče RSA2048 s hashováním SHA256 jako výchozí.",
|
||||
"KEY_TYPE": "Typ klíče",
|
||||
"BITS": "Bity",
|
||||
"HASHER": "Hasher",
|
||||
"CURVE": "Křivka"
|
||||
},
|
||||
"PREVIOUS_TABLE": {
|
||||
"TITLE": "Předchozí webové klíče",
|
||||
"DESCRIPTION": "Toto jsou vaše předchozí webové klíče, které již nejsou aktivní.",
|
||||
"DEACTIVATED_ON": "Deaktivováno dne"
|
||||
}
|
||||
},
|
||||
"MESSAGE_TEXTS": {
|
||||
"TITLE": "Texty zpráv",
|
||||
"DESCRIPTION": "Přizpůsob si texty svých e-mailových nebo SMS notifikací. Pokud chceš některé z jazyků zakázat, omez je ve svém nastavení jazyků instance.",
|
||||
@@ -1353,6 +1379,7 @@
|
||||
"BRANDING": "Branding",
|
||||
"PRIVACYPOLICY": "Zásady ochrany osobních údajů",
|
||||
"OIDC": "Životnost a expirace OIDC tokenu",
|
||||
"WEB_KEYS": "OIDC Web Keys",
|
||||
"SECRETS": "Generátor tajemství",
|
||||
"SECURITY": "Bezpečnostní nastavení",
|
||||
"EVENTS": "Události",
|
||||
|
@@ -185,6 +185,32 @@
|
||||
"DESCRIPTION": "Die maximale Inaktivitätsdauer eines Aktualisierungstokens ist die maximale Zeit, in der ein Aktualisierungstoken unbenutzt sein kann."
|
||||
}
|
||||
},
|
||||
"WEB_KEYS": {
|
||||
"DESCRIPTION": "Verwalte deine OIDC Web Keys, um Tokens für deine ZITADEL-Instanz sicher zu signieren und zu validieren.",
|
||||
"TABLE": {
|
||||
"TITLE": "Aktive und zukünftige Web Keys",
|
||||
"DESCRIPTION": "Deine aktiven und kommenden Web Keys. Das Aktivieren eines neuen Schlüssels deaktiviert den aktuellen.",
|
||||
"NOTE": "Hinweis: Der JWKs OIDC-Endpunkt gibt eine zwischenspeicherbare Antwort zurück (Standard: 5 Min.). Vermeide es, einen Schlüssel zu früh zu aktivieren, da er möglicherweise noch nicht in Caches und Clients verfügbar ist.",
|
||||
"ACTIVATE": "Nächsten Web Key aktivieren",
|
||||
"ACTIVE": "Derzeit aktiv",
|
||||
"NEXT": "Als Nächstes in der Warteschlange",
|
||||
"FUTURE": "Zukünftig",
|
||||
"WARNING": "Der Web Key ist weniger als 5 Minuten alt"
|
||||
},
|
||||
"CREATE": {
|
||||
"TITLE": "Neuen Web Key erstellen",
|
||||
"DESCRIPTION": "Das Erstellen eines neuen Web Keys fügt ihn zu deiner Liste hinzu. ZITADEL verwendet standardmäßig RSA2048-Schlüssel mit einem SHA256-Hasher.",
|
||||
"KEY_TYPE": "Schlüsseltyp",
|
||||
"BITS": "Bits",
|
||||
"HASHER": "Hasher",
|
||||
"CURVE": "Kurve"
|
||||
},
|
||||
"PREVIOUS_TABLE": {
|
||||
"TITLE": "Frühere Web Keys",
|
||||
"DESCRIPTION": "Dies sind deine früheren Web Keys, die nicht mehr aktiv sind.",
|
||||
"DEACTIVATED_ON": "Deaktiviert am"
|
||||
}
|
||||
},
|
||||
"MESSAGE_TEXTS": {
|
||||
"TITLE": "Nachrichtentexte",
|
||||
"DESCRIPTION": "Passe die Texte deiner Benachrichtigungs-E-Mails oder SMS-Nachrichten an. Wenn du einige der Sprachen deaktivieren möchtest, beschränke sie in den Spracheinstellungen deiner Instanz.",
|
||||
@@ -1353,6 +1379,7 @@
|
||||
"BRANDING": "Branding",
|
||||
"PRIVACYPOLICY": "Datenschutzrichtlinie",
|
||||
"OIDC": "OIDC Token Lifetime und Expiration",
|
||||
"WEB_KEYS": "OIDC Web Keys",
|
||||
"SECRETS": "Secret Generator",
|
||||
"SECURITY": "Sicherheitseinstellungen",
|
||||
"EVENTS": "Events",
|
||||
|
@@ -185,6 +185,32 @@
|
||||
"DESCRIPTION": "The idle refresh token lifetime is the maximum time a refresh token can be unused."
|
||||
}
|
||||
},
|
||||
"WEB_KEYS": {
|
||||
"DESCRIPTION": "Manage your OIDC Web Keys to securely sign and validate tokens for your ZITADEL instance.",
|
||||
"TABLE": {
|
||||
"TITLE": "Active and Future Web Keys",
|
||||
"DESCRIPTION": "Your active and upcoming web keys. Activating a new key will deactivate the current one.",
|
||||
"NOTE": "Note: The JWKs OIDC endpoint returns a cacheable response (default 5 min). Avoid activating a key too soon, as it may not be available to caches and clients yet.",
|
||||
"ACTIVATE": "Activate next Web Key",
|
||||
"ACTIVE": "Currently active",
|
||||
"NEXT": "Next in queue",
|
||||
"FUTURE": "Future",
|
||||
"WARNING": "Web Key is less than 5 min old"
|
||||
},
|
||||
"CREATE": {
|
||||
"TITLE": "Create new Web Key",
|
||||
"DESCRIPTION": "Creating a new web key adds it to your list. ZITADEL uses RSA2048 keys with a SHA256 hasher by default.",
|
||||
"KEY_TYPE": "Key Type",
|
||||
"BITS": "Bits",
|
||||
"HASHER": "Hasher",
|
||||
"CURVE": "Curve"
|
||||
},
|
||||
"PREVIOUS_TABLE": {
|
||||
"TITLE": "Previous Web Keys",
|
||||
"DESCRIPTION": "These are your previous web keys that are no longer active.",
|
||||
"DEACTIVATED_ON": "Deactivated on"
|
||||
}
|
||||
},
|
||||
"MESSAGE_TEXTS": {
|
||||
"TITLE": "Message Texts",
|
||||
"DESCRIPTION": "Customize the texts of your notification email or SMS messages. If you want to disable some of the languages, restrict them in your instances language settings.",
|
||||
@@ -1353,6 +1379,7 @@
|
||||
"BRANDING": "Branding",
|
||||
"PRIVACYPOLICY": "External links",
|
||||
"OIDC": "OIDC Token lifetime and expiration",
|
||||
"WEB_KEYS": "OIDC Web Keys",
|
||||
"SECRETS": "Secret Generator",
|
||||
"SECURITY": "Security settings",
|
||||
"EVENTS": "Events",
|
||||
|
@@ -185,6 +185,32 @@
|
||||
"DESCRIPTION": "La duración de vida del token de actualización en espera es el tiempo máximo que un token de actualización puede estar sin usar."
|
||||
}
|
||||
},
|
||||
"WEB_KEYS": {
|
||||
"DESCRIPTION": "Administra tus claves web OIDC para firmar y validar tokens de manera segura en tu instancia de ZITADEL.",
|
||||
"TABLE": {
|
||||
"TITLE": "Claves Web Activas y Futuras",
|
||||
"DESCRIPTION": "Tus claves web activas y próximas. Activar una nueva clave desactivará la actual.",
|
||||
"NOTE": "Nota: El endpoint JWKs OIDC devuelve una respuesta almacenable en caché (por defecto 5 min). Evita activar una clave demasiado pronto, ya que puede que aún no esté disponible en cachés y clientes.",
|
||||
"ACTIVATE": "Activar la siguiente Clave Web",
|
||||
"ACTIVE": "Actualmente activa",
|
||||
"NEXT": "Siguiente en la cola",
|
||||
"FUTURE": "Futuro",
|
||||
"WARNING": "La clave web tiene menos de 5 minutos"
|
||||
},
|
||||
"CREATE": {
|
||||
"TITLE": "Crear nueva Clave Web",
|
||||
"DESCRIPTION": "Crear una nueva clave web la añadirá a tu lista. ZITADEL usa por defecto claves RSA2048 con un algoritmo de hash SHA256.",
|
||||
"KEY_TYPE": "Tipo de Clave",
|
||||
"BITS": "Bits",
|
||||
"HASHER": "Algoritmo de Hash",
|
||||
"CURVE": "Curva"
|
||||
},
|
||||
"PREVIOUS_TABLE": {
|
||||
"TITLE": "Claves Web Anteriores",
|
||||
"DESCRIPTION": "Estas son tus claves web anteriores que ya no están activas.",
|
||||
"DEACTIVATED_ON": "Desactivada el"
|
||||
}
|
||||
},
|
||||
"MESSAGE_TEXTS": {
|
||||
"TITLE": "Textos de Mensajes",
|
||||
"DESCRIPTION": "Personaliza los textos de tus mensajes de correo electrónico de notificación o mensajes SMS. Si deseas desactivar algunos de los idiomas, restríngelos en la configuración de idiomas de tus instancias.",
|
||||
@@ -1354,6 +1380,7 @@
|
||||
"BRANDING": "Imagen de marca",
|
||||
"PRIVACYPOLICY": "Política de privacidad",
|
||||
"OIDC": "OIDC Token lifetime and expiration",
|
||||
"WEB_KEYS": "OIDC Web Keys",
|
||||
"SECRETS": "Apariencia del secreto",
|
||||
"SECURITY": "Ajustes de seguridad",
|
||||
"EVENTS": "Eventos",
|
||||
|
@@ -185,6 +185,32 @@
|
||||
"DESCRIPTION": "La durée de vie du token de rafraîchissement inactif est le temps maximum qu'un token de rafraîchissement peut être inutilisé."
|
||||
}
|
||||
},
|
||||
"WEB_KEYS": {
|
||||
"DESCRIPTION": "Gérez vos clés Web OIDC pour signer et valider en toute sécurité les jetons de votre instance ZITADEL.",
|
||||
"TABLE": {
|
||||
"TITLE": "Clés Web Actives et Futures",
|
||||
"DESCRIPTION": "Vos clés Web actives et à venir. L'activation d'une nouvelle clé désactivera l'actuelle.",
|
||||
"NOTE": "Remarque : Le point de terminaison JWKs OIDC renvoie une réponse mise en cache (par défaut 5 min). Évitez d'activer une clé trop tôt, car elle pourrait ne pas encore être disponible pour les caches et les clients.",
|
||||
"ACTIVATE": "Activer la prochaine Clé Web",
|
||||
"ACTIVE": "Actuellement active",
|
||||
"NEXT": "Prochaine dans la file d'attente",
|
||||
"FUTURE": "Futur",
|
||||
"WARNING": "La clé Web a moins de 5 minutes"
|
||||
},
|
||||
"CREATE": {
|
||||
"TITLE": "Créer une nouvelle Clé Web",
|
||||
"DESCRIPTION": "Créer une nouvelle clé Web l'ajoutera à votre liste. ZITADEL utilise par défaut des clés RSA2048 avec un hacheur SHA256.",
|
||||
"KEY_TYPE": "Type de Clé",
|
||||
"BITS": "Bits",
|
||||
"HASHER": "Hacheur",
|
||||
"CURVE": "Courbe"
|
||||
},
|
||||
"PREVIOUS_TABLE": {
|
||||
"TITLE": "Clés Web Précédentes",
|
||||
"DESCRIPTION": "Voici vos anciennes clés Web qui ne sont plus actives.",
|
||||
"DEACTIVATED_ON": "Désactivée le"
|
||||
}
|
||||
},
|
||||
"MESSAGE_TEXTS": {
|
||||
"TITLE": "Textes des Messages",
|
||||
"DESCRIPTION": "Personnalisez les textes de vos e-mails de notification ou messages SMS. Si vous souhaitez désactiver certaines langues, restreignez-les dans les paramètres de langue de vos instances.",
|
||||
@@ -1353,6 +1379,7 @@
|
||||
"BRANDING": "Image de marque",
|
||||
"PRIVACYPOLICY": "Politique de confidentialité",
|
||||
"OIDC": "Durée de vie et expiration des jetons OIDC",
|
||||
"WEB_KEYS": "OIDC Web Keys",
|
||||
"SECRETS": "Générateur de secrets",
|
||||
"SECURITY": "Paramètres de sécurité",
|
||||
"EVENTS": "Événements",
|
||||
|
@@ -185,6 +185,32 @@
|
||||
"DESCRIPTION": "A tétlen frissítő token élettartama az a maximális idő, ameddig a frissítő token használaton kívül maradhat."
|
||||
}
|
||||
},
|
||||
"WEB_KEYS": {
|
||||
"DESCRIPTION": "Kezelje az OIDC Webkulcsokat, hogy biztonságosan aláírja és érvényesítse a tokeneket a ZITADEL példányában.",
|
||||
"TABLE": {
|
||||
"TITLE": "Aktív és Jövőbeli Webkulcsok",
|
||||
"DESCRIPTION": "Az aktív és közelgő webkulcsai. Egy új kulcs aktiválása deaktiválja az aktuálisat.",
|
||||
"NOTE": "Megjegyzés: A JWKs OIDC végpont egy gyorsítótárazható választ ad vissza (alapértelmezett: 5 perc). Kerülje a kulcs túl korai aktiválását, mivel lehet, hogy még nem érhető el a gyorsítótárakban és a klienseknél.",
|
||||
"ACTIVATE": "Következő Webkulcs aktiválása",
|
||||
"ACTIVE": "Jelenleg aktív",
|
||||
"NEXT": "Következő a sorban",
|
||||
"FUTURE": "Jövőbeli",
|
||||
"WARNING": "A webkulcs kevesebb mint 5 perces"
|
||||
},
|
||||
"CREATE": {
|
||||
"TITLE": "Új Webkulcs létrehozása",
|
||||
"DESCRIPTION": "Egy új webkulcs létrehozása hozzáadja azt a listájához. A ZITADEL alapértelmezés szerint RSA2048 kulcsokat használ SHA256 hasheléssel.",
|
||||
"KEY_TYPE": "Kulcstípus",
|
||||
"BITS": "Bitek",
|
||||
"HASHER": "Hasher",
|
||||
"CURVE": "Görbe"
|
||||
},
|
||||
"PREVIOUS_TABLE": {
|
||||
"TITLE": "Korábbi Webkulcsok",
|
||||
"DESCRIPTION": "Ezek a korábbi webkulcsai, amelyek már nem aktívak.",
|
||||
"DEACTIVATED_ON": "Deaktiválva ekkor"
|
||||
}
|
||||
},
|
||||
"MESSAGE_TEXTS": {
|
||||
"TITLE": "Üzenet Szövegek",
|
||||
"DESCRIPTION": "Testreszabhatod az értesítési e-mailjeid vagy SMS üzeneteid szövegeit. Ha le szeretnél tiltani néhány nyelvet, korlátozd azokat az instance nyelvi beállításaiban.",
|
||||
@@ -1353,6 +1379,7 @@
|
||||
"BRANDING": "Márkaépítés",
|
||||
"PRIVACYPOLICY": "Külső hivatkozások",
|
||||
"OIDC": "OIDC token élettartam és lejárat",
|
||||
"WEB_KEYS": "OIDC Web Keys",
|
||||
"SECRETS": "Titokgenerátor",
|
||||
"SECURITY": "Biztonsági beállítások",
|
||||
"EVENTS": "Események",
|
||||
|
@@ -173,6 +173,32 @@
|
||||
"DESCRIPTION": "Masa pakai token penyegaran yang menganggur adalah waktu maksimum token penyegaran tidak dapat digunakan."
|
||||
}
|
||||
},
|
||||
"WEB_KEYS": {
|
||||
"DESCRIPTION": "Kelola Kunci Web OIDC Anda untuk menandatangani dan memvalidasi token dengan aman untuk instance ZITADEL Anda.",
|
||||
"TABLE": {
|
||||
"TITLE": "Kunci Web Aktif dan Mendatang",
|
||||
"DESCRIPTION": "Kunci web Anda yang aktif dan akan datang. Mengaktifkan kunci baru akan menonaktifkan kunci yang sedang digunakan.",
|
||||
"NOTE": "Catatan: Endpoint JWKs OIDC mengembalikan respons yang dapat di-cache (default 5 menit). Hindari mengaktifkan kunci terlalu cepat, karena mungkin belum tersedia di cache dan klien.",
|
||||
"ACTIVATE": "Aktifkan Kunci Web Berikutnya",
|
||||
"ACTIVE": "Saat ini aktif",
|
||||
"NEXT": "Berikutnya dalam antrean",
|
||||
"FUTURE": "Mendatang",
|
||||
"WARNING": "Kunci Web berusia kurang dari 5 menit"
|
||||
},
|
||||
"CREATE": {
|
||||
"TITLE": "Buat Kunci Web Baru",
|
||||
"DESCRIPTION": "Membuat kunci web baru akan menambahkannya ke daftar Anda. ZITADEL secara default menggunakan kunci RSA2048 dengan fungsi hash SHA256.",
|
||||
"KEY_TYPE": "Jenis Kunci",
|
||||
"BITS": "Bit",
|
||||
"HASHER": "Hasher",
|
||||
"CURVE": "Kurva"
|
||||
},
|
||||
"PREVIOUS_TABLE": {
|
||||
"TITLE": "Kunci Web Sebelumnya",
|
||||
"DESCRIPTION": "Ini adalah kunci web sebelumnya yang tidak lagi aktif.",
|
||||
"DEACTIVATED_ON": "Dinonaktifkan pada"
|
||||
}
|
||||
},
|
||||
"MESSAGE_TEXTS": {
|
||||
"TITLE": "Teks Pesan",
|
||||
"DESCRIPTION": "Sesuaikan teks email notifikasi atau pesan SMS Anda. Jika Anda ingin menonaktifkan beberapa bahasa, batasi bahasa tersebut di pengaturan bahasa instance Anda.",
|
||||
@@ -1231,6 +1257,7 @@
|
||||
"BRANDING": "merek",
|
||||
"PRIVACYPOLICY": "Tautan eksternal",
|
||||
"OIDC": "Masa berlaku dan masa berlaku Token OIDC",
|
||||
"WEB_KEYS": "OIDC Web Keys",
|
||||
"SECRETS": "Pembuat Rahasia",
|
||||
"SECURITY": "Pengaturan keamanan",
|
||||
"EVENTS": "Acara",
|
||||
|
@@ -185,6 +185,32 @@
|
||||
"DESCRIPTION": "La durata massima di un token di refresh inattivo è il tempo massimo in cui un token di refresh può rimanere inutilizzato."
|
||||
}
|
||||
},
|
||||
"WEB_KEYS": {
|
||||
"DESCRIPTION": "Gestisci le tue chiavi Web OIDC per firmare e convalidare in modo sicuro i token per la tua istanza di ZITADEL.",
|
||||
"TABLE": {
|
||||
"TITLE": "Chiavi Web Attive e Future",
|
||||
"DESCRIPTION": "Le tue chiavi web attive e future. L'attivazione di una nuova chiave disattiverà quella attuale.",
|
||||
"NOTE": "Nota: L'endpoint JWKs OIDC restituisce una risposta memorizzabile nella cache (predefinito 5 min). Evita di attivare una chiave troppo presto, poiché potrebbe non essere ancora disponibile nelle cache e nei client.",
|
||||
"ACTIVATE": "Attiva la prossima Chiave Web",
|
||||
"ACTIVE": "Attualmente attiva",
|
||||
"NEXT": "Prossima in coda",
|
||||
"FUTURE": "Futura",
|
||||
"WARNING": "La chiave web ha meno di 5 minuti"
|
||||
},
|
||||
"CREATE": {
|
||||
"TITLE": "Crea una nuova Chiave Web",
|
||||
"DESCRIPTION": "Creare una nuova chiave web la aggiungerà alla tua lista. ZITADEL utilizza chiavi RSA2048 con hash SHA256 per impostazione predefinita.",
|
||||
"KEY_TYPE": "Tipo di Chiave",
|
||||
"BITS": "Bit",
|
||||
"HASHER": "Hasher",
|
||||
"CURVE": "Curva"
|
||||
},
|
||||
"PREVIOUS_TABLE": {
|
||||
"TITLE": "Chiavi Web Precedenti",
|
||||
"DESCRIPTION": "Queste sono le tue chiavi web precedenti che non sono più attive.",
|
||||
"DEACTIVATED_ON": "Disattivata il"
|
||||
}
|
||||
},
|
||||
"MESSAGE_TEXTS": {
|
||||
"TITLE": "Testi dei Messaggi",
|
||||
"DESCRIPTION": "Personalizza i testi delle tue email di notifica o messaggi SMS. Se vuoi disabilitare alcune lingue, limitale nelle impostazioni lingua delle tue istanze.",
|
||||
@@ -1353,6 +1379,7 @@
|
||||
"BRANDING": "Branding",
|
||||
"PRIVACYPOLICY": "Informativa sulla privacy e TOS",
|
||||
"OIDC": "OIDC Token lifetime e scadenza",
|
||||
"WEB_KEYS": "OIDC Web Keys",
|
||||
"SECRETS": "Aspetto dei segreti",
|
||||
"SECURITY": "Impostazioni di sicurezza",
|
||||
"EVENTS": "Eventi",
|
||||
|
@@ -185,6 +185,32 @@
|
||||
"DESCRIPTION": "アイドル状態のリフレッシュトークンの有効期間は、リフレッシュトークンが使用されない最大時間です。"
|
||||
}
|
||||
},
|
||||
"WEB_KEYS": {
|
||||
"DESCRIPTION": "ZITADELインスタンスのトークンを安全に署名および検証するために、OIDC Webキーを管理します。",
|
||||
"TABLE": {
|
||||
"TITLE": "アクティブおよび今後のWebキー",
|
||||
"DESCRIPTION": "現在アクティブなWebキーと、今後使用予定のWebキーです。新しいキーをアクティブ化すると、現在のキーは無効になります。",
|
||||
"NOTE": "注意: JWKs OIDCエンドポイントはキャッシュ可能なレスポンスを返します(デフォルト5分)。キーを早くアクティブ化しすぎると、キャッシュやクライアントでまだ利用できない可能性があります。",
|
||||
"ACTIVATE": "次のWebキーをアクティブ化",
|
||||
"ACTIVE": "現在アクティブ",
|
||||
"NEXT": "次のキュー",
|
||||
"FUTURE": "今後",
|
||||
"WARNING": "ウェブキーは5分未満です。"
|
||||
},
|
||||
"CREATE": {
|
||||
"TITLE": "新しいWebキーを作成",
|
||||
"DESCRIPTION": "新しいWebキーを作成すると、リストに追加されます。ZITADELはデフォルトでRSA2048キーとSHA256ハッシュを使用します。",
|
||||
"KEY_TYPE": "キーの種類",
|
||||
"BITS": "ビット",
|
||||
"HASHER": "ハッシュ方式",
|
||||
"CURVE": "カーブ"
|
||||
},
|
||||
"PREVIOUS_TABLE": {
|
||||
"TITLE": "以前のWebキー",
|
||||
"DESCRIPTION": "これらは、すでに無効になった以前のWebキーです。",
|
||||
"DEACTIVATED_ON": "無効化日"
|
||||
}
|
||||
},
|
||||
"MESSAGE_TEXTS": {
|
||||
"TITLE": "メッセージテキスト",
|
||||
"DESCRIPTION": "通知メールやSMSメッセージのテキストをカスタマイズします。一部の言語を無効にしたい場合は、インスタンスの言語設定で制限してください。",
|
||||
@@ -1353,6 +1379,7 @@
|
||||
"BRANDING": "ブランディング",
|
||||
"PRIVACYPOLICY": "プライバシーポリシー",
|
||||
"OIDC": "OIDCトークンのライフタイムと有効期限",
|
||||
"WEB_KEYS": "OIDC Web Keys",
|
||||
"SECRETS": "シークレット設定",
|
||||
"SECURITY": "セキュリティ設定",
|
||||
"EVENTS": "イベント",
|
||||
|
@@ -185,6 +185,32 @@
|
||||
"DESCRIPTION": "유휴 갱신 토큰 수명은 갱신 토큰이 사용되지 않는 최대 기간을 의미합니다."
|
||||
}
|
||||
},
|
||||
"WEB_KEYS": {
|
||||
"DESCRIPTION": "ZITADEL 인스턴스의 토큰을 안전하게 서명하고 검증하기 위해 OIDC 웹 키를 관리하세요.",
|
||||
"TABLE": {
|
||||
"TITLE": "활성 및 예정된 웹 키",
|
||||
"DESCRIPTION": "현재 활성화된 웹 키와 앞으로 활성화될 웹 키입니다. 새로운 키를 활성화하면 기존 키는 비활성화됩니다.",
|
||||
"NOTE": "참고: JWKs OIDC 엔드포인트는 캐시 가능한 응답을 반환합니다 (기본값: 5분). 키를 너무 빨리 활성화하면 캐시 및 클라이언트에서 아직 사용할 수 없을 수 있습니다.",
|
||||
"ACTIVATE": "다음 웹 키 활성화",
|
||||
"ACTIVE": "현재 활성화됨",
|
||||
"NEXT": "대기 중인 다음 키",
|
||||
"FUTURE": "향후 사용 예정",
|
||||
"WARNING": "웹 키가 5분 미만입니다."
|
||||
},
|
||||
"CREATE": {
|
||||
"TITLE": "새 웹 키 생성",
|
||||
"DESCRIPTION": "새 웹 키를 생성하면 목록에 추가됩니다. ZITADEL은 기본적으로 RSA2048 키와 SHA256 해시 알고리즘을 사용합니다.",
|
||||
"KEY_TYPE": "키 유형",
|
||||
"BITS": "비트",
|
||||
"HASHER": "해시 알고리즘",
|
||||
"CURVE": "곡선"
|
||||
},
|
||||
"PREVIOUS_TABLE": {
|
||||
"TITLE": "이전 웹 키",
|
||||
"DESCRIPTION": "더 이상 활성 상태가 아닌 이전 웹 키 목록입니다.",
|
||||
"DEACTIVATED_ON": "비활성화된 날짜"
|
||||
}
|
||||
},
|
||||
"MESSAGE_TEXTS": {
|
||||
"TITLE": "메시지 텍스트",
|
||||
"DESCRIPTION": "알림 이메일 또는 SMS 메시지의 텍스트를 사용자 정의하세요. 언어를 비활성화하려면 인스턴스의 언어 설정에서 제한하세요.",
|
||||
@@ -1353,6 +1379,7 @@
|
||||
"BRANDING": "브랜딩",
|
||||
"PRIVACYPOLICY": "외부 링크",
|
||||
"OIDC": "OIDC 토큰 수명 및 만료",
|
||||
"WEB_KEYS": "OIDC Web Keys",
|
||||
"SECRETS": "시크릿 생성기",
|
||||
"SECURITY": "보안 설정",
|
||||
"EVENTS": "이벤트",
|
||||
|
@@ -185,6 +185,32 @@
|
||||
"DESCRIPTION": "Животниот век на неактивниот токен за освежување е максималното време кое токен за освежување може да не се користи."
|
||||
}
|
||||
},
|
||||
"WEB_KEYS": {
|
||||
"DESCRIPTION": "Управувајте со вашите OIDC веб-клучеви за безбедно потпишување и валидација на токени за вашата ZITADEL инстанца.",
|
||||
"TABLE": {
|
||||
"TITLE": "Активни и Идни Веб-Клучеви",
|
||||
"DESCRIPTION": "Вашите активни и претстојни веб-клучеви. Активирањето на нов клуч ќе го деактивира тековниот.",
|
||||
"NOTE": "Забелешка: JWKs OIDC крајната точка враќа одговор што може да се кешира (стандардно 5 минути). Избегнувајте активирање на клучот пребрзо, бидејќи можеби сè уште не е достапен во кешот и кај клиентите.",
|
||||
"ACTIVATE": "Активирај го следниот веб-клуч",
|
||||
"ACTIVE": "Моментално активен",
|
||||
"NEXT": "Следен во редот",
|
||||
"FUTURE": "Иднина",
|
||||
"WARNING": "Веб-клучот е помалку од 5 минути стар"
|
||||
},
|
||||
"CREATE": {
|
||||
"TITLE": "Креирај нов веб-клуч",
|
||||
"DESCRIPTION": "Креирањето нов веб-клуч го додава на вашата листа. ZITADEL стандардно користи RSA2048 клучеви со SHA256 алгоритам за хаширање.",
|
||||
"KEY_TYPE": "Тип на клуч",
|
||||
"BITS": "Битови",
|
||||
"HASHER": "Алгоритам за хаширање",
|
||||
"CURVE": "Крива"
|
||||
},
|
||||
"PREVIOUS_TABLE": {
|
||||
"TITLE": "Претходни веб-клучеви",
|
||||
"DESCRIPTION": "Ова се вашите претходни веб-клучеви кои повеќе не се активни.",
|
||||
"DEACTIVATED_ON": "Деактивиран на"
|
||||
}
|
||||
},
|
||||
"MESSAGE_TEXTS": {
|
||||
"TITLE": "Текстови на пораки",
|
||||
"DESCRIPTION": "Прилагодете ги текстовите на вашите е-маил или SMS пораки за известување. Ако сакате да оневозможите некои јазици, ограничете ги во поставките за јазик на вашите инстанци.",
|
||||
@@ -1354,6 +1380,7 @@
|
||||
"BRANDING": "Брендирање",
|
||||
"PRIVACYPOLICY": "Политика за приватност",
|
||||
"OIDC": "OIDC времетраење и истекување на токени",
|
||||
"WEB_KEYS": "OIDC Web Keys",
|
||||
"SECRETS": "Изглед на тајни",
|
||||
"SECURITY": "Подесувања за безбедност",
|
||||
"EVENTS": "Настани",
|
||||
|
@@ -185,6 +185,32 @@
|
||||
"DESCRIPTION": "De levensduur van het inactieve vernieuwingstoken is de maximale tijd dat een vernieuwingstoken ongebruikt kan zijn."
|
||||
}
|
||||
},
|
||||
"WEB_KEYS": {
|
||||
"DESCRIPTION": "Beheer je OIDC Web Keys om tokens veilig te ondertekenen en te valideren voor je ZITADEL-instantie.",
|
||||
"TABLE": {
|
||||
"TITLE": "Actieve en Toekomstige Websleutels",
|
||||
"DESCRIPTION": "Je actieve en aankomende websleutels. Het activeren van een nieuwe sleutel deactiveert de huidige.",
|
||||
"NOTE": "Opmerking: Het JWKs OIDC-eindpunt geeft een cachebare respons terug (standaard 5 minuten). Vermijd het te vroeg activeren van een sleutel, omdat deze mogelijk nog niet beschikbaar is in caches en bij clients.",
|
||||
"ACTIVATE": "Volgende Websleutel activeren",
|
||||
"ACTIVE": "Momenteel actief",
|
||||
"NEXT": "Volgende in de wachtrij",
|
||||
"FUTURE": "Toekomstig",
|
||||
"WARNING": "De websleutel is minder dan 5 minuten oud"
|
||||
},
|
||||
"CREATE": {
|
||||
"TITLE": "Nieuwe Websleutel aanmaken",
|
||||
"DESCRIPTION": "Het aanmaken van een nieuwe websleutel voegt deze toe aan je lijst. ZITADEL gebruikt standaard RSA2048-sleutels met een SHA256-hasher.",
|
||||
"KEY_TYPE": "Sleuteltype",
|
||||
"BITS": "Bits",
|
||||
"HASHER": "Hasher",
|
||||
"CURVE": "Curve"
|
||||
},
|
||||
"PREVIOUS_TABLE": {
|
||||
"TITLE": "Vorige Websleutels",
|
||||
"DESCRIPTION": "Dit zijn je vorige websleutels die niet langer actief zijn.",
|
||||
"DEACTIVATED_ON": "Gedeactiveerd op"
|
||||
}
|
||||
},
|
||||
"MESSAGE_TEXTS": {
|
||||
"TITLE": "Berichtteksten",
|
||||
"DESCRIPTION": "Pas de teksten van je notificatie-e-mail of SMS-berichten aan. Als je sommige talen wilt uitschakelen, beperk ze dan in de taalinstellingen van je instanties.",
|
||||
@@ -1353,6 +1379,7 @@
|
||||
"BRANDING": "Branding",
|
||||
"PRIVACYPOLICY": "Privacybeleid",
|
||||
"OIDC": "OIDC Token levensduur en vervaldatum",
|
||||
"WEB_KEYS": "OIDC Web Keys",
|
||||
"SECRETS": "Secret Generator",
|
||||
"SECURITY": "Beveiligingsinstellingen",
|
||||
"EVENTS": "Evenementen",
|
||||
|
@@ -185,6 +185,32 @@
|
||||
"DESCRIPTION": "Czas życia bezczynnego tokena odświeżania to maksymalny czas, przez który token odświeżania może pozostać nieużywany."
|
||||
}
|
||||
},
|
||||
"WEB_KEYS": {
|
||||
"DESCRIPTION": "Zarządzaj swoimi kluczami internetowymi OIDC, aby bezpiecznie podpisywać i weryfikować tokeny w swojej instancji ZITADEL.",
|
||||
"TABLE": {
|
||||
"TITLE": "Aktywne i Przyszłe Klucze Internetowe",
|
||||
"DESCRIPTION": "Twoje aktywne i nadchodzące klucze internetowe. Aktywacja nowego klucza spowoduje dezaktywację obecnego.",
|
||||
"NOTE": "Uwaga: Punkt końcowy JWKs OIDC zwraca odpowiedź możliwą do buforowania (domyślnie 5 minut). Unikaj zbyt wczesnej aktywacji klucza, ponieważ może on nie być jeszcze dostępny w pamięci podręcznej i dla klientów.",
|
||||
"ACTIVATE": "Aktywuj następny klucz internetowy",
|
||||
"ACTIVE": "Obecnie aktywny",
|
||||
"NEXT": "Następny w kolejce",
|
||||
"FUTURE": "Przyszłe",
|
||||
"WARNING": "Klucz sieciowy ma mniej niż 5 minut"
|
||||
},
|
||||
"CREATE": {
|
||||
"TITLE": "Utwórz nowy klucz internetowy",
|
||||
"DESCRIPTION": "Utworzenie nowego klucza internetowego doda go do Twojej listy. ZITADEL domyślnie używa kluczy RSA2048 z haszowaniem SHA256.",
|
||||
"KEY_TYPE": "Typ klucza",
|
||||
"BITS": "Bity",
|
||||
"HASHER": "Haszowanie",
|
||||
"CURVE": "Krzywa"
|
||||
},
|
||||
"PREVIOUS_TABLE": {
|
||||
"TITLE": "Poprzednie Klucze Internetowe",
|
||||
"DESCRIPTION": "To są Twoje poprzednie klucze internetowe, które nie są już aktywne.",
|
||||
"DEACTIVATED_ON": "Dezaktywowany dnia"
|
||||
}
|
||||
},
|
||||
"MESSAGE_TEXTS": {
|
||||
"TITLE": "Teksty wiadomości",
|
||||
"DESCRIPTION": "Dostosuj teksty swoich e-maili lub wiadomości SMS z powiadomieniami. Jeśli chcesz wyłączyć niektóre języki, ogranicz je w ustawieniach językowych swoich instancji.",
|
||||
@@ -1352,6 +1378,7 @@
|
||||
"BRANDING": "Marka",
|
||||
"PRIVACYPOLICY": "Polityka prywatności",
|
||||
"OIDC": "Czas trwania tokenów OIDC i wygaśnięcie",
|
||||
"WEB_KEYS": "OIDC Web Keys",
|
||||
"SECRETS": "Wygląd sekretów",
|
||||
"SECURITY": "Ustawienia bezpieczeństwa",
|
||||
"EVENTS": "Zdarzenia",
|
||||
|
@@ -185,6 +185,32 @@
|
||||
"DESCRIPTION": "A vida útil do token de atualização inativo é o tempo máximo que um token de atualização pode ficar sem uso."
|
||||
}
|
||||
},
|
||||
"WEB_KEYS": {
|
||||
"DESCRIPTION": "Gerencie suas Chaves Web OIDC para assinar e validar tokens com segurança em sua instância do ZITADEL.",
|
||||
"TABLE": {
|
||||
"TITLE": "Chaves Web Ativas e Futuras",
|
||||
"DESCRIPTION": "Suas chaves web ativas e futuras. Ativar uma nova chave desativará a atual.",
|
||||
"NOTE": "Nota: O endpoint JWKs OIDC retorna uma resposta que pode ser armazenada em cache (padrão: 5 min). Evite ativar uma chave muito cedo, pois ela pode ainda não estar disponível no cache e para os clientes.",
|
||||
"ACTIVATE": "Ativar próxima Chave Web",
|
||||
"ACTIVE": "Atualmente ativa",
|
||||
"NEXT": "Próxima na fila",
|
||||
"FUTURE": "Futuro",
|
||||
"WARNING": "A chave da Web tem menos de 5 minutos"
|
||||
},
|
||||
"CREATE": {
|
||||
"TITLE": "Criar nova Chave Web",
|
||||
"DESCRIPTION": "Criar uma nova chave web a adicionará à sua lista. O ZITADEL usa, por padrão, chaves RSA2048 com um algoritmo de hash SHA256.",
|
||||
"KEY_TYPE": "Tipo de Chave",
|
||||
"BITS": "Bits",
|
||||
"HASHER": "Algoritmo de Hash",
|
||||
"CURVE": "Curva"
|
||||
},
|
||||
"PREVIOUS_TABLE": {
|
||||
"TITLE": "Chaves Web Anteriores",
|
||||
"DESCRIPTION": "Estas são suas chaves web anteriores que não estão mais ativas.",
|
||||
"DEACTIVATED_ON": "Desativada em"
|
||||
}
|
||||
},
|
||||
"MESSAGE_TEXTS": {
|
||||
"TITLE": "Textos de Mensagens",
|
||||
"DESCRIPTION": "Personalize os textos do seu e-mail de notificação ou mensagens SMS. Se desejar desativar alguns idiomas, restrinja-os nas configurações de idioma da sua instância.",
|
||||
@@ -1354,6 +1380,7 @@
|
||||
"BRANDING": "Marca",
|
||||
"PRIVACYPOLICY": "Política de Privacidade",
|
||||
"OIDC": "Tempo de Vida e Expiração do Token OIDC",
|
||||
"WEB_KEYS": "OIDC Web Keys",
|
||||
"SECRETS": "Aparência de Segredo",
|
||||
"SECURITY": "Configurações de Segurança",
|
||||
"EVENTS": "Eventos",
|
||||
|
@@ -185,6 +185,32 @@
|
||||
"DESCRIPTION": "Durata de viață inactivă a tokenului de reîmprospătare este timpul maxim în care un token de reîmprospătare poate fi neutilizat."
|
||||
}
|
||||
},
|
||||
"WEB_KEYS": {
|
||||
"DESCRIPTION": "Gestionează-ți cheile web OIDC pentru a semna și valida în siguranță tokenurile pentru instanța ta ZITADEL.",
|
||||
"TABLE": {
|
||||
"TITLE": "Chei Web Active și Viitoare",
|
||||
"DESCRIPTION": "Cheile tale web active și viitoare. Activarea unei noi chei va dezactiva cheia curentă.",
|
||||
"NOTE": "Notă: Endpoint-ul JWKs OIDC returnează un răspuns care poate fi stocat în cache (implicit 5 min). Evită activarea unei chei prea devreme, deoarece este posibil să nu fie încă disponibilă în cache și pentru clienți.",
|
||||
"ACTIVATE": "Activează următoarea Cheie Web",
|
||||
"ACTIVE": "În prezent activă",
|
||||
"NEXT": "Următoarea în coadă",
|
||||
"FUTURE": "Viitoare",
|
||||
"WARNING": "Cheia web are mai puțin de 5 minute"
|
||||
},
|
||||
"CREATE": {
|
||||
"TITLE": "Creează o nouă Cheie Web",
|
||||
"DESCRIPTION": "Crearea unei noi chei web o va adăuga pe lista ta. ZITADEL folosește implicit chei RSA2048 cu un algoritm de hash SHA256.",
|
||||
"KEY_TYPE": "Tip de Cheie",
|
||||
"BITS": "Biti",
|
||||
"HASHER": "Algoritm de Hash",
|
||||
"CURVE": "Curbă"
|
||||
},
|
||||
"PREVIOUS_TABLE": {
|
||||
"TITLE": "Chei Web Anterioare",
|
||||
"DESCRIPTION": "Acestea sunt cheile tale web anterioare care nu mai sunt active.",
|
||||
"DEACTIVATED_ON": "Dezactivată pe"
|
||||
}
|
||||
},
|
||||
"MESSAGE_TEXTS": {
|
||||
"TITLE": "Texte de mesaje",
|
||||
"DESCRIPTION": "Personalizați textele mesajelor de e-mail sau SMS de notificare. Dacă doriți să dezactivați unele dintre limbi, restricționați-le în setările de limbă ale instanțelor dvs.",
|
||||
@@ -1351,6 +1377,7 @@
|
||||
"BRANDING": "Branding",
|
||||
"PRIVACYPOLICY": "Linkuri externe",
|
||||
"OIDC": "Durata de viață și expirarea tokenului OIDC",
|
||||
"WEB_KEYS": "OIDC Web Keys",
|
||||
"SECRETS": "Generator de secrete",
|
||||
"SECURITY": "Setări de securitate",
|
||||
"EVENTS": "Evenimente",
|
||||
|
@@ -185,6 +185,33 @@
|
||||
"DESCRIPTION": "Срок действия неактивного токена обновления - это максимальное время, в течение которого токен обновления может оставаться неиспользованным."
|
||||
}
|
||||
},
|
||||
"WEB_KEYS": {
|
||||
"DESCRIPTION": "Управляйте своими OIDC веб-ключами для безопасной подписи и валидации токенов в вашем экземпляре ZITADEL.",
|
||||
"TABLE": {
|
||||
"TITLE": "Активные и будущие веб-ключи",
|
||||
"DESCRIPTION": "Ваши активные и будущие веб-ключи. Активация нового ключа приведёт к деактивации текущего.",
|
||||
"NOTE": "Примечание: Конечная точка JWKs OIDC возвращает кэшируемый ответ (по умолчанию 5 минут). Избегайте слишком ранней активации ключа, так как он может ещё не быть доступен в кэше и у клиентов.",
|
||||
"ACTIVATE": "Активировать следующий веб-ключ",
|
||||
"ACTIVE": "В настоящее время активен",
|
||||
"NEXT": "Следующий в очереди",
|
||||
"FUTURE": "Будущий",
|
||||
"WARNING": "Веб-ключу менее 5 минут"
|
||||
},
|
||||
"CREATE": {
|
||||
"TITLE": "Создать новый веб-ключ",
|
||||
"DESCRIPTION": "Создание нового веб-ключа добавит его в ваш список. ZITADEL по умолчанию использует ключи RSA2048 с хешированием SHA256.",
|
||||
"KEY_TYPE": "Тип ключа",
|
||||
"BITS": "Биты",
|
||||
"HASHER": "Алгоритм хеширования",
|
||||
"CURVE": "Кривая"
|
||||
},
|
||||
"PREVIOUS_TABLE": {
|
||||
"TITLE": "Предыдущие веб-ключи",
|
||||
"DESCRIPTION": "Это ваши предыдущие веб-ключи, которые больше не активны.",
|
||||
"DEACTIVATED_ON": "Деактивирован"
|
||||
}
|
||||
},
|
||||
|
||||
"MESSAGE_TEXTS": {
|
||||
"TITLE": "Тексты сообщений",
|
||||
"DESCRIPTION": "Настройте тексты ваших уведомлений по электронной почте или SMS. Если вы хотите отключить некоторые языки, ограничьте их в настройках языка ваших экземпляров.",
|
||||
@@ -1397,6 +1424,7 @@
|
||||
"BRANDING": "Брендинг",
|
||||
"PRIVACYPOLICY": "Политика конфиденциальности",
|
||||
"OIDC": "Срок действия токена OIDC",
|
||||
"WEB_KEYS": "OIDC Web Keys",
|
||||
"SECRETS": "Отображение ключа",
|
||||
"SECURITY": "Настройки безопасности",
|
||||
"EVENTS": "События",
|
||||
|
@@ -185,6 +185,32 @@
|
||||
"DESCRIPTION": "Den inaktiva förnyelsetokenens livslängd är den maximala tiden en förnyelsetoken kan vara oanvänd."
|
||||
}
|
||||
},
|
||||
"WEB_KEYS": {
|
||||
"DESCRIPTION": "Hantera dina OIDC-webbnycklar för att säkert signera och validera tokens för din ZITADEL-instans.",
|
||||
"TABLE": {
|
||||
"TITLE": "Aktiva och framtida webbnycklar",
|
||||
"DESCRIPTION": "Dina aktiva och kommande webbnycklar. Aktivering av en ny nyckel kommer att inaktivera den nuvarande.",
|
||||
"NOTE": "Observera: JWKs OIDC-slutpunkten returnerar ett cachebart svar (standard 5 min). Undvik att aktivera en nyckel för tidigt, eftersom den kanske ännu inte är tillgänglig i cache och för klienter.",
|
||||
"ACTIVATE": "Aktivera nästa webbnyckel",
|
||||
"ACTIVE": "För närvarande aktiv",
|
||||
"NEXT": "Nästa i kön",
|
||||
"FUTURE": "Framtida",
|
||||
"WARNING": "Webbnyckeln är mindre än 5 minuter gammal"
|
||||
},
|
||||
"CREATE": {
|
||||
"TITLE": "Skapa ny webbnyckel",
|
||||
"DESCRIPTION": "Att skapa en ny webbnyckel lägger till den i din lista. ZITADEL använder som standard RSA2048-nycklar med en SHA256-hasher.",
|
||||
"KEY_TYPE": "Nyckeltyp",
|
||||
"BITS": "Bitar",
|
||||
"HASHER": "Hasher",
|
||||
"CURVE": "Kurva"
|
||||
},
|
||||
"PREVIOUS_TABLE": {
|
||||
"TITLE": "Tidigare webbnycklar",
|
||||
"DESCRIPTION": "Detta är dina tidigare webbnycklar som inte längre är aktiva.",
|
||||
"DEACTIVATED_ON": "Inaktiverad den"
|
||||
}
|
||||
},
|
||||
"MESSAGE_TEXTS": {
|
||||
"TITLE": "Meddelandetexter",
|
||||
"DESCRIPTION": "Anpassa texterna i dina notifikationsmail eller SMS-meddelanden. Om du vill inaktivera några av språken, begränsa dem i dina instansers språkinställningar.",
|
||||
@@ -1357,6 +1383,7 @@
|
||||
"BRANDING": "Varumärke",
|
||||
"PRIVACYPOLICY": "Externa länkar",
|
||||
"OIDC": "OIDC-token livstid och utgång",
|
||||
"WEB_KEYS": "OIDC Web Keys",
|
||||
"SECRETS": "Hemlighetsgenerator",
|
||||
"SECURITY": "Säkerhetsinställningar",
|
||||
"EVENTS": "Händelser",
|
||||
|
@@ -185,6 +185,32 @@
|
||||
"DESCRIPTION": "空闲刷新令牌的生命周期是刷新令牌可以未使用的最长时间。"
|
||||
}
|
||||
},
|
||||
"WEB_KEYS": {
|
||||
"DESCRIPTION": "管理您的 OIDC Web 密钥,以安全地签署和验证您的 ZITADEL 实例的令牌。",
|
||||
"TABLE": {
|
||||
"TITLE": "活动和未来的 Web 密钥",
|
||||
"DESCRIPTION": "您的当前活动和即将启用的 Web 密钥。激活新密钥将会停用当前密钥。",
|
||||
"NOTE": "注意:JWKs OIDC 端点返回可缓存的响应(默认 5 分钟)。请避免过早激活密钥,否则它可能尚未在缓存或客户端中可用。",
|
||||
"ACTIVATE": "激活下一个 Web 密钥",
|
||||
"ACTIVE": "当前活动",
|
||||
"NEXT": "队列中的下一个",
|
||||
"FUTURE": "未来",
|
||||
"WARNING": "Web密钥不到5分钟。"
|
||||
},
|
||||
"CREATE": {
|
||||
"TITLE": "创建新的 Web 密钥",
|
||||
"DESCRIPTION": "创建新的 Web 密钥会将其添加到您的列表。ZITADEL 默认使用 RSA2048 密钥和 SHA256 哈希算法。",
|
||||
"KEY_TYPE": "密钥类型",
|
||||
"BITS": "位数",
|
||||
"HASHER": "哈希算法",
|
||||
"CURVE": "曲线"
|
||||
},
|
||||
"PREVIOUS_TABLE": {
|
||||
"TITLE": "先前的 Web 密钥",
|
||||
"DESCRIPTION": "这些是您之前使用但不再活动的 Web 密钥。",
|
||||
"DEACTIVATED_ON": "停用时间"
|
||||
}
|
||||
},
|
||||
"MESSAGE_TEXTS": {
|
||||
"TITLE": "消息文本",
|
||||
"DESCRIPTION": "自定义您的通知电子邮件或短信消息的文本。如果您想禁用某些语言,请在您的实例语言设置中限制它们。",
|
||||
@@ -1353,6 +1379,7 @@
|
||||
"BRANDING": "品牌标识",
|
||||
"PRIVACYPOLICY": "隐私政策",
|
||||
"OIDC": "OIDC 令牌有效期和过期时间",
|
||||
"WEB_KEYS": "OIDC Web Keys",
|
||||
"SECRETS": "验证码外观",
|
||||
"SECURITY": "安全设置",
|
||||
"EVENTS": "活动",
|
||||
|
@@ -133,6 +133,11 @@
|
||||
color: if($is-dark-theme, #ffc1c1, #620e0e);
|
||||
background-color: if($is-dark-theme, map-get($background, state-inactive), #ffc1c1);
|
||||
}
|
||||
|
||||
&.neutral {
|
||||
background: if($is-dark-theme, #01489c78, #47a8ff82);
|
||||
color: if($is-dark-theme, #47a8ff, #01489c);
|
||||
}
|
||||
}
|
||||
|
||||
.bg-state {
|
||||
|
@@ -3528,13 +3528,20 @@
|
||||
"@zitadel/proto" "1.0.3"
|
||||
jose "^5.3.0"
|
||||
|
||||
"@zitadel/proto@1.0.3", "@zitadel/proto@^1.0.3":
|
||||
"@zitadel/proto@1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@zitadel/proto/-/proto-1.0.3.tgz#28721710d9e87009adf14f90e0c8cb9bae5275ec"
|
||||
integrity sha512-95XPGgFgfTwU1A3oQYxTv4p+Qy/9yMO/o21VRtPBfVhPusFFCW0ddg4YoKTKpQl9FbIG7VYMLmRyuJBPuf3r+g==
|
||||
dependencies:
|
||||
"@bufbuild/protobuf" "^2.2.2"
|
||||
|
||||
"@zitadel/proto@1.0.5-sha-47a2ab5":
|
||||
version "1.0.5-sha-47a2ab5"
|
||||
resolved "https://registry.yarnpkg.com/@zitadel/proto/-/proto-1.0.5-sha-47a2ab5.tgz#c0ef7d9e7d0c41d12206e5762a255b3336763732"
|
||||
integrity sha512-ZqPGJEs9Rl5ialU3OY48pU8QcR3yy79SBDMFluFNc256VJI73t7EI/aAxlJn6BcyQ7/CTASg3QfdVDKLMNEU5g==
|
||||
dependencies:
|
||||
"@bufbuild/protobuf" "^2.2.2"
|
||||
|
||||
"@zkochan/js-yaml@0.0.6":
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@zkochan/js-yaml/-/js-yaml-0.0.6.tgz#975f0b306e705e28b8068a07737fa46d3fc04826"
|
||||
|
Reference in New Issue
Block a user