Add running display of stdout.log when executing upgrade

This commit is contained in:
Lance Edgar 2017-08-09 11:44:31 -05:00
parent fbd73a48c4
commit e5b0fe7198
6 changed files with 243 additions and 92 deletions

View file

@ -0,0 +1,59 @@
/********************************************************************************
* progress.css
*
* Styles for progress bar page.
********************************************************************************/
/******************************
* general
******************************/
#body-wrapper {
position: relative;
}
#wrapper {
height: 60px;
left: 50%;
margin-top: -45px;
margin-left: -350px;
position: absolute;
top: 50%;
width: 700px;
}
/******************************
* progress bar
******************************/
#progress-wrapper {
border-collapse: collapse;
}
#progress {
border-collapse: collapse;
height: 25px;
width: 550px;
}
#complete {
background-color: Gray;
width: 0px;
}
#remaining {
background-color: LightGray;
width: 100%;
}
#percentage {
padding-left: 3px;
min-width: 50px;
width: 50px;
}
#cancel .ui-button-text {
white-space: nowrap;
}

View file

@ -5,98 +5,19 @@
<html style="direction: ltr;" xmlns="http://www.w3.org/1999/xhtml" lang="en-us">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title>Working...</title>
<title>${initial_msg or "Working"}...</title>
${core_javascript()}
${h.stylesheet_link(request.static_url('tailbone:static/css/normalize.css'))}
${jquery_theme()}
${h.stylesheet_link(request.static_url('tailbone:static/css/base.css'))}
${h.stylesheet_link(request.static_url('tailbone:static/css/layout.css'))}
<style type="text/css">
#body-wrapper {
position: relative;
}
#wrapper {
height: 60px;
left: 50%;
margin-top: -45px;
margin-left: -350px;
position: absolute;
top: 50%;
width: 700px;
}
#progress-wrapper {
border-collapse: collapse;
}
#progress {
border-collapse: collapse;
height: 25px;
width: 550px;
}
#complete {
background-color: Gray;
width: 0px;
}
#remaining {
background-color: LightGray;
width: 100%;
}
#percentage {
padding-left: 3px;
min-width: 50px;
width: 50px;
}
#cancel .ui-button-text {
white-space: nowrap;
}
</style>
<script language="javascript" type="text/javascript">
${h.stylesheet_link(request.static_url('tailbone:static/css/progress.css'))}
${self.update_progress_func()}
${self.extra_styles()}
<script type="text/javascript">
var updater = null;
function update_progress() {
$.ajax({
url: '${url('progress', key=progress.key)}?sessiontype=${progress.session.type}',
success: function(data) {
if (data.error) {
location.href = '${cancel_url}';
} else if (data.complete || data.maximum) {
$('#message').html(data.message);
$('#total').html('('+data.maximum_display+' total)');
$('#cancel button').show();
if (data.complete) {
clearInterval(updater);
$('#cancel button').hide();
$('#total').html('done!');
$('#complete').css('width', '100%');
$('#remaining').hide();
$('#percentage').html('100 %');
location.href = data.success_url;
} else {
var width = parseInt(data.value) / parseInt(data.maximum);
width = Math.round(100 * width);
if (width) {
$('#complete').css('width', width+'%');
$('#percentage').html(width+' %');
} else {
$('#complete').css('width', '0.01%');
$('#percentage').html('0 %');
}
$('#remaining').css('width', 'auto');
}
}
},
});
}
updater = setInterval(function() {update_progress()}, 1000);
$(function() {
@ -147,6 +68,52 @@
</div><!-- #wrapper -->
${self.after_progress()}
</div><!-- #body-wrapper -->
</body>
</html>
<%def name="update_progress_func()">
<script type="text/javascript">
function update_progress() {
$.ajax({
url: '${url('progress', key=progress.key)}?sessiontype=${progress.session.type}',
success: function(data) {
if (data.error) {
location.href = '${cancel_url}';
} else if (data.complete || data.maximum) {
$('#message').html(data.message);
$('#total').html('('+data.maximum_display+' total)');
$('#cancel button').show();
if (data.complete) {
clearInterval(updater);
$('#cancel button').hide();
$('#total').html('done!');
$('#complete').css('width', '100%');
$('#remaining').hide();
$('#percentage').html('100 %');
location.href = data.success_url;
} else {
var width = parseInt(data.value) / parseInt(data.maximum);
width = Math.round(100 * width);
if (width) {
$('#complete').css('width', width+'%');
$('#percentage').html(width+' %');
} else {
$('#complete').css('width', '0.01%');
$('#percentage').html('0 %');
}
$('#remaining').css('width', 'auto');
}
}
},
});
}
</script>
</%def>
<%def name="extra_styles()"></%def>
<%def name="after_progress()"></%def>

View file

@ -0,0 +1,77 @@
## -*- coding: utf-8; -*-
<%inherit file="/progress.mako" />
<%def name="update_progress_func()">
<script type="text/javascript">
function update_progress() {
$.ajax({
url: '${url('upgrades.execute_progress', uuid=instance.uuid)}',
success: function(data) {
if (data.error) {
location.href = '${cancel_url}';
} else {
var stdout = $('.stdout');
var height = $(window).height() - stdout.offset().top - 50;
stdout.height(height);
stdout.append(data.stdout);
stdout.animate({scrollTop: stdout.get(0).scrollHeight - height}, 250);
if (data.complete || data.maximum) {
$('#message').html(data.message);
$('#total').html('('+data.maximum_display+' total)');
$('#cancel button').show();
if (data.complete) {
clearInterval(updater);
$('#cancel button').hide();
$('#total').html('done!');
$('#complete').css('width', '100%');
$('#remaining').hide();
$('#percentage').html('100 %');
location.href = data.success_url;
} else {
var width = parseInt(data.value) / parseInt(data.maximum);
width = Math.round(100 * width);
if (width) {
$('#complete').css('width', width+'%');
$('#percentage').html(width+' %');
} else {
$('#complete').css('width', '0.01%');
$('#percentage').html('0 %');
}
$('#remaining').css('width', 'auto');
}
}
}
}
});
}
</script>
</%def>
<%def name="extra_styles()">
${parent.extra_styles()}
<style type="text/css">
#wrapper {
top: 6em;
}
.stdout {
border: 1px solid Black;
height: 500px;
margin-left: 4.5%;
overflow: auto;
padding: 4px;
position: absolute;
top: 10em;
white-space: nowrap;
width: 90%;
}
</style>
</%def>
<%def name="after_progress()">
<div class="stdout"></div>
</%def>
${parent.body()}

View file

@ -90,12 +90,15 @@ class View(object):
def progress_loop(self, func, items, factory, *args, **kwargs):
return progress_loop(func, items, factory, *args, **kwargs)
def render_progress(self, progress, kwargs):
# TODO: this signature seems wonky
def render_progress(self, progress, kwargs, template=None):
"""
Render the progress page, with given kwargs as context.
"""
if not template:
template = '/progress.mako'
kwargs['progress'] = progress
return render_to_response('/progress.mako', kwargs, request=self.request)
return render_to_response(template, kwargs, request=self.request)
def file_response(self, path):
"""

View file

@ -71,8 +71,10 @@ class MasterView(View):
bulk_deletable = False
mergeable = False
downloadable = False
executable = False
cloneable = False
executable = False
execute_progress_template = None
execute_progress_initial_msg = None
supports_mobile = False
mobile_creatable = False
@ -841,20 +843,22 @@ class MasterView(View):
model_title = self.get_model_title()
if self.request.method == 'POST':
progress = self.make_execute_progress()
progress = self.make_execute_progress(obj)
kwargs = {'progress': progress}
thread = Thread(target=self.execute_thread, args=(obj.uuid, self.request.user.uuid), kwargs=kwargs)
thread.start()
return self.render_progress(progress, {
'instance': obj,
'initial_msg': self.execute_progress_initial_msg,
'cancel_url': self.get_action_url('view', obj),
'cancel_msg': "{} execution was canceled".format(model_title),
})
}, template=self.execute_progress_template)
self.request.session.flash("Sorry, you must POST to execute a {}.".format(model_title), 'error')
return self.redirect(self.get_action_url('view', obj))
def make_execute_progress(self):
def make_execute_progress(self, obj):
key = '{}.execute'.format(self.get_grid_key())
return SessionProgress(self.request, key)

View file

@ -43,7 +43,7 @@ from deform import widget as dfwidget
from webhelpers2.html import tags
from tailbone.views import MasterView3 as MasterView
from tailbone.progress import SessionProgress
from tailbone.progress import SessionProgress, get_progress_session
class UpgradeView(MasterView):
@ -51,9 +51,12 @@ class UpgradeView(MasterView):
Master view for all user events
"""
model_class = model.Upgrade
executable = True
downloadable = True
cloneable = True
executable = True
execute_progress_template = '/upgrade.mako'
execute_progress_initial_msg = "Upgrading"
labels = {
'executed_by': "Executed by",
'status_code': "Status",
@ -281,6 +284,44 @@ class UpgradeView(MasterView):
upgrade.executed = make_utc()
upgrade.executed_by = user
def execute_progress(self):
upgrade = self.get_instance()
key = '{}.execute'.format(self.get_grid_key())
session = get_progress_session(self.request, key)
if session.get('complete'):
msg = session.get('success_msg')
if msg:
self.request.session.flash(msg)
elif session.get('error'):
self.request.session.flash(session.get('error_msg', "An unspecified error occurred."), 'error')
data = dict(session)
path = self.rattail_config.upgrade_filepath(upgrade.uuid, filename='stdout.log')
offset = session.get('stdout.offset', 0)
size = os.path.getsize(path) - offset
with open(path, 'rb') as f:
f.seek(offset)
chunk = f.read(size)
data['stdout'] = chunk.replace('\n', '<br />')
session['stdout.offset'] = offset + size
session.save()
return data
@classmethod
def defaults(cls, config):
route_prefix = cls.get_route_prefix()
url_prefix = cls.get_url_prefix()
permission_prefix = cls.get_permission_prefix()
model_key = cls.get_model_key()
# execution progress
config.add_route('{}.execute_progress'.format(route_prefix), '{}/{{{}}}/execute/progress'.format(url_prefix, model_key))
config.add_view(cls, attr='execute_progress', route_name='{}.execute_progress'.format(route_prefix),
permission='{}.execute'.format(permission_prefix), renderer='json')
cls._defaults(config)
def includeme(config):
UpgradeView.defaults(config)