]> 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
bump version to 18.2.4-pve3
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / cluster / services / service-daemon-list / service-daemon-list.component.ts
1 import { HttpParams } from '@angular/common/http';
2 import {
3 AfterViewInit,
4 ChangeDetectorRef,
5 Component,
6 Input,
7 OnChanges,
8 OnDestroy,
9 OnInit,
10 QueryList,
11 TemplateRef,
12 ViewChild,
13 ViewChildren
14 } from '@angular/core';
15
16 import _ from 'lodash';
17 import { Observable, Subscription } from 'rxjs';
18 import { take } from 'rxjs/operators';
19
20 import { CephServiceService } from '~/app/shared/api/ceph-service.service';
21 import { DaemonService } from '~/app/shared/api/daemon.service';
22 import { HostService } from '~/app/shared/api/host.service';
23 import { OrchestratorService } from '~/app/shared/api/orchestrator.service';
24 import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
25 import { TableComponent } from '~/app/shared/datatable/table/table.component';
26 import { Icons } from '~/app/shared/enum/icons.enum';
27 import { NotificationType } from '~/app/shared/enum/notification-type.enum';
28 import { CdTableAction } from '~/app/shared/models/cd-table-action';
29 import { CdTableColumn } from '~/app/shared/models/cd-table-column';
30 import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
31 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
32 import { Daemon } from '~/app/shared/models/daemon.interface';
33 import { Permissions } from '~/app/shared/models/permissions';
34 import { CephServiceSpec } from '~/app/shared/models/service.interface';
35 import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
36 import { RelativeDatePipe } from '~/app/shared/pipes/relative-date.pipe';
37 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
38 import { NotificationService } from '~/app/shared/services/notification.service';
39
40 @Component({
41 selector: 'cd-service-daemon-list',
42 templateUrl: './service-daemon-list.component.html',
43 styleUrls: ['./service-daemon-list.component.scss']
44 })
45 export class ServiceDaemonListComponent implements OnInit, OnChanges, AfterViewInit, OnDestroy {
46 @ViewChild('statusTpl', { static: true })
47 statusTpl: TemplateRef<any>;
48
49 @ViewChild('listTpl', { static: true })
50 listTpl: TemplateRef<any>;
51
52 @ViewChild('cpuTpl', { static: true })
53 cpuTpl: TemplateRef<any>;
54
55 @ViewChildren('daemonsTable')
56 daemonsTableTpls: QueryList<TemplateRef<TableComponent>>;
57
58 @Input()
59 serviceName?: string;
60
61 @Input()
62 hostname?: string;
63
64 @Input()
65 hiddenColumns: string[] = [];
66
67 @Input()
68 flag?: string;
69
70 total = 100;
71
72 warningThreshold = 0.8;
73
74 errorThreshold = 0.9;
75
76 icons = Icons;
77
78 daemons: Daemon[] = [];
79 services: Array<CephServiceSpec> = [];
80 columns: CdTableColumn[] = [];
81 serviceColumns: CdTableColumn[] = [];
82 tableActions: CdTableAction[];
83 selection = new CdTableSelection();
84 permissions: Permissions;
85
86 hasOrchestrator = false;
87 showDocPanel = false;
88
89 private daemonsTable: TableComponent;
90 private daemonsTableTplsSub: Subscription;
91 private serviceSub: Subscription;
92
93 constructor(
94 private hostService: HostService,
95 private cephServiceService: CephServiceService,
96 private orchService: OrchestratorService,
97 private relativeDatePipe: RelativeDatePipe,
98 private dimlessBinary: DimlessBinaryPipe,
99 public actionLabels: ActionLabelsI18n,
100 private authStorageService: AuthStorageService,
101 private daemonService: DaemonService,
102 private notificationService: NotificationService,
103 private cdRef: ChangeDetectorRef
104 ) {}
105
106 ngOnInit() {
107 this.permissions = this.authStorageService.getPermissions();
108 this.tableActions = [
109 {
110 permission: 'update',
111 icon: Icons.start,
112 click: () => this.daemonAction('start'),
113 name: this.actionLabels.START,
114 disable: () => this.actionDisabled('start')
115 },
116 {
117 permission: 'update',
118 icon: Icons.stop,
119 click: () => this.daemonAction('stop'),
120 name: this.actionLabels.STOP,
121 disable: () => this.actionDisabled('stop')
122 },
123 {
124 permission: 'update',
125 icon: Icons.restart,
126 click: () => this.daemonAction('restart'),
127 name: this.actionLabels.RESTART,
128 disable: () => this.actionDisabled('restart')
129 },
130 {
131 permission: 'update',
132 icon: Icons.deploy,
133 click: () => this.daemonAction('redeploy'),
134 name: this.actionLabels.REDEPLOY,
135 disable: () => this.actionDisabled('redeploy')
136 }
137 ];
138 this.columns = [
139 {
140 name: $localize`Hostname`,
141 prop: 'hostname',
142 flexGrow: 2,
143 filterable: true
144 },
145 {
146 name: $localize`Daemon name`,
147 prop: 'daemon_name',
148 flexGrow: 1,
149 filterable: true
150 },
151 {
152 name: $localize`Version`,
153 prop: 'version',
154 flexGrow: 1,
155 filterable: true
156 },
157 {
158 name: $localize`Status`,
159 prop: 'status_desc',
160 flexGrow: 1,
161 filterable: true,
162 cellTemplate: this.statusTpl
163 },
164 {
165 name: $localize`Last Refreshed`,
166 prop: 'last_refresh',
167 pipe: this.relativeDatePipe,
168 flexGrow: 1
169 },
170 {
171 name: $localize`CPU Usage`,
172 prop: 'cpu_percentage',
173 flexGrow: 1,
174 cellTemplate: this.cpuTpl
175 },
176 {
177 name: $localize`Memory Usage`,
178 prop: 'memory_usage',
179 flexGrow: 1,
180 pipe: this.dimlessBinary,
181 cellClass: 'text-right'
182 },
183 {
184 name: $localize`Daemon Events`,
185 prop: 'events',
186 flexGrow: 2,
187 cellTemplate: this.listTpl
188 }
189 ];
190
191 this.serviceColumns = [
192 {
193 name: $localize`Service Name`,
194 prop: 'service_name',
195 flexGrow: 2,
196 filterable: true
197 },
198 {
199 name: $localize`Service Type`,
200 prop: 'service_type',
201 flexGrow: 1,
202 filterable: true
203 },
204 {
205 name: $localize`Service Events`,
206 prop: 'events',
207 flexGrow: 5,
208 cellTemplate: this.listTpl
209 }
210 ];
211
212 this.orchService.status().subscribe((data: { available: boolean }) => {
213 this.hasOrchestrator = data.available;
214 this.showDocPanel = !data.available;
215 });
216
217 this.columns = this.columns.filter((col: any) => {
218 return !this.hiddenColumns.includes(col.prop);
219 });
220
221 setTimeout(() => {
222 this.cdRef.detectChanges();
223 }, 1000);
224 }
225
226 ngOnChanges() {
227 if (!_.isUndefined(this.daemonsTable)) {
228 this.daemonsTable.reloadData();
229 }
230 }
231
232 ngAfterViewInit() {
233 this.daemonsTableTplsSub = this.daemonsTableTpls.changes.subscribe(
234 (tableRefs: QueryList<TableComponent>) => {
235 this.daemonsTable = tableRefs.first;
236 }
237 );
238 }
239
240 ngOnDestroy() {
241 if (this.daemonsTableTplsSub) {
242 this.daemonsTableTplsSub.unsubscribe();
243 }
244 if (this.serviceSub) {
245 this.serviceSub.unsubscribe();
246 }
247 }
248
249 getStatusClass(row: Daemon): string {
250 return _.get(
251 {
252 '-1': 'badge-danger',
253 '0': 'badge-warning',
254 '1': 'badge-success'
255 },
256 row.status,
257 'badge-dark'
258 );
259 }
260
261 getDaemons(context: CdTableFetchDataContext) {
262 let observable: Observable<Daemon[]>;
263 if (this.hostname) {
264 observable = this.hostService.getDaemons(this.hostname);
265 } else if (this.serviceName) {
266 observable = this.cephServiceService.getDaemons(this.serviceName);
267 } else {
268 this.daemons = [];
269 return;
270 }
271 observable.subscribe(
272 (daemons: Daemon[]) => {
273 this.daemons = daemons;
274 this.sortDaemonEvents();
275 },
276 () => {
277 this.daemons = [];
278 context.error();
279 }
280 );
281 }
282
283 sortDaemonEvents() {
284 this.daemons.forEach((daemon: any) => {
285 daemon.events?.sort((event1: any, event2: any) => {
286 return new Date(event2.created).getTime() - new Date(event1.created).getTime();
287 });
288 });
289 }
290 getServices(context: CdTableFetchDataContext) {
291 this.serviceSub = this.cephServiceService
292 .list(new HttpParams({ fromObject: { limit: -1, offset: 0 } }), this.serviceName)
293 .observable.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 }