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';
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: [
NvmeofGatewayComponent,
NvmeofSubsystemsComponent,
NvmeofSubsystemsDetailsComponent,
- NvmeofTabsComponent,
+ NvmeofGatewayGroupComponent,
NvmeofSubsystemsFormComponent,
NvmeofListenersFormComponent,
NvmeofListenersListComponent,
}
},
children: [
- { path: '', redirectTo: 'subsystems', pathMatch: 'full' },
+ { path: '', redirectTo: 'gateways', pathMatch: 'full' },
+ { path: '', component: NvmeofGatewayComponent, data: { breadcrumbs: 'Gateways' } },
{
path: 'subsystems',
component: NvmeofSubsystemsComponent,
outlet: 'modal'
}
]
- },
- { path: 'gateways', component: NvmeofGatewayComponent, data: { breadcrumbs: 'Gateways' } }
+ }
]
}
];
--- /dev/null
+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();
+ });
+ });
+});
--- /dev/null
+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;
+ }
+}
-<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>
+
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;
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);
- });
});
-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) {
@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) {}
}
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';
beforeEach(async () => {
await TestBed.configureTestingModule({
- declarations: [
- NvmeofNamespacesListComponent,
- NvmeofTabsComponent,
- NvmeofSubsystemsDetailsComponent
- ],
+ declarations: [NvmeofNamespacesListComponent, NvmeofSubsystemsDetailsComponent],
imports: [HttpClientModule, RouterTestingModule, SharedModule],
providers: [
{ provide: NvmeofService, useClass: MockNvmeOfService },
-<cd-nvmeof-tabs></cd-nvmeof-tabs>
-
<div class="pb-3"
cdsCol
[columnNumbers]="{md: 4}">
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';
beforeEach(async () => {
await TestBed.configureTestingModule({
- declarations: [
- NvmeofSubsystemsComponent,
- NvmeofTabsComponent,
- NvmeofSubsystemsDetailsComponent
- ],
+ declarations: [NvmeofSubsystemsComponent, NvmeofSubsystemsDetailsComponent],
imports: [HttpClientModule, RouterTestingModule, SharedModule, ComboBoxModule, GridModule],
providers: [
{ provide: NvmeofService, useClass: MockNvmeOfService },
+++ /dev/null
-<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>
+++ /dev/null
-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();
- });
-});
+++ /dev/null
-import { Component } from '@angular/core';
-
-@Component({
- selector: 'cd-nvmeof-tabs',
- templateUrl: './nvmeof-tabs.component.html',
- styleUrls: ['./nvmeof-tabs.component.scss']
-})
-export class NvmeofTabsComponent {}
// Gateway groups
listGatewayGroups() {
- return this.http.get<GatewayGroup[][]>(`${API_PATH}/gateway/group`);
+ return this.http.get<CephServiceSpec[][]>(`${API_PATH}/gateway/group`);
}
// Gateways
+import { CephServiceSpec } from './service.interface';
+
export interface NvmeofGateway {
version: string;
name: string;
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;
+}