From 753649a321a3a0fa4737d4c3a9eb707a6266ffb2 Mon Sep 17 00:00:00 2001 From: Piotr Pekala Date: Tue, 30 Jul 2019 07:21:55 -0700 Subject: [PATCH 1/4] Support for editing VPCS config files --- src/app/app.module.ts | 9 +++- .../edit-config-action.component.html | 4 ++ .../edit-config-action.component.ts | 30 +++++++++++++ .../context-menu/context-menu.component.html | 5 +++ .../config-editor.component.html | 10 +++++ .../config-editor.component.scss | 4 ++ .../config-editor/config-editor.component.ts | 43 +++++++++++++++++++ src/app/services/node.service.ts | 18 ++++++++ 8 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 src/app/components/project-map/context-menu/actions/edit-config/edit-config-action.component.html create mode 100644 src/app/components/project-map/context-menu/actions/edit-config/edit-config-action.component.ts create mode 100644 src/app/components/project-map/node-editors/config-editor/config-editor.component.html create mode 100644 src/app/components/project-map/node-editors/config-editor/config-editor.component.scss create mode 100644 src/app/components/project-map/node-editors/config-editor/config-editor.component.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 4242bce2..475862b3 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -191,6 +191,8 @@ import { DuplicateActionComponent } from './components/project-map/context-menu/ import { MapSettingService } from './services/mapsettings.service'; import { ProjectMapMenuComponent } from './components/project-map/project-map-menu/project-map-menu.component'; import { HelpComponent } from './components/help/help.component'; +import { ConfigEditorDialogComponent } from './components/project-map/node-editors/config-editor/config-editor.component'; +import { EditConfigActionComponent } from './components/project-map/context-menu/actions/edit-config/edit-config-action.component'; if (environment.production) { Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', { @@ -312,7 +314,9 @@ if (environment.production) { ConsoleComponent, NodesMenuComponent, ProjectMapMenuComponent, - HelpComponent + HelpComponent, + ConfigEditorDialogComponent, + EditConfigActionComponent ], imports: [ BrowserModule, @@ -404,7 +408,8 @@ if (environment.production) { SymbolsComponent, DeleteConfirmationDialogComponent, HelpDialogComponent, - StartCaptureDialogComponent + StartCaptureDialogComponent, + ConfigEditorDialogComponent ], bootstrap: [AppComponent] }) diff --git a/src/app/components/project-map/context-menu/actions/edit-config/edit-config-action.component.html b/src/app/components/project-map/context-menu/actions/edit-config/edit-config-action.component.html new file mode 100644 index 00000000..3cd439d9 --- /dev/null +++ b/src/app/components/project-map/context-menu/actions/edit-config/edit-config-action.component.html @@ -0,0 +1,4 @@ + diff --git a/src/app/components/project-map/context-menu/actions/edit-config/edit-config-action.component.ts b/src/app/components/project-map/context-menu/actions/edit-config/edit-config-action.component.ts new file mode 100644 index 00000000..de87c525 --- /dev/null +++ b/src/app/components/project-map/context-menu/actions/edit-config/edit-config-action.component.ts @@ -0,0 +1,30 @@ +import { Component, Input } from '@angular/core'; +import { Node } from '../../../../../cartography/models/node'; +import { Project } from '../../../../../models/project'; +import { Server } from '../../../../../models/server'; +import { ConfigEditorDialogComponent } from '../../../node-editors/config-editor/config-editor.component'; +import { MatDialog } from '@angular/material'; + +@Component({ + selector: 'app-edit-config-action', + templateUrl: './edit-config-action.component.html' +}) +export class EditConfigActionComponent { + @Input() server: Server; + @Input() project: Project; + @Input() node: Node; + + constructor(private dialog: MatDialog) {} + + editConfig() { + const dialogRef = this.dialog.open(ConfigEditorDialogComponent, { + width: '600px', + height: '500px', + autoFocus: false + }); + let instance = dialogRef.componentInstance; + instance.server = this.server; + instance.project = this.project; + instance.node = this.node; + } +} diff --git a/src/app/components/project-map/context-menu/context-menu.component.html b/src/app/components/project-map/context-menu/context-menu.component.html index 2ebfeb6a..35ed76d8 100644 --- a/src/app/components/project-map/context-menu/context-menu.component.html +++ b/src/app/components/project-map/context-menu/context-menu.component.html @@ -32,6 +32,11 @@ [link]="links[0]" [linkNode]="linkNodes[0]" > + Configuration for node {{node.name}} + + + +
+ + +
diff --git a/src/app/components/project-map/node-editors/config-editor/config-editor.component.scss b/src/app/components/project-map/node-editors/config-editor/config-editor.component.scss new file mode 100644 index 00000000..d874a5ff --- /dev/null +++ b/src/app/components/project-map/node-editors/config-editor/config-editor.component.scss @@ -0,0 +1,4 @@ +.textArea { + width: 100%; + height: 350px; +} diff --git a/src/app/components/project-map/node-editors/config-editor/config-editor.component.ts b/src/app/components/project-map/node-editors/config-editor/config-editor.component.ts new file mode 100644 index 00000000..8a1124c5 --- /dev/null +++ b/src/app/components/project-map/node-editors/config-editor/config-editor.component.ts @@ -0,0 +1,43 @@ +import { Component, OnInit } from '@angular/core'; +import { Node } from '../../../../cartography/models/node'; +import { Project } from '../../../../models/project'; +import { Server } from '../../../../models/server'; +import { MatDialogRef } from '@angular/material'; +import { NodeService } from '../../../../services/node.service'; +import { ToasterService } from '../../../../services/toaster.service'; + +@Component({ + selector: 'app-config-editor', + templateUrl: './config-editor.component.html', + styleUrls: ['./config-editor.component.scss'] +}) +export class ConfigEditorDialogComponent implements OnInit { + server: Server; + project: Project; + node: Node; + + config: any; + + constructor( + public dialogRef: MatDialogRef, + public nodeService: NodeService, + private toasterService: ToasterService + ) {} + + ngOnInit() { + this.nodeService.getConfiguration(this.server, this.node).subscribe((config: any) => { + this.config = config; + }); + } + + onSaveClick() { + this.nodeService.saveConfiguration(this.server, this.node, this.config).subscribe((response) => { + this.dialogRef.close(); + this.toasterService.success(`Configuration for node ${this.node.name} saved.`); + }); + } + + onCancelClick() { + this.dialogRef.close(); + } +} diff --git a/src/app/services/node.service.ts b/src/app/services/node.service.ts index b0852956..c35ef48f 100644 --- a/src/app/services/node.service.ts +++ b/src/app/services/node.service.ts @@ -84,4 +84,22 @@ export class NodeService { "z": node.z }); } + + getConfiguration(server: Server, node: Node) { + let urlPath: string = `/projects/${node.project_id}/nodes/${node.node_id}` + + if (node.node_type === 'vpcs') { + urlPath += '/files/startup.vpc'; + return this.httpServer.get(server, urlPath, { responseType: 'text' as 'json'}); + } + } + + saveConfiguration(server: Server, node: Node, configuration: string) { + let urlPath: string = `/projects/${node.project_id}/nodes/${node.node_id}` + + if (node.node_type === 'vpcs') { + urlPath += '/files/startup.vpc'; + return this.httpServer.post(server, urlPath, configuration); + } + } } From 6a5aca2dcd6c522a5725b88c6d94a67c85e6ab9b Mon Sep 17 00:00:00 2001 From: Piotr Pekala Date: Tue, 30 Jul 2019 08:44:43 -0700 Subject: [PATCH 2/4] Unit tests added --- .../config-editor.component.spec.ts | 86 +++++++++++++++++++ .../project-map/project-map.component.spec.ts | 8 ++ 2 files changed, 94 insertions(+) create mode 100644 src/app/components/project-map/node-editors/config-editor/config-editor.component.spec.ts diff --git a/src/app/components/project-map/node-editors/config-editor/config-editor.component.spec.ts b/src/app/components/project-map/node-editors/config-editor/config-editor.component.spec.ts new file mode 100644 index 00000000..e89cb86b --- /dev/null +++ b/src/app/components/project-map/node-editors/config-editor/config-editor.component.spec.ts @@ -0,0 +1,86 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { Server } from '../../../../models/server'; +import { + MatDialogModule, + MatFormFieldModule, + MatDialogRef, + MAT_DIALOG_DATA, + MatSnackBarModule +} from '@angular/material'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { ToasterService } from '../../../../services/toaster.service'; +import { of } from 'rxjs/internal/observable/of'; +import { ConfigEditorDialogComponent } from './config-editor.component'; +import { NodeService } from '../../../../services/node.service'; +import { FormsModule } from '@angular/forms'; +import { MockedNodeService } from '../../project-map.component.spec'; +import { Node } from '../../../../cartography/models/node'; + +describe('ConfigEditorDialogComponent', () => { + let component: ConfigEditorDialogComponent; + let fixture: ComponentFixture; + let server: Server; + let node: Node; + let toaster = { + success: jasmine.createSpy('success') + }; + let dialogRef = { + close: jasmine.createSpy('close') + }; + let mockedNodeService: MockedNodeService = new MockedNodeService(); + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + MatDialogModule, + MatFormFieldModule, + NoopAnimationsModule, + MatSnackBarModule, + FormsModule + ], + providers: [ + { provide: MatDialogRef, useValue: dialogRef }, + { provide: MAT_DIALOG_DATA }, + { provide: NodeService, useValue: mockedNodeService }, + { provide: ToasterService, useValue: toaster } + ], + declarations: [ConfigEditorDialogComponent] + }).compileComponents(); + + server = new Server(); + server.host = 'localhost'; + server.port = 80; + + node = new Node(); + node.name = 'sample name'; + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ConfigEditorDialogComponent); + component = fixture.componentInstance; + component.server = server; + component.node = node; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(fixture).toBeDefined(); + expect(component).toBeTruthy(); + }); + + it('should call node service when save configuration chosen', () => { + spyOn(mockedNodeService, 'saveConfiguration').and.returnValue(of('sample config')); + + component.onSaveClick(); + + expect(mockedNodeService.saveConfiguration).toHaveBeenCalled(); + }); + + it('should not call node service when save configuration chosen', () => { + spyOn(mockedNodeService, 'saveConfiguration').and.returnValue(of('sample config')); + + component.onCancelClick(); + + expect(mockedNodeService.saveConfiguration).not.toHaveBeenCalled(); + }); +}); diff --git a/src/app/components/project-map/project-map.component.spec.ts b/src/app/components/project-map/project-map.component.spec.ts index 8daeda92..9482b832 100644 --- a/src/app/components/project-map/project-map.component.spec.ts +++ b/src/app/components/project-map/project-map.component.spec.ts @@ -94,6 +94,14 @@ export class MockedNodeService { duplicate(server: Server, node: Node) { return of(node); } + + getConfiguration(server: Server, node: Node) { + return of('sample config'); + } + + saveConfiguration(server: Server, node: Node, configuration: string) { + return of(configuration); + } } export class MockedDrawingService { From a146dfcb40e4bf29fcc4f8e6b079966a6c1e50a9 Mon Sep 17 00:00:00 2001 From: Piotr Pekala Date: Mon, 2 Sep 2019 01:24:12 -0700 Subject: [PATCH 3/4] Export config added --- src/app/app.module.ts | 4 ++- .../export-config-action.component.html | 4 +++ .../export-config-action.component.ts | 33 +++++++++++++++++++ .../context-menu/context-menu.component.html | 12 ++++--- 4 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 src/app/components/project-map/context-menu/actions/export-config/export-config-action.component.html create mode 100644 src/app/components/project-map/context-menu/actions/export-config/export-config-action.component.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 7e43e027..83c14c7b 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -199,6 +199,7 @@ import { ShowNodeActionComponent } from './components/project-map/context-menu/a import { InfoDialogComponent } from './components/project-map/info-dialog/info-dialog.component'; import { InfoService } from './services/info.service'; import { BringToFrontActionComponent } from './components/project-map/context-menu/actions/bring-to-front-action/bring-to-front-action.component'; +import { ExportConfigActionComponent } from './components/project-map/context-menu/actions/export-config/export-config-action.component'; if (environment.production) { Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', { @@ -327,7 +328,8 @@ if (environment.production) { SaveProjectDialogComponent, TopologySummaryComponent, InfoDialogComponent, - BringToFrontActionComponent + BringToFrontActionComponent, + ExportConfigActionComponent ], imports: [ BrowserModule, diff --git a/src/app/components/project-map/context-menu/actions/export-config/export-config-action.component.html b/src/app/components/project-map/context-menu/actions/export-config/export-config-action.component.html new file mode 100644 index 00000000..5730195b --- /dev/null +++ b/src/app/components/project-map/context-menu/actions/export-config/export-config-action.component.html @@ -0,0 +1,4 @@ + diff --git a/src/app/components/project-map/context-menu/actions/export-config/export-config-action.component.ts b/src/app/components/project-map/context-menu/actions/export-config/export-config-action.component.ts new file mode 100644 index 00000000..852e89ff --- /dev/null +++ b/src/app/components/project-map/context-menu/actions/export-config/export-config-action.component.ts @@ -0,0 +1,33 @@ +import { Component, Input } from '@angular/core'; +import { Node } from '../../../../../cartography/models/node'; +import { NodeService } from '../../../../../services/node.service'; +import { Server } from '../../../../../models/server'; + +@Component({ + selector: 'app-export-config-action', + templateUrl: './export-config-action.component.html' +}) +export class ExportConfigActionComponent { + @Input() server: Server; + @Input() node: Node; + + constructor( + private nodeService: NodeService + ) {} + + exportConfig() { + this.nodeService.getConfiguration(this.server, this.node).subscribe((config: any) => { + this.downloadByHtmlTag(config); + }); + } + + private downloadByHtmlTag(config: string) { + const element = document.createElement('a'); + const fileType = 'vpc'; + element.setAttribute('href', `data:${fileType};charset=utf-8,${encodeURIComponent(config)}`); + element.setAttribute('download', 'configFile.vpc'); + + var event = new MouseEvent("click"); + element.dispatchEvent(event); + } +} diff --git a/src/app/components/project-map/context-menu/context-menu.component.html b/src/app/components/project-map/context-menu/context-menu.component.html index 69dd1ffa..87957817 100644 --- a/src/app/components/project-map/context-menu/context-menu.component.html +++ b/src/app/components/project-map/context-menu/context-menu.component.html @@ -36,11 +36,15 @@ [link]="links[0]" [linkNode]="linkNodes[0]" > - + Date: Mon, 2 Sep 2019 05:43:36 -0700 Subject: [PATCH 4/4] Code cleaned up --- src/app/app.module.ts | 4 +++- .../export-config-action.component.ts | 4 ++-- .../import-config-action.component.html | 4 ++++ .../import-config-action.component.ts | 19 +++++++++++++++++++ .../context-menu/context-menu.component.html | 8 ++++++-- 5 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 src/app/components/project-map/context-menu/actions/import-config/import-config-action.component.html create mode 100644 src/app/components/project-map/context-menu/actions/import-config/import-config-action.component.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 83c14c7b..2b86b4de 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -200,6 +200,7 @@ import { InfoDialogComponent } from './components/project-map/info-dialog/info-d import { InfoService } from './services/info.service'; import { BringToFrontActionComponent } from './components/project-map/context-menu/actions/bring-to-front-action/bring-to-front-action.component'; import { ExportConfigActionComponent } from './components/project-map/context-menu/actions/export-config/export-config-action.component'; +import { ImportConfigActionComponent } from './components/project-map/context-menu/actions/import-config/import-config-action.component'; if (environment.production) { Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', { @@ -329,7 +330,8 @@ if (environment.production) { TopologySummaryComponent, InfoDialogComponent, BringToFrontActionComponent, - ExportConfigActionComponent + ExportConfigActionComponent, + ImportConfigActionComponent ], imports: [ BrowserModule, diff --git a/src/app/components/project-map/context-menu/actions/export-config/export-config-action.component.ts b/src/app/components/project-map/context-menu/actions/export-config/export-config-action.component.ts index 852e89ff..29b221cf 100644 --- a/src/app/components/project-map/context-menu/actions/export-config/export-config-action.component.ts +++ b/src/app/components/project-map/context-menu/actions/export-config/export-config-action.component.ts @@ -23,9 +23,9 @@ export class ExportConfigActionComponent { private downloadByHtmlTag(config: string) { const element = document.createElement('a'); - const fileType = 'vpc'; + const fileType = 'text/plain'; element.setAttribute('href', `data:${fileType};charset=utf-8,${encodeURIComponent(config)}`); - element.setAttribute('download', 'configFile.vpc'); + element.setAttribute('download', `${this.node.name}_startup.vpc`); var event = new MouseEvent("click"); element.dispatchEvent(event); diff --git a/src/app/components/project-map/context-menu/actions/import-config/import-config-action.component.html b/src/app/components/project-map/context-menu/actions/import-config/import-config-action.component.html new file mode 100644 index 00000000..a8cc1358 --- /dev/null +++ b/src/app/components/project-map/context-menu/actions/import-config/import-config-action.component.html @@ -0,0 +1,4 @@ + diff --git a/src/app/components/project-map/context-menu/actions/import-config/import-config-action.component.ts b/src/app/components/project-map/context-menu/actions/import-config/import-config-action.component.ts new file mode 100644 index 00000000..5adba173 --- /dev/null +++ b/src/app/components/project-map/context-menu/actions/import-config/import-config-action.component.ts @@ -0,0 +1,19 @@ +import { Component, Input } from '@angular/core'; +import { Node } from '../../../../../cartography/models/node'; +import { NodeService } from '../../../../../services/node.service'; +import { Server } from '../../../../../models/server'; + +@Component({ + selector: 'app-import-config-action', + templateUrl: './import-config-action.component.html' +}) +export class ImportConfigActionComponent { + @Input() server: Server; + @Input() node: Node; + + constructor() {} + + importConfig() { + //needs implementation + } +} diff --git a/src/app/components/project-map/context-menu/context-menu.component.html b/src/app/components/project-map/context-menu/context-menu.component.html index 87957817..3bd5d4d5 100644 --- a/src/app/components/project-map/context-menu/context-menu.component.html +++ b/src/app/components/project-map/context-menu/context-menu.component.html @@ -36,15 +36,19 @@ [link]="links[0]" [linkNode]="linkNodes[0]" > - - +