diff --git a/README.md b/README.md index 81337bd..801fb63 100644 --- a/README.md +++ b/README.md @@ -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). +``` \ No newline at end of file diff --git a/src/app/config.ts b/src/app/config.ts index 56dbeb2..9afd139 100644 --- a/src/app/config.ts +++ b/src/app/config.ts @@ -19,7 +19,7 @@ export const config = { 'web-only' ], iframeUrl: '/webservice', - hide_messages: false + hideMessages: false }, ide: { route: 'ide', diff --git a/src/app/console/console-command.ts b/src/app/console/console-command.ts deleted file mode 100644 index c23e6a8..0000000 --- a/src/app/console/console-command.ts +++ /dev/null @@ -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; -} diff --git a/src/app/console/console.component.ts b/src/app/console/console.component.ts index 5c960bd..857406f 100644 --- a/src/app/console/console.component.ts +++ b/src/app/console/console.component.ts @@ -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('console').subscribe( + this.webSocketService.observeKey('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) { diff --git a/src/app/dashboard/dashboard.component.html b/src/app/dashboard/dashboard.component.html index 2473be4..77d23d3 100644 --- a/src/app/dashboard/dashboard.component.html +++ b/src/app/dashboard/dashboard.component.html @@ -4,7 +4,7 @@
-
+
('dashboard').subscribe((event) => { + this.webSocketService.observeKey('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); } diff --git a/src/app/dashboard/layout-command.ts b/src/app/dashboard/layout-command.ts deleted file mode 100644 index 18274fd..0000000 --- a/src/app/dashboard/layout-command.ts +++ /dev/null @@ -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; -} diff --git a/src/app/ide/ide.component.ts b/src/app/ide/ide.component.ts index 596def1..8aaa57e 100644 --- a/src/app/ide/ide.component.ts +++ b/src/app/ide/ide.component.ts @@ -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(this.key_id).subscribe((event) => { + this.webSocketService.observeKey(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); } diff --git a/src/app/messages/messagecontrol.ts b/src/app/message-types/command-message.ts similarity index 59% rename from src/app/messages/messagecontrol.ts rename to src/app/message-types/command-message.ts index 9f49cf0..9249ee4 100644 --- a/src/app/messages/messagecontrol.ts +++ b/src/app/message-types/command-message.ts @@ -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; } diff --git a/src/app/message-types/console-commands.ts b/src/app/message-types/console-commands.ts new file mode 100644 index 0000000..1b7de79 --- /dev/null +++ b/src/app/message-types/console-commands.ts @@ -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 {} + +export interface RewriteContentCommand extends CommandMessage, SetValueCommand {} diff --git a/src/app/message-types/dashboard-commands.ts b/src/app/message-types/dashboard-commands.ts new file mode 100644 index 0000000..6abbdb8 --- /dev/null +++ b/src/app/message-types/dashboard-commands.ts @@ -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 {} + +export interface HideMessagesCommand extends CommandMessage, SetValueCommand {} + +export interface TerminalMenuItemCommand extends CommandMessage, SetValueCommand {} diff --git a/src/app/services/fsmupdate.ts b/src/app/message-types/fsm-update.ts similarity index 100% rename from src/app/services/fsmupdate.ts rename to src/app/message-types/fsm-update.ts diff --git a/src/app/ide/source-code.ts b/src/app/message-types/ide-command.ts similarity index 64% rename from src/app/ide/source-code.ts rename to src/app/message-types/ide-command.ts index a860436..e72e2dd 100644 --- a/src/app/ide/source-code.ts +++ b/src/app/message-types/ide-command.ts @@ -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; } diff --git a/src/app/services/log.message.ts b/src/app/message-types/log-message.ts similarity index 100% rename from src/app/services/log.message.ts rename to src/app/message-types/log-message.ts diff --git a/src/app/message-types/messages-control-command.ts b/src/app/message-types/messages-control-command.ts new file mode 100644 index 0000000..cd53f52 --- /dev/null +++ b/src/app/message-types/messages-control-command.ts @@ -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 {} diff --git a/src/app/messages/message.ts b/src/app/message-types/messages-message.ts similarity index 84% rename from src/app/messages/message.ts rename to src/app/message-types/messages-message.ts index 0b58bad..d8b1d0a 100644 --- a/src/app/messages/message.ts +++ b/src/app/message-types/messages-message.ts @@ -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; diff --git a/src/app/message-types/process-command.ts b/src/app/message-types/process-command.ts new file mode 100644 index 0000000..aef7b3d --- /dev/null +++ b/src/app/message-types/process-command.ts @@ -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; +} diff --git a/src/app/message-types/process-log-command.ts b/src/app/message-types/process-log-command.ts new file mode 100644 index 0000000..3792c37 --- /dev/null +++ b/src/app/message-types/process-log-command.ts @@ -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 {} diff --git a/src/app/message-types/set-value-command.ts b/src/app/message-types/set-value-command.ts new file mode 100644 index 0000000..e64dee2 --- /dev/null +++ b/src/app/message-types/set-value-command.ts @@ -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 extends CommandMessage { + value: T; +} diff --git a/src/app/services/wsmessage.ts b/src/app/message-types/websocket-message.ts similarity index 82% rename from src/app/services/wsmessage.ts rename to src/app/message-types/websocket-message.ts index 690f72c..c0d3b52 100644 --- a/src/app/services/wsmessage.ts +++ b/src/app/message-types/websocket-message.ts @@ -1,7 +1,7 @@ // Copyright (C) 2018 Avatao.com Innovative Learning Kft. // All Rights Reserved. See LICENSE file for details. -export class WSMessage { +export class WebSocketMessage { key: string; trigger?: string; data: T; diff --git a/src/app/messages/messages.component.ts b/src/app/messages/messages.component.ts index 9960bfc..bf971bf 100644 --- a/src/app/messages/messages.component.ts +++ b/src/app/messages/messages.component.ts @@ -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').subscribe( + this.websocketService.observeKey('message').subscribe( (event) => { this.messages.push(event.data); event.data.message = this.convert(event.data.message); this.changeDetectorRef.detectChanges(); }); - this.websocketService.observeKey('messagecontrol').subscribe( + this.websocketService.observeKey('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'}); } diff --git a/src/app/services/fsmupdate.service.ts b/src/app/services/fsmupdate.service.ts index 85cf01a..b05d503 100644 --- a/src/app/services/fsmupdate.service.ts +++ b/src/app/services/fsmupdate.service.ts @@ -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 { diff --git a/src/app/services/processcommand.ts b/src/app/services/processcommand.ts deleted file mode 100644 index 8952530..0000000 --- a/src/app/services/processcommand.ts +++ /dev/null @@ -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; -} diff --git a/src/app/services/processlog-command.ts b/src/app/services/processlog-command.ts deleted file mode 100644 index 1ed732e..0000000 --- a/src/app/services/processlog-command.ts +++ /dev/null @@ -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; -} diff --git a/src/app/services/processlog.service.ts b/src/app/services/processlog.service.ts index 0feda37..afb7f53 100644 --- a/src/app/services/processlog.service.ts +++ b/src/app/services/processlog.service.ts @@ -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('processlog').subscribe( + this.webSocketService.observeKey('processlog').subscribe( (event) => this.command_handlers[event.data.command](event.data) ); } diff --git a/src/app/services/processmanager.service.ts b/src/app/services/processmanager.service.ts index 4fc96b3..67c2cea 100644 --- a/src/app/services/processmanager.service.ts +++ b/src/app/services/processmanager.service.ts @@ -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) => void) { + subscribeCallback(process_name: string, callback: (event: WebSocketMessage) => void) { this.observeProcessMessage(process_name).subscribe(callback); } - subscribeSuccessCallback(process_name: string, callback: (event: WSMessage) => void) { + subscribeSuccessCallback(process_name: string, callback: (event: WebSocketMessage) => void) { this.observeProcessMessage(process_name).pipe(filter(message => !('error' in message.data))).subscribe(callback); } - subscribeErrorCallback(process_name: string, callback: (event: WSMessage) => void) { + subscribeErrorCallback(process_name: string, callback: (event: WebSocketMessage) => void) { this.observeProcessMessage(process_name).pipe(filter(message => 'error' in message.data)).subscribe(callback); } diff --git a/src/app/services/websocket.service.ts b/src/app/services/websocket.service.ts index 435b648..e473d94 100644 --- a/src/app/services/websocket.service.ts +++ b/src/app/services/websocket.service.ts @@ -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, protocols?: string | string[]) { @@ -19,7 +19,7 @@ function jsonWebsocketConnect(url: string, input: Observable, protocols? @Injectable() export class WebSocketService { private uplink: QueueingSubject; - public downlink: Observable>; + public downlink: Observable>; constructor() {} @@ -33,13 +33,13 @@ export class WebSocketService { wsproto + window.location.host + '/ws', this.uplink = new QueueingSubject() ).messages.pipe( - map(message => > message), + map(message => > message), share() ); console.log('ws connected'); } - public observeKey(key: string): Observable> { + public observeKey(key: string): Observable> { return this.downlink.pipe(filter(message => message.key === key)); }