10 } from '@angular/core';
11 import { ActivatedRoute } from '@angular/router';
12 import { forkJoin, Subject, Subscription } from 'rxjs';
13 import { finalize, mergeMap } from 'rxjs/operators';
15 import { HostService } from '~/app/shared/api/host.service';
16 import { OrchestratorService } from '~/app/shared/api/orchestrator.service';
17 import { TableComponent } from '~/app/shared/datatable/table/table.component';
18 import { HostStatus } from '~/app/shared/enum/host-status.enum';
19 import { Icons } from '~/app/shared/enum/icons.enum';
20 import { NvmeofGatewayNodeMode } from '~/app/shared/enum/nvmeof.enum';
22 import { CdTableAction } from '~/app/shared/models/cd-table-action';
23 import { CdTableColumn } from '~/app/shared/models/cd-table-column';
24 import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
25 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
26 import { OrchestratorStatus } from '~/app/shared/models/orchestrator.interface';
27 import { Permission } from '~/app/shared/models/permissions';
29 import { Host } from '~/app/shared/models/host.interface';
30 import { CephServiceSpec } from '~/app/shared/models/service.interface';
31 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
32 import { NvmeofService } from '~/app/shared/api/nvmeof.service';
35 selector: 'cd-nvmeof-gateway-node',
36 templateUrl: './nvmeof-gateway-node.component.html',
37 styleUrls: ['./nvmeof-gateway-node.component.scss'],
40 export class NvmeofGatewayNodeComponent implements OnInit, OnDestroy {
41 @ViewChild(TableComponent, { static: true })
42 table!: TableComponent;
44 @ViewChild('hostNameTpl', { static: true })
45 hostNameTpl!: TemplateRef<any>;
47 @ViewChild('statusTpl', { static: true })
48 statusTpl!: TemplateRef<any>;
50 @ViewChild('addrTpl', { static: true })
51 addrTpl!: TemplateRef<any>;
53 @ViewChild('labelsTpl', { static: true })
54 labelsTpl!: TemplateRef<any>;
56 @Output() selectionChange = new EventEmitter<CdTableSelection>();
57 @Output() hostsLoaded = new EventEmitter<number>();
58 @Input() groupName: string | undefined;
59 @Input() mode: NvmeofGatewayNodeMode = NvmeofGatewayNodeMode.SELECTOR;
61 usedHostnames: Set<string> = new Set();
62 serviceSpec: CephServiceSpec | undefined;
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 hostService: HostService,
83 private orchService: OrchestratorService,
84 private nvmeofService: NvmeofService,
85 private route: ActivatedRoute
87 this.permission = this.authStorageService.getPermissions().nvmeof;
91 this.route.data.subscribe((data) => {
93 this.mode = data['mode'];
97 this.selectionType = this.mode === NvmeofGatewayNodeMode.SELECTOR ? 'multiClick' : 'single';
99 if (this.mode === NvmeofGatewayNodeMode.DETAILS) {
100 this.route.parent?.params.subscribe((params: { group: string }) => {
101 this.groupName = params.group;
103 this.tableActions = [
105 permission: 'create',
107 click: () => this.addGateway(),
108 name: $localize`Add`,
109 canBePrimary: (selection: CdTableSelection) => !selection.hasSelection
112 permission: 'delete',
114 click: () => this.removeGateway(),
115 name: $localize`Remove`,
116 disable: (selection: CdTableSelection) => !selection.hasSelection
123 name: $localize`Hostname`,
126 cellTemplate: this.hostNameTpl
129 name: $localize`IP address`,
132 cellTemplate: this.addrTpl
135 name: $localize`Status`,
138 cellTemplate: this.statusTpl
141 name: $localize`Labels (tags)`,
144 cellTemplate: this.labelsTpl
153 removeGateway(): void {
157 ngOnDestroy(): void {
158 this.destroy$.next();
159 this.destroy$.complete();
161 this.sub.unsubscribe();
165 updateSelection(selection: CdTableSelection): void {
166 this.selection = selection;
167 this.selectionChange.emit(selection);
170 getSelectedHostnames(): string[] {
171 return this.selection.selected.map((host: Host) => host.hostname);
174 getHosts(context: CdTableFetchDataContext): void {
176 context || this.tableContext || new CdTableFetchDataContext(() => undefined);
177 if (this.isLoadingHosts) {
180 this.isLoadingHosts = true;
183 this.sub.unsubscribe();
187 this.mode === NvmeofGatewayNodeMode.DETAILS
188 ? this.nvmeofService.fetchHostsAndGroups()
190 groups: this.nvmeofService.listGatewayGroups(),
191 hosts: this.orchService.status().pipe(
192 mergeMap((orchStatus: OrchestratorStatus) => {
193 this.orchStatus = orchStatus;
194 const factsAvailable = this.hostService.checkHostsFactsAvailable(orchStatus);
195 return this.hostService.list(
196 this.tableContext?.toParams(),
197 factsAvailable.toString()
203 this.sub = fetchData$
206 this.isLoadingHosts = false;
210 next: (result: any) => {
211 this.mode === NvmeofGatewayNodeMode.DETAILS
212 ? this.processHostsForDetailsMode(result.groups, result.hosts)
213 : this.processHostsForSelectorMode(result.groups, result.hosts);
215 error: () => context?.error()
220 * Selector Mode: Used in 'Add/Create' forms.
221 * Filters the entire cluster inventory to show only **available** candidates
222 * (excluding nodes that are already part of a gateway group).
224 private processHostsForSelectorMode(groups: CephServiceSpec[][] = [[]], hostList: Host[] = []) {
225 const usedHosts = new Set<string>();
226 (groups?.[0] ?? []).forEach((group: CephServiceSpec) => {
227 group.placement?.hosts?.forEach((hostname: string) => usedHosts.add(hostname));
229 this.usedHostnames = usedHosts;
231 this.hosts = (hostList || []).filter((host: Host) => !this.usedHostnames.has(host.hostname));
237 * Details Mode: Used in 'Details' views.
238 * Filters specifically for the nodes that are **configured members**
239 * of the current gateway group, regardless of their status.
241 private processHostsForDetailsMode(groups: any[][], hostList: Host[]) {
242 const groupList = groups?.[0] ?? [];
243 const currentGroup: CephServiceSpec | undefined = groupList.find(
244 (group: CephServiceSpec) => group.spec?.group === this.groupName
250 const placementHosts =
251 currentGroup.placement?.hosts || (currentGroup.spec as any)?.placement?.hosts || [];
252 const currentGroupHosts = new Set<string>(placementHosts);
254 this.hosts = (hostList || []).filter((host: Host) => {
255 return currentGroupHosts.has(host.hostname);
259 this.serviceSpec = currentGroup;
263 private updateCount(): void {
264 this.totalHostCount = this.hosts.length;
265 this.hostsLoaded.emit(this.totalHostCount);