mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-12 17:15:15 +00:00
feat(console): feature settings (#7713)
* feature settings * feature component, i18n * i18n, save settings * i18n * reset button, state if system inherited, i18n * iam.restrictions.read * refetch feature after reset * use toggles instead of checkboxes * i18n * rm logs * fix i18n * show unspecified as inherited --------- Co-authored-by: Elio Bischof <[email protected]>
This commit is contained in:
co-authored by
Elio Bischof
parent
dcfa2f7955
commit
2061d1acdc
@@ -0,0 +1,347 @@
|
||||
<div class="feature-settings-wrapper">
|
||||
<div class="feature-title-row">
|
||||
<h2>{{ 'DESCRIPTIONS.SETTINGS.FEATURES.TITLE' | translate }}</h2>
|
||||
<a
|
||||
mat-icon-button
|
||||
href="https://zitadel.com/docs/apis/resources/feature_service_v2/feature-service"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<mat-icon class="icon">info_outline</mat-icon>
|
||||
</a>
|
||||
</div>
|
||||
<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>
|
||||
{{ 'SETTING.FEATURES.RESET' | translate }}
|
||||
</button>
|
||||
</ng-template>
|
||||
|
||||
<cnsl-card *ngIf="toggleStates && featureData">
|
||||
<div class="features">
|
||||
<div class="feature-row" *ngIf="toggleStates.loginDefaultOrg">
|
||||
<span>{{ 'SETTING.FEATURES.LOGINDEFAULTORG' | translate }}</span>
|
||||
<div class="row">
|
||||
<mat-button-toggle-group
|
||||
class="theme-toggle"
|
||||
class="buttongroup"
|
||||
[(ngModel)]="toggleStates.loginDefaultOrg.state"
|
||||
(change)="validateAndSave()"
|
||||
name="displayview"
|
||||
aria-label="Display View"
|
||||
>
|
||||
<mat-button-toggle [value]="ToggleState.INHERITED">
|
||||
<div class="toggle-row">
|
||||
<span>{{ 'SETTING.FEATURES.STATES.INHERITED' | translate }}</span>
|
||||
<i
|
||||
class="info-i las la-question-circle"
|
||||
matTooltip="{{ 'SETTING.FEATURES.INHERITED_DESCRIPTION' | translate }}"
|
||||
></i>
|
||||
<div
|
||||
*ngIf="
|
||||
!!featureData.loginDefaultOrg?.enabled &&
|
||||
(featureData.loginDefaultOrg?.source === Source.SOURCE_SYSTEM ||
|
||||
featureData.loginDefaultOrg?.source === Source.SOURCE_UNSPECIFIED)
|
||||
"
|
||||
class="current-dot enabled"
|
||||
></div>
|
||||
<div
|
||||
*ngIf="
|
||||
!featureData.loginDefaultOrg?.enabled &&
|
||||
(featureData.loginDefaultOrg?.source === Source.SOURCE_SYSTEM ||
|
||||
featureData.loginDefaultOrg?.source === Source.SOURCE_UNSPECIFIED)
|
||||
"
|
||||
class="current-dot disabled"
|
||||
></div>
|
||||
</div>
|
||||
</mat-button-toggle>
|
||||
<mat-button-toggle [value]="ToggleState.DISABLED">
|
||||
<div class="toggle-row">
|
||||
<span> {{ 'SETTING.FEATURES.STATES.DISABLED' | translate }}</span>
|
||||
</div>
|
||||
</mat-button-toggle>
|
||||
<mat-button-toggle [value]="ToggleState.ENABLED">
|
||||
<div class="toggle-row">
|
||||
<span> {{ 'SETTING.FEATURES.STATES.ENABLED' | translate }}</span>
|
||||
</div>
|
||||
</mat-button-toggle>
|
||||
</mat-button-toggle-group>
|
||||
</div>
|
||||
|
||||
<cnsl-info-section class="feature-info">{{
|
||||
'SETTING.FEATURES.LOGINDEFAULTORG_DESCRIPTION' | translate
|
||||
}}</cnsl-info-section>
|
||||
</div>
|
||||
|
||||
<div class="feature-row" *ngIf="toggleStates.oidcLegacyIntrospection">
|
||||
<span>{{ 'SETTING.FEATURES.OIDCLEGACYINTROSPECTION' | translate }}</span>
|
||||
<div class="row">
|
||||
<mat-button-toggle-group
|
||||
class="theme-toggle"
|
||||
class="buttongroup"
|
||||
[(ngModel)]="toggleStates.oidcLegacyIntrospection.state"
|
||||
(change)="validateAndSave()"
|
||||
name="displayview"
|
||||
aria-label="Display View"
|
||||
>
|
||||
<mat-button-toggle [value]="ToggleState.INHERITED">
|
||||
<div class="toggle-row">
|
||||
<span>{{ 'SETTING.FEATURES.STATES.INHERITED' | translate }}</span>
|
||||
<i
|
||||
class="info-i las la-question-circle"
|
||||
matTooltip="{{ 'SETTING.FEATURES.INHERITED_DESCRIPTION' | translate }}"
|
||||
></i>
|
||||
<div
|
||||
*ngIf="
|
||||
!!featureData.oidcLegacyIntrospection?.enabled &&
|
||||
(featureData.oidcLegacyIntrospection?.source === Source.SOURCE_SYSTEM ||
|
||||
featureData.oidcLegacyIntrospection?.source === Source.SOURCE_UNSPECIFIED)
|
||||
"
|
||||
class="current-dot enabled"
|
||||
></div>
|
||||
<div
|
||||
*ngIf="
|
||||
!featureData.oidcLegacyIntrospection?.enabled &&
|
||||
(featureData.oidcLegacyIntrospection?.source === Source.SOURCE_SYSTEM ||
|
||||
featureData.oidcLegacyIntrospection?.source === Source.SOURCE_UNSPECIFIED)
|
||||
"
|
||||
class="current-dot disabled"
|
||||
></div>
|
||||
</div>
|
||||
</mat-button-toggle>
|
||||
<mat-button-toggle [value]="ToggleState.DISABLED">
|
||||
<div class="toggle-row">
|
||||
<span> {{ 'SETTING.FEATURES.STATES.DISABLED' | translate }}</span>
|
||||
</div>
|
||||
</mat-button-toggle>
|
||||
<mat-button-toggle [value]="ToggleState.ENABLED">
|
||||
<div class="toggle-row">
|
||||
<span> {{ 'SETTING.FEATURES.STATES.ENABLED' | translate }}</span>
|
||||
</div>
|
||||
</mat-button-toggle>
|
||||
</mat-button-toggle-group>
|
||||
</div>
|
||||
<cnsl-info-section class="feature-info">{{
|
||||
'SETTING.FEATURES.OIDCLEGACYINTROSPECTION_DESCRIPTION' | translate
|
||||
}}</cnsl-info-section>
|
||||
</div>
|
||||
|
||||
<div class="feature-row" *ngIf="toggleStates.oidcTokenExchange">
|
||||
<span>{{ 'SETTING.FEATURES.OIDCTOKENEXCHANGE' | translate }}</span>
|
||||
<div class="row">
|
||||
<mat-button-toggle-group
|
||||
class="theme-toggle"
|
||||
class="buttongroup"
|
||||
[(ngModel)]="toggleStates.oidcTokenExchange.state"
|
||||
(change)="validateAndSave()"
|
||||
name="displayview"
|
||||
aria-label="Display View"
|
||||
>
|
||||
<mat-button-toggle [value]="ToggleState.INHERITED">
|
||||
<div class="toggle-row">
|
||||
<span>{{ 'SETTING.FEATURES.STATES.INHERITED' | translate }}</span>
|
||||
<i
|
||||
class="info-i las la-question-circle"
|
||||
matTooltip="{{ 'SETTING.FEATURES.INHERITED_DESCRIPTION' | translate }}"
|
||||
></i>
|
||||
<div
|
||||
*ngIf="
|
||||
!!featureData.oidcTokenExchange?.enabled &&
|
||||
(featureData.oidcTokenExchange?.source === Source.SOURCE_SYSTEM ||
|
||||
featureData.oidcTokenExchange?.source === Source.SOURCE_UNSPECIFIED)
|
||||
"
|
||||
class="current-dot enabled"
|
||||
></div>
|
||||
<div
|
||||
*ngIf="
|
||||
!featureData.oidcTokenExchange?.enabled &&
|
||||
(featureData.oidcTokenExchange?.source === Source.SOURCE_SYSTEM ||
|
||||
featureData.oidcTokenExchange?.source === Source.SOURCE_UNSPECIFIED)
|
||||
"
|
||||
class="current-dot disabled"
|
||||
></div>
|
||||
</div>
|
||||
</mat-button-toggle>
|
||||
<mat-button-toggle [value]="ToggleState.DISABLED">
|
||||
<div class="toggle-row">
|
||||
<span> {{ 'SETTING.FEATURES.STATES.DISABLED' | translate }}</span>
|
||||
</div>
|
||||
</mat-button-toggle>
|
||||
<mat-button-toggle [value]="ToggleState.ENABLED">
|
||||
<div class="toggle-row">
|
||||
<span> {{ 'SETTING.FEATURES.STATES.ENABLED' | translate }}</span>
|
||||
</div>
|
||||
</mat-button-toggle>
|
||||
</mat-button-toggle-group>
|
||||
</div>
|
||||
<cnsl-info-section class="feature-info">{{
|
||||
'SETTING.FEATURES.OIDCTOKENEXCHANGE_DESCRIPTION' | translate
|
||||
}}</cnsl-info-section>
|
||||
</div>
|
||||
|
||||
<div class="feature-row" *ngIf="toggleStates.oidcTriggerIntrospectionProjections">
|
||||
<span>{{ 'SETTING.FEATURES.OIDCTRIGGERINTROSPECTIONPROJECTIONS' | translate }}</span>
|
||||
<div class="row">
|
||||
<mat-button-toggle-group
|
||||
class="theme-toggle"
|
||||
class="buttongroup"
|
||||
[(ngModel)]="toggleStates.oidcTriggerIntrospectionProjections.state"
|
||||
(change)="validateAndSave()"
|
||||
name="displayview"
|
||||
aria-label="Display View"
|
||||
>
|
||||
<mat-button-toggle [value]="ToggleState.INHERITED">
|
||||
<div class="toggle-row">
|
||||
<span>{{ 'SETTING.FEATURES.STATES.INHERITED' | translate }}</span>
|
||||
<i
|
||||
class="info-i las la-question-circle"
|
||||
matTooltip="{{ 'SETTING.FEATURES.INHERITED_DESCRIPTION' | translate }}"
|
||||
></i>
|
||||
<div
|
||||
*ngIf="
|
||||
!!featureData.oidcTriggerIntrospectionProjections?.enabled &&
|
||||
(featureData.oidcTriggerIntrospectionProjections?.source === Source.SOURCE_SYSTEM ||
|
||||
featureData.oidcTriggerIntrospectionProjections?.source === Source.SOURCE_UNSPECIFIED)
|
||||
"
|
||||
class="current-dot enabled"
|
||||
></div>
|
||||
<div
|
||||
*ngIf="
|
||||
!featureData.oidcTriggerIntrospectionProjections?.enabled &&
|
||||
(featureData.oidcTriggerIntrospectionProjections?.source === Source.SOURCE_SYSTEM ||
|
||||
featureData.oidcTriggerIntrospectionProjections?.source === Source.SOURCE_UNSPECIFIED)
|
||||
"
|
||||
class="current-dot disabled"
|
||||
></div>
|
||||
</div>
|
||||
</mat-button-toggle>
|
||||
<mat-button-toggle [value]="ToggleState.DISABLED">
|
||||
<div class="toggle-row">
|
||||
<span> {{ 'SETTING.FEATURES.STATES.DISABLED' | translate }}</span>
|
||||
</div>
|
||||
</mat-button-toggle>
|
||||
<mat-button-toggle [value]="ToggleState.ENABLED">
|
||||
<div class="toggle-row">
|
||||
<span> {{ 'SETTING.FEATURES.STATES.ENABLED' | translate }}</span>
|
||||
</div>
|
||||
</mat-button-toggle>
|
||||
</mat-button-toggle-group>
|
||||
</div>
|
||||
<cnsl-info-section class="feature-info">{{
|
||||
'SETTING.FEATURES.OIDCTRIGGERINTROSPECTIONPROJECTIONS_DESCRIPTION' | translate
|
||||
}}</cnsl-info-section>
|
||||
</div>
|
||||
|
||||
<div class="feature-row" *ngIf="toggleStates.userSchema">
|
||||
<span>{{ 'SETTING.FEATURES.USERSCHEMA' | translate }}</span>
|
||||
<div class="row">
|
||||
<mat-button-toggle-group
|
||||
class="theme-toggle"
|
||||
class="buttongroup"
|
||||
[(ngModel)]="toggleStates.userSchema.state"
|
||||
(change)="validateAndSave()"
|
||||
name="displayview"
|
||||
aria-label="Display View"
|
||||
>
|
||||
<mat-button-toggle [value]="ToggleState.INHERITED">
|
||||
<div class="toggle-row">
|
||||
<span>{{ 'SETTING.FEATURES.STATES.INHERITED' | translate }}</span>
|
||||
<i
|
||||
class="info-i las la-question-circle"
|
||||
matTooltip="{{ 'SETTING.FEATURES.INHERITED_DESCRIPTION' | translate }}"
|
||||
></i>
|
||||
<div
|
||||
*ngIf="
|
||||
!!featureData.userSchema?.enabled &&
|
||||
(featureData.userSchema?.source === Source.SOURCE_SYSTEM ||
|
||||
featureData.userSchema?.source === Source.SOURCE_UNSPECIFIED)
|
||||
"
|
||||
class="current-dot enabled"
|
||||
></div>
|
||||
<div
|
||||
*ngIf="
|
||||
!featureData.userSchema?.enabled &&
|
||||
(featureData.userSchema?.source === Source.SOURCE_SYSTEM ||
|
||||
featureData.userSchema?.source === Source.SOURCE_UNSPECIFIED)
|
||||
"
|
||||
class="current-dot disabled"
|
||||
></div>
|
||||
</div>
|
||||
</mat-button-toggle>
|
||||
<mat-button-toggle [value]="ToggleState.DISABLED">
|
||||
<div class="toggle-row">
|
||||
<span> {{ 'SETTING.FEATURES.STATES.DISABLED' | translate }}</span>
|
||||
</div>
|
||||
</mat-button-toggle>
|
||||
<mat-button-toggle [value]="ToggleState.ENABLED">
|
||||
<div class="toggle-row">
|
||||
<span> {{ 'SETTING.FEATURES.STATES.ENABLED' | translate }}</span>
|
||||
</div>
|
||||
</mat-button-toggle>
|
||||
</mat-button-toggle-group>
|
||||
</div>
|
||||
<cnsl-info-section class="feature-info">{{
|
||||
'SETTING.FEATURES.USERSCHEMA_DESCRIPTION' | translate
|
||||
}}</cnsl-info-section>
|
||||
</div>
|
||||
|
||||
<div class="feature-row" *ngIf="toggleStates.actions">
|
||||
<span>{{ 'SETTING.FEATURES.ACTIONS' | translate }}</span>
|
||||
<div class="row">
|
||||
<mat-button-toggle-group
|
||||
class="theme-toggle"
|
||||
class="buttongroup"
|
||||
[(ngModel)]="toggleStates.actions.state"
|
||||
(change)="validateAndSave()"
|
||||
name="displayview"
|
||||
aria-label="Display View"
|
||||
>
|
||||
<mat-button-toggle [value]="ToggleState.INHERITED">
|
||||
<div class="toggle-row">
|
||||
<span>{{ 'SETTING.FEATURES.STATES.INHERITED' | translate }}</span>
|
||||
<i
|
||||
class="info-i las la-question-circle"
|
||||
matTooltip="{{ 'SETTING.FEATURES.INHERITED_DESCRIPTION' | translate }}"
|
||||
></i>
|
||||
<div
|
||||
*ngIf="
|
||||
!!featureData.actions?.enabled &&
|
||||
(featureData.actions?.source === Source.SOURCE_SYSTEM ||
|
||||
featureData.actions?.source === Source.SOURCE_UNSPECIFIED)
|
||||
"
|
||||
class="current-dot enabled"
|
||||
></div>
|
||||
<div
|
||||
*ngIf="
|
||||
!featureData.actions?.enabled &&
|
||||
(featureData.actions?.source === Source.SOURCE_SYSTEM ||
|
||||
featureData.actions?.source === Source.SOURCE_UNSPECIFIED)
|
||||
"
|
||||
class="current-dot disabled"
|
||||
></div>
|
||||
</div>
|
||||
</mat-button-toggle>
|
||||
<mat-button-toggle [value]="ToggleState.DISABLED">
|
||||
<div class="toggle-row">
|
||||
<span> {{ 'SETTING.FEATURES.STATES.DISABLED' | translate }}</span>
|
||||
</div>
|
||||
</mat-button-toggle>
|
||||
<mat-button-toggle [value]="ToggleState.ENABLED">
|
||||
<div class="toggle-row">
|
||||
<span> {{ 'SETTING.FEATURES.STATES.ENABLED' | translate }}</span>
|
||||
</div>
|
||||
</mat-button-toggle>
|
||||
</mat-button-toggle-group>
|
||||
</div>
|
||||
<cnsl-info-section class="feature-info">{{ 'SETTING.FEATURES.ACTIONS_DESCRIPTION' | translate }}</cnsl-info-section>
|
||||
</div>
|
||||
</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>
|
||||
@@ -0,0 +1,69 @@
|
||||
.feature-settings-wrapper {
|
||||
.feature-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
a .icon {
|
||||
font-size: 1.2rem;
|
||||
height: 1.2rem;
|
||||
line-height: 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.features {
|
||||
.feature-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.buttongroup {
|
||||
margin-right: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
|
||||
.toggle-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
i {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.info-i {
|
||||
font-size: 1.2rem;
|
||||
margin-left: 0.5rem;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.current-dot {
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
border-radius: 50%;
|
||||
// background-color: rgb(84, 142, 230);
|
||||
margin-left: 0.5rem;
|
||||
|
||||
&.enabled {
|
||||
background-color: var(--success);
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
background-color: var(--warn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.feature-info {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { FeaturesComponent } from './features.component';
|
||||
|
||||
describe('FeaturesComponent', () => {
|
||||
let component: FeaturesComponent;
|
||||
let fixture: ComponentFixture<FeaturesComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [FeaturesComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FeaturesComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,255 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatButtonToggleModule } from '@angular/material/button-toggle';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { BehaviorSubject, Subject } from 'rxjs';
|
||||
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
||||
import { CardModule } from 'src/app/modules/card/card.module';
|
||||
import { DisplayJsonDialogComponent } from 'src/app/modules/display-json-dialog/display-json-dialog.component';
|
||||
import { InfoSectionModule } from 'src/app/modules/info-section/info-section.module';
|
||||
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
|
||||
import { Event } from 'src/app/proto/generated/zitadel/event_pb';
|
||||
import { Source } from 'src/app/proto/generated/zitadel/feature/v2beta/feature_pb';
|
||||
import {
|
||||
GetInstanceFeaturesResponse,
|
||||
SetInstanceFeaturesRequest,
|
||||
} from 'src/app/proto/generated/zitadel/feature/v2beta/instance_pb';
|
||||
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
|
||||
import { FeatureService } from 'src/app/services/feature.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
enum ToggleState {
|
||||
ENABLED = 'ENABLED',
|
||||
DISABLED = 'DISABLED',
|
||||
INHERITED = 'INHERITED',
|
||||
}
|
||||
|
||||
type FeatureState = { source: Source; state: ToggleState };
|
||||
type ToggleStates = {
|
||||
loginDefaultOrg?: FeatureState;
|
||||
oidcTriggerIntrospectionProjections?: FeatureState;
|
||||
oidcLegacyIntrospection?: FeatureState;
|
||||
userSchema?: FeatureState;
|
||||
oidcTokenExchange?: FeatureState;
|
||||
actions?: FeatureState;
|
||||
};
|
||||
|
||||
@Component({
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
MatButtonToggleModule,
|
||||
HasRolePipeModule,
|
||||
MatIconModule,
|
||||
CardModule,
|
||||
TranslateModule,
|
||||
MatButtonModule,
|
||||
MatCheckboxModule,
|
||||
InfoSectionModule,
|
||||
MatTooltipModule,
|
||||
HasRoleModule,
|
||||
],
|
||||
standalone: true,
|
||||
selector: 'cnsl-features',
|
||||
templateUrl: './features.component.html',
|
||||
styleUrls: ['./features.component.scss'],
|
||||
})
|
||||
export class FeaturesComponent implements OnDestroy {
|
||||
private destroy$: Subject<void> = new Subject();
|
||||
|
||||
public _loading: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
public featureData: GetInstanceFeaturesResponse.AsObject | undefined = undefined;
|
||||
|
||||
public toggleStates: ToggleStates | undefined = undefined;
|
||||
public Source: any = Source;
|
||||
public ToggleState: any = ToggleState;
|
||||
|
||||
constructor(
|
||||
private featureService: FeatureService,
|
||||
private breadcrumbService: BreadcrumbService,
|
||||
private toast: ToastService,
|
||||
private dialog: MatDialog,
|
||||
) {
|
||||
const breadcrumbs = [
|
||||
new Breadcrumb({
|
||||
type: BreadcrumbType.INSTANCE,
|
||||
name: 'Instance',
|
||||
routerLink: ['/instance'],
|
||||
}),
|
||||
];
|
||||
this.breadcrumbService.setBreadcrumb(breadcrumbs);
|
||||
|
||||
this.getFeatures(true);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
public openDialog(event: Event): void {
|
||||
this.dialog.open(DisplayJsonDialogComponent, {
|
||||
data: {
|
||||
event: event,
|
||||
},
|
||||
width: '450px',
|
||||
});
|
||||
}
|
||||
|
||||
public validateAndSave() {
|
||||
this.featureService.resetInstanceFeatures().then(() => {
|
||||
const req = new SetInstanceFeaturesRequest();
|
||||
let changed = false;
|
||||
|
||||
console.log(this.toggleStates);
|
||||
|
||||
if (this.toggleStates?.loginDefaultOrg?.state !== ToggleState.INHERITED) {
|
||||
req.setLoginDefaultOrg(this.toggleStates?.loginDefaultOrg?.state === ToggleState.ENABLED);
|
||||
changed = true;
|
||||
}
|
||||
if (this.toggleStates?.oidcTriggerIntrospectionProjections?.state !== ToggleState.INHERITED) {
|
||||
req.setOidcTriggerIntrospectionProjections(
|
||||
this.toggleStates?.oidcTriggerIntrospectionProjections?.state === ToggleState.ENABLED,
|
||||
);
|
||||
changed = true;
|
||||
}
|
||||
if (this.toggleStates?.oidcLegacyIntrospection?.state !== ToggleState.INHERITED) {
|
||||
req.setOidcLegacyIntrospection(this.toggleStates?.oidcLegacyIntrospection?.state === ToggleState.ENABLED);
|
||||
changed = true;
|
||||
}
|
||||
if (this.toggleStates?.userSchema?.state !== ToggleState.INHERITED) {
|
||||
req.setUserSchema(this.toggleStates?.userSchema?.state === ToggleState.ENABLED);
|
||||
changed = true;
|
||||
}
|
||||
if (this.toggleStates?.oidcTokenExchange?.state !== ToggleState.INHERITED) {
|
||||
req.setOidcTokenExchange(this.toggleStates?.oidcTokenExchange?.state === ToggleState.ENABLED);
|
||||
changed = true;
|
||||
}
|
||||
if (this.toggleStates?.actions?.state !== ToggleState.INHERITED) {
|
||||
req.setActions(this.toggleStates?.actions?.state === ToggleState.ENABLED);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
this.featureService
|
||||
.setInstanceFeatures(req)
|
||||
.then(() => {
|
||||
this.toast.showInfo('POLICY.TOAST.SET', true);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getFeatures(inheritance: boolean) {
|
||||
this.featureService.getInstanceFeatures(inheritance).then((instanceFeaturesResponse) => {
|
||||
this.featureData = instanceFeaturesResponse.toObject();
|
||||
console.log(this.featureData);
|
||||
|
||||
this.toggleStates = {
|
||||
loginDefaultOrg: {
|
||||
source: this.featureData.loginDefaultOrg?.source || Source.SOURCE_SYSTEM,
|
||||
state:
|
||||
this.featureData.loginDefaultOrg?.source === Source.SOURCE_SYSTEM ||
|
||||
this.featureData.loginDefaultOrg?.source === Source.SOURCE_UNSPECIFIED
|
||||
? ToggleState.INHERITED
|
||||
: !!this.featureData.loginDefaultOrg?.enabled
|
||||
? ToggleState.ENABLED
|
||||
: ToggleState.DISABLED,
|
||||
},
|
||||
oidcTriggerIntrospectionProjections: {
|
||||
source: this.featureData.oidcTriggerIntrospectionProjections?.source || Source.SOURCE_SYSTEM,
|
||||
state:
|
||||
this.featureData.oidcTriggerIntrospectionProjections?.source === Source.SOURCE_SYSTEM ||
|
||||
this.featureData.oidcTriggerIntrospectionProjections?.source === Source.SOURCE_UNSPECIFIED
|
||||
? ToggleState.INHERITED
|
||||
: !!this.featureData.oidcTriggerIntrospectionProjections?.enabled
|
||||
? ToggleState.ENABLED
|
||||
: ToggleState.DISABLED,
|
||||
},
|
||||
oidcLegacyIntrospection: {
|
||||
source: this.featureData.oidcLegacyIntrospection?.source || Source.SOURCE_SYSTEM,
|
||||
state:
|
||||
this.featureData.oidcLegacyIntrospection?.source === Source.SOURCE_SYSTEM ||
|
||||
this.featureData.oidcLegacyIntrospection?.source === Source.SOURCE_UNSPECIFIED
|
||||
? ToggleState.INHERITED
|
||||
: !!this.featureData.oidcLegacyIntrospection?.enabled
|
||||
? ToggleState.ENABLED
|
||||
: ToggleState.DISABLED,
|
||||
},
|
||||
userSchema: {
|
||||
source: this.featureData.userSchema?.source || Source.SOURCE_SYSTEM,
|
||||
state:
|
||||
this.featureData.userSchema?.source === Source.SOURCE_SYSTEM ||
|
||||
this.featureData.userSchema?.source === Source.SOURCE_UNSPECIFIED
|
||||
? ToggleState.INHERITED
|
||||
: !!this.featureData.userSchema?.enabled
|
||||
? ToggleState.ENABLED
|
||||
: ToggleState.DISABLED,
|
||||
},
|
||||
oidcTokenExchange: {
|
||||
source: this.featureData.oidcTokenExchange?.source || Source.SOURCE_SYSTEM,
|
||||
state:
|
||||
this.featureData.oidcTokenExchange?.source === Source.SOURCE_SYSTEM ||
|
||||
this.featureData.oidcTokenExchange?.source === Source.SOURCE_UNSPECIFIED
|
||||
? ToggleState.INHERITED
|
||||
: !!this.featureData.oidcTokenExchange?.enabled
|
||||
? ToggleState.ENABLED
|
||||
: ToggleState.DISABLED,
|
||||
},
|
||||
actions: {
|
||||
source: Source.SOURCE_SYSTEM,
|
||||
state:
|
||||
this.featureData.actions?.source === Source.SOURCE_SYSTEM ||
|
||||
this.featureData.actions?.source === Source.SOURCE_UNSPECIFIED
|
||||
? ToggleState.INHERITED
|
||||
: !!this.featureData.actions?.enabled
|
||||
? ToggleState.ENABLED
|
||||
: ToggleState.DISABLED,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
public resetSettings(): void {
|
||||
this.featureService
|
||||
.resetInstanceFeatures()
|
||||
.then(() => {
|
||||
this.toast.showInfo('POLICY.TOAST.RESETSUCCESS', true);
|
||||
setTimeout(() => {
|
||||
this.getFeatures(true);
|
||||
}, 1000);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
|
||||
public saveFeatures(): void {
|
||||
if (this.featureData) {
|
||||
const req = new SetInstanceFeaturesRequest();
|
||||
req.setLoginDefaultOrg(!!this.featureData.loginDefaultOrg?.enabled);
|
||||
req.setOidcLegacyIntrospection(!!this.featureData.oidcLegacyIntrospection?.enabled);
|
||||
req.setOidcTokenExchange(!!this.featureData.oidcTokenExchange?.enabled);
|
||||
req.setOidcTriggerIntrospectionProjections(!!this.featureData.oidcTriggerIntrospectionProjections?.enabled);
|
||||
req.setUserSchema(!!this.featureData.userSchema?.enabled);
|
||||
|
||||
this.featureService
|
||||
.setInstanceFeatures(req)
|
||||
.then(() => {
|
||||
this.toast.showInfo('POLICY.TOAST.SET', true);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,9 @@
|
||||
|
||||
<cnsl-org-table></cnsl-org-table>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="currentSetting === 'features'">
|
||||
<cnsl-features></cnsl-features>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="currentSetting === 'complexity'">
|
||||
<cnsl-password-complexity-policy [serviceType]="serviceType"></cnsl-password-complexity-policy>
|
||||
</ng-container>
|
||||
|
||||
@@ -27,8 +27,8 @@ import { SettingsListComponent } from './settings-list.component';
|
||||
import FailedEventsModule from '../failed-events/failed-events.module';
|
||||
import IamViewsModule from '../iam-views/iam-views.module';
|
||||
import EventsModule from '../events/events.module';
|
||||
import OrgListModule from 'src/app/pages/org-list/org-list.module';
|
||||
import { OrgTableModule } from '../org-table/org-table.module';
|
||||
import { FeaturesComponent } from 'src/app/components/features/features.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [SettingsListComponent],
|
||||
@@ -53,6 +53,7 @@ import { OrgTableModule } from '../org-table/org-table.module';
|
||||
DomainPolicyModule,
|
||||
TranslateModule,
|
||||
HasRolePipeModule,
|
||||
FeaturesComponent,
|
||||
NotificationSMTPProviderModule,
|
||||
NotificationSMSProviderModule,
|
||||
OIDCConfigurationModule,
|
||||
|
||||
@@ -10,6 +10,15 @@ export const ORGANIZATIONS: SidenavSetting = {
|
||||
},
|
||||
};
|
||||
|
||||
export const FEATURESETTINGS: SidenavSetting = {
|
||||
id: 'features',
|
||||
i18nKey: 'SETTINGS.LIST.FEATURESETTINGS',
|
||||
groupI18nKey: 'SETTINGS.GROUPS.GENERAL',
|
||||
requiredRoles: {
|
||||
[PolicyComponentServiceType.ADMIN]: ['iam.restrictions.read'],
|
||||
},
|
||||
};
|
||||
|
||||
export const LANGUAGES: SidenavSetting = {
|
||||
id: 'languages',
|
||||
i18nKey: 'SETTINGS.LIST.LANGUAGES',
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
FAILEDEVENTS,
|
||||
EVENTS,
|
||||
ORGANIZATIONS,
|
||||
FEATURESETTINGS,
|
||||
} from '../../modules/settings-list/settings';
|
||||
import { SidenavSetting } from 'src/app/modules/sidenav/sidenav.component';
|
||||
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||
@@ -53,6 +54,7 @@ export class InstanceComponent implements OnInit, OnDestroy {
|
||||
public id: string = '';
|
||||
public defaultSettingsList: SidenavSetting[] = [
|
||||
ORGANIZATIONS,
|
||||
FEATURESETTINGS,
|
||||
// notifications
|
||||
// { showWarn: true, ...NOTIFICATIONS },
|
||||
NOTIFICATIONS,
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { GrpcService } from './grpc.service';
|
||||
|
||||
import {
|
||||
GetInstanceFeaturesRequest,
|
||||
GetInstanceFeaturesResponse,
|
||||
ResetInstanceFeaturesRequest,
|
||||
SetInstanceFeaturesRequest,
|
||||
SetInstanceFeaturesResponse,
|
||||
} from '../proto/generated/zitadel/feature/v2beta/instance_pb';
|
||||
import {
|
||||
GetOrganizationFeaturesRequest,
|
||||
GetOrganizationFeaturesResponse,
|
||||
} from '../proto/generated/zitadel/feature/v2beta/organization_pb';
|
||||
import { GetUserFeaturesRequest, GetUserFeaturesResponse } from '../proto/generated/zitadel/feature/v2beta/user_pb';
|
||||
import { GetSystemFeaturesRequest, GetSystemFeaturesResponse } from '../proto/generated/zitadel/feature/v2beta/system_pb';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class FeatureService {
|
||||
constructor(private readonly grpcService: GrpcService) {}
|
||||
|
||||
public getInstanceFeatures(inheritance: boolean): Promise<GetInstanceFeaturesResponse> {
|
||||
const req = new GetInstanceFeaturesRequest();
|
||||
req.setInheritance(inheritance);
|
||||
return this.grpcService.feature.getInstanceFeatures(req, null).then((resp) => resp);
|
||||
}
|
||||
|
||||
public setInstanceFeatures(req: SetInstanceFeaturesRequest): Promise<SetInstanceFeaturesResponse> {
|
||||
return this.grpcService.feature.setInstanceFeatures(req, null);
|
||||
}
|
||||
|
||||
public resetInstanceFeatures(): Promise<SetInstanceFeaturesResponse> {
|
||||
const req = new ResetInstanceFeaturesRequest();
|
||||
return this.grpcService.feature.resetInstanceFeatures(req, null);
|
||||
}
|
||||
|
||||
public getOrganizationFeatures(orgId: string, inheritance: boolean): Promise<GetOrganizationFeaturesResponse> {
|
||||
const req = new GetOrganizationFeaturesRequest();
|
||||
req.setOrganizationId(orgId);
|
||||
req.setInheritance(inheritance);
|
||||
return this.grpcService.feature.getOrganizationFeatures(req, null).then((resp) => resp);
|
||||
}
|
||||
|
||||
public getSystemFeatures(): Promise<GetSystemFeaturesResponse> {
|
||||
const req = new GetSystemFeaturesRequest();
|
||||
return this.grpcService.feature.getSystemFeatures(req, null).then((resp) => resp);
|
||||
}
|
||||
|
||||
public getUserFeatures(userId: string, inheritance: boolean): Promise<GetUserFeaturesResponse> {
|
||||
const req = new GetUserFeaturesRequest();
|
||||
req.setInheritance(inheritance);
|
||||
req.setUserId(userId);
|
||||
return this.grpcService.feature.getUserFeatures(req, null).then((resp) => resp);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import { ExhaustedGrpcInterceptor } from './interceptors/exhausted.grpc.intercep
|
||||
import { I18nInterceptor } from './interceptors/i18n.interceptor';
|
||||
import { OrgInterceptor } from './interceptors/org.interceptor';
|
||||
import { StorageService } from './storage.service';
|
||||
import { FeatureServiceClient } from '../proto/generated/zitadel/feature/v2beta/Feature_serviceServiceClientPb';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@@ -25,6 +26,7 @@ export class GrpcService {
|
||||
public auth!: AuthServiceClient;
|
||||
public mgmt!: ManagementServiceClient;
|
||||
public admin!: AdminServiceClient;
|
||||
public feature!: FeatureServiceClient;
|
||||
|
||||
constructor(
|
||||
private envService: EnvironmentService,
|
||||
@@ -76,6 +78,12 @@ export class GrpcService {
|
||||
// @ts-ignore
|
||||
interceptors,
|
||||
);
|
||||
this.feature = new FeatureServiceClient(
|
||||
env.api,
|
||||
null,
|
||||
// @ts-ignore
|
||||
interceptors,
|
||||
);
|
||||
|
||||
const authConfig: AuthConfig = {
|
||||
scope: 'openid profile email',
|
||||
|
||||
Reference in New Issue
Block a user