Merge pull request #25 from avatao-content/console

Console
This commit is contained in:
therealkrispet 2018-05-30 14:30:09 +02:00 committed by GitHub
commit 3b94ed0db6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 275 additions and 20 deletions

View File

@ -8,6 +8,7 @@ import { DashboardComponent } from './dashboard/dashboard.component';
import { IdeComponent } from './ide/ide.component';
import { TerminalComponent } from './terminal/terminal.component';
import { MessagesComponent } from './messages/messages.component';
import { ConsoleComponent } from './console/console.component';
import { TestmessengerComponent } from './testmessenger/testmessenger.component';
import { config } from './config';
@ -17,6 +18,7 @@ const routes: Routes = [
{ path: config.ide.route, component: IdeComponent },
{ path: config.terminal.route, component: TerminalComponent },
{ path: config.messages.route, component: MessagesComponent },
{ path: config.console.route, component: ConsoleComponent },
{ path: config.testmessenger.route, component: TestmessengerComponent }
];

View File

@ -26,6 +26,8 @@ import { AppRoutingModule } from './app-routing.module';
import { TestmessengerComponent } from './testmessenger/testmessenger.component';
import { DeploymentNotificationService } from './services/deployment-notification.service';
import { SafePipe } from './pipes/safe.pipe';
import { ConsoleComponent } from './console/console.component';
import { ProcessLogService } from './services/processlog.service';
@NgModule({
@ -39,7 +41,8 @@ import { SafePipe } from './pipes/safe.pipe';
TerminalComponent,
DashboardComponent,
TestmessengerComponent,
SafePipe
SafePipe,
ConsoleComponent
],
imports: [
BrowserModule,
@ -55,7 +58,8 @@ import { SafePipe } from './pipes/safe.pipe';
TerminadoService,
FSMUpdateService,
ProcessManagerService,
DeploymentNotificationService
DeploymentNotificationService,
ProcessLogService
],
bootstrap: [
AppComponent

View File

@ -5,6 +5,7 @@ export const config = {
documentTitle: 'Avatao Tutorials',
dashboard: {
route: 'dashboard',
terminalOrConsole: 'terminal',
currentLayout: 'terminal-ide-web',
enabledLayouts: [
'terminal-ide-web',
@ -26,14 +27,25 @@ export const config = {
defaultLanguage: 'text',
deployProcessName: 'webservice',
showDeployButton: true,
reloadIframeOnDeployButtonClick: true
reloadIframeOnDeploy: true,
showConsoleOnDeploy: true,
},
terminal: {
route: 'shell'
},
messages: {
route: 'messages',
showNextButton: true
showNextButton: false
},
console: {
route: 'console',
defaultContent: '',
rewriteContentWithProcessLogsOnDeploy: 'stdout',
showLiveLogs: true,
defaultLogs: {
stdout: '',
stderr: ''
}
},
testmessenger: {
route: 'testmessenger'

View File

@ -0,0 +1,9 @@
// Copyright (C) 2018 Avatao.com Innovative Learning Kft.
// All Rights Reserved. See LICENSE file for details.
export interface ConsoleCommand {
command: string;
content?: string;
showLiveLogs?: boolean;
rewriteContentWithProcessLogsOnDeploy?: string;
}

View File

@ -0,0 +1,10 @@
<!-- Copyright (C) 2018 Avatao.com Innovative Learning Kft.
All Rights Reserved. See LICENSE file for details. -->
<textarea [(ngModel)]="console_content"
#tfwconsole
[scrollTop]="tfwconsole.scrollHeight"
readonly
class="tfw-console"
spellcheck="false">
</textarea>

View File

@ -0,0 +1,15 @@
// Copyright (C) 2018 Avatao.com Innovative Learning Kft.
// All Rights Reserved. See LICENSE file for details.
@import "../../assets/scss/variables.scss";
.tfw-console {
resize: none;
height: 100%;
width: 100%;
background-color: $tao-gray-800;
border: 1px solid $tao-gray-800;
color: white;
padding: 0 8px 8px 8px;
font-size: small;
}

View File

@ -0,0 +1,73 @@
// Copyright (C) 2018 Avatao.com Innovative Learning Kft.
// All Rights Reserved. See LICENSE file for details.
import { Component, OnInit } from '@angular/core';
import { WebSocketService } from '../services/websocket.service';
import { ConsoleCommand } from './console-command';
import { config } from '../config';
import { ProcessLogService } from '../services/processlog.service';
import { LogMessage } from '../services/log.message';
@Component({
selector: 'app-console',
templateUrl: './console.component.html',
styleUrls: ['./console.component.scss']
})
export class ConsoleComponent implements OnInit {
console_content: string = config.console.defaultContent;
rewriteContentWithProcessLogsOnDeploy: string = config.console.rewriteContentWithProcessLogsOnDeploy;
command_handlers = {
'write': this.writeHandler.bind(this),
'read': this.readHandler.bind(this),
'showLiveLogs': this.showLiveLogsHandler.bind(this),
'rewriteContentWithProcessLogsOnDeploy': this.rewriteContentWithProcessLogsOnDeployHandler.bind(this)
};
constructor(private webSocketService: WebSocketService,
private processLogService: ProcessLogService) {}
ngOnInit() {
this.webSocketService.connect();
this.webSocketService.observeKey<ConsoleCommand>('console').subscribe(
(event) => this.command_handlers[event.data.command](event.data)
);
this.processLogService.newLogs.subscribe((data) => this.newLogsHandler(data));
}
writeHandler(data: ConsoleCommand) {
this.setContent(data.content);
}
readHandler(data: ConsoleCommand) {
this.sendContent(this.console_content);
}
newLogsHandler(logs: LogMessage) {
if (this.rewriteContentWithProcessLogsOnDeploy !== '') {
const log = logs[this.rewriteContentWithProcessLogsOnDeploy];
if (log) {
this.setContent(log);
}
}
}
showLiveLogsHandler(data: ConsoleCommand) {
this.processLogService.showLiveLogs = data.showLiveLogs;
}
rewriteContentWithProcessLogsOnDeployHandler(data: ConsoleCommand) {
this.rewriteContentWithProcessLogsOnDeploy = data.rewriteContentWithProcessLogsOnDeploy;
}
setContent(content: string) {
this.console_content = content;
}
sendContent(content: string) {
this.webSocketService.send('console', {
'command': 'read',
'content': content
});
}
}

View File

@ -4,9 +4,11 @@
<div [attr.class]="layout">
<div class="tfw-grid-main-components">
<div class="tfw-header"><app-header></app-header></div>
<div [ngClass]="{ 'hide-attribute': hide_messages }" class="tfw-messages"><app-messages></app-messages></div>
<div [ngClass]="{'hide-attribute': hide_messages}" class="tfw-messages">
<app-messages></app-messages>
</div>
<div class="tfw-web tao-grid-top-left"
[ngClass]="{ 'deploy-blur': deploying }">
[ngClass]="{'deploy-blur': deploying}">
<app-web *ngIf="!iframeUrl"></app-web>
<div *ngIf="iframeUrl" class="iframe-container">
<iframe class="iframe"
@ -18,10 +20,20 @@
</div>
</div>
<div class="tfw-ide">
<app-ide></app-ide>
<app-ide (newLogs)="setConsoleContentIfNoLiveLogs($event)"></app-ide>
</div>
<div class="tfw-terminal">
<app-terminal></app-terminal>
<div class="btn-group btn-group-sm flex-wrap tao-grid-center-left tfw-console-terminal-menu">
<button class="tfw-console-terminal-menu-button"
(click)="selectTerminalMenuItem('terminal')"
[class.selected]="selectedTerminalMenuItem === 'terminal'">TERMINAL</button>
<button class="tfw-console-terminal-menu-button"
(click)="selectTerminalMenuItem('console')"
[class.selected]="selectedTerminalMenuItem === 'console'">CONSOLE</button>
</div>
<hr>
<app-terminal [hidden]="selectedTerminalMenuItem !== 'terminal'"></app-terminal>
<app-console [hidden]="selectedTerminalMenuItem !== 'console'"></app-console>
</div>
<div class="tfw-sidebar">
<app-sidebar (layoutChanged)="setLayout($event)" [layout]="layout"></app-sidebar>

View File

@ -76,11 +76,34 @@
.tfw-terminal {
overflow-y: hidden;
background-color: $tao-gray-800;
padding-bottom: 2.2em;
div[class*="web"] & {
border-top: 1px solid $tao-plum-100;
}
}
hr {
display: block;
height: 1px;
border: 0;
border-top: 1px solid rgba(255, 255, 255, 0.15);
margin: 2px 0 7px 0;
padding: 0;
}
.tfw-console-terminal-menu-button {
font-size: $font-size-small;
background-color: transparent;
border: none;
color: gray;
cursor: pointer;
}
.selected {
color: white;
cursor: default;
}
}
.deploy-blur {

View File

@ -7,6 +7,8 @@ import { Subscription } from 'rxjs';
import { WebSocketService } from '../services/websocket.service';
import { LayoutCommand } from './layout-command';
import { config } from '../config';
import { ProcessLogService } from '../services/processlog.service';
import { LogMessage } from '../services/log.message';
@Component({
selector: 'app-dashboard',
@ -20,12 +22,17 @@ export class DashboardComponent implements OnInit, OnDestroy {
hide_messages: boolean = config.dashboard.hide_messages;
iframeUrl: string = config.dashboard.iframeUrl;
@ViewChild('webiframe') webiframe: ElementRef;
selectedTerminalMenuItem = config.dashboard.terminalOrConsole;
command_handlers = {'layout': this.layoutHandler.bind(this),
'hide_messages': this.hideMessagesHandler.bind(this),
'terminal_menu': this.terminalMenuSelectHandler.bind(this),
'reload_frontend': this.reloadFrontendHandlder.bind(this)};
constructor(private deploymentNotificationService: DeploymentNotificationService,
private webSocketService: WebSocketService,
private changeDetectorRef: ChangeDetectorRef) {}
private changeDetectorRef: ChangeDetectorRef,
private processLogService: ProcessLogService) {}
ngOnInit() {
this.webSocketService.connect();
@ -44,7 +51,7 @@ export class DashboardComponent implements OnInit, OnDestroy {
this.deploymentNotificationSubscription = this.deploymentNotificationService.deploying.subscribe(
(deploying) => {
this.deploying = deploying;
if (!deploying && config.ide.reloadIframeOnDeployButtonClick) {
if (!deploying && config.ide.reloadIframeOnDeploy) {
this.reloadIframe();
}
});
@ -56,9 +63,14 @@ export class DashboardComponent implements OnInit, OnDestroy {
} else {
console.log('Invalid ide layout "' + data.layout + '" received!');
}
if (data.hide_messages !== undefined) {
this.hide_messages = data.hide_messages;
}
}
hideMessagesHandler(data: LayoutCommand) {
this.hide_messages = data.hide_messages;
}
terminalMenuSelectHandler(data: LayoutCommand) {
this.selectTerminalMenuItem(data.terminal_menu_item);
}
reloadFrontendHandlder(data: LayoutCommand) {
@ -83,4 +95,18 @@ export class DashboardComponent implements OnInit, OnDestroy {
this.webiframe.nativeElement.contentWindow.location.reload(true);
});
}
selectTerminalMenuItem(item: string) {
if (!item.match('(terminal|console)')) {
return;
}
this.selectedTerminalMenuItem = item;
}
setConsoleContentIfNoLiveLogs(logs: LogMessage) {
this.processLogService.emitNewLogsIfNoLiveLogs(logs);
if (config.ide.showConsoleOnDeploy) {
this.selectTerminalMenuItem('console');
}
}
}

View File

@ -1,8 +1,9 @@
// Copyright (C) 2018 Avatao.com Innovative Learning Kft.
// All Rights Reserved. See LICENSE file for details.
export class LayoutCommand {
command: string;
layout: string;
hide_messages?: boolean;
export interface LayoutCommand {
command: string;
layout?: string;
hide_messages?: boolean;
terminal_menu_item?: string;
}

View File

@ -51,6 +51,7 @@
padding: 6px 19px;
img {
padding-right: 0.5em;
position: relative;
bottom: 1px;
height: $small;

View File

@ -1,7 +1,7 @@
// Copyright (C) 2018 Avatao.com Innovative Learning Kft.
// All Rights Reserved. See LICENSE file for details.
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { ChangeDetectorRef, Component, EventEmitter, OnInit, Output } from '@angular/core';
import * as brace from 'brace';
import 'brace/ext/modelist';
@ -62,6 +62,9 @@ export class IdeComponent implements OnInit {
language: string = config.ide.defaultLanguage;
theme = 'cobalt';
@Output() newLogs = new EventEmitter<any>();
options: any = {enableBasicAutocompletion: true,
enableSnippets: true,
enableLiveAutocompletion: true};
@ -96,7 +99,13 @@ export class IdeComponent implements OnInit {
this.processManagerService.init();
this.processManagerService.subscribeCallback(
config.ide.deployProcessName,
(event) => this.deploymentNotificationService.deploying.next(false)
(event) => {
this.deploymentNotificationService.deploying.next(false);
this.newLogs.emit({
stdout: event.data.stdout,
stderr: event.data.stderr
});
}
);
this.processManagerService.subscribeSuccessCallback(

View File

@ -0,0 +1,7 @@
// Copyright (C) 2018 Avatao.com Innovative Learning Kft.
// All Rights Reserved. See LICENSE file for details.
export interface LogMessage {
stdout: string;
stderr: string;
}

View File

@ -1,8 +1,10 @@
// Copyright (C) 2018 Avatao.com Innovative Learning Kft.
// All Rights Reserved. See LICENSE file for details.
export class ProcessCommand {
export interface ProcessCommand {
command: string;
process_name: string;
error?: string;
stdout: string;
stderr: string;
}

View File

@ -0,0 +1,8 @@
// Copyright (C) 2018 Avatao.com Innovative Learning Kft.
// All Rights Reserved. See LICENSE file for details.
export interface ProcessLogCommand {
command: string;
stdout: string;
stderr: string;
}

View File

@ -0,0 +1,41 @@
// Copyright (C) 2018 Avatao.com Innovative Learning Kft.
// All Rights Reserved. See LICENSE file for details.
import { Injectable } from '@angular/core';
import { WebSocketService } from './websocket.service';
import { ProcessLogCommand } from './processlog-command';
import { config } from '../config';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { LogMessage } from './log.message';
@Injectable()
export class ProcessLogService {
newLogs = new BehaviorSubject<LogMessage>(config.console.defaultLogs);
showLiveLogs = config.console.showLiveLogs;
command_handlers = {
'new_log': this.newLogsHandler.bind(this)
};
constructor(private webSocketService: WebSocketService) {
this.webSocketService.connect();
this.webSocketService.observeKey<ProcessLogCommand>('processlog').subscribe(
(event) => this.command_handlers[event.data.command](event.data)
);
}
emitNewLogsIfNoLiveLogs(logs: LogMessage) {
if (!this.showLiveLogs) {
this.newLogs.next(logs);
}
}
newLogsHandler(data: ProcessLogCommand) {
if (this.showLiveLogs) {
this.newLogs.next({
stdout: data.stdout,
stderr: data.stderr
});
}
}
}