mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-14 03:37:35 +00:00
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:
@@ -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>
|
||||
|
@@ -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),
|
||||
);
|
||||
}
|
||||
|
@@ -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>
|
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
@@ -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>
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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": {
|
||||
|
@@ -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": {
|
||||
|
@@ -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": {
|
||||
|
@@ -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": {
|
||||
|
@@ -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": {
|
||||
|
@@ -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": "L’activation 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": {
|
||||
|
@@ -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": {
|
||||
|
@@ -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": {
|
||||
|
@@ -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": {
|
||||
|
@@ -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": {
|
||||
|
@@ -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": {
|
||||
|
@@ -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": {
|
||||
|
@@ -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": {
|
||||
|
@@ -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": {
|
||||
|
@@ -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": {
|
||||
|
@@ -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": {
|
||||
|
@@ -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": {
|
||||
|
@@ -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": {
|
||||
|
@@ -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": {
|
||||
|
Reference in New Issue
Block a user