Merge branch 'message-types'

This commit is contained in:
Kristóf Tóth 2018-06-04 22:18:50 +02:00
commit 995feeeddd
27 changed files with 171 additions and 116 deletions

View File

@ -21,10 +21,25 @@ These APIs are documented in the [baseimage-tutorial-framework](https://github.c
## Configuration
Most of the time it is not necessary to edit the source code of our components as you can easily customise their behaviour through the `src/app/config.ts` config file.
Generally it is unadvised to directly modify the source code of our pre-written components (this is hard for us to support and is prone to break).
The most notable setting available in this file is the enabling of different layouts, which allows the user and you to switch between them.
Layouts describe which components are visible and where they are on the screen.
For this reason most components are extensively configurable through the `src/app/config.ts` file.
These configurations range from the enabling of different layouts to how frequently should our IDE save automatically.
Many configuration options are changeable dynamically using API messages sent from the backend, like so:
```
{
"key": ...component name...,
"data":
{
"command": ...configuration key...,
"value": ...
}
}
```
Should you encounter any missing features, feel free to contact team TFW and we'll consider implementing them as configuration options (a common example would be making a configuration option dynamic).
### Terminal (webshell)
@ -41,6 +56,21 @@ You can write to it (and thus execute commands) and read what commands were exec
This enables you to pre-type or execute commands for the user and figure out what they are doing in the terminal.
### Console
Not unlike how a desktop IDE displays the output of your application, TFW provides a similar component as well.
The console can appear in place of the terminal and allows you to display the output of a supervisor process in real time.
This means that if you type `print('cats like cheese')` in your application code and run it, you will see `cats like cheese` appear on the console! Pretty neat, right?
You can control the displaying of process logs to the console using the `console.rewriteContentWithProcessLogsOnDeploy` key in `config.ts`.
The value of `stdout` or `stderr` will cause the console to display the respective stream, while an empty string will disable any automatic output to the console altogether.
We recommend redirecting `stdout` and `stderr` to the same file and displaying the together.
The `console.showLiveLogs` key enables real time output from the standard stream you've selected.
### IDE (webIde)
This component is a simple text editor based on ACE.
@ -120,10 +150,7 @@ The dashboard also exposes a frontend API to dynamically change layouts any time
"data":
{
"command": "layout",
"layout": ...,
"hide_messages": ...
"value": ...
}
}
```
You can use the `hide_messages` key to hide the message component (sadly it currently takes up the space it would occupy).
```

View File

