]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: integrate site name into block mirroring overview
authorJason Dillaman <dillaman@redhat.com>
Tue, 22 Oct 2019 17:58:31 +0000 (13:58 -0400)
committerJason Dillaman <dillaman@redhat.com>
Thu, 5 Dec 2019 14:32:42 +0000 (09:32 -0500)
The site name is used to differentiate clusters for RBD mirroring.
The local site name is now displayed on the block mirroring
overview page. It also includes a new modal window to edit the site
name.

Signed-off-by: Jason Dillaman <dillaman@redhat.com>
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/edit-site-name-modal/edit-site-name-modal.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/edit-site-name-modal/edit-site-name-modal.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/edit-site-name-modal/edit-site-name-modal.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/edit-site-name-modal/edit-site-name-modal.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/mirroring.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/overview/overview.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/overview/overview.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-message.service.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-message.service.ts

diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/edit-site-name-modal/edit-site-name-modal.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/edit-site-name-modal/edit-site-name-modal.component.html
new file mode 100644 (file)
index 0000000..64ea83a
--- /dev/null
@@ -0,0 +1,47 @@
+<cd-modal [modalRef]="modalRef">
+  <ng-container i18n
+                class="modal-title">Edit site name</ng-container>
+
+  <ng-container class="modal-content">
+    <form name="editSiteNameForm"
+          class="form"
+          #formDir="ngForm"
+          [formGroup]="editSiteNameForm"
+          novalidate>
+      <div class="modal-body">
+        <p>
+          <ng-container i18n>Edit the site name and click&nbsp;
+          <kbd>Update</kbd>.</ng-container>
+        </p>
+
+        <div class="form-group">
+          <label class="col-form-label"
+                 for="siteName">
+            <span i18n>Site Name</span>
+            <span class="required"></span>
+          </label>
+          <input class="form-control"
+                 type="text"
+                 placeholder="Name..."
+                 i18n-placeholder
+                 id="siteName"
+                 name="siteName"
+                 formControlName="siteName"
+                 autofocus>
+        </div>
+      </div>
+
+      <div class="modal-footer">
+        <div class="button-group text-right">
+          <cd-submit-button i18n
+                            [form]="editSiteNameForm"
+                            (submitAction)="update()">Update</cd-submit-button>
+          <cd-back-button [back]="modalRef.hide"
+                          name="Cancel"
+                          i18n-name>
+          </cd-back-button>
+        </div>
+      </div>
+    </form>
+  </ng-container>
+</cd-modal>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/edit-site-name-modal/edit-site-name-modal.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/edit-site-name-modal/edit-site-name-modal.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/edit-site-name-modal/edit-site-name-modal.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/edit-site-name-modal/edit-site-name-modal.component.spec.ts
new file mode 100644 (file)
index 0000000..227964a
--- /dev/null
@@ -0,0 +1,73 @@
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ReactiveFormsModule } from '@angular/forms';
+import { RouterTestingModule } from '@angular/router/testing';
+
+import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
+import { ToastrModule } from 'ngx-toastr';
+import { of } from 'rxjs';
+
+import { configureTestBed, i18nProviders } from '../../../../../testing/unit-test-helper';
+import { RbdMirroringService } from '../../../../shared/api/rbd-mirroring.service';
+import { NotificationService } from '../../../../shared/services/notification.service';
+import { SharedModule } from '../../../../shared/shared.module';
+import { EditSiteNameModalComponent } from './edit-site-name-modal.component';
+
+describe('EditSiteNameModalComponent', () => {
+  let component: EditSiteNameModalComponent;
+  let fixture: ComponentFixture<EditSiteNameModalComponent>;
+  let notificationService: NotificationService;
+  let rbdMirroringService: RbdMirroringService;
+
+  configureTestBed({
+    declarations: [EditSiteNameModalComponent],
+    imports: [
+      HttpClientTestingModule,
+      ReactiveFormsModule,
+      RouterTestingModule,
+      SharedModule,
+      ToastrModule.forRoot()
+    ],
+    providers: [BsModalRef, BsModalService, i18nProviders]
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(EditSiteNameModalComponent);
+    component = fixture.componentInstance;
+    component.siteName = 'site-A';
+
+    notificationService = TestBed.get(NotificationService);
+    spyOn(notificationService, 'show').and.stub();
+
+    rbdMirroringService = TestBed.get(RbdMirroringService);
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  describe('edit site name', () => {
+    beforeEach(() => {
+      spyOn(rbdMirroringService, 'getSiteName').and.callFake(() => of({ site_name: 'site-A' }));
+      spyOn(rbdMirroringService, 'refresh').and.stub();
+      spyOn(component.modalRef, 'hide').and.callThrough();
+      fixture.detectChanges();
+    });
+
+    afterEach(() => {
+      expect(rbdMirroringService.getSiteName).toHaveBeenCalledTimes(1);
+      expect(rbdMirroringService.refresh).toHaveBeenCalledTimes(1);
+      expect(component.modalRef.hide).toHaveBeenCalledTimes(1);
+    });
+
+    it('should call setSiteName', () => {
+      spyOn(rbdMirroringService, 'setSiteName').and.callFake(() => of({ site_name: 'new-site-A' }));
+
+      component.editSiteNameForm.patchValue({
+        siteName: 'new-site-A'
+      });
+      component.update();
+      expect(rbdMirroringService.setSiteName).toHaveBeenCalledWith('new-site-A');
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/edit-site-name-modal/edit-site-name-modal.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/edit-site-name-modal/edit-site-name-modal.component.ts
new file mode 100644 (file)
index 0000000..e7e256b
--- /dev/null
@@ -0,0 +1,57 @@
+import { Component, OnInit } from '@angular/core';
+import { FormControl } from '@angular/forms';
+
+import { BsModalRef } from 'ngx-bootstrap/modal';
+
+import { RbdMirroringService } from '../../../../shared/api/rbd-mirroring.service';
+import { CdFormGroup } from '../../../../shared/forms/cd-form-group';
+import { FinishedTask } from '../../../../shared/models/finished-task';
+import { TaskWrapperService } from '../../../../shared/services/task-wrapper.service';
+
+@Component({
+  selector: 'cd-edit-site-mode-modal',
+  templateUrl: './edit-site-name-modal.component.html',
+  styleUrls: ['./edit-site-name-modal.component.scss']
+})
+export class EditSiteNameModalComponent implements OnInit {
+  siteName: string;
+
+  editSiteNameForm: CdFormGroup;
+
+  constructor(
+    public modalRef: BsModalRef,
+    private rbdMirroringService: RbdMirroringService,
+    private taskWrapper: TaskWrapperService
+  ) {
+    this.createForm();
+  }
+
+  createForm() {
+    this.editSiteNameForm = new CdFormGroup({
+      siteName: new FormControl('', {})
+    });
+  }
+
+  ngOnInit() {
+    this.editSiteNameForm.get('siteName').setValue(this.siteName);
+    this.rbdMirroringService.getSiteName().subscribe((response: any) => {
+      this.editSiteNameForm.get('siteName').setValue(response.site_name);
+    });
+  }
+
+  update() {
+    const action = this.taskWrapper.wrapTaskAroundCall({
+      task: new FinishedTask('rbd/mirroring/site_name/edit', {}),
+      call: this.rbdMirroringService.setSiteName(this.editSiteNameForm.getValue('siteName'))
+    });
+
+    action.subscribe(
+      undefined,
+      () => this.editSiteNameForm.setErrors({ cdSubmitButton: true }),
+      () => {
+        this.rbdMirroringService.refresh();
+        this.modalRef.hide();
+      }
+    );
+  }
+}
index 0e8f48280fc8ac880dbb9712304d1cb4d2ee82b6..9df7e83db1ea77ba7f34702cc32f55fab4573511 100644 (file)
@@ -15,6 +15,7 @@ import { TooltipModule } from 'ngx-bootstrap/tooltip';
 import { SharedModule } from '../../../shared/shared.module';
 
 import { DaemonListComponent } from './daemon-list/daemon-list.component';
+import { EditSiteNameModalComponent } from './edit-site-name-modal/edit-site-name-modal.component';
 import { ImageListComponent } from './image-list/image-list.component';
 import { MirrorHealthColorPipe } from './mirror-health-color.pipe';
 import { OverviewComponent } from './overview/overview.component';
@@ -23,7 +24,12 @@ import { PoolEditPeerModalComponent } from './pool-edit-peer-modal/pool-edit-pee
 import { PoolListComponent } from './pool-list/pool-list.component';
 
 @NgModule({
-  entryComponents: [OverviewComponent, PoolEditModeModalComponent, PoolEditPeerModalComponent],
+  entryComponents: [
+    EditSiteNameModalComponent,
+    OverviewComponent,
+    PoolEditModeModalComponent,
+    PoolEditPeerModalComponent
+  ],
   imports: [
     CommonModule,
     TabsModule.forRoot(),
@@ -41,6 +47,7 @@ import { PoolListComponent } from './pool-list/pool-list.component';
   ],
   declarations: [
     DaemonListComponent,
+    EditSiteNameModalComponent,
     ImageListComponent,
     OverviewComponent,
     PoolEditModeModalComponent,
index 6f598e75068848034af9a95226b272e5897387d9..38e7fa8d0a79f82dd7800975a0e90fa360d0835c 100644 (file)
@@ -1,5 +1,16 @@
 <cd-view-cache [status]="status"></cd-view-cache>
 
+<div class="row">
+  <div class="col-md-12">
+    <span><strong i18n>Site Name:</strong> {{siteName}}</span>
+    <cd-table-actions class="table-actions float-right"
+                      [permission]="permission"
+                      [selection]="selection"
+                      [tableActions]="tableActions">
+    </cd-table-actions>
+  </div>
+</div>
+
 <div class="row">
   <div class="col-sm-6">
     <legend i18n>Daemons</legend>
index 1e2853395914bcbcfade0c7cf3e3eef5484d4245..b2047f1c385df569813791eb8f3a271f3f019f8c 100644 (file)
@@ -1,9 +1,17 @@
 import { Component, OnDestroy, OnInit } from '@angular/core';
 
+import { I18n } from '@ngx-translate/i18n-polyfill';
+import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
 import { Subscription } from 'rxjs';
 
 import { RbdMirroringService } from '../../../../shared/api/rbd-mirroring.service';
+import { Icons } from '../../../../shared/enum/icons.enum';
 import { ViewCacheStatus } from '../../../../shared/enum/view-cache-status.enum';
+import { CdTableAction } from '../../../../shared/models/cd-table-action';
+import { CdTableSelection } from '../../../../shared/models/cd-table-selection';
+import { Permission } from '../../../../shared/models/permissions';
+import { AuthStorageService } from '../../../../shared/services/auth-storage.service';
+import { EditSiteNameModalComponent } from '../edit-site-name-modal/edit-site-name-modal.component';
 
 @Component({
   selector: 'cd-mirroring',
@@ -11,11 +19,36 @@ import { ViewCacheStatus } from '../../../../shared/enum/view-cache-status.enum'
   styleUrls: ['./overview.component.scss']
 })
 export class OverviewComponent implements OnInit, OnDestroy {
+  permission: Permission;
+  tableActions: CdTableAction[];
+  selection = new CdTableSelection();
+
   subs: Subscription;
 
+  modalRef: BsModalRef;
+
+  peersExist = true;
+  siteName: any;
   status: ViewCacheStatus;
 
-  constructor(private rbdMirroringService: RbdMirroringService) {}
+  constructor(
+    private authStorageService: AuthStorageService,
+    private rbdMirroringService: RbdMirroringService,
+    private modalService: BsModalService,
+    private i18n: I18n
+  ) {
+    this.permission = this.authStorageService.getPermissions().rbdMirroring;
+
+    const editSiteNameAction: CdTableAction = {
+      permission: 'update',
+      icon: Icons.edit,
+      click: () => this.editSiteNameModal(),
+      name: this.i18n('Edit Site Name'),
+      canBePrimary: () => true,
+      disable: () => false
+    };
+    this.tableActions = [editSiteNameAction];
+  }
 
   ngOnInit() {
     this.subs = this.rbdMirroringService.subscribeSummary((data: any) => {
@@ -23,10 +56,20 @@ export class OverviewComponent implements OnInit, OnDestroy {
         return;
       }
       this.status = data.content_data.status;
+      this.siteName = data.site_name;
+
+      this.peersExist = !!data.content_data.pools.find((o) => o['peer_uuids'].length > 0);
     });
   }
 
   ngOnDestroy(): void {
     this.subs.unsubscribe();
   }
+
+  editSiteNameModal() {
+    const initialState = {
+      siteName: this.siteName
+    };
+    this.modalRef = this.modalService.show(EditSiteNameModalComponent, { initialState });
+  }
 }
index bff7cff540ce4393b07e47d131a5f0a0cc044a39..df2281283f63a580eb7554d1d420b4d0fd3902ca 100644 (file)
@@ -252,6 +252,10 @@ describe('TaskManagerMessageService', () => {
         peerMsg = `mirror peer for pool '${metadata.pool_name}'`;
         finishedTask.metadata = metadata;
       });
+      it('tests rbd/mirroring/site_name/edit messages', () => {
+        finishedTask.name = 'rbd/mirroring/site_name/edit';
+        testUpdate('mirroring site name');
+      });
       it('tests rbd/mirroring/pool/edit messages', () => {
         finishedTask.name = 'rbd/mirroring/pool/edit';
         testUpdate(modeMsg);
index c4ce8834cda20ab1b08e107c55b0bab4794e5009..d1ede98fc299c1fc841b1a046b5a3f446425c4ca 100644 (file)
@@ -139,6 +139,7 @@ export class TaskMessageService {
   };
 
   rbd_mirroring = {
+    site_name: () => this.i18n('mirroring site name'),
     pool: (metadata) =>
       this.i18n(`mirror mode for pool '{{id}}'`, {
         id: `${metadata.pool_name}`
@@ -327,6 +328,11 @@ export class TaskMessageService {
       }
     ),
     // RBD mirroring tasks
+    'rbd/mirroring/site_name/edit': this.newTaskMessage(
+      this.commonOperations.update,
+      this.rbd_mirroring.site_name,
+      () => ({})
+    ),
     'rbd/mirroring/pool/edit': this.newTaskMessage(
       this.commonOperations.update,
       this.rbd_mirroring.pool,