From 2c133a33f5afb363a7443d5934ed1159e9043da6 Mon Sep 17 00:00:00 2001 From: Sagar Gopale Date: Thu, 11 Dec 2025 18:04:26 +0530 Subject: [PATCH] mgr/dashboard: carbonized-import-modal Fixes: https://tracker.ceph.com/issues/74162 Signed-off-by: Sagar Gopale --- .../rgw-multisite-details.component.ts | 4 +- .../rgw-multisite-import.component.html | 513 +++++++++--------- .../rgw-multisite-import.component.spec.ts | 24 +- .../rgw-multisite-import.component.ts | 93 ++-- 4 files changed, 331 insertions(+), 303 deletions(-) diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.ts index 0d0b56ef2d4..2def1fdf8b3 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.ts @@ -190,9 +190,7 @@ export class RgwMultisiteDetailsComponent extends CdForm implements OnDestroy, O const initialState = { multisiteInfo: this.multisiteInfo }; - this.bsModalRef = this.modalService.show(RgwMultisiteImportComponent, initialState, { - size: 'lg' - }); + this.cdsModalService.show(RgwMultisiteImportComponent, initialState); } openExportModal() { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.html index 378a0d0255a..6631ac352a1 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.html @@ -1,155 +1,157 @@ - - Import Multi-site Token + + +

+ Import multi-site tokens +