@ -19,7 +19,7 @@ export const config = {
'web-only'
],
iframeUrl: '/webservice',
hide_messages: false
hideMessages: false
},
ide: {
route: 'ide',

View File

@ -1,9 +0,0 @@
// 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

@ -3,10 +3,11 @@
import { Component, OnInit } from '@angular/core';
import { WebSocketService } from '../services/websocket.service';
import { ConsoleCommand } from './console-command';
import { ConsoleContentCommand, RewriteContentCommand, ShowLiveLogsCommand } from '../message-types/console-commands';
import { config } from '../config';
import { ProcessLogService } from '../services/processlog.service';
import { LogMessage } from '../services/log.message';
import { LogMessage } from '../message-types/log-message';
import { CommandMessage } from '../message-types/command-message';
@Component({
selector: 'app-console',
@ -29,17 +30,17 @@ export class ConsoleComponent implements OnInit {
ngOnInit() {
this.webSocketService.connect();
this.webSocketService.observeKey<ConsoleCommand>('console').subscribe(
this.webSocketService.observeKey<CommandMessage>('console').subscribe(
(event) => this.command_handlers[event.data.command](event.data)
);
this.processLogService.newLogs.subscribe((data) => this.newLogsHandler(data));
}
writeHandler(data: ConsoleCommand) {
writeHandler(data: ConsoleContentCommand) {
this.setContent(data.content);
}
readHandler(data: ConsoleCommand) {
readHandler(data: ConsoleContentCommand) {
this.sendContent(this.console_content);
}
@ -52,12 +53,12 @@ export class ConsoleComponent implements OnInit {
}
}
showLiveLogsHandler(data: ConsoleCommand) {
this.processLogService.showLiveLogs = data.showLiveLogs;
showLiveLogsHandler(data: ShowLiveLogsCommand) {
this.processLogService.showLiveLogs = data.value;
}
rewriteContentWithProcessLogsOnDeployHandler(data: ConsoleCommand) {
this.rewriteContentWithProcessLogsOnDeploy = data.rewriteContentWithProcessLogsOnDeploy;
rewriteContentWithProcessLogsOnDeployHandler(data: RewriteContentCommand) {
this.rewriteContentWithProcessLogsOnDeploy = data.value;
}
setContent(content: string) {

View File

@ -4,7 +4,7 @@
<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">
<div [ngClass]="{'hide-attribute': hideMessages}" class="tfw-messages">
<app-messages></app-messages>
</div>
<div class="tfw-web tao-grid-top-left"

View File

@ -5,10 +5,11 @@ import { Component, OnDestroy, OnInit, ChangeDetectorRef, ElementRef, ViewChild
import { DeploymentNotificationService } from '../services/deployment-notification.service';
import { Subscription } from 'rxjs';
import { WebSocketService } from '../services/websocket.service';
import { LayoutCommand } from './layout-command';
import { HideMessagesCommand, LayoutCommand, TerminalMenuItemCommand } from '../message-types/dashboard-commands';
import { config } from '../config';
import { ProcessLogService } from '../services/processlog.service';
import { LogMessage } from '../services/log.message';
import { LogMessage } from '../message-types/log-message';
import { CommandMessage } from '../message-types/command-message';
@Component({
selector: 'app-dashboard',
@ -18,16 +19,17 @@ import { LogMessage } from '../services/log.message';
export class DashboardComponent implements OnInit, OnDestroy {
deploying = false;
deploymentNotificationSubscription: Subscription;
layout: string = config.dashboard.currentLayout;
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)};
layout: string = config.dashboard.currentLayout;
hideMessages: boolean = config.dashboard.hideMessages;
iframeUrl: string = config.dashboard.iframeUrl;
selectedTerminalMenuItem: string = config.dashboard.terminalOrConsole;
command_handlers = {'layout': this.layoutHandler.bind(this),
'hideMessages': this.hideMessagesHandler.bind(this),
'terminalMenuItem': this.terminalMenuItemHandler.bind(this),
'reloadFrontend': this.reloadFrontendHandlder.bind(this)};
constructor(private deploymentNotificationService: DeploymentNotificationService,
private webSocketService: WebSocketService,
@ -42,7 +44,7 @@ export class DashboardComponent implements OnInit, OnDestroy {
}
initCommandHandling() {
this.webSocketService.observeKey<LayoutCommand>('dashboard').subscribe((event) => {
this.webSocketService.observeKey<CommandMessage>('dashboard').subscribe((event) => {
this.command_handlers[event.data.command](event.data);
this.changeDetectorRef.detectChanges();
});
@ -70,22 +72,22 @@ export class DashboardComponent implements OnInit, OnDestroy {
}
layoutHandler(data: LayoutCommand) {
if (config.dashboard.enabledLayouts.includes(data.layout)) {
this.setLayout(data.layout);
if (config.dashboard.enabledLayouts.includes(data.value)) {
this.setLayout(data.value);
} else {
console.log('Invalid ide layout "' + data.layout + '" received!');
console.log('Invalid ide layout "' + data.value + '" received!');
}
}
hideMessagesHandler(data: LayoutCommand) {
this.hide_messages = data.hide_messages;
hideMessagesHandler(data: HideMessagesCommand) {
this.hideMessages = data.value;
}
terminalMenuSelectHandler(data: LayoutCommand) {
this.selectTerminalMenuItem(data.terminal_menu_item);
terminalMenuItemHandler(data: TerminalMenuItemCommand) {
this.selectTerminalMenuItem(data.value);
}
reloadFrontendHandlder(data: LayoutCommand) {
reloadFrontendHandlder(data: CommandMessage) {
setTimeout(() => window.location.reload(), 2000);
}

View File

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

View File

@ -16,11 +16,12 @@ import 'brace/mode/python';
import 'brace/mode/sql';
import 'brace/theme/cobalt';
import { SourceCode } from './source-code';
import { IDECommand } from '../message-types/ide-command';
import { WebSocketService } from '../services/websocket.service';
import { ProcessManagerService } from '../services/processmanager.service';
import { DeploymentNotificationService } from '../services/deployment-notification.service';
import { config } from '../config';
import { CommandMessage } from '../message-types/command-message';
const modelist = brace.acequire('ace/ext/modelist');
const langTools = brace.acequire('ace/ext/language_tools');
@ -89,7 +90,7 @@ export class IdeComponent implements OnInit {
}
subscribeWS() {
this.webSocketService.observeKey<SourceCode>(this.key_id).subscribe((event) => {
this.webSocketService.observeKey<CommandMessage>(this.key_id).subscribe((event) => {
this.command_handlers[event.data.command](event.data);
this.changeDetectorRef.detectChanges();
});
@ -119,7 +120,7 @@ export class IdeComponent implements OnInit {
);
}
updateFileData(data: SourceCode) {
updateFileData(data: IDECommand) {
this.filename = data.filename;
this.directory = data.directory;
this.code = (data.content != null) ? data.content : this.code;
@ -127,25 +128,25 @@ export class IdeComponent implements OnInit {
this.files = data.files;
}
selectHandler(data: SourceCode) {
selectHandler(data: IDECommand) {
this.updateFileData(data);
}
reloadHandler(data: SourceCode) {
reloadHandler(data: CommandMessage) {
this.requestCode();
}
readHandler(data: SourceCode) {
readHandler(data: IDECommand) {
if (this.codeState === CodeState.SAVED) {
this.updateFileData(data);
}
}
writeHandler() {
writeHandler(data: CommandMessage) {
this.setCodeState(CodeState.SAVED);
}
selectdirHandler(data: SourceCode) {
selectdirHandler(data: IDECommand) {
this.updateFileData(data);
}

View File

@ -1,7 +1,6 @@
// Copyright (C) 2018 Avatao.com Innovative Learning Kft.
// All Rights Reserved. See LICENSE file for details.
export class MessageControl {
command: string;
next_visibility?: boolean;
export interface CommandMessage {
readonly command: string;
}

View File

@ -0,0 +1,13 @@
// Copyright (C) 2018 Avatao.com Innovative Learning Kft.
// All Rights Reserved. See LICENSE file for details.
import { CommandMessage } from './command-message';
import { SetValueCommand } from './set-value-command';
export interface ConsoleContentCommand extends CommandMessage {
content?: string;
}
export interface ShowLiveLogsCommand extends CommandMessage, SetValueCommand<boolean> {}
export interface RewriteContentCommand extends CommandMessage, SetValueCommand<string> {}

View File

@ -0,0 +1,11 @@
// Copyright (C) 2018 Avatao.com Innovative Learning Kft.
// All Rights Reserved. See LICENSE file for details.
import { CommandMessage } from './command-message';
import { SetValueCommand } from './set-value-command';
export interface LayoutCommand extends CommandMessage, SetValueCommand<string> {}
export interface HideMessagesCommand extends CommandMessage, SetValueCommand<boolean> {}
export interface TerminalMenuItemCommand extends CommandMessage, SetValueCommand<string> {}

View File

@ -1,10 +1,11 @@
// Copyright (C) 2018 Avatao.com Innovative Learning Kft.
// All Rights Reserved. See LICENSE file for details.
export interface SourceCode {
import { CommandMessage } from './command-message';
export interface IDECommand extends CommandMessage {
filename: string;
content?: string;
files: string[];
directory: string;
command: string;
}

View File

@ -0,0 +1,7 @@
// Copyright (C) 2018 Avatao.com Innovative Learning Kft.
// All Rights Reserved. See LICENSE file for details.
import { CommandMessage } from './command-message';
import { SetValueCommand } from './set-value-command';
export interface MessagesControlCommand extends CommandMessage, SetValueCommand<boolean> {}

View File

@ -1,7 +1,7 @@
// Copyright (C) 2018 Avatao.com Innovative Learning Kft.
// All Rights Reserved. See LICENSE file for details.
export class Message {
export class MessagesMessage {
originator: string;
timestamp: Date;
message: string;

View File

@ -0,0 +1,10 @@
// Copyright (C) 2018 Avatao.com Innovative Learning Kft.
// All Rights Reserved. See LICENSE file for details.
import { CommandMessage } from './command-message';
import { LogMessage } from './log-message';
export interface ProcessCommand extends CommandMessage, LogMessage {
process_name: string;
error?: string;
}

View File

@ -0,0 +1,7 @@
// Copyright (C) 2018 Avatao.com Innovative Learning Kft.
// All Rights Reserved. See LICENSE file for details.
import { CommandMessage } from './command-message';
import { LogMessage } from './log-message';
export interface ProcessLogCommand extends CommandMessage, LogMessage {}

View File

@ -0,0 +1,8 @@
// Copyright (C) 2018 Avatao.com Innovative Learning Kft.
// All Rights Reserved. See LICENSE file for details.
import { CommandMessage } from './command-message';
export interface SetValueCommand<T> extends CommandMessage {
value: T;
}

View File

@ -1,7 +1,7 @@
// Copyright (C) 2018 Avatao.com Innovative Learning Kft.
// All Rights Reserved. See LICENSE file for details.
export class WSMessage<T> {
export class WebSocketMessage<T> {
key: string;
trigger?: string;
data: T;

View File

@ -5,9 +5,10 @@ import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { MarkdownService } from '../services/markdown.service';
import { WebSocketService } from '../services/websocket.service';
import { Message } from './message';
import { MessageControl } from './messagecontrol';
import { MessagesMessage } from '../message-types/messages-message';
import { MessagesControlCommand } from '../message-types/messages-control-command';
import { config } from '../config';
import { CommandMessage } from '../message-types/command-message';
@Component({
selector: 'app-messages',
@ -15,9 +16,11 @@ import { config } from '../config';
styleUrls: ['./messages.component.scss']
})
export class MessagesComponent implements OnInit {
messages: Message[] = [];
messages: MessagesMessage[] = [];
showNextButton: boolean = config.messages.showNextButton;
command_handlers = {'showbutton': this.showButton.bind(this)};
command_handlers = {
'showNextButton': this.showButtonHandler.bind(this)
};
constructor(
private markdownService: MarkdownService,
@ -25,28 +28,28 @@ export class MessagesComponent implements OnInit {
private changeDetectorRef: ChangeDetectorRef
) {}
convert(text: string) {
return this.markdownService.convertToHtml(text);
}
showButton(data: MessageControl) {
this.showNextButton = data.next_visibility;
}
ngOnInit() {
this.websocketService.connect();
this.websocketService.observeKey<Message>('message').subscribe(
this.websocketService.observeKey<MessagesMessage>('message').subscribe(
(event) => {
this.messages.push(event.data);
event.data.message = this.convert(event.data.message);
this.changeDetectorRef.detectChanges();
});
this.websocketService.observeKey<MessageControl>('messagecontrol').subscribe(
this.websocketService.observeKey<CommandMessage>('messagecontrol').subscribe(
(event) => {
this.command_handlers[event.data.command](event.data);
});
}
convert(text: string) {
return this.markdownService.convertToHtml(text);
}
showButtonHandler(data: MessagesControlCommand) {
this.showNextButton = data.value;
}
stepFSM() {
this.websocketService.sendJSON({key: '', trigger: 'step_next'});
}

View File

@ -4,7 +4,7 @@
import { Injectable } from '@angular/core';
import { WebSocketService } from './websocket.service';
import { FSMUpdate } from './fsmupdate';
import { FSMUpdate } from '../message-types/fsm-update';
@Injectable()
export class FSMUpdateService {

View File

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

View File

@ -1,8 +0,0 @@
// 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

@ -3,10 +3,11 @@
import { Injectable } from '@angular/core';
import { WebSocketService } from './websocket.service';
import { ProcessLogCommand } from './processlog-command';
import { ProcessLogCommand } from '../message-types/process-log-command';
import { config } from '../config';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { LogMessage } from './log.message';
import { LogMessage } from '../message-types/log-message';
import { CommandMessage } from '../message-types/command-message';
@Injectable()
export class ProcessLogService {
@ -19,7 +20,7 @@ export class ProcessLogService {
constructor(private webSocketService: WebSocketService) {
this.webSocketService.connect();
this.webSocketService.observeKey<ProcessLogCommand>('processlog').subscribe(
this.webSocketService.observeKey<CommandMessage>('processlog').subscribe(
(event) => this.command_handlers[event.data.command](event.data)
);
}

View File

@ -4,9 +4,9 @@
import { Injectable } from '@angular/core';
import { WebSocketService } from './websocket.service';
import { ProcessCommand } from './processcommand';
import { ProcessCommand } from '../message-types/process-command';
import { filter } from 'rxjs/operators';
import { WSMessage } from './wsmessage';
import { WebSocketMessage } from '../message-types/websocket-message';
@Injectable()
@ -20,15 +20,15 @@ export class ProcessManagerService {
this.webSocketService.connect();
}
subscribeCallback(process_name: string, callback: (event: WSMessage<ProcessCommand>) => void) {
subscribeCallback(process_name: string, callback: (event: WebSocketMessage<ProcessCommand>) => void) {
this.observeProcessMessage(process_name).subscribe(callback);
}
subscribeSuccessCallback(process_name: string, callback: (event: WSMessage<ProcessCommand>) => void) {
subscribeSuccessCallback(process_name: string, callback: (event: WebSocketMessage<ProcessCommand>) => void) {
this.observeProcessMessage(process_name).pipe(filter(message => !('error' in message.data))).subscribe(callback);
}
subscribeErrorCallback(process_name: string, callback: (event: WSMessage<ProcessCommand>) => void) {
subscribeErrorCallback(process_name: string, callback: (event: WebSocketMessage<ProcessCommand>) => void) {
this.observeProcessMessage(process_name).pipe(filter(message => 'error' in message.data)).subscribe(callback);
}

View File

@ -6,7 +6,7 @@ import { QueueingSubject } from './queueing-subject';
import { Observable } from 'rxjs';
import websocketConnect from 'rxjs-websockets';
import { filter, map, share } from 'rxjs/operators';
import { WSMessage } from './wsmessage';
import { WebSocketMessage } from '../message-types/websocket-message';
function jsonWebsocketConnect(url: string, input: Observable<object>, protocols?: string | string[]) {
@ -19,7 +19,7 @@ function jsonWebsocketConnect(url: string, input: Observable<object>, protocols?
@Injectable()
export class WebSocketService {
private uplink: QueueingSubject<object>;
public downlink: Observable<WSMessage<undefined>>;
public downlink: Observable<WebSocketMessage<undefined>>;
constructor() {}
@ -33,13 +33,13 @@ export class WebSocketService {
wsproto + window.location.host + '/ws',
this.uplink = new QueueingSubject<object>()
).messages.pipe(
map(message => <WSMessage<undefined>> message),
map(message => <WebSocketMessage<undefined>> message),
share()
);
console.log('ws connected');
}
public observeKey<T>(key: string): Observable<WSMessage<T>> {
public observeKey<T>(key: string): Observable<WebSocketMessage<T>> {
return this.downlink.pipe(filter(message => message.key === key));
}