Add running display of stdout.log when executing upgrade
This commit is contained in:
parent
fbd73a48c4
commit
e5b0fe7198
59
tailbone/static/css/progress.css
Normal file
59
tailbone/static/css/progress.css
Normal 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;
|
||||
}
|
|
@ -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>
|
||||
|
|
77
tailbone/templates/upgrade.mako
Normal file
77
tailbone/templates/upgrade.mako
Normal 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()}
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue