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 { forkJoin, 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 subsystems: (NvmeofSubsystem & { gw_group?: string; initiator_count?: number })[] = [];
50 subsystemsColumns: any;
51 permissions: Permissions;
52 selection = new CdTableSelection();
53 tableActions: CdTableAction[];
54 subsystemDetails: any[];
55 context: CdTableFetchDataContext;
56 gwGroups: GroupsComboboxItem[] = [];
58 gwGroupsEmpty: boolean = false;
59 gwGroupPlaceholder: string = DEFAULT_PLACEHOLDER;
60 authType = NvmeofSubsystemAuthType;
62 private destroy$ = new Subject<void>();
65 private nvmeofService: NvmeofService,
66 private authStorageService: AuthStorageService,
67 public actionLabels: ActionLabelsI18n,
68 private router: Router,
69 private route: ActivatedRoute,
70 private modalService: ModalCdsService,
71 private taskWrapper: TaskWrapperService,
72 private notificationService: NotificationService
75 this.permissions = this.authStorageService.getPermissions();
79 this.route.queryParams.pipe(takeUntil(this.destroy$)).subscribe((params) => {
80 if (params?.['group']) this.onGroupSelection({ content: params?.['group'] });
82 this.setGatewayGroups();
83 this.subsystemsColumns = [
85 name: $localize`Subsystem NQN`,
90 name: $localize`Gateway group`,
94 name: $localize`Initiators`,
95 prop: 'initiator_count'
99 name: $localize`Namespaces`,
100 prop: 'namespace_count'
103 name: $localize`Authentication`,
104 prop: 'authentication',
105 cellTemplate: this.authenticationTpl
108 name: $localize`Traffic encryption`,
110 cellTemplate: this.encryptionTpl
114 this.tableActions = [
116 name: this.actionLabels.CREATE,
117 permission: 'create',
120 this.router.navigate([BASE_URL, { outlets: { modal: [URLVerbs.CREATE] } }], {
121 queryParams: { group: this.group }
123 canBePrimary: (selection: CdTableSelection) => !selection.hasSelection,
124 disable: () => !this.group
127 name: this.actionLabels.DELETE,
128 permission: 'delete',
130 click: () => this.deleteSubsystemModal()
135 updateSelection(selection: CdTableSelection) {
136 this.selection = selection;
142 .listSubsystems(this.group)
144 switchMap((subsystems: NvmeofSubsystem[] | NvmeofSubsystem) => {
145 const subs = Array.isArray(subsystems) ? subsystems : [subsystems];
146 if (subs.length === 0) return of([]);
148 return forkJoin(subs.map((sub) => this.enrichSubsystemWithInitiators(sub)));
151 .pipe(takeUntil(this.destroy$))
153 next: (subsystems: NvmeofSubsystem[]) => {
154 this.subsystems = subsystems;
157 this.subsystems = [];
158 this.notificationService.show(
159 NotificationType.error,
160 $localize`Unable to fetch Gateway group`,
161 $localize`Gateway group does not exist`
163 this.handleError(error);
167 this.subsystems = [];
171 deleteSubsystemModal() {
172 const subsystem = this.selection.first();
173 this.modalService.show(DeleteConfirmationModalComponent, {
174 itemDescription: $localize`Subsystem`,
175 impact: DeletionImpact.high,
176 bodyTemplate: this.deleteTpl,
177 itemNames: [subsystem.nqn],
178 actionDescription: 'delete',
180 deletionMessage: $localize`Deleting <strong>${subsystem.nqn}</strong> will remove all associated configurations and resources. Dependent services may stop working. This action cannot be undone.`
182 submitActionObservable: () =>
183 this.taskWrapper.wrapTaskAroundCall({
184 task: new FinishedTask('nvmeof/subsystem/delete', { nqn: subsystem.nqn }),
185 call: this.nvmeofService.deleteSubsystem(subsystem.nqn, this.group)
190 onGroupSelection(selected: GroupsComboboxItem) {
191 selected.selected = true;
192 this.group = selected.content;
193 this.getSubsystems();
198 this.getSubsystems();
204 .pipe(takeUntil(this.destroy$))
206 next: (response: CephServiceSpec[][]) => this.handleGatewayGroupsSuccess(response),
207 error: (error) => this.handleGatewayGroupsError(error)
211 handleGatewayGroupsSuccess(response: CephServiceSpec[][]) {
212 if (response?.[0]?.length) {
213 this.gwGroups = this.nvmeofService.formatGwGroupsList(response);
217 this.updateGroupSelectionState();
220 updateGroupSelectionState() {
221 if (!this.group && this.gwGroups.length) {
222 this.onGroupSelection(this.gwGroups[0]);
223 this.gwGroupsEmpty = false;
224 this.gwGroupPlaceholder = DEFAULT_PLACEHOLDER;
226 this.gwGroupsEmpty = true;
227 this.gwGroupPlaceholder = $localize`No groups available`;
231 handleGatewayGroupsError(error: any) {
233 this.gwGroupsEmpty = true;
234 this.gwGroupPlaceholder = $localize`Unable to fetch Gateway groups`;
235 this.handleError(error);
238 private handleError(error: any): void {
239 if (error?.preventDefault) {
240 error?.preventDefault?.();
242 this.context?.error?.(error);
245 private enrichSubsystemWithInitiators(sub: NvmeofSubsystem) {
246 return this.nvmeofService.getInitiators(sub.nqn, this.group).pipe(
247 catchError(() => of([])),
248 map((initiators: NvmeofSubsystemInitiator[] | { hosts?: NvmeofSubsystemInitiator[] }) => {
250 if (Array.isArray(initiators)) count = initiators.length;
251 else if (initiators?.hosts && Array.isArray(initiators.hosts)) {
252 count = initiators.hosts.length;
257 gw_group: this.group,
258 initiator_count: count,
259 auth: getSubsystemAuthStatus(sub, initiators)
260 } as NvmeofSubsystem & { initiator_count?: number; auth?: string };
266 this.destroy$.next();
267 this.destroy$.complete();