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