mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-05-09 12:02:53 +00:00
Merge branch 'master' into Support-for-editing-config-files
This commit is contained in:
commit
2fca17376a
@ -67,8 +67,10 @@
|
|||||||
"raven-js": "^3.27.2",
|
"raven-js": "^3.27.2",
|
||||||
"rxjs": "^6.5.2",
|
"rxjs": "^6.5.2",
|
||||||
"rxjs-compat": "^6.5.2",
|
"rxjs-compat": "^6.5.2",
|
||||||
|
"save-svg-as-png": "^1.4.14",
|
||||||
"tree-kill": "^1.2.1",
|
"tree-kill": "^1.2.1",
|
||||||
"typeface-roboto": "^0.0.75",
|
"typeface-roboto": "^0.0.75",
|
||||||
|
"xterm": "^3.14.5",
|
||||||
"yargs": "^13.3.0",
|
"yargs": "^13.3.0",
|
||||||
"zone.js": "^0.9.1"
|
"zone.js": "^0.9.1"
|
||||||
},
|
},
|
||||||
|
@ -193,6 +193,8 @@ import { ProjectMapMenuComponent } from './components/project-map/project-map-me
|
|||||||
import { HelpComponent } from './components/help/help.component';
|
import { HelpComponent } from './components/help/help.component';
|
||||||
import { ConfigEditorDialogComponent } from './components/project-map/node-editors/config-editor/config-editor.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 { 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 { SaveProjectDialogComponent } from './components/projects/save-project-dialog/save-project-dialog.component';
|
||||||
import { TopologySummaryComponent } from './components/topology-summary/topology-summary.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 { ShowNodeActionComponent } from './components/project-map/context-menu/actions/show-node-action/show-node-action.component';
|
||||||
@ -201,6 +203,7 @@ 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 { 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 { 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 { ImportConfigActionComponent } from './components/project-map/context-menu/actions/import-config/import-config-action.component';
|
||||||
|
import { ConsoleDeviceActionBrowserComponent } from './components/project-map/context-menu/actions/console-device-action-browser/console-device-action-browser.component';
|
||||||
|
|
||||||
if (environment.production) {
|
if (environment.production) {
|
||||||
Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', {
|
Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', {
|
||||||
@ -326,12 +329,14 @@ if (environment.production) {
|
|||||||
HelpComponent,
|
HelpComponent,
|
||||||
ConfigEditorDialogComponent,
|
ConfigEditorDialogComponent,
|
||||||
EditConfigActionComponent,
|
EditConfigActionComponent,
|
||||||
|
LogConsoleComponent,
|
||||||
SaveProjectDialogComponent,
|
SaveProjectDialogComponent,
|
||||||
TopologySummaryComponent,
|
TopologySummaryComponent,
|
||||||
InfoDialogComponent,
|
InfoDialogComponent,
|
||||||
BringToFrontActionComponent,
|
BringToFrontActionComponent,
|
||||||
ExportConfigActionComponent,
|
ExportConfigActionComponent,
|
||||||
ImportConfigActionComponent
|
ImportConfigActionComponent,
|
||||||
|
ConsoleDeviceActionBrowserComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
@ -371,6 +376,7 @@ if (environment.production) {
|
|||||||
LinksDataSource,
|
LinksDataSource,
|
||||||
NodesDataSource,
|
NodesDataSource,
|
||||||
SymbolsDataSource,
|
SymbolsDataSource,
|
||||||
|
LogEventsDataSource,
|
||||||
SelectionManager,
|
SelectionManager,
|
||||||
InRectangleHelper,
|
InRectangleHelper,
|
||||||
DrawingsDataSource,
|
DrawingsDataSource,
|
||||||
|
@ -14,6 +14,10 @@ export class MockedSymbolService {
|
|||||||
public list() {
|
public list() {
|
||||||
return of([]);
|
return of([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public raw() {
|
||||||
|
return of('<svg></svg>')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('Symbols component', () => {
|
describe('Symbols component', () => {
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
<button mat-menu-item (click)="openConsole()">
|
||||||
|
<mat-icon>web_asset</mat-icon>
|
||||||
|
<span>Console</span>
|
||||||
|
</button>
|
@ -0,0 +1,23 @@
|
|||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
import { Node } from '../../../../../cartography/models/node';
|
||||||
|
import { ToasterService } from '../../../../../services/toaster.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-console-device-action-browser',
|
||||||
|
templateUrl: './console-device-action-browser.component.html'
|
||||||
|
})
|
||||||
|
export class ConsoleDeviceActionBrowserComponent {
|
||||||
|
@Input() node: Node;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private toasterService: ToasterService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
openConsole() {
|
||||||
|
if(this.node.status !== "started") {
|
||||||
|
this.toasterService.error("This node must be started before a console can be opened");
|
||||||
|
} else {
|
||||||
|
location.assign(`telnet://${this.node.console_host}:${this.node.console}/`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,10 @@
|
|||||||
[server]="server"
|
[server]="server"
|
||||||
[nodes]="nodes"
|
[nodes]="nodes"
|
||||||
></app-console-device-action>
|
></app-console-device-action>
|
||||||
|
<app-console-device-action-browser
|
||||||
|
*ngIf="!projectService.isReadOnly(project) && nodes.length===1 && !isElectronApp && nodes[0].console_type!=='none'"
|
||||||
|
[node]="nodes[0]"
|
||||||
|
></app-console-device-action-browser>
|
||||||
<app-duplicate-action *ngIf="drawings.length>0 || nodes.length>0"
|
<app-duplicate-action *ngIf="drawings.length>0 || nodes.length>0"
|
||||||
[server]="server"
|
[server]="server"
|
||||||
[project]="project"
|
[project]="project"
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
<div class="consoleWrapper">
|
||||||
|
<div class="consoleHeader">
|
||||||
|
<div class="consoleFiltering">
|
||||||
|
<div class="consoleName">Console</div>
|
||||||
|
<button class="filterButton" [matMenuTriggerFor]="filterMenu">
|
||||||
|
<mat-icon>keyboard_arrow_down</mat-icon>
|
||||||
|
</button>
|
||||||
|
<mat-menu #filterMenu="matMenu" xPosition="after">
|
||||||
|
<button mat-menu-item>all</button>
|
||||||
|
<button mat-menu-item>errors</button>
|
||||||
|
<button mat-menu-item>warnings</button>
|
||||||
|
<button mat-menu-item>info</button>
|
||||||
|
<button mat-menu-item>map updates</button>
|
||||||
|
<button mat-menu-item>server requests</button>
|
||||||
|
</mat-menu>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="consoleMenu">
|
||||||
|
<mat-icon (click)="close()" class="closeButton">close</mat-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div #console class="console">
|
||||||
|
<span class="console-item" *ngFor="let event of filteredEvents">
|
||||||
|
{{event.message}} <br/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="consoleInput">
|
||||||
|
<mat-icon class="inputIcon">keyboard_arrow_right</mat-icon>
|
||||||
|
<input
|
||||||
|
class="commandLine"
|
||||||
|
autofocus
|
||||||
|
(keydown)="onKeyDown($event)"
|
||||||
|
type="text"
|
||||||
|
[(ngModel)]="command"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,86 @@
|
|||||||
|
.consoleWrapper {
|
||||||
|
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 20px;
|
||||||
|
height: 180px;
|
||||||
|
width: 600px;
|
||||||
|
background: #263238;
|
||||||
|
color: white;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filterButton {
|
||||||
|
background: #263238;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.consoleFiltering {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.consoleHeader {
|
||||||
|
width: 100%;
|
||||||
|
height: 30px;
|
||||||
|
font-size: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
padding: 2px;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console {
|
||||||
|
width: 596px;
|
||||||
|
height: 120px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
padding: 2px;
|
||||||
|
color: #dbd5d5;
|
||||||
|
scrollbar-color: darkgrey #263238;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
.consoleInput {
|
||||||
|
width: 100%;
|
||||||
|
height: 30px;
|
||||||
|
padding: 2px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.commandLine {
|
||||||
|
background-color: #263238;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputIcon {
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus{
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.closeButton {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
@ -0,0 +1,176 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
import { NO_ERRORS_SCHEMA, EventEmitter, inject } from '@angular/core';
|
||||||
|
import { MatMenuModule } from '@angular/material';
|
||||||
|
import { Server } from '../../../models/server';
|
||||||
|
import { LogConsoleComponent } from './log-console.component';
|
||||||
|
import { ProjectWebServiceHandler, WebServiceMessage } from '../../../handlers/project-web-service-handler';
|
||||||
|
import { NodeService } from '../../../services/node.service';
|
||||||
|
import { MockedNodeService, MockedNodesDataSource } from '../project-map.component.spec';
|
||||||
|
import { NodesDataSource } from '../../../cartography/datasources/nodes-datasource';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { LogEventsDataSource } from './log-events-datasource';
|
||||||
|
import { HttpServer, ServerErrorHandler } from '../../../services/http-server.service';
|
||||||
|
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
|
||||||
|
export class MockedProjectWebServiceHandler {
|
||||||
|
public nodeNotificationEmitter = new EventEmitter<WebServiceMessage>();
|
||||||
|
public linkNotificationEmitter = new EventEmitter<WebServiceMessage>();
|
||||||
|
public drawingNotificationEmitter = new EventEmitter<WebServiceMessage>();
|
||||||
|
public infoNotificationEmitter = new EventEmitter<any>();
|
||||||
|
public warningNotificationEmitter = new EventEmitter<any>();
|
||||||
|
public errorNotificationEmitter = new EventEmitter<any>();
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('LogConsoleComponent', () => {
|
||||||
|
let component: LogConsoleComponent;
|
||||||
|
let fixture: ComponentFixture<LogConsoleComponent>;
|
||||||
|
|
||||||
|
let mockedNodeService: MockedNodeService = new MockedNodeService();
|
||||||
|
let mockedNodesDataSource: MockedNodesDataSource = new MockedNodesDataSource();
|
||||||
|
let mockedProjectWebServiceHandler: MockedProjectWebServiceHandler = new MockedProjectWebServiceHandler();
|
||||||
|
|
||||||
|
let httpServer = new HttpServer({} as HttpClient, {} as ServerErrorHandler);
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [HttpClientTestingModule, MatMenuModule, BrowserModule],
|
||||||
|
providers: [
|
||||||
|
{ provide: ProjectWebServiceHandler, useValue: mockedProjectWebServiceHandler },
|
||||||
|
{ provide: NodeService, useValue: mockedNodeService },
|
||||||
|
{ provide: NodesDataSource, useValue: mockedNodesDataSource },
|
||||||
|
{ provide: LogEventsDataSource, useClass: LogEventsDataSource },
|
||||||
|
{ provide: HttpServer, useValue: httpServer }
|
||||||
|
],
|
||||||
|
declarations: [LogConsoleComponent],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(LogConsoleComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
component.server = {location: 'local'} as Server;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call show message when help command entered', () => {
|
||||||
|
spyOn(component, 'showMessage');
|
||||||
|
component.command = 'help';
|
||||||
|
|
||||||
|
component.handleCommand();
|
||||||
|
|
||||||
|
expect(component.showMessage).toHaveBeenCalledWith({type: 'command', message: 'Available commands: help, version, start all, start {node name}, stop all, stop {node name}, suspend all, suspend {node name}, reload all, reload {node name}, show {node name}.'});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call show message when version command entered', () => {
|
||||||
|
spyOn(component, 'showMessage');
|
||||||
|
component.command = 'version';
|
||||||
|
|
||||||
|
component.handleCommand();
|
||||||
|
|
||||||
|
expect(component.showMessage).toHaveBeenCalledWith({type: 'command', message: 'Current version: 2019.2.0'});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call show message when unknown command entered', () => {
|
||||||
|
spyOn(component, 'showMessage');
|
||||||
|
component.command = 'xyz';
|
||||||
|
|
||||||
|
component.handleCommand();
|
||||||
|
|
||||||
|
expect(component.showMessage).toHaveBeenCalledWith({type: 'command', message: 'Unknown syntax: xyz'});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call node service when start all entered', () => {
|
||||||
|
spyOn(component, 'showMessage');
|
||||||
|
spyOn(mockedNodeService, 'startAll').and.returnValue(of({}));
|
||||||
|
component.command = 'start all';
|
||||||
|
|
||||||
|
component.handleCommand();
|
||||||
|
|
||||||
|
expect(component.showMessage).toHaveBeenCalledWith({type: 'command', message: 'Starting all nodes...'});
|
||||||
|
expect(mockedNodeService.startAll).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call node service when stop all entered', () => {
|
||||||
|
spyOn(component, 'showMessage');
|
||||||
|
spyOn(mockedNodeService, 'stopAll').and.returnValue(of({}));
|
||||||
|
component.command = 'stop all';
|
||||||
|
|
||||||
|
component.handleCommand();
|
||||||
|
|
||||||
|
expect(component.showMessage).toHaveBeenCalledWith({type: 'command', message: 'Stopping all nodes...'});
|
||||||
|
expect(mockedNodeService.stopAll).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call node service when suspend all entered', () => {
|
||||||
|
spyOn(component, 'showMessage');
|
||||||
|
spyOn(mockedNodeService, 'suspendAll').and.returnValue(of({}));
|
||||||
|
component.command = 'suspend all';
|
||||||
|
|
||||||
|
component.handleCommand();
|
||||||
|
|
||||||
|
expect(component.showMessage).toHaveBeenCalledWith({type: 'command', message: 'Suspending all nodes...'});
|
||||||
|
expect(mockedNodeService.suspendAll).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call node service when reload all entered', () => {
|
||||||
|
spyOn(component, 'showMessage');
|
||||||
|
spyOn(mockedNodeService, 'reloadAll').and.returnValue(of({}));
|
||||||
|
component.command = 'reload all';
|
||||||
|
|
||||||
|
component.handleCommand();
|
||||||
|
|
||||||
|
expect(component.showMessage).toHaveBeenCalledWith({type: 'command', message: 'Reloading all nodes...'});
|
||||||
|
expect(mockedNodeService.reloadAll).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call node service when start node entered', () => {
|
||||||
|
spyOn(component, 'showMessage');
|
||||||
|
spyOn(mockedNodeService, 'start').and.returnValue(of({}));
|
||||||
|
component.command = 'start testNode';
|
||||||
|
|
||||||
|
component.handleCommand();
|
||||||
|
|
||||||
|
expect(component.showMessage).toHaveBeenCalledWith({type: 'command', message: 'Starting node testNode...'});
|
||||||
|
expect(mockedNodeService.start).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call node service when stop node entered', () => {
|
||||||
|
spyOn(component, 'showMessage');
|
||||||
|
spyOn(mockedNodeService, 'stop').and.returnValue(of({}));
|
||||||
|
component.command = 'stop testNode';
|
||||||
|
|
||||||
|
component.handleCommand();
|
||||||
|
|
||||||
|
expect(component.showMessage).toHaveBeenCalledWith({type: 'command', message: 'Stopping node testNode...'});
|
||||||
|
expect(mockedNodeService.stop).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call node service when suspend node entered', () => {
|
||||||
|
spyOn(component, 'showMessage');
|
||||||
|
spyOn(mockedNodeService, 'suspend').and.returnValue(of({}));
|
||||||
|
component.command = 'suspend testNode';
|
||||||
|
|
||||||
|
component.handleCommand();
|
||||||
|
|
||||||
|
expect(component.showMessage).toHaveBeenCalledWith({type: 'command', message: 'Suspending node testNode...'});
|
||||||
|
expect(mockedNodeService.suspend).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call node service when reload node entered', () => {
|
||||||
|
spyOn(component, 'showMessage');
|
||||||
|
spyOn(mockedNodeService, 'reload').and.returnValue(of({}));
|
||||||
|
component.command = 'reload testNode';
|
||||||
|
|
||||||
|
component.handleCommand();
|
||||||
|
|
||||||
|
expect(component.showMessage).toHaveBeenCalledWith({type: 'command', message: 'Reloading node testNode...'});
|
||||||
|
expect(mockedNodeService.reload).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,286 @@
|
|||||||
|
import { Component, OnInit, AfterViewInit, OnDestroy, Input, ViewChild, ElementRef, Output, EventEmitter } from '@angular/core';
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
import { ProjectWebServiceHandler } from '../../../handlers/project-web-service-handler';
|
||||||
|
import { NodeService } from '../../../services/node.service';
|
||||||
|
import { NodesDataSource } from '../../../cartography/datasources/nodes-datasource';
|
||||||
|
import { Project } from '../../../models/project';
|
||||||
|
import { Server } from '../../../models/server';
|
||||||
|
import { Drawing } from '../../../cartography/models/drawing';
|
||||||
|
import { Link } from '../../../models/link';
|
||||||
|
import { Node } from '../../../cartography/models/node';
|
||||||
|
import { Port } from '../../../models/port';
|
||||||
|
import { LogEventsDataSource } from './log-events-datasource';
|
||||||
|
import { HttpServer } from '../../../services/http-server.service';
|
||||||
|
import { LogEvent } from '../../../models/logEvent';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-log-console',
|
||||||
|
templateUrl: './log-console.component.html',
|
||||||
|
styleUrls: ['./log-console.component.scss']
|
||||||
|
})
|
||||||
|
export class LogConsoleComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
|
@Input() project: Project;
|
||||||
|
@Input() server: Server;
|
||||||
|
@Output() closeConsole = new EventEmitter<boolean>();
|
||||||
|
@ViewChild('console', {static: false}) console: ElementRef;
|
||||||
|
private nodeSubscription: Subscription;
|
||||||
|
private linkSubscription: Subscription;
|
||||||
|
private drawingSubscription: Subscription;
|
||||||
|
private serverRequestsSubscription: Subscription;
|
||||||
|
private errorSubscription: Subscription;
|
||||||
|
private warningSubscription: Subscription;
|
||||||
|
private infoSubscription: Subscription;
|
||||||
|
command: string = '';
|
||||||
|
|
||||||
|
filters: string[] = ['all', 'errors', 'warnings', 'info', 'map updates', 'server requests'];
|
||||||
|
selectedFilter: string = 'all';
|
||||||
|
filteredEvents: LogEvent[] = [];
|
||||||
|
|
||||||
|
private regexStart: RegExp = /^start (.*?)$/;
|
||||||
|
private regexStop: RegExp = /^stop (.*?)$/;
|
||||||
|
private regexSuspend: RegExp = /^suspend (.*?)$/;
|
||||||
|
private regexReload: RegExp = /^reload (.*?)$/;
|
||||||
|
private regexShow: RegExp = /^show (.*?)$/;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private projectWebServiceHandler: ProjectWebServiceHandler,
|
||||||
|
private nodeService: NodeService,
|
||||||
|
private nodesDataSource: NodesDataSource,
|
||||||
|
private logEventsDataSource: LogEventsDataSource,
|
||||||
|
private httpService: HttpServer
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.nodeSubscription = this.projectWebServiceHandler.nodeNotificationEmitter.subscribe((event) => {
|
||||||
|
let node: Node = event.event as Node;
|
||||||
|
let message = `Event received: ${event.action} - ${this.printNode(node)}.`
|
||||||
|
this.showMessage({
|
||||||
|
type: 'map update',
|
||||||
|
message: message
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.linkSubscription = this.projectWebServiceHandler.linkNotificationEmitter.subscribe((event) => {
|
||||||
|
let link: Link = event.event as Link;
|
||||||
|
let message = `Event received: ${event.action} - ${this.printLink(link)}.`
|
||||||
|
this.showMessage({
|
||||||
|
type: 'map update',
|
||||||
|
message: message
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.drawingSubscription = this.projectWebServiceHandler.drawingNotificationEmitter.subscribe((event) => {
|
||||||
|
let drawing: Drawing = event.event as Drawing;
|
||||||
|
let message = `Event received: ${event.action} - ${this.printDrawing(drawing)}.`
|
||||||
|
this.showMessage({
|
||||||
|
type: 'map update',
|
||||||
|
message: message
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.serverRequestsSubscription = this.httpService.requestsNotificationEmitter.subscribe((message) => {
|
||||||
|
this.showMessage({
|
||||||
|
type: 'server request',
|
||||||
|
message: message
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.errorSubscription = this.projectWebServiceHandler.errorNotificationEmitter.subscribe((message) => {
|
||||||
|
this.showMessage({
|
||||||
|
type: 'error',
|
||||||
|
message: message
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.errorSubscription = this.projectWebServiceHandler.warningNotificationEmitter.subscribe((message) => {
|
||||||
|
this.showMessage({
|
||||||
|
type: 'warning',
|
||||||
|
message: message
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.errorSubscription = this.projectWebServiceHandler.infoNotificationEmitter.subscribe((message) => {
|
||||||
|
this.showMessage({
|
||||||
|
type: 'info',
|
||||||
|
message: message
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit() {
|
||||||
|
this.console.nativeElement.scrollTop = this.console.nativeElement.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.nodeSubscription.unsubscribe();
|
||||||
|
this.linkSubscription.unsubscribe();
|
||||||
|
this.drawingSubscription.unsubscribe();
|
||||||
|
this.serverRequestsSubscription.unsubscribe();
|
||||||
|
this.errorSubscription.unsubscribe();
|
||||||
|
this.warningSubscription.unsubscribe();
|
||||||
|
this.infoSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
applyFilter() {
|
||||||
|
this.filteredEvents = this.getFilteredEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
onKeyDown(event) {
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
this.handleCommand();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCommand() {
|
||||||
|
if (this.command === 'help') {
|
||||||
|
this.showCommand("Available commands: help, version, start all, start {node name}, stop all, stop {node name}, suspend all, suspend {node name}, reload all, reload {node name}, show {node name}.")
|
||||||
|
} else if (this.command === 'version') {
|
||||||
|
this.showCommand("Current version: 2019.2.0");
|
||||||
|
} else if (this.command === 'start all') {
|
||||||
|
this.showCommand("Starting all nodes...");
|
||||||
|
this.nodeService.startAll(this.server, this.project).subscribe(() => {
|
||||||
|
this.showCommand("All nodes started.")
|
||||||
|
});
|
||||||
|
} else if (this.command === 'stop all') {
|
||||||
|
this.showCommand("Stopping all nodes...");
|
||||||
|
this.nodeService.stopAll(this.server, this.project).subscribe(() => {
|
||||||
|
this.showCommand("All nodes stopped.")
|
||||||
|
});
|
||||||
|
} else if (this.command === 'suspend all') {
|
||||||
|
this.showCommand("Suspending all nodes...");
|
||||||
|
this.nodeService.suspendAll(this.server, this.project).subscribe(() => {
|
||||||
|
this.showCommand("All nodes suspended.")
|
||||||
|
});
|
||||||
|
} else if (this.command === 'reload all') {
|
||||||
|
this.showCommand("Reloading all nodes...");
|
||||||
|
this.nodeService.reloadAll(this.server, this.project).subscribe(() => {
|
||||||
|
this.showCommand("All nodes reloaded.")
|
||||||
|
});
|
||||||
|
} else if (
|
||||||
|
this.regexStart.test(this.command) || this.regexStop.test(this.command) || this.regexSuspend.test(this.command) || this.regexReload.test(this.command) || this.regexShow.test(this.command)) {
|
||||||
|
let splittedCommand = this.command.split(/[ ,]+/);
|
||||||
|
let node = this.nodesDataSource.getItems().find(n => n.name.valueOf() === splittedCommand[1].valueOf());
|
||||||
|
if (node) {
|
||||||
|
if (this.regexStart.test(this.command)) {
|
||||||
|
this.showCommand(`Starting node ${splittedCommand[1]}...`);
|
||||||
|
this.nodeService.start(this.server, node).subscribe(() => this.showCommand(`Node ${node.name} started.`));
|
||||||
|
}
|
||||||
|
else if (this.regexStop.test(this.command)) {
|
||||||
|
this.showCommand(`Stopping node ${splittedCommand[1]}...`);
|
||||||
|
this.nodeService.stop(this.server, node).subscribe(() => this.showCommand(`Node ${node.name} stopped.`));
|
||||||
|
}
|
||||||
|
else if (this.regexSuspend.test(this.command)) {
|
||||||
|
this.showCommand(`Suspending node ${splittedCommand[1]}...`);
|
||||||
|
this.nodeService.suspend(this.server, node).subscribe(() => this.showCommand(`Node ${node.name} suspended.`));
|
||||||
|
}
|
||||||
|
else if (this.regexReload.test(this.command)) {
|
||||||
|
this.showCommand(`Reloading node ${splittedCommand[1]}...`);
|
||||||
|
this.nodeService.reload(this.server, node).subscribe(() => this.showCommand(`Node ${node.name} reloaded.`));
|
||||||
|
}
|
||||||
|
else if (this.regexShow.test(this.command)) {
|
||||||
|
this.showCommand(`Information about node ${node.name}:`);
|
||||||
|
this.showCommand(this.printNode(node));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.showCommand(`Node with ${splittedCommand[1]} name was not found.`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.showCommand(`Unknown syntax: ${this.command}`);
|
||||||
|
}
|
||||||
|
this.command = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
clearConsole() {
|
||||||
|
this.filteredEvents = [];
|
||||||
|
this.console.nativeElement.scrollTop = this.console.nativeElement.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
showCommand(message: string) {
|
||||||
|
this.showMessage({
|
||||||
|
type: 'command',
|
||||||
|
message: message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
showMessage(event: LogEvent) {
|
||||||
|
this.logEventsDataSource.add(event);
|
||||||
|
this.filteredEvents = this.getFilteredEvents();
|
||||||
|
this.console.nativeElement.scrollTop = this.console.nativeElement.scrollHeight;
|
||||||
|
|
||||||
|
setTimeout( () => {
|
||||||
|
this.console.nativeElement.scrollTop = this.console.nativeElement.scrollHeight;
|
||||||
|
}, 100 );
|
||||||
|
}
|
||||||
|
|
||||||
|
getFilteredEvents(): LogEvent[] {
|
||||||
|
if (this.selectedFilter === 'server requests') {
|
||||||
|
return this.logEventsDataSource.getItems().filter(n => n.type === 'server request');
|
||||||
|
} else if (this.selectedFilter === 'errors') {
|
||||||
|
return this.logEventsDataSource.getItems().filter(n => n.type === 'error');
|
||||||
|
} else if (this.selectedFilter === 'warnings') {
|
||||||
|
return this.logEventsDataSource.getItems().filter(n => n.type === 'warning');
|
||||||
|
} else if (this.selectedFilter === 'info') {
|
||||||
|
return this.logEventsDataSource.getItems().filter(n => n.type === 'info');
|
||||||
|
} else if (this.selectedFilter === 'map updates') {
|
||||||
|
return this.logEventsDataSource.getItems().filter(n => n.type === 'map update' || n.type === 'command');
|
||||||
|
} else {
|
||||||
|
return this.logEventsDataSource.getItems();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printNode(node: Node): string {
|
||||||
|
return `command_line: ${node.command_line},
|
||||||
|
compute_id: ${node.compute_id},
|
||||||
|
console: ${node.console},
|
||||||
|
console_host: ${node.console_host},
|
||||||
|
console_type: ${node.console_type},
|
||||||
|
first_port_name: ${node.first_port_name},
|
||||||
|
height: ${node.height},
|
||||||
|
label: ${node.label.text},
|
||||||
|
name: ${node.name},
|
||||||
|
node_directory: ${node.node_directory},
|
||||||
|
node_id: ${node.node_id},
|
||||||
|
node_type: ${node.node_type},
|
||||||
|
port_name_format: ${node.port_name_format},
|
||||||
|
port_segment_size: ${node.port_segment_size}, ` +
|
||||||
|
this.printPorts(node.ports) +
|
||||||
|
`project_id: ${node.project_id},
|
||||||
|
status: ${node.status},
|
||||||
|
symbol: ${node.symbol},
|
||||||
|
symbol_url: ${node.symbol_url},
|
||||||
|
width: ${node.width},
|
||||||
|
x: ${node.x},
|
||||||
|
y: ${node.y},
|
||||||
|
z: ${node.z}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
printPorts(ports: Port[]): string {
|
||||||
|
let response: string = `ports: `
|
||||||
|
ports.forEach(port => {
|
||||||
|
response = response + `adapter_number: ${port.adapter_number},
|
||||||
|
link_type: ${port.link_type},
|
||||||
|
name: ${port.name},
|
||||||
|
port_number: ${port.port_number},
|
||||||
|
short_name: ${port.short_name}, `
|
||||||
|
});
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
printLink(link: Link): string {
|
||||||
|
return `capture_file_name: ${link.capture_file_name},
|
||||||
|
capture_file_path: ${link.capture_file_path},
|
||||||
|
capturing: ${link.capturing},
|
||||||
|
link_id: ${link.link_id},
|
||||||
|
link_type: ${link.link_type},
|
||||||
|
project_id: ${link.project_id},
|
||||||
|
suspend: ${link.suspend}, `;
|
||||||
|
}
|
||||||
|
|
||||||
|
printDrawing(drawing: Drawing): string {
|
||||||
|
return `drawing_id: ${drawing.drawing_id},
|
||||||
|
project_id: ${drawing.project_id},
|
||||||
|
rotation: ${drawing.rotation},
|
||||||
|
x: ${drawing.x},
|
||||||
|
y: ${drawing.y},
|
||||||
|
z: ${drawing.z}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.closeConsole.emit(false);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { DataSource } from '../../../cartography/datasources/datasource';
|
||||||
|
import { LogEvent } from '../../../models/logEvent';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LogEventsDataSource extends DataSource<LogEvent> {
|
||||||
|
protected getItemKey(log: LogEvent) {
|
||||||
|
return log;
|
||||||
|
}
|
||||||
|
}
|
@ -57,6 +57,14 @@
|
|||||||
(click)="changeLockValue()">
|
(click)="changeLockValue()">
|
||||||
<mat-icon>lock</mat-icon>
|
<mat-icon>lock</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
matTooltip="Take a screenshot"
|
||||||
|
mat-icon-button
|
||||||
|
class="menu-button"
|
||||||
|
(click)="takeScreenshot()"
|
||||||
|
>
|
||||||
|
<mat-icon>photo_camera</mat-icon>
|
||||||
|
</button>
|
||||||
<app-drawing-added
|
<app-drawing-added
|
||||||
[server]="server"
|
[server]="server"
|
||||||
[project]="project"
|
[project]="project"
|
||||||
|
@ -10,12 +10,15 @@ import { ToolsService } from '../../../services/tools.service';
|
|||||||
import { D3MapComponent } from '../../../cartography/components/d3-map/d3-map.component';
|
import { D3MapComponent } from '../../../cartography/components/d3-map/d3-map.component';
|
||||||
import { ANGULAR_MAP_DECLARATIONS } from '../../../cartography/angular-map.imports';
|
import { ANGULAR_MAP_DECLARATIONS } from '../../../cartography/angular-map.imports';
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { SymbolService } from '../../../services/symbol.service';
|
||||||
|
import { MockedSymbolService } from '../../preferences/common/symbols/symbols.component.spec';
|
||||||
|
|
||||||
describe('ProjectMapMenuComponent', () => {
|
describe('ProjectMapMenuComponent', () => {
|
||||||
let component: ProjectMapMenuComponent;
|
let component: ProjectMapMenuComponent;
|
||||||
let fixture: ComponentFixture<ProjectMapMenuComponent>;
|
let fixture: ComponentFixture<ProjectMapMenuComponent>;
|
||||||
let drawingService = new MockedDrawingService();
|
let drawingService = new MockedDrawingService();
|
||||||
let mapSettingService = new MapSettingsService();
|
let mapSettingService = new MapSettingsService();
|
||||||
|
let mockedSymbolService = new MockedSymbolService;
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@ -23,7 +26,8 @@ describe('ProjectMapMenuComponent', () => {
|
|||||||
providers: [
|
providers: [
|
||||||
{ provide: DrawingService, useValue: drawingService },
|
{ provide: DrawingService, useValue: drawingService },
|
||||||
{ provide: ToolsService },
|
{ provide: ToolsService },
|
||||||
{ provide: MapSettingsService, useValue: mapSettingService }
|
{ provide: MapSettingsService, useValue: mapSettingService },
|
||||||
|
{ provide: SymbolService, useValue: mockedSymbolService}
|
||||||
],
|
],
|
||||||
declarations: [ProjectMapMenuComponent, D3MapComponent, ...ANGULAR_MAP_DECLARATIONS],
|
declarations: [ProjectMapMenuComponent, D3MapComponent, ...ANGULAR_MAP_DECLARATIONS],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
@ -4,6 +4,8 @@ import { Server } from '../../../models/server';
|
|||||||
import { ToolsService } from '../../../services/tools.service';
|
import { ToolsService } from '../../../services/tools.service';
|
||||||
import { MapSettingsService } from '../../../services/mapsettings.service';
|
import { MapSettingsService } from '../../../services/mapsettings.service';
|
||||||
import { DrawingService } from '../../../services/drawing.service';
|
import { DrawingService } from '../../../services/drawing.service';
|
||||||
|
import * as svg from 'save-svg-as-png';
|
||||||
|
import { SymbolService } from '../../../services/symbol.service';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -27,11 +29,34 @@ export class ProjectMapMenuComponent implements OnInit, OnDestroy {
|
|||||||
constructor(
|
constructor(
|
||||||
private toolsService: ToolsService,
|
private toolsService: ToolsService,
|
||||||
private mapSettingsService: MapSettingsService,
|
private mapSettingsService: MapSettingsService,
|
||||||
private drawingService: DrawingService
|
private drawingService: DrawingService,
|
||||||
|
private symbolService: SymbolService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {}
|
ngOnInit() {}
|
||||||
|
|
||||||
|
public async takeScreenshot() {
|
||||||
|
let splittedSvg = document.getElementsByTagName("svg")[0].outerHTML.split('image');
|
||||||
|
let i = 1;
|
||||||
|
|
||||||
|
while (i < splittedSvg.length) {
|
||||||
|
let splittedImage = splittedSvg[i].split("\"");
|
||||||
|
let splittedUrl = splittedImage[1].split("/");
|
||||||
|
|
||||||
|
let elem = await this.symbolService.raw(this.server, splittedUrl[7]).toPromise();
|
||||||
|
let splittedElement = elem.split('-->');
|
||||||
|
splittedSvg[i] = splittedElement[1].substring(2);
|
||||||
|
i += 2;
|
||||||
|
}
|
||||||
|
let svgString = splittedSvg.join();
|
||||||
|
|
||||||
|
let placeholder = document.createElement('div');
|
||||||
|
placeholder.innerHTML = svgString;
|
||||||
|
let element = placeholder.firstChild;
|
||||||
|
|
||||||
|
svg.saveSvgAsPng(element, "screenshot.png");
|
||||||
|
}
|
||||||
|
|
||||||
public addDrawing(selectedObject: string) {
|
public addDrawing(selectedObject: string) {
|
||||||
switch (selectedObject) {
|
switch (selectedObject) {
|
||||||
case 'rectangle':
|
case 'rectangle':
|
||||||
|
@ -77,6 +77,9 @@
|
|||||||
<mat-checkbox [ngModel]="project.show_interface_labels" (change)="toggleShowInterfaceLabels($event.checked)">
|
<mat-checkbox [ngModel]="project.show_interface_labels" (change)="toggleShowInterfaceLabels($event.checked)">
|
||||||
Show interface labels
|
Show interface labels
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
|
<mat-checkbox [ngModel]="isConsoleVisible" (change)="toggleShowConsole($event.checked)">
|
||||||
|
Show console
|
||||||
|
</mat-checkbox>
|
||||||
<mat-checkbox [ngModel]="isTopologySummaryVisible" (change)="toggleShowTopologySummary($event.checked)">
|
<mat-checkbox [ngModel]="isTopologySummaryVisible" (change)="toggleShowTopologySummary($event.checked)">
|
||||||
Show topology summary
|
Show topology summary
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
@ -143,6 +146,9 @@
|
|||||||
<app-node-label-dragged [server]="server"></app-node-label-dragged>
|
<app-node-label-dragged [server]="server"></app-node-label-dragged>
|
||||||
<app-text-added [server]="server" [project]="project" (drawingSaved)="onDrawingSaved()"> </app-text-added>
|
<app-text-added [server]="server" [project]="project" (drawingSaved)="onDrawingSaved()"> </app-text-added>
|
||||||
<app-text-edited [server]="server"></app-text-edited>
|
<app-text-edited [server]="server"></app-text-edited>
|
||||||
|
<div [ngClass]="{ visible: !isConsoleVisible }">
|
||||||
|
<app-log-console [server]="server" [project]="project" (closeConsole)='toggleShowConsole($event)'></app-log-console>
|
||||||
|
</div>
|
||||||
<div [ngClass]="{ visible: !isTopologySummaryVisible }">
|
<div [ngClass]="{ visible: !isTopologySummaryVisible }">
|
||||||
<app-topology-summary *ngIf="project" [server]="server" [project]="project" (closeTopologySummary)='toggleShowTopologySummary($event)'></app-topology-summary>
|
<app-topology-summary *ngIf="project" [server]="server" [project]="project" (closeTopologySummary)='toggleShowTopologySummary($event)'></app-topology-summary>
|
||||||
</div>
|
</div>
|
||||||
|
@ -80,7 +80,7 @@ g.node:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.extended {
|
.extended {
|
||||||
width: 700px !important;
|
width: 770px !important;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
@ -93,6 +93,22 @@ export class MockedNodeService {
|
|||||||
return of();
|
return of();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
start(server: Server, node: Node) {
|
||||||
|
return of();
|
||||||
|
}
|
||||||
|
|
||||||
|
stop(server: Server, node: Node) {
|
||||||
|
return of();
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend(server: Server, node: Node) {
|
||||||
|
return of();
|
||||||
|
}
|
||||||
|
|
||||||
|
reload(server: Server, node: Node) {
|
||||||
|
return of();
|
||||||
|
}
|
||||||
|
|
||||||
duplicate(server: Server, node: Node) {
|
duplicate(server: Server, node: Node) {
|
||||||
return of(node);
|
return of(node);
|
||||||
}
|
}
|
||||||
@ -198,6 +214,10 @@ export class MockedNodesDataSource {
|
|||||||
return {status: 'started'};
|
return {status: 'started'};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getItems() {
|
||||||
|
return [{name: 'testNode'}];
|
||||||
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
return of({});
|
return of({});
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,7 @@ import { RecentlyOpenedProjectService } from '../../services/recentlyOpenedProje
|
|||||||
import { MapLink } from '../../cartography/models/map/map-link';
|
import { MapLink } from '../../cartography/models/map/map-link';
|
||||||
import { MapLinkToLinkConverter } from '../../cartography/converters/map/map-link-to-link-converter';
|
import { MapLinkToLinkConverter } from '../../cartography/converters/map/map-link-to-link-converter';
|
||||||
import { MovingEventSource } from '../../cartography/events/moving-event-source';
|
import { MovingEventSource } from '../../cartography/events/moving-event-source';
|
||||||
|
import { log } from 'util';
|
||||||
import { LinkWidget } from '../../cartography/widgets/link';
|
import { LinkWidget } from '../../cartography/widgets/link';
|
||||||
import { MapScaleService } from '../../services/mapScale.service';
|
import { MapScaleService } from '../../services/mapScale.service';
|
||||||
import { NodeCreatedLabelStylesFixer } from './helpers/node-created-label-styles-fixer';
|
import { NodeCreatedLabelStylesFixer } from './helpers/node-created-label-styles-fixer';
|
||||||
@ -74,6 +75,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
|||||||
public server: Server;
|
public server: Server;
|
||||||
public ws: WebSocket;
|
public ws: WebSocket;
|
||||||
public isProjectMapMenuVisible: boolean = false;
|
public isProjectMapMenuVisible: boolean = false;
|
||||||
|
public isConsoleVisible: boolean = false;
|
||||||
public isTopologySummaryVisible: boolean = false;
|
public isTopologySummaryVisible: boolean = false;
|
||||||
|
|
||||||
tools = {
|
tools = {
|
||||||
@ -135,6 +137,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
|||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.settings = this.settingsService.getAll();
|
this.settings = this.settingsService.getAll();
|
||||||
this.isTopologySummaryVisible = this.mapSettingsService.isTopologySummaryVisible;
|
this.isTopologySummaryVisible = this.mapSettingsService.isTopologySummaryVisible;
|
||||||
|
this.isConsoleVisible = this.mapSettingsService.isLogConsoleVisible;
|
||||||
|
|
||||||
this.progressService.activate();
|
this.progressService.activate();
|
||||||
const routeSub = this.route.paramMap.subscribe((paramMap: ParamMap) => {
|
const routeSub = this.route.paramMap.subscribe((paramMap: ParamMap) => {
|
||||||
@ -403,6 +406,11 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
|||||||
this.project.show_interface_labels = enabled;
|
this.project.show_interface_labels = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public toggleShowConsole(visible: boolean) {
|
||||||
|
this.isConsoleVisible = visible;
|
||||||
|
this.mapSettingsService.toggleLogConsole(this.isConsoleVisible);
|
||||||
|
}
|
||||||
|
|
||||||
public toggleShowTopologySummary(visible: boolean) {
|
public toggleShowTopologySummary(visible: boolean) {
|
||||||
this.isTopologySummaryVisible = visible;
|
this.isTopologySummaryVisible = visible;
|
||||||
this.mapSettingsService.toggleTopologySummary(this.isTopologySummaryVisible);
|
this.mapSettingsService.toggleTopologySummary(this.isTopologySummaryVisible);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable, EventEmitter } from '@angular/core';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
|
|
||||||
import { NodesDataSource } from '../cartography/datasources/nodes-datasource';
|
import { NodesDataSource } from '../cartography/datasources/nodes-datasource';
|
||||||
@ -15,6 +15,14 @@ export class WebServiceMessage {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ProjectWebServiceHandler {
|
export class ProjectWebServiceHandler {
|
||||||
|
public nodeNotificationEmitter = new EventEmitter<WebServiceMessage>();
|
||||||
|
public linkNotificationEmitter = new EventEmitter<WebServiceMessage>();
|
||||||
|
public drawingNotificationEmitter = new EventEmitter<WebServiceMessage>();
|
||||||
|
|
||||||
|
public infoNotificationEmitter = new EventEmitter<any>();
|
||||||
|
public warningNotificationEmitter = new EventEmitter<any>();
|
||||||
|
public errorNotificationEmitter = new EventEmitter<any>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private nodesDataSource: NodesDataSource,
|
private nodesDataSource: NodesDataSource,
|
||||||
private linksDataSource: LinksDataSource,
|
private linksDataSource: LinksDataSource,
|
||||||
@ -24,30 +32,48 @@ export class ProjectWebServiceHandler {
|
|||||||
public handleMessage(message: WebServiceMessage) {
|
public handleMessage(message: WebServiceMessage) {
|
||||||
if (message.action === 'node.updated') {
|
if (message.action === 'node.updated') {
|
||||||
this.nodesDataSource.update(message.event as Node);
|
this.nodesDataSource.update(message.event as Node);
|
||||||
|
this.nodeNotificationEmitter.emit(message);
|
||||||
}
|
}
|
||||||
if (message.action === 'node.created') {
|
if (message.action === 'node.created') {
|
||||||
this.nodesDataSource.add(message.event as Node);
|
this.nodesDataSource.add(message.event as Node);
|
||||||
|
this.nodeNotificationEmitter.emit(message);
|
||||||
}
|
}
|
||||||
if (message.action === 'node.deleted') {
|
if (message.action === 'node.deleted') {
|
||||||
this.nodesDataSource.remove(message.event as Node);
|
this.nodesDataSource.remove(message.event as Node);
|
||||||
|
this.nodeNotificationEmitter.emit(message);
|
||||||
}
|
}
|
||||||
if (message.action === 'link.created') {
|
if (message.action === 'link.created') {
|
||||||
this.linksDataSource.add(message.event as Link);
|
this.linksDataSource.add(message.event as Link);
|
||||||
|
this.linkNotificationEmitter.emit(message);
|
||||||
}
|
}
|
||||||
if (message.action === 'link.updated') {
|
if (message.action === 'link.updated') {
|
||||||
this.linksDataSource.update(message.event as Link);
|
this.linksDataSource.update(message.event as Link);
|
||||||
|
this.linkNotificationEmitter.emit(message);
|
||||||
}
|
}
|
||||||
if (message.action === 'link.deleted') {
|
if (message.action === 'link.deleted') {
|
||||||
this.linksDataSource.remove(message.event as Link);
|
this.linksDataSource.remove(message.event as Link);
|
||||||
|
this.linkNotificationEmitter.emit(message);
|
||||||
}
|
}
|
||||||
if (message.action === 'drawing.created') {
|
if (message.action === 'drawing.created') {
|
||||||
this.drawingsDataSource.add(message.event as Drawing);
|
this.drawingsDataSource.add(message.event as Drawing);
|
||||||
|
this.drawingNotificationEmitter.emit(message);
|
||||||
}
|
}
|
||||||
if (message.action === 'drawing.updated') {
|
if (message.action === 'drawing.updated') {
|
||||||
this.drawingsDataSource.update(message.event as Drawing);
|
this.drawingsDataSource.update(message.event as Drawing);
|
||||||
|
this.drawingNotificationEmitter.emit(message);
|
||||||
}
|
}
|
||||||
if (message.action === 'drawing.deleted') {
|
if (message.action === 'drawing.deleted') {
|
||||||
this.drawingsDataSource.remove(message.event as Drawing);
|
this.drawingsDataSource.remove(message.event as Drawing);
|
||||||
|
this.drawingNotificationEmitter.emit(message);
|
||||||
|
}
|
||||||
|
if (message.action === 'log.error') {
|
||||||
|
this.errorNotificationEmitter.emit(message.event);
|
||||||
|
}
|
||||||
|
if (message.action === 'log.warning') {
|
||||||
|
this.warningNotificationEmitter.emit(message.event);
|
||||||
|
}
|
||||||
|
if (message.action === 'log.info') {
|
||||||
|
this.infoNotificationEmitter.emit(message.event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
4
src/app/models/logEvent.ts
Normal file
4
src/app/models/logEvent.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export class LogEvent {
|
||||||
|
type: string;
|
||||||
|
message: string;
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable, EventEmitter } from '@angular/core';
|
||||||
import { HttpHeaders, HttpClient, HttpParams, HttpErrorResponse } from '@angular/common/http';
|
import { HttpHeaders, HttpClient, HttpParams, HttpErrorResponse } from '@angular/common/http';
|
||||||
|
|
||||||
import { Observable, throwError } from 'rxjs';
|
import { Observable, throwError } from 'rxjs';
|
||||||
@ -79,11 +79,15 @@ export class ServerErrorHandler {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class HttpServer {
|
export class HttpServer {
|
||||||
|
public requestsNotificationEmitter = new EventEmitter<string>();
|
||||||
|
|
||||||
constructor(private http: HttpClient, private errorHandler: ServerErrorHandler) {}
|
constructor(private http: HttpClient, private errorHandler: ServerErrorHandler) {}
|
||||||
|
|
||||||
get<T>(server: Server, url: string, options?: JsonOptions): Observable<T> {
|
get<T>(server: Server, url: string, options?: JsonOptions): Observable<T> {
|
||||||
options = this.getJsonOptions(options);
|
options = this.getJsonOptions(options);
|
||||||
const intercepted = this.getOptionsForServer<JsonOptions>(server, url, options);
|
const intercepted = this.getOptionsForServer<JsonOptions>(server, url, options);
|
||||||
|
this.requestsNotificationEmitter.emit(`GET ${intercepted.url}`);
|
||||||
|
|
||||||
return this.http
|
return this.http
|
||||||
.get<T>(intercepted.url, intercepted.options as JsonOptions)
|
.get<T>(intercepted.url, intercepted.options as JsonOptions)
|
||||||
.pipe(catchError<T, any>(this.errorHandler.handleError)) as Observable<T>;
|
.pipe(catchError<T, any>(this.errorHandler.handleError)) as Observable<T>;
|
||||||
@ -92,6 +96,8 @@ export class HttpServer {
|
|||||||
getText(server: Server, url: string, options?: TextOptions): Observable<string> {
|
getText(server: Server, url: string, options?: TextOptions): Observable<string> {
|
||||||
options = this.getTextOptions(options);
|
options = this.getTextOptions(options);
|
||||||
const intercepted = this.getOptionsForServer<TextOptions>(server, url, options);
|
const intercepted = this.getOptionsForServer<TextOptions>(server, url, options);
|
||||||
|
this.requestsNotificationEmitter.emit(`GET ${intercepted.url}`);
|
||||||
|
|
||||||
return this.http
|
return this.http
|
||||||
.get(intercepted.url, intercepted.options as TextOptions)
|
.get(intercepted.url, intercepted.options as TextOptions)
|
||||||
.pipe(catchError(this.errorHandler.handleError));
|
.pipe(catchError(this.errorHandler.handleError));
|
||||||
@ -100,6 +106,8 @@ export class HttpServer {
|
|||||||
post<T>(server: Server, url: string, body: any | null, options?: JsonOptions): Observable<T> {
|
post<T>(server: Server, url: string, body: any | null, options?: JsonOptions): Observable<T> {
|
||||||
options = this.getJsonOptions(options);
|
options = this.getJsonOptions(options);
|
||||||
const intercepted = this.getOptionsForServer(server, url, options);
|
const intercepted = this.getOptionsForServer(server, url, options);
|
||||||
|
this.requestsNotificationEmitter.emit(`POST ${intercepted.url}`);
|
||||||
|
|
||||||
return this.http
|
return this.http
|
||||||
.post<T>(intercepted.url, body, intercepted.options)
|
.post<T>(intercepted.url, body, intercepted.options)
|
||||||
.pipe(catchError<T, any>(this.errorHandler.handleError)) as Observable<T>;
|
.pipe(catchError<T, any>(this.errorHandler.handleError)) as Observable<T>;
|
||||||
@ -108,6 +116,8 @@ export class HttpServer {
|
|||||||
put<T>(server: Server, url: string, body: any, options?: JsonOptions): Observable<T> {
|
put<T>(server: Server, url: string, body: any, options?: JsonOptions): Observable<T> {
|
||||||
options = this.getJsonOptions(options);
|
options = this.getJsonOptions(options);
|
||||||
const intercepted = this.getOptionsForServer(server, url, options);
|
const intercepted = this.getOptionsForServer(server, url, options);
|
||||||
|
this.requestsNotificationEmitter.emit(`PUT ${intercepted.url}`);
|
||||||
|
|
||||||
return this.http
|
return this.http
|
||||||
.put<T>(intercepted.url, body, intercepted.options)
|
.put<T>(intercepted.url, body, intercepted.options)
|
||||||
.pipe(catchError<T, any>(this.errorHandler.handleError)) as Observable<T>;
|
.pipe(catchError<T, any>(this.errorHandler.handleError)) as Observable<T>;
|
||||||
@ -116,6 +126,8 @@ export class HttpServer {
|
|||||||
delete<T>(server: Server, url: string, options?: JsonOptions): Observable<T> {
|
delete<T>(server: Server, url: string, options?: JsonOptions): Observable<T> {
|
||||||
options = this.getJsonOptions(options);
|
options = this.getJsonOptions(options);
|
||||||
const intercepted = this.getOptionsForServer(server, url, options);
|
const intercepted = this.getOptionsForServer(server, url, options);
|
||||||
|
this.requestsNotificationEmitter.emit(`DELETE ${intercepted.url}`);
|
||||||
|
|
||||||
return this.http
|
return this.http
|
||||||
.delete<T>(intercepted.url, intercepted.options)
|
.delete<T>(intercepted.url, intercepted.options)
|
||||||
.pipe(catchError<T, any>(this.errorHandler.handleError)) as Observable<T>;
|
.pipe(catchError<T, any>(this.errorHandler.handleError)) as Observable<T>;
|
||||||
|
@ -5,6 +5,7 @@ import { Subject } from 'rxjs';
|
|||||||
export class MapSettingsService {
|
export class MapSettingsService {
|
||||||
public isMapLocked = new Subject<boolean>();
|
public isMapLocked = new Subject<boolean>();
|
||||||
public isTopologySummaryVisible: boolean = false;
|
public isTopologySummaryVisible: boolean = false;
|
||||||
|
public isLogConsoleVisible: boolean = false;
|
||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
@ -15,4 +16,8 @@ export class MapSettingsService {
|
|||||||
toggleTopologySummary(value: boolean) {
|
toggleTopologySummary(value: boolean) {
|
||||||
this.isTopologySummaryVisible = value;
|
this.isTopologySummaryVisible = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleLogConsole(value: boolean) {
|
||||||
|
this.isLogConsoleVisible = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,30 @@ describe('NodeService', () => {
|
|||||||
expect(req.request.body).toEqual({});
|
expect(req.request.body).toEqual({});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should suspend node', inject([NodeService], (service:NodeService) => {
|
||||||
|
const node = new Node();
|
||||||
|
node.project_id = 'myproject';
|
||||||
|
node.node_id = 'id';
|
||||||
|
|
||||||
|
service.suspend(server, node).subscribe();
|
||||||
|
|
||||||
|
const req = httpTestingController.expectOne('http://127.0.0.1:3080/v2/projects/myproject/nodes/id/suspend');
|
||||||
|
expect(req.request.method).toEqual('POST');
|
||||||
|
expect(req.request.body).toEqual({});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should reload node', inject([NodeService], (service:NodeService) => {
|
||||||
|
const node = new Node();
|
||||||
|
node.project_id = 'myproject';
|
||||||
|
node.node_id = 'id';
|
||||||
|
|
||||||
|
service.reload(server, node).subscribe();
|
||||||
|
|
||||||
|
const req = httpTestingController.expectOne('http://127.0.0.1:3080/v2/projects/myproject/nodes/id/reload');
|
||||||
|
expect(req.request.method).toEqual('POST');
|
||||||
|
expect(req.request.body).toEqual({});
|
||||||
|
}));
|
||||||
|
|
||||||
it('should start all nodes', inject([NodeService], (service: NodeService) => {
|
it('should start all nodes', inject([NodeService], (service: NodeService) => {
|
||||||
let project = {
|
let project = {
|
||||||
project_id: '1'
|
project_id: '1'
|
||||||
|
@ -29,10 +29,18 @@ export class NodeService {
|
|||||||
return this.httpServer.post(server, `/projects/${project.project_id}/nodes/stop`, {});
|
return this.httpServer.post(server, `/projects/${project.project_id}/nodes/stop`, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend(server: Server, node: Node) {
|
||||||
|
return this.httpServer.post<Node>(server, `/projects/${node.project_id}/nodes/${node.node_id}/suspend`, {});
|
||||||
|
}
|
||||||
|
|
||||||
suspendAll(server: Server, project: Project) {
|
suspendAll(server: Server, project: Project) {
|
||||||
return this.httpServer.post(server, `/projects/${project.project_id}/nodes/suspend`, {});
|
return this.httpServer.post(server, `/projects/${project.project_id}/nodes/suspend`, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reload(server: Server, node: Node) {
|
||||||
|
return this.httpServer.post<Node>(server, `/projects/${node.project_id}/nodes/${node.node_id}/reload`, {});
|
||||||
|
}
|
||||||
|
|
||||||
reloadAll(server: Server, project: Project) {
|
reloadAll(server: Server, project: Project) {
|
||||||
return this.httpServer.post(server, `/projects/${project.project_id}/nodes/reload`, {});
|
return this.httpServer.post(server, `/projects/${project.project_id}/nodes/reload`, {});
|
||||||
}
|
}
|
||||||
|
10
yarn.lock
10
yarn.lock
@ -7351,6 +7351,11 @@ saucelabs@^1.5.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
https-proxy-agent "^2.2.1"
|
https-proxy-agent "^2.2.1"
|
||||||
|
|
||||||
|
save-svg-as-png@^1.4.14:
|
||||||
|
version "1.4.14"
|
||||||
|
resolved "https://registry.npmjs.org/save-svg-as-png/-/save-svg-as-png-1.4.14.tgz#d5017bb9746adf00c146a17e63ed4badd1e10b40"
|
||||||
|
integrity sha512-hJqOFSdRvhBVD2pQSM+mJStvQGfnvQCCF6ULtAxdjF4lDwXYfWZ9Eug0fcRl05YyPL2yknCDBEOpbO4Fkw5qmg==
|
||||||
|
|
||||||
sax@0.5.x:
|
sax@0.5.x:
|
||||||
version "0.5.8"
|
version "0.5.8"
|
||||||
resolved "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1"
|
resolved "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1"
|
||||||
@ -9006,6 +9011,11 @@ xtend@~2.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
object-keys "~0.4.0"
|
object-keys "~0.4.0"
|
||||||
|
|
||||||
|
xterm@^3.14.5:
|
||||||
|
version "3.14.5"
|
||||||
|
resolved "https://registry.npmjs.org/xterm/-/xterm-3.14.5.tgz#c9d14e48be6873aa46fb429f22f2165557fd2dea"
|
||||||
|
integrity sha512-DVmQ8jlEtL+WbBKUZuMxHMBgK/yeIZwkXB81bH+MGaKKnJGYwA+770hzhXPfwEIokK9On9YIFPRleVp/5G7z9g==
|
||||||
|
|
||||||
y18n@^3.2.1:
|
y18n@^3.2.1:
|
||||||
version "3.2.1"
|
version "3.2.1"
|
||||||
resolved "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
|
resolved "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user