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