]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
'mgr/dashboard: Carbonize Block Module > NVme-Listing Gateway group
authorpujashahu <pshahu@redhat.com>
Mon, 17 Nov 2025 14:12:03 +0000 (19:42 +0530)
committerAfreen Misbah <afreen@ibm.com>
Wed, 29 Apr 2026 07:42:22 +0000 (13:12 +0530)
Fixes: https://tracker.ceph.com/issues/73719
Signed-off-by: pujaoshahu <pshahu@redhat.com>
(cherry picked from commit 0755593b4c87e519474f15011e3aac8bc086386f)

 Conflicts:
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/block.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-group/nvmeof-gateway-group.component.html
src/pybind/mgr/dashboard/frontend/src/styles/_carbon-defaults.scss
src/pybind/mgr/dashboard/frontend/src/styles/ceph-custom/_spacings.scss

16 files changed:
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/block.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-group/nvmeof-gateway-group.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-group/nvmeof-gateway-group.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-group/nvmeof-gateway-group.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway/nvmeof-gateway.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway/nvmeof-gateway.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway/nvmeof-gateway.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-list/nvmeof-namespaces-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems/nvmeof-subsystems.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems/nvmeof-subsystems.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-tabs/nvmeof-tabs.component.html [deleted file]
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-tabs/nvmeof-tabs.component.scss [deleted file]
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-tabs/nvmeof-tabs.component.spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-tabs/nvmeof-tabs.component.ts [deleted file]
src/pybind/mgr/dashboard/frontend/src/app/shared/api/nvmeof.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/models/nvmeof.ts

index 66c0b781117aa9ddec2c76b9b4d4d6c4051e295b..df03b8ecb84c34f9aeefbe9acd4707081a23accd 100644 (file)
@@ -40,7 +40,6 @@ import { RbdTrashRestoreModalComponent } from './rbd-trash-restore-modal/rbd-tra
 import { NvmeofGatewayComponent } from './nvmeof-gateway/nvmeof-gateway.component';
 import { NvmeofSubsystemsComponent } from './nvmeof-subsystems/nvmeof-subsystems.component';
 import { NvmeofSubsystemsDetailsComponent } from './nvmeof-subsystems-details/nvmeof-subsystems-details.component';
-import { NvmeofTabsComponent } from './nvmeof-tabs/nvmeof-tabs.component';
 import { NvmeofSubsystemsFormComponent } from './nvmeof-subsystems-form/nvmeof-subsystems-form.component';
 import { NvmeofListenersFormComponent } from './nvmeof-listeners-form/nvmeof-listeners-form.component';
 import { NvmeofListenersListComponent } from './nvmeof-listeners-list/nvmeof-listeners-list.component';
@@ -74,6 +73,7 @@ import Close from '@carbon/icons/es/close/32';
 import AddFilled from '@carbon/icons/es/add--filled/32';
 import SubtractFilled from '@carbon/icons/es/subtract--filled/32';
 import Reset from '@carbon/icons/es/reset/32';
