]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.ts
import quincy beta 17.1.0
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / cluster / telemetry / telemetry.component.ts
1 import { Component, OnInit } from '@angular/core';
2 import { ValidatorFn, Validators } from '@angular/forms';
3 import { Router } from '@angular/router';
4
5 import _ from 'lodash';
6 import { forkJoin as observableForkJoin } from 'rxjs';
7
8 import { MgrModuleService } from '~/app/shared/api/mgr-module.service';
9 import { TelemetryService } from '~/app/shared/api/telemetry.service';
10 import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
11 import { NotificationType } from '~/app/shared/enum/notification-type.enum';
12 import { CdForm } from '~/app/shared/forms/cd-form';
13 import { CdFormBuilder } from '~/app/shared/forms/cd-form-builder';
14 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
15 import { NotificationService } from '~/app/shared/services/notification.service';
16 import { TelemetryNotificationService } from '~/app/shared/services/telemetry-notification.service';
17
18 @Component({
19 selector: 'cd-telemetry',
20 templateUrl: './telemetry.component.html',
21 styleUrls: ['./telemetry.component.scss']
22 })
23 export class TelemetryComponent extends CdForm implements OnInit {
24 configForm: CdFormGroup;
25 licenseAgrmt = false;
26 moduleEnabled: boolean;
27 options: Object = {};
28 newConfig: Object = {};
29 configResp: object = {};
30 previewForm: CdFormGroup;
31 requiredFields = [
32 'channel_basic',
33 'channel_crash',
34 'channel_device',
35 'channel_ident',
36 'channel_perf',
37 'interval',
38 'proxy',
39 'contact',
40 'description',
41 'organization'
42 ];
43 contactInfofields = ['contact', 'description', 'organization'];
44 report: object = undefined;
45 reportId: number = undefined;
46 sendToUrl = '';
47 sendToDeviceUrl = '';
48 step = 1;
49 showContactInfo: boolean;
50
51 constructor(
52 public actionLabels: ActionLabelsI18n,
53 private formBuilder: CdFormBuilder,
54 private mgrModuleService: MgrModuleService,
55 private notificationService: NotificationService,
56 private router: Router,
57 private telemetryService: TelemetryService,
58 private telemetryNotificationService: TelemetryNotificationService
59 ) {
60 super();
61 }
62
63 ngOnInit() {
64 const observables = [
65 this.mgrModuleService.getOptions('telemetry'),
66 this.mgrModuleService.getConfig('telemetry')
67 ];
68 observableForkJoin(observables).subscribe(
69 (resp: object) => {
70 const configResp = resp[1];
71 this.moduleEnabled = configResp['enabled'];
72 this.sendToUrl = configResp['url'];
73 this.sendToDeviceUrl = configResp['device_url'];
74 this.showContactInfo = configResp['channel_ident'];
75 this.options = _.pick(resp[0], this.requiredFields);
76 this.configResp = _.pick(configResp, this.requiredFields);
77 this.createConfigForm();
78 this.configForm.setValue(this.configResp);
79 this.loadingReady();
80 },
81 (_error) => {
82 this.loadingError();
83 }
84 );
85 }
86
87 private createConfigForm() {
88 const controlsConfig = {};
89 _.forEach(Object.values(this.options), (option) => {
90 controlsConfig[option.name] = [option.default_value, this.getValidators(option)];
91 });
92 this.configForm = this.formBuilder.group(controlsConfig);
93 }
94
95 private replacer(key: string, value: any) {
96 // Display the arrays of keys 'ranges' and 'values' horizontally as they take up too much space
97 // and Stringify displays it in vertical by default.
98 if ((key === 'ranges' || key === 'values') && Array.isArray(value)) {
99 const elements = [];
100 for (let i = 0; i < value.length; i++) {
101 elements.push(JSON.stringify(value[i]));
102 }
103 return elements;
104 }
105 // Else, just return the value as is, without any formatting.
106 return value;
107 }
108
109 replacerTest(report: object) {
110 return JSON.stringify(report, this.replacer, 2);
111 }
112
113 private formatReport() {
114 let copy = {};
115 copy = JSON.parse(JSON.stringify(this.report));
116 const perf_keys = [
117 'perf_counters',
118 'stats_per_pool',
119 'stats_per_pg',
120 'io_rate',
121 'osd_perf_histograms',
122 'mempool',
123 'heap_stats',
124 'rocksdb_stats'
125 ];
126 for (let i = 0; i < perf_keys.length; i++) {
127 const key = perf_keys[i];
128 if (key in copy['report']) {
129 delete copy['report'][key];
130 }
131 }
132 return JSON.stringify(copy, null, 2);
133 }
134
135 formatReportTest(report: object) {
136 let copy = {};
137 copy = JSON.parse(JSON.stringify(report));
138 const perf_keys = [
139 'perf_counters',
140 'stats_per_pool',
141 'stats_per_pg',
142 'io_rate',
143 'osd_perf_histograms',
144 'mempool',
145 'heap_stats',
146 'rocksdb_stats'
147 ];
148 for (let i = 0; i < perf_keys.length; i++) {
149 const key = perf_keys[i];
150 if (key in copy) {
151 delete copy[key];
152 }
153 }
154 return JSON.stringify(copy, null, 2);
155 }
156
157 private createPreviewForm() {
158 const controls = {
159 report: this.formatReport(),
160 reportId: this.reportId,
161 licenseAgrmt: [this.licenseAgrmt, Validators.requiredTrue]
162 };
163 this.previewForm = this.formBuilder.group(controls);
164 }
165
166 private getValidators(option: any): ValidatorFn[] {
167 const result = [];
168 switch (option.type) {
169 case 'int':
170 result.push(Validators.required);
171 break;
172 case 'str':
173 if (_.isNumber(option.min)) {
174 result.push(Validators.minLength(option.min));
175 }
176 if (_.isNumber(option.max)) {
177 result.push(Validators.maxLength(option.max));
178 }
179 break;
180 }
181 return result;
182 }
183
184 private updateReportFromConfig(updatedConfig: Object = {}) {
185 // update channels
186 const availableChannels: string[] = this.report['report']['channels_available'];
187 const updatedChannels = [];
188 for (const channel of availableChannels) {
189 const key = `channel_${channel}`;
190 if (updatedConfig[key]) {
191 updatedChannels.push(channel);
192 }
193 }
194 this.report['report']['channels'] = updatedChannels;
195 // update contactInfo
196 for (const contactInfofield of this.contactInfofields) {
197 this.report['report'][contactInfofield] = updatedConfig[contactInfofield];
198 }
199 }
200
201 private getReport() {
202 this.loadingStart();
203
204 this.telemetryService.getReport().subscribe(
205 (resp: object) => {
206 this.report = resp;
207 this.reportId = resp['report']['report_id'];
208 this.updateReportFromConfig(this.newConfig);
209 this.createPreviewForm();
210 this.loadingReady();
211 this.step++;
212 },
213 (_error) => {
214 this.loadingError();
215 }
216 );
217 }
218
219 toggleIdent() {
220 this.showContactInfo = !this.showContactInfo;
221 }
222
223 buildReport() {
224 this.newConfig = {};
225 for (const option of Object.values(this.options)) {
226 const control = this.configForm.get(option.name);
227 // Append the option only if they are valid
228 if (control.valid) {
229 this.newConfig[option.name] = control.value;
230 } else {
231 this.configForm.setErrors({ cdSubmitButton: true });
232 return;
233 }
234 }
235 // reset contact info field if ident channel is off
236 if (!this.newConfig['channel_ident']) {
237 for (const contactInfofield of this.contactInfofields) {
238 this.newConfig[contactInfofield] = '';
239 }
240 }
241 this.getReport();
242 }
243
244 disableModule(message: string = null, followUpFunc: Function = null) {
245 this.telemetryService.enable(false).subscribe(() => {
246 this.telemetryNotificationService.setVisibility(true);
247 if (message) {
248 this.notificationService.show(NotificationType.success, message);
249 }
250 if (followUpFunc) {
251 followUpFunc();
252 } else {
253 this.router.navigate(['']);
254 }
255 });
256 }
257
258 next() {
259 this.buildReport();
260 }
261
262 back() {
263 this.step--;
264 }
265
266 getChangedConfig() {
267 const updatedConfig = {};
268 _.forEach(this.requiredFields, (configField) => {
269 if (!_.isEqual(this.configResp[configField], this.newConfig[configField])) {
270 updatedConfig[configField] = this.newConfig[configField];
271 }
272 });
273 return updatedConfig;
274 }
275
276 onSubmit() {
277 const updatedConfig = this.getChangedConfig();
278 const observables = [
279 this.telemetryService.enable(),
280 this.mgrModuleService.updateConfig('telemetry', updatedConfig)
281 ];
282
283 observableForkJoin(observables).subscribe(
284 () => {
285 this.telemetryNotificationService.setVisibility(false);
286 this.notificationService.show(
287 NotificationType.success,
288 $localize`The Telemetry module has been configured and activated successfully.`
289 );
290 },
291 () => {
292 this.telemetryNotificationService.setVisibility(false);
293 this.notificationService.show(
294 NotificationType.error,
295 $localize`An Error occurred while updating the Telemetry module configuration.\
296 Please Try again`
297 );
298 // Reset the 'Update' button.
299 this.previewForm.setErrors({ cdSubmitButton: true });
300 },
301 () => {
302 this.newConfig = {};
303 this.router.navigate(['']);
304 }
305 );
306 }
307 }