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