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