1 import { Component, Input, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
2 import { ActivatedRoute, Router } from '@angular/router';
3 import { NvmeofService, GroupsComboboxItem } from '~/app/shared/api/nvmeof.service';
4 import { DeleteConfirmationModalComponent } from '~/app/shared/components/delete-confirmation-modal/delete-confirmation-modal.component';
5 import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants';
6 import { DeletionImpact } from '~/app/shared/enum/delete-confirmation-modal-impact.enum';
7 import { Icons } from '~/app/shared/enum/icons.enum';
9 import { CdTableAction } from '~/app/shared/models/cd-table-action';
10 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
11 import { FinishedTask } from '~/app/shared/models/finished-task';
12 import { NvmeofSubsystemNamespace } from '~/app/shared/models/nvmeof';
13 import { Permission } from '~/app/shared/models/permissions';
14 import { CephServiceSpec } from '~/app/shared/models/service.interface';
15 import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
16 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
17 import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
19 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
20 import { BehaviorSubject, Observable, of, Subject } from 'rxjs';
21 import { catchError, map, switchMap, takeUntil } from 'rxjs/operators';
23 const BASE_URL = 'block/nvmeof/subsystems';
24 const DEFAULT_PLACEHOLDER = $localize`Enter group name`;
27 selector: 'cd-nvmeof-namespaces-list',
28 templateUrl: './nvmeof-namespaces-list.component.html',
29 styleUrls: ['./nvmeof-namespaces-list.component.scss'],
32 export class NvmeofNamespacesListComponent implements OnInit, OnDestroy {
37 @ViewChild('deleteTpl', { static: true })
38 deleteTpl: TemplateRef<any>;
39 namespacesColumns: any;
40 tableActions: CdTableAction[];
41 selection = new CdTableSelection();
42 permission: Permission;
43 namespaces$: Observable<NvmeofSubsystemNamespace[]>;
44 private namespaceSubject = new BehaviorSubject<void>(undefined);
46 // Gateway group dropdown properties
47 gwGroups: GroupsComboboxItem[] = [];
48 gwGroupsEmpty: boolean = false;
49 gwGroupPlaceholder: string = DEFAULT_PLACEHOLDER;
51 private destroy$ = new Subject<void>();
54 public actionLabels: ActionLabelsI18n,
55 private router: Router,
56 private route: ActivatedRoute,
57 private modalService: ModalCdsService,
58 private authStorageService: AuthStorageService,
59 private taskWrapper: TaskWrapperService,
60 private nvmeofService: NvmeofService,
61 private dimlessBinaryPipe: DimlessBinaryPipe
63 this.permission = this.authStorageService.getPermissions().nvmeof;
67 this.route.queryParams.pipe(takeUntil(this.destroy$)).subscribe((params) => {
68 if (params?.['group']) this.onGroupSelection({ content: params?.['group'] });
70 this.setGatewayGroups();
71 this.namespacesColumns = [
73 name: $localize`Namespace ID`,
77 name: $localize`Size`,
78 prop: 'rbd_image_size',
79 pipe: this.dimlessBinaryPipe
82 name: $localize`Pool`,
86 name: $localize`Image`,
87 prop: 'rbd_image_name'
90 name: $localize`Subsystem`,
91 prop: 'ns_subsystem_nqn'
96 name: this.actionLabels.CREATE,
100 this.router.navigate(['block/nvmeof/namespaces/create'], {
103 subsystem_nqn: this.subsystemNQN
107 canBePrimary: (selection: CdTableSelection) => !selection.hasSelection,
108 disable: () => !this.group
111 name: this.actionLabels.EDIT,
112 permission: 'update',
115 this.router.navigate(
119 this.selection.first().ns_subsystem_nqn,
121 this.selection.first().nsid
123 { queryParams: { group: this.group } }
127 name: this.actionLabels.DELETE,
128 permission: 'delete',
130 click: () => this.deleteNamespaceModal()
134 this.namespaces$ = this.namespaceSubject.pipe(
139 return this.nvmeofService.listNamespaces(this.group).pipe(
140 map((res: NvmeofSubsystemNamespace[] | { namespaces: NvmeofSubsystemNamespace[] }) => {
141 const namespaces = Array.isArray(res) ? res : res.namespaces || [];
142 // Deduplicate by nsid + subsystem NQN (API with wildcard can return duplicates per gateway)
143 const seen = new Set<string>();
144 return namespaces.filter((ns) => {
145 const key = `${ns.nsid}_${ns['ns_subsystem_nqn']}`;
146 if (seen.has(key)) return false;
151 catchError(() => of([]))
154 takeUntil(this.destroy$)
158 updateSelection(selection: CdTableSelection) {
159 this.selection = selection;
163 this.namespaceSubject.next();
167 this.namespaceSubject.next();
170 // Gateway groups methods
171 onGroupSelection(selected: GroupsComboboxItem) {
172 selected.selected = true;
173 this.group = selected.content;
174 this.listNamespaces();
179 this.listNamespaces();
185 .pipe(takeUntil(this.destroy$))
187 next: (response: CephServiceSpec[][]) => this.handleGatewayGroupsSuccess(response),
188 error: (error) => this.handleGatewayGroupsError(error)
192 handleGatewayGroupsSuccess(response: CephServiceSpec[][]) {
193 if (response?.[0]?.length) {
194 this.gwGroups = this.nvmeofService.formatGwGroupsList(response);
198 this.updateGroupSelectionState();
201 updateGroupSelectionState() {
202 if (!this.group && this.gwGroups.length) {
203 this.onGroupSelection(this.gwGroups[0]);
204 this.gwGroupsEmpty = false;
205 this.gwGroupPlaceholder = DEFAULT_PLACEHOLDER;
206 } else if (!this.gwGroups.length) {
207 this.gwGroupsEmpty = true;
208 this.gwGroupPlaceholder = $localize`No groups available`;
212 handleGatewayGroupsError(error: any) {
214 this.gwGroupsEmpty = true;
215 this.gwGroupPlaceholder = $localize`Unable to fetch Gateway groups`;
216 if (error?.preventDefault) {
217 error?.preventDefault?.();
221 deleteNamespaceModal() {
222 const namespace = this.selection.first();
223 const subsystemNqn = namespace.ns_subsystem_nqn;
224 this.modalService.show(DeleteConfirmationModalComponent, {
225 itemDescription: $localize`Namespace`,
226 impact: DeletionImpact.high,
227 bodyTemplate: this.deleteTpl,
228 itemNames: [namespace.nsid],
229 actionDescription: 'delete',
231 deletionMessage: $localize`Deleting the namespace <strong>${namespace.nsid}</strong> will permanently remove all resources, services, and configurations within it. This action cannot be undone.`
233 submitActionObservable: () =>
234 this.taskWrapper.wrapTaskAroundCall({
235 task: new FinishedTask('nvmeof/namespace/delete', {
239 call: this.nvmeofService.deleteNamespace(subsystemNqn, namespace.nsid, this.group)
245 this.destroy$.next();
246 this.destroy$.complete();