]> git.proxmox.com Git - ceph.git/blobdiff - 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
index d6d928e19500c8f15bf53ef347c6df454b360254..882a2fe3c403637aafe50ca0d9faf4a7ca481846 100644 (file)
@@ -2,66 +2,65 @@ import { Component, OnInit } from '@angular/core';
 import { ValidatorFn, Validators } from '@angular/forms';
 import { Router } from '@angular/router';
 
-import { I18n } from '@ngx-translate/i18n-polyfill';
-import * as _ from 'lodash';
-import { BlockUI, NgBlockUI } from 'ng-block-ui';
+import _ from 'lodash';
 import { forkJoin as observableForkJoin } from 'rxjs';
 
-import { MgrModuleService } from '../../../shared/api/mgr-module.service';
-import { TelemetryService } from '../../../shared/api/telemetry.service';
-import { NotificationType } from '../../../shared/enum/notification-type.enum';
-import { CdFormBuilder } from '../../../shared/forms/cd-form-builder';
-import { CdFormGroup } from '../../../shared/forms/cd-form-group';
-import { CdValidators } from '../../../shared/forms/cd-validators';
-import { NotificationService } from '../../../shared/services/notification.service';
-import { TelemetryNotificationService } from '../../../shared/services/telemetry-notification.service';
-import { TextToDownloadService } from '../../../shared/services/text-to-download.service';
+import { MgrModuleService } from '~/app/shared/api/mgr-module.service';
+import { TelemetryService } from '~/app/shared/api/telemetry.service';
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { NotificationType } from '~/app/shared/enum/notification-type.enum';
+import { CdForm } from '~/app/shared/forms/cd-form';
+import { CdFormBuilder } from '~/app/shared/forms/cd-form-builder';
+import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
+import { NotificationService } from '~/app/shared/services/notification.service';
+import { TelemetryNotificationService } from '~/app/shared/services/telemetry-notification.service';
 
 @Component({
   selector: 'cd-telemetry',
   templateUrl: './telemetry.component.html',
   styleUrls: ['./telemetry.component.scss']
 })
