]> git.apps.os.sepia.ceph.com Git - ceph.git/blob
ba8068b23be17c920d6fdf046ebe80dcfc7a3072
[ceph.git] /
1 import {
2   AfterViewInit,
3   Component,
4   Input,
5   OnChanges,
6   OnDestroy,
7   OnInit,
8   QueryList,
9   TemplateRef,
10   ViewChild,
11   ViewChildren
12 } from '@angular/core';
13
14 import _ from 'lodash';
15 import { Observable, Subscription } from 'rxjs';
16 import { take } from 'rxjs/operators';
17
18 import { CephServiceService } from '~/app/shared/api/ceph-service.service';
19 import { DaemonService } from '~/app/shared/api/daemon.service';
20 import { HostService } from '~/app/shared/api/host.service';
21 import { OrchestratorService } from '~/app/shared/api/orchestrator.service';
22 import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
23 import { TableComponent } from '~/app/shared/datatable/table/table.component';
24 import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
25 import { Icons } from '~/app/shared/enum/icons.enum';
26 import { NotificationType } from '~/app/shared/enum/notification-type.enum';
27 import { CdTableAction } from '~/app/shared/models/cd-table-action';
28 import { CdTableColumn } from '~/app/shared/models/cd-table-column';
29 import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
30 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
31 import { Daemon } from '~/app/shared/models/daemon.interface';
32 import { Permissions } from '~/app/shared/models/permissions';
33 import { CephServiceSpec } from '~/app/shared/models/service.interface';
34 import { RelativeDatePipe } from '~/app/shared/pipes/relative-date.pipe';
35 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
36 import { NotificationService } from '~/app/shared/services/notification.service';
37
38 @Component({
39   selector: 'cd-service-daemon-list',
40   templateUrl: './service-daemon-list.component.html',
41   styleUrls: ['./service-daemon-list.component.scss']
42 })
43 export class ServiceDaemonListComponent implements OnInit, OnChanges, AfterViewInit, OnDestroy {
44   @ViewChild('statusTpl', { static: true })
45   statusTpl: TemplateRef<any>;
46
47   @ViewChild('listTpl', { static: true })
48   listTpl: TemplateRef<any>;
49
50   @ViewChildren('daemonsTable')
51   daemonsTableTpls: QueryList<TemplateRef<TableComponent>>;
52
53   @Input()
54   serviceName?: string;
55
56   @Input()
57   hostname?: string;
58
59   @Input()
60   hiddenColumns: string[] = [];
61
62   @Input()
63   flag?: string;
64
65   icons = Icons;
66
67   daemons: Daemon[] = [];
68   services: Array<CephServiceSpec> = [];
69   columns: CdTableColumn[] = [];
70   serviceColumns: CdTableColumn[] = [];
71   tableActions: CdTableAction[];
72   selection = new CdTableSelection();
73   permissions: Permissions;
74
75   hasOrchestrator = false;
76   showDocPanel = false;
77
78   private daemonsTable: TableComponent;
79   private daemonsTableTplsSub: Subscription;
80   private serviceSub: Subscription;
81
82   constructor(
83     private hostService: HostService,
84     private cephServiceService: CephServiceService,
85     private orchService: OrchestratorService,
86     private relativeDatePipe: RelativeDatePipe,
87     public actionLabels: ActionLabelsI18n,
88     private authStorageService: AuthStorageService,
89     private daemonService: DaemonService,
90     private notificationService: NotificationService
91   ) {}
92
93   ngOnInit() {
94     this.permissions = this.authStorageService.getPermissions();
95     this.tableActions = [
96       {
97         permission: 'update',
98         icon: Icons.start,
99         click: () => this.daemonAction('start'),
100         name: this.actionLabels.START,
101         disable: () => this.actionDisabled('start')
102       },
103       {
104         permission: 'update',
105         icon: Icons.stop,
106         click: () => this.daemonAction('stop'),
107         name: this.actionLabels.STOP,
108         disable: () => this.actionDisabled('stop')
109       },
110       {
111         permission: 'update',
112         icon: Icons.restart,
113         click: () => this.daemonAction('restart'),
114         name: this.actionLabels.RESTART,
115         disable: () => this.actionDisabled('restart')
116       },
117       {
118         permission: 'update',
119         icon: Icons.deploy,
120         click: () => this.daemonAction('redeploy'),
121         name: this.actionLabels.REDEPLOY,
122         disable: () => this.actionDisabled('redeploy')
123       }
124     ];
125     this.columns = [
126       {
127         name: $localize`Hostname`,
128         prop: 'hostname',
129         flexGrow: 2,
130         filterable: true
131       },
132       {
133         name: $localize`Daemon type`,
134         prop: 'daemon_type',
135         flexGrow: 1,
136         filterable: true
137       },
138       {
139         name: $localize`Daemon ID`,
140         prop: 'daemon_id',
141         flexGrow: 1,
142         filterable: true
143       },
144       {
145         name: $localize`Container ID`,
146         prop: 'container_id',
147         flexGrow: 2,
148         filterable: true,
149         cellTransformation: CellTemplate.truncate,
150         customTemplateConfig: {
151           length: 12
152         }
153       },
154       {
155         name: $localize`Container Image name`,
156         prop: 'container_image_name',
157         flexGrow: 3,
158         filterable: true
159       },
160       {
161         name: $localize`Container Image ID`,
162         prop: 'container_image_id',
163         flexGrow: 2,
164         filterable: true,
165         cellTransformation: CellTemplate.truncate,
166         customTemplateConfig: {
167           length: 12
168         }
169       },
170       {
171         name: $localize`Version`,
172         prop: 'version',
173         flexGrow: 1,
174         filterable: true
175       },
176       {
177         name: $localize`Status`,
178         prop: 'status_desc',
179         flexGrow: 1,
180         filterable: true,
181         cellTemplate: this.statusTpl
182       },
183       {
184         name: $localize`Last Refreshed`,
185         prop: 'last_refresh',
186         pipe: this.relativeDatePipe,
187         flexGrow: 1
188       },
189       {
190         name: $localize`Daemon Events`,
191         prop: 'events',
192         flexGrow: 5,
193         cellTemplate: this.listTpl
194       }
195     ];
196
197     this.serviceColumns = [
198       {
199         name: $localize`Service Name`,
200         prop: 'service_name',
201         flexGrow: 2,
202         filterable: true
203       },
204       {
205         name: $localize`Service Type`,
206         prop: 'service_type',
207         flexGrow: 1,
208         filterable: true
209       },
210       {
211         name: $localize`Service Events`,
212         prop: 'events',
213         flexGrow: 5,
214         cellTemplate: this.listTpl
215       }
216     ];
217
218     this.orchService.status().subscribe((data: { available: boolean }) => {
219       this.hasOrchestrator = data.available;
220       this.showDocPanel = !data.available;
221     });
222
223     this.columns = this.columns.filter((col: any) => {
224       return !this.hiddenColumns.includes(col.prop);
225     });
226   }
227
228   ngOnChanges() {
229     if (!_.isUndefined(this.daemonsTable)) {
230       this.daemonsTable.reloadData();
231     }
232   }
233
234   ngAfterViewInit() {
235     this.daemonsTableTplsSub = this.daemonsTableTpls.changes.subscribe(
236       (tableRefs: QueryList<TableComponent>) => {
237         this.daemonsTable = tableRefs.first;
238       }
239     );
240   }
241
242   ngOnDestroy() {
243     if (this.daemonsTableTplsSub) {
244       this.daemonsTableTplsSub.unsubscribe();
245     }
246     if (this.serviceSub) {
247       this.serviceSub.unsubscribe();
248     }
249   }
250
251   getStatusClass(row: Daemon): string {
252     return _.get(
253       {
254         '-1': 'badge-danger',
255         '0': 'badge-warning',
256         '1': 'badge-success'
257       },
258       row.status,
259       'badge-dark'
260     );
261   }
262
263   getDaemons(context: CdTableFetchDataContext) {
264     let observable: Observable<Daemon[]>;
265     if (this.hostname) {
266       observable = this.hostService.getDaemons(this.hostname);
267     } else if (this.serviceName) {
268       observable = this.cephServiceService.getDaemons(this.serviceName);
269     } else {
270       this.daemons = [];
271       return;
272     }
273     observable.subscribe(
274       (daemons: Daemon[]) => {
275         this.daemons = daemons;
276         this.sortDaemonEvents();
277       },
278       () => {
279         this.daemons = [];
280         context.error();
281       }
282     );
283   }
284
285   sortDaemonEvents() {
286     this.daemons.forEach((daemon: any) => {
287       daemon.events?.sort((event1: any, event2: any) => {
288         return new Date(event2.created).getTime() - new Date(event1.created).getTime();
289       });
290     });
291   }
292   getServices(context: CdTableFetchDataContext) {
293     this.serviceSub = this.cephServiceService.list(this.serviceName).subscribe(
294       (services: CephServiceSpec[]) => {
295         this.services = services;
296       },
297       () => {
298         this.services = [];
299         context.error();
300       }
301     );
302   }
303
304   trackByFn(_index: any, item: any) {
305     return item.created;
306   }
307
308   updateSelection(selection: CdTableSelection) {
309     this.selection = selection;
310   }
311
312   daemonAction(actionType: string) {
313     this.daemonService
314       .action(this.selection.first()?.daemon_name, actionType)
315       .pipe(take(1))
316       .subscribe({
317         next: (resp) => {
318           this.notificationService.show(
319             NotificationType.success,
320             `Daemon ${actionType} scheduled`,
321             resp.body.toString()
322           );
323         },
324         error: (resp) => {
325           this.notificationService.show(
326             NotificationType.error,
327             'Daemon action failed',
328             resp.body.toString()
329           );
330         }
331       });
332   }
333
334   actionDisabled(actionType: string) {
335     if (this.selection?.hasSelection) {
336       const daemon = this.selection.selected[0];
337       if (daemon.daemon_type === 'mon' || daemon.daemon_type === 'mgr') {
338         return true; // don't allow actions on mon and mgr, dashboard requires them.
339       }
340       switch (actionType) {
341         case 'start':
342           if (daemon.status_desc === 'running') {
343             return true;
344           }
345           break;
346         case 'stop':
347           if (daemon.status_desc === 'stopped') {
348             return true;
349           }
350           break;
351       }
352       return false;
353     }
354     return true; // if no selection then disable everything
355   }
356 }