10 } from '@angular/core';
11 import { ActivatedRoute } from '@angular/router';
12 import { Observable, Subject, Subscription } from 'rxjs';
13 import { finalize } from 'rxjs/operators';
15 import { TableComponent } from '~/app/shared/datatable/table/table.component';
16 import { HostStatus } from '~/app/shared/enum/host-status.enum';
17 import { Icons } from '~/app/shared/enum/icons.enum';
18 import { CdTableAction } from '~/app/shared/models/cd-table-action';
19 import { CdTableColumn } from '~/app/shared/models/cd-table-column';
20 import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
21 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
22 import { OrchestratorStatus } from '~/app/shared/models/orchestrator.interface';
23 import { Permission } from '~/app/shared/models/permissions';
25 import { Host } from '~/app/shared/models/host.interface';
26 import { CephServiceSpec } from '~/app/shared/models/service.interface';
27 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
28 import { NvmeofService } from '~/app/shared/api/nvmeof.service';
29 import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
30 import { NvmeofGatewayNodeAddModalComponent } from './nvmeof-gateway-node-add-modal/nvmeof-gateway-node-add-modal.component';
33 selector: 'cd-nvmeof-gateway-node',
34 templateUrl: './nvmeof-gateway-node.component.html',
35 styleUrls: ['./nvmeof-gateway-node.component.scss'],
38 export class NvmeofGatewayNodeComponent implements OnInit, OnDestroy {
39 @ViewChild(TableComponent, { static: true })
40 table!: TableComponent;
42 @ViewChild('hostNameTpl', { static: true })
43 hostNameTpl!: TemplateRef<any>;
45 @ViewChild('statusTpl', { static: true })
46 statusTpl!: TemplateRef<any>;
48 @ViewChild('addrTpl', { static: true })
49 addrTpl!: TemplateRef<any>;
51 @ViewChild('labelsTpl', { static: true })
52 labelsTpl!: TemplateRef<any>;
54 @Output() selectionChange = new EventEmitter<CdTableSelection>();
55 @Output() hostsLoaded = new EventEmitter<number>();
57 @Input() groupName: string | undefined;
58 @Input() mode: 'selector' | 'details' = 'selector';
60 usedHostnames: Set<string> = new Set();
61 serviceSpec: CephServiceSpec | undefined;
62 hasAvailableHosts = false;
64 permission: Permission;
65 columns: CdTableColumn[] = [];
67 isLoadingHosts = false;
68 tableActions: CdTableAction[] = [];
69 selectionType: 'single' | 'multiClick' | 'none' = 'single';
71 selection = new CdTableSelection();
73 HostStatus = HostStatus;
74 private tableContext: CdTableFetchDataContext | undefined;
76 orchStatus: OrchestratorStatus | undefined;
77 private destroy$ = new Subject<void>();
78 private sub: Subscription | undefined;
81 private authStorageService: AuthStorageService,
82 private nvmeofService: NvmeofService,
83 private route: ActivatedRoute,
84 private modalService: ModalCdsService
86 this.permission = this.authStorageService.getPermissions().nvmeof;
90 const routeData = this.route.snapshot.data;
91 if (routeData?.['mode']) {
92 this.mode = routeData['mode'];
95 this.selectionType = this.mode === 'selector' ? 'multiClick' : 'single';
97 if (this.mode === 'details') {
98 this.route.parent?.params.subscribe((params: any) => {
99 this.groupName = params.group;
101 this.setTableActions();
106 name: $localize`Hostname`,
109 cellTemplate: this.hostNameTpl
112 name: $localize`IP address`,
115 cellTemplate: this.addrTpl
118 name: $localize`Status`,
121 cellTemplate: this.statusTpl
124 name: $localize`Labels (tags)`,
127 cellTemplate: this.labelsTpl
132 private setTableActions() {
133 this.tableActions = [
135 permission: 'create',
137 click: () => this.addGateway(),
138 name: $localize`Add`,
139 canBePrimary: (selection: CdTableSelection) => !selection.hasSelection,
140 disable: () => (!this.hasAvailableHosts ? $localize`No available nodes to add` : false)
143 permission: 'delete',
145 click: () => this.removeGateway(),
146 name: $localize`Remove`,
147 disable: (selection: CdTableSelection) => !selection.hasSelection
153 const modalRef = this.modalService.show(NvmeofGatewayNodeAddModalComponent, {
154 groupName: this.groupName,
155 usedHostnames: Array.from(this.usedHostnames),
156 serviceSpec: this.serviceSpec
159 modalRef.gatewayAdded.subscribe(() => {
160 this.table.refreshBtn();
164 removeGateway(): void {
168 ngOnDestroy(): void {
169 this.destroy$.next();
170 this.destroy$.complete();
172 this.sub.unsubscribe();
176 updateSelection(selection: CdTableSelection): void {
177 this.selection = selection;
178 this.selectionChange.emit(selection);
181 getSelectedHostnames(): string[] {
182 return this.selection.selected.map((host: Host) => host.hostname);
185 getHosts(context: CdTableFetchDataContext): void {
187 context || this.tableContext || new CdTableFetchDataContext(() => undefined);
188 if (this.isLoadingHosts) {
191 this.isLoadingHosts = true;
194 this.sub.unsubscribe();
197 const fetchData$: Observable<any> =
198 this.mode === 'details'
199 ? this.nvmeofService.fetchHostsAndGroups()
200 : this.nvmeofService.getAvailableHosts(this.tableContext?.toParams());
202 this.sub = fetchData$
205 this.isLoadingHosts = false;
209 next: (result: any) => {
210 if (this.mode === 'details') {
211 this.processDetailsData(result.groups, result.hosts);
214 this.count = this.hosts.length;
215 this.hostsLoaded.emit(this.count);
218 error: () => context?.error()
222 private processDetailsData(groups: any[][], hostList: Host[]) {
223 const groupList = groups?.[0] ?? [];
225 const allUsedHostnames = new Set<string>();
226 groupList.forEach((group: CephServiceSpec) => {
227 const hosts = group.placement?.hosts || (group.spec as any)?.placement?.hosts || [];
228 hosts.forEach((hostname: string) => allUsedHostnames.add(hostname));
231 this.usedHostnames = allUsedHostnames;
233 // Check if there are any available hosts globally (not used by any group)
234 this.hasAvailableHosts = (hostList || []).some(
235 (host: Host) => !this.usedHostnames.has(host.hostname)
237 this.setTableActions();
239 const currentGroup = groupList.find((group: CephServiceSpec) => {
241 group.spec?.group === this.groupName ||
242 group.service_id === `nvmeof.${this.groupName}` ||
243 group.service_id.endsWith(`.${this.groupName}`)
247 this.serviceSpec = currentGroup as CephServiceSpec;
249 if (!this.serviceSpec) {
252 const placementHosts =
253 this.serviceSpec.placement?.hosts || (this.serviceSpec.spec as any)?.placement?.hosts || [];
254 const currentGroupHosts = new Set<string>(placementHosts);
256 this.hosts = (hostList || []).filter((host: Host) => {
257 return currentGroupHosts.has(host.hostname);
261 this.count = this.hosts.length;
262 this.hostsLoaded.emit(this.count);