1 import { Component, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
2 import { ActivatedRoute, Router } from '@angular/router';
3 import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants';
4 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
7 NvmeofSubsystemInitiator,
9 } from '~/app/shared/models/nvmeof';
10 import { Permissions } from '~/app/shared/models/permissions';
11 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
12 import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
13 import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
14 import { CdTableAction } from '~/app/shared/models/cd-table-action';
16 import { Icons } from '~/app/shared/enum/icons.enum';
17 import { NvmeofSubsystemAuthType } from '~/app/shared/enum/nvmeof.enum';
18 import { DeleteConfirmationModalComponent } from '~/app/shared/components/delete-confirmation-modal/delete-confirmation-modal.component';
19 import { FinishedTask } from '~/app/shared/models/finished-task';
20 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
21 import { NvmeofService, GroupsComboboxItem } from '~/app/shared/api/nvmeof.service';
22 import { NotificationService } from '~/app/shared/services/notification.service';
23 import { NotificationType } from '~/app/shared/enum/notification-type.enum';
24 import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
25 import { CephServiceSpec } from '~/app/shared/models/service.interface';
26 import { BehaviorSubject, forkJoin, Observable, of, Subject } from 'rxjs';
27 import { catchError, map, switchMap, takeUntil } from 'rxjs/operators';
28 import { DeletionImpact } from '~/app/shared/enum/delete-confirmation-modal-impact.enum';
30 const BASE_URL = 'block/nvmeof/subsystems';
31 const DEFAULT_PLACEHOLDER = $localize`Enter group name`;
34 selector: 'cd-nvmeof-subsystems',
35 templateUrl: './nvmeof-subsystems.component.html',
36 styleUrls: ['./nvmeof-subsystems.component.scss'],
39 export class NvmeofSubsystemsComponent extends ListWithDetails implements OnInit, OnDestroy {
40 @ViewChild('authenticationTpl', { static: true })
41 authenticationTpl: TemplateRef<any>;
43 @ViewChild('encryptionTpl', { static: true })
44 encryptionTpl: TemplateRef<any>;
46 @ViewChild('deleteTpl', { static: true })
47 deleteTpl: TemplateRef<any>;
49 subsystemsColumns: any;
50 permissions: Permissions;
51 selection = new CdTableSelection();
52 tableActions: CdTableAction[];
53 subsystemDetails: any[];
54 context: CdTableFetchDataContext;
55 gwGroups: GroupsComboboxItem[] = [];
57 gwGroupsEmpty: boolean = false;
58 gwGroupPlaceholder: string = DEFAULT_PLACEHOLDER;
59 authType = NvmeofSubsystemAuthType;
60 subsystems$: Observable<(NvmeofSubsystem & { gw_group?: string; initiator_count?: number })[]>;
61 private subsystemSubject = new BehaviorSubject<void>(undefined);
63 private destroy$ = new Subject<void>();
66 private nvmeofService: NvmeofService,
67 private authStorageService: AuthStorageService,
68 public actionLabels: ActionLabelsI18n,
69 private router: Router,
70 private route: ActivatedRoute,
71 private modalService: ModalCdsService,
72 private taskWrapper: TaskWrapperService,
73 private notificationService: NotificationService
76 this.permissions = this.authStorageService.getPermissions();
80 this.route.queryParams.pipe(takeUntil(this.destroy$)).subscribe((params) => {
81 if (params?.['group']) this.onGroupSelection({ content: params?.['group'] });
83 this.setGatewayGroups();
84 this.subsystemsColumns = [
86 name: $localize`Subsystem NQN`,
91 name: $localize`Gateway group`,
95 name: $localize`Initiators`,
96 prop: 'initiator_count'
100 name: $localize`Namespaces`,
101 prop: 'namespace_count'
104 name: $localize`Authentication`,
105 prop: 'authentication',
106 cellTemplate: this.authenticationTpl
109 name: $localize`Traffic encryption`,
111 cellTemplate: this.encryptionTpl
115 this.tableActions = [
117 name: this.actionLabels.CREATE,
118 permission: 'create',
121 this.router.navigate([BASE_URL, { outlets: { modal: [URLVerbs.CREATE] } }], {
122 queryParams: { group: this.group }
124 canBePrimary: (selection: CdTableSelection) => !selection.hasSelection,
125 disable: () => !this.group
128 name: this.actionLabels.DELETE,
129 permission: 'delete',
131 click: () => this.deleteSubsystemModal()
135 this.subsystems$ = this.subsystemSubject.pipe(
140 return this.nvmeofService.listSubsystems(this.group).pipe(
141 switchMap((subsystems: NvmeofSubsystem[] | NvmeofSubsystem) => {
142 const subs = Array.isArray(subsystems) ? subsystems : [subsystems];
143 if (subs.length === 0) return of([]);
144 return forkJoin(subs.map((sub) => this.enrichSubsystemWithInitiators(sub)));
146 catchError((error) => {
147 this.notificationService.show(
148 NotificationType.error,
149 $localize`Unable to fetch Gateway group`,
150 $localize`Gateway group does not exist`
152 this.handleError(error);
157 takeUntil(this.destroy$)
161 updateSelection(selection: CdTableSelection) {
162 this.selection = selection;
166 this.subsystemSubject.next();
170 this.subsystemSubject.next();
173 deleteSubsystemModal() {
174 const subsystem = this.selection.first();
175 this.modalService.show(DeleteConfirmationModalComponent, {
176 itemDescription: $localize`Subsystem`,
177 impact: DeletionImpact.high,
178 bodyTemplate: this.deleteTpl,
179 itemNames: [subsystem.nqn],
180 actionDescription: 'delete',
182 deletionMessage: $localize`Deleting <strong>${subsystem.nqn}</strong> will remove all associated configurations and resources. Dependent services may stop working. This action cannot be undone.`
184 submitActionObservable: () =>
185 this.taskWrapper.wrapTaskAroundCall({
186 task: new FinishedTask('nvmeof/subsystem/delete', { nqn: subsystem.nqn }),
187 call: this.nvmeofService.deleteSubsystem(subsystem.nqn, this.group)
192 onGroupSelection(selected: GroupsComboboxItem) {
193 selected.selected = true;
194 this.group = selected.content;
195 this.getSubsystems();
200 this.getSubsystems();
206 .pipe(takeUntil(this.destroy$))
208 next: (response: CephServiceSpec[][]) => this.handleGatewayGroupsSuccess(response),
209 error: (error) => this.handleGatewayGroupsError(error)
213 handleGatewayGroupsSuccess(response: CephServiceSpec[][]) {
214 if (response?.[0]?.length) {
215 this.gwGroups = this.nvmeofService.formatGwGroupsList(response);
219 this.updateGroupSelectionState();
222 updateGroupSelectionState() {
223 if (!this.group && this.gwGroups.length) {
224 this.onGroupSelection(this.gwGroups[0]);
225 this.gwGroupsEmpty = false;
226 this.gwGroupPlaceholder = DEFAULT_PLACEHOLDER;
228 this.gwGroupsEmpty = true;
229 this.gwGroupPlaceholder = $localize`No groups available`;
233 handleGatewayGroupsError(error: any) {
235 this.gwGroupsEmpty = true;
236 this.gwGroupPlaceholder = $localize`Unable to fetch Gateway groups`;
237 this.handleError(error);
240 private handleError(error: any): void {
241 if (error?.preventDefault) {
242 error?.preventDefault?.();
244 this.context?.error?.(error);
247 private enrichSubsystemWithInitiators(sub: NvmeofSubsystem) {
248 return this.nvmeofService.getInitiators(sub.nqn, this.group).pipe(
249 catchError(() => of([])),
250 map((initiators: NvmeofSubsystemInitiator[] | { hosts?: NvmeofSubsystemInitiator[] }) => {
252 if (Array.isArray(initiators)) count = initiators.length;
253 else if (initiators?.hosts && Array.isArray(initiators.hosts)) {
254 count = initiators.hosts.length;
259 gw_group: this.group,
260 initiator_count: count,
261 auth: getSubsystemAuthStatus(sub, initiators)
262 } as NvmeofSubsystem & { initiator_count?: number; auth?: string };
268 this.destroy$.next();
269 this.destroy$.complete();