-export class TelemetryComponent implements OnInit {
-  @BlockUI()
-  blockUI: NgBlockUI;
-
-  error = false;
+export class TelemetryComponent extends CdForm implements OnInit {
   configForm: CdFormGroup;
   licenseAgrmt = false;
-  loading = false;
   moduleEnabled: boolean;
   options: Object = {};
+  newConfig: Object = {};
+  configResp: object = {};
   previewForm: CdFormGroup;
   requiredFields = [
     'channel_basic',
     'channel_crash',
     'channel_device',
     'channel_ident',
+    'channel_perf',
     'interval',
     'proxy',
     'contact',
-    'description'
+    'description',
+    'organization'
   ];
+  contactInfofields = ['contact', 'description', 'organization'];
   report: object = undefined;
   reportId: number = undefined;
   sendToUrl = '';
   sendToDeviceUrl = '';
   step = 1;
+  showContactInfo: boolean;
 
   constructor(
+    public actionLabels: ActionLabelsI18n,
     private formBuilder: CdFormBuilder,
     private mgrModuleService: MgrModuleService,
     private notificationService: NotificationService,
     private router: Router,
     private telemetryService: TelemetryService,
-    private i18n: I18n,
-    private textToDownloadService: TextToDownloadService,
     private telemetryNotificationService: TelemetryNotificationService
-  ) {}
+  ) {
+    super();
+  }
 
   ngOnInit() {
-    this.loading = true;
     const observables = [
       this.mgrModuleService.getOptions('telemetry'),
       this.mgrModuleService.getConfig('telemetry')
@@ -72,14 +71,15 @@ export class TelemetryComponent implements OnInit {
         this.moduleEnabled = configResp['enabled'];
         this.sendToUrl = configResp['url'];
         this.sendToDeviceUrl = configResp['device_url'];
+        this.showContactInfo = configResp['channel_ident'];
         this.options = _.pick(resp[0], this.requiredFields);
-        const configs = _.pick(configResp, this.requiredFields);
+        this.configResp = _.pick(configResp, this.requiredFields);
         this.createConfigForm();
-        this.configForm.setValue(configs);
-        this.loading = false;
+        this.configForm.setValue(this.configResp);
+        this.loadingReady();
       },
       (_error) => {
-        this.error = true;
+        this.loadingError();
       }
     );
   }
@@ -92,9 +92,71 @@ export class TelemetryComponent implements OnInit {
     this.configForm = this.formBuilder.group(controlsConfig);
   }
 
+  private replacer(key: string, value: any) {
+    // Display the arrays of keys 'ranges' and 'values' horizontally as they take up too much space
+    // and Stringify displays it in vertical by default.
+    if ((key === 'ranges' || key === 'values') && Array.isArray(value)) {
+      const elements = [];
+      for (let i = 0; i < value.length; i++) {
+        elements.push(JSON.stringify(value[i]));
+      }
+      return elements;
+    }
+    // Else, just return the value as is, without any formatting.
+    return value;
+  }
+
+  replacerTest(report: object) {
+    return JSON.stringify(report, this.replacer, 2);
+  }
+
+  private formatReport() {
+    let copy = {};
+    copy = JSON.parse(JSON.stringify(this.report));
+    const perf_keys = [
+      'perf_counters',
+      'stats_per_pool',
+      'stats_per_pg',
+      'io_rate',
+      'osd_perf_histograms',
+      'mempool',
+      'heap_stats',
+      'rocksdb_stats'
+    ];
+    for (let i = 0; i < perf_keys.length; i++) {
+      const key = perf_keys[i];
+      if (key in copy['report']) {
+        delete copy['report'][key];
+      }
+    }
+    return JSON.stringify(copy, null, 2);
+  }
+
+  formatReportTest(report: object) {
+    let copy = {};
+    copy = JSON.parse(JSON.stringify(report));
+    const perf_keys = [
+      'perf_counters',
+      'stats_per_pool',
+      'stats_per_pg',
+      'io_rate',
+      'osd_perf_histograms',
+      'mempool',
+      'heap_stats',
+      'rocksdb_stats'
+    ];
+    for (let i = 0; i < perf_keys.length; i++) {
+      const key = perf_keys[i];
+      if (key in copy) {
+        delete copy[key];
+      }
+    }
+    return JSON.stringify(copy, null, 2);
+  }
+
   private createPreviewForm() {
     const controls = {
-      report: JSON.stringify(this.report, null, 2),
+      report: this.formatReport(),
       reportId: this.reportId,
       licenseAgrmt: [this.licenseAgrmt, Validators.requiredTrue]
     };
@@ -105,14 +167,7 @@ export class TelemetryComponent implements OnInit {
     const result = [];
     switch (option.type) {
       case 'int':
-        result.push(CdValidators.number());
         result.push(Validators.required);
-        if (_.isNumber(option.min)) {
-          result.push(Validators.min(option.min));
-        }
-        if (_.isNumber(option.max)) {
-          result.push(Validators.max(option.max));
-        }
         break;
       case 'str':
         if (_.isNumber(option.min)) {
@@ -126,53 +181,64 @@ export class TelemetryComponent implements OnInit {
     return result;
   }
 
+  private updateReportFromConfig(updatedConfig: Object = {}) {
+    // update channels
+    const availableChannels: string[] = this.report['report']['channels_available'];
+    const updatedChannels = [];
+    for (const channel of availableChannels) {
+      const key = `channel_${channel}`;
+      if (updatedConfig[key]) {
+        updatedChannels.push(channel);
+      }
+    }
+    this.report['report']['channels'] = updatedChannels;
+    // update contactInfo
+    for (const contactInfofield of this.contactInfofields) {
+      this.report['report'][contactInfofield] = updatedConfig[contactInfofield];
+    }
+  }
+
   private getReport() {
-    this.loading = true;
+    this.loadingStart();
+
     this.telemetryService.getReport().subscribe(
       (resp: object) => {
         this.report = resp;
         this.reportId = resp['report']['report_id'];
+        this.updateReportFromConfig(this.newConfig);
         this.createPreviewForm();
-        this.loading = false;
+        this.loadingReady();
         this.step++;
       },
       (_error) => {
-        this.error = true;
+        this.loadingError();
       }
     );
   }
 
-  updateConfig() {
-    const config = {};
-    _.forEach(Object.values(this.options), (option) => {
+  toggleIdent() {
+    this.showContactInfo = !this.showContactInfo;
+  }
+
+  buildReport() {
+    this.newConfig = {};
+    for (const option of Object.values(this.options)) {
       const control = this.configForm.get(option.name);
-      // Append the option only if the value has been modified.
-      if (control.dirty && control.valid) {
-        config[option.name] = control.value;
-      }
-    });
-    this.mgrModuleService.updateConfig('telemetry', config).subscribe(
-      () => {
-        this.disableModule(
-          this.i18n(
-            `Your settings have been applied successfully. \
-Due to privacy/legal reasons the Telemetry module is now disabled until you \
-complete the next step and accept the license.`
-          ),
-          () => {
-            this.getReport();
-          }
-        );
-      },
-      () => {
-        // Reset the 'Submit' button.
+      // Append the option only if they are valid
+      if (control.valid) {
+        this.newConfig[option.name] = control.value;
+      } else {
         this.configForm.setErrors({ cdSubmitButton: true });
+        return;
       }
-    );
-  }
-
-  download(report: object, fileName: string) {
-    this.textToDownloadService.download(JSON.stringify(report, null, 2), fileName);
+    }
+    // reset contact info field  if ident channel is off
+    if (!this.newConfig['channel_ident']) {
+      for (const contactInfofield of this.contactInfofields) {
+        this.newConfig[contactInfofield] = '';
+      }
+    }
+    this.getReport();
   }
 
   disableModule(message: string = null, followUpFunc: Function = null) {
@@ -190,25 +256,52 @@ complete the next step and accept the license.`
   }
 
   next() {
-    if (this.configForm.pristine) {
-      this.getReport();
-    } else {
-      this.updateConfig();
-    }
+    this.buildReport();
   }
 
   back() {
     this.step--;
   }
 
-  onSubmit() {
-    this.telemetryService.enable().subscribe(() => {
-      this.telemetryNotificationService.setVisibility(false);
-      this.notificationService.show(
-        NotificationType.success,
-        this.i18n('The Telemetry module has been configured and activated successfully.')
-      );
-      this.router.navigate(['']);
+  getChangedConfig() {
+    const updatedConfig = {};
+    _.forEach(this.requiredFields, (configField) => {
+      if (!_.isEqual(this.configResp[configField], this.newConfig[configField])) {
+        updatedConfig[configField] = this.newConfig[configField];
+      }
     });
+    return updatedConfig;
+  }
+
+  onSubmit() {
+    const updatedConfig = this.getChangedConfig();
+    const observables = [
+      this.telemetryService.enable(),
+      this.mgrModuleService.updateConfig('telemetry', updatedConfig)
+    ];
+
+    observableForkJoin(observables).subscribe(
+      () => {
+        this.telemetryNotificationService.setVisibility(false);
+        this.notificationService.show(
+          NotificationType.success,
+          $localize`The Telemetry module has been configured and activated successfully.`
+        );
+      },
+      () => {
+        this.telemetryNotificationService.setVisibility(false);
+        this.notificationService.show(
+          NotificationType.error,
+          $localize`An Error occurred while updating the Telemetry module configuration.\
+             Please Try again`
+        );
+        // Reset the 'Update' button.
+        this.previewForm.setErrors({ cdSubmitButton: true });
+      },
+      () => {
+        this.newConfig = {};
+        this.router.navigate(['']);
+      }
+    );
   }
 }