Initial implementation

This commit is contained in:
piotrpekala7 2020-03-23 01:26:52 +01:00
parent 34059da0f6
commit 4fc87c4cda
18 changed files with 418 additions and 24 deletions

View File

@ -80,6 +80,7 @@
"tree-kill": "^1.2.1",
"typeface-roboto": "^0.0.75",
"xterm": "^4.1.0",
"xterm-addon-attach": "^0.5.0",
"yargs": "^15.0.2",
"zone.js": "^0.10.2"
},

View File

@ -268,6 +268,10 @@ import { OpenFileExplorerActionComponent } from './components/project-map/contex
import { NgxChildProcessModule } from 'ngx-childprocess';
import { ServerResolve } from './resolvers/server-resolve';
import { ProjectMapGuard } from './guards/project-map-guard';
import { HttpConsoleActionComponent } from './components/project-map/context-menu/actions/http-console/http-console-action.component';
import { WebConsoleComponent } from './components/project-map/web-console/web-console.component';
import { ConsoleWrapperComponent } from './components/project-map/console-wrapper/console-wrapper.component';
import { NodeConsoleService } from './services/nodeConsole.service';
if (environment.production) {
Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', {
@ -449,7 +453,10 @@ if (environment.production) {
SystemStatusComponent,
StatusInfoComponent,
StatusChartComponent,
OpenFileExplorerActionComponent
OpenFileExplorerActionComponent,
HttpConsoleActionComponent,
WebConsoleComponent,
ConsoleWrapperComponent
],
imports: [
BrowserModule,
@ -541,6 +548,7 @@ if (environment.production) {
Gns3vmService,
ThemeService,
GoogleAnalyticsService,
NodeConsoleService,
ServerResolve,
ProjectMapGuard,
Title

View File

@ -0,0 +1,44 @@
<div
*ngIf="isDraggingEnabled"
(document:mousemove)="dragWidget($event)"
(document:mouseup)="toggleDragging(false)">
</div>
<div
class="consoleWrapper"
[ngClass]="{lightTheme: isLightThemeEnabled}"
(mousedown)="toggleDragging(true)"
[ngStyle]="style"
mwlResizable
[validateResize]="validate"
[resizeEdges]="{ right: true, left: true, bottom: true, top: true }"
[enableGhostResize]="true"
(resizeStart)="toggleDragging(false)"
(resizeEnd)="onResizeEnd($event)">
<mat-tab-group [selectedIndex]="selected.value" (selectedIndexChange)="selected.setValue($event)">
<mat-tab>
<ng-template mat-tab-label>
<div class="col" style="margin-left: 20px;">GNS3 console</div>
<button *ngIf="tab !== 'GNS3 console'" style="color:white" mat-icon-button (click)="removeTab(index)">
<mat-icon>close</mat-icon>
</button>
</ng-template>
place for log console
</mat-tab>
<mat-tab *ngFor="let node of nodes; let index = index" [label]="tab">
<ng-template mat-tab-label>
<div class="col" style="margin-left: 20px;">{{node.name}}</div>
<button style="color:white" mat-icon-button (click)="removeTab(index)">
<mat-icon>close</mat-icon>
</button>
</ng-template>
<div>
<app-web-console [server]="server" [node]="node"></app-web-console>
</div>
</mat-tab>
</mat-tab-group>
</div>

View File

@ -0,0 +1,96 @@
.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;
}
.lightTheme {
background: white!important;
color: black;
}
.filterButton {
background: transparent;
color: white;
border: none;
margin-top: 0px;
outline: none;
color: #dbd5d5;
font-weight: bold;
padding: 0px;
}
.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: transparent;
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;
}

View File

@ -0,0 +1,127 @@
import { Component, OnInit, AfterViewInit, OnDestroy, Input, Output, EventEmitter } from '@angular/core';
import { Subscription } from 'rxjs';
import { Project } from '../../../models/project';
import { Server } from '../../../models/server';
import { ResizeEvent } from 'angular-resizable-element';
import { ThemeService } from '../../../services/theme.service';
import { FormControl } from '@angular/forms';
import { NodeConsoleService } from '../../../services/nodeConsole.service';
@Component({
selector: 'app-console-wrapper',
templateUrl: './console-wrapper.component.html',
styleUrls: ['./console-wrapper.component.scss']
})
export class ConsoleWrapperComponent implements OnInit, AfterViewInit, OnDestroy {
@Input() server: Server;
@Input() project: Project;
@Output() closeConsole = new EventEmitter<boolean>();
filters: string[] = ['all', 'errors', 'warnings', 'info', 'map updates', 'server requests'];
selectedFilter: string = 'all';
public style: object = {};
public styleInside: object = { height: `120px` };
public isDraggingEnabled: boolean = false;
public isLightThemeEnabled: boolean = false;
constructor(
private consoleService: NodeConsoleService,
private themeService: ThemeService
) {}
nodes: Node[] = [];
selected = new FormControl(0);
ngOnInit() {
this.themeService.getActualTheme() === 'light' ? this.isLightThemeEnabled = true : this.isLightThemeEnabled = false;
this.style = { bottom: '20px', left: '20px', width: '600px', height: '180px'}; // style properties
this.consoleService.nodeConsoleTrigger.subscribe((node) => {
this.addTab(node, true);
});
}
ngAfterViewInit() {
// this.console.nativeElement.scrollTop = this.console.nativeElement.scrollHeight;
}
ngOnDestroy() {}
addTab(node: Node, selectAfterAdding: boolean) {
this.nodes.push(node);
if (selectAfterAdding) {
this.selected.setValue(this.nodes.length);
}
}
removeTab(index: number) {
this.nodes.splice(index, 1);
}
toggleDragging(value: boolean) {
this.isDraggingEnabled = value;
}
dragWidget(event) {
let x: number = Number(event.movementX);
let y: number = Number(event.movementY);
let width: number = Number(this.style['width'].split('px')[0]);
let height: number = Number(this.style['height'].split('px')[0]);
let left: number = Number(this.style['left'].split('px')[0]) + x;
if (this.style['top']) {
let top: number = Number(this.style['top'].split('px')[0]) + y;
this.style = {
position: 'fixed',
left: `${left}px`,
top: `${top}px`,
width: `${width}px`,
height: `${height}px`
};
} else {
let bottom: number = Number(this.style['bottom'].split('px')[0]) - y;
this.style = {
position: 'fixed',
left: `${left}px`,
bottom: `${bottom}px`,
width: `${width}px`,
height: `${height}px`
};
}
}
validate(event: ResizeEvent): boolean {
if (
event.rectangle.width &&
event.rectangle.height &&
(event.rectangle.width < 600 ||
event.rectangle.height < 180)
) {
return false;
}
return true;
}
onResizeEnd(event: ResizeEvent): void {
this.style = {
position: 'fixed',
left: `${event.rectangle.left}px`,
top: `${event.rectangle.top}px`,
width: `${event.rectangle.width}px`,
height: `${event.rectangle.height}px`
};
this.styleInside = {
height: `${event.rectangle.height - 60}px`,
width: `${event.rectangle.width}px`
};
}
close() {
this.closeConsole.emit(false);
}
}

View File

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

View File

@ -0,0 +1,24 @@
import { Component, OnInit, Input } from '@angular/core';
import { Node } from '../../../../../cartography/models/node';
import { Server } from '../../../../../models/server';
import { NodeConsoleService } from '../../../../../services/nodeConsole.service';
@Component({
selector: 'app-http-console-action',
templateUrl: './http-console-action.component.html'
})
export class HttpConsoleActionComponent implements OnInit {
@Input() server: Server;
@Input() nodes: Node[];
constructor(
private consoleService: NodeConsoleService
) { }
ngOnInit() {}
openConsole() {
this.consoleService.openConsoleForNode(this.nodes[0]);
}
}

View File

@ -15,6 +15,11 @@
<app-reload-node-action
*ngIf="nodes.length" [server]="server" [nodes]="nodes"
></app-reload-node-action>
<app-http-console-action
*ngIf="!projectService.isReadOnly(project) && nodes.length"
[server]="server"
[nodes]="nodes"
></app-http-console-action>
<app-console-device-action
*ngIf="!projectService.isReadOnly(project) && nodes.length && isElectronApp"
[server]="server"

View File

@ -14,7 +14,24 @@
[enableGhostResize]="true"
(resizeStart)="toggleDragging(false)"
(resizeEnd)="onResizeEnd($event)">
<div class="consoleHeader">
<mat-tab-group [selectedIndex]="selected.value"
(selectedIndexChange)="selected.setValue($event)">
<mat-tab *ngFor="let tab of tabs; let index = index" [label]="tab">
<ng-template mat-tab-label>
<div class="col" style="margin-left: 20px;">{{tab}}</div>
<button *ngIf="tab !== 'GNS3 console'" style="color:white" mat-icon-button (click)="removeTab(index)">
<mat-icon>close</mat-icon>
</button>
</ng-template>
<div *ngIf="tab !== 'GNS3 console'">
<app-web-console [server]="server" [node]="node"></app-web-console>
</div>
</mat-tab>
</mat-tab-group>
<!-- <div class="consoleHeader">
<div class="consoleFiltering">
<button [ngClass]="{lightTheme: isLightThemeEnabled}" class="filterButton" [matMenuTriggerFor]="filterMenu">
Apply filter
@ -49,5 +66,5 @@
(keydown)="onKeyDown($event)"
type="text"
[(ngModel)]="command"/>
</div>
</div> -->
</div>

View File

@ -14,6 +14,7 @@ import { HttpServer } from '../../../services/http-server.service';
import { LogEvent } from '../../../models/logEvent';
import { ResizeEvent } from 'angular-resizable-element';
import { ThemeService } from '../../../services/theme.service';
import { FormControl } from '@angular/forms';
@Component({
@ -22,10 +23,12 @@ import { ThemeService } from '../../../services/theme.service';
styleUrls: ['./log-console.component.scss']
})
export class LogConsoleComponent implements OnInit, AfterViewInit, OnDestroy {
@Input() project: Project;
@Input() server: Server;
@Input() project: Project;
@Output() closeConsole = new EventEmitter<boolean>();
@ViewChild('console', {static: false}) console: ElementRef;
private nodeSubscription: Subscription;
private linkSubscription: Subscription;
private drawingSubscription: Subscription;
@ -60,6 +63,21 @@ export class LogConsoleComponent implements OnInit, AfterViewInit, OnDestroy {
private httpService: HttpServer,
private themeService: ThemeService
) {}
tabs = ['GNS3 console', 'PC2'];
selected = new FormControl(0);
addTab(selectAfterAdding: boolean) {
this.tabs.push('New');
if (selectAfterAdding) {
this.selected.setValue(this.tabs.length - 1);
}
}
removeTab(index: number) {
this.tabs.splice(index, 1);
}
ngOnInit() {
this.themeService.getActualTheme() === 'light' ? this.isLightThemeEnabled = true : this.isLightThemeEnabled = false;
@ -155,7 +173,7 @@ export class LogConsoleComponent implements OnInit, AfterViewInit, OnDestroy {
}
ngAfterViewInit() {
this.console.nativeElement.scrollTop = this.console.nativeElement.scrollHeight;
// this.console.nativeElement.scrollTop = this.console.nativeElement.scrollHeight;
}
ngOnDestroy() {
@ -294,13 +312,13 @@ export class LogConsoleComponent implements OnInit, AfterViewInit, OnDestroy {
}
showMessage(event: LogEvent) {
this.logEventsDataSource.add(event);
this.filteredEvents = this.getFilteredEvents();
this.console.nativeElement.scrollTop = this.console.nativeElement.scrollHeight;
// 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 );
// setTimeout( () => {
// this.console.nativeElement.scrollTop = this.console.nativeElement.scrollHeight;
// }, 100 );
}
getFilteredEvents(): LogEvent[] {

View File

@ -51,6 +51,14 @@
<mat-icon>developer_board</mat-icon>
<span>Go to servers</span>
</button>
<button mat-menu-item routerLink="/server/{{server.id}}/preferences">
<mat-icon>settings_applications</mat-icon>
<span>Go to preferences</span>
</button>
<button mat-menu-item routerLink="/server/{{server.id}}/systemstatus">
<mat-icon>info</mat-icon>
<span>Go to system status</span>
</button>
<button mat-menu-item (click)="addNewProject()">
<mat-icon>add</mat-icon>
<span>Add new blank project</span>
@ -132,12 +140,6 @@
<app-snapshot-menu-item [server]="server" [project]="project"> </app-snapshot-menu-item>
</mat-toolbar-row>
<mat-toolbar-row *ngIf="!readonly">
<button matTooltip="Go to preferences" mat-icon-button routerLink="/server/{{server.id}}/preferences">
<mat-icon>settings_applications</mat-icon>
</button>
</mat-toolbar-row>
<mat-toolbar-row *ngIf="!readonly">
<button matTooltip="Fit in view" mat-icon-button (click)="fitInView()">
<mat-icon>fullscreen</mat-icon>
@ -149,12 +151,6 @@
<mat-icon>center_focus_strong</mat-icon>
</button>
</mat-toolbar-row>
<mat-toolbar-row *ngIf="!readonly">
<button matTooltip="Go to system status" mat-icon-button routerLink="/server/{{server.id}}/systemstatus">
<mat-icon>info</mat-icon>
</button>
</mat-toolbar-row>
</mat-toolbar>
</div>
@ -191,7 +187,7 @@
<app-text-added [server]="server" [project]="project" (drawingSaved)="onDrawingSaved()"> </app-text-added>
<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>
<app-console-wrapper *ngIf="project" [server]="server" [project]="project" (closeConsole)='toggleShowConsole($event)'></app-console-wrapper>
</div>
<div [ngClass]="{ visible: !isTopologySummaryVisible }">
<app-topology-summary *ngIf="project" [server]="server" [project]="project" (closeTopologySummary)='toggleShowTopologySummary($event)'></app-topology-summary>

View File

@ -0,0 +1 @@
<div id="terminal"></div>

View File

@ -0,0 +1,40 @@
import { Component, OnInit, Input, AfterViewInit } from '@angular/core';
import { Project } from '../../../models/project';
import { Server } from '../../../models/server';
import { Terminal } from 'xterm';
import { AttachAddon } from 'xterm-addon-attach';
import { Node } from '../../../cartography/models/node';
@Component({
selector: 'app-web-console',
templateUrl: './web-console.component.html',
styleUrls: ['../../../../../node_modules/xterm/css/xterm.css']
})
export class WebConsoleComponent implements OnInit, AfterViewInit {
@Input() server: Server;
@Input() project: Project;
@Input() node: Node;
constructor() {}
ngOnInit() {}
ngAfterViewInit() {
const term = new Terminal();
setTimeout(() => {
term.open(document.getElementById('terminal'));
term.write('\x1B[1;3;31mxterm.js\x1B[0m $ ')
const socket = new WebSocket(this.getUrl());
const attachAddon = new AttachAddon(socket);
term.loadAddon(attachAddon);
console.log('Console is running...');
}, 1000);
}
getUrl() {
return `ws://${this.server.host}:${this.server.port}/v2/projects/${this.node.project_id}/nodes/${this.node.node_id}/console/ws`
}
}

View File

@ -0,0 +1,13 @@
import { Injectable, EventEmitter } from '@angular/core';
import { Node } from '../cartography/models/node';
@Injectable()
export class NodeConsoleService {
public nodeConsoleTrigger = new EventEmitter<Node>();
constructor() {}
openConsoleForNode(node: Node) {
this.nodeConsoleTrigger.emit(node);
}
}