+import { NvmeofGatewayGroupComponent } from './nvmeof-gateway-group/nvmeof-gateway-group.component';
 
 @NgModule({
   imports: [
@@ -131,7 +131,7 @@ import Reset from '@carbon/icons/es/reset/32';
     NvmeofGatewayComponent,
     NvmeofSubsystemsComponent,
     NvmeofSubsystemsDetailsComponent,
-    NvmeofTabsComponent,
+    NvmeofGatewayGroupComponent,
     NvmeofSubsystemsFormComponent,
     NvmeofListenersFormComponent,
     NvmeofListenersListComponent,
@@ -283,7 +283,8 @@ const routes: Routes = [
       }
     },
     children: [
-      { path: '', redirectTo: 'subsystems', pathMatch: 'full' },
+      { path: '', redirectTo: 'gateways', pathMatch: 'full' },
+      { path: '', component: NvmeofGatewayComponent, data: { breadcrumbs: 'Gateways' } },
       {
         path: 'subsystems',
         component: NvmeofSubsystemsComponent,
@@ -320,8 +321,7 @@ const routes: Routes = [
             outlet: 'modal'
           }
         ]
-      },
-      { path: 'gateways', component: NvmeofGatewayComponent, data: { breadcrumbs: 'Gateways' } }
+      }
     ]
   }
 ];
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-group/nvmeof-gateway-group.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-group/nvmeof-gateway-group.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-group/nvmeof-gateway-group.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-group/nvmeof-gateway-group.component.spec.ts
new file mode 100644 (file)
index 0000000..d61a4f3
--- /dev/null
@@ -0,0 +1,245 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { NvmeofGatewayGroupComponent } from './nvmeof-gateway-group.component';
+import { GridModule, TabsModule } from 'carbon-components-angular';
+import { NvmeofService } from '~/app/shared/api/nvmeof.service';
+import { of } from 'rxjs';
+import { HttpClientModule } from '@angular/common/http';
+import { SharedModule } from '~/app/shared/shared.module';
+
+describe('NvmeofGatewayGroupComponent', () => {
+  let component: NvmeofGatewayGroupComponent;
+  let fixture: ComponentFixture<NvmeofGatewayGroupComponent>;
+  let nvmeofService: any;
+
+  beforeEach(async () => {
+    const nvmeofServiceSpy = {
+      listGatewayGroups: jest.fn().mockReturnValue(of([])),
+      listSubsystems: jest.fn().mockReturnValue(of([]))
+    };
+
+    await TestBed.configureTestingModule({
+      imports: [HttpClientModule, SharedModule, TabsModule, GridModule],
+      declarations: [NvmeofGatewayGroupComponent],
+      providers: [{ provide: NvmeofService, useValue: nvmeofServiceSpy }]
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(NvmeofGatewayGroupComponent);
+    component = fixture.componentInstance;
+    nvmeofService = TestBed.inject(NvmeofService);
+    fixture.detectChanges();
+  });
+
+  it('should create the component', () => {
+    expect(component).toBeTruthy();
+  });
+
+  it('should call listGatewayGroups and getDaemons on initialization', () => {
+    expect(nvmeofService.listGatewayGroups).toHaveBeenCalled();
+  });
+
+  it('should populate gatewayGroup$ with the correct data', (done) => {
+    const mockData = [
+      {
+        service_type: 'nvmeof',
+        service_id: 'rbd.default',
+        service_name: 'nvmeof.rbd.default',
+        placement: {
+          count: 1
+        },
+        spec: {
+          abort_discovery_on_errors: true,
+          abort_on_errors: true,
+          allowed_consecutive_spdk_ping_failures: 1,
+          break_update_interval_sec: 25,
+          cluster_connections: 32,
+          conn_retries: 10,
+          discovery_port: 8009,
+          enable_monitor_client: true,
+          enable_prometheus_exporter: true,
+          group: 'default',
+          log_directory: '/var/log/ceph/',
+          log_files_enabled: true,
+          log_files_rotation_enabled: true,
+          log_level: 'INFO',
+          max_gws_in_grp: 16,
+          max_hosts: 2048,
+          max_hosts_per_namespace: 8,
+          max_hosts_per_subsystem: 128,
+          max_log_directory_backups: 10,
+          max_log_file_size_in_mb: 10,
+          max_log_files_count: 20,
+          max_namespaces: 4096,
+          max_namespaces_per_subsystem: 512,
+          max_namespaces_with_netmask: 1000,
+          max_ns_to_change_lb_grp: 8,
+          max_subsystems: 128,
+          monitor_timeout: 1,
+          notifications_interval: 60,
+          omap_file_lock_duration: 20,
+          omap_file_lock_on_read: true,
+          omap_file_lock_retries: 30,
+          omap_file_lock_retry_sleep_interval: 1,
+          omap_file_update_attempts: 500,
+          omap_file_update_reloads: 10,
+          pool: 'rbd',
+          port: 5500,
+          prometheus_connection_list_cache_expiration: 60,
+          prometheus_cycles_to_adjust_speed: 3,
+          prometheus_frequency_slow_down_factor: 3,
+          prometheus_port: 10008,
+          prometheus_startup_delay: 240,
+          prometheus_stats_interval: 10,
+          rebalance_period_sec: 7,
+          rpc_socket_dir: '/var/tmp/',
+          rpc_socket_name: 'spdk.sock',
+          spdk_path: '/usr/local/bin/nvmf_tgt',
+          spdk_ping_interval_in_seconds: 2,
+          spdk_protocol_log_level: 'WARNING',
+          spdk_timeout: 60,
+          state_update_interval_sec: 5,
+          state_update_notify: true,
+          subsystem_cache_expiration: 5,
+          tgt_path: '/usr/local/bin/nvmf_tgt',
+          transport_tcp_options: {
+            in_capsule_data_size: 8192,
+            max_io_qpairs_per_ctrlr: 7
+          },
+          transports: 'tcp',
+          verbose_log_messages: true,
+          verify_keys: true,
+          verify_listener_ip: true,
+          verify_nqns: true
+        },
+        status: {
+          size: 1,
+          running: 1,
+          last_refresh: new Date('2025-12-01T16:50:21.122930Z'),
+          created: new Date('2025-10-16T16:35:09.623842Z'),
+          ports: [5500, 4420, 8009, 10008],
+          container_image_id: 'image_id_1',
+          container_image_name: 'image_name_1'
+        },
+        events: [
+          {
+            created: '2025-10-16T16:35:59.879726Z',
+            subject: 'service:nvmeof.rbd.default',
+            level: 'INFO',
+            message: 'service was created'
+          }
+        ],
+        name: 'default',
+        gatewayCount: {
+          running: 1,
+          error: 0
+        },
+        subSystemCount: 0,
+        nodeCount: 0,
+        unmanaged: true
+      },
+      {
+        service_type: 'nvmeof',
+        service_id: 'rbd.foo',
+        service_name: 'nvmeof.rbd.foo',
+        placement: {
+          hosts: ['ceph-node-01', 'ceph-node-02', 'ceph-node-03']
+        },
+        spec: {
+          abort_discovery_on_errors: true,
+          abort_on_errors: true,
+          allowed_consecutive_spdk_ping_failures: 1,
+          break_update_interval_sec: 25,
+          cluster_connections: 32,
+          conn_retries: 10,
+          discovery_port: 8009,
+          enable_monitor_client: true,
+          enable_prometheus_exporter: true,
+          group: 'foo',
+          log_directory: '/var/log/ceph/',
+          log_files_enabled: true,
+          log_files_rotation_enabled: true,
+          log_level: 'INFO',
+          max_gws_in_grp: 16,
+          max_hosts: 2048,
+          max_hosts_per_namespace: 8,
+          max_hosts_per_subsystem: 128,
+          max_log_directory_backups: 10,
+          max_log_file_size_in_mb: 10,
+          max_log_files_count: 20,
+          max_namespaces: 4096,
+          max_namespaces_per_subsystem: 512,
+          max_namespaces_with_netmask: 1000,
+          max_ns_to_change_lb_grp: 8,
+          max_subsystems: 128,
+          monitor_timeout: 1,
+          notifications_interval: 60,
+          omap_file_lock_duration: 20,
+          omap_file_lock_on_read: true,
+          omap_file_lock_retries: 30,
+          omap_file_lock_retry_sleep_interval: 1,
+          omap_file_update_attempts: 500,
+          omap_file_update_reloads: 10,
+          pool: 'rbd',
+          port: 5500,
+          prometheus_connection_list_cache_expiration: 60,
+          prometheus_cycles_to_adjust_speed: 3,
+          prometheus_frequency_slow_down_factor: 3,
+          prometheus_port: 10008,
+          prometheus_startup_delay: 240,
+          prometheus_stats_interval: 10,
+          rebalance_period_sec: 7,
+          rpc_socket_dir: '/var/tmp/',
+          rpc_socket_name: 'spdk.sock',
+          spdk_path: '/usr/local/bin/nvmf_tgt',
+          spdk_ping_interval_in_seconds: 2,
+          spdk_protocol_log_level: 'WARNING',
+          spdk_timeout: 60,
+          state_update_interval_sec: 5,
+          state_update_notify: true,
+          subsystem_cache_expiration: 5,
+          tgt_path: '/usr/local/bin/nvmf_tgt',
+          transport_tcp_options: {
+            in_capsule_data_size: 8192,
+            max_io_qpairs_per_ctrlr: 7
+          },
+          transports: 'tcp',
+          verbose_log_messages: true,
+          verify_keys: true,
+          verify_listener_ip: true,
+          verify_nqns: true
+        },
+        status: {
+          size: 3,
+          running: 2,
+          last_refresh: new Date('2025-12-01T16:44:42.361882Z'),
+          created: new Date('2025-11-11T12:55:32.770910Z'),
+          ports: [5500, 4420, 8009, 10008],
+          container_image_id: 'image_id_2',
+          container_image_name: 'image_name_2'
+        },
+        events: [
+          {
+            created: '2025-11-11T12:56:42.509108Z',
+            subject: 'service:nvmeof.rbd.foo',
+            level: 'INFO',
+            message: 'service was created'
+          }
+        ],
+        name: 'foo',
+        gatewayCount: {
+          running: 2,
+          error: 1
+        },
+        subSystemCount: 0,
+        nodeCount: 3,
+        unmanaged: true
+      }
+    ];
+
+    component.gatewayGroup$ = of(mockData as any);
+
+    component.gatewayGroup$.subscribe((data) => {
+      expect(data).toEqual(mockData);
+      done();
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-group/nvmeof-gateway-group.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-group/nvmeof-gateway-group.component.ts
new file mode 100644 (file)
index 0000000..cc594d1
--- /dev/null
@@ -0,0 +1,118 @@
+import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
+import { BehaviorSubject, forkJoin, Observable, of } from 'rxjs';
+import { catchError, map, switchMap } from 'rxjs/operators';
+import { GatewayGroup, NvmeofService } from '~/app/shared/api/nvmeof.service';
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+import { Permission } from '~/app/shared/models/permissions';
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { Icons, IconSize } from '~/app/shared/enum/icons.enum';
+import { NvmeofGatewayGroup } from '~/app/shared/models/nvmeof';
+import { CephServiceSpec } from '~/app/shared/models/service.interface';
+
+@Component({
+  selector: 'cd-nvmeof-gateway-group',
+  templateUrl: './nvmeof-gateway-group.component.html',
+  styleUrls: ['./nvmeof-gateway-group.component.scss']
+})
+export class NvmeofGatewayGroupComponent implements OnInit {
+  @ViewChild(TableComponent, { static: true })
+  table: TableComponent;
+
+  @ViewChild('dateTpl', { static: true })
+  dateTpl: TemplateRef<any>;
+
+  @ViewChild('gatewayStatusTpl', { static: true })
+  gatewayStatusTpl: TemplateRef<any>;
+
+  permission: Permission;
+  tableActions: CdTableAction[];
+  columns: CdTableColumn[] = [];
+  selection: CdTableSelection = new CdTableSelection();
+  gatewayGroup$: Observable<CephServiceSpec[]>;
+  subject = new BehaviorSubject<CephServiceSpec[]>([]);
+  context: CdTableFetchDataContext;
+  gatewayGroupName: string;
+  subsystemCount: number;
+  gatewayCount: number;
+
+  icons = Icons;
+
+  iconSize = IconSize;
+
+  constructor(
+    public actionLabels: ActionLabelsI18n,
+    private authStorageService: AuthStorageService,
+    private nvmeofService: NvmeofService
+  ) {}
+
+  ngOnInit(): void {
+    this.permission = this.authStorageService.getPermissions().nvmeof;
+
+    this.columns = [
+      {
+        name: $localize`Name`,
+        prop: 'name'
+      },
+      {
+        name: $localize`Gateways`,
+        prop: 'statusCount',
+        cellTemplate: this.gatewayStatusTpl
+      },
+      {
+        name: $localize`Subsystems`,
+        prop: 'subSystemCount'
+      },
+      {
+        name: $localize`Created on`,
+        prop: 'created',
+        cellTemplate: this.dateTpl
+      }
+    ];
+
+    this.gatewayGroup$ = this.subject.pipe(
+      switchMap(() =>
+        this.nvmeofService.listGatewayGroups().pipe(
+          switchMap((gatewayGroups: GatewayGroup[][]) => {
+            const groups = gatewayGroups?.[0] ?? [];
+            return forkJoin(
+              groups.map((group: NvmeofGatewayGroup) =>
+                this.nvmeofService.listSubsystems(group.spec.group).pipe(
+                  catchError(() => of([])),
+                  map((subs) => ({
+                    ...group,
+                    name: group.spec?.group,
+                    statusCount: {
+                      running: group.status?.running ?? 0,
+                      error: (group.status?.size ?? 0) - (group.status?.running ?? 0)
+                    },
+
+                    subSystemCount: Array.isArray(subs) ? subs.length : 0,
+                    gateWayNode: group.placement?.hosts?.length ?? 0,
+                    created: group.status?.created ? new Date(group.status.created) : null
+                  }))
+                )
+              )
+            );
+          }),
+          catchError((error) => {
+            this.context?.error?.(error);
+            return of([]);
+          })
+        )
+      )
+    );
+  }
+
+  fetchData(): void {
+    this.subject.next([]);
+  }
+
+  updateSelection(selection: CdTableSelection): void {
+    this.selection = selection;
+  }
+}
index 7da764d12029b7132cab2e909706d2aab2a448a1..37a344907f30159ac24b2a827bf993935b89b6d6 100644 (file)
@@ -1,85 +1,44 @@
-<cd-nvmeof-tabs></cd-nvmeof-tabs>
-
-<div class="pb-3"
-     cdsCol
-     [columnNumbers]="{md: 4}">
-  <cds-combo-box
-      type="single"
-      label="Selected Gateway Group"
-      i18n-label
-      [placeholder]="gwGroupPlaceholder"
-      [items]="gwGroups"
-      (selected)="onGroupSelection($event)"
-      (clear)="onGroupClear()"
-      [disabled]="gwGroupsEmpty">
-    <cds-dropdown-list></cds-dropdown-list>
-  </cds-combo-box>
-</div>
-
-<ng-template #statusTpl
-             let-row="data.row">
-  <cds-tag [class]="row | pipeFunction:getStatusClass">
-    {{ row.status_desc }}
-</cds-tag>
-</ng-template>
-
-<cds-tabs
-  type="contained"
-  followFocus="true"
-  isNavigation="true"
-  [cacheActive]="false">
+<fieldset>
+  <legend>
+    <h1 class="cds--type-heading-03">NVMe over Fabrics (TCP)</h1>
+  <cd-help-text>Monitor and manage NVMe-over-TCP resources for high-performance block storage.</cd-help-text>
+  </legend>
+</fieldset>
+<section>
+  <cds-tabs type="contained"
+            followFocus="true"
+            isNavigation="true"
+            [cacheActive]="false">
   <cds-tab
-    heading="Gateways"
+    heading="Gateway groups"
     [tabContent]="gateways_content"
     i18n-heading
     (selected)="onSelected(Tabs.gateways)">
   </cds-tab>
   <cds-tab
-    heading="Overview"
-    [tabContent]="overview_content"
+    heading="Subsystem"
+    [tabContent]="subsystem_content"
     i18n-heading
-    (selected)="onSelected(Tabs.overview)">
+    (selected)="onSelected(Tabs.subsystem)">
   </cds-tab>
   <cds-tab
-    heading="Performance"
-    [tabContent]="performance_content"
+    heading="Namespace"
+    [tabContent]="namespace_content"
     i18n-heading
-    (selected)="onSelected(Tabs.overview)">
+    (selected)="onSelected(Tabs.namespace)">
   </cds-tab>
 </cds-tabs>
 
 <ng-template #gateways_content>
-  <legend *ngIf="selectedTab === Tabs.gateways"
-          i18n>
-    Gateways
-    <cd-help-text>
-      Ceph NVMe-oF gateways provide Ceph Block Device storage through NVMe/TCP. For VMware clients the NVMe/TCP volumes display as  VMFS Datastores. For Linux clients the NVMe/TCP volumes display as as block devices.
-    </cd-help-text>
-  </legend>
-  <div>
-    <cd-table [data]="gateways"
-              (fetchData)="getGateways()"
-              [columns]="gatewayColumns">
-    </cd-table>
-  </div>
+  <cd-nvmeof-gateway-group></cd-nvmeof-gateway-group>
 </ng-template>
 
-<ng-template #overview_content>
-  <cd-grafana i18n-title
-              title="Gateway overview"
-              grafanaPath="ceph-nvme-of-gateways-overview?var-group={{selectedGatewayGroup}}&var-gateway=All"
-              [type]="'metrics'"
-              uid="feeuv1dno43r4ddjhjssdd"
-              grafanaStyle="three">
-  </cd-grafana>
+<ng-template #subsystem_content>
+  <cd-nvmeof-subsystems></cd-nvmeof-subsystems>
 </ng-template>
 
-<ng-template #performance_content>
-  <cd-grafana i18n-title
-              title="Gateway performance"
-              grafanaPath="ceph-nvme-of-gateways-performance?var-group={{selectedGatewayGroup}}"
-              [type]="'metrics'"
-              uid="feeuv1dno43r4deed"
-              grafanaStyle="three">
-  </cd-grafana>
+<ng-template #namespace_content>
+  <cd-nvmeof-namespaces-list></cd-nvmeof-namespaces-list>
 </ng-template>
+</section>
+
index f4fb0c50d870c1a63a603394cb8285c90b5dad51..893a3aefc2bafd56fa2f62f94ed41f65648fbb66 100644 (file)
 import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { of } from 'rxjs';
+
 import { NvmeofGatewayComponent } from './nvmeof-gateway.component';
-import { NvmeofService } from '../../../shared/api/nvmeof.service';
+
 import { HttpClientModule } from '@angular/common/http';
 import { SharedModule } from '~/app/shared/shared.module';
-import { ComboBoxModule, GridModule } from 'carbon-components-angular';
-import { NvmeofTabsComponent } from '../nvmeof-tabs/nvmeof-tabs.component';
-import { CephServiceService } from '~/app/shared/api/ceph-service.service';
-import { CephServiceSpec } from '~/app/shared/models/service.interface';
-
-const mockServiceDaemons = [
-  {
-    daemon_type: 'nvmeof',
-    daemon_id: 'nvmeof.default.ceph-node-01.kdcguk',
-    daemon_name: 'nvmeof.nvmeof.default.ceph-node-01.kdcguk',
-    hostname: 'ceph-node-01',
-    container_id: '6fe5a9ae9c96',
-    container_image_id: '32a3d75b7c146d6c37b04ee3c9ba883ab88a8f7ae8f286de268d0f41ebd86a51',
-    container_image_name: 'quay.io/ceph/nvmeof:1.2.17',
-    container_image_digests: [
-      'quay.io/ceph/nvmeof@sha256:4308d05d3bb2167fc695d755316fec8d12ec3f00eb7639eeeabad38a5c4df0f9'
-    ],
-    memory_usage: 89443532,
-    cpu_percentage: '98.87%',
-    version: '1.2.17',
-    status: 1,
-    status_desc: 'running'
-  },
-  {
-    daemon_type: 'nvmeof',
-    daemon_id: 'nvmeof.default.ceph-node-02.hybprc',
-    daemon_name: 'nvmeof.nvmeof.default.ceph-node-02.hybprc',
-    hostname: 'ceph-node-02',
-    container_id: '2b061130726b',
-    container_image_id: '32a3d75b7c146d6c37b04ee3c9ba883ab88a8f7ae8f286de268d0f41ebd86a51',
-    container_image_name: 'quay.io/ceph/nvmeof:1.2.17',
-    container_image_digests: [
-      'quay.io/ceph/nvmeof@sha256:4308d05d3bb2167fc695d755316fec8d12ec3f00eb7639eeeabad38a5c4df0f9'
-    ],
-    memory_usage: 89328189,
-    cpu_percentage: '98.89%',
-    version: '1.2.17',
-    status: 1,
-    status_desc: 'running'
-  }
-];
-
-const mockGateways = [
-  {
-    id: 'client.nvmeof.nvmeof.default.ceph-node-01.kdcguk',
-    hostname: 'ceph-node-01',
-    status_desc: 'running',
-    status: 1
-  },
-  {
-    id: 'client.nvmeof.nvmeof.default.ceph-node-02.hybprc',
-    hostname: 'ceph-node-02',
-    status_desc: 'running',
-    status: 1
-  }
-];
-
-const mockformattedGwGroups = [
-  {
-    content: 'default',
-    serviceName: 'nvmeof.rbd.default'
-  },
-  {
-    content: 'foo',
-    serviceName: 'nvmeof.rbd.foo'
-  }
-];
-
-const mockServices = [
-  [
-    {
-      service_name: 'nvmeof.rbd.default',
-      service_type: 'nvmeof',
-      unmanaged: false,
-      spec: {
-        group: 'default'
-      }
-    },
-    {
-      service_name: 'nvmeof.rbd.foo',
-      service_type: 'nvmeof',
-      unmanaged: false,
-      spec: {
-        group: 'foo'
-      }
-    }
-  ],
-  2
-];
-class MockNvmeOfService {
-  listGatewayGroups() {
-    return of(mockServices);
-  }
-
-  formatGwGroupsList(_data: CephServiceSpec[][]) {
-    return mockformattedGwGroups;
-  }
-}
-
-class MockCephServiceService {
-  getDaemons(_service: string) {
-    return of(mockServiceDaemons);
-  }
-}
+import { ComboBoxModule, GridModule, TabsModule } from 'carbon-components-angular';
 
 describe('NvmeofGatewayComponent', () => {
   let component: NvmeofGatewayComponent;
@@ -115,35 +12,17 @@ describe('NvmeofGatewayComponent', () => {
 
   beforeEach(async () => {
     await TestBed.configureTestingModule({
-      declarations: [NvmeofGatewayComponent, NvmeofTabsComponent],
-      imports: [HttpClientModule, SharedModule, ComboBoxModule, GridModule],
-      providers: [
-        { provide: NvmeofService, useClass: MockNvmeOfService },
-        { provide: CephServiceService, useClass: MockCephServiceService }
-      ]
+      declarations: [NvmeofGatewayComponent],
+      imports: [HttpClientModule, SharedModule, ComboBoxModule, GridModule, TabsModule],
+      providers: []
     }).compileComponents();
 
     fixture = TestBed.createComponent(NvmeofGatewayComponent);
     component = fixture.componentInstance;
-    component.ngOnInit();
     fixture.detectChanges();
   });
 
   it('should create', () => {
     expect(component).toBeTruthy();
   });
-
-  it('should load gateway groups correctly', () => {
-    expect(component.gwGroups.length).toBe(2);
-    expect(component.gwGroups).toStrictEqual(mockformattedGwGroups);
-  });
-
-  it('should set service name of gateway groups correctly', () => {
-    expect(component.groupService).toBe(mockServices[0][0].service_name);
-  });
-
-  it('should set gateways correctly', () => {
-    expect(component.gateways.length).toBe(2);
-    expect(component.gateways).toStrictEqual(mockGateways);
-  });
 });
index 33764a87be56bd0272d8182fb651fdf4bf4de710..44f6b9f04a134112d9371233591875cbc3bfd8ed 100644 (file)
@@ -1,35 +1,22 @@
-import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
+import { Component, TemplateRef, ViewChild } from '@angular/core';
 
 import _ from 'lodash';
 
 import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
 
-import { GroupsComboboxItem, NvmeofService } from '../../../shared/api/nvmeof.service';
-import { CephServiceSpec } from '~/app/shared/models/service.interface';
-import { CephServiceService } from '~/app/shared/api/ceph-service.service';
-import { Daemon } from '~/app/shared/models/daemon.interface';
-
-type Gateway = {
-  id: string;
-  hostname: string;
-  status: number;
-  status_desc: string;
-};
-
 enum TABS {
   'gateways',
-  'overview'
+  'subsystem',
+  'namespace'
 }
 
-const DEFAULT_PLACEHOLDER = $localize`Enter group name`;
-
 @Component({
   selector: 'cd-nvmeof-gateway',
   templateUrl: './nvmeof-gateway.component.html',
   styleUrls: ['./nvmeof-gateway.component.scss']
 })
-export class NvmeofGatewayComponent implements OnInit {
+export class NvmeofGatewayComponent {
   selectedTab: TABS;
 
   onSelected(tab: TABS) {
@@ -42,97 +29,7 @@ export class NvmeofGatewayComponent implements OnInit {
 
   @ViewChild('statusTpl', { static: true })
   statusTpl: TemplateRef<any>;
-
-  gateways: Gateway[] = [];
-  gatewayColumns: any;
   selection = new CdTableSelection();
-  gwGroups: GroupsComboboxItem[] = [];
-  groupService: string = null;
-  selectedGatewayGroup: string = null;
-  gwGroupsEmpty: boolean = false;
-  gwGroupPlaceholder: string = DEFAULT_PLACEHOLDER;
-
-  constructor(
-    private nvmeofService: NvmeofService,
-    private cephServiceService: CephServiceService,
-    public actionLabels: ActionLabelsI18n
-  ) {}
-
-  ngOnInit() {
-    this.setGatewayGroups();
-    this.gatewayColumns = [
-      {
-        name: $localize`Gateway ID`,
-        prop: 'id'
-      },
-      {
-        name: $localize`Hostname`,
-        prop: 'hostname'
-      },
-      {
-        name: $localize`Status`,
-        prop: 'status_desc',
-        cellTemplate: this.statusTpl
-      }
-    ];
-  }
-
-  // for Status column
-  getStatusClass(row: Gateway): string {
-    return _.get(
-      {
-        '-1': 'tag-danger',
-        '0': 'tag-warning',
-        '1': 'tag-success'
-      },
-      row.status,
-      'tag-dark'
-    );
-  }
-
-  // Gateways
-  getGateways() {
-    this.cephServiceService.getDaemons(this.groupService).subscribe((daemons: Daemon[]) => {
-      this.gateways = daemons.length
-        ? daemons.map((daemon: Daemon) => {
-            return {
-              id: `client.${daemon.daemon_name}`,
-              hostname: daemon.hostname,
-              status_desc: daemon.status_desc,
-              status: daemon.status
-            };
-          })
-        : [];
-    });
-  }
-
-  // Gateway groups
-  onGroupSelection(selected: GroupsComboboxItem) {
-    selected.selected = true;
-    this.groupService = selected.serviceName;
-    this.selectedGatewayGroup = selected.content;
-    this.getGateways();
-  }
-
-  onGroupClear() {
-    this.groupService = null;
-    this.getGateways();
-  }
 
-  setGatewayGroups() {
-    this.nvmeofService.listGatewayGroups().subscribe((response: CephServiceSpec[][]) => {
-      if (response?.[0]?.length) {
-        this.gwGroups = this.nvmeofService.formatGwGroupsList(response, true);
-      } else this.gwGroups = [];
-      // Select first group if no group is selected
-      if (!this.groupService && this.gwGroups.length) {
-        this.onGroupSelection(this.gwGroups[0]);
-        this.gwGroupsEmpty = false;
-        this.gwGroupPlaceholder = DEFAULT_PLACEHOLDER;
-      } else {
-        this.gwGroupsEmpty = true;
-        this.gwGroupPlaceholder = $localize`No groups available`;
-      }
-    });
-  }
+  constructor(public actionLabels: ActionLabelsI18n) {}
 }
index 75562626ee5041a5e6f1c49eff2e473fd70f957b..30775d2156916ac3cca51204d3b496db83b831a6 100644 (file)
@@ -8,7 +8,6 @@ import { NvmeofService } from '../../../shared/api/nvmeof.service';
 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
 import { ModalService } from '~/app/shared/services/modal.service';
 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
-import { NvmeofTabsComponent } from '../nvmeof-tabs/nvmeof-tabs.component';
 import { NvmeofSubsystemsDetailsComponent } from '../nvmeof-subsystems-details/nvmeof-subsystems-details.component';
 import { NvmeofNamespacesListComponent } from './nvmeof-namespaces-list.component';
 
@@ -51,11 +50,7 @@ describe('NvmeofNamespacesListComponent', () => {
 
   beforeEach(async () => {
     await TestBed.configureTestingModule({
-      declarations: [
-        NvmeofNamespacesListComponent,
-        NvmeofTabsComponent,
-        NvmeofSubsystemsDetailsComponent
-      ],
+      declarations: [NvmeofNamespacesListComponent, NvmeofSubsystemsDetailsComponent],
       imports: [HttpClientModule, RouterTestingModule, SharedModule],
       providers: [
         { provide: NvmeofService, useClass: MockNvmeOfService },
index 3246f7286f51ddc418258f4f6f97503cf4a40632..413cec31ce346543376382a5400eb31890a99183 100644 (file)
@@ -9,7 +9,6 @@ import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
 import { ModalService } from '~/app/shared/services/modal.service';
 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
 import { NvmeofSubsystemsComponent } from './nvmeof-subsystems.component';
-import { NvmeofTabsComponent } from '../nvmeof-tabs/nvmeof-tabs.component';
 import { NvmeofSubsystemsDetailsComponent } from '../nvmeof-subsystems-details/nvmeof-subsystems-details.component';
 import { ComboBoxModule, GridModule } from 'carbon-components-angular';
 import { CephServiceSpec } from '~/app/shared/models/service.interface';
@@ -89,11 +88,7 @@ describe('NvmeofSubsystemsComponent', () => {
 
   beforeEach(async () => {
     await TestBed.configureTestingModule({
-      declarations: [
-        NvmeofSubsystemsComponent,
-        NvmeofTabsComponent,
-        NvmeofSubsystemsDetailsComponent
-      ],
+      declarations: [NvmeofSubsystemsComponent, NvmeofSubsystemsDetailsComponent],
       imports: [HttpClientModule, RouterTestingModule, SharedModule, ComboBoxModule, GridModule],
       providers: [
         { provide: NvmeofService, useClass: MockNvmeOfService },
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-tabs/nvmeof-tabs.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-tabs/nvmeof-tabs.component.html
deleted file mode 100644 (file)
index 29f1e2a..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-<ul class="nav nav-tabs">
-  <li class="nav-item">
-    <a class="nav-link"
-       routerLink="/block/nvmeof/subsystems"
-       routerLinkActive="active"
-       ariaCurrentWhenActive="page"
-       i18n>Subsystems</a>
-  </li>
-  <li class="nav-item">
-    <a class="nav-link"
-       routerLink="/block/nvmeof/gateways"
-       routerLinkActive="active"
-       ariaCurrentWhenActive="page"
-       i18n>Gateways</a>
-  </li>
-</ul>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-tabs/nvmeof-tabs.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-tabs/nvmeof-tabs.component.scss
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-tabs/nvmeof-tabs.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-tabs/nvmeof-tabs.component.spec.ts
deleted file mode 100644 (file)
index 23e334a..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-
-import { NvmeofTabsComponent } from './nvmeof-tabs.component';
-
-describe('NvmeofTabsComponent', () => {
-  let component: NvmeofTabsComponent;
-  let fixture: ComponentFixture<NvmeofTabsComponent>;
-
-  beforeEach(async () => {
-    await TestBed.configureTestingModule({
-      declarations: [NvmeofTabsComponent]
-    }).compileComponents();
-
-    fixture = TestBed.createComponent(NvmeofTabsComponent);
-    component = fixture.componentInstance;
-    fixture.detectChanges();
-  });
-
-  it('should create', () => {
-    expect(component).toBeTruthy();
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-tabs/nvmeof-tabs.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-tabs/nvmeof-tabs.component.ts
deleted file mode 100644 (file)
index 507116c..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-import { Component } from '@angular/core';
-
-@Component({
-  selector: 'cd-nvmeof-tabs',
-  templateUrl: './nvmeof-tabs.component.html',
-  styleUrls: ['./nvmeof-tabs.component.scss']
-})
-export class NvmeofTabsComponent {}
index 1420841a6efb5ed0536c9ff9ddd47559f0109e25..aaccc68e14c17b13ffc6c30ea2ca9e5c22053fa5 100644 (file)
@@ -74,7 +74,7 @@ export class NvmeofService {
 
   // Gateway groups
   listGatewayGroups() {
-    return this.http.get<GatewayGroup[][]>(`${API_PATH}/gateway/group`);
+    return this.http.get<CephServiceSpec[][]>(`${API_PATH}/gateway/group`);
   }
 
   // Gateways
index db3bc8b1fad8028aa855c7b8a37993b3ae09e5b5..43a24a4b5ce2d0b6ec9bcd496323648505c3f4c5 100644 (file)
@@ -1,3 +1,5 @@
+import { CephServiceSpec } from './service.interface';
+
 export interface NvmeofGateway {
   version: string;
   name: string;
@@ -47,3 +49,13 @@ export interface NvmeofSubsystemNamespace {
   r_mbytes_per_second: number;
   w_mbytes_per_second: number;
 }
+
+export interface NvmeofGatewayGroup extends CephServiceSpec {
+  name: string;
+  gatewayCount: {
+    running: number;
+    error: number;
+  };
+  subSystemCount: number;
+  nodeCount: number;
+}