+
- +
-
+ + + + @if (control?.errors) { + @for (err of control.errors | keyvalue; track err.key) { + {{ INVALID_TEXTS[err.key] }} + } + } + + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.spec.ts index f2532238549..71692c13cd7 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.spec.ts @@ -3,7 +3,15 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ReactiveFormsModule } from '@angular/forms'; import { RouterTestingModule } from '@angular/router/testing'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; - +import { + ButtonModule, + CheckboxModule, + ComboBoxModule, + InputModule, + ModalModule, + NumberModule, + SelectModule +} from 'carbon-components-angular'; import { SharedModule } from '~/app/shared/shared.module'; import { RgwMultisiteImportComponent } from './rgw-multisite-import.component'; @@ -14,7 +22,19 @@ describe('RgwMultisiteImportComponent', () => { let fixture: ComponentFixture; configureTestBed({ - imports: [SharedModule, ReactiveFormsModule, RouterTestingModule, HttpClientTestingModule], + imports: [ + SharedModule, + ReactiveFormsModule, + RouterTestingModule, + HttpClientTestingModule, + ModalModule, + InputModule, + CheckboxModule, + ComboBoxModule, + SelectModule, + NumberModule, + ButtonModule + ], declarations: [RgwMultisiteImportComponent], providers: [NgbActiveModal] }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.ts index 2714bf65942..da7a0b2c7ae 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.ts @@ -1,7 +1,10 @@ -import { Component, OnInit, ViewChild } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; +import { BaseModal } from 'carbon-components-angular'; import { FormControl, Validators } from '@angular/forms'; -import { NgbActiveModal, NgbTypeahead } from '@ng-bootstrap/ng-bootstrap'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { RgwRealmService } from '~/app/shared/api/rgw-realm.service'; +import { ComboBoxItem } from '~/app/shared/models/combo-box.model'; +import { HostService } from '~/app/shared/api/host.service'; import { ActionLabelsI18n } from '~/app/shared/constants/app.constants'; import { NotificationType } from '~/app/shared/enum/notification-type.enum'; import { CdFormGroup } from '~/app/shared/forms/cd-form-group'; @@ -9,11 +12,6 @@ import { CdValidators } from '~/app/shared/forms/cd-validators'; import { NotificationService } from '~/app/shared/services/notification.service'; import { RgwZone } from '../models/rgw-multisite'; import _ from 'lodash'; -import { SelectMessages } from '~/app/shared/components/select/select-messages.model'; -import { HostService } from '~/app/shared/api/host.service'; -import { SelectOption } from '~/app/shared/components/select/select-option.model'; -import { Observable, Subject, merge } from 'rxjs'; -import { debounceTime, distinctUntilChanged, filter, map } from 'rxjs/operators'; @Component({ selector: 'cd-rgw-multisite-import', @@ -21,22 +19,26 @@ import { debounceTime, distinctUntilChanged, filter, map } from 'rxjs/operators' styleUrls: ['./rgw-multisite-import.component.scss'], standalone: false }) -export class RgwMultisiteImportComponent implements OnInit { +export class RgwMultisiteImportComponent extends BaseModal implements OnInit { readonly endpoints = /^((https?:\/\/)|(www.))(?:([a-zA-Z]+)|(\d+\.\d+.\d+.\d+)):\d{2,4}$/; readonly ipv4Rgx = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/i; readonly ipv6Rgx = /^(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}$/i; - @ViewChild(NgbTypeahead, { static: false }) - typeahead: NgbTypeahead; - importTokenForm: CdFormGroup; multisiteInfo: object[] = []; zoneList: RgwZone[] = []; zoneNames: string[]; - hosts: any; - labels: string[]; - labelClick = new Subject(); - labelFocus = new Subject(); + hosts: ComboBoxItem[] = []; + labels: ComboBoxItem[] = []; + selectedHosts: string[] = []; + + INVALID_TEXTS = { + required: $localize`This field is required.`, + uniqueName: $localize`The chosen zone name is already in use.`, + min: $localize`The value must be at least 1.`, + max: $localize`The value cannot exceed 65535.`, + pattern: $localize`The entered value needs to be a number.` + }; constructor( public activeModal: NgbActiveModal, @@ -46,13 +48,7 @@ export class RgwMultisiteImportComponent implements OnInit { public actionLabels: ActionLabelsI18n, public notificationService: NotificationService ) { - this.hosts = { - options: [], - messages: new SelectMessages({ - empty: $localize`There are no hosts.`, - filter: $localize`Filter hosts` - }) - }; + super(); this.createForm(); } ngOnInit(): void { @@ -64,17 +60,24 @@ export class RgwMultisiteImportComponent implements OnInit { return zone['name']; }); this.hostService.getAllHosts().subscribe((resp: object[]) => { - const options: SelectOption[] = []; + const options: ComboBoxItem[] = []; _.forEach(resp, (host: object) => { if (_.get(host, 'sources.orchestrator', false)) { - const option = new SelectOption(false, _.get(host, 'hostname'), ''); - options.push(option); + const hostname = _.get(host, 'hostname'); + options.push({ content: hostname, selected: false, name: hostname }); } }); - this.hosts.options = [...options]; + + this.hosts = [...options]; }); this.hostService.getLabels().subscribe((resp: string[]) => { - this.labels = resp; + this.labels = resp.map((label) => { + return { + content: label, + selected: false, + name: label + }; + }); }); } @@ -92,7 +95,12 @@ export class RgwMultisiteImportComponent implements OnInit { ] }), rgw_frontend_port: new FormControl(null, { - validators: [Validators.required, Validators.pattern('^[0-9]*$')] + validators: [ + CdValidators.requiredIf({ + unmanaged: false + }), + Validators.pattern('^[0-9]*$') + ] }), placement: new FormControl('hosts'), label: new FormControl(null, [ @@ -108,6 +116,10 @@ export class RgwMultisiteImportComponent implements OnInit { }); } + multiSelector(event: ComboBoxItem[]) { + this.selectedHosts = event.map((host: ComboBoxItem) => host.content); + } + onSubmit() { const values = this.importTokenForm.value; const placementSpec: object = { @@ -117,18 +129,21 @@ export class RgwMultisiteImportComponent implements OnInit { if (!values['unmanaged']) { switch (values['placement']) { case 'hosts': - if (values['hosts'].length > 0) { - placementSpec['placement']['hosts'] = values['hosts']; + if (this.selectedHosts.length > 0) { + placementSpec['placement']['hosts'] = this.selectedHosts; } break; case 'label': - placementSpec['placement']['label'] = values['label']; + if (!_.isEmpty(values['label'])) { + placementSpec['placement']['label'] = values['label']?.content; + } break; } if (_.isNumber(values['count']) && values['count'] > 0) { placementSpec['placement']['count'] = values['count']; } } + this.rgwRealmService .importRealmToken( values['realmToken'], @@ -143,25 +158,11 @@ export class RgwMultisiteImportComponent implements OnInit { NotificationType.success, $localize`Realm token import successfull` ); - this.activeModal.close(); + this.closeModal(); }, () => { this.importTokenForm.setErrors({ cdSubmitButton: true }); } ); } - - searchLabels = (text$: Observable) => { - return merge( - text$.pipe(debounceTime(200), distinctUntilChanged()), - this.labelFocus, - this.labelClick.pipe(filter(() => !this.typeahead.isPopupOpen())) - ).pipe( - map((value) => - this.labels - .filter((label: string) => label.toLowerCase().indexOf(value.toLowerCase()) > -1) - .slice(0, 10) - ) - ); - }; }