]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/blob
7dcd81843d6dc622494f75b34507966028298c74
[ceph.git] /
1 import { Component, Input, OnChanges, OnInit, TemplateRef, ViewChild } from '@angular/core';
2
3 import { Node } from 'carbon-components-angular/treeview/tree-node.types';
4 import _ from 'lodash';
5 import { USER } from '~/app/shared/constants/app.constants';
6
7 import { TableComponent } from '~/app/shared/datatable/table/table.component';
8 import { Icons } from '~/app/shared/enum/icons.enum';
9 import { CdTableColumn } from '~/app/shared/models/cd-table-column';
10 import { BooleanTextPipe } from '~/app/shared/pipes/boolean-text.pipe';
11 import { IscsiBackstorePipe } from '~/app/shared/pipes/iscsi-backstore.pipe';
12 import { TreeViewService } from '~/app/shared/services/tree-view.service';
13
14 @Component({
15   selector: 'cd-iscsi-target-details',
16   templateUrl: './iscsi-target-details.component.html',
17   styleUrls: ['./iscsi-target-details.component.scss']
18 })
19 export class IscsiTargetDetailsComponent implements OnChanges, OnInit {
20   @Input()
21   selection: any;
22   @Input()
23   settings: any;
24   @Input()
25   cephIscsiConfigVersion: number;
26
27   @ViewChild('highlightTpl', { static: true })
28   highlightTpl: TemplateRef<any>;
29
30   private detailTable: TableComponent;
31   @ViewChild('detailTable')
32   set content(content: TableComponent) {
33     this.detailTable = content;
34     if (content) {
35       content.updateColumns();
36     }
37   }
38
39   @ViewChild('treeNodeTemplate', { static: true }) labelTpl: TemplateRef<any>;
40
41   icons = Icons;
42   columns: CdTableColumn[];
43   data: any;
44   metadata: any = {};
45   selectedItem: any;
46   title: string;
47
48   nodes: Node[] = [];
49
50   constructor(
51     private iscsiBackstorePipe: IscsiBackstorePipe,
52     private booleanTextPipe: BooleanTextPipe,
53     public treeViewService: TreeViewService
54   ) {}
55
56   ngOnInit() {
57     this.columns = [
58       {
59         prop: 'displayName',
60         name: $localize`Name`,
61         flexGrow: 1,
62         cellTemplate: this.highlightTpl
63       },
64       {
65         prop: 'current',
66         name: $localize`Current`,
67         flexGrow: 1,
68         cellTemplate: this.highlightTpl
69       },
70       {
71         prop: 'default',
72         name: $localize`Default`,
73         flexGrow: 1,
74         cellTemplate: this.highlightTpl
75       }
76     ];
77   }
78
79   ngOnChanges() {
80     if (this.selection) {
81       this.selectedItem = this.selection;
82       this.generateTree();
83     }
84
85     this.data = undefined;
86   }
87
88   private generateTree() {
89     const target_meta = _.cloneDeep(this.selectedItem.target_controls);
90     // Target level authentication was introduced in ceph-iscsi config v11
91     if (this.cephIscsiConfigVersion > 10) {
92       _.extend(target_meta, _.cloneDeep(this.selectedItem.auth));
93     }
94     this.metadata = { root: target_meta };
95     const cssClasses = {
96       target: {
97         expanded: _.join(
98           this.selectedItem.cdExecuting
99             ? [Icons.large, Icons.spinner, Icons.spin]
100             : [Icons.large, Icons.bullseye],
101           ' '
102         )
103       },
104       initiators: {
105         expanded: _.join([Icons.large, Icons.user], ' '),
106         leaf: _.join([Icons.user], ' ')
107       },
108       groups: {
109         expanded: _.join([Icons.large, Icons.users], ' '),
110         leaf: _.join([Icons.users], ' ')
111       },
112       disks: {
113         expanded: _.join([Icons.large, Icons.disk], ' '),
114         leaf: _.join([Icons.disk], ' ')
115       },
116       portals: {
117         expanded: _.join([Icons.large, Icons.server], ' '),
118         leaf: _.join([Icons.server], ' ')
119       }
120     };
121
122     const disks: any[] = [];
123     _.forEach(this.selectedItem.disks, (disk) => {
124       const id = 'disk_' + disk.pool + '_' + disk.image;
125       this.metadata[id] = {
126         controls: disk.controls,
127         backstore: disk.backstore
128       };
129       ['wwn', 'lun'].forEach((k) => {
130         if (k in disk) {
131           this.metadata[id][k] = disk[k];
132         }
133       });
134       disks.push({
135         id: id,
136         name: `${disk.pool}/${disk.image}`,
137         label: `${disk.pool}/${disk.image}`,
138         value: { cdIcon: cssClasses.disks.leaf }
139       });
140     });
141
142     const portals: Node[] = [];
143     _.forEach(this.selectedItem.portals, (portal) => {
144       portals.push({
145         label: this.labelTpl,
146         labelContext: {
147           name: `${portal.host}:${portal.ip}`,
148           cdIcon: cssClasses.portals.leaf
149         },
150         value: {
151           name: `${portal.host}:${portal.ip}`,
152           cdIcon: cssClasses.portals.leaf
153         }
154       });
155     });
156
157     const clients: Node[] = [];
158     _.forEach(this.selectedItem.clients, (client: Node) => {
159       const client_metadata = _.cloneDeep(client.auth);
160       if (client.info) {
161         _.extend(client_metadata, client.info);
162         delete client_metadata['state'];
163         _.forEach(Object.keys(client.info.state), (state) => {
164           client_metadata[state.toLowerCase()] = client.info.state[state];
165         });
166       }
167       this.metadata['client_' + client.client_iqn] = client_metadata;
168
169       const luns: Node[] = [];
170       client.luns.forEach((lun: Node) => {
171         luns.push({
172           label: this.labelTpl,
173           labelContext: {
174             name: `${lun.pool}/${lun.image}`,
175             cdIcon: cssClasses.disks.leaf
176           },
177           value: {
178             name: `${lun.pool}/${lun.image}`,
179             cdIcon: cssClasses.disks.leaf
180           },
181           id: 'disk_' + lun.pool + '_' + lun.image
182         });
183       });
184
185       let status = '';
186       if (client.info) {
187         status = Object.keys(client.info.state).includes('LOGGED_IN') ? 'logged_in' : 'logged_out';
188       }
189       clients.push({
190         label: this.labelTpl,
191         labelContext: {
192           name: client.client_iqn,
193           status: status,
194           cdIcon: cssClasses.initiators.leaf
195         },
196         value: {
197           name: client.client_iqn,
198           status: status,
199           cdIcon: cssClasses.initiators.leaf
200         },
201         id: 'client_' + client.client_iqn,
202         children: luns
203       });
204     });
205
206     const groups: Node[] = [];
207     _.forEach(this.selectedItem.groups, (group: Node) => {
208       const luns: Node[] = [];
209       group.disks.forEach((disk: Node) => {
210         luns.push({
211           label: this.labelTpl,
212           labelContext: {
213             name: `${disk.pool}/${disk.image}`,
214             cdIcon: cssClasses.disks.leaf
215           },
216           value: {
217             name: `${disk.pool}/${disk.image}`,
218             cdIcon: cssClasses.disks.leaf
219           },
220           id: 'disk_' + disk.pool + '_' + disk.image
221         });
222       });
223
224       const initiators: Node[] = [];
225       group.members.forEach((member: string) => {
226         initiators.push({
227           label: this.labelTpl,
228           labelContext: { name: member },
229           value: { name: member },
230           id: 'client_' + member
231         });
232       });
233
234       groups.push({
235         label: this.labelTpl,
236         labelContext: { name: group.group_id, cdIcon: cssClasses.groups.leaf },
237         value: { name: group.group_id, cdIcon: cssClasses.groups.leaf },
238         children: [
239           {
240             label: this.labelTpl,
241             labelContext: { name: 'Disks', cdIcon: cssClasses.disks.expanded },
242             value: { name: 'Disks', cdIcon: cssClasses.disks.expanded },
243             children: luns
244           },
245           {
246             label: this.labelTpl,
247             labelContext: { name: 'Initiators', cdIcon: cssClasses.initiators.expanded },
248             value: { name: 'Initiators', cdIcon: cssClasses.initiators.expanded },
249             children: initiators
250           }
251         ]
252       });
253     });
254
255     this.nodes = [
256       {
257         id: 'root',
258         label: this.labelTpl,
259         labelContext: {
260           name: this.selectedItem.target_iqn,
261           cdIcon: cssClasses.target.expanded
262         },
263         value: {
264           name: this.selectedItem.target_iqn,
265           cdIcon: cssClasses.target.expanded
266         },
267         expanded: true,
268         children: [
269           {
270             label: this.labelTpl,
271             labelContext: { name: 'Disks', cdIcon: cssClasses.disks.expanded },
272             value: { name: 'Disks', cdIcon: cssClasses.disks.expanded },
273             expanded: true,
274             children: disks
275           },
276           {
277             label: this.labelTpl,
278             labelContext: { name: 'Portals', cdIcon: cssClasses.portals.expanded },
279             value: { name: 'Portals', cdIcon: cssClasses.portals.expanded },
280             expanded: true,
281             children: portals
282           },
283           {
284             label: this.labelTpl,
285             labelContext: { name: 'Initiators', cdIcon: cssClasses.initiators.expanded },
286             value: { name: 'Initiators', cdIcon: cssClasses.initiators.expanded },
287             expanded: true,
288             children: clients
289           },
290           {
291             label: this.labelTpl,
292             labelContext: { name: 'Groups', cdIcon: cssClasses.groups.expanded },
293             value: { name: 'Groups', cdIcon: cssClasses.groups.expanded },
294             expanded: true,
295             children: groups
296           }
297         ]
298       }
299     ];
300   }
301
302   private format(value: any) {
303     if (typeof value === 'boolean') {
304       return this.booleanTextPipe.transform(value);
305     }
306     return value;
307   }
308
309   onNodeSelected(node: Node) {
310     if (node.id) {
311       this.title = node?.value?.name;
312       const tempData = this.metadata[node.id] || {};
313
314       if (node.id === 'root') {
315         this.detailTable?.toggleColumn({ prop: 'default', isHidden: true });
316         this.data = _.map(this.settings.target_default_controls, (value, key) => {
317           value = this.format(value);
318           return {
319             displayName: key,
320             default: value,
321             current: !_.isUndefined(tempData[key]) ? this.format(tempData[key]) : value
322           };
323         });
324         // Target level authentication was introduced in ceph-iscsi config v11
325         if (this.cephIscsiConfigVersion > 10) {
326           [USER, 'password', 'mutual_user', 'mutual_password'].forEach((key) => {
327             this.data.push({
328               displayName: key,
329               default: null,
330               current: tempData[key]
331             });
332           });
333         }
334       } else if (node.id.toString().startsWith('disk_')) {
335         this.detailTable?.toggleColumn({ prop: 'default', isHidden: true });
336         this.data = _.map(this.settings.disk_default_controls[tempData.backstore], (value, key) => {
337           value = this.format(value);
338           return {
339             displayName: key,
340             default: value,
341             current: !_.isUndefined(tempData.controls[key])
342               ? this.format(tempData.controls[key])
343               : value
344           };
345         });
346         this.data.push({
347           displayName: 'backstore',
348           default: this.iscsiBackstorePipe.transform(this.settings.default_backstore),
349           current: this.iscsiBackstorePipe.transform(tempData.backstore)
350         });
351         ['wwn', 'lun'].forEach((k) => {
352           if (k in tempData) {
353             this.data.push({
354               displayName: k,
355               default: undefined,
356               current: tempData[k]
357             });
358           }
359         });
360       } else {
361         this.detailTable?.toggleColumn({ prop: 'default', isHidden: false });
362         this.data = _.map(tempData, (value, key) => {
363           return {
364             displayName: key,
365             default: undefined,
366             current: this.format(value)
367           };
368         });
369       }
370     } else {
371       this.data = undefined;
372     }
373
374     this.detailTable?.updateColumns();
375   }
376 }