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