]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Created a Download and Copy-to-Clipboard option for the logs 37193/head
authorNizamudeen A <nia@redhat.com>
Wed, 16 Sep 2020 13:54:31 +0000 (19:24 +0530)
committerNizamudeen A <nia@redhat.com>
Thu, 22 Oct 2020 11:09:37 +0000 (16:39 +0530)
Created Download Button Component.
Added a Download and Copy-to-Clipboard button for the Cluster and Audit Logs in the Log section. Also added a flag in the current Copy2ClipboardButton directive to indicate the incoming string is actually a log.

Fixes: https://tracker.ceph.com/issues/47498
Signed-off-by: Nizamudeen A <nia@redhat.com>
14 files changed:
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/logs/logs.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/logs/logs.component.scss
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/logs/logs.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/download-button/download-button.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/download-button/download-button.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/download-button/download-button.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/download-button/download-button.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/directives/copy2clipboard-button.directive.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/enum/icons.enum.ts

index eb73ccc49aba51b746197f35f1e5090e31169d15..1a8198be99dd444952461480d5ec87401f745fd1 100644 (file)
@@ -6,6 +6,7 @@ import { RouterModule } from '@angular/router';
 import { TreeModule } from '@circlon/angular-tree-component';
 import {
   NgbDatepickerModule,
+  NgbDropdownModule,
   NgbNavModule,
   NgbPopoverModule,
   NgbTimepickerModule,
@@ -69,7 +70,8 @@ import { TelemetryComponent } from './telemetry/telemetry.component';
     NgBootstrapFormValidationModule,
     CephSharedModule,
     NgbDatepickerModule,
-    NgbPopoverModule
+    NgbPopoverModule,
+    NgbDropdownModule
   ],
   declarations: [
     HostsComponent,
index 1f6aec722c95e03befad063e7543d33f819622f8..3b9caabbe733036f51e032218aba74e608ed3170 100644 (file)
       <ng-template ngbNavContent>
         <div class="card bg-light mb-3"
              *ngIf="clog">
+          <div class="btn-group"
+               role="group"
+               (click)="logToText(clog)"
+               *ngIf="clog.length">
+            <cd-download-button [objectItem]="clog"
+                                [textItem]="logText"
+                                fileName="cluster_log">
+            </cd-download-button>
+            <button type="button"
+                    class="btn btn-light"
+                    [cdCopy2ClipboardButton]="logText"
+                    [byId]="false"></button>
+          </div>
           <div class="card-body">
             <p *ngFor="let line of clog">
               <span class="timestamp">{{ line.stamp | cdDate }}</span>
       <ng-template ngbNavContent>
         <div class="card bg-light mb-3"
              *ngIf="audit_log">
+          <div class="btn-group"
+               role="group"
+               (click)="logToText(audit_log)"
+               *ngIf="audit_log.length">
+            <cd-download-button [objectItem]="audit_log"
+                                [textItem]="logText"
+                                fileName="audit_log">
+            </cd-download-button>
+            <button type="button"
+                    class="btn btn-light"
+                    [cdCopy2ClipboardButton]="logText"
+                    [byId]="false"></button>
+          </div>
           <div class="card-body">
             <p *ngFor="let line of audit_log">
               <span class="timestamp">{{ line.stamp | cdDate }}</span>
index 7638196c49dc9381d0a9f5ec43ebee8467dfc711..3026c27a07ca46e064af93ca77f71faa1e969b6a 100644 (file)
@@ -5,6 +5,12 @@ p {
 }
 
 .card {
+  .btn-group {
+    margin-top: -45px;
+    position: absolute;
+    right: 0;
+  }
+
   div p {
     display: flex;
 
index a34e455e7e7b353b100111c21aaad447162e84e1..c298af22ab4eba2a9199b96a9152a5d0779ef534 100644 (file)
@@ -16,6 +16,7 @@ export class LogsComponent implements OnInit, OnDestroy {
   clog: Array<any>;
   audit_log: Array<any>;
   icons = Icons;
+  logText: string;
 
   interval: number;
   priorities: Array<{ name: string; value: string }> = [
@@ -136,4 +137,18 @@ export class LogsComponent implements OnInit, OnDestroy {
 
     return false;
   }
+
+  logToText(log: object) {
+    this.logText = '';
+    for (const line of Object.keys(log)) {
+      this.logText =
+        this.logText +
+        this.datePipe.transform(log[line].stamp, 'medium') +
+        '\t' +
+        log[line].priority +
+        '\t' +
+        log[line].message +
+        '\n';
+    }
+  }
 }
index 63a73935595b8aba57cdeeff152df3345c7865f5..a128c6f2510f757416e00003f1a3428bf60bb334 100644 (file)
             </div>
             <div class="form-group row">
               <div class="cd-col-form-offset">
-                <button type="button"
-                        class="btn btn-light mr-2"
-                        title="Download"
-                        (click)="download(report, 'telemetry_report.json')"
-                        i18n-title>
-                  <i class="fa fa-download"></i>
-                </button>
-                <button type="button"
-                        class="btn btn-light"
-                        cdCopy2ClipboardButton="report">
-                </button>
+                <div class="btn-group"
+                     role="group">
+                  <cd-download-button [objectItem]="report"
+                                      fileName="telemetry_report">
+                  </cd-download-button>
+                  <button type="button"
+                          class="btn btn-light"
+                          cdCopy2ClipboardButton="report">
+                  </button>
+                </div>
               </div>
             </div>
 
index a655a635aba6d71f361788504bcbc26824a84752..901f1774307a63691c34ee3425ecd9a15cc464fa 100644 (file)
@@ -4,15 +4,14 @@ import { ReactiveFormsModule } from '@angular/forms';
 import { Router } from '@angular/router';
 import { RouterTestingModule } from '@angular/router/testing';
 
+import { DownloadButtonComponent } from 'app/shared/components/download-button/download-button.component';
 import _ from 'lodash';
 import { ToastrModule } from 'ngx-toastr';
 import { of as observableOf } from 'rxjs';
 
 import { configureTestBed } from '../../../../testing/unit-test-helper';
 import { MgrModuleService } from '../../../shared/api/mgr-module.service';
-import { TelemetryService } from '../../../shared/api/telemetry.service';
 import { LoadingPanelComponent } from '../../../shared/components/loading-panel/loading-panel.component';
-import { TextToDownloadService } from '../../../shared/services/text-to-download.service';
 import { SharedModule } from '../../../shared/shared.module';
 import { TelemetryComponent } from './telemetry.component';
 
@@ -20,7 +19,6 @@ describe('TelemetryComponent', () => {
   let component: TelemetryComponent;
   let fixture: ComponentFixture<TelemetryComponent>;
   let mgrModuleService: MgrModuleService;
-  let telemetryService: TelemetryService;
   let options: any;
   let configs: any;
   let httpTesting: HttpTestingController;
@@ -58,7 +56,7 @@ describe('TelemetryComponent', () => {
         ToastrModule.forRoot()
       ]
     },
-    [LoadingPanelComponent]
+    [LoadingPanelComponent, DownloadButtonComponent]
   );
 
   describe('configForm', () => {
@@ -135,16 +133,10 @@ describe('TelemetryComponent', () => {
   });
 
   describe('previewForm', () => {
-    const reportText = {
-      testA: 'testA',
-      testB: 'testB'
-    };
-
     beforeEach(() => {
       fixture = TestBed.createComponent(TelemetryComponent);
       component = fixture.componentInstance;
       fixture.detectChanges();
-      telemetryService = TestBed.inject(TelemetryService);
       httpTesting = TestBed.inject(HttpTestingController);
       router = TestBed.inject(Router);
       spyOn(router, 'navigate');
@@ -154,16 +146,6 @@ describe('TelemetryComponent', () => {
       expect(component).toBeTruthy();
     });
 
-    it('should call TextToDownloadService download function', () => {
-      spyOn(telemetryService, 'getReport').and.returnValue(observableOf(reportText));
-      component.ngOnInit();
-
-      const downloadSpy = spyOn(TestBed.inject(TextToDownloadService), 'download');
-      const filename = 'reportText.json';
-      component.download(reportText, filename);
-      expect(downloadSpy).toHaveBeenCalledWith(JSON.stringify(reportText, null, 2), filename);
-    });
-
     it('should submit', () => {
       component.onSubmit();
       const req = httpTesting.expectOne('api/telemetry');
index d2c4c894459a09a17201d09e1e8763ceb8b9197a..ccb695d864aca6dcadd314e939a5cf0ffad6a5ae 100644 (file)
@@ -14,7 +14,6 @@ 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';
 
 @Component({
   selector: 'cd-telemetry',
@@ -49,7 +48,6 @@ export class TelemetryComponent extends CdForm implements OnInit {
     private notificationService: NotificationService,
     private router: Router,
     private telemetryService: TelemetryService,
-    private textToDownloadService: TextToDownloadService,
     private telemetryNotificationService: TelemetryNotificationService
   ) {
     super();
@@ -164,10 +162,6 @@ export class TelemetryComponent extends CdForm implements OnInit {
     );
   }
 
-  download(report: object, fileName: string) {
-    this.textToDownloadService.download(JSON.stringify(report, null, 2), fileName);
-  }
-
   disableModule(message: string = null, followUpFunc: Function = null) {
     this.telemetryService.enable(false).subscribe(() => {
       this.telemetryNotificationService.setVisibility(true);
index 95cc5ae0e3908cb47708bdb38e3fc3b8b6360685..1cec301e5337d8452227b24ed5a4c44082e84abb 100644 (file)
@@ -26,6 +26,7 @@ import { ConfirmationModalComponent } from './confirmation-modal/confirmation-mo
 import { CriticalConfirmationModalComponent } from './critical-confirmation-modal/critical-confirmation-modal.component';
 import { DateTimePickerComponent } from './date-time-picker/date-time-picker.component';
 import { DocComponent } from './doc/doc.component';
+import { DownloadButtonComponent } from './download-button/download-button.component';
 import { FormModalComponent } from './form-modal/form-modal.component';
 import { GrafanaComponent } from './grafana/grafana.component';
 import { HelperComponent } from './helper/helper.component';
@@ -87,7 +88,8 @@ import { UsageBarComponent } from './usage-bar/usage-bar.component';
     TelemetryNotificationComponent,
     OrchestratorDocPanelComponent,
     DateTimePickerComponent,
-    DocComponent
+    DocComponent,
+    DownloadButtonComponent
   ],
   providers: [],
   exports: [
@@ -110,7 +112,8 @@ import { UsageBarComponent } from './usage-bar/usage-bar.component';
     TelemetryNotificationComponent,
     OrchestratorDocPanelComponent,
     DateTimePickerComponent,
-    DocComponent
+    DocComponent,
+    DownloadButtonComponent
   ]
 })
 export class ComponentsModule {}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/download-button/download-button.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/download-button/download-button.component.html
new file mode 100644 (file)
index 0000000..a7e4765
--- /dev/null
@@ -0,0 +1,23 @@
+<div ngbDropdown
+     placement="bottom-right">
+  <button type="button"
+          [title]="title"
+          class="btn btn-light dropdown-toggle-split"
+          ngbDropdownToggle>
+    <i [ngClass]="[icons.download]"></i>
+  </button>
+  <div ngbDropdownMenu>
+    <button ngbDropdownItem
+            (click)="download('json')"
+            *ngIf="objectItem">
+      <i [ngClass]="[icons.json]"></i>
+      <span>JSON</span>
+    </button>
+    <button ngbDropdownItem
+            (click)="download()"
+            *ngIf="textItem">
+      <i [ngClass]="[icons.text]"></i>
+      <span>Text</span>
+    </button>
+  </div>
+</div>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/download-button/download-button.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/download-button/download-button.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/download-button/download-button.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/download-button/download-button.component.spec.ts
new file mode 100644 (file)
index 0000000..9814b48
--- /dev/null
@@ -0,0 +1,39 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { configureTestBed } from '../../../../testing/unit-test-helper';
+import { TextToDownloadService } from '../../services/text-to-download.service';
+import { DownloadButtonComponent } from './download-button.component';
+
+describe('DownloadButtonComponent', () => {
+  let component: DownloadButtonComponent;
+  let fixture: ComponentFixture<DownloadButtonComponent>;
+
+  configureTestBed({
+    declarations: [DownloadButtonComponent],
+    providers: [TextToDownloadService]
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(DownloadButtonComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  it('should call download function', () => {
+    component.objectItem = {
+      testA: 'testA',
+      testB: 'testB'
+    };
+    const downloadSpy = spyOn(TestBed.inject(TextToDownloadService), 'download');
+    component.fileName = `${'reportText.json'}_${new Date().toLocaleDateString()}`;
+    component.download('json');
+    expect(downloadSpy).toHaveBeenCalledWith(
+      JSON.stringify(component.objectItem, null, 2),
+      `${component.fileName}.json`
+    );
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/download-button/download-button.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/download-button/download-button.component.ts
new file mode 100644 (file)
index 0000000..4915186
--- /dev/null
@@ -0,0 +1,31 @@
+import { Component, Input } from '@angular/core';
+
+import { Icons } from '../../enum/icons.enum';
+import { TextToDownloadService } from '../../services/text-to-download.service';
+
+@Component({
+  selector: 'cd-download-button',
+  templateUrl: './download-button.component.html',
+  styleUrls: ['./download-button.component.scss']
+})
+export class DownloadButtonComponent {
+  @Input() objectItem: object;
+  @Input() textItem: string;
+  @Input() fileName: any;
+  @Input() title = $localize`Download`;
+
+  icons = Icons;
+  constructor(private textToDownloadService: TextToDownloadService) {}
+
+  download(format?: string) {
+    this.fileName = `${this.fileName}_${new Date().toLocaleDateString()}`;
+    if (format === 'json') {
+      this.textToDownloadService.download(
+        JSON.stringify(this.objectItem, null, 2),
+        `${this.fileName}.json`
+      );
+    } else {
+      this.textToDownloadService.download(this.textItem, `${this.fileName}.txt`);
+    }
+  }
+}
index 2a42f19b077341b06e160337c930244b8d65a9b9..218af07eedd90b3868e500ace5235198763feb1e 100644 (file)
@@ -9,6 +9,8 @@ import { ToastrService } from 'ngx-toastr';
 export class Copy2ClipboardButtonDirective implements OnInit {
   @Input()
   private cdCopy2ClipboardButton: string;
+  @Input()
+  byId = true;
 
   constructor(
     private elementRef: ElementRef,
@@ -33,7 +35,7 @@ export class Copy2ClipboardButtonDirective implements OnInit {
   onClick() {
     try {
       const browser = detect();
-      const text = this.getText();
+      const text = this.byId ? this.getText() : this.cdCopy2ClipboardButton;
       const toastrFn = () => {
         this.toastr.success('Copied text to the clipboard successfully.');
       };
index 3e5d0a213d4fba33d8a09430f8f6e354ac1f4b55..4fe6d98aca9b583cc04e24774a712f1eedd98bb1 100644 (file)
@@ -62,6 +62,8 @@ export enum Icons {
   download = 'fa fa-download', // Download
   upload = 'fa fa-upload', // Upload
   close = 'fa fa-times', // Close
+  json = 'fa fa-file-code-o', // JSON file
+  text = 'fa fa-file-text', // Text file
 
   /* Icons for special effect */
   large = 'fa fa-lg', // icon becomes 33% larger