Included method for synchronisation between multiple clients
Added clear and download button Several CSS improvements
This commit is contained in:
parent
ac0d1acb90
commit
cd6164ac3e
5 changed files with 167 additions and 67 deletions
51
warp/hook.py
51
warp/hook.py
|
@ -1,9 +1,11 @@
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
from threading import Thread, Event
|
from threading import Thread, Event, Lock
|
||||||
from multiprocessing import Queue
|
from multiprocessing import Queue
|
||||||
|
import queue
|
||||||
from os import fdopen, path
|
from os import fdopen, path
|
||||||
|
|
||||||
from . import argparser_wrapper
|
from . import argparser_wrapper
|
||||||
|
@ -41,19 +43,39 @@ class FlaskThread(Thread):
|
||||||
def run(self):
|
def run(self):
|
||||||
views.app.run(port=self.port, threaded=True, host=self.host)
|
views.app.run(port=self.port, threaded=True, host=self.host)
|
||||||
|
|
||||||
class Output():
|
class OutputThread(Thread):
|
||||||
def __init__(self, queue, restart):
|
def __init__(self, inqueue, restart):
|
||||||
self._queue = queue
|
super().__init__()
|
||||||
self._restart = restart
|
self.queue = inqueue
|
||||||
|
self.restart = restart
|
||||||
self.cache = []
|
self.cache = []
|
||||||
|
self.clients = []
|
||||||
|
self.sem = Lock()
|
||||||
|
self.stopped = False
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.stopped = True
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while not self.restart.is_set() or self.stopped:
|
||||||
|
item = self.queue.get()
|
||||||
|
self.cache.append(item)
|
||||||
|
self.sem.acquire()
|
||||||
|
for i, (outqueue, active) in enumerate(self.clients):
|
||||||
|
try:
|
||||||
|
outqueue.put(item, True, 1)
|
||||||
|
except queue.Full:
|
||||||
|
self.clients[i] = self.clients[i][0], False
|
||||||
|
self.sem.release()
|
||||||
|
|
||||||
|
self.clients = [client for client in self.clients if client[1] == True]
|
||||||
|
|
||||||
|
def add_client(self):
|
||||||
|
print("new connection. Current connections: %s" % len(self.clients))
|
||||||
|
new_queue = queue.Queue(10)
|
||||||
|
self.clients.append((new_queue, True))
|
||||||
|
return new_queue
|
||||||
|
|
||||||
def get_output(self):
|
|
||||||
def gen_output():
|
|
||||||
while not self._restart.is_set():
|
|
||||||
msg_type, line = self._queue.get()
|
|
||||||
self.cache.append((msg_type, line))
|
|
||||||
yield (msg_type, line)
|
|
||||||
return gen_output()
|
|
||||||
|
|
||||||
def start_module(name, is_module):
|
def start_module(name, is_module):
|
||||||
views.app.restart.clear()
|
views.app.restart.clear()
|
||||||
|
@ -63,7 +85,8 @@ def start_module(name, is_module):
|
||||||
views.app.queue = Queue()
|
views.app.queue = Queue()
|
||||||
ioout = QueuedOut("out", views.app.queue)
|
ioout = QueuedOut("out", views.app.queue)
|
||||||
ioerr = QueuedOut("err", views.app.queue)
|
ioerr = QueuedOut("err", views.app.queue)
|
||||||
views.app.output = Output(views.app.queue, views.app.restart)
|
views.app.output = OutputThread(views.app.queue, views.app.restart)
|
||||||
|
#Output(views.app.queue, views.app.restart)
|
||||||
|
|
||||||
views.app.actionQueue = Queue() # This holds only one Argparser Object
|
views.app.actionQueue = Queue() # This holds only one Argparser Object
|
||||||
views.app.namespaceQueue = Queue() # This hold only one Namespace Object
|
views.app.namespaceQueue = Queue() # This hold only one Namespace Object
|
||||||
|
@ -79,11 +102,13 @@ def start_module(name, is_module):
|
||||||
is_module = is_module
|
is_module = is_module
|
||||||
)
|
)
|
||||||
views.app.module_process.start()
|
views.app.module_process.start()
|
||||||
|
views.app.output.start()
|
||||||
views.app.mutex_groups, views.app.actions, name, views.app.desc = views.app.actionQueue.get()
|
views.app.mutex_groups, views.app.actions, name, views.app.desc = views.app.actionQueue.get()
|
||||||
if name:
|
if name:
|
||||||
views.app.name = name
|
views.app.name = name
|
||||||
|
|
||||||
views.app.module_process.join()
|
views.app.module_process.join()
|
||||||
|
views.app.output.stop()
|
||||||
|
|
||||||
ioerr.write("Process stopped ({})\n".format(views.app.module_process.exitcode))
|
ioerr.write("Process stopped ({})\n".format(views.app.module_process.exitcode))
|
||||||
views.app.restart.wait()
|
views.app.restart.wait()
|
||||||
|
|
|
@ -1,12 +1,27 @@
|
||||||
|
.page {
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main-content {
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
#output {
|
#output {
|
||||||
position: relative;
|
flex:1;
|
||||||
background-color: #000000;
|
background-color: #000000;
|
||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-family: "Lucida Console", Monaco, monospace;
|
font-family: "Lucida Console", Monaco, monospace;
|
||||||
white-space:pre;
|
white-space:pre;
|
||||||
overflow-y: auto;
|
overflow-y: scroll;
|
||||||
height: 100vh;
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width:40em) {
|
||||||
|
#main-content {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
|
@ -25,6 +40,7 @@ li.subparser {
|
||||||
|
|
||||||
#arguments {
|
#arguments {
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
|
overflow: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs-panel {
|
.tabs-panel {
|
||||||
|
|
|
@ -228,6 +228,8 @@ window.onload = function() {
|
||||||
oboe('/output.json').node('output.*', function(e){
|
oboe('/output.json').node('output.*', function(e){
|
||||||
if(e.type === "err") {
|
if(e.type === "err") {
|
||||||
printErr(e.line);
|
printErr(e.line);
|
||||||
|
} else if (e.type === "sig") {
|
||||||
|
signal(e.line)
|
||||||
} else {
|
} else {
|
||||||
printOut(e.line);
|
printOut(e.line);
|
||||||
}
|
}
|
||||||
|
@ -245,52 +247,78 @@ function printErr(data){
|
||||||
$("#output").scrollTop($("#output")[0].scrollHeight);
|
$("#output").scrollTop($("#output")[0].scrollHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function signal(type) {
|
||||||
|
console.log(type);
|
||||||
|
if(type === "pause") {
|
||||||
|
pauseCallback();
|
||||||
|
} else if(type === "resume") {
|
||||||
|
resumeCallback();
|
||||||
|
} else if(type === "reload") {
|
||||||
|
reloadCallback();
|
||||||
|
} else if(type === "stop") {
|
||||||
|
stopCallback();
|
||||||
|
} else if(type === "start") {
|
||||||
|
startCallback();
|
||||||
|
} else if(type === "clear") {
|
||||||
|
clearCallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function resumeProcess() {
|
function resumeProcess() {
|
||||||
|
$.get({url: '/resume'});
|
||||||
|
}
|
||||||
|
|
||||||
|
function resumeCallback() {
|
||||||
$("#resumeButton").css('display', 'none');
|
$("#resumeButton").css('display', 'none');
|
||||||
//$("#sendButton").css('display', 'inline-block');
|
//$("#sendButton").css('display', 'inline-block');
|
||||||
$("#pauseButton").css('display', 'inline-block');//removeClass("disabled").prop('disabled', false);
|
$("#pauseButton").css('display', 'inline-block');//removeClass("disabled").prop('disabled', false);
|
||||||
$.get({url: '/resume'});
|
|
||||||
printErr("Process resumed")
|
printErr("Process resumed")
|
||||||
}
|
}
|
||||||
|
|
||||||
function pauseProcess() {
|
function pauseProcess() {
|
||||||
//$("#sendButton").css('display', 'none');
|
//$("#sendButton").css('display', 'none');
|
||||||
|
$.get({url: '/pause'});
|
||||||
|
}
|
||||||
|
|
||||||
|
function pauseCallback() {
|
||||||
$("#resumeButton").css('display', 'inline-block');
|
$("#resumeButton").css('display', 'inline-block');
|
||||||
$("#pauseButton").css('display', 'none');//addClass("disabled").prop('disabled', true);
|
$("#pauseButton").css('display', 'none');//addClass("disabled").prop('disabled', true);
|
||||||
$.get({url: '/pause'});
|
|
||||||
printErr("Process paused")
|
printErr("Process paused")
|
||||||
}
|
}
|
||||||
|
|
||||||
function reloadProcess() {
|
function reloadProcess() {
|
||||||
|
$.get({url: '/reload'});
|
||||||
|
}
|
||||||
|
|
||||||
|
function reloadCallback() {
|
||||||
$("#sendButton").removeClass("disabled").prop('disabled', false);
|
$("#sendButton").removeClass("disabled").prop('disabled', false);
|
||||||
$("#stopButton").addClass("disabled").prop('disabled', true);//css('display', 'inline-block');
|
$("#stopButton").addClass("disabled").prop('disabled', true);//css('display', 'inline-block');
|
||||||
$("#pauseButton").addClass("disabled").prop('disabled', true);//css('display', 'inline-block');
|
$("#pauseButton").addClass("disabled").prop('disabled', true);//css('display', 'inline-block');
|
||||||
$("#reloadButton").addClass("disabled").prop('disabled', true);//css('display', 'inline-block');
|
$("#reloadButton").addClass("disabled").prop('disabled', true);//css('display', 'inline-block');
|
||||||
$.get({url: '/reload',
|
oboe('/output.json').node('output.*', function(e){
|
||||||
success: function() {
|
if(e.type === "err") {
|
||||||
oboe('/output.json').node('output.*', function(e){
|
printErr(e.line);
|
||||||
if(e.type === "err") {
|
} else if (e.type === "sig") {
|
||||||
printErr(e.line);
|
signal(e.line)
|
||||||
} else {
|
} else {
|
||||||
printOut(e.line);
|
printOut(e.line);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopProcess() {
|
function stopProcess() {
|
||||||
|
$.get({url: '/stop'});
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopCallback() {
|
||||||
$("#sendButton").css('display', 'inline-block').prop('disabled', true).addClass('disabled');
|
$("#sendButton").css('display', 'inline-block').prop('disabled', true).addClass('disabled');
|
||||||
$("#pauseButton").css('display', 'none');
|
$("#pauseButton").css('display', 'none');
|
||||||
$("#resumeButton").css('display', 'none');
|
$("#resumeButton").css('display', 'none');
|
||||||
$("#stopButton").addClass("disabled").prop('disabled', true);//css('display', 'none');
|
$("#stopButton").addClass("disabled").prop('disabled', true);//css('display', 'none');
|
||||||
$("#reloadButton").removeClass("disabled").prop('disabled', false);//css('display', 'inline-block');
|
$("#reloadButton").removeClass("disabled").prop('disabled', false);//css('display', 'inline-block');
|
||||||
$.get({url: '/stop'});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendData() {
|
function sendData() {
|
||||||
$("#sendButton").css('display', 'none');//addClass("disabled").prop('disabled', true);
|
|
||||||
$("#stopButton").removeClass("disabled").prop('disabled', false);//css('display', 'inline-block');
|
|
||||||
$("#pauseButton").css('display', 'inline-block').removeClass("disabled").prop('disabled', false);//css('display', 'inline-block');
|
|
||||||
console.log(calcParams());
|
console.log(calcParams());
|
||||||
$.post({
|
$.post({
|
||||||
url: '/arguments',
|
url: '/arguments',
|
||||||
|
@ -299,6 +327,22 @@ function sendData() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearOutput() {
|
||||||
|
$.get({
|
||||||
|
url: 'clear'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearCallback() {
|
||||||
|
$("#output").empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
function startCallback() {
|
||||||
|
$("#sendButton").css('display', 'none');//addClass("disabled").prop('disabled', true);
|
||||||
|
$("#stopButton").removeClass("disabled").prop('disabled', false);//css('display', 'inline-block');
|
||||||
|
$("#pauseButton").css('display', 'inline-block').removeClass("disabled").prop('disabled', false);//css('display', 'inline-block');
|
||||||
|
}
|
||||||
|
|
||||||
function addParam(params, object) {
|
function addParam(params, object) {
|
||||||
params[$(object).data('name')] = [];
|
params[$(object).data('name')] = [];
|
||||||
if($(object).attr('type') === "checkbox") {
|
if($(object).attr('type') === "checkbox") {
|
||||||
|
|
|
@ -1,42 +1,45 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>{{ name }}</title>
|
<title>{{ name }}</title>
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/foundation.min.css') }}" />
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/foundation.min.css') }}" />
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='icons/foundation-icons.css') }}" />
|
<link rel="stylesheet" href="{{ url_for('static', filename='icons/foundation-icons.css') }}" />
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/app.css') }}" />
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/app.css') }}" />
|
||||||
<script src="{{ url_for('static', filename='js/vendor/what-input.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/vendor/what-input.js') }}"></script>
|
||||||
|
|
||||||
<script src="{{ url_for('static', filename='js/vendor/jquery.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/vendor/jquery.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/vendor/foundation.min.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/vendor/foundation.min.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/oboe-browser.min.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/oboe-browser.min.js') }}"></script>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class=page>
|
<div class=page>
|
||||||
|
<div id="main-content">
|
||||||
|
<div class="columns large-4 medium-6 small-12" id="arguments">
|
||||||
<div class="top-bar" id="header">
|
<div class="top-bar" id="header">
|
||||||
<div class="top-bar-title"><strong>{{ name }} - {{ description }}</strong></div>
|
<div class="top-bar-title"><strong>{{ name }} - {{ description }}</strong></div>
|
||||||
<div class="top-bar-right">
|
<div class="top-bar-right">
|
||||||
<div id="control-buttons">
|
<div id="control-buttons">
|
||||||
<div class="button-group">
|
<div class="button-group">
|
||||||
<button type="button" class="button success" id="sendButton" onclick="sendData()"> <i class="fi-play"></i> </button>
|
<button type="button" class="button success" id="sendButton" onclick="sendData()"> <i class="fi-play"></i> </button>
|
||||||
<button type="button" style="display: none;" class="button secondary disabled" disabled id="pauseButton" onclick="pauseProcess()"> <i class="fi-pause"></i> </button>
|
<button type="button" style="display: none;" class="button secondary disabled" disabled id="pauseButton" onclick="pauseProcess()"> <i class="fi-pause"></i> </button>
|
||||||
<button type="button" style="display: none;" class="button secondary" id="resumeButton" onclick="resumeProcess()"> <i class="fi-play"></i> </button>
|
<button type="button" style="display: none;" class="button secondary" id="resumeButton" onclick="resumeProcess()"> <i class="fi-play"></i> </button>
|
||||||
<button type="button" class="button alert disabled" disabled id="stopButton" onclick="stopProcess()"> <i class="fi-stop"></i> </button>
|
<button type="button" class="button alert disabled" disabled id="stopButton" onclick="stopProcess()"> <i class="fi-stop"></i> </button>
|
||||||
<button type="button" class="button disabled" disabled id="reloadButton" onclick="reloadProcess()"> <i class="fi-refresh"></i> </button>
|
<button type="button" class="button disabled" disabled id="reloadButton" onclick="reloadProcess()"> <i class="fi-refresh"></i> </button>
|
||||||
<button type="button" class="button" onclick="window.open('/download')"> <i class="fi-download"></i> </button>
|
<button type="button" class="button" onclick="window.open('/download')"> <i class="fi-download"></i> </button>
|
||||||
</div>
|
<button type="button" class="button alert" id="clear-button" onclick="clearOutput()"> <i class="fi-x"></i></button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="columns large-3 medium-5 small-12" id="arguments">
|
<ul id="actions" class="vertical menu">
|
||||||
<ul id="actions" class="vertical menu">
|
</ul>
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="columns large-9 medium-7 small-12" id="output_wrap">
|
|
||||||
<div id="output"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
|
<div class="columns large-8 medium-6 small-12" id="output_wrap">
|
||||||
</body>
|
<div id="output"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -43,6 +43,7 @@ def fill_namespace():
|
||||||
setattr(namespace, action.name, value)
|
setattr(namespace, action.name, value)
|
||||||
|
|
||||||
app.namespaceQueue.put(namespace)
|
app.namespaceQueue.put(namespace)
|
||||||
|
app.output.queue.put(("sig", "start"))
|
||||||
return "OK"
|
return "OK"
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,16 +57,24 @@ def get_arguments():
|
||||||
def stop():
|
def stop():
|
||||||
os.kill(app.module_process.pid, signal.SIGCONT)
|
os.kill(app.module_process.pid, signal.SIGCONT)
|
||||||
app.module_process.terminate()
|
app.module_process.terminate()
|
||||||
|
app.output.queue.put(("sig", "stop"))
|
||||||
return "OK"
|
return "OK"
|
||||||
|
|
||||||
@app.route("/resume")
|
@app.route("/resume")
|
||||||
def resume():
|
def resume():
|
||||||
os.kill(app.module_process.pid, signal.SIGCONT)
|
os.kill(app.module_process.pid, signal.SIGCONT)
|
||||||
|
app.output.queue.put(("sig", "resume"))
|
||||||
return "OK"
|
return "OK"
|
||||||
|
|
||||||
@app.route("/pause")
|
@app.route("/pause")
|
||||||
def pause():
|
def pause():
|
||||||
os.kill(app.module_process.pid, signal.SIGSTOP)
|
os.kill(app.module_process.pid, signal.SIGSTOP)
|
||||||
|
app.output.queue.put(("sig", "pause"))
|
||||||
|
return "OK"
|
||||||
|
|
||||||
|
@app.route('/clear')
|
||||||
|
def clear():
|
||||||
|
app.output.queue.put(("sig", "clear"));
|
||||||
return "OK"
|
return "OK"
|
||||||
|
|
||||||
@app.route("/reload")
|
@app.route("/reload")
|
||||||
|
@ -73,11 +82,12 @@ def reload():
|
||||||
if app.module_process.is_alive():
|
if app.module_process.is_alive():
|
||||||
return 403, "Process is still running"
|
return 403, "Process is still running"
|
||||||
app.restart.set()
|
app.restart.set()
|
||||||
|
app.output.queue.put(("sig", "reload"))
|
||||||
return "OK"
|
return "OK"
|
||||||
|
|
||||||
@app.route("/download", methods=['GET'])
|
@app.route("/download", methods=['GET'])
|
||||||
def download():
|
def download():
|
||||||
output = "\n".join([line for msg_type, line in app.output.cache])
|
output = "\n".join([line for msg_type, line in app.output.cache if msg_type != 'sig'])
|
||||||
response = make_response(output)
|
response = make_response(output)
|
||||||
response.headers["Content-Disposition"] = \
|
response.headers["Content-Disposition"] = \
|
||||||
"attachment; filename=%s_%s.log" \
|
"attachment; filename=%s_%s.log" \
|
||||||
|
@ -89,16 +99,20 @@ def download():
|
||||||
def output():
|
def output():
|
||||||
def generate():
|
def generate():
|
||||||
yield '{"output":['
|
yield '{"output":['
|
||||||
|
app.output.sem.acquire()
|
||||||
cache = app.output.cache
|
cache = app.output.cache
|
||||||
|
output = app.output.add_client()
|
||||||
|
app.output.sem.release()
|
||||||
for msg_type, line in cache:
|
for msg_type, line in cache:
|
||||||
yield json.dumps({'type' : msg_type, 'line': line})
|
yield json.dumps({'type': msg_type, 'line': line})
|
||||||
yield ','
|
yield ','
|
||||||
|
|
||||||
output = app.output.get_output()
|
while not app.restart.is_set():
|
||||||
for msg_type, line in output:
|
msg_type, line = output.get()
|
||||||
print("Send ({}): {} (length: {})".format(msg_type, line, len(line)), file=sys.__stdout__)
|
print("Send ({}): {} (length: {})".format(msg_type, line, len(line)), file=sys.__stdout__)
|
||||||
yield json.dumps({'type' : msg_type, 'line': line})
|
yield json.dumps({'type' : msg_type, 'line': line})
|
||||||
yield ','
|
yield ','
|
||||||
|
|
||||||
yield '\{\}]}'
|
yield '\{\}]}'
|
||||||
|
|
||||||
return Response(generate(), mimetype="application/json")
|
return Response(generate(), mimetype="application/json")
|
||||||
|
@ -107,7 +121,5 @@ def output():
|
||||||
|
|
||||||
@app.route("/", methods=['GET'])
|
@app.route("/", methods=['GET'])
|
||||||
def index():
|
def index():
|
||||||
if not app.actionQueue.empty():
|
|
||||||
app.mutex_groups, app.actions, app.name, app.desc = app.actionQueue.get()
|
|
||||||
return render_template("index.html", mutex_groups=app.mutex_groups, actions=app.actions, name=app.name, description=app.desc)
|
return render_template("index.html", mutex_groups=app.mutex_groups, actions=app.actions, name=app.name, description=app.desc)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue