diff --git a/package.json b/package.json index f973a565..066d2671 100644 --- a/package.json +++ b/package.json @@ -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" }, diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 110717b4..657c1be0 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -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 diff --git a/src/app/components/project-map/console-wrapper/console-wrapper.component.html b/src/app/components/project-map/console-wrapper/console-wrapper.component.html new file mode 100644 index 00000000..7b2280a4 --- /dev/null +++ b/src/app/components/project-map/console-wrapper/console-wrapper.component.html @@ -0,0 +1,44 @@ +
+
+
+ + + + +
GNS3 console
+ +
+ + place for log console +
+ + + +
{{node.name}}
+ +
+ +
+ +
+
+ +
+
diff --git a/src/app/components/project-map/console-wrapper/console-wrapper.component.scss b/src/app/components/project-map/console-wrapper/console-wrapper.component.scss new file mode 100644 index 00000000..374dd797 --- /dev/null +++ b/src/app/components/project-map/console-wrapper/console-wrapper.component.scss @@ -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; +} diff --git a/src/app/components/project-map/console-wrapper/console-wrapper.component.spec.ts b/src/app/components/project-map/console-wrapper/console-wrapper.component.spec.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/app/components/project-map/console-wrapper/console-wrapper.component.ts b/src/app/components/project-map/console-wrapper/console-wrapper.component.ts new file mode 100644 index 00000000..102ca13c --- /dev/null +++ b/src/app/components/project-map/console-wrapper/console-wrapper.component.ts @@ -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(); + + 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); + } +} diff --git a/src/app/components/project-map/context-menu/actions/http-console/http-console-action.component.html b/src/app/components/project-map/context-menu/actions/http-console/http-console-action.component.html new file mode 100644 index 00000000..55f6dcce --- /dev/null +++ b/src/app/components/project-map/context-menu/actions/http-console/http-console-action.component.html @@ -0,0 +1,4 @@ + diff --git a/src/app/components/project-map/context-menu/actions/http-console/http-console-action.component.spec.ts b/src/app/components/project-map/context-menu/actions/http-console/http-console-action.component.spec.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/app/components/project-map/context-menu/actions/http-console/http-console-action.component.ts b/src/app/components/project-map/context-menu/actions/http-console/http-console-action.component.ts new file mode 100644 index 00000000..6ead7e85 --- /dev/null +++ b/src/app/components/project-map/context-menu/actions/http-console/http-console-action.component.ts @@ -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]); + } +} diff --git a/src/app/components/project-map/context-menu/context-menu.component.html b/src/app/components/project-map/context-menu/context-menu.component.html index 3bc02127..6b35ff5d 100644 --- a/src/app/components/project-map/context-menu/context-menu.component.html +++ b/src/app/components/project-map/context-menu/context-menu.component.html @@ -15,6 +15,11 @@ + -
+ + + + +
{{tab}}
+ +
+ +
+ +
+
+
+ +
diff --git a/src/app/components/project-map/log-console/log-console.component.ts b/src/app/components/project-map/log-console/log-console.component.ts index 8e6d4657..c685f7cf 100644 --- a/src/app/components/project-map/log-console/log-console.component.ts +++ b/src/app/components/project-map/log-console/log-console.component.ts @@ -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(); + @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[] { diff --git a/src/app/components/project-map/project-map.component.html b/src/app/components/project-map/project-map.component.html index 8276557c..bc4c464a 100644 --- a/src/app/components/project-map/project-map.component.html +++ b/src/app/components/project-map/project-map.component.html @@ -51,6 +51,14 @@ developer_board Go to servers + + - - - - - - @@ -191,7 +187,7 @@
- +
diff --git a/src/app/components/project-map/web-console/web-console.component.html b/src/app/components/project-map/web-console/web-console.component.html new file mode 100644 index 00000000..424caa42 --- /dev/null +++ b/src/app/components/project-map/web-console/web-console.component.html @@ -0,0 +1 @@ +
diff --git a/src/app/components/project-map/web-console/web-console.component.scss b/src/app/components/project-map/web-console/web-console.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/components/project-map/web-console/web-console.component.spec.ts b/src/app/components/project-map/web-console/web-console.component.spec.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/app/components/project-map/web-console/web-console.component.ts b/src/app/components/project-map/web-console/web-console.component.ts new file mode 100644 index 00000000..481eb55e --- /dev/null +++ b/src/app/components/project-map/web-console/web-console.component.ts @@ -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` + } +} diff --git a/src/app/services/nodeConsole.service.ts b/src/app/services/nodeConsole.service.ts new file mode 100644 index 00000000..a311b3b2 --- /dev/null +++ b/src/app/services/nodeConsole.service.ts @@ -0,0 +1,13 @@ +import { Injectable, EventEmitter } from '@angular/core'; +import { Node } from '../cartography/models/node'; + +@Injectable() +export class NodeConsoleService { + public nodeConsoleTrigger = new EventEmitter(); + + constructor() {} + + openConsoleForNode(node: Node) { + this.nodeConsoleTrigger.emit(node); + } +}