diff --git a/console/src/app/modules/policies/general-settings/general-settings.component.html b/console/src/app/modules/policies/general-settings/general-settings.component.html deleted file mode 100644 index a465cf346..000000000 --- a/console/src/app/modules/policies/general-settings/general-settings.component.html +++ /dev/null @@ -1,27 +0,0 @@ -

{{ 'SETTING.DEFAULTLANGUAGE' | translate }}

- -
- -
- - - {{ 'SETTING.DEFAULTLANGUAGE' | translate }} - - - {{ lang }} - {{ 'SETTING.LANGUAGE.' + lang | translate }} - - - - -
- -
diff --git a/console/src/app/modules/policies/general-settings/general-settings.component.scss b/console/src/app/modules/policies/general-settings/general-settings.component.scss deleted file mode 100644 index 1e41e2908..000000000 --- a/console/src/app/modules/policies/general-settings/general-settings.component.scss +++ /dev/null @@ -1,18 +0,0 @@ -.spinner-wr { - margin: 0.5rem 0; -} - -.default-language { - max-width: 400px; - display: block; -} - -.general-btn-container { - display: flex; - justify-content: flex-start; - margin-top: 1rem; - - .save-button { - display: block; - } -} diff --git a/console/src/app/modules/policies/general-settings/general-settings.component.ts b/console/src/app/modules/policies/general-settings/general-settings.component.ts deleted file mode 100644 index a3c8c399d..000000000 --- a/console/src/app/modules/policies/general-settings/general-settings.component.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { SetDefaultLanguageResponse } from 'src/app/proto/generated/zitadel/admin_pb'; -import { AdminService } from 'src/app/services/admin.service'; -import { ToastService } from 'src/app/services/toast.service'; - -@Component({ - selector: 'cnsl-general-settings', - templateUrl: './general-settings.component.html', - styleUrls: ['./general-settings.component.scss'], -}) -export class GeneralSettingsComponent implements OnInit { - public defaultLanguage: string = ''; - public defaultLanguageOptions: string[] = []; - - public loading: boolean = false; - constructor( - private service: AdminService, - private toast: ToastService, - ) {} - - ngOnInit(): void { - this.fetchData(); - } - - private fetchData(): void { - this.service.getDefaultLanguage().then((langResp) => { - this.defaultLanguage = langResp.language; - }); - this.service.getAllowedLanguages().then((supportedResp) => { - this.defaultLanguageOptions = supportedResp.languagesList; - }); - } - - private updateData(): Promise { - return (this.service as AdminService).setDefaultLanguage(this.defaultLanguage); - } - - public savePolicy(): void { - const prom = this.updateData(); - this.loading = true; - if (prom) { - prom - .then(() => { - this.toast.showInfo('POLICY.LOGIN_POLICY.SAVED', true); - this.loading = false; - setTimeout(() => { - this.fetchData(); - }, 2000); - }) - .catch((error) => { - this.loading = false; - this.toast.showError(error); - }); - } - } -} diff --git a/console/src/app/modules/policies/general-settings/general-settings.module.ts b/console/src/app/modules/policies/general-settings/general-settings.module.ts deleted file mode 100644 index 98f0e4cb5..000000000 --- a/console/src/app/modules/policies/general-settings/general-settings.module.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { CommonModule } from '@angular/common'; -import { NgModule } from '@angular/core'; -import { FormsModule } from '@angular/forms'; -import { MatButtonModule } from '@angular/material/button'; -import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; -import { MatSelectModule } from '@angular/material/select'; -import { TranslateModule } from '@ngx-translate/core'; -import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module'; - -import { CardModule } from '../../card/card.module'; -import { FormFieldModule } from '../../form-field/form-field.module'; -import { GeneralSettingsComponent } from './general-settings.component'; - -@NgModule({ - declarations: [GeneralSettingsComponent], - imports: [ - CommonModule, - CardModule, - FormsModule, - MatButtonModule, - FormFieldModule, - MatProgressSpinnerModule, - MatSelectModule, - HasRolePipeModule, - TranslateModule, - ], - exports: [GeneralSettingsComponent], -}) -export class GeneralSettingsModule {} diff --git a/console/src/app/modules/policies/language-settings/language-settings.component.html b/console/src/app/modules/policies/language-settings/language-settings.component.html new file mode 100644 index 000000000..e03b005a5 --- /dev/null +++ b/console/src/app/modules/policies/language-settings/language-settings.component.html @@ -0,0 +1,135 @@ +

{{ 'SETTING.LANGUAGES.TITLE' | translate }}

