fix(console): add loginV2 feature flag (#9682)

# Which Problems Are Solved

While there was already an option to enable the new login UI for a
specific application, there was no possibility to do so for the whole
instance yet. This prevents an easy switch to the new hosted UI or when
running a self hosted login UI.

# How the Problems Are Solved

Added the LoginV2 settings to the feature toggle screen.

# Additional Changes

Simplified the feature flags code a bit and made typings more robust.

# Additional Context

Part of https://github.com/zitadel/zitadel/issues/9481
This commit is contained in:
Ramon
2025-04-02 06:36:44 +02:00
committed by GitHub
parent c0ce57fef0
commit 71765c2152
25 changed files with 280 additions and 156 deletions

View File

@@ -1,29 +1,29 @@
<div class="feature-row" *ngIf="$any(toggleStates)[toggleStateKey]">
<span>{{ 'SETTING.FEATURES.' + toggleStateKey.toUpperCase() | translate }}</span>
<div class="feature-row" *ngIf="toggleState$ | async as toggleState">
<span>{{ 'SETTING.FEATURES.' + (toggleStateKey | uppercase) | translate }}</span>
<div class="row">
<mat-button-toggle-group
class="theme-toggle"
class="buttongroup"
[(ngModel)]="$any(toggleStates)[toggleStateKey].state"
(change)="onToggleChange()"
[(ngModel)]="toggleState.enabled"
(change)="toggleChange.emit(toggleState)"
name="displayview"
>
<mat-button-toggle [value]="ToggleState.DISABLED">
<mat-button-toggle [value]="false">
<div class="toggle-row">
<span>{{ 'SETTING.FEATURES.STATES.DISABLED' | translate }}</span>
<div
*ngIf="!enabled && isInherited"
*ngIf="!toggleState.enabled && (isInherited$ | async)"
class="current-dot"
matTooltip="{{ 'SETTING.FEATURES.INHERITEDINDICATOR_DESCRIPTION.DISABLED' | translate }}"
></div>
</div>
</mat-button-toggle>
<mat-button-toggle [value]="ToggleState.ENABLED">
<mat-button-toggle [value]="true">
<div class="toggle-row">
<span>{{ 'SETTING.FEATURES.STATES.ENABLED' | translate }}</span>
<div
*ngIf="enabled && isInherited"
*ngIf="toggleState.enabled && (isInherited$ | async)"
class="current-dot"
matTooltip="{{ 'SETTING.FEATURES.INHERITEDINDICATOR_DESCRIPTION.ENABLED' | translate }}"
></div>
@@ -34,7 +34,7 @@
<ng-content></ng-content>
<cnsl-info-section
class="feature-info"
*ngIf="'SETTING.FEATURES.' + toggleStateKey.toUpperCase() + '_DESCRIPTION' | translate as i18nDescription"
*ngIf="'SETTING.FEATURES.' + (toggleStateKey | uppercase) + '_DESCRIPTION' | translate as i18nDescription"
>{{ i18nDescription }}</cnsl-info-section
>
</div>

View File

@@ -1,16 +1,14 @@
import { CommonModule } from '@angular/common';
import { AsyncPipe, NgIf, UpperCasePipe } from '@angular/common';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
import { MatButtonModule } from '@angular/material/button';
import { MatTooltipModule } from '@angular/material/tooltip';
import { CopyToClipboardModule } from '../../directives/copy-to-clipboard/copy-to-clipboard.module';
import { CopyRowComponent } from '../copy-row/copy-row.component';
import { InfoSectionModule } from 'src/app/modules/info-section/info-section.module';
import { ToggleState, ToggleStateKeys, ToggleStates } from '../features/features.component';
import { ToggleStateKeys, ToggleStates } from '../features/features.component';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { FormsModule } from '@angular/forms';
import { GetInstanceFeaturesResponse } from '@zitadel/proto/zitadel/feature/v2/instance_pb';
import { FeatureFlag, Source } from '@zitadel/proto/zitadel/feature/v2/feature_pb';
import { Source } from '@zitadel/proto/zitadel/feature/v2/feature_pb';
import { ReplaySubject } from 'rxjs';
import { map } from 'rxjs/operators';
@Component({
standalone: true,
@@ -19,37 +17,29 @@ import { FeatureFlag, Source } from '@zitadel/proto/zitadel/feature/v2/feature_p
styleUrls: ['./feature-toggle.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
CommonModule,
FormsModule,
TranslateModule,
MatButtonModule,
InfoSectionModule,
MatTooltipModule,
CopyToClipboardModule,
CopyRowComponent,
MatButtonToggleModule,
UpperCasePipe,
TranslateModule,
FormsModule,
MatTooltipModule,
InfoSectionModule,
AsyncPipe,
NgIf,
],
})
export class FeatureToggleComponent {
@Input() featureData: Partial<GetInstanceFeaturesResponse> = {};
@Input() toggleStates: Partial<ToggleStates> = {};
@Input() toggleStateKey: string = '';
@Output() toggleChange = new EventEmitter<void>();
protected ToggleState = ToggleState;
protected Source = Source;
get isInherited(): boolean {
const source = this.featureData[this.toggleStateKey as ToggleStateKeys]?.source;
return source == Source.SYSTEM || source == Source.UNSPECIFIED;
export class FeatureToggleComponent<TKey extends ToggleStateKeys, TValue extends ToggleStates[TKey]> {
@Input({ required: true }) toggleStateKey!: TKey;
@Input({ required: true })
set toggleState(toggleState: TValue) {
// we copy the toggleState so we can mutate it
this.toggleState$.next(structuredClone(toggleState));
}
get enabled() {
// TODO: remove casting as not all features are a FeatureFlag
return (this.featureData[this.toggleStateKey as ToggleStateKeys] as FeatureFlag)?.enabled;
}
@Output() readonly toggleChange = new EventEmitter<TValue>();
onToggleChange() {
this.toggleChange.emit();
}
protected readonly Source = Source;
protected readonly toggleState$ = new ReplaySubject<TValue>(1);
protected readonly isInherited$ = this.toggleState$.pipe(
map(({ source }) => source == Source.SYSTEM || source == Source.UNSPECIFIED),
);
}

View File

@@ -0,0 +1,27 @@
<cnsl-feature-toggle
*ngIf="toggleState$ | async as toggleState"
toggleStateKey="loginV2"
[toggleState]="toggleState"
(toggleChange)="toggleState$.next($event); !$event.enabled && toggleChanged.emit($event)"
>
<cnsl-form-field *ngIf="toggleState.enabled">
<cnsl-label>{{ 'SETTING.FEATURES.LOGINV2_BASEURI' | translate }}</cnsl-label>
<input cnslInput [formControl]="baseUri" />
<button
matTooltip="{{ 'ACTIONS.SAVE' | translate }}"
mat-icon-button
[disabled]="baseUri.invalid"
color="primary"
type="submit"
(click)="
toggleChanged.emit({
source: toggleState.source,
enabled: toggleState.enabled,
baseUri: baseUri.value,
})
"
>
<i class="las la-save"></i>
</button>
</cnsl-form-field>
</cnsl-feature-toggle>

View File

@@ -0,0 +1,47 @@
import { ChangeDetectionStrategy, Component, DestroyRef, EventEmitter, Input, Output } from '@angular/core';
import { FeatureToggleComponent } from '../feature-toggle.component';
import { ToggleStates } from 'src/app/components/features/features.component';
import { distinctUntilKeyChanged, ReplaySubject } from 'rxjs';
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
import { AsyncPipe, NgIf } from '@angular/common';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { InputModule } from 'src/app/modules/input/input.module';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
import { MatButtonModule } from '@angular/material/button';
import { TranslateModule } from '@ngx-translate/core';
import { MatTooltipModule } from '@angular/material/tooltip';
@Component({
standalone: true,
selector: 'cnsl-login-v2-feature-toggle',
templateUrl: './login-v2-feature-toggle.component.html',
imports: [
FeatureToggleComponent,
AsyncPipe,
NgIf,
ReactiveFormsModule,
InputModule,
HasRolePipeModule,
MatButtonModule,
TranslateModule,
MatTooltipModule,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LoginV2FeatureToggleComponent {
@Input({ required: true })
set toggleState(toggleState: ToggleStates['loginV2']) {
this.toggleState$.next(toggleState);
}
@Output()
public toggleChanged = new EventEmitter<ToggleStates['loginV2']>();
protected readonly toggleState$ = new ReplaySubject<ToggleStates['loginV2']>(1);
protected readonly baseUri = new FormControl('', { nonNullable: true, validators: [Validators.required] });
constructor(destroyRef: DestroyRef) {
this.toggleState$.pipe(distinctUntilKeyChanged('baseUri'), takeUntilDestroyed(destroyRef)).subscribe(({ baseUri }) => {
this.baseUri.setValue(baseUri);
});
}
}

View File

@@ -13,26 +13,20 @@
<p class="events-desc cnsl-secondary-text">{{ 'DESCRIPTIONS.SETTINGS.FEATURES.DESCRIPTION' | translate }}</p>
<ng-template cnslHasRole [hasRole]="['iam.restrictions.write']">
<button color="warn" (click)="resetSettings()" mat-stroked-button>
<button color="warn" (click)="resetFeatures()" mat-stroked-button>
{{ 'SETTING.FEATURES.RESET' | translate }}
</button>
</ng-template>
<cnsl-card *ngIf="toggleStates && featureData">
<cnsl-card *ngIf="toggleStates$ | async as toggleStates">
<div class="features">
<cnsl-feature-toggle
*ngFor="let key of toggleStateKeys"
[featureData]="featureData"
[toggleStates]="toggleStates"
*ngFor="let key of FEATURE_KEYS"
[toggleStateKey]="key"
(toggleChange)="validateAndSave()"
[toggleState]="toggleStates[key]"
(toggleChange)="saveFeatures(key, $event)"
></cnsl-feature-toggle>
<cnsl-login-v2-feature-toggle [toggleState]="toggleStates.loginV2" (toggleChanged)="saveFeatures('loginV2', $event)" />
</div>
</cnsl-card>
</div>
<ng-template #sourceLabel let-source="source" let-last="last">
<span class="state" *ngIf="source === Source.SOURCE_SYSTEM">
{{ 'SETTING.FEATURES.SOURCE.' + source | translate }}
</span>
</ng-template>

View File

@@ -19,16 +19,14 @@ import {
GetInstanceFeaturesResponse,
SetInstanceFeaturesRequestSchema,
} from '@zitadel/proto/zitadel/feature/v2/instance_pb';
import { FeatureFlag, Source } from '@zitadel/proto/zitadel/feature/v2/feature_pb';
import { Source } from '@zitadel/proto/zitadel/feature/v2/feature_pb';
import { MessageInitShape } from '@bufbuild/protobuf';
import { firstValueFrom, Observable, ReplaySubject, shareReplay, switchMap } from 'rxjs';
import { filter, map, startWith } from 'rxjs/operators';
import { LoginV2FeatureToggleComponent } from '../feature-toggle/login-v2-feature-toggle/login-v2-feature-toggle.component';
export enum ToggleState {
ENABLED = 'ENABLED',
DISABLED = 'DISABLED',
}
// TODO: to add a new feature, add the key here and in the FEATURE_KEYS array
const FEATURE_KEYS: ToggleStateKeys[] = [
// to add a new feature, add the key here and in the FEATURE_KEYS array
const FEATURE_KEYS = [
'actions',
'consoleUseV2UserApi',
'debugOidcParentError',
@@ -36,7 +34,6 @@ const FEATURE_KEYS: ToggleStateKeys[] = [
'enableBackChannelLogout',
// 'improvedPerformance',
'loginDefaultOrg',
// 'loginV2',
'oidcLegacyIntrospection',
'oidcSingleV1SessionTermination',
'oidcTokenExchange',
@@ -44,15 +41,17 @@ const FEATURE_KEYS: ToggleStateKeys[] = [
'permissionCheckV2',
'userSchema',
// 'webKey',
];
type FeatureState = { source: Source; state: ToggleState };
export type ToggleStateKeys = Exclude<keyof GetInstanceFeaturesResponse, 'details' | '$typeName' | '$unknown'>;
] as const;
export type ToggleState = { source: Source; enabled: boolean };
export type ToggleStates = {
[key in ToggleStateKeys]: FeatureState;
[key in (typeof FEATURE_KEYS)[number]]: ToggleState;
} & {
loginV2: ToggleState & { baseUri: string };
};
export type ToggleStateKeys = keyof ToggleStates;
@Component({
imports: [
CommonModule,
@@ -68,6 +67,7 @@ export type ToggleStates = {
MatTooltipModule,
HasRoleModule,
FeatureToggleComponent,
LoginV2FeatureToggleComponent,
],
standalone: true,
selector: 'cnsl-features',
@@ -75,16 +75,15 @@ export type ToggleStates = {
styleUrls: ['./features.component.scss'],
})
export class FeaturesComponent {
protected featureData: GetInstanceFeaturesResponse | undefined;
protected toggleStates: ToggleStates | undefined;
protected Source: any = Source;
protected ToggleState: any = ToggleState;
private readonly refresh$ = new ReplaySubject<true>(1);
protected readonly toggleStates$: Observable<ToggleStates>;
protected readonly Source = Source;
protected readonly FEATURE_KEYS = FEATURE_KEYS;
constructor(
private featureService: NewFeatureService,
private breadcrumbService: BreadcrumbService,
private toast: ToastService,
private readonly featureService: NewFeatureService,
private readonly breadcrumbService: BreadcrumbService,
private readonly toast: ToastService,
) {
const breadcrumbs = [
new Breadcrumb({
@@ -95,74 +94,84 @@ export class FeaturesComponent {
];
this.breadcrumbService.setBreadcrumb(breadcrumbs);
this.getFeatures();
this.toggleStates$ = this.getToggleStates().pipe(shareReplay({ refCount: true, bufferSize: 1 }));
}
public validateAndSave() {
const req: MessageInitShape<typeof SetInstanceFeaturesRequestSchema> = {
actions: this.toggleStates?.actions?.state === ToggleState.ENABLED,
consoleUseV2UserApi: this.toggleStates?.consoleUseV2UserApi?.state === ToggleState.ENABLED,
debugOidcParentError: this.toggleStates?.debugOidcParentError?.state === ToggleState.ENABLED,
disableUserTokenEvent: this.toggleStates?.disableUserTokenEvent?.state === ToggleState.ENABLED,
enableBackChannelLogout: this.toggleStates?.enableBackChannelLogout?.state === ToggleState.ENABLED,
loginDefaultOrg: this.toggleStates?.loginDefaultOrg?.state === ToggleState.ENABLED,
oidcLegacyIntrospection: this.toggleStates?.oidcLegacyIntrospection?.state === ToggleState.ENABLED,
oidcSingleV1SessionTermination: this.toggleStates?.oidcSingleV1SessionTermination?.state === ToggleState.ENABLED,
oidcTokenExchange: this.toggleStates?.oidcTokenExchange?.state === ToggleState.ENABLED,
oidcTriggerIntrospectionProjections:
this.toggleStates?.oidcTriggerIntrospectionProjections?.state === ToggleState.ENABLED,
permissionCheckV2: this.toggleStates?.permissionCheckV2?.state === ToggleState.ENABLED,
userSchema: this.toggleStates?.userSchema?.state === ToggleState.ENABLED,
// webKey: this.toggleStates?.webKey?.state === ToggleState.ENABLED,
};
this.featureService
.setInstanceFeatures(req)
.then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
})
.catch((error) => {
this.toast.showError(error);
});
}
private getFeatures() {
this.featureService.getInstanceFeatures().then((instanceFeaturesResponse) => {
this.featureData = instanceFeaturesResponse;
this.toggleStates = this.createToggleStates(this.featureData);
});
private getToggleStates() {
return this.refresh$.pipe(
startWith(true),
switchMap(async () => {
try {
return await this.featureService.getInstanceFeatures();
} catch (error) {
this.toast.showError(error);
return undefined;
}
}),
filter(Boolean),
map((res) => this.createToggleStates(res)),
);
}
private createToggleStates(featureData: GetInstanceFeaturesResponse): ToggleStates {
const toggleStates: Partial<ToggleStates> = {};
FEATURE_KEYS.forEach((key) => {
// TODO: Fix this type cast as not all keys are present as FeatureFlag
const feature = featureData[key] as unknown as FeatureFlag;
toggleStates[key] = {
source: feature?.source || Source.SYSTEM,
state: !!feature?.enabled ? ToggleState.ENABLED : ToggleState.DISABLED,
};
});
return toggleStates as ToggleStates;
return FEATURE_KEYS.reduce(
(acc, key) => {
const feature = featureData[key];
acc[key] = {
source: feature?.source ?? Source.SYSTEM,
enabled: !!feature?.enabled,
};
return acc;
},
{
// to add special feature flags they have to be mapped here
loginV2: {
source: featureData.loginV2?.source ?? Source.SYSTEM,
enabled: !!featureData.loginV2?.required,
baseUri: featureData.loginV2?.baseUri ?? '',
},
} as ToggleStates,
);
}
public resetSettings(): void {
this.featureService
.resetInstanceFeatures()
.then(() => {
this.toast.showInfo('POLICY.TOAST.RESETSUCCESS', true);
setTimeout(() => {
this.getFeatures();
}, 1000);
})
.catch((error) => {
this.toast.showError(error);
});
public async saveFeatures<TKey extends ToggleStateKeys, TValue extends ToggleStates[TKey]>(key: TKey, value: TValue) {
const toggleStates = { ...(await firstValueFrom(this.toggleStates$)), [key]: value };
const req = FEATURE_KEYS.reduce<MessageInitShape<typeof SetInstanceFeaturesRequestSchema>>((acc, key) => {
acc[key] = toggleStates[key].enabled;
return acc;
}, {});
// to save special flags they have to be handled here
req.loginV2 = {
required: toggleStates.loginV2.enabled,
baseUri: toggleStates.loginV2.baseUri,
};
try {
await this.featureService.setInstanceFeatures(req);
// needed because of eventual consistency
await new Promise((res) => setTimeout(res, 1000));
this.refresh$.next(true);
this.toast.showInfo('POLICY.TOAST.SET', true);
} catch (error) {
this.toast.showError(error);
}
}
public get toggleStateKeys() {
return Object.keys(this.toggleStates ?? {});
public async resetFeatures() {
try {
await this.featureService.resetInstanceFeatures();
// needed because of eventual consistency
await new Promise((res) => setTimeout(res, 1000));
this.refresh$.next(true);
this.toast.showInfo('POLICY.TOAST.RESETSUCCESS', true);
} catch (error) {
this.toast.showError(error);
}
}
}

View File

@@ -1541,7 +1541,10 @@
},
"RESET": "Задай всички на наследено",
"CONSOLEUSEV2USERAPI": "Използвайте V2 API в конзолата за създаване на потребител",
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Когато този флаг е активиран, конзолата използва V2 User API за създаване на нови потребители. С V2 API новосъздадените потребители започват без начален статус."
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Когато този флаг е активиран, конзолата използва V2 User API за създаване на нови потребители. С V2 API новосъздадените потребители започват без начален статус.",
"LOGINV2": "Вход V2",
"LOGINV2_DESCRIPTION": "Активирането на това включва новия потребителски интерфейс за вход, базиран на TypeScript, с подобрена сигурност, производителност и възможности за персонализиране.",
"LOGINV2_BASEURI": "Базов URI"
},
"DIALOG": {
"RESET": {

View File

@@ -1542,7 +1542,10 @@
},
"RESET": "Nastavit vše na děděné",
"CONSOLEUSEV2USERAPI": "Použijte V2 API v konzoli pro vytvoření uživatele",
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Když je tato příznak povolen, konzole používá V2 User API k vytvoření nových uživatelů. S V2 API nově vytvoření uživatelé začínají bez počátečního stavu."
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Když je tato příznak povolen, konzole používá V2 User API k vytvoření nových uživatelů. S V2 API nově vytvoření uživatelé začínají bez počátečního stavu.",
"LOGINV2": "Přihlášení V2",
"LOGINV2_DESCRIPTION": "Povolením této možnosti se aktivuje nové přihlašovací rozhraní založené na TypeScriptu s vylepšeným zabezpečením, výkonem a přizpůsobitelností.",
"LOGINV2_BASEURI": "Základní URI"
},
"DIALOG": {
"RESET": {

View File

@@ -1542,7 +1542,10 @@
},
"RESET": "Alle auf Erben setzen",
"CONSOLEUSEV2USERAPI": "Verwende die V2-API in der Konsole zur Erstellung von Benutzern",
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Wenn diese Option aktiviert ist, verwendet die Konsole die V2 User API, um neue Benutzer zu erstellen. Mit der V2 API starten neu erstellte Benutzer nicht im Initial Zustand."
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Wenn diese Option aktiviert ist, verwendet die Konsole die V2 User API, um neue Benutzer zu erstellen. Mit der V2 API starten neu erstellte Benutzer nicht im Initial Zustand.",
"LOGINV2": "Login V2",
"LOGINV2_DESCRIPTION": "Durch das Aktivieren wird das neue TypeScript-basierte Login-UI mit verbesserter Sicherheit, Leistung und Anpassbarkeit aktiviert.",
"LOGINV2_BASEURI": "Basis-URI"
},
"DIALOG": {
"RESET": {

View File

@@ -1542,7 +1542,10 @@
},
"RESET": "Set all to inherit",
"CONSOLEUSEV2USERAPI": "Use V2 Api in Console for User creation",
"CONSOLEUSEV2USERAPI_DESCRIPTION": "When this flag is enabled, the console uses the V2 User API to create new users. With the V2 API, newly created users start without an initial state."
"CONSOLEUSEV2USERAPI_DESCRIPTION": "When this flag is enabled, the console uses the V2 User API to create new users. With the V2 API, newly created users start without an initial state.",
"LOGINV2": "Login V2",
"LOGINV2_DESCRIPTION": "Enabling this activates the new TypeScript-based login UI with improved security, performance, and customization.",
"LOGINV2_BASEURI": "Base URI"
},
"DIALOG": {
"RESET": {

View File

@@ -1543,7 +1543,10 @@
},
"RESET": "Establecer todo a heredado",
"CONSOLEUSEV2USERAPI": "Utilice la API V2 en la consola para la creación de usuarios",
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Cuando esta opción está habilitada, la consola utiliza la API V2 de usuario para crear nuevos usuarios. Con la API V2, los usuarios recién creados comienzan sin un estado inicial."
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Cuando esta opción está habilitada, la consola utiliza la API V2 de usuario para crear nuevos usuarios. Con la API V2, los usuarios recién creados comienzan sin un estado inicial.",
"LOGINV2": "Inicio de sesión V2",
"LOGINV2_DESCRIPTION": "Al habilitar esto, se activa la nueva interfaz de inicio de sesión basada en TypeScript con mejoras en seguridad, rendimiento y personalización.",
"LOGINV2_BASEURI": "URI base"
},
"DIALOG": {
"RESET": {

View File

@@ -1542,7 +1542,10 @@
},
"RESET": "Réinitialiser tout sur hérité",
"CONSOLEUSEV2USERAPI": "Utilisez l'API V2 dans la console pour la création d'utilisateurs",
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Lorsque ce drapeau est activé, la console utilise l'API V2 User pour créer de nouveaux utilisateurs. Avec l'API V2, les nouveaux utilisateurs commencent sans état initial."
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Lorsque ce drapeau est activé, la console utilise l'API V2 User pour créer de nouveaux utilisateurs. Avec l'API V2, les nouveaux utilisateurs commencent sans état initial.",
"LOGINV2": "Connexion V2",
"LOGINV2_DESCRIPTION": "Lactivation de cette option lance la nouvelle interface de connexion basée sur TypeScript, avec une sécurité, des performances et une personnalisation améliorées.",
"LOGINV2_BASEURI": "URI de base"
},
"DIALOG": {
"RESET": {

View File

@@ -1540,7 +1540,10 @@
},
"RESET": "Mindent állíts öröklésre",
"CONSOLEUSEV2USERAPI": "Használja a V2 API-t a konzolban felhasználók létrehozásához",
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Ha ez a jelző engedélyezve van, a konzol a V2 User API-t használja új felhasználók létrehozásához. A V2 API-val az újonnan létrehozott felhasználók kezdeti állapot nélkül indulnak."
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Ha ez a jelző engedélyezve van, a konzol a V2 User API-t használja új felhasználók létrehozásához. A V2 API-val az újonnan létrehozott felhasználók kezdeti állapot nélkül indulnak.",
"LOGINV2": "Bejelentkezés V2",
"LOGINV2_DESCRIPTION": "Ennek engedélyezésével aktiválódik az új, TypeScript-alapú bejelentkezési felület, amely jobb biztonságot, teljesítményt és testreszabhatóságot nyújt.",
"LOGINV2_BASEURI": "Alap URI"
},
"DIALOG": {
"RESET": {

View File

@@ -1409,7 +1409,10 @@
},
"RESET": "Tetapkan semua untuk diwarisi",
"CONSOLEUSEV2USERAPI": "Gunakan API V2 di konsol untuk pembuatan pengguna",
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Ketika flag ini diaktifkan, konsol menggunakan API Pengguna V2 untuk membuat pengguna baru. Dengan API V2, pengguna yang baru dibuat dimulai tanpa keadaan awal."
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Ketika flag ini diaktifkan, konsol menggunakan API Pengguna V2 untuk membuat pengguna baru. Dengan API V2, pengguna yang baru dibuat dimulai tanpa keadaan awal.",
"LOGINV2": "Login V2",
"LOGINV2_DESCRIPTION": "Mengaktifkan ini akan mengaktifkan antarmuka login baru berbasis TypeScript dengan keamanan, performa, dan kustomisasi yang lebih baik.",
"LOGINV2_BASEURI": "URI dasar"
},
"DIALOG": {
"RESET": {

View File

@@ -1542,7 +1542,10 @@
},
"RESET": "Imposta tutto su predefinito",
"CONSOLEUSEV2USERAPI": "Utilizza l'API V2 nella console per la creazione degli utenti",
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Quando questa opzione è abilitata, la console utilizza l'API V2 User per creare nuovi utenti. Con l'API V2, i nuovi utenti creati iniziano senza uno stato iniziale."
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Quando questa opzione è abilitata, la console utilizza l'API V2 User per creare nuovi utenti. Con l'API V2, i nuovi utenti creati iniziano senza uno stato iniziale.",
"LOGINV2": "Accesso V2",
"LOGINV2_DESCRIPTION": "Abilitando questa opzione si attiva la nuova interfaccia di login basata su TypeScript con sicurezza, prestazioni e personalizzazione migliorate.",
"LOGINV2_BASEURI": "URI di base"
},
"DIALOG": {
"RESET": {

View File

@@ -1542,7 +1542,10 @@
},
"RESET": "すべて継承に設定",
"CONSOLEUSEV2USERAPI": "コンソールでユーザー作成のためにV2 APIを使用してください。",
"CONSOLEUSEV2USERAPI_DESCRIPTION": "このフラグが有効化されると、コンソールはV2ユーザーAPIを使用して新しいユーザーを作成します。V2 APIでは、新しく作成されたユーザーは初期状態なしで開始します。"
"CONSOLEUSEV2USERAPI_DESCRIPTION": "このフラグが有効化されると、コンソールはV2ユーザーAPIを使用して新しいユーザーを作成します。V2 APIでは、新しく作成されたユーザーは初期状態なしで開始します。",
"LOGINV2": "ログイン V2",
"LOGINV2_DESCRIPTION": "これを有効にすると、セキュリティ、パフォーマンス、およびカスタマイズ性が向上した、TypeScript ベースの新しいログイン UI が有効になります。",
"LOGINV2_BASEURI": "ベースURI"
},
"DIALOG": {
"RESET": {

View File

@@ -1542,7 +1542,10 @@
},
"RESET": "모두 상속으로 설정",
"CONSOLEUSEV2USERAPI": "콘솔에서 사용자 생성을 위해 V2 API를 사용하세요",
"CONSOLEUSEV2USERAPI_DESCRIPTION": "이 플래그가 활성화되면 콘솔은 V2 사용자 API를 사용하여 새 사용자를 생성합니다. V2 API를 사용하면 새로 생성된 사용자는 초기 상태 없이 시작합니다."
"CONSOLEUSEV2USERAPI_DESCRIPTION": "이 플래그가 활성화되면 콘솔은 V2 사용자 API를 사용하여 새 사용자를 생성합니다. V2 API를 사용하면 새로 생성된 사용자는 초기 상태 없이 시작합니다.",
"LOGINV2": "로그인 V2",
"LOGINV2_DESCRIPTION": "이 옵션을 활성화하면 보안, 성능 및 사용자 정의 기능이 향상된 새로운 TypeScript 기반 로그인 UI가 활성화됩니다.",
"LOGINV2_BASEURI": "기본 URI"
},
"DIALOG": {
"RESET": {

View File

@@ -1543,7 +1543,10 @@
},
"RESET": "Поставете ги сите да наследат",
"CONSOLEUSEV2USERAPI": "Користете V2 API во конзолата за креирање на корисници",
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Кога ова знаме е овозможено, конзолата го користи V2 User API за креирање на нови корисници. Со V2 API, новосоздадените корисници започнуваат без почетна состојба."
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Кога ова знаме е овозможено, конзолата го користи V2 User API за креирање на нови корисници. Со V2 API, новосоздадените корисници започнуваат без почетна состојба.",
"LOGINV2": "Најава V2",
"LOGINV2_DESCRIPTION": "Овозможувањето на ова ја активира новата TypeScript-базирана најава со подобрена безбедност, перформанси и прилагодливост.",
"LOGINV2_BASEURI": "Основен URI"
},
"DIALOG": {
"RESET": {

View File

@@ -1542,7 +1542,10 @@
},
"RESET": "Alles instellen op overgenomen",
"CONSOLEUSEV2USERAPI": "Gebruik de V2 API in de console voor het aanmaken van gebruikers",
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Wanneer deze vlag is ingeschakeld, gebruikt de console de V2 User API om nieuwe gebruikers aan te maken. Met de V2 API beginnen nieuw aangemaakte gebruikers zonder een initiële status."
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Wanneer deze vlag is ingeschakeld, gebruikt de console de V2 User API om nieuwe gebruikers aan te maken. Met de V2 API beginnen nieuw aangemaakte gebruikers zonder een initiële status.",
"LOGINV2": "Inloggen V2",
"LOGINV2_DESCRIPTION": "Door dit in te schakelen wordt de nieuwe TypeScript-gebaseerde login-UI geactiveerd met verbeterde beveiliging, prestaties en aanpasbaarheid.",
"LOGINV2_BASEURI": "Basis-URI"
},
"DIALOG": {
"RESET": {

View File

@@ -1541,7 +1541,10 @@
},
"RESET": "Ustaw wszystko na dziedziczone",
"CONSOLEUSEV2USERAPI": "Użyj API V2 w konsoli do tworzenia użytkowników",
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Gdy ta flaga jest włączona, konsola używa API V2 User do tworzenia nowych użytkowników. W przypadku API V2 nowo utworzeni użytkownicy rozpoczynają bez stanu początkowego."
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Gdy ta flaga jest włączona, konsola używa API V2 User do tworzenia nowych użytkowników. W przypadku API V2 nowo utworzeni użytkownicy rozpoczynają bez stanu początkowego.",
"LOGINV2": "Logowanie V2",
"LOGINV2_DESCRIPTION": "Włączenie tej opcji aktywuje nowy interfejs logowania oparty na TypeScript z ulepszonym bezpieczeństwem, wydajnością i możliwością dostosowania.",
"LOGINV2_BASEURI": "Podstawowy URI"
},
"DIALOG": {
"RESET": {

View File

@@ -1543,7 +1543,10 @@
},
"RESET": "Definir tudo para herdar",
"CONSOLEUSEV2USERAPI": "Use a API V2 no console para criação de usuários",
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Quando esta opção está ativada, o console utiliza a API V2 de Usuários para criar novos usuários. Com a API V2, os novos usuários criados começam sem um estado inicial."
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Quando esta opção está ativada, o console utiliza a API V2 de Usuários para criar novos usuários. Com a API V2, os novos usuários criados começam sem um estado inicial.",
"LOGINV2": "Login V2",
"LOGINV2_DESCRIPTION": "Ativar esta opção ativa a nova interface de login baseada em TypeScript, com melhorias na segurança, desempenho e personalização.",
"LOGINV2_BASEURI": "URI base"
},
"DIALOG": {
"RESET": {

View File

@@ -1540,7 +1540,10 @@
},
"RESET": "Setați totul pentru a moșteni",
"CONSOLEUSEV2USERAPI": "Utilizați API-ul V2 în Consola pentru crearea utilizatorului",
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Când acest indicator este activat, consola utilizează API-ul de utilizator V2 pentru a crea utilizatori noi. Cu API-ul V2, utilizatorii nou creați încep fără o stare inițială."
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Când acest indicator este activat, consola utilizează API-ul de utilizator V2 pentru a crea utilizatori noi. Cu API-ul V2, utilizatorii nou creați încep fără o stare inițială.",
"LOGINV2": "Autentificare V2",
"LOGINV2_DESCRIPTION": "Activarea acestei opțiuni pornește noua interfață de autentificare bazată pe TypeScript, cu securitate, performanță și personalizare îmbunătățite.",
"LOGINV2_BASEURI": "URI de bază"
},
"DIALOG": {
"RESET": {

View File

@@ -1595,7 +1595,10 @@
},
"RESET": "Установить все по умолчанию",
"CONSOLEUSEV2USERAPI": "Используйте V2 API в консоли для создания пользователей",
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Когда этот флаг включен, консоль использует V2 User API для создания новых пользователей. С API V2 новые пользователи создаются без начального состояния."
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Когда этот флаг включен, консоль использует V2 User API для создания новых пользователей. С API V2 новые пользователи создаются без начального состояния.",
"LOGINV2": "Вход V2",
"LOGINV2_DESCRIPTION": "Включение этой опции активирует новый интерфейс входа на основе TypeScript с улучшенной безопасностью, производительностью и возможностью настройки.",
"LOGINV2_BASEURI": "Базовый URI"
},
"DIALOG": {
"RESET": {

View File

@@ -1546,7 +1546,10 @@
},
"RESET": "Återställ allt till arv",
"CONSOLEUSEV2USERAPI": "Använd V2 API i konsolen för att skapa användare",
"CONSOLEUSEV2USERAPI_DESCRIPTION": "När denna flagga är aktiverad använder konsolen V2 User API för att skapa nya användare. Med V2 API startar nyligen skapade användare utan ett initialt tillstånd."
"CONSOLEUSEV2USERAPI_DESCRIPTION": "När denna flagga är aktiverad använder konsolen V2 User API för att skapa nya användare. Med V2 API startar nyligen skapade användare utan ett initialt tillstånd.",
"LOGINV2": "Inloggning V2",
"LOGINV2_DESCRIPTION": "Att aktivera detta startar det nya inloggningsgränssnittet baserat på TypeScript med förbättrad säkerhet, prestanda och anpassning.",
"LOGINV2_BASEURI": "Bas-URI"
},
"DIALOG": {
"RESET": {

View File

@@ -1542,7 +1542,10 @@
},
"RESET": "全部设置为继承",
"CONSOLEUSEV2USERAPI": "在控制台中使用V2 API创建用户。",
"CONSOLEUSEV2USERAPI_DESCRIPTION": "启用此标志时控制台使用V2用户API创建新用户。使用V2 API新创建的用户将以无初始状态开始。"
"CONSOLEUSEV2USERAPI_DESCRIPTION": "启用此标志时控制台使用V2用户API创建新用户。使用V2 API新创建的用户将以无初始状态开始。",
"LOGINV2": "登录 V2",
"LOGINV2_DESCRIPTION": "启用此选项将激活基于 TypeScript 的新登录界面,具有更高的安全性、性能和可定制性。",
"LOGINV2_BASEURI": "基础 URI"
},
"DIALOG": {
"RESET": {