From f4c3ac1add397045ec26a309cb7e17298b973bd8 Mon Sep 17 00:00:00 2001 From: Nizamudeen A Date: Wed, 16 Sep 2020 19:24:31 +0530 Subject: [PATCH] mgr/dashboard: Created a Download and Copy-to-Clipboard option for the logs 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 --- .../src/app/ceph/cluster/cluster.module.ts | 4 +- .../app/ceph/cluster/logs/logs.component.html | 26 +++++++++++++ .../app/ceph/cluster/logs/logs.component.scss | 6 +++ .../app/ceph/cluster/logs/logs.component.ts | 15 +++++++ .../telemetry/telemetry.component.html | 21 +++++----- .../telemetry/telemetry.component.spec.ts | 22 +---------- .../cluster/telemetry/telemetry.component.ts | 6 --- .../shared/components/components.module.ts | 7 +++- .../download-button.component.html | 23 +++++++++++ .../download-button.component.scss | 0 .../download-button.component.spec.ts | 39 +++++++++++++++++++ .../download-button.component.ts | 31 +++++++++++++++ .../copy2clipboard-button.directive.ts | 4 +- .../src/app/shared/enum/icons.enum.ts | 2 + 14 files changed, 165 insertions(+), 41 deletions(-) create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/download-button/download-button.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/download-button/download-button.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/download-button/download-button.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/download-button/download-button.component.ts diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts index eb73ccc49ab..1a8198be99d 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts @@ -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, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/logs/logs.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/logs/logs.component.html index 1f6aec722c9..3b9caabbe73 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/logs/logs.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/logs/logs.component.html @@ -11,6 +11,19 @@
+
+ + + +

{{ line.stamp | cdDate }} @@ -29,6 +42,19 @@

+
+ + + +

{{ line.stamp | cdDate }} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/logs/logs.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/logs/logs.component.scss index 7638196c49d..3026c27a07c 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/logs/logs.component.scss +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/logs/logs.component.scss @@ -5,6 +5,12 @@ p { } .card { + .btn-group { + margin-top: -45px; + position: absolute; + right: 0; + } + div p { display: flex; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/logs/logs.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/logs/logs.component.ts index a34e455e7e7..c298af22ab4 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/logs/logs.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/logs/logs.component.ts @@ -16,6 +16,7 @@ export class LogsComponent implements OnInit, OnDestroy { clog: Array; audit_log: Array; 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'; + } + } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.html index 63a73935595..a128c6f2510 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.html @@ -262,17 +262,16 @@

- - +
+ + + +
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.spec.ts index a655a635aba..901f1774307 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.spec.ts @@ -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; 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'); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.ts index d2c4c894459..ccb695d864a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.ts @@ -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); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts index 95cc5ae0e39..1cec301e533 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts @@ -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 index 00000000000..a7e47650189 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/download-button/download-button.component.html @@ -0,0 +1,23 @@ +
+ +
+ + +
+
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 index 00000000000..e69de29bb2d 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 index 00000000000..9814b48ac50 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/download-button/download-button.component.spec.ts @@ -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; + + 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 index 00000000000..491518665ba --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/download-button/download-button.component.ts @@ -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`); + } + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/directives/copy2clipboard-button.directive.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/directives/copy2clipboard-button.directive.ts index 2a42f19b077..218af07eedd 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/directives/copy2clipboard-button.directive.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/directives/copy2clipboard-button.directive.ts @@ -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.'); }; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/icons.enum.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/icons.enum.ts index 3e5d0a213d4..4fe6d98aca9 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/icons.enum.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/icons.enum.ts @@ -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 -- 2.39.5