+ +
+ +
+ +
+
+
+
+
+ {{ 'SETTING.LANGUAGES.ALLOWED' | translate }} + +
+
+
+ {{ i + 1 }} + {{ lang }} + {{ 'SETTING.LANGUAGES.OPTIONS.' + lang | translate }} + {{ + 'SETTING.LANGUAGES.DEFAULT' | translate + }} + + + + + + +
+
+
+
+
+
+
+ {{ 'SETTING.LANGUAGES.NOT_ALLOWED' | translate }} + +
+
+
+ {{ lang }} + {{ 'SETTING.LANGUAGES.OPTIONS.' + lang | translate }} +
+
+
+
+
+ + +
+
+
diff --git a/console/src/app/modules/policies/language-settings/language-settings.component.scss b/console/src/app/modules/policies/language-settings/language-settings.component.scss new file mode 100644 index 000000000..90f0b42e9 --- /dev/null +++ b/console/src/app/modules/policies/language-settings/language-settings.component.scss @@ -0,0 +1,112 @@ +.languages-container-wrapper { + display: grid; + grid-template-columns: 1fr 1fr; + column-gap: 2rem; + + @media only screen and (max-width: 700px) { + grid-template-columns: 1fr; + } + + .languages-container { + display: inline-block; + max-width: 400px; + vertical-align: top; + width: 100%; + + .spinner-wr { + margin: 0.5rem 0; + } + + .default-language { + max-width: 400px; + display: block; + } + } + + .general-btn-container { + display: flex; + justify-content: flex-start; + margin-top: 1rem; + + .save-button { + display: block; + margin-left: 1rem; + } + } +} + +.languages-list { + overflow: hidden; + display: block; + height: 100%; + + .languages-top-row { + display: flex; + flex-direction: row; + align-items: center; + margin-bottom: 1rem; + margin-top: 1.5rem; + + .label { + margin-right: 1rem; + flex: 1; + white-space: nowrap; + } + + .list-button { + white-space: nowrap; + } + } +} + +.languages-box { + position: relative; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + padding: 0.5rem 0.5rem 0.5rem 2rem; + height: 56px; + cursor: move; + margin: 2px 0; + + .index { + position: absolute; + top: 4px; + left: 4px; + opacity: 0.5; + font-size: 10px; + } + + .locale { + width: 35px; + margin-right: 1rem; + } + + .lang { + flex: 1; + } + + [hoveractions] { + display: none; + } + + .more-button { + height: 40px; + width: 40px; + display: flex; + align-items: center; + justify-content: center; + padding: 0; + } + + &:hover { + [hoveractions] { + display: flex; + } + } + + .defaultlanglabel { + margin-left: 0.5rem; + } +} diff --git a/console/src/app/modules/policies/general-settings/general-settings.component.spec.ts b/console/src/app/modules/policies/language-settings/language-settings.component.spec.ts similarity index 50% rename from console/src/app/modules/policies/general-settings/general-settings.component.spec.ts rename to console/src/app/modules/policies/language-settings/language-settings.component.spec.ts index 23eee2e9f..8da2c7bb8 100644 --- a/console/src/app/modules/policies/general-settings/general-settings.component.spec.ts +++ b/console/src/app/modules/policies/language-settings/language-settings.component.spec.ts @@ -1,19 +1,19 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { GeneralSettingsComponent } from './general-settings.component'; +import { LanguageSettingsComponent } from './language-settings.component'; -describe('GeneralSettingsComponent', () => { - let component: GeneralSettingsComponent; - let fixture: ComponentFixture; +describe('LanguageSettingsComponent', () => { + let component: LanguageSettingsComponent; + let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [GeneralSettingsComponent], + declarations: [LanguageSettingsComponent], }).compileComponents(); }); beforeEach(() => { - fixture = TestBed.createComponent(GeneralSettingsComponent); + fixture = TestBed.createComponent(LanguageSettingsComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/console/src/app/modules/policies/language-settings/language-settings.component.ts b/console/src/app/modules/policies/language-settings/language-settings.component.ts new file mode 100644 index 000000000..f2af4641f --- /dev/null +++ b/console/src/app/modules/policies/language-settings/language-settings.component.ts @@ -0,0 +1,116 @@ +import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; +import { AdminService } from 'src/app/services/admin.service'; +import { ToastService } from 'src/app/services/toast.service'; +import { UntypedFormBuilder } from '@angular/forms'; +import { LanguagesService } from '../../../services/languages.service'; +import { BehaviorSubject, concat, forkJoin, from, Observable, of, Subject, switchMap, take, takeUntil } from 'rxjs'; +import { GrpcAuthService } from '../../../services/grpc-auth.service'; +import { CdkDrag, CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop'; +import { catchError, map } from 'rxjs/operators'; + +interface State { + allowed: string[]; + notAllowed: string[]; +} + +@Component({ + selector: 'cnsl-language-settings', + templateUrl: './language-settings.component.html', + styleUrls: ['./language-settings.component.scss'], +}) +export class LanguageSettingsComponent { + public canWriteRestrictions$: Observable = this.authService.isAllowed(['iam.restrictions.write']); + public canWriteDefaultLanguage$: Observable = this.authService.isAllowed(['iam.write']); + + public localState$ = new BehaviorSubject({ allowed: [], notAllowed: [] }); + public remoteState$ = new BehaviorSubject({ allowed: [], notAllowed: [] }); + public defaultLang$ = new BehaviorSubject(''); + + public loading: boolean = false; + constructor( + private service: AdminService, + private toast: ToastService, + private langSvc: LanguagesService, + private authService: GrpcAuthService, + ) { + const sub = forkJoin([ + langSvc.allowed$.pipe(take(1)), + langSvc.notAllowed$.pipe(take(1)), + from(this.service.getDefaultLanguage()).pipe(take(1)), + ]).subscribe({ + next: ([allowed, notAllowed, { language: defaultLang }]) => { + this.defaultLang$.next(defaultLang); + this.remoteState$.next({ notAllowed: [...notAllowed], ...{ allowed: [...allowed] } }); + this.localState$.next({ notAllowed: [...notAllowed], ...{ allowed: [...allowed] } }); + }, + error: this.toast.showError, + complete: () => { + sub.unsubscribe(); + }, + }); + } + + drop(event: CdkDragDrop) { + if (event.previousContainer === event.container) { + moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); + } else { + transferArrayItem(event.previousContainer.data, event.container.data, event.previousIndex, event.currentIndex); + } + } + + public defaultLangPredicate = (lang: CdkDrag) => { + return !!lang?.data && lang.data !== this.defaultLang$.value; + }; + + public isRemotelyAllowed$(lang: string): Observable { + return this.remoteState$.pipe(map(({ allowed }) => allowed.includes(lang))); + } + + public allowAll(): void { + this.localState$.next({ allowed: [...this.allLocalLangs()], notAllowed: [] }); + } + + public disallowAll(): void { + const disallowed = this.allLocalLangs().filter((lang) => lang !== this.defaultLang$.value); + this.localState$.next({ allowed: [this.defaultLang$.value], notAllowed: disallowed }); + } + + public submit(): void { + const { allowed, notAllowed } = this.localState$.value; + const sub = from(this.service.setRestrictions(undefined, allowed)).subscribe({ + next: () => { + this.remoteState$.next({ + allowed: [...allowed], + notAllowed: [...notAllowed], + }); + this.langSvc.newAllowed(allowed); + this.toast.showInfo('SETTING.LANGUAGES.ALLOWED_SAVED', true); + }, + error: this.toast.showError, + complete: () => { + sub.unsubscribe(); + }, + }); + } + + public discard(): void { + this.localState$.next(this.remoteState$.value); + } + + public setDefaultLang(lang: string): void { + const sub = from(this.service.setDefaultLanguage(lang)).subscribe({ + next: () => { + this.defaultLang$.next(lang); + this.toast.showInfo('SETTING.LANGUAGES.DEFAULT_SAVED', true); + }, + error: this.toast.showError, + complete: () => { + sub.unsubscribe(); + }, + }); + } + + private allLocalLangs(): string[] { + return [...this.localState$.value.allowed, ...this.localState$.value.notAllowed]; + } +} diff --git a/console/src/app/modules/policies/language-settings/language-settings.module.ts b/console/src/app/modules/policies/language-settings/language-settings.module.ts new file mode 100644 index 000000000..f19a0d3d3 --- /dev/null +++ b/console/src/app/modules/policies/language-settings/language-settings.module.ts @@ -0,0 +1,46 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatSelectModule } from '@angular/material/select'; +import { TranslateModule } from '@ngx-translate/core'; +import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module'; + +import { CardModule } from '../../card/card.module'; +import { FormFieldModule } from '../../form-field/form-field.module'; +import { LanguageSettingsComponent } from './language-settings.component'; +import { MatListModule } from '@angular/material/list'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { DragDropModule } from '@angular/cdk/drag-drop'; +import { MatRadioModule } from '@angular/material/radio'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { TableActionsModule } from '../../table-actions/table-actions.module'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatIconModule } from '@angular/material/icon'; + +@NgModule({ + declarations: [LanguageSettingsComponent], + imports: [ + CommonModule, + CardModule, + FormsModule, + ReactiveFormsModule, + MatFormFieldModule, + MatButtonModule, + MatSelectModule, + FormFieldModule, + MatProgressSpinnerModule, + MatSelectModule, + HasRolePipeModule, + TranslateModule, + MatListModule, + DragDropModule, + MatRadioModule, + MatTooltipModule, + MatMenuModule, + MatIconModule, + ], + exports: [LanguageSettingsComponent], +}) +export class LanguageSettingsModule {} diff --git a/console/src/app/modules/policies/login-policy/login-policy.component.html b/console/src/app/modules/policies/login-policy/login-policy.component.html index 7fd7e2627..ece4d1c40 100644 --- a/console/src/app/modules/policies/login-policy/login-policy.component.html +++ b/console/src/app/modules/policies/login-policy/login-policy.component.html @@ -242,14 +242,20 @@ | async) === false " > - {{ 'POLICY.DATA.ALLOWREGISTER' | translate }} + {{ 'POLICY.DATA.ALLOWREGISTERUSERS' | translate }} + + +