diff --git a/src/app/app.module.ts b/src/app/app.module.ts index d5ecc84c..9145d6f3 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 { MapSettingsService } 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'; import { LogConsoleComponent } from './components/project-map/log-console/log-console.component'; import { LogEventsDataSource } from './components/project-map/log-console/log-events-datasource'; import { SaveProjectDialogComponent } from './components/projects/save-project-dialog/save-project-dialog.component'; @@ -199,6 +201,8 @@ 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'; +import { ImportConfigActionComponent } from './components/project-map/context-menu/actions/import-config/import-config-action.component'; import { ConsoleDeviceActionBrowserComponent } from './components/project-map/context-menu/actions/console-device-action-browser/console-device-action-browser.component'; if (environment.production) { @@ -323,11 +327,15 @@ if (environment.production) { NodesMenuComponent, ProjectMapMenuComponent, HelpComponent, + ConfigEditorDialogComponent, + EditConfigActionComponent, LogConsoleComponent, SaveProjectDialogComponent, TopologySummaryComponent, InfoDialogComponent, BringToFrontActionComponent, + ExportConfigActionComponent, + ImportConfigActionComponent, ConsoleDeviceActionBrowserComponent ], imports: [ @@ -423,6 +431,7 @@ if (environment.production) { DeleteConfirmationDialogComponent, HelpDialogComponent, StartCaptureDialogComponent, + ConfigEditorDialogComponent, SaveProjectDialogComponent, InfoDialogComponent ], 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/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..29b221cf --- /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 = 'text/plain'; + element.setAttribute('href', `data:${fileType};charset=utf-8,${encodeURIComponent(config)}`); + 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 f4754c95..63d75132 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 @@ -40,6 +40,19 @@ [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.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/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/components/project-map/project-map.component.spec.ts b/src/app/components/project-map/project-map.component.spec.ts index 65ea89ad..17cf354f 100644 --- a/src/app/components/project-map/project-map.component.spec.ts +++ b/src/app/components/project-map/project-map.component.spec.ts @@ -112,6 +112,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); + } update(server: Server, node: Node) { return of(node); diff --git a/src/app/services/node.service.ts b/src/app/services/node.service.ts index 4f894d09..7398a988 100644 --- a/src/app/services/node.service.ts +++ b/src/app/services/node.service.ts @@ -92,4 +92,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); + } + } }