Included method for synchronisation between multiple clients

Added clear and download button
Several CSS improvements
This commit is contained in:
Christoph Stahl 2017-05-22 11:16:32 +02:00
parent ac0d1acb90
commit cd6164ac3e
5 changed files with 167 additions and 67 deletions

View file

@ -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()

View file

@ -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 {

View file

@ -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") {

View file

@ -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>

View file

@ -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)