mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-06-18 14:58:15 +00:00
Merge pull request #768 from GNS3/HTML5-console-support
HTML5 console support
This commit is contained in:
@ -80,6 +80,8 @@
|
|||||||
"tree-kill": "^1.2.1",
|
"tree-kill": "^1.2.1",
|
||||||
"typeface-roboto": "^0.0.75",
|
"typeface-roboto": "^0.0.75",
|
||||||
"xterm": "^4.1.0",
|
"xterm": "^4.1.0",
|
||||||
|
"xterm-addon-attach": "^0.5.0",
|
||||||
|
"xterm-addon-fit": "^0.3.0",
|
||||||
"yargs": "^15.0.2",
|
"yargs": "^15.0.2",
|
||||||
"zone.js": "^0.10.2"
|
"zone.js": "^0.10.2"
|
||||||
},
|
},
|
||||||
|
@ -268,6 +268,10 @@ import { OpenFileExplorerActionComponent } from './components/project-map/contex
|
|||||||
import { NgxChildProcessModule } from 'ngx-childprocess';
|
import { NgxChildProcessModule } from 'ngx-childprocess';
|
||||||
import { ServerResolve } from './resolvers/server-resolve';
|
import { ServerResolve } from './resolvers/server-resolve';
|
||||||
import { ProjectMapGuard } from './guards/project-map-guard';
|
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) {
|
if (environment.production) {
|
||||||
Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', {
|
Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', {
|
||||||
@ -449,7 +453,10 @@ if (environment.production) {
|
|||||||
SystemStatusComponent,
|
SystemStatusComponent,
|
||||||
StatusInfoComponent,
|
StatusInfoComponent,
|
||||||
StatusChartComponent,
|
StatusChartComponent,
|
||||||
OpenFileExplorerActionComponent
|
OpenFileExplorerActionComponent,
|
||||||
|
HttpConsoleActionComponent,
|
||||||
|
WebConsoleComponent,
|
||||||
|
ConsoleWrapperComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
@ -541,6 +548,7 @@ if (environment.production) {
|
|||||||
Gns3vmService,
|
Gns3vmService,
|
||||||
ThemeService,
|
ThemeService,
|
||||||
GoogleAnalyticsService,
|
GoogleAnalyticsService,
|
||||||
|
NodeConsoleService,
|
||||||
ServerResolve,
|
ServerResolve,
|
||||||
ProjectMapGuard,
|
ProjectMapGuard,
|
||||||
Title
|
Title
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
<div
|
||||||
|
*ngIf="isDraggingEnabled"
|
||||||
|
(document:mousemove)="dragWidget($event)"
|
||||||
|
(document:mouseup)="toggleDragging(false)">
|
||||||
|
</div>
|
||||||
|
<!-- Option with resizing
|
||||||
|
<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)"> -->
|
||||||
|
<div
|
||||||
|
class="consoleWrapper"
|
||||||
|
[ngClass]="{lightTheme: isLightThemeEnabled}"
|
||||||
|
(mousedown)="toggleDragging(true)"
|
||||||
|
[ngStyle]="style">
|
||||||
|
|
||||||
|
<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>
|
||||||
|
</ng-template>
|
||||||
|
</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>
|
||||||
|
</mat-tab>
|
||||||
|
|
||||||
|
</mat-tab-group>
|
||||||
|
|
||||||
|
<app-log-console [hidden]="!(selected.value===0)" [server]="server" [project]="project"></app-log-console>
|
||||||
|
|
||||||
|
<div *ngFor="let node of nodes; let index = index">
|
||||||
|
<app-web-console [hidden]="!(selected.value===(index+1))" [server]="server" [node]="nodes[index]"></app-web-console>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -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;
|
||||||
|
}
|
@ -0,0 +1,129 @@
|
|||||||
|
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';
|
||||||
|
import { Node } from '../../../cartography/models/node';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-console-wrapper',
|
||||||
|
templateUrl: './console-wrapper.component.html',
|
||||||
|
styleUrls: ['./console-wrapper.component.scss']
|
||||||
|
})
|
||||||
|
export class ConsoleWrapperComponent implements OnInit {
|
||||||
|
@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: '720px', height: '456px'};
|
||||||
|
|
||||||
|
this.consoleService.nodeConsoleTrigger.subscribe((node) => {
|
||||||
|
this.addTab(node, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.consoleService.closeNodeConsoleTrigger.subscribe((node) => {
|
||||||
|
let index = this.nodes.findIndex(n => n.node_id === node.node_id)
|
||||||
|
this.removeTab(index);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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 < 720 ||
|
||||||
|
event.rectangle.height < 456)
|
||||||
|
) {
|
||||||
|
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`
|
||||||
|
};
|
||||||
|
|
||||||
|
this.consoleService.resizeTerminal();
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.closeConsole.emit(false);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
<button mat-menu-item (click)="openConsole()">
|
||||||
|
<mat-icon>http</mat-icon>
|
||||||
|
<span>Web console</span>
|
||||||
|
</button>
|
@ -0,0 +1,32 @@
|
|||||||
|
import { Component, OnInit, Input } from '@angular/core';
|
||||||
|
import { Node } from '../../../../../cartography/models/node';
|
||||||
|
import { Server } from '../../../../../models/server';
|
||||||
|
import { NodeConsoleService } from '../../../../../services/nodeConsole.service';
|
||||||
|
import { ToasterService } from '../../../../../services/toaster.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,
|
||||||
|
private toasterService: ToasterService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit() {}
|
||||||
|
|
||||||
|
openConsole() {
|
||||||
|
this.nodes.forEach(n => {
|
||||||
|
if (n.status === 'started') {
|
||||||
|
this.consoleService.openConsoleForNode(n);
|
||||||
|
} else {
|
||||||
|
this.toasterService.error('To open console please start the node');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,11 @@
|
|||||||
<app-reload-node-action
|
<app-reload-node-action
|
||||||
*ngIf="nodes.length" [server]="server" [nodes]="nodes"
|
*ngIf="nodes.length" [server]="server" [nodes]="nodes"
|
||||||
></app-reload-node-action>
|
></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
|
<app-console-device-action
|
||||||
*ngIf="!projectService.isReadOnly(project) && nodes.length && isElectronApp"
|
*ngIf="!projectService.isReadOnly(project) && nodes.length && isElectronApp"
|
||||||
[server]="server"
|
[server]="server"
|
||||||
|
@ -1,19 +1,3 @@
|
|||||||
<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)">
|
|
||||||
<div class="consoleHeader">
|
<div class="consoleHeader">
|
||||||
<div class="consoleFiltering">
|
<div class="consoleFiltering">
|
||||||
<button [ngClass]="{lightTheme: isLightThemeEnabled}" class="filterButton" [matMenuTriggerFor]="filterMenu">
|
<button [ngClass]="{lightTheme: isLightThemeEnabled}" class="filterButton" [matMenuTriggerFor]="filterMenu">
|
||||||
@ -28,13 +12,9 @@
|
|||||||
<button (click)="applyFilter('server requests')" mat-menu-item>server requests</button>
|
<button (click)="applyFilter('server requests')" mat-menu-item>server requests</button>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="consoleMenu">
|
|
||||||
<mat-icon (click)="close()" class="closeButton">close</mat-icon>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div [ngClass]="{lightTheme: isLightThemeEnabled}" #console class="console" [ngStyle]="styleInside">
|
<div [ngClass]="{lightTheme: isLightThemeEnabled}" #console class="console" [ngStyle]="style">
|
||||||
<span class="console-item" *ngFor="let event of filteredEvents">
|
<span class="console-item" *ngFor="let event of filteredEvents">
|
||||||
{{event.message}} <br/>
|
{{event.message}} <br/>
|
||||||
</span>
|
</span>
|
||||||
@ -50,4 +30,3 @@
|
|||||||
type="text"
|
type="text"
|
||||||
[(ngModel)]="command"/>
|
[(ngModel)]="command"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
|
|
||||||
.consoleHeader {
|
.consoleHeader {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 30px;
|
height: 40px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit, AfterViewInit, OnDestroy, Input, ViewChild, ElementRef, Output, EventEmitter } from '@angular/core';
|
import { Component, OnInit, AfterViewInit, OnDestroy, Input, ViewChild, ElementRef, Output, EventEmitter, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { ProjectWebServiceHandler } from '../../../handlers/project-web-service-handler';
|
import { ProjectWebServiceHandler } from '../../../handlers/project-web-service-handler';
|
||||||
import { NodeService } from '../../../services/node.service';
|
import { NodeService } from '../../../services/node.service';
|
||||||
@ -14,18 +14,21 @@ import { HttpServer } from '../../../services/http-server.service';
|
|||||||
import { LogEvent } from '../../../models/logEvent';
|
import { LogEvent } from '../../../models/logEvent';
|
||||||
import { ResizeEvent } from 'angular-resizable-element';
|
import { ResizeEvent } from 'angular-resizable-element';
|
||||||
import { ThemeService } from '../../../services/theme.service';
|
import { ThemeService } from '../../../services/theme.service';
|
||||||
|
import { FormControl } from '@angular/forms';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
selector: 'app-log-console',
|
selector: 'app-log-console',
|
||||||
templateUrl: './log-console.component.html',
|
templateUrl: './log-console.component.html',
|
||||||
styleUrls: ['./log-console.component.scss']
|
styleUrls: ['./log-console.component.scss']
|
||||||
})
|
})
|
||||||
export class LogConsoleComponent implements OnInit, AfterViewInit, OnDestroy {
|
export class LogConsoleComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
@Input() project: Project;
|
|
||||||
@Input() server: Server;
|
@Input() server: Server;
|
||||||
@Output() closeConsole = new EventEmitter<boolean>();
|
@Input() project: Project;
|
||||||
|
|
||||||
@ViewChild('console', {static: false}) console: ElementRef;
|
@ViewChild('console', {static: false}) console: ElementRef;
|
||||||
|
|
||||||
private nodeSubscription: Subscription;
|
private nodeSubscription: Subscription;
|
||||||
private linkSubscription: Subscription;
|
private linkSubscription: Subscription;
|
||||||
private drawingSubscription: Subscription;
|
private drawingSubscription: Subscription;
|
||||||
@ -33,11 +36,11 @@ export class LogConsoleComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
private errorSubscription: Subscription;
|
private errorSubscription: Subscription;
|
||||||
private warningSubscription: Subscription;
|
private warningSubscription: Subscription;
|
||||||
private infoSubscription: Subscription;
|
private infoSubscription: Subscription;
|
||||||
command: string = '';
|
|
||||||
|
|
||||||
filters: string[] = ['all', 'errors', 'warnings', 'info', 'map updates', 'server requests'];
|
public command: string = '';
|
||||||
selectedFilter: string = 'all';
|
public filters: string[] = ['all', 'errors', 'warnings', 'info', 'map updates', 'server requests'];
|
||||||
filteredEvents: LogEvent[] = [];
|
public selectedFilter: string = 'all';
|
||||||
|
public filteredEvents: LogEvent[] = [];
|
||||||
|
|
||||||
private regexStart: RegExp = /^start (.*?)$/;
|
private regexStart: RegExp = /^start (.*?)$/;
|
||||||
private regexStop: RegExp = /^stop (.*?)$/;
|
private regexStop: RegExp = /^stop (.*?)$/;
|
||||||
@ -47,10 +50,9 @@ export class LogConsoleComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
private regexConsole: RegExp = /^console (.*?)$/;
|
private regexConsole: RegExp = /^console (.*?)$/;
|
||||||
|
|
||||||
public style: object = {};
|
public style: object = {};
|
||||||
public styleInside: object = { height: `120px` };
|
public isDraggingEnabled: boolean = false;
|
||||||
|
|
||||||
isDraggingEnabled: boolean = false;
|
|
||||||
public isLightThemeEnabled: boolean = false;
|
public isLightThemeEnabled: boolean = false;
|
||||||
|
public selected = new FormControl(0);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private projectWebServiceHandler: ProjectWebServiceHandler,
|
private projectWebServiceHandler: ProjectWebServiceHandler,
|
||||||
@ -58,11 +60,14 @@ export class LogConsoleComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
private nodesDataSource: NodesDataSource,
|
private nodesDataSource: NodesDataSource,
|
||||||
private logEventsDataSource: LogEventsDataSource,
|
private logEventsDataSource: LogEventsDataSource,
|
||||||
private httpService: HttpServer,
|
private httpService: HttpServer,
|
||||||
private themeService: ThemeService
|
private themeService: ThemeService,
|
||||||
|
private cd: ChangeDetectorRef
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.themeService.getActualTheme() === 'light' ? this.isLightThemeEnabled = true : this.isLightThemeEnabled = false;
|
this.themeService.getActualTheme() === 'light' ? this.isLightThemeEnabled = true : this.isLightThemeEnabled = false
|
||||||
|
this.style = { bottom: '20px', left: '20px', width: '720px', height: '340px'};
|
||||||
|
|
||||||
this.nodeSubscription = this.projectWebServiceHandler.nodeNotificationEmitter.subscribe((event) => {
|
this.nodeSubscription = this.projectWebServiceHandler.nodeNotificationEmitter.subscribe((event) => {
|
||||||
let node: Node = event.event as Node;
|
let node: Node = event.event as Node;
|
||||||
let message: string = '';
|
let message: string = '';
|
||||||
@ -118,40 +123,6 @@ export class LogConsoleComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
message: message
|
message: message
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.style = { bottom: '20px', left: '20px', width: '600px', height: '180px'};
|
|
||||||
}
|
|
||||||
|
|
||||||
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`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit() {
|
ngAfterViewInit() {
|
||||||
@ -168,36 +139,10 @@ export class LogConsoleComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
this.infoSubscription.unsubscribe();
|
this.infoSubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
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`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
applyFilter(filter: string) {
|
applyFilter(filter: string) {
|
||||||
this.selectedFilter = filter;
|
this.selectedFilter = filter;
|
||||||
this.filteredEvents = this.getFilteredEvents();
|
this.filteredEvents = this.getFilteredEvents();
|
||||||
|
this.cd.detectChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyDown(event) {
|
onKeyDown(event) {
|
||||||
@ -279,6 +224,7 @@ export class LogConsoleComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
this.showCommand(`Unknown syntax: ${this.command}`);
|
this.showCommand(`Unknown syntax: ${this.command}`);
|
||||||
}
|
}
|
||||||
this.command = '';
|
this.command = '';
|
||||||
|
this.cd.detectChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
clearConsole() {
|
clearConsole() {
|
||||||
@ -301,6 +247,7 @@ export class LogConsoleComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
setTimeout( () => {
|
setTimeout( () => {
|
||||||
this.console.nativeElement.scrollTop = this.console.nativeElement.scrollHeight;
|
this.console.nativeElement.scrollTop = this.console.nativeElement.scrollHeight;
|
||||||
}, 100 );
|
}, 100 );
|
||||||
|
this.cd.detectChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
getFilteredEvents(): LogEvent[] {
|
getFilteredEvents(): LogEvent[] {
|
||||||
@ -375,8 +322,4 @@ export class LogConsoleComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
y: ${drawing.y},
|
y: ${drawing.y},
|
||||||
z: ${drawing.z}`;
|
z: ${drawing.z}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
close() {
|
|
||||||
this.closeConsole.emit(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,14 @@
|
|||||||
<mat-icon>developer_board</mat-icon>
|
<mat-icon>developer_board</mat-icon>
|
||||||
<span>Go to servers</span>
|
<span>Go to servers</span>
|
||||||
</button>
|
</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()">
|
<button mat-menu-item (click)="addNewProject()">
|
||||||
<mat-icon>add</mat-icon>
|
<mat-icon>add</mat-icon>
|
||||||
<span>Add new blank project</span>
|
<span>Add new blank project</span>
|
||||||
@ -132,12 +140,6 @@
|
|||||||
<app-snapshot-menu-item [server]="server" [project]="project"> </app-snapshot-menu-item>
|
<app-snapshot-menu-item [server]="server" [project]="project"> </app-snapshot-menu-item>
|
||||||
</mat-toolbar-row>
|
</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">
|
<mat-toolbar-row *ngIf="!readonly">
|
||||||
<button matTooltip="Fit in view" mat-icon-button (click)="fitInView()">
|
<button matTooltip="Fit in view" mat-icon-button (click)="fitInView()">
|
||||||
<mat-icon>fullscreen</mat-icon>
|
<mat-icon>fullscreen</mat-icon>
|
||||||
@ -149,12 +151,6 @@
|
|||||||
<mat-icon>center_focus_strong</mat-icon>
|
<mat-icon>center_focus_strong</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</mat-toolbar-row>
|
</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>
|
</mat-toolbar>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -191,7 +187,7 @@
|
|||||||
<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 }">
|
<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>
|
||||||
<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>
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
.wrapper {
|
||||||
|
height: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
app-root,
|
app-root,
|
||||||
app-project-map,
|
app-project-map,
|
||||||
.project-map,
|
.project-map,
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
<div #terminal id="terminal"></div>
|
@ -0,0 +1,59 @@
|
|||||||
|
import { Component, OnInit, Input, AfterViewInit, ViewEncapsulation, ViewChild, ElementRef } 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';
|
||||||
|
import { FitAddon } from 'xterm-addon-fit';
|
||||||
|
import { NodeConsoleService } from '../../../services/nodeConsole.service';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
encapsulation: ViewEncapsulation.None,
|
||||||
|
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;
|
||||||
|
|
||||||
|
public term: Terminal = new Terminal();
|
||||||
|
public fitAddon: FitAddon = new FitAddon();
|
||||||
|
|
||||||
|
@ViewChild('terminal', {static: false}) terminal: ElementRef;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private consoleService: NodeConsoleService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.consoleService.consoleResized.subscribe(ev => {
|
||||||
|
this.fitAddon.fit();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit() {
|
||||||
|
this.term.open(this.terminal.nativeElement);
|
||||||
|
const socket = new WebSocket(this.getUrl());
|
||||||
|
|
||||||
|
socket.onerror = ((event) => {
|
||||||
|
this.term.write('Connection lost');
|
||||||
|
});
|
||||||
|
socket.onclose = ((event) => {
|
||||||
|
this.consoleService.closeConsoleForNode(this.node);
|
||||||
|
});
|
||||||
|
|
||||||
|
const attachAddon = new AttachAddon(socket);
|
||||||
|
this.term.loadAddon(attachAddon);
|
||||||
|
this.term.setOption('cursorBlink', true);
|
||||||
|
this.term.loadAddon(this.fitAddon);
|
||||||
|
this.fitAddon.activate(this.term);
|
||||||
|
this.term.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
getUrl() {
|
||||||
|
return `ws://${this.server.host}:${this.server.port}/v2/projects/${this.node.project_id}/nodes/${this.node.node_id}/console/ws`
|
||||||
|
}
|
||||||
|
}
|
24
src/app/services/nodeConsole.service.ts
Normal file
24
src/app/services/nodeConsole.service.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { Injectable, EventEmitter } from '@angular/core';
|
||||||
|
import { Node } from '../cartography/models/node';
|
||||||
|
import { Subject } from 'rxjs';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class NodeConsoleService {
|
||||||
|
public nodeConsoleTrigger = new EventEmitter<Node>();
|
||||||
|
public closeNodeConsoleTrigger = new Subject<Node>();
|
||||||
|
public consoleResized = new Subject<boolean>();
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
openConsoleForNode(node: Node) {
|
||||||
|
this.nodeConsoleTrigger.emit(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
closeConsoleForNode(node: Node) {
|
||||||
|
this.closeNodeConsoleTrigger.next(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
resizeTerminal() {
|
||||||
|
this.consoleResized.next(true);
|
||||||
|
}
|
||||||
|
}
|
10
yarn.lock
10
yarn.lock
@ -10129,6 +10129,16 @@ xterm@^4.1.0:
|
|||||||
resolved "https://registry.npmjs.org/xterm/-/xterm-4.3.0.tgz#9a302efefe75172d4f7ea3afc20f9bd983f05027"
|
resolved "https://registry.npmjs.org/xterm/-/xterm-4.3.0.tgz#9a302efefe75172d4f7ea3afc20f9bd983f05027"
|
||||||
integrity sha512-6dnrC4nxgnRKQzIWwC5HA0mnT9/rpDPZflUIr24gdcdSMTKM1QQcor4qQ/xz4Zerz6AIL/CuuBPypFfzsB63dQ==
|
integrity sha512-6dnrC4nxgnRKQzIWwC5HA0mnT9/rpDPZflUIr24gdcdSMTKM1QQcor4qQ/xz4Zerz6AIL/CuuBPypFfzsB63dQ==
|
||||||
|
|
||||||
|
xterm-addon-attach@^0.5.0:
|
||||||
|
version "0.5.0"
|
||||||
|
resolved "https://registry.npmjs.org/xterm-addon-attach/-/xterm-addon-attach-0.5.0.tgz#e35cde4ae493ecace7d07e52ff2b9714e3c43068"
|
||||||
|
integrity sha512-L6ThPjF/fVt+gJS2+2h2rEEAXNxIRmCU8/RCM6rYR08K9GtPiHmYcnpRT7WNJf31yFLpWVA8dKcItfP3C0ZKlA==
|
||||||
|
|
||||||
|
xterm-addon-fit@^0.3.0:
|
||||||
|
version "0.3.0"
|
||||||
|
resolved "https://registry.npmjs.org/xterm-addon-fit/-/xterm-addon-fit-0.3.0.tgz#341710741027de9d648a9f84415a01ddfdbbe715"
|
||||||
|
integrity sha512-kvkiqHVrnMXgyCH9Xn0BOBJ7XaWC/4BgpSWQy3SueqximgW630t/QOankgqkvk11iTOCwWdAY9DTyQBXUMN3lw==
|
||||||
|
|
||||||
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"
|
||||||
|
Reference in New Issue
Block a user