Merge branch 'master' into custom-html-adbutler-ads

This commit is contained in:
Piotr Pekala 2019-10-10 05:44:00 -07:00
commit d756ce9b87
225 changed files with 7608 additions and 601 deletions

View File

@ -14,10 +14,11 @@ init:
- git config --global core.autocrlf input
install:
- ps: Install-Product node 11 x64
- ps: Install-Product node 12 x64
- yarn install
build_script:
- cmd: set NODE_OPTIONS=--max-old-space-size=8092
- yarn buildforelectron
- "%PYTHON%\\python.exe -m pip install -r scripts\\requirements.txt"
- "%PYTHON%\\python.exe scripts\\build.py download -a"

View File

@ -1,6 +1,6 @@
{
"name": "gns3-web-ui",
"version": "2019.2.0-alpha.6dev",
"version": "2019.2.0-alpha.8dev",
"author": {
"name": "GNS3 Technology Inc.",
"email": "developers@gns3.com"
@ -38,25 +38,26 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^8.1.2",
"@angular/cdk": "^8.1.1",
"@angular/common": "^8.1.2",
"@angular/compiler": "^8.1.2",
"@angular/core": "^8.1.2",
"@angular/forms": "^8.1.2",
"@angular/animations": "^8.2.8",
"@angular/cdk": "^8.2.1",
"@angular/common": "^8.2.8",
"@angular/compiler": "^8.2.8",
"@angular/core": "^8.2.8",
"@angular/forms": "^8.2.8",
"@angular/http": "^7.2.15",
"@angular/material": "^8.1.1",
"@angular/platform-browser": "^8.1.2",
"@angular/platform-browser-dynamic": "^8.1.2",
"@angular/router": "^8.1.2",
"@angular/material": "^8.2.1",
"@angular/platform-browser": "^8.2.8",
"@angular/platform-browser-dynamic": "^8.2.8",
"@angular/router": "^8.2.8",
"angular-persistence": "^1.0.1",
"angular2-hotkeys": "^2.1.4",
"angular2-hotkeys": "^2.1.5",
"angular2-indexeddb": "^1.2.3",
"bootstrap": "4.3.1",
"command-exists": "^1.2.8",
"core-js": "^3.1.4",
"css-tree": "^1.0.0-alpha.33",
"core-js": "^3.2.1",
"css-tree": "^1.0.0-alpha.34",
"d3-ng2-service": "^2.1.0",
"file-saver": "^2.0.2",
"hammerjs": "^2.0.8",
"ini": "^1.3.5",
"material-design-icons": "^3.0.1",
@ -65,24 +66,27 @@
"node-fetch": "^2.6.0",
"notosans-fontface": "^1.1.0",
"raven-js": "^3.27.2",
"rxjs": "^6.5.2",
"rxjs-compat": "^6.5.2",
"rxjs": "^6.5.3",
"rxjs-compat": "^6.5.3",
"save-svg-as-png": "^1.4.14",
"svg-crowbar": "^0.2.3",
"tree-kill": "^1.2.1",
"typeface-roboto": "^0.0.75",
"xterm": "^3.14.5",
"yargs": "^13.3.0",
"zone.js": "^0.9.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.801.2",
"@angular/cli": "^8.1.2",
"@angular/compiler-cli": "^8.1.2",
"@angular/language-service": "^8.1.2",
"@sentry/cli": "^1.47.0",
"@sentry/electron": "^0.17.3",
"@types/jasmine": "~3.3.15",
"@types/jasminewd2": "~2.0.6",
"@types/node": "~12.6.8",
"codelyzer": "~5.1.0",
"@angular-devkit/build-angular": "^0.801.3",
"@angular/cli": "^8.3.6",
"@angular/compiler-cli": "^8.2.8",
"@angular/language-service": "^8.2.8",
"@sentry/cli": "^1.47.2",
"@sentry/electron": "^0.17.4",
"@types/jasmine": "^3.3.16",
"@types/jasminewd2": "^2.0.7",
"@types/node": "^12.6.9",
"codelyzer": "^5.1.2",
"electron": "5.0.8",
"electron-builder": "21.1.5",
"jasmine-core": "~3.4.0",
@ -99,8 +103,8 @@
"popper.js": "^1.15.0",
"prettier": "^1.18.2",
"protractor": "~5.4.2",
"replace": "^1.1.0",
"ts-mockito": "^2.4.2",
"replace": "^1.1.1",
"ts-mockito": "^2.5.0",
"ts-node": "~8.3.0",
"tslint": "~5.18.0",
"tslint-config-prettier": "^1.18.0",

View File

@ -3,14 +3,17 @@ GNS3 WebUI is web implementation of user interface for GNS3 software.
Current version: 2019.2.0
What's New
- Help section added with information about third party components
- Showing progress when server starting
- Possibility to edit interface & node labels by using context menu
- Enhancements in moving elements on map
- Context menu extended with option to duplicate
- Main menu extended with option to lock all items on map
- Editing interface labels on double click
- Support for keyboard shortcuts
- Menu extended with option to delete currently opened project, export & import project
- Possibility to save current state of project
- Ability to duplicate project from projects page
- Node information dialog available from context menu
- Topology summary widget on map view
- Improvements in dialog styles
Bug Fixes
- Removing issues with positioning interface labels while adding link between nodes on map
- Removing issues with opening console
- Context menu now is correctly placed
- Entered text in text & style editor is now validated
- Text validation in dialogs
- Removing errors with creating WebSockets

View File

@ -53,6 +53,10 @@ import { CopyIouTemplateComponent } from './components/preferences/ios-on-unix/c
import { ListOfSnapshotsComponent } from './components/snapshots/list-of-snapshots/list-of-snapshots.component';
import { ConsoleComponent } from './components/settings/console/console.component';
import { HelpComponent } from './components/help/help.component';
import { TracengPreferencesComponent } from './components/preferences/traceng/traceng-preferences/traceng-preferences.component';
import { TracengTemplatesComponent } from './components/preferences/traceng/traceng-templates/traceng-templates.component';
import { AddTracengTemplateComponent } from './components/preferences/traceng/add-traceng/add-traceng-template.component';
import { TracengTemplateDetailsComponent } from './components/preferences/traceng/traceng-template-details/traceng-template-details.component';
const routes: Routes = [
{
@ -111,6 +115,11 @@ const routes: Routes = [
{ path: 'server/:server_id/preferences/vmware/templates/:template_id', component: VmwareTemplateDetailsComponent },
{ path: 'server/:server_id/preferences/vmware/addtemplate', component: AddVmwareTemplateComponent },
// { path: 'server/:server_id/preferences/traceng', component: TracengPreferencesComponent },
// { path: 'server/:server_id/preferences/traceng/templates', component: TracengTemplatesComponent },
// { path: 'server/:server_id/preferences/traceng/templates/:template_id', component: TracengTemplateDetailsComponent },
// { path: 'server/:server_id/preferences/traceng/addtemplate', component: AddTracengTemplateComponent },
{ path: 'server/:server_id/preferences/docker/templates', component: DockerTemplatesComponent },
{ path: 'server/:server_id/preferences/docker/templates/:template_id', component: DockerTemplateDetailsComponent },
{ path: 'server/:server_id/preferences/docker/templates/:template_id/copy', component: CopyDockerTemplateComponent },

View File

@ -0,0 +1,3 @@
mat-menu-panel {
min-height: 0px;
}

View File

@ -189,9 +189,59 @@ import { NotificationBoxComponent } from './components/notification-box/notifica
import { NonNegativeValidator } from './validators/non-negative-validator';
import { RotationValidator } from './validators/rotation-validator';
import { DuplicateActionComponent } from './components/project-map/context-menu/actions/duplicate-action/duplicate-action.component';
import { MapSettingService } from './services/mapsettings.service';
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';
import { TopologySummaryComponent } from './components/topology-summary/topology-summary.component';
import { ShowNodeActionComponent } from './components/project-map/context-menu/actions/show-node-action/show-node-action.component';
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';
import { ChangeSymbolDialogComponent } from './components/project-map/change-symbol-dialog/change-symbol-dialog.component';
import { ChangeSymbolActionComponent } from './components/project-map/context-menu/actions/change-symbol/change-symbol-action.component';
import { EditProjectDialogComponent } from './components/projects/edit-project-dialog/edit-project-dialog.component';
import { ProjectsFilter } from './filters/projectsFilter.pipe';
import { ComputeService } from './services/compute.service';
import { ReloadNodeActionComponent } from './components/project-map/context-menu/actions/reload-node-action/reload-node-action.component';
import { SuspendNodeActionComponent } from './components/project-map/context-menu/actions/suspend-node-action/suspend-node-action.component';
import { ConfigActionComponent } from './components/project-map/context-menu/actions/config-action/config-action.component';
import { ConfiguratorDialogVpcsComponent } from './components/project-map/node-editors/configurator/vpcs/configurator-vpcs.component';
import { ConfiguratorDialogEthernetHubComponent } from './components/project-map/node-editors/configurator/ethernet_hub/configurator-ethernet-hub.component';
import { ConfiguratorDialogEthernetSwitchComponent } from './components/project-map/node-editors/configurator/ethernet-switch/configurator-ethernet-switch.component';
import { PortsComponent } from './components/preferences/common/ports/ports.component';
import { ConfiguratorDialogSwitchComponent } from './components/project-map/node-editors/configurator/switch/configurator-switch.component';
import { ConfiguratorDialogVirtualBoxComponent } from './components/project-map/node-editors/configurator/virtualbox/configurator-virtualbox.component';
import { CustomAdaptersTableComponent } from './components/preferences/common/custom-adapters-table/custom-adapters-table.component';
import { ConfiguratorDialogQemuComponent } from './components/project-map/node-editors/configurator/qemu/configurator-qemu.component';
import { ConfiguratorDialogCloudComponent } from './components/project-map/node-editors/configurator/cloud/configurator-cloud.component';
import { UdpTunnelsComponent } from './components/preferences/common/udp-tunnels/udp-tunnels.component';
import { ConfiguratorDialogAtmSwitchComponent } from './components/project-map/node-editors/configurator/atm_switch/configurator-atm-switch.component';
import { ConfiguratorDialogVmwareComponent } from './components/project-map/node-editors/configurator/vmware/configurator-vmware.component';
import { ConfiguratorDialogIouComponent } from './components/project-map/node-editors/configurator/iou/configurator-iou.component';
import { ConfiguratorDialogIosComponent } from './components/project-map/node-editors/configurator/ios/configurator-ios.component';
import { ConfiguratorDialogDockerComponent } from './components/project-map/node-editors/configurator/docker/configurator-docker.component';
import { ConfiguratorDialogNatComponent } from './components/project-map/node-editors/configurator/nat/configurator-nat.component';
import { ConfiguratorDialogTracengComponent } from './components/project-map/node-editors/configurator/traceng/configurator-traceng.component';
import { AddTracengTemplateComponent } from './components/preferences/traceng/add-traceng/add-traceng-template.component';
import { TracengPreferencesComponent } from './components/preferences/traceng/traceng-preferences/traceng-preferences.component';
import { TracengTemplatesComponent } from './components/preferences/traceng/traceng-templates/traceng-templates.component';
import { TracengService } from './services/traceng.service';
import { TracengTemplateDetailsComponent } from './components/preferences/traceng/traceng-template-details/traceng-template-details.component';
import { QemuImageCreatorComponent } from './components/project-map/node-editors/configurator/qemu/qemu-image-creator/qemu-image-creator.component';
import { ChooseNameDialogComponent } from './components/projects/choose-name-dialog/choose-name-dialog.component';
import { PacketCaptureService } from './services/packet-capture.service';
import { StartCaptureOnStartedLinkActionComponent } from './components/project-map/context-menu/actions/start-capture-on-started-link/start-capture-on-started-link.component';
import { LockActionComponent } from './components/project-map/context-menu/actions/lock-action/lock-action.component';
import { NavigationDialogComponent } from './components/projects/navigation-dialog/navigation-dialog.component';
import { ScreenshotDialogComponent } from './components/project-map/screenshot-dialog/screenshot-dialog.component';
if (environment.production) {
Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', {
@ -305,16 +355,61 @@ if (environment.production) {
SearchFilter,
DateFilter,
NameFilter,
ProjectsFilter,
ListOfSnapshotsComponent,
CustomAdaptersComponent,
NodesMenuComponent,
AdbutlerComponent,
ConsoleDeviceActionComponent,
ShowNodeActionComponent,
ConsoleComponent,
NodesMenuComponent,
NotificationBoxComponent,
ProjectMapMenuComponent,
HelpComponent
HelpComponent,
ConfigEditorDialogComponent,
EditConfigActionComponent,
LogConsoleComponent,
SaveProjectDialogComponent,
TopologySummaryComponent,
InfoDialogComponent,
BringToFrontActionComponent,
ExportConfigActionComponent,
ImportConfigActionComponent,
ConsoleDeviceActionBrowserComponent,
ChangeSymbolDialogComponent,
ChangeSymbolActionComponent,
EditProjectDialogComponent,
ReloadNodeActionComponent,
SuspendNodeActionComponent,
ConfigActionComponent,
ConfiguratorDialogVpcsComponent,
ConfiguratorDialogEthernetHubComponent,
ConfiguratorDialogEthernetSwitchComponent,
PortsComponent,
ConfiguratorDialogSwitchComponent,
ConfiguratorDialogVirtualBoxComponent,
CustomAdaptersTableComponent,
ConfiguratorDialogQemuComponent,
ConfiguratorDialogCloudComponent,
UdpTunnelsComponent,
ConfiguratorDialogAtmSwitchComponent,
ConfiguratorDialogVmwareComponent,
ConfiguratorDialogIouComponent,
ConfiguratorDialogIosComponent,
ConfiguratorDialogDockerComponent,
ConfiguratorDialogNatComponent,
ConfiguratorDialogTracengComponent,
AddTracengTemplateComponent,
TracengPreferencesComponent,
TracengTemplatesComponent,
TracengTemplateDetailsComponent,
QemuImageCreatorComponent,
ChooseNameDialogComponent,
StartCaptureOnStartedLinkActionComponent,
LockActionComponent,
NavigationDialogComponent,
ScreenshotDialogComponent
],
imports: [
BrowserModule,
@ -354,6 +449,7 @@ if (environment.production) {
LinksDataSource,
NodesDataSource,
SymbolsDataSource,
LogEventsDataSource,
SelectionManager,
InRectangleHelper,
DrawingsDataSource,
@ -390,7 +486,11 @@ if (environment.production) {
NodeCreatedLabelStylesFixer,
NonNegativeValidator,
RotationValidator,
MapSettingService
MapSettingsService,
InfoService,
ComputeService,
TracengService,
PacketCaptureService
],
entryComponents: [
AddServerDialogComponent,
@ -406,7 +506,30 @@ if (environment.production) {
SymbolsComponent,
DeleteConfirmationDialogComponent,
HelpDialogComponent,
StartCaptureDialogComponent
StartCaptureDialogComponent,
ConfigEditorDialogComponent,
SaveProjectDialogComponent,
InfoDialogComponent,
ChangeSymbolDialogComponent,
EditProjectDialogComponent,
ConfiguratorDialogVpcsComponent,
ConfiguratorDialogEthernetHubComponent,
ConfiguratorDialogEthernetSwitchComponent,
ConfiguratorDialogSwitchComponent,
ConfiguratorDialogVirtualBoxComponent,
ConfiguratorDialogQemuComponent,
ConfiguratorDialogCloudComponent,
ConfiguratorDialogAtmSwitchComponent,
ConfiguratorDialogVmwareComponent,
ConfiguratorDialogIouComponent,
ConfiguratorDialogIosComponent,
ConfiguratorDialogDockerComponent,
ConfiguratorDialogNatComponent,
ConfiguratorDialogTracengComponent,
QemuImageCreatorComponent,
ChooseNameDialogComponent,
NavigationDialogComponent,
ScreenshotDialogComponent
],
bootstrap: [AppComponent]
})

View File

@ -58,6 +58,8 @@ import { DrawingAddingComponent } from './components/drawing-adding/drawing-addi
import { MovingEventSource } from './events/moving-event-source';
import { MovingCanvasDirective } from './directives/moving-canvas.directive';
import { ZoomingCanvasDirective } from './directives/zooming-canvas.directive';
import { EthernetLinkWidget } from './widgets/links/ethernet-link';
import { SerialLinkWidget } from './widgets/links/serial-link';
@NgModule({
imports: [CommonModule, MatMenuModule, MatIconModule],
@ -117,6 +119,8 @@ import { ZoomingCanvasDirective } from './directives/zooming-canvas.directive';
MapSettingsManager,
FontBBoxCalculator,
StylesToFontConverter,
EthernetLinkWidget,
SerialLinkWidget,
...D3_MAP_IMPORTS
],
exports: [D3MapComponent, ExperimentalMapComponent]

View File

@ -21,7 +21,7 @@ import { MapLabel } from '../../models/map/map-label';
import { MapLinkNode } from '../../models/map/map-link-node';
import { select } from 'd3-selection';
import { MapLink } from '../../models/map/map-link';
import { MapSettingService } from '../../../services/mapsettings.service';
import { MapSettingsService } from '../../../services/mapsettings.service';
describe('DraggableSelectionComponent', () => {
let component: DraggableSelectionComponent;
@ -123,7 +123,7 @@ describe('DraggableSelectionComponent', () => {
{ provide: DrawingsEventSource, useValue: drawingsEventSourceStub },
{ provide: GraphDataManager, useValue: mockedGraphDataManager },
{ provide: LinksEventSource, useValue: linksEventSourceStub },
{ provide: MapSettingService, useClass: MapSettingService }
{ provide: MapSettingsService, useClass: MapSettingsService }
],
declarations: [DraggableSelectionComponent]
}).compileComponents();

View File

@ -17,7 +17,7 @@ import { LabelWidget } from '../../widgets/label';
import { InterfaceLabelWidget } from '../../widgets/interface-label';
import { MapLinkNode } from '../../models/map/map-link-node';
import { LinksEventSource } from '../../events/links-event-source';
import { MapSettingService } from '../../../services/mapsettings.service';
import { MapSettingsService } from '../../../services/mapsettings.service';
@Component({
selector: 'app-draggable-selection',
@ -44,7 +44,7 @@ export class DraggableSelectionComponent implements OnInit, OnDestroy {
private drawingsEventSource: DrawingsEventSource,
private graphDataManager: GraphDataManager,
private linksEventSource: LinksEventSource,
private mapSettingsService: MapSettingService
private mapSettingsService: MapSettingsService
) {}
ngOnInit() {
@ -94,8 +94,10 @@ export class DraggableSelectionComponent implements OnInit, OnDestroy {
).subscribe((evt: DraggableDrag<any>) => {
if (!this.isMapLocked) {
const selected = this.selectionManager.getSelected();
const selectedNodes = selected.filter(item => item instanceof MapNode);
// update nodes
let mapNodes = selected.filter(item => item instanceof MapNode);
const lockedNodes = mapNodes.filter((item: MapNode) => item.locked);
const selectedNodes = mapNodes.filter((item: MapNode) => !item.locked);
selectedNodes.forEach((node: MapNode) => {
node.x += evt.dx;
node.y += evt.dy;
@ -116,52 +118,52 @@ export class DraggableSelectionComponent implements OnInit, OnDestroy {
});
// update drawings
selected
.filter(item => item instanceof MapDrawing)
.forEach((drawing: MapDrawing) => {
drawing.x += evt.dx;
drawing.y += evt.dy;
this.drawingsWidget.redrawDrawing(svg, drawing);
});
let mapDrawings = selected.filter(item => item instanceof MapDrawing);
const selectedDrawings = mapDrawings.filter((item: MapDrawing) => !item.locked);
selectedDrawings.forEach((drawing: MapDrawing) => {
drawing.x += evt.dx;
drawing.y += evt.dy;
this.drawingsWidget.redrawDrawing(svg, drawing);
});
// update labels
selected
.filter(item => item instanceof MapLabel)
.forEach((label: MapLabel) => {
const isParentNodeSelected = selectedNodes.filter(node => node.id === label.nodeId).length > 0;
if (isParentNodeSelected) {
return;
}
let mapLabels = selected.filter(item => item instanceof MapLabel);
const selectedLabels = mapLabels.filter((item: MapLabel) => lockedNodes.filter((node) => node.id === item.nodeId).length === 0);
selectedLabels.forEach((label: MapLabel) => {
const isParentNodeSelected = selectedNodes.filter(node => node.id === label.nodeId).length > 0;
if (isParentNodeSelected) {
return;
}
const node = this.graphDataManager.getNodes().filter(node => node.id === label.nodeId)[0];
node.label.x += evt.dx;
node.label.y += evt.dy;
this.labelWidget.redrawLabel(svg, label);
});
const node = this.graphDataManager.getNodes().filter(node => node.id === label.nodeId)[0];
node.label.x += evt.dx;
node.label.y += evt.dy;
this.labelWidget.redrawLabel(svg, label);
});
// update interface labels
selected
.filter(item => item instanceof MapLinkNode)
.forEach((interfaceLabel: MapLinkNode) => {
const isParentNodeSelected = selectedNodes.filter(node => node.id === interfaceLabel.nodeId).length > 0;
if (isParentNodeSelected) {
return;
}
let mapLinkNodes = selected.filter(item => item instanceof MapLinkNode);
const selectedLinkNodes = mapLinkNodes.filter((item: MapLinkNode) => lockedNodes.filter((node) => node.id === item.nodeId).length === 0);
selectedLinkNodes.forEach((interfaceLabel: MapLinkNode) => {
const isParentNodeSelected = selectedNodes.filter(node => node.id === interfaceLabel.nodeId).length > 0;
if (isParentNodeSelected) {
return;
}
const link = this.graphDataManager
.getLinks()
.filter(link => link.nodes[0].id === interfaceLabel.id || link.nodes[1].id === interfaceLabel.id)[0];
if (link.nodes[0].id === interfaceLabel.id) {
link.nodes[0].label.x += evt.dx;
link.nodes[0].label.y += evt.dy;
}
if (link.nodes[1].id === interfaceLabel.id) {
link.nodes[1].label.x += evt.dx;
link.nodes[1].label.y += evt.dy;
}
const link = this.graphDataManager
.getLinks()
.filter(link => link.nodes[0].id === interfaceLabel.id || link.nodes[1].id === interfaceLabel.id)[0];
if (link.nodes[0].id === interfaceLabel.id) {
link.nodes[0].label.x += evt.dx;
link.nodes[0].label.y += evt.dy;
}
if (link.nodes[1].id === interfaceLabel.id) {
link.nodes[1].label.x += evt.dx;
link.nodes[1].label.y += evt.dy;
}
this.linksWidget.redrawLink(svg, link);
});
this.linksWidget.redrawLink(svg, link);
});
}
});
@ -173,39 +175,41 @@ export class DraggableSelectionComponent implements OnInit, OnDestroy {
).subscribe((evt: DraggableEnd<any>) => {
if (!this.isMapLocked) {
const selected = this.selectionManager.getSelected();
const selectedNodes = selected.filter(item => item instanceof MapNode);
let mapNodes = selected.filter(item => item instanceof MapNode);
const lockedNodes = mapNodes.filter((item: MapNode) => item.locked);
const selectedNodes = mapNodes.filter((item: MapNode) => !item.locked);
selectedNodes.forEach((item: MapNode) => {
this.nodesEventSource.dragged.emit(new DraggedDataEvent<MapNode>(item, evt.dx, evt.dy));
});
selected
.filter(item => item instanceof MapDrawing)
.forEach((item: MapDrawing) => {
this.drawingsEventSource.dragged.emit(new DraggedDataEvent<MapDrawing>(item, evt.dx, evt.dy));
});
let mapDrawings = selected.filter(item => item instanceof MapDrawing);
const selectedDrawings = mapDrawings.filter((item: MapDrawing) => !item.locked);
selectedDrawings.forEach((item: MapDrawing) => {
this.drawingsEventSource.dragged.emit(new DraggedDataEvent<MapDrawing>(item, evt.dx, evt.dy));
});
selected
.filter(item => item instanceof MapLabel)
.forEach((label: MapLabel) => {
const isParentNodeSelected = selectedNodes.filter(node => node.id === label.nodeId).length > 0;
if (isParentNodeSelected) {
return;
}
let mapLabels = selected.filter(item => item instanceof MapLabel);
const selectedLabels = mapLabels.filter((item: MapLabel) => lockedNodes.filter((node) => node.id === item.nodeId).length === 0);
selectedLabels.forEach((label: MapLabel) => {
const isParentNodeSelected = selectedNodes.filter(node => node.id === label.nodeId).length > 0;
if (isParentNodeSelected) {
return;
}
this.nodesEventSource.labelDragged.emit(new DraggedDataEvent<MapLabel>(label, evt.dx, evt.dy));
});
this.nodesEventSource.labelDragged.emit(new DraggedDataEvent<MapLabel>(label, evt.dx, evt.dy));
});
selected
.filter(item => item instanceof MapLinkNode)
.forEach((label: MapLinkNode) => {
const isParentNodeSelected = selectedNodes.filter(node => node.id === label.nodeId).length > 0;
if (isParentNodeSelected) {
return;
}
this.linksEventSource.interfaceDragged.emit(new DraggedDataEvent<MapLinkNode>(label, evt.dx, evt.dy));
});
}
let mapLinkNodes = selected.filter(item => item instanceof MapLinkNode);
const selectedLinkNodes = mapLinkNodes.filter((item: MapLinkNode) => lockedNodes.filter((node) => node.id === item.nodeId).length === 0)
selectedLinkNodes.forEach((label: MapLinkNode) => {
const isParentNodeSelected = selectedNodes.filter(node => node.id === label.nodeId).length > 0;
if (isParentNodeSelected) {
return;
}
this.linksEventSource.interfaceDragged.emit(new DraggedDataEvent<MapLinkNode>(label, evt.dx, evt.dy));
});
}
});
}

View File

@ -14,6 +14,7 @@ export class DrawingToMapDrawingConverter implements Converter<Drawing, MapDrawi
mapDrawing.projectId = drawing.project_id;
mapDrawing.rotation = drawing.rotation;
mapDrawing.svg = drawing.svg;
mapDrawing.locked = drawing.locked;
mapDrawing.x = drawing.x;
mapDrawing.y = drawing.y;
mapDrawing.z = drawing.z;

View File

@ -14,6 +14,7 @@ export class MapDrawingToDrawingConverter implements Converter<MapDrawing, Drawi
drawing.project_id = mapDrawing.projectId;
drawing.rotation = mapDrawing.rotation;
drawing.svg = mapDrawing.svg;
drawing.locked = mapDrawing.locked;
drawing.x = mapDrawing.x;
drawing.y = mapDrawing.y;
drawing.z = mapDrawing.z;

View File

@ -20,6 +20,7 @@ export class MapNodeToNodeConverter implements Converter<MapNode, Node> {
node.first_port_name = mapNode.firstPortName;
node.height = mapNode.height;
node.label = mapNode.label ? this.mapLabelToLabel.convert(mapNode.label) : undefined;
node.locked = mapNode.locked;
node.name = mapNode.name;
node.node_directory = mapNode.nodeDirectory;
node.node_type = mapNode.nodeType;

View File

@ -28,13 +28,14 @@ export class NodeToMapNodeConverter implements Converter<Node, MapNode> {
mapNode.consoleHost = node.console_host;
mapNode.firstPortName = node.first_port_name;
mapNode.height = node.height;
mapNode.label = this.labelToMapLabel.convert(node.label, { node_id: node.node_id });
mapNode.label = this.labelToMapLabel ? this.labelToMapLabel.convert(node.label, { node_id: node.node_id }) : undefined;
mapNode.locked = node.locked;
mapNode.name = node.name;
mapNode.nodeDirectory = node.node_directory;
mapNode.nodeType = node.node_type;
mapNode.portNameFormat = node.port_name_format;
mapNode.portSegmentSize = node.port_segment_size;
mapNode.ports = node.ports.map(port => this.portToMapPort.convert(port));
mapNode.ports = node.ports ? node.ports.map(port => this.portToMapPort.convert(port)) : [];
mapNode.projectId = node.project_id;
mapNode.status = node.status;
mapNode.symbol = node.symbol;

View File

@ -34,31 +34,39 @@ export class GraphDataManager {
) {}
public setNodes(nodes: Node[]) {
const mapNodes = nodes.map(n => this.nodeToMapNode.convert(n));
this.mapNodesDataSource.set(mapNodes);
if (nodes) {
const mapNodes = nodes.map(n => this.nodeToMapNode.convert(n));
this.mapNodesDataSource.set(mapNodes);
this.assignDataToLinks();
this.onDataUpdate();
this.assignDataToLinks();
this.onDataUpdate();
}
}
public setLinks(links: Link[]) {
const mapLinks = links.map(l => this.linkToMapLink.convert(l));
this.mapLinksDataSource.set(mapLinks);
if (links) {
const mapLinks = links.map(l => this.linkToMapLink.convert(l));
this.mapLinksDataSource.set(mapLinks);
this.assignDataToLinks();
this.onDataUpdate();
this.assignDataToLinks();
this.onDataUpdate();
}
}
public setDrawings(drawings: Drawing[]) {
const mapDrawings = drawings.map(d => this.drawingToMapDrawing.convert(d));
this.mapDrawingsDataSource.set(mapDrawings);
this.onDataUpdate();
if (drawings) {
const mapDrawings = drawings.map(d => this.drawingToMapDrawing.convert(d));
this.mapDrawingsDataSource.set(mapDrawings);
this.onDataUpdate();
}
}
public setSymbols(symbols: Symbol[]) {
const mapSymbols = symbols.map(s => this.symbolToMapSymbol.convert(s));
this.mapSymbolsDataSource.set(mapSymbols);
if (symbols) {
const mapSymbols = symbols.map(s => this.symbolToMapSymbol.convert(s));
this.mapSymbolsDataSource.set(mapSymbols);
}
}
public getNodes() {

View File

@ -5,6 +5,7 @@ export class Drawing {
project_id: string;
rotation: number;
svg: string;
locked: boolean;
x: number;
y: number;
z: number;

View File

@ -6,6 +6,7 @@ export class MapDrawing implements Indexed {
projectId: string;
rotation: number;
svg: string;
locked: boolean;
x: number;
y: number;
z: number;

View File

@ -12,6 +12,7 @@ export class MapNode implements Indexed {
firstPortName: string;
height: number;
label: MapLabel;
locked: boolean;
name: string;
nodeDirectory: string;
nodeType: string;

View File

@ -1,15 +1,75 @@
import { Label } from './label';
import { Port } from '../../models/port';
import { CustomAdapter } from '../../models/qemu/qemu-custom-adapter';
export class PortsMapping {
name: string;
interface?: string;
port_number: number;
type?: string;
}
export class Properties {
adapter_type: string;
adapters: number;
headless: boolean;
linked_clone: boolean;
on_close: string;
ram: number;
nvram: number;
usage: string;
use_any_adapter: boolean;
vmname: string;
ports_mapping: PortsMapping[];
mappings: any;
bios_image: string;
bios_image_md5sum?: any;
boot_priority: string;
cdrom_image: string;
cdrom_image_md5sum?: any;
cpu_throttling: number;
cpus: number;
hda_disk_image: string;
hda_disk_image_md5sum: string;
hda_disk_interface: string;
hdb_disk_image: string;
hdb_disk_image_md5sum?: any;
hdb_disk_interface: string;
hdc_disk_image: string;
hdc_disk_image_md5sum?: any;
hdc_disk_interface: string;
hdd_disk_image: string;
hdd_disk_image_md5sum?: any;
hdd_disk_interface: string;
initrd: string;
initrd_md5sum?: any;
kernel_command_line: string;
kernel_image: string;
kernel_image_md5sum?: any;
legacy_networking: boolean;
mac_address: string;
options: string;
platform: string;
process_priority: string;
qemu_path: string;
environment: string;
extra_hosts: string;
}
export class Node {
command_line: string;
compute_id: string;
console: number;
console_auto_start: boolean;
console_host: string;
console_type: string;
custom_adapters?: CustomAdapter[];
ethernet_adapters?: any;
serial_adapters?: any;
first_port_name: string;
height: number;
label: Label;
locked: boolean;
name: string;
node_directory: string;
node_id: string;
@ -18,6 +78,7 @@ export class Node {
port_segment_size: number;
ports: Port[];
project_id: string;
properties: Properties;
status: string;
symbol: string;
symbol_url: string; // @TODO: full URL to symbol, move to MapNode once converters are moved to app module

View File

@ -16,16 +16,17 @@ export class InterfaceStatusWidget implements Widget {
const link_group = select<SVGGElement, MapLink>(this);
const link_path = link_group.select<SVGPathElement>('path');
const start_point: SVGPoint = link_path.node().getPointAtLength(45);
const end_point: SVGPoint = link_path.node().getPointAtLength(link_path.node().getTotalLength() - 45);
let statuses = [];
if (link_path.node()) {
const start_point: SVGPoint = link_path.node().getPointAtLength(45);
const end_point: SVGPoint = link_path.node().getPointAtLength(link_path.node().getTotalLength() - 45);
if (link_path.node().getTotalLength() > 2 * 45 + 10) {
statuses = [
new LinkStatus(start_point.x, start_point.y, l.source.status),
new LinkStatus(end_point.x, end_point.y, l.target.status)
];
if (link_path.node().getTotalLength() > 2 * 45 + 10) {
statuses = [
new LinkStatus(start_point.x, start_point.y, l.source.status),
new LinkStatus(end_point.x, end_point.y, l.target.status)
];
}
}
const status_started = link_group

View File

@ -20,7 +20,9 @@ export class LinkWidget implements Widget {
private multiLinkCalculatorHelper: MultiLinkCalculatorHelper,
private interfaceLabelWidget: InterfaceLabelWidget,
private interfaceStatusWidget: InterfaceStatusWidget,
private selectionManager: SelectionManager
private selectionManager: SelectionManager,
private ethernetLinkWidget: EthernetLinkWidget,
private serialLinkWidget: SerialLinkWidget
) {}
public draw(view: SVGSelection) {
@ -88,11 +90,8 @@ export class LinkWidget implements Widget {
.attr('height', '48px')
.attr("xlink:href", "assets/resources/images/filter.svg");
const serial_link_widget = new SerialLinkWidget();
serial_link_widget.draw(link_body_merge);
const ethernet_link_widget = new EthernetLinkWidget();
ethernet_link_widget.draw(link_body_merge);
this.serialLinkWidget.draw(link_body_merge);
this.ethernetLinkWidget.draw(link_body_merge);
link_body_merge
.select<SVGPathElement>('path')

View File

@ -1,14 +1,19 @@
import { path } from 'd3-path';
import { EventEmitter, Injectable } from '@angular/core';
import { Widget } from '../widget';
import { SVGSelection } from '../../models/types';
import { MapLink } from '../../models/map/map-link';
import { LinkContextMenu } from '../../events/event-source';
class EthernetLinkPath {
constructor(public source: [number, number], public target: [number, number]) {}
}
export class EthernetLinkWidget implements Widget {
@Injectable() export class EthernetLinkWidget implements Widget {
public onContextMenu = new EventEmitter<LinkContextMenu>();
constructor() {}
private linktoEthernetLink(link: MapLink) {
return new EthernetLinkPath(
[link.source.x + link.source.width / 2, link.source.y + link.source.height / 2],
@ -27,9 +32,21 @@ export class EthernetLinkWidget implements Widget {
const link_enter = link
.enter()
.append<SVGPathElement>('path')
.attr('class', 'ethernet_link');
.attr('class', 'ethernet_link')
.on('contextmenu', (datum) => {
let link: MapLink = datum as unknown as MapLink;
const evt = event;
this.onContextMenu.emit(new LinkContextMenu(evt, link));
});
link_enter.attr('stroke', '#000').attr('stroke-width', '2');
link_enter
.attr('stroke', '#000')
.attr('stroke-width', '2')
.on('contextmenu', (datum) => {
let link: MapLink = datum as unknown as MapLink;
const evt = event;
this.onContextMenu.emit(new LinkContextMenu(evt, link));
});
const link_merge = link.merge(link_enter);

View File

@ -3,6 +3,8 @@ import { path } from 'd3-path';
import { Widget } from '../widget';
import { SVGSelection } from '../../models/types';
import { MapLink } from '../../models/map/map-link';
import { Injectable, EventEmitter } from '@angular/core';
import { LinkContextMenu } from '../../events/event-source';
class SerialLinkPath {
constructor(
@ -13,7 +15,11 @@ class SerialLinkPath {
) {}
}
export class SerialLinkWidget implements Widget {
@Injectable() export class SerialLinkWidget implements Widget {
public onContextMenu = new EventEmitter<LinkContextMenu>();
constructor() {}
private linkToSerialLink(link: MapLink) {
const source = {
x: link.source.x + link.source.width / 2,
@ -55,7 +61,12 @@ export class SerialLinkWidget implements Widget {
const link_enter = link
.enter()
.append<SVGPathElement>('path')
.attr('class', 'serial_link');
.attr('class', 'serial_link')
.on('contextmenu', (datum) => {
let link: MapLink = datum as unknown as MapLink;
const evt = event;
this.onContextMenu.emit(new LinkContextMenu(evt, link));
});
link_enter
.attr('stroke', '#B22222')

View File

@ -7,7 +7,7 @@ import { RouterTestingModule } from '@angular/router/testing';
import { Router } from '@angular/router';
import { BehaviorSubject, Observable } from 'rxjs';
class MockedRouter {
export class MockedRouter {
events: BehaviorSubject<boolean>;
constructor() {

View File

@ -48,6 +48,7 @@ describe('DrawingDraggedComponent', () => {
};
const mapDrawing: MapDrawing = {
id: 'sampleId',
locked: false,
projectId: 'sampleprojectId',
rotation: 0,
svg: 'sampleSvg',

View File

@ -51,6 +51,7 @@ describe('DrawingResizedComponent', () => {
};
const mapDrawing: MapDrawing = {
id: 'sampleId',
locked: false,
projectId: 'sampleprojectId',
rotation: 0,
svg: 'sampleSvg',

View File

@ -72,6 +72,7 @@ describe('LinkCreatedComponent', () => {
firstPortName: 'sampleFirstPortName',
height: 0,
label: {} as MapLabel,
locked: false,
name: 'sampleName',
nodeDirectory: 'sampleNodeDirectory',
nodeType: 'sampleNodeType',

View File

@ -52,6 +52,7 @@ describe('NodeDraggedComponent', () => {
firstPortName: 'sampleFirstPortName',
height: 0,
label: {} as MapLabel,
locked: false,
name: 'sampleName',
nodeDirectory: 'sampleNodeDirectory',
nodeType: 'sampleNodeType',

View File

@ -3,6 +3,19 @@
<div class="default-content">
<div class="container mat-elevation-z8">
<mat-accordion>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title> Useful shortcuts </mat-panel-title>
</mat-expansion-panel-header>
<mat-list>
<mat-list-item> ctrl + + to zoom in </mat-list-item>
<mat-list-item> ctrl + - to zoom out </mat-list-item>
<mat-list-item> ctrl + 0 to reset zoom </mat-list-item>
<mat-list-item> ctrl + a to select all items on map </mat-list-item>
<mat-list-item> ctrl + shift + a to deselect all items on map </mat-list-item>
<mat-list-item> ctrl + shift + s to go to preferences </mat-list-item>
</mat-list>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title> Third party components </mat-panel-title>

View File

@ -82,7 +82,7 @@
placeholder="Ethernet interface"
[ngModelOptions]="{standalone: true}"
[(ngModel)]="ethernetInterface">
<mat-option *ngFor="let type of " [value]="type">
<mat-option *ngFor="let type of ethernetInterfaces" [value]="type">
{{type}}
</mat-option>
</mat-select>

View File

@ -57,69 +57,13 @@
</mat-form-field>
</form>
</mat-expansion-panel>
<mat-expansion-panel *ngIf="newPort">
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
Port settings
</mat-panel-title>
</mat-expansion-panel-header>
<table class="table" mat-table [dataSource]="dataSource">
<ng-container matColumnDef="port_number">
<th mat-header-cell *matHeaderCellDef> Port number </th>
<td mat-cell *matCellDef="let element"> {{element.port_number}} </td>
</ng-container>
<ng-container matColumnDef="vlan">
<th mat-header-cell *matHeaderCellDef> VLAN </th>
<td mat-cell *matCellDef="let element"> {{element.vlan}} </td>
</ng-container>
<ng-container matColumnDef="type">
<th mat-header-cell *matHeaderCellDef> Type </th>
<td mat-cell *matCellDef="let element"> {{element.type}} </td>
</ng-container>
<ng-container matColumnDef="ethertype">
<th mat-header-cell *matHeaderCellDef> EtherType </th>
<td mat-cell *matCellDef="let element"> {{element.ethertype}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table><br/>
<mat-form-field class="form-field">
<input
matInput type="number"
[(ngModel)]="newPort.port_number"
placeholder="Port">
</mat-form-field>
<mat-form-field class="form-field">
<input
matInput type="number"
[(ngModel)]="newPort.vlan"
placeholder="VLAN">
</mat-form-field>
<mat-form-field class="select">
<mat-select
placeholder="Type"
[ngModelOptions]="{standalone: true}"
[(ngModel)]="newPort.type">
<mat-option *ngFor="let type of portTypes" [value]="type">
{{type}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="select">
<mat-select
placeholder="EtherType"
[ngModelOptions]="{standalone: true}"
[(ngModel)]="newPort.ethertype">
<mat-option *ngFor="let type of etherTypes" [value]="type">
{{type}}
</mat-option>
</mat-select>
</mat-form-field>
<button mat-button class="form-field" (click)="onAdd()">Add</button>
<app-ports #ports [ethernetPorts]="ethernetSwitchTemplate.ports_mapping"></app-ports>
</mat-expansion-panel>
</mat-accordion>
<div class="buttons-bar">

View File

@ -17,6 +17,7 @@ import { BuiltInTemplatesService } from '../../../../../services/built-in-templa
import { EthernetSwitchTemplate } from '../../../../../models/templates/ethernet-switch-template';
import { EthernetSwitchesTemplateDetailsComponent } from './ethernet-switches-template-details.component';
import { BuiltInTemplatesConfigurationService } from '../../../../../services/built-in-templates-configuration.service';
import { PortsComponent } from '../../../common/ports/ports.component';
export class MockedBuiltInTemplatesService {
public getTemplate(server: Server, template_id: string) {
@ -68,6 +69,7 @@ describe('EthernetSwitchesTemplateDetailsComponent', () => {
it('should call save template', () => {
spyOn(mockedBuiltInTemplatesService, 'saveTemplate').and.returnValue(of({} as EthernetSwitchTemplate));
component.portsComponent = {ethernetPorts: []} as PortsComponent;
component.inputForm.controls['templateName'].setValue('template name');
component.inputForm.controls['defaultName'].setValue('default name');
component.inputForm.controls['symbol'].setValue('symbol');
@ -102,7 +104,7 @@ describe('EthernetSwitchesTemplateDetailsComponent', () => {
expect(mockedBuiltInTemplatesService.saveTemplate).not.toHaveBeenCalled();
});
it('should call save template when symbol path is empty', () => {
it('should not call save template when symbol path is empty', () => {
spyOn(mockedBuiltInTemplatesService, 'saveTemplate').and.returnValue(of({} as EthernetSwitchTemplate));
component.inputForm.controls['templateName'].setValue('template name');
component.inputForm.controls['defaultName'].setValue('default name');

View File

@ -1,4 +1,4 @@
import { Component, OnInit } from "@angular/core";
import { Component, OnInit, ViewChild } from "@angular/core";
import { ActivatedRoute, Router } from '@angular/router';
import { ServerService } from '../../../../../services/server.service';
import { Server } from '../../../../../models/server';
@ -6,8 +6,8 @@ import { ToasterService } from '../../../../../services/toaster.service';
import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms';
import { BuiltInTemplatesService } from '../../../../../services/built-in-templates.service';
import { EthernetSwitchTemplate } from '../../../../../models/templates/ethernet-switch-template';
import { PortsMappingEntity } from '../../../../../models/ethernetHub/ports-mapping-enity';
import { BuiltInTemplatesConfigurationService } from '../../../../../services/built-in-templates-configuration.service';
import { PortsComponent } from '../../../common/ports/ports.component';
@Component({
@ -16,20 +16,13 @@ import { BuiltInTemplatesConfigurationService } from '../../../../../services/bu
styleUrls: ['./ethernet-switches-template-details.component.scss', '../../../preferences.component.scss']
})
export class EthernetSwitchesTemplateDetailsComponent implements OnInit {
@ViewChild(PortsComponent, {static: false}) portsComponent: PortsComponent;
server: Server;
ethernetSwitchTemplate: EthernetSwitchTemplate;
inputForm: FormGroup;
ethernetPorts: PortsMappingEntity[] = [];
dataSource: PortsMappingEntity[] = [];
newPort: PortsMappingEntity;
isSymbolSelectionOpened: boolean = false;
categories = [];
consoleTypes: string[] = [];
portTypes: string[] = [];
etherTypes: string[] = [];
displayedColumns: string[] = ['port_number', 'vlan', 'type', 'ethertype'];
constructor(
private route: ActivatedRoute,
@ -45,11 +38,6 @@ export class EthernetSwitchesTemplateDetailsComponent implements OnInit {
defaultName: new FormControl('', Validators.required),
symbol: new FormControl('', Validators.required)
});
this.newPort = {
name: '',
port_number: 0,
};
}
ngOnInit() {
@ -61,8 +49,6 @@ export class EthernetSwitchesTemplateDetailsComponent implements OnInit {
this.getConfiguration();
this.builtInTemplatesService.getTemplate(this.server, template_id).subscribe((ethernetSwitchTemplate: EthernetSwitchTemplate) => {
this.ethernetSwitchTemplate = ethernetSwitchTemplate;
this.ethernetPorts = this.ethernetSwitchTemplate.ports_mapping;
this.dataSource = this.ethernetSwitchTemplate.ports_mapping;
});
});
}
@ -70,18 +56,6 @@ export class EthernetSwitchesTemplateDetailsComponent implements OnInit {
getConfiguration() {
this.categories = this.builtInTemplatesConfigurationService.getCategoriesForEthernetSwitches();
this.consoleTypes = this.builtInTemplatesConfigurationService.getConsoleTypesForEthernetSwitches();
this.portTypes = this.builtInTemplatesConfigurationService.getPortTypesForEthernetSwitches();
this.etherTypes = this.builtInTemplatesConfigurationService.getEtherTypesForEthernetSwitches();
}
onAdd() {
this.ethernetPorts.push(this.newPort);
this.dataSource = [...this.ethernetPorts];
this.newPort = {
name: '',
port_number: 0,
};
}
goBack() {
@ -92,6 +66,7 @@ export class EthernetSwitchesTemplateDetailsComponent implements OnInit {
if (this.inputForm.invalid) {
this.toasterService.error(`Fill all required fields`);
} else {
this.ethernetSwitchTemplate.ports_mapping = this.portsComponent.ethernetPorts;
this.builtInTemplatesService.saveTemplate(this.server, this.ethernetSwitchTemplate).subscribe((ethernetSwitchTemplate: EthernetSwitchTemplate) => {
this.toasterService.success("Changes saved");
});

View File

@ -0,0 +1,35 @@
<table class="table" mat-table [dataSource]="adapters">
<ng-container matColumnDef="adapter_number">
<th mat-header-cell *matHeaderCellDef> Adapter number </th>
<td mat-cell *matCellDef="let element"> Adapter {{element.adapter_number}} </td>
</ng-container>
<ng-container matColumnDef="port_name">
<th mat-header-cell *matHeaderCellDef> Port name </th>
<td mat-cell *matCellDef="let element"> Ethernet {{element.adapter_number}} </td>
</ng-container>
<ng-container matColumnDef="adapter_type">
<th mat-header-cell *matHeaderCellDef> Adapter type </th>
<td mat-cell *matCellDef="let element; let i = index;">
<mat-select placeholder="Type" [(ngModel)]="element.adapter_type">
<mat-option *ngFor="let type of networkTypes" [value]="type">
{{type}}
</mat-option>
</mat-select>
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef> Actions </th>
<td mat-cell *matCellDef="let element">
<button mat-icon-button matTooltip="Delete adapter" (click)="delete(element)">
<mat-icon aria-label="Delete adapter">delete</mat-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<button mat-button class="form-field" (click)="onAdd()">Add</button>

View File

@ -0,0 +1,28 @@
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { CustomAdapter } from '../../../../models/qemu/qemu-custom-adapter';
@Component({
selector: 'app-custom-adapters-table',
templateUrl: './custom-adapters-table.component.html',
styleUrls: ['../../preferences.component.scss']
})
export class CustomAdaptersTableComponent {
@Input() networkTypes = [];
@Input() displayedColumns: string[] = [];
@Input() adapters: CustomAdapter[] = [];
public numberOfAdapters: number;
onAdd() {
let adapter: CustomAdapter = {
adapter_number: this.adapters.length,
adapter_type: this.networkTypes[0]
}
this.adapters = this.adapters.concat([adapter]);
}
delete(adapter: CustomAdapter) {
this.adapters = this.adapters.filter(elem => elem!== adapter);
}
}

View File

@ -6,31 +6,12 @@
</div>
<div class="default-content">
<div class="container mat-elevation-z8">
<table class="table" mat-table [dataSource]="adapters">
<ng-container matColumnDef="adapter_number">
<th mat-header-cell *matHeaderCellDef> Adapter number </th>
<td mat-cell *matCellDef="let element"> Adapter {{element.adapter_number}} </td>
</ng-container>
<ng-container matColumnDef="port_name">
<th mat-header-cell *matHeaderCellDef> Port name </th>
<td mat-cell *matCellDef="let element"> Ethernet {{element.adapter_number}} </td>
</ng-container>
<ng-container matColumnDef="adapter_type">
<th mat-header-cell *matHeaderCellDef> Adapter type </th>
<td mat-cell *matCellDef="let element; let i = index;">
<mat-select placeholder="Type" [(ngModel)]="adapters[i].adapter_type">
<mat-option *ngFor="let type of networkTypes" [value]="type[0]">
{{type[1]}} ({{type[0]}})
</mat-option>
</mat-select>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<app-custom-adapters-table
#customAdapters
[networkTypes]="networkTypes"
[displayedColumns]="displayedColumns"
[adapters]="adapters"
></app-custom-adapters-table>
</div>
<div class="buttons-bar">
<button mat-button (click)="cancelConfigureCustomAdapters()">Cancel</button>

View File

@ -4,6 +4,7 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { CommonModule } from '@angular/common';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { CustomAdaptersComponent } from './custom-adapters.component';
import { CustomAdaptersTableComponent } from '../custom-adapters-table/custom-adapters-table.component';
describe('Custom adapters component', () => {
let component: CustomAdaptersComponent;
@ -31,6 +32,7 @@ describe('Custom adapters component', () => {
it('should emit event when apply clicked', () => {
spyOn(component.saveConfigurationEmitter, 'emit');
component.customAdapters = {adapters: []} as CustomAdaptersTableComponent;
component.configureCustomAdapters();

View File

@ -1,5 +1,6 @@
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core';
import { CustomAdapter } from '../../../../models/qemu/qemu-custom-adapter';
import { CustomAdaptersTableComponent } from '../custom-adapters-table/custom-adapters-table.component';
@Component({
@ -13,14 +14,30 @@ export class CustomAdaptersComponent {
@Output() closeConfiguratorEmitter = new EventEmitter<boolean>();
@Output() saveConfigurationEmitter = new EventEmitter<CustomAdapter[]>();
@ViewChild("customAdapters", {static: false}) customAdapters: CustomAdaptersTableComponent;
public adapters: CustomAdapter[];
public numberOfAdapters: number;
constructor() {
console.log(this.networkTypes);
}
cancelConfigureCustomAdapters(){
this.closeConfiguratorEmitter.emit(false);
}
configureCustomAdapters(){
this.adapters = [];
console.log(this.customAdapters);
this.customAdapters.adapters.forEach(n => {
this.adapters.push({
adapter_number: n.adapter_number,
adapter_type: n.adapter_type
})
});
this.saveConfigurationEmitter.emit(this.adapters);
}
}

View File

@ -0,0 +1,66 @@
<table class="table" mat-table [dataSource]="ethernetPorts">
<ng-container matColumnDef="port_number">
<th mat-header-cell *matHeaderCellDef> Port number </th>
<td mat-cell *matCellDef="let element"> {{element.port_number}} </td>
</ng-container>
<ng-container matColumnDef="vlan">
<th mat-header-cell *matHeaderCellDef> VLAN </th>
<td mat-cell *matCellDef="let element"> {{element.vlan}} </td>
</ng-container>
<ng-container matColumnDef="type">
<th mat-header-cell *matHeaderCellDef> Type </th>
<td mat-cell *matCellDef="let element"> {{element.type}} </td>
</ng-container>
<ng-container matColumnDef="ethertype">
<th mat-header-cell *matHeaderCellDef> EtherType </th>
<td mat-cell *matCellDef="let element"> {{element.ethertype}} </td>
</ng-container>
<ng-container matColumnDef="action">
<th mat-header-cell *matHeaderCellDef> Actions </th>
<td mat-cell *matCellDef="let element">
<button mat-icon-button matTooltip="Delete port" (click)="delete(element)">
<mat-icon aria-label="Delete port">delete</mat-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table><br/>
<mat-form-field class="form-field">
<input
matInput type="number"
[(ngModel)]="newPort.port_number"
placeholder="Port">
</mat-form-field>
<mat-form-field class="form-field">
<input
matInput type="number"
[(ngModel)]="newPort.vlan"
placeholder="VLAN">
</mat-form-field>
<mat-form-field class="select">
<mat-select
placeholder="Type"
[ngModelOptions]="{standalone: true}"
[(ngModel)]="newPort.type">
<mat-option *ngFor="let type of portTypes" [value]="type">
{{type}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="select">
<mat-select
placeholder="EtherType"
[ngModelOptions]="{standalone: true}"
[(ngModel)]="newPort.ethertype">
<mat-option *ngFor="let type of etherTypes" [value]="type">
{{type}}
</mat-option>
</mat-select>
</mat-form-field>
<button mat-button class="form-field" (click)="onAdd()">Add</button>

View File

@ -0,0 +1,48 @@
import { Component, OnInit, Input, Output, EventEmitter } from "@angular/core";
import { Server } from '../../../../models/server';
import { PortsMappingEntity } from '../../../../models/ethernetHub/ports-mapping-enity';
import { BuiltInTemplatesConfigurationService } from '../../../../services/built-in-templates-configuration.service';
@Component({
selector: 'app-ports',
templateUrl: './ports.component.html',
styleUrls: ['../../preferences.component.scss']
})
export class PortsComponent implements OnInit {
@Input() ethernetPorts: PortsMappingEntity[] = [];
newPort: PortsMappingEntity = {
name: '',
port_number: 0,
};
portTypes: string[] = [];
etherTypes: string[] = [];
displayedColumns: string[] = ['port_number', 'vlan', 'type', 'ethertype', 'action'];
constructor(
private builtInTemplatesConfigurationService: BuiltInTemplatesConfigurationService
) {}
ngOnInit() {
this.getConfiguration();
}
getConfiguration() {
this.etherTypes = this.builtInTemplatesConfigurationService.getEtherTypesForEthernetSwitches();
this.portTypes = this.builtInTemplatesConfigurationService.getPortTypesForEthernetSwitches();
}
onAdd() {
this.ethernetPorts.push(this.newPort);
this.newPort = {
name: '',
port_number: 0,
};
}
delete(port: PortsMappingEntity) {
this.ethernetPorts = this.ethernetPorts.filter(n => n !== port);
}
}

View File

@ -1,7 +1,31 @@
<mat-card>
<input matInput type="text" [(ngModel)]="searchText" placeholder="Search by filename"><br/><br/>
<div class="menu">
<mat-radio-group aria-label="Select an option" class="radio-selection">
<mat-radio-button value="1" (click)="setFilter('all')" checked>All symbols</mat-radio-button>
<mat-radio-button value="2" (click)="setFilter('builtin')">Built-in symbols</mat-radio-button>
<mat-radio-button value="3" (click)="setFilter('custom')">Custom symbols</mat-radio-button>
</mat-radio-group>
<input
type="file"
accept=".svg, .bmp, .jpeg, .jpg, .gif, .png"
class="non-visible"
#file
(change)="uploadSymbolFile($event)"/>
<button mat-button (click)="file.click()">
<mat-icon>add</mat-icon>
Add symbol
</button>
</div>
<form>
<mat-form-field class="example-full-width">
<input matInput
placeholder="Search by filename"
[(ngModel)]="searchText"
[ngModelOptions]="{standalone: true}">
</mat-form-field>
</form>
<div class="wrapper">
<div class="buttonWrapper" *ngFor="let symbol of symbols | filenamefilter: searchText">
<div class="buttonWrapper" *ngFor="let symbol of filteredSymbols | filenamefilter: searchText">
<button [ngClass]="{ buttonSelected: isSelected === symbol.symbol_id }" class="button" (click)="setSelected(symbol.symbol_id)">
<img [ngClass]="{ imageSelected: isSelected === symbol.symbol_id }" class="image" src="http://127.0.0.1:3080/v2/symbols/{{symbol.symbol_id}}/raw"/>
</button>

View File

@ -8,6 +8,11 @@
outline: none;
}
.menu {
display: flex;
justify-content: space-between;
}
.button {
background: border-box;
border-width: 0px;
@ -36,3 +41,19 @@
grid-row-gap: 3em;
grid-column-gap: 1em;
}
.radio-selection {
width: 90%;
}
.mat-radio-button ~ .mat-radio-button {
margin-left: 16px;
}
.non-visible {
display: none;
}
.example-full-width {
width: 100%;
}

View File

@ -14,6 +14,10 @@ export class MockedSymbolService {
public list() {
return of([]);
}
public raw() {
return of('<svg></svg>')
}
}
describe('Symbols component', () => {

View File

@ -15,6 +15,7 @@ export class SymbolsComponent implements OnInit {
@Output() symbolChanged = new EventEmitter<string>();
symbols: Symbol[] = [];
filteredSymbols: Symbol[] = [];
isSelected: string = '';
searchText: string = '';
@ -24,14 +25,55 @@ export class SymbolsComponent implements OnInit {
ngOnInit() {
this.isSelected = this.symbol;
this.loadSymbols();
}
this.symbolService.list(this.server).subscribe((symbols: Symbol[]) => {
this.symbols = symbols;
});
setFilter(filter: string) {
if (filter === 'all') {
this.filteredSymbols = this.symbols;
} else if (filter === 'builtin') {
this.filteredSymbols = this.symbols.filter(elem => elem.builtin);
} else {
this.filteredSymbols = this.symbols.filter(elem => !elem.builtin);
}
}
setSelected(symbol_id: string) {
this.isSelected = symbol_id;
this.symbolChanged.emit(this.isSelected);
}
loadSymbols() {
this.symbolService.list(this.server).subscribe((symbols: Symbol[]) => {
this.symbols = symbols;
this.filteredSymbols = symbols;
});
}
public uploadSymbolFile(event) {
this.readSymbolFile(event.target);
}
private readSymbolFile(symbolInput) {
let file: File = symbolInput.files[0];
let fileName = symbolInput.files[0].name;
let fileReader: FileReader = new FileReader();
let imageToUpload = new Image();
fileReader.onloadend = () => {
let image = fileReader.result;
let svg = this.createSvgFileForImage(image, imageToUpload);
this.symbolService.add(this.server, fileName, svg).subscribe(() => {
this.loadSymbols();
});
}
imageToUpload.onload = () => { fileReader.readAsDataURL(file) };
imageToUpload.src = window.URL.createObjectURL(file);
}
private createSvgFileForImage(image: string|ArrayBuffer, imageToUpload: HTMLImageElement) {
return `<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" height=\"${imageToUpload.height}\"
width=\"${imageToUpload.width}\">\n<image height=\"${imageToUpload.height}\" width=\"${imageToUpload.width}\" xlink:href=\"${image}\"/>\n</svg>`
}
}

View File

@ -0,0 +1,59 @@
<table *ngIf="dataSourceUdp.length" class="table" mat-table [dataSource]="dataSourceUdp">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> Name </th>
<td mat-cell *matCellDef="let element"> {{element.name}} </td>
</ng-container>
<ng-container matColumnDef="rport">
<th mat-header-cell *matHeaderCellDef> Local port </th>
<td mat-cell *matCellDef="let element"> {{element.rport}} </td>
</ng-container>
<ng-container matColumnDef="rhost">
<th mat-header-cell *matHeaderCellDef> Type </th>
<td mat-cell *matCellDef="let element"> {{element.rhost}} </td>
</ng-container>
<ng-container matColumnDef="lport">
<th mat-header-cell *matHeaderCellDef> Remote port </th>
<td mat-cell *matCellDef="let element"> {{element.lport}} </td>
</ng-container>
<ng-container matColumnDef="action">
<th mat-header-cell *matHeaderCellDef> Actions </th>
<td mat-cell *matCellDef="let element">
<button mat-icon-button matTooltip="Delete port" (click)="delete(element)">
<mat-icon aria-label="Delete port">delete</mat-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<br *ngIf="dataSourceUdp.length"/>
<mat-form-field class="form-field">
<input
matInput type="text"
[(ngModel)]="newPort.name"
placeholder="Name">
</mat-form-field>
<mat-form-field class="form-field">
<input
matInput type="number"
[(ngModel)]="newPort.lport"
placeholder="Local port">
</mat-form-field>
<mat-form-field class="form-field">
<input
matInput type="text"
[(ngModel)]="newPort.rhost"
placeholder="Remote host">
</mat-form-field>
<mat-form-field class="form-field">
<input
matInput type="number"
[(ngModel)]="newPort.rport"
placeholder="Remote port">
</mat-form-field>
<button mat-button class="form-field" (click)="onAddUdpInterface()">Add</button>

View File

@ -0,0 +1,47 @@
import { Component, OnInit, Input, Output, EventEmitter } from "@angular/core";
import { Server } from '../../../../models/server';
import { PortsMappingEntity } from '../../../../models/ethernetHub/ports-mapping-enity';
import { BuiltInTemplatesConfigurationService } from '../../../../services/built-in-templates-configuration.service';
@Component({
selector: 'app-udp-tunnels',
templateUrl: './udp-tunnels.component.html',
styleUrls: ['../../preferences.component.scss']
})
export class UdpTunnelsComponent implements OnInit {
@Input() dataSourceUdp: PortsMappingEntity[] = [];
displayedColumns: string[] = ['name', 'lport', 'rhost', 'rport', 'action'];
newPort: PortsMappingEntity = {
name: '',
port_number: 0,
};
portTypes: string[] = [];
etherTypes: string[] = [];
constructor(
private builtInTemplatesConfigurationService: BuiltInTemplatesConfigurationService
) {}
ngOnInit() {
this.getConfiguration();
}
getConfiguration() {
this.etherTypes = this.builtInTemplatesConfigurationService.getEtherTypesForEthernetSwitches();
this.portTypes = this.builtInTemplatesConfigurationService.getPortTypesForEthernetSwitches();
}
onAddUdpInterface() {
this.dataSourceUdp = this.dataSourceUdp.concat([this.newPort]);
this.newPort = {
name: '',
port_number: 0,
};
}
delete(port: PortsMappingEntity) {
this.dataSourceUdp = this.dataSourceUdp.filter(n => n !== port);
}
}

View File

@ -31,6 +31,9 @@
<mat-list-item routerLink="/server/{{serverId}}/preferences/docker/templates">
Docker
</mat-list-item>
<!-- <mat-list-item routerLink="/server/{{serverId}}/preferences/traceng/templates">
TraceNG
</mat-list-item> -->
</mat-nav-list>
</div>
</div>

View File

@ -31,7 +31,7 @@ export class QemuVmTemplateDetailsComponent implements OnInit {
binaries: QemuBinary[] = [];
activateCpuThrottling: boolean = true;
isConfiguratorOpened: boolean = false;
displayedColumns: string[] = ['adapter_number', 'port_name', 'adapter_type'];
displayedColumns: string[] = ['adapter_number', 'port_name', 'adapter_type', 'actions'];
generalSettingsForm: FormGroup;
@ViewChild("customAdaptersConfigurator", {static: false})

View File

@ -0,0 +1,23 @@
<div class="content">
<div class="default-header">
<div class="row">
<h1 class="col">New VPCS node template</h1>
</div>
</div>
<div class="default-content">
<mat-card class="matCard">
<form [formGroup]="templateNameForm">
<mat-form-field class="form-field">
<input matInput formControlName="templateName" type="text" placeholder="Template name">
</mat-form-field>
<mat-form-field class="form-field">
<input matInput formControlName="ipAddress" type="text" placeholder="IP address">
</mat-form-field>
</form>
</mat-card>
<div class="buttons-bar">
<button mat-button class="cancel-button" (click)="goBack()">Cancel</button>
<button mat-raised-button color="primary" (click)="addTemplate()">Add template</button>
</div>
</div>
</div>

View File

@ -0,0 +1,67 @@
import { Component, OnInit } from "@angular/core";
import { Server } from '../../../../models/server';
import { ActivatedRoute, Router } from '@angular/router';
import { ServerService } from '../../../../services/server.service';
import { ToasterService } from '../../../../services/toaster.service';
import { v4 as uuid } from 'uuid';
import { TemplateMocksService } from '../../../../services/template-mocks.service';
import { FormGroup, FormBuilder, FormControl, Validators } from '@angular/forms';
import { TracengService } from '../../../../services/traceng.service';
import { TracengTemplate } from '../../../../models/templates/traceng-template';
@Component({
selector: 'app-add-traceng-template',
templateUrl: './add-traceng-template.component.html',
styleUrls: ['./add-traceng-template.component.scss', '../../preferences.component.scss']
})
export class AddTracengTemplateComponent implements OnInit {
server: Server;
templateName: string = '';
ipAddress: string = '';
templateNameForm: FormGroup
constructor(
private route: ActivatedRoute,
private serverService: ServerService,
private tracengService: TracengService,
private router: Router,
private toasterService: ToasterService,
private templateMocksService: TemplateMocksService,
private formBuilder: FormBuilder
) {
this.templateNameForm = this.formBuilder.group({
templateName: new FormControl(null, [Validators.required]),
ipAddress: new FormControl(null, [Validators.required])
});
}
ngOnInit() {
const server_id = this.route.snapshot.paramMap.get("server_id");
this.serverService.get(parseInt(server_id, 10)).then((server: Server) => {
this.server = server;
});
}
goBack() {
this.router.navigate(['/server', this.server.id, 'preferences', 'traceng', 'templates']);
}
addTemplate() {
if (!this.templateNameForm.invalid) {
this.templateName = this.templateNameForm.get('templateName').value;
this.ipAddress = this.templateNameForm.get('ipAddress').value;
let tracengTemplate: TracengTemplate = this.templateMocksService.getTracengTemplate();
tracengTemplate.template_id = uuid();
tracengTemplate.name = this.templateName;
tracengTemplate.ip_address = this.ipAddress;
this.tracengService.addTemplate(this.server, tracengTemplate).subscribe(() => {
this.goBack();
});
} else {
this.toasterService.error(`Fill all required fields`);
}
}
}

View File

@ -0,0 +1,12 @@
<div class="content">
<div class="default-header">
<div class="row">
<h1 class="col">TraceNG preferences</h1>
</div>
</div>
<div class="default-content">
<mat-form-field class="form-field">
<input matInput type="text" [(ngModel)]="tracengExecutable" placeholder="Path to TraceNG executable"/>
</mat-form-field>
</div>
</div>

View File

@ -0,0 +1,3 @@
.form-field {
width: 100%;
}

View File

@ -0,0 +1,32 @@
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from '@angular/router';
import { Server } from '../../../../models/server';
import { ServerService } from '../../../../services/server.service';
@Component({
selector: 'app-traceng-preferences',
templateUrl: './traceng-preferences.component.html',
styleUrls: ['./traceng-preferences.component.scss']
})
export class TracengPreferencesComponent implements OnInit {
server: Server;
tracengExecutable: string;
constructor(
private route: ActivatedRoute,
private serverService: ServerService
) {}
ngOnInit() {
const server_id = this.route.snapshot.paramMap.get("server_id");
this.serverService.get(parseInt(server_id, 10)).then((server: Server) => {
this.server = server;
});
}
restoreDefaults(){
this.tracengExecutable = '';
}
}

View File

@ -0,0 +1,40 @@
<div class="content" [ngClass]="{ shadowed: isSymbolSelectionOpened }">
<div class="default-header">
<div class="row">
<h1 class="col">TraceNG device configuration</h1>
</div>
</div>
<div class="default-content" *ngIf="tracengTemplate">
<mat-card class="matCard">
<form [formGroup]="inputForm">
<mat-form-field class="form-field">
<input
matInput type="text"
[(ngModel)]="tracengTemplate.name"
formControlName="templateName"
placeholder="Template name">
</mat-form-field>
<mat-form-field class="form-field">
<input
matInput type="text"
[(ngModel)]="tracengTemplate.default_name_format"
formControlName="defaultName"
placeholder="Default name format">
</mat-form-field>
<mat-form-field class="form-field">
<input
matInput type="text"
[(ngModel)]="tracengTemplate.symbol"
formControlName="symbol"
placeholder="Symbol">
</mat-form-field>
<button mat-button class="symbolSelectionButton" (click)="chooseSymbol()">Choose symbol</button><br/><br/>
</form>
</mat-card>
<div class="buttons-bar">
<button class="cancel-button" (click)="goBack()" mat-button>Cancel</button>
<button mat-raised-button color="primary" (click)="onSave()">Save</button>
</div>
</div>
</div>
<app-symbols-menu *ngIf="isSymbolSelectionOpened && tracengTemplate" [server]="server" [symbol]="tracengTemplate.symbol" (symbolChangedEmitter)="symbolChanged($event)"></app-symbols-menu>

View File

@ -0,0 +1,71 @@
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from '@angular/router';
import { ServerService } from '../../../../services/server.service';
import { Server } from '../../../../models/server';
import { ToasterService } from '../../../../services/toaster.service';
import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms';
import { TracengService } from '../../../../services/traceng.service';
import { TracengTemplate } from '../../../../models/templates/traceng-template';
@Component({
selector: 'app-traceng-template-details',
templateUrl: './traceng-template-details.component.html',
styleUrls: ['./traceng-template-details.component.scss', '../../preferences.component.scss']
})
export class TracengTemplateDetailsComponent implements OnInit {
server: Server;
tracengTemplate: TracengTemplate;
inputForm: FormGroup;
isSymbolSelectionOpened: boolean = false;
constructor(
private route: ActivatedRoute,
private serverService: ServerService,
private tracengService: TracengService,
private toasterService: ToasterService,
private formBuilder: FormBuilder,
private router: Router
) {
this.inputForm = this.formBuilder.group({
templateName: new FormControl('', Validators.required),
defaultName: new FormControl('', Validators.required),
symbol: new FormControl('', Validators.required)
});
}
ngOnInit() {
const server_id = this.route.snapshot.paramMap.get("server_id");
const template_id = this.route.snapshot.paramMap.get("template_id");
this.serverService.get(parseInt(server_id, 10)).then((server: Server) => {
this.server = server;
this.tracengService.getTemplate(this.server, template_id).subscribe((tracengTemplate: TracengTemplate) => {
this.tracengTemplate = tracengTemplate;
});
});
}
goBack() {
this.router.navigate(['/server', this.server.id, 'preferences', 'traceng', 'templates']);
}
onSave() {
if (this.inputForm.invalid) {
this.toasterService.error(`Fill all required fields`);
} else {
this.tracengService.saveTemplate(this.server, this.tracengTemplate).subscribe((tracengTemplate: TracengTemplate) => {
this.toasterService.success("Changes saved");
});
}
}
chooseSymbol() {
this.isSymbolSelectionOpened = !this.isSymbolSelectionOpened;
}
symbolChanged(chosenSymbol: string) {
this.isSymbolSelectionOpened = !this.isSymbolSelectionOpened;
this.tracengTemplate.symbol = chosenSymbol;
}
}

View File

@ -0,0 +1,32 @@
<div class="content">
<div class="default-header">
<div class="row">
<h1 class="col">TraceNG node templates</h1>
<button *ngIf="server" class="top-button" class="cancel-button" routerLink="/server/{{server.id}}/preferences" mat-button>Back</button>
<button *ngIf="server" class="top-button" routerLink="/server/{{server.id}}/preferences/traceng/addtemplate" mat-raised-button color="primary">Add TraceNG template</button>
</div>
</div>
<app-empty-templates-list *ngIf="!tracengTemplates.length"></app-empty-templates-list>
<div class="default-content" *ngIf="tracengTemplates.length">
<div class="listcontainer mat-elevation-z8">
<mat-nav-list *ngIf="server">
<div class="list-item" *ngFor='let template of tracengTemplates'>
<mat-list-item class="template-name" routerLink="{{template.template_id}}">{{template.name}}</mat-list-item>
<button mat-button class="menu-button" [matMenuTriggerFor]="menu">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item (click)="deleteTemplate(template)">
<mat-icon>delete</mat-icon><span>Delete</span>
</button>
</mat-menu>
</div>
</mat-nav-list>
</div>
</div>
</div>
<app-delete-template
#deleteComponent
[server]="server"
(deleteEvent)="onDeleteEvent()">
</app-delete-template>

View File

@ -0,0 +1,46 @@
import { Component, OnInit, ViewChild } from "@angular/core";
import { Server } from '../../../../models/server';
import { ActivatedRoute } from '@angular/router';
import { ServerService } from '../../../../services/server.service';
import { DeleteTemplateComponent } from '../../common/delete-template-component/delete-template.component';
import { TracengTemplate } from '../../../../models/templates/traceng-template';
import { TracengService } from '../../../../services/traceng.service';
@Component({
selector: 'app-traceng-templates',
templateUrl: './traceng-templates.component.html',
styleUrls: ['./traceng-templates.component.scss', '../../preferences.component.scss']
})
export class TracengTemplatesComponent implements OnInit {
server: Server;
tracengTemplates: TracengTemplate[] = [];
@ViewChild(DeleteTemplateComponent, {static: false}) deleteComponent: DeleteTemplateComponent;
constructor(
private route: ActivatedRoute,
private serverService: ServerService,
private tracengService: TracengService
) {}
ngOnInit() {
const server_id = this.route.snapshot.paramMap.get("server_id");
this.serverService.get(parseInt(server_id, 10)).then((server: Server) => {
this.server = server;
this.getTemplates();
});
}
getTemplates() {
this.tracengService.getTemplates(this.server).subscribe((tracengTemplates: TracengTemplate[]) => {
this.tracengTemplates = tracengTemplates.filter((elem) => elem.template_type === 'traceng' && !elem.builtin);
});
}
deleteTemplate(template: TracengTemplate) {
this.deleteComponent.deleteItem(template.name, template.template_id);
}
onDeleteEvent() {
this.getTemplates();
}
}

View File

@ -24,7 +24,7 @@ export class VirtualBoxTemplateDetailsComponent implements OnInit {
onCloseOptions = [];
categories = [];
networkTypes = [];
displayedColumns: string[] = ['adapter_number', 'port_name', 'adapter_type'];
displayedColumns: string[] = ['adapter_number', 'port_name', 'adapter_type', 'actions'];
isConfiguratorOpened: boolean = false;
generalSettingsForm: FormGroup;
networkForm: FormGroup

View File

@ -20,7 +20,7 @@ export class VmwareTemplateDetailsComponent implements OnInit {
server: Server;
vmwareTemplate: VmwareTemplate;
generalSettingsForm: FormGroup;
displayedColumns: string[] = ['adapter_number', 'port_name', 'adapter_type'];
displayedColumns: string[] = ['adapter_number', 'port_name', 'adapter_type', 'actions'];
isConfiguratorOpened: boolean = false;
isSymbolSelectionOpened: boolean = false;
consoleTypes: string[] = [];

View File

@ -0,0 +1,12 @@
<h1 mat-dialog-title>Change symbol for node: {{node.name}}</h1>
<div class="modal-form-container">
<div class="symbolsWrapper">
<app-symbols [server]="server" [symbol]="symbol" (symbolChanged)="symbolChanged($event)"></app-symbols>
</div>
</div>
<div mat-dialog-actions>
<button mat-button (click)="onCloseClick()" color="accent">Cancel</button>
<button mat-button (click)="onSelectClick()" tabindex="2" mat-raised-button color="primary">Apply</button>
</div>

View File

@ -0,0 +1,19 @@
.symbolsWrapper {
height: 350px;
overflow-y: scroll;
scrollbar-color: darkgrey #263238;
scrollbar-width: thin;
}
::-webkit-scrollbar {
width: 0.5em;
}
::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
}
::-webkit-scrollbar-thumb {
background-color: darkgrey;
outline: 1px solid #263238;
}

View File

@ -0,0 +1,41 @@
import { Component, Input, OnInit } from '@angular/core';
import { MatDialogRef } from '@angular/material';
import { Message } from '../../../models/message';
import { Server } from '../../../models/server';
import { Node } from '../../../cartography/models/node';
import { Symbol } from '../../../models/symbol';
import { NodeService } from '../../../services/node.service';
@Component({
selector: 'app-change-symbol-dialog',
templateUrl: './change-symbol-dialog.component.html',
styleUrls: ['./change-symbol-dialog.component.scss']
})
export class ChangeSymbolDialogComponent implements OnInit {
@Input() server: Server;
@Input() node: Node;
symbol: string;
constructor(
public dialogRef: MatDialogRef<ChangeSymbolDialogComponent>,
private nodeService: NodeService
) {}
ngOnInit() {
this.symbol = this.node.symbol;
}
symbolChanged(chosenSymbol: string) {
this.symbol = chosenSymbol;
}
onCloseClick() {
this.dialogRef.close();
}
onSelectClick() {
this.nodeService.updateSymbol(this.server, this.node, this.symbol).subscribe(() => {
this.onCloseClick()
});
}
}

View File

@ -0,0 +1,4 @@
<button mat-menu-item (click)="bringToFront()">
<mat-icon>vertical_align_top</mat-icon>
<span>Bring to front</span>
</button>

View File

@ -0,0 +1,66 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { BringToFrontActionComponent } from './bring-to-front-action.component';
import { MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule } from '@angular/material';
import { CommonModule } from '@angular/common';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { MockedDrawingService, MockedDrawingsDataSource, MockedNodesDataSource, MockedNodeService } from '../../../project-map.component.spec';
import { DrawingService } from '../../../../../services/drawing.service';
import { NodesDataSource } from '../../../../../cartography/datasources/nodes-datasource';
import { DrawingsDataSource } from '../../../../../cartography/datasources/drawings-datasource';
import { NodeService } from '../../../../../services/node.service';
import { Node } from '../../../../../cartography/models/node';
import { of } from 'rxjs';
import { ComponentFactoryResolver } from '@angular/core';
import { Drawing } from '../../../../../cartography/models/drawing';
describe('BringToFrontActionComponent', () => {
let component: BringToFrontActionComponent;
let fixture: ComponentFixture<BringToFrontActionComponent>;
let drawingService = new MockedDrawingService();
let drawingsDataSource = new MockedDrawingsDataSource();
let nodeService = new MockedNodeService();
let nodesDataSource = new MockedNodesDataSource();
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule],
providers: [
{ provide: DrawingService, useValue: drawingService },
{ provide: DrawingsDataSource, useValue: drawingsDataSource },
{ provide: NodeService, useValue: nodeService },
{ provide: NodesDataSource, useValue: nodesDataSource },
],
declarations: [BringToFrontActionComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(BringToFrontActionComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should call node service when bring to front action called', () => {
spyOn(nodeService, 'update').and.returnValue(of());
component.nodes = [{z: 0} as Node];
component.drawings = [];
component.bringToFront();
expect(nodeService.update).toHaveBeenCalled();
});
it('should call drawing service when bring to front action called', () => {
spyOn(drawingService, 'update').and.returnValue(of());
component.nodes = [];
component.drawings = [{z: 0} as Drawing];
component.bringToFront();
expect(drawingService.update).toHaveBeenCalled();
});
});

View File

@ -0,0 +1,43 @@
import { Component, OnInit, Input } from '@angular/core';
import { Server } from '../../../../../models/server';
import { Node } from '../../../../../cartography/models/node';
import { NodesDataSource } from '../../../../../cartography/datasources/nodes-datasource';
import { NodeService } from '../../../../../services/node.service';
import { Drawing } from '../../../../../cartography/models/drawing';
import { DrawingsDataSource } from '../../../../../cartography/datasources/drawings-datasource';
import { DrawingService } from '../../../../../services/drawing.service';
@Component({
selector: 'app-bring-to-front-action',
templateUrl: './bring-to-front-action.component.html'
})
export class BringToFrontActionComponent implements OnInit {
@Input() server: Server;
@Input() nodes: Node[];
@Input() drawings: Drawing[];
constructor(
private nodesDataSource: NodesDataSource,
private drawingsDataSource: DrawingsDataSource,
private nodeService: NodeService,
private drawingService: DrawingService
) {}
ngOnInit() {}
bringToFront() {
this.nodes.forEach((node) => {
node.z = 100;
this.nodesDataSource.update(node);
this.nodeService.update(this.server, node).subscribe((node: Node) => {});
});
this.drawings.forEach((drawing) => {
drawing.z = 100;
this.drawingsDataSource.update(drawing);
this.drawingService.update(this.server, drawing).subscribe((drawing: Drawing) => {});
});
}
}

View File

@ -0,0 +1,4 @@
<button mat-menu-item (click)="changeSymbol()">
<mat-icon>edit</mat-icon>
<span>Change symbol</span>
</button>

View File

@ -0,0 +1,29 @@
import { Component, OnInit, Input } from '@angular/core';
import { Server } from '../../../../../models/server';
import { Node } from '../../../../../cartography/models/node';
import { MatDialog } from '@angular/material';
import { ChangeSymbolDialogComponent } from '../../../change-symbol-dialog/change-symbol-dialog.component';
@Component({
selector: 'app-change-symbol-action',
templateUrl: './change-symbol-action.component.html'
})
export class ChangeSymbolActionComponent implements OnInit {
@Input() server: Server;
@Input() node: Node;
constructor(private dialog: MatDialog) {}
ngOnInit() {}
changeSymbol() {
const dialogRef = this.dialog.open(ChangeSymbolDialogComponent, {
width: '1000px',
height: '500px',
autoFocus: false
});
let instance = dialogRef.componentInstance;
instance.server = this.server;
instance.node = this.node;
}
}

View File

@ -0,0 +1,4 @@
<button mat-menu-item (click)="configureNode()">
<mat-icon>settings_applications</mat-icon>
<span>Configure</span>
</button>

View File

@ -0,0 +1,71 @@
import { Component, Input, OnInit, OnChanges } from '@angular/core';
import { Server } from '../../../../../models/server';
import { Node } from '../../../../../cartography/models/node';
import { MatDialog, MatDialogRef } from '@angular/material';
import { ConfiguratorDialogVpcsComponent } from '../../../node-editors/configurator/vpcs/configurator-vpcs.component';
import { ConfiguratorDialogEthernetHubComponent } from '../../../node-editors/configurator/ethernet_hub/configurator-ethernet-hub.component';
import { ConfiguratorDialogEthernetSwitchComponent } from '../../../node-editors/configurator/ethernet-switch/configurator-ethernet-switch.component';
import { ConfiguratorDialogSwitchComponent } from '../../../node-editors/configurator/switch/configurator-switch.component';
import { ConfiguratorDialogVirtualBoxComponent } from '../../../node-editors/configurator/virtualbox/configurator-virtualbox.component';
import { ConfiguratorDialogQemuComponent } from '../../../node-editors/configurator/qemu/configurator-qemu.component';
import { ConfiguratorDialogCloudComponent } from '../../../node-editors/configurator/cloud/configurator-cloud.component';
import { ConfiguratorDialogAtmSwitchComponent } from '../../../node-editors/configurator/atm_switch/configurator-atm-switch.component';
import { ConfiguratorDialogVmwareComponent } from '../../../node-editors/configurator/vmware/configurator-vmware.component';
import { ConfiguratorDialogIouComponent } from '../../../node-editors/configurator/iou/configurator-iou.component';
import { ConfiguratorDialogIosComponent } from '../../../node-editors/configurator/ios/configurator-ios.component';
import { ConfiguratorDialogDockerComponent } from '../../../node-editors/configurator/docker/configurator-docker.component';
import { ConfiguratorDialogNatComponent } from '../../../node-editors/configurator/nat/configurator-nat.component';
import { ConfiguratorDialogTracengComponent } from '../../../node-editors/configurator/traceng/configurator-traceng.component';
@Component({
selector: 'app-config-node-action',
templateUrl: './config-action.component.html'
})
export class ConfigActionComponent {
@Input() server: Server;
@Input() node: Node;
private conf = {
autoFocus: false,
width: '800px'
};
dialogRef;
constructor(private dialog: MatDialog) {}
configureNode() {
if (this.node.node_type === 'vpcs') {
this.dialogRef = this.dialog.open(ConfiguratorDialogVpcsComponent, this.conf);
} else if (this.node.node_type === 'ethernet_hub') {
this.dialogRef = this.dialog.open(ConfiguratorDialogEthernetHubComponent, this.conf);
} else if (this.node.node_type === 'ethernet_switch') {
this.dialogRef = this.dialog.open(ConfiguratorDialogEthernetSwitchComponent, this.conf);
} else if (this.node.node_type === 'cloud') {
this.dialogRef = this.dialog.open(ConfiguratorDialogCloudComponent, this.conf);
} else if (this.node.node_type === 'dynamips') {
this.dialogRef = this.dialog.open(ConfiguratorDialogIosComponent, this.conf);
} else if (this.node.node_type === 'iou') {
this.dialogRef = this.dialog.open(ConfiguratorDialogIouComponent, this.conf);
} else if (this.node.node_type === 'qemu') {
this.dialogRef = this.dialog.open(ConfiguratorDialogQemuComponent, this.conf);
} else if (this.node.node_type === 'virtualbox') {
this.dialogRef = this.dialog.open(ConfiguratorDialogVirtualBoxComponent, this.conf);
} else if (this.node.node_type === 'vmware') {
this.dialogRef = this.dialog.open(ConfiguratorDialogVmwareComponent, this.conf);
} else if (this.node.node_type === 'docker') {
this.dialogRef = this.dialog.open(ConfiguratorDialogDockerComponent, this.conf);
} else if (this.node.node_type === 'nat') {
this.dialogRef = this.dialog.open(ConfiguratorDialogNatComponent, this.conf);
} else if (this.node.node_type === 'frame_relay_switch') {
this.dialogRef = this.dialog.open(ConfiguratorDialogSwitchComponent, this.conf);
} else if (this.node.node_type === 'atm_switch') {
this.dialogRef = this.dialog.open(ConfiguratorDialogAtmSwitchComponent, this.conf);
} else if (this.node.node_type === 'traceng') {
this.dialogRef = this.dialog.open(ConfiguratorDialogTracengComponent, this.conf);
}
let instance = this.dialogRef.componentInstance;
instance.server = this.server;
instance.node = this.node;
}
}

View File

@ -0,0 +1,4 @@
<button mat-menu-item (click)="openConsole()">
<mat-icon>web_asset</mat-icon>
<span>Console</span>
</button>

View File

@ -0,0 +1,43 @@
import { Component, Input } from '@angular/core';
import { Node } from '../../../../../cartography/models/node';
import { ToasterService } from '../../../../../services/toaster.service';
import { NodeService } from '../../../../../services/node.service';
import { Server } from '../../../../../models/server';
@Component({
selector: 'app-console-device-action-browser',
templateUrl: './console-device-action-browser.component.html'
})
export class ConsoleDeviceActionBrowserComponent {
@Input() server: Server;
@Input() node: Node;
constructor(
private toasterService: ToasterService,
private nodeService: NodeService
) {}
openConsole() {
this.nodeService.getNode(this.server, this.node).subscribe((node: Node) => {
this.node = node;
this.startConsole();
});
}
startConsole() {
if (this.node.status !== "started") {
this.toasterService.error("This node must be started before a console can be opened");
} else {
if (this.node.console_type === "telnet") {
location.assign(`gns3+telnet://${this.node.console_host}:${this.node.console}?name=${this.node.name}&project_id=${this.node.project_id}&node_id=${this.node.node_id}`);
} else if (this.node.console_type === "vnc") {
location.assign(`gns3+vnc://${this.node.console_host}:${this.node.console}?name=${this.node.name}&project_id=${this.node.project_id}&node_id=${this.node.node_id}`);
} else if(this.node.console_type === "spice") {
location.assign(`gns3+spice://${this.node.console_host}:${this.node.console}?name=${this.node.name}&project_id=${this.node.project_id}&node_id=${this.node.node_id}`);
} else {
this.toasterService.error("Supported console types: telnet, vnc, spice.");
}
}
}
}

View File

@ -11,6 +11,8 @@ import { SettingsService } from '../../../../../services/settings.service';
import { MockedSettingsService } from '../../../../../services/settings.service.spec';
import { Node } from '../../../../../cartography/models/node';
import { Server } from '../../../../../models/server';
import { MockedNodeService } from '../../../project-map.component.spec';
import { NodeService } from '../../../../../services/node.service';
describe('ConsoleDeviceActionComponent', () => {
@ -20,7 +22,8 @@ describe('ConsoleDeviceActionComponent', () => {
let server: Server;
let mockedSettingsService: MockedSettingsService;
let mockedServerService: MockedServerService;
let mockedToaster: MockedToasterService
let mockedToaster: MockedToasterService;
let mockedNodeService: MockedNodeService = new MockedNodeService();
beforeEach(() => {
electronService = {
@ -47,7 +50,8 @@ describe('ConsoleDeviceActionComponent', () => {
{ provide: ElectronService, useValue: electronService },
{ provide: ServerService, useValue: mockedServerService },
{ provide: SettingsService, useValue: mockedSettingsService },
{ provide: ToasterService, useValue: mockedToaster }
{ provide: ToasterService, useValue: mockedToaster },
{ provide: NodeService, useValue: mockedNodeService }
],
imports: [
MatIconModule

View File

@ -5,6 +5,7 @@ import { ElectronService } from 'ngx-electron';
import { ServerService } from '../../../../../services/server.service';
import { SettingsService } from '../../../../../services/settings.service';
import { ToasterService } from '../../../../../services/toaster.service';
import { NodeService } from '../../../../../services/node.service';
@Component({
selector: 'app-console-device-action',
@ -18,19 +19,14 @@ export class ConsoleDeviceActionComponent implements OnInit {
private electronService: ElectronService,
private serverService: ServerService,
private settingsService: SettingsService,
private toasterService: ToasterService
private toasterService: ToasterService,
private nodeService: NodeService
) { }
ngOnInit() {
}
ngOnInit() {}
async console() {
let consoleCommand = this.settingsService.get<string>('console_command');
if(consoleCommand === undefined) {
consoleCommand = `putty.exe -telnet \%h \%p -wt \"\%d\" -gns3 5 -skin 4`;
}
let consoleCommand = this.settingsService.get<string>('console_command') ? this.settingsService.get<string>('console_command') : this.nodeService.getDefaultCommand();
const startedNodes = this.nodes.filter(node => node.status === 'started');
if(startedNodes.length === 0) {

View File

@ -45,7 +45,9 @@ export class DuplicateActionComponent {
})
}
runningNodes = runningNodes.substring(0, runningNodes.length-2);
this.toasterService.error(`Cannot duplicate node data for nodes: ${runningNodes}`);
if (runningNodes.length > 0) {
runningNodes = runningNodes.substring(0, runningNodes.length-2);
this.toasterService.error(`Cannot duplicate node data for nodes: ${runningNodes}`);
}
}
}

View File

@ -0,0 +1,6 @@
<button mat-menu-item
*ngIf="node.node_type==='vpcs' || node.node_type==='iou' || node.node_type==='dynamips'"
(click)="editConfig()">
<mat-icon>settings</mat-icon>
<span>Edit config</span>
</button>

View File

@ -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;
}
}

View File

@ -20,7 +20,7 @@ export class EditStyleActionComponent implements OnInit {
editStyle() {
const dialogRef = this.dialog.open(StyleEditorDialogComponent, {
width: '300px',
width: '800px',
autoFocus: false
});
let instance = dialogRef.componentInstance;

View File

@ -0,0 +1,6 @@
<button mat-menu-item
*ngIf="node.node_type==='vpcs' || node.node_type==='dynamips' || node.node_type==='iou'"
(click)="exportConfig()">
<mat-icon>call_made</mat-icon>
<span>Export config</span>
</button>

View File

@ -0,0 +1,37 @@
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.getStartupConfiguration(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)}`);
if (this.node.node_type === 'vpcs') {
element.setAttribute('download', `${this.node.name}_startup.vpc`);
} else if (this.node.node_type === 'iou' || this.node.node_type === 'dynamips') {
element.setAttribute('download', `${this.node.name}_startup.cfg`);
}
var event = new MouseEvent("click");
element.dispatchEvent(event);
}
}

View File

@ -0,0 +1,4 @@
<button mat-menu-item (click)="importConfig()">
<mat-icon>call_received</mat-icon>
<span>Import config</span>
</button>

View File

@ -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
}
}

View File

@ -0,0 +1,4 @@
<button mat-menu-item (click)="lock()">
<mat-icon>lock</mat-icon>
<span>{{command}}</span>
</button>

View File

@ -0,0 +1,48 @@
import { Component, OnInit, Input, OnDestroy, OnChanges } from '@angular/core';
import { Server } from '../../../../../models/server';
import { Node } from '../../../../../cartography/models/node';
import { NodesDataSource } from '../../../../../cartography/datasources/nodes-datasource';
import { NodeService } from '../../../../../services/node.service';
import { Drawing } from '../../../../../cartography/models/drawing';
import { DrawingsDataSource } from '../../../../../cartography/datasources/drawings-datasource';
import { DrawingService } from '../../../../../services/drawing.service';
@Component({
selector: 'app-lock-action',
templateUrl: './lock-action.component.html'
})
export class LockActionComponent implements OnChanges {
@Input() server: Server;
@Input() nodes: Node[];
@Input() drawings: Drawing[];
command: string;
constructor(
private nodesDataSource: NodesDataSource,
private drawingsDataSource: DrawingsDataSource,
private nodeService: NodeService,
private drawingService: DrawingService
) {}
ngOnChanges() {
if (this.nodes.length === 1 && this.drawings.length === 0) {
this.command = this.nodes[0].locked ? 'Unlock item' : 'Lock item';
} else if (this.nodes.length === 0 && this.drawings.length === 1) {
this.command = this.drawings[0].locked ? 'Unlock item' : 'Lock item';
} else {
this.command = 'Lock/unlock items';
}
}
lock() {
this.nodes.forEach((node) => {
node.locked = !node.locked;
this.nodeService.updateNode(this.server, node).subscribe((node) => { this.nodesDataSource.update(node) });
});
this.drawings.forEach((drawing) => {
drawing.locked = ! drawing.locked;
this.drawingService.update(this.server, drawing).subscribe((drawing) => { this.drawingsDataSource.update(drawing) });
});
}
}

View File

@ -0,0 +1,4 @@
<button mat-menu-item *ngIf="filteredNodes.length > 0" (click)="reloadNodes()">
<mat-icon>refresh</mat-icon>
<span>Reload</span>
</button>

View File

@ -0,0 +1,31 @@
import { Component, Input, OnInit, OnChanges } from '@angular/core';
import { Server } from '../../../../../models/server';
import { NodeService } from '../../../../../services/node.service';
import { Node } from '../../../../../cartography/models/node';
@Component({
selector: 'app-reload-node-action',
templateUrl: './reload-node-action.component.html'
})
export class ReloadNodeActionComponent implements OnInit {
@Input() server: Server;
@Input() nodes: Node[];
filteredNodes: Node[] = [];
constructor(private nodeService: NodeService) {}
ngOnInit() {
this.nodes.forEach((node) => {
if (node.node_type === 'vpcs' || node.node_type === 'qemu' || node.node_type === 'virtualbox' || node.node_type === 'vmware') {
this.filteredNodes.push(node);
}
});
}
reloadNodes() {
this.filteredNodes.forEach((node) => {
this.nodeService.reload(this.server, node).subscribe((n: Node) => {});
});
}
}

View File

@ -0,0 +1,4 @@
<button mat-menu-item (click)="showNode()">
<mat-icon>info</mat-icon>
<span>Show node information</span>
</button>

View File

@ -0,0 +1,26 @@
import { Component, Input, OnInit, OnChanges } from '@angular/core';
import { Node } from '../../../../../cartography/models/node';
import { MatDialog } from '@angular/material';
import { InfoDialogComponent } from '../../../info-dialog/info-dialog.component';
import { Server } from '../../../../../models/server';
@Component({
selector: 'app-show-node-action',
templateUrl: './show-node-action.component.html'
})
export class ShowNodeActionComponent {
@Input() node: Node;
@Input() server: Server
constructor(private dialog: MatDialog) {}
showNode() {
const dialogRef = this.dialog.open(InfoDialogComponent, {
width: '600px',
autoFocus: false
});
let instance = dialogRef.componentInstance;
instance.node = this.node;
instance.server = this.server;
}
}

View File

@ -0,0 +1,4 @@
<button mat-menu-item *ngIf="link.capturing" (click)="startCapture()">
<mat-icon>search</mat-icon>
<span>Start Wireshark</span>
</button>

View File

@ -0,0 +1,24 @@
import { Component, Input } from '@angular/core';
import { Server } from '../../../../../models/server';
import { Link } from '../../../../../models/link';
import { Project } from '../../../../../models/project';
import { PacketCaptureService } from '../../../../../services/packet-capture.service';
@Component({
selector: 'app-start-capture-on-started-link-action',
templateUrl: './start-capture-on-started-link.component.html'
})
export class StartCaptureOnStartedLinkActionComponent {
@Input() server: Server;
@Input() project: Project;
@Input() link: Link;
constructor(
private packetCaptureService: PacketCaptureService
) {}
startCapture() {
var splittedFileName = this.link.capture_file_name.split('.');
this.packetCaptureService.startCapture(this.server, this.project, this.link, splittedFileName[0]);
}
}

View File

@ -3,6 +3,7 @@ import { Server } from '../../../../../models/server';
import { Link } from '../../../../../models/link';
import { MatDialog } from '@angular/material';
import { StartCaptureDialogComponent } from '../../../packet-capturing/start-capture/start-capture.component';
import { Project } from '../../../../../models/project';
@Component({
selector: 'app-start-capture-action',
@ -10,6 +11,7 @@ import { StartCaptureDialogComponent } from '../../../packet-capturing/start-cap
})
export class StartCaptureActionComponent {
@Input() server: Server;
@Input() project: Project;
@Input() link: Link;
constructor(private dialog: MatDialog) {}
@ -21,6 +23,7 @@ export class StartCaptureActionComponent {
});
let instance = dialogRef.componentInstance;
instance.server = this.server;
instance.project = this.project;
instance.link = this.link;
}
}

View File

@ -0,0 +1,4 @@
<button mat-menu-item *ngIf="isNodeWithStartedStatus" (click)="suspendNodes()">
<mat-icon>pause</mat-icon>
<span>Suspend</span>
</button>

Some files were not shown because too many files have changed in this diff Show More