Filter and colour output, handle exit properly
This commit is contained in:
@ -25,9 +25,11 @@ $(function() {
|
|||||||
numMessages = 0,
|
numMessages = 0,
|
||||||
turtlecanvas = $('#turtlecanvas'),
|
turtlecanvas = $('#turtlecanvas'),
|
||||||
prompt = $('#prompt'),
|
prompt = $('#prompt'),
|
||||||
commands = ['input', 'write', 'turtle', 'turtlebatch'],
|
commands = ['input', 'write', 'turtle', 'turtlebatch', 'exit'],
|
||||||
streams = ['stdin', 'stdout', 'stderr'];
|
streams = ['stdin', 'stdout', 'stderr'];
|
||||||
|
|
||||||
|
var ENTER_KEY_CODE = 13;
|
||||||
|
|
||||||
var flowrResultHtml = '<div class="panel panel-default"><div id="{{headingId}}" role="tab" class="panel-heading"><h4 class="panel-title"><a data-toggle="collapse" data-parent="#flowrHint" href="#{{collapseId}}" aria-expanded="true" aria-controls="{{collapseId}}"></a></h4></div><div id="{{collapseId}}" role="tabpanel" aria-labelledby="{{headingId}}" class="panel-collapse collapse"><div class="panel-body"></div></div></div>'
|
var flowrResultHtml = '<div class="panel panel-default"><div id="{{headingId}}" role="tab" class="panel-heading"><h4 class="panel-title"><a data-toggle="collapse" data-parent="#flowrHint" href="#{{collapseId}}" aria-expanded="true" aria-controls="{{collapseId}}"></a></h4></div><div id="{{collapseId}}" role="tabpanel" aria-labelledby="{{headingId}}" class="panel-collapse collapse"><div class="panel-body"></div></div></div>'
|
||||||
|
|
||||||
var ajax = function(options) {
|
var ajax = function(options) {
|
||||||
@ -190,6 +192,7 @@ $(function() {
|
|||||||
|
|
||||||
var evaluateCodeWithStreamedResponse = function(url, callback) {
|
var evaluateCodeWithStreamedResponse = function(url, callback) {
|
||||||
initWebsocketConnection(url);
|
initWebsocketConnection(url);
|
||||||
|
console.log(callback);
|
||||||
|
|
||||||
// TODO only init turtle when required
|
// TODO only init turtle when required
|
||||||
initTurtle();
|
initTurtle();
|
||||||
@ -645,11 +648,11 @@ $(function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var initializeWorkspaceButtons = function() {
|
var initializeWorkspaceButtons = function() {
|
||||||
$('#assess').on('click', scoreCode);
|
$('#assess').on('click', scoreCode); // todo
|
||||||
$('#dropdown-render, #render').on('click', renderCode);
|
$('#dropdown-render, #render').on('click', renderCode);
|
||||||
$('#dropdown-run, #run').on('click', runCode);
|
$('#dropdown-run, #run').on('click', runCode);
|
||||||
$('#dropdown-stop, #stop').on('click', stopCode);
|
$('#dropdown-stop, #stop').on('click', stopCode); // todo
|
||||||
$('#dropdown-test, #test').on('click', testCode);
|
$('#dropdown-test, #test').on('click', testCode); // todo
|
||||||
$('#save').on('click', saveCode);
|
$('#save').on('click', saveCode);
|
||||||
$('#start-over').on('click', confirmReset);
|
$('#start-over').on('click', confirmReset);
|
||||||
};
|
};
|
||||||
@ -690,6 +693,7 @@ $(function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var isBrowserSupported = function() {
|
var isBrowserSupported = function() {
|
||||||
|
// todo event streams are no longer required with websockets
|
||||||
return window.EventSource !== undefined;
|
return window.EventSource !== undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -717,12 +721,16 @@ $(function() {
|
|||||||
chunkBuffer.push(output);
|
chunkBuffer.push(output);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
resetOutputTab();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var resetOutputTab = function() {
|
||||||
clearOutput();
|
clearOutput();
|
||||||
$('#hint').fadeOut();
|
$('#hint').fadeOut();
|
||||||
$('#flowrHint').fadeOut();
|
$('#flowrHint').fadeOut();
|
||||||
showTab(2);
|
showTab(2);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
var printOutput = function(output, colorize, index) {
|
var printOutput = function(output, colorize, index) {
|
||||||
var element = findOrCreateOutputElement(index);
|
var element = findOrCreateOutputElement(index);
|
||||||
@ -1010,6 +1018,14 @@ $(function() {
|
|||||||
var stopCode = function(event) {
|
var stopCode = function(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if ($('#stop').is(':visible')) {
|
if ($('#stop').is(':visible')) {
|
||||||
|
killWebsocket();
|
||||||
|
stopContainer();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// todo we are missing the url here
|
||||||
|
// we could also hide the container completely by killing it on the server and only exposing the websocket
|
||||||
|
var stopContainer = function() {
|
||||||
var jqxhr = ajax({
|
var jqxhr = ajax({
|
||||||
data: {
|
data: {
|
||||||
container_id: $('#stop').data('container').id
|
container_id: $('#stop').data('container').id
|
||||||
@ -1023,8 +1039,22 @@ $(function() {
|
|||||||
});
|
});
|
||||||
jqxhr.fail(ajaxError);
|
jqxhr.fail(ajaxError);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
|
var killWebsocket = function() {
|
||||||
|
if (websocket.readyState != WebSocket.OPEN) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// todo flash notification
|
||||||
|
websocket.send(JSON.stringify({cmd: 'exit'}));
|
||||||
|
websocket.flush();
|
||||||
|
websocket.close();
|
||||||
|
// todo remove this once xhr works or container is killed on the server
|
||||||
|
hideSpinner();
|
||||||
|
running = false;
|
||||||
|
toggleButtonStates();
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo set this from websocket command, required to e.g. stop container
|
||||||
var storeContainerInformation = function(event) {
|
var storeContainerInformation = function(event) {
|
||||||
var container_information = JSON.parse(event.data);
|
var container_information = JSON.parse(event.data);
|
||||||
$('#stop').data('container', container_information);
|
$('#stop').data('container', container_information);
|
||||||
@ -1089,15 +1119,16 @@ $(function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var initTurtle = function() {
|
var initTurtle = function() {
|
||||||
turtlescreen = new Turtle(websocket, $('#turtlecanvas'));
|
// todo guard clause if turtle is not required for the current exercise
|
||||||
|
turtlescreen = new Turtle(websocket, turtlecanvas);
|
||||||
|
if ($('#run').isPresent()) {
|
||||||
|
$('#run').bind('click', hideCanvas);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var initCallbacks = function() {
|
var initPrompt = function() {
|
||||||
if ($('#run').isPresent()) {
|
if ($('#run').isPresent()) {
|
||||||
$('#run').bind('click', function(event) {
|
$('#run').bind('click', hidePrompt);
|
||||||
hideCanvas();
|
|
||||||
hidePrompt();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if ($('#prompt').isPresent()) {
|
if ($('#prompt').isPresent()) {
|
||||||
$('#prompt').on('keypress', handlePromptKeyPress);
|
$('#prompt').on('keypress', handlePromptKeyPress);
|
||||||
@ -1106,23 +1137,22 @@ $(function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var onWebSocketOpen = function(evt) {
|
var onWebSocketOpen = function(evt) {
|
||||||
//alert("Session started");
|
resetOutputTab();
|
||||||
};
|
};
|
||||||
|
|
||||||
var onWebSocketClose = function(evt) {
|
var onWebSocketClose = function(evt) {
|
||||||
//alert("Session terminated");
|
// no reason to alert since this will happen either way
|
||||||
};
|
};
|
||||||
|
|
||||||
var onWebSocketMessage = function(evt) {
|
var onWebSocketMessage = function(evt) {
|
||||||
numMessages++;
|
|
||||||
parseCanvasMessage(evt.data, true);
|
parseCanvasMessage(evt.data, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
var onWebSocketError = function(evt) {
|
var onWebSocketError = function(evt) {
|
||||||
//alert("Something went wrong.")
|
// todo flash error message
|
||||||
};
|
};
|
||||||
|
|
||||||
var executeCommand = function(msg) {
|
var executeWebsocketCommand = function(msg) {
|
||||||
if ($.inArray(msg.cmd, commands) == -1) {
|
if ($.inArray(msg.cmd, commands) == -1) {
|
||||||
console.log("Unknown command: " + msg.cmd);
|
console.log("Unknown command: " + msg.cmd);
|
||||||
// skipping unregistered commands is required
|
// skipping unregistered commands is required
|
||||||
@ -1144,26 +1174,16 @@ $(function() {
|
|||||||
showCanvas();
|
showCanvas();
|
||||||
handleTurtlebatchCommand(msg);
|
handleTurtlebatchCommand(msg);
|
||||||
break;
|
break;
|
||||||
|
case 'exit':
|
||||||
|
killWebsocket();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// todo reuse method from editor.js
|
|
||||||
var printWebsocketOutput = function(msg) {
|
var printWebsocketOutput = function(msg) {
|
||||||
var element = findOrCreateOutputElement(0);
|
var stream = {};
|
||||||
console.log(element);
|
stream[msg.stream] = msg.data;
|
||||||
switch (msg.stream) {
|
printOutput(stream, true, 0);
|
||||||
case 'internal':
|
|
||||||
element.addClass('text-danger');
|
|
||||||
break;
|
|
||||||
case 'stderr':
|
|
||||||
element.addClass('text-warning');
|
|
||||||
break;
|
|
||||||
case 'stdout':
|
|
||||||
case 'stdin': // for eventual prompts
|
|
||||||
default:
|
|
||||||
element.addClass('text-muted');
|
|
||||||
}
|
|
||||||
element.append(msg.data)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var handleTurtleCommand = function(msg) {
|
var handleTurtleCommand = function(msg) {
|
||||||
@ -1190,6 +1210,7 @@ $(function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var submitPromptInput = function() {
|
var submitPromptInput = function() {
|
||||||
|
// todo make sure websocket is actually open
|
||||||
var input = $('#prompt-input');
|
var input = $('#prompt-input');
|
||||||
var message = input.val();
|
var message = input.val();
|
||||||
websocket.send(JSON.stringify({cmd: 'result', 'data': message}));
|
websocket.send(JSON.stringify({cmd: 'result', 'data': message}));
|
||||||
@ -1219,7 +1240,7 @@ $(function() {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
executeCommand(msg);
|
executeWebsocketCommand(msg);
|
||||||
};
|
};
|
||||||
|
|
||||||
var showPrompt = function() {
|
var showPrompt = function() {
|
||||||
@ -1231,7 +1252,6 @@ $(function() {
|
|||||||
|
|
||||||
var hidePrompt = function() {
|
var hidePrompt = function() {
|
||||||
if (prompt.isPresent() && !prompt.hasClass('hidden')) {
|
if (prompt.isPresent() && !prompt.hasClass('hidden')) {
|
||||||
console.log("hiding prompt2");
|
|
||||||
prompt.addClass('hidden');
|
prompt.addClass('hidden');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1314,6 +1334,7 @@ $(function() {
|
|||||||
initializeEventHandlers();
|
initializeEventHandlers();
|
||||||
initializeFileTree();
|
initializeFileTree();
|
||||||
initializeTooltips();
|
initializeTooltips();
|
||||||
|
initPrompt();
|
||||||
renderScore();
|
renderScore();
|
||||||
showFirstFile();
|
showFirstFile();
|
||||||
showRequestedTab();
|
showRequestedTab();
|
||||||
|
@ -71,6 +71,7 @@ class SubmissionsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def run
|
def run
|
||||||
|
# TODO reimplement SSEs with websocket commands
|
||||||
# with_server_sent_events do |server_sent_event|
|
# with_server_sent_events do |server_sent_event|
|
||||||
# output = @docker_client.execute_run_command(@submission, params[:filename])
|
# output = @docker_client.execute_run_command(@submission, params[:filename])
|
||||||
|
|
||||||
@ -95,18 +96,37 @@ class SubmissionsController < ApplicationController
|
|||||||
socket = result[:socket]
|
socket = result[:socket]
|
||||||
|
|
||||||
socket.on :message do |event|
|
socket.on :message do |event|
|
||||||
puts "Docker sending: " + event.data
|
Rails.logger.info("Docker sending: " + event.data)
|
||||||
parse_message(event.data, 'stdout', tubesock)
|
handle_message(event.data, tubesock)
|
||||||
|
end
|
||||||
|
|
||||||
|
socket.on :close do |event|
|
||||||
|
kill_socket(tubesock)
|
||||||
end
|
end
|
||||||
|
|
||||||
tubesock.onmessage do |data|
|
tubesock.onmessage do |data|
|
||||||
puts "Client sending: " + data
|
Rails.logger.info("Client sending: " + data)
|
||||||
res = socket.send data
|
socket.send data
|
||||||
if res == false
|
|
||||||
puts "Something is wrong."
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def kill_socket(tubesock)
|
||||||
|
# Hijacked connection needs to be notified correctly
|
||||||
|
tubesock.send_data JSON.dump({'cmd' => 'exit'})
|
||||||
|
tubesock.close
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_message(message, tubesock)
|
||||||
|
# Handle special commands first
|
||||||
|
if (/^exit/.match(message))
|
||||||
|
kill_socket(tubesock)
|
||||||
|
else
|
||||||
|
# Filter out information about user and working directory
|
||||||
|
if !(/root|workspace/.match(message))
|
||||||
|
parse_message(message, 'stdout', tubesock)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse_message(message, output_stream, socket, recursive = true)
|
def parse_message(message, output_stream, socket, recursive = true)
|
||||||
@ -114,9 +134,7 @@ class SubmissionsController < ApplicationController
|
|||||||
parsed = JSON.parse(message)
|
parsed = JSON.parse(message)
|
||||||
socket.send_data message
|
socket.send_data message
|
||||||
rescue JSON::ParserError => e
|
rescue JSON::ParserError => e
|
||||||
print "1\n"
|
|
||||||
if ((recursive == true) && (message.include? "\n"))
|
if ((recursive == true) && (message.include? "\n"))
|
||||||
print "3\n"
|
|
||||||
for part in message.split("\n")
|
for part in message.split("\n")
|
||||||
self.parse_message(part,output_stream,socket,false)
|
self.parse_message(part,output_stream,socket,false)
|
||||||
end
|
end
|
||||||
|
Reference in New Issue
Block a user