Replace 'default' theme to match 'falafel'
falafel is now an empty wrapper around default hell yeah
This commit is contained in:
parent
f0880785a9
commit
320aaab4b3
File diff suppressed because it is too large
Load diff
|
@ -1,145 +1,219 @@
|
|||
## -*- coding: utf-8; -*-
|
||||
<%namespace file="tailbone:templates/base.mako" import="core_javascript" />
|
||||
<%namespace file="/base.mako" import="jquery_theme" />
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html style="direction: ltr;" xmlns="http://www.w3.org/1999/xhtml" lang="en-us">
|
||||
<%namespace file="/base.mako" import="core_javascript" />
|
||||
<%namespace file="/base.mako" import="core_styles" />
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
|
||||
<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'))}
|
||||
${h.stylesheet_link(request.static_url('tailbone:static/css/progress.css'))}
|
||||
${self.update_progress_func()}
|
||||
${core_styles()}
|
||||
${self.extra_styles()}
|
||||
<script type="text/javascript">
|
||||
|
||||
var stillInProgress = true;
|
||||
|
||||
// fetch first progress data, one second from now
|
||||
setTimeout(function() {
|
||||
update_progress();
|
||||
}, 1000);
|
||||
|
||||
% if can_cancel:
|
||||
$(function() {
|
||||
|
||||
$('#cancel button').click(function() {
|
||||
if (confirm("Do you really wish to cancel this operation?")) {
|
||||
stillInProgress = false;
|
||||
$(this).button('disable').button('option', 'label', "Canceling, please wait...");
|
||||
$.ajax({
|
||||
url: '${url('progress.cancel', key=progress.key)}?sessiontype=${progress.session.type}',
|
||||
data: {
|
||||
'cancel_msg': '${cancel_msg}',
|
||||
},
|
||||
success: function(data) {
|
||||
location.href = '${cancel_url}';
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
% endif
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="body-wrapper">
|
||||
<body style="height: 100%;">
|
||||
|
||||
<div id="wrapper">
|
||||
<div id="whole-page-app">
|
||||
<whole-page></whole-page>
|
||||
</div>
|
||||
|
||||
<p><span id="message">${initial_msg or "Working"} (please wait)</span> ... <span id="total"></span></p>
|
||||
<script type="text/x-template" id="whole-page-template">
|
||||
|
||||
<section class="hero is-fullheight">
|
||||
<div class="hero-body">
|
||||
<div class="container">
|
||||
|
||||
<div style="display: flex;">
|
||||
<div style="flex-grow: 1;"></div>
|
||||
<div>
|
||||
|
||||
<p class="block">
|
||||
{{ progressMessage }} ... {{ totalDisplay }}
|
||||
</p>
|
||||
|
||||
<div class="level">
|
||||
|
||||
<div class="level-item">
|
||||
<b-progress size="is-large"
|
||||
style="width: 400px;"
|
||||
:max="progressMax"
|
||||
:value="progressValue"
|
||||
show-value
|
||||
format="percent"
|
||||
precision="0">
|
||||
</b-progress>
|
||||
</div>
|
||||
|
||||
<table id="progress-wrapper">
|
||||
<tr>
|
||||
<td>
|
||||
<table id="progress">
|
||||
<tr>
|
||||
<td id="complete"></td>
|
||||
<td id="remaining"></td>
|
||||
</tr>
|
||||
</table><!-- #progress -->
|
||||
</td>
|
||||
<td id="percentage"></td>
|
||||
% if can_cancel:
|
||||
<td id="cancel">
|
||||
<button type="button" style="display: none;">Cancel</button>
|
||||
</td>
|
||||
<div class="level-item"
|
||||
style="margin-left: 2rem;">
|
||||
<b-button v-show="canCancel"
|
||||
@click="cancelProgress()"
|
||||
:disabled="cancelingProgress"
|
||||
icon-pack="fas"
|
||||
icon-left="ban">
|
||||
{{ cancelingProgress ? "Canceling, please wait..." : "Cancel" }}
|
||||
</b-button>
|
||||
</div>
|
||||
% endif
|
||||
</tr>
|
||||
</table><!-- #progress-wrapper -->
|
||||
|
||||
</div><!-- #wrapper -->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div style="flex-grow: 1;"></div>
|
||||
</div>
|
||||
|
||||
${self.after_progress()}
|
||||
|
||||
</div><!-- #body-wrapper -->
|
||||
</body>
|
||||
</html>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</script>
|
||||
|
||||
<%def name="update_progress_func()">
|
||||
<script type="text/javascript">
|
||||
|
||||
function update_progress() {
|
||||
$.ajax({
|
||||
url: '${url('progress', key=progress.key)}?sessiontype=${progress.session.type}',
|
||||
let WholePage = {
|
||||
template: '#whole-page-template',
|
||||
|
||||
success: function(data) {
|
||||
computed: {
|
||||
|
||||
if (data.error) {
|
||||
// errors stop the show, we redirect to "cancel" page
|
||||
location.href = '${cancel_url}';
|
||||
totalDisplay() {
|
||||
|
||||
} else {
|
||||
if (data.complete || data.maximum) {
|
||||
$('#message').html(data.message);
|
||||
$('#total').html('('+data.maximum_display+' total)');
|
||||
% if can_cancel:
|
||||
$('#cancel button').show();
|
||||
if (!this.stillInProgress && !this.cancelingProgress) {
|
||||
% else:
|
||||
if (!this.stillInProgress) {
|
||||
% endif
|
||||
if (data.complete) {
|
||||
stillInProgress = false;
|
||||
% if can_cancel:
|
||||
$('#cancel button').hide();
|
||||
% endif
|
||||
$('#total').html('done!');
|
||||
$('#complete').css('width', '100%');
|
||||
$('#remaining').hide();
|
||||
$('#percentage').html('100 %');
|
||||
location.href = data.success_url;
|
||||
|
||||
} else {
|
||||
// got progress data, so update display
|
||||
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');
|
||||
}
|
||||
return "done!"
|
||||
}
|
||||
|
||||
if (stillInProgress) {
|
||||
// fetch progress data again, in one second from now
|
||||
setTimeout(function() {
|
||||
update_progress();
|
||||
}, 1000);
|
||||
}
|
||||
if (this.progressMaxDisplay) {
|
||||
return `(${'$'}{this.progressMaxDisplay} total)`
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
mounted() {
|
||||
|
||||
// fetch first progress data, one second from now
|
||||
setTimeout(() => {
|
||||
this.updateProgress()
|
||||
}, 1000)
|
||||
|
||||
// custom logic if applicable
|
||||
this.mountedCustom()
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
mountedCustom() {},
|
||||
|
||||
updateProgress() {
|
||||
|
||||
this.$http.get(this.progressURL).then(response => {
|
||||
|
||||
if (response.data.error) {
|
||||
// errors stop the show, we redirect to "cancel" page
|
||||
location.href = '${cancel_url}'
|
||||
|
||||
} else {
|
||||
|
||||
if (response.data.complete || response.data.maximum) {
|
||||
this.progressMessage = response.data.message
|
||||
this.progressMaxDisplay = response.data.maximum_display
|
||||
|
||||
if (response.data.complete) {
|
||||
this.progressValue = this.progressMax
|
||||
this.stillInProgress = false
|
||||
% if can_cancel:
|
||||
this.canCancel = false
|
||||
% endif
|
||||
|
||||
location.href = response.data.success_url
|
||||
|
||||
} else {
|
||||
this.progressValue = response.data.value
|
||||
this.progressMax = response.data.maximum
|
||||
}
|
||||
}
|
||||
|
||||
// custom logic if applicable
|
||||
this.updateProgressCustom(response)
|
||||
|
||||
if (this.stillInProgress) {
|
||||
|
||||
// fetch progress data again, in one second from now
|
||||
setTimeout(() => {
|
||||
this.updateProgress()
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
updateProgressCustom(response) {},
|
||||
|
||||
% if can_cancel:
|
||||
|
||||
cancelProgress() {
|
||||
|
||||
if (confirm("Do you really wish to cancel this operation?")) {
|
||||
|
||||
this.cancelingProgress = true
|
||||
this.stillInProgress = false
|
||||
|
||||
let params = {cancel_msg: ${json.dumps(cancel_msg)|n}}
|
||||
this.$http.get(this.cancelURL, {params: params}).then(response => {
|
||||
location.href = ${json.dumps(cancel_url)|n}
|
||||
})
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
% endif
|
||||
}
|
||||
}
|
||||
|
||||
let WholePageData = {
|
||||
|
||||
progressURL: '${url('progress', key=progress.key, _query={'sessiontype': progress.session.type})}',
|
||||
progressMessage: "${(initial_msg or "Working").replace('"', '\\"')} (please wait)",
|
||||
progressMax: null,
|
||||
progressMaxDisplay: null,
|
||||
progressValue: null,
|
||||
stillInProgress: true,
|
||||
|
||||
% if can_cancel:
|
||||
canCancel: true,
|
||||
cancelURL: '${url('progress.cancel', key=progress.key, _query={'sessiontype': progress.session.type})}',
|
||||
cancelingProgress: false,
|
||||
% endif
|
||||
}
|
||||
|
||||
</script>
|
||||
</%def>
|
||||
|
||||
${self.modify_whole_page_vars()}
|
||||
${self.make_whole_page_app()}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<%def name="extra_styles()"></%def>
|
||||
|
||||
<%def name="after_progress()"></%def>
|
||||
|
||||
<%def name="modify_whole_page_vars()"></%def>
|
||||
|
||||
<%def name="make_whole_page_app()">
|
||||
<script type="text/javascript">
|
||||
|
||||
WholePage.data = function() { return WholePageData }
|
||||
|
||||
Vue.component('whole-page', WholePage)
|
||||
|
||||
new Vue({
|
||||
el: '#whole-page-app'
|
||||
})
|
||||
|
||||
</script>
|
||||
</%def>
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
## -*- coding: utf-8; -*-
|
||||
<%inherit file="tailbone:templates/base.mako" />
|
||||
|
||||
<%def name="jquery_theme()">
|
||||
${h.stylesheet_link('https://code.jquery.com/ui/1.11.4/themes/excite-bike/jquery-ui.css')}
|
||||
</%def>
|
||||
|
||||
${parent.body()}
|
|
@ -1,928 +1,4 @@
|
|||
## -*- coding: utf-8; -*-
|
||||
<%namespace file="/grids/nav.mako" import="grid_index_nav" />
|
||||
<%namespace file="/autocomplete.mako" import="tailbone_autocomplete_template" />
|
||||
<%namespace name="base_meta" file="/base_meta.mako" />
|
||||
<%namespace file="/formposter.mako" import="declare_formposter_mixin" />
|
||||
<%namespace name="page_help" file="/page_help.mako" />
|
||||
<%namespace name="multi_file_upload" file="/multi_file_upload.mako" />
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
|
||||
<title>${base_meta.global_title()} » ${capture(self.title)|n}</title>
|
||||
${base_meta.favicon()}
|
||||
${self.header_core()}
|
||||
<%inherit file="tailbone:templates/base.mako" />
|
||||
|
||||
% if background_color:
|
||||
<style type="text/css">
|
||||
body, .navbar, .footer {
|
||||
background-color: ${background_color};
|
||||
}
|
||||
</style>
|
||||
% endif
|
||||
|
||||
% if not request.rattail_config.production():
|
||||
<style type="text/css">
|
||||
body, .navbar, .footer {
|
||||
background-image: url(${request.static_url('tailbone:static/img/testing.png')});
|
||||
}
|
||||
</style>
|
||||
% endif
|
||||
|
||||
${self.head_tags()}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
${declare_formposter_mixin()}
|
||||
|
||||
${self.body()}
|
||||
|
||||
<div id="whole-page-app">
|
||||
<whole-page></whole-page>
|
||||
</div>
|
||||
|
||||
${self.render_whole_page_template()}
|
||||
${self.make_whole_page_component()}
|
||||
${self.make_whole_page_app()}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<%def name="title()"></%def>
|
||||
|
||||
<%def name="content_title()">
|
||||
${self.title()}
|
||||
</%def>
|
||||
|
||||
<%def name="header_core()">
|
||||
|
||||
${self.core_javascript()}
|
||||
${self.extra_javascript()}
|
||||
${self.core_styles()}
|
||||
${self.extra_styles()}
|
||||
|
||||
## TODO: should this be elsewhere / more customizable?
|
||||
% if dform is not Undefined:
|
||||
<% resources = dform.get_widget_resources() %>
|
||||
% for path in resources['js']:
|
||||
${h.javascript_link(request.static_url(path))}
|
||||
% endfor
|
||||
% for path in resources['css']:
|
||||
${h.stylesheet_link(request.static_url(path))}
|
||||
% endfor
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
<%def name="core_javascript()">
|
||||
${self.jquery()}
|
||||
${self.vuejs()}
|
||||
${self.buefy()}
|
||||
${self.fontawesome()}
|
||||
|
||||
## some commonly-useful logic for detecting (non-)numeric input
|
||||
${h.javascript_link(request.static_url('tailbone:static/js/numeric.js') + '?ver={}'.format(tailbone.__version__))}
|
||||
|
||||
## debounce, for better autocomplete performance
|
||||
${h.javascript_link(request.static_url('tailbone:static/js/debounce.js') + '?ver={}'.format(tailbone.__version__))}
|
||||
|
||||
## Tailbone / Buefy stuff
|
||||
${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.datepicker.js') + '?ver={}'.format(tailbone.__version__))}
|
||||
${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.numericinput.js') + '?ver={}'.format(tailbone.__version__))}
|
||||
${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.oncebutton.js') + '?ver={}'.format(tailbone.__version__))}
|
||||
${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.timepicker.js') + '?ver={}'.format(tailbone.__version__))}
|
||||
${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.grid.js') + '?ver={}'.format(tailbone.__version__))}
|
||||
|
||||
<script type="text/javascript">
|
||||
var session_timeout = ${request.get_session_timeout() or 'null'};
|
||||
var logout_url = '${request.route_url('logout')}';
|
||||
var noop_url = '${request.route_url('noop')}';
|
||||
$(function() {
|
||||
## NOTE: this code was copied from
|
||||
## https://bulma.io/documentation/components/navbar/#navbar-menu
|
||||
$('.navbar-burger').click(function() {
|
||||
$('.navbar-burger').toggleClass('is-active');
|
||||
$('.navbar-menu').toggleClass('is-active');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</%def>
|
||||
|
||||
<%def name="jquery()">
|
||||
${h.javascript_link(h.get_liburl(request, 'jquery'))}
|
||||
</%def>
|
||||
|
||||
<%def name="vuejs()">
|
||||
${h.javascript_link(h.get_liburl(request, 'vue'))}
|
||||
${h.javascript_link(h.get_liburl(request, 'vue_resource'))}
|
||||
</%def>
|
||||
|
||||
<%def name="buefy()">
|
||||
${h.javascript_link(h.get_liburl(request, 'buefy'))}
|
||||
</%def>
|
||||
|
||||
<%def name="fontawesome()">
|
||||
<script defer src="${h.get_liburl(request, 'fontawesome')}"></script>
|
||||
</%def>
|
||||
|
||||
<%def name="extra_javascript()"></%def>
|
||||
|
||||
<%def name="core_styles()">
|
||||
|
||||
${self.buefy_styles()}
|
||||
|
||||
${h.stylesheet_link(request.static_url('tailbone:static/themes/falafel/css/base.css') + '?ver={}'.format(tailbone.__version__))}
|
||||
${h.stylesheet_link(request.static_url('tailbone:static/themes/falafel/css/layout.css') + '?ver={}'.format(tailbone.__version__))}
|
||||
${h.stylesheet_link(request.static_url('tailbone:static/css/grids.css') + '?ver={}'.format(tailbone.__version__))}
|
||||
${h.stylesheet_link(request.static_url('tailbone:static/themes/falafel/css/grids.css') + '?ver={}'.format(tailbone.__version__))}
|
||||
${h.stylesheet_link(request.static_url('tailbone:static/themes/falafel/css/grids.rowstatus.css') + '?ver={}'.format(tailbone.__version__))}
|
||||
## ${h.stylesheet_link(request.static_url('tailbone:static/css/filters.css') + '?ver={}'.format(tailbone.__version__))}
|
||||
${h.stylesheet_link(request.static_url('tailbone:static/themes/falafel/css/filters.css') + '?ver={}'.format(tailbone.__version__))}
|
||||
${h.stylesheet_link(request.static_url('tailbone:static/themes/falafel/css/forms.css') + '?ver={}'.format(tailbone.__version__))}
|
||||
${h.stylesheet_link(request.static_url('tailbone:static/css/diffs.css') + '?ver={}'.format(tailbone.__version__))}
|
||||
|
||||
${h.stylesheet_link(request.static_url('tailbone:static/css/codehilite.css') + '?ver={}'.format(tailbone.__version__))}
|
||||
|
||||
<style type="text/css">
|
||||
.filters .filter-fieldname {
|
||||
min-width: ${filter_fieldname_width};
|
||||
justify-content: left;
|
||||
}
|
||||
.filters .filter-verb {
|
||||
min-width: ${filter_verb_width};
|
||||
}
|
||||
</style>
|
||||
</%def>
|
||||
|
||||
<%def name="buefy_styles()">
|
||||
% if buefy_css:
|
||||
## custom Buefy CSS
|
||||
${h.stylesheet_link(buefy_css)}
|
||||
% else:
|
||||
## upstream Buefy CSS
|
||||
${h.stylesheet_link(h.get_liburl(request, 'buefy.css'))}
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
## TODO: this is only being referenced by the progress template i think?
|
||||
## (so, should make a Buefy progress page at least)
|
||||
<%def name="jquery_theme()">
|
||||
${h.stylesheet_link(h.get_liburl(request, 'jquery_ui'))}
|
||||
</%def>
|
||||
|
||||
<%def name="extra_styles()"></%def>
|
||||
|
||||
<%def name="head_tags()"></%def>
|
||||
|
||||
<%def name="render_whole_page_template()">
|
||||
<script type="text/x-template" id="whole-page-template">
|
||||
<div>
|
||||
<header>
|
||||
|
||||
<nav class="navbar" role="navigation" aria-label="main navigation">
|
||||
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="${url('home')}"
|
||||
v-show="!globalSearchActive">
|
||||
${base_meta.header_logo()}
|
||||
<div id="global-header-title">
|
||||
${base_meta.global_title()}
|
||||
</div>
|
||||
</a>
|
||||
<div v-show="globalSearchActive"
|
||||
class="navbar-item">
|
||||
<b-autocomplete ref="globalSearchAutocomplete"
|
||||
v-model="globalSearchTerm"
|
||||
:data="globalSearchFilteredData"
|
||||
field="label"
|
||||
open-on-focus
|
||||
keep-first
|
||||
icon-pack="fas"
|
||||
clearable
|
||||
@keydown.native="globalSearchKeydown"
|
||||
@select="globalSearchSelect">
|
||||
</b-autocomplete>
|
||||
</div>
|
||||
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false">
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
|
||||
<div v-if="globalSearchData.length"
|
||||
class="navbar-item">
|
||||
<b-button type="is-primary"
|
||||
size="is-small"
|
||||
@click="globalSearchInit()">
|
||||
<span><i class="fa fa-search"></i></span>
|
||||
</b-button>
|
||||
</div>
|
||||
|
||||
% for topitem in menus:
|
||||
% if topitem['is_link']:
|
||||
${h.link_to(topitem['title'], topitem['url'], target=topitem['target'], class_='navbar-item')}
|
||||
% else:
|
||||
<div class="navbar-item has-dropdown is-hoverable">
|
||||
<a class="navbar-link">${topitem['title']}</a>
|
||||
<div class="navbar-dropdown">
|
||||
% for item in topitem['items']:
|
||||
% if item['is_menu']:
|
||||
<% item_hash = id(item) %>
|
||||
<% toggle = 'menu_{}_shown'.format(item_hash) %>
|
||||
<div>
|
||||
<a class="navbar-link" @click.prevent="toggleNestedMenu('${item_hash}')">
|
||||
${item['title']}
|
||||
</a>
|
||||
</div>
|
||||
% for subitem in item['items']:
|
||||
% if subitem['is_sep']:
|
||||
<hr class="navbar-divider" v-show="${toggle}">
|
||||
% else:
|
||||
${h.link_to("{}".format(subitem['title']), subitem['url'], class_='navbar-item nested', target=subitem['target'], **{'v-show': toggle})}
|
||||
% endif
|
||||
% endfor
|
||||
% else:
|
||||
% if item['is_sep']:
|
||||
<hr class="navbar-divider">
|
||||
% else:
|
||||
${h.link_to(item['title'], item['url'], class_='navbar-item', target=item['target'])}
|
||||
% endif
|
||||
% endif
|
||||
% endfor
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
% endfor
|
||||
|
||||
</div><!-- navbar-start -->
|
||||
${self.render_navbar_end()}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<nav class="level" style="margin: 0.5rem auto;">
|
||||
<div class="level-left">
|
||||
|
||||
## Current Context
|
||||
<div id="current-context" class="level-item">
|
||||
% if master:
|
||||
% if master.listing:
|
||||
<span class="header-text">
|
||||
${index_title}
|
||||
</span>
|
||||
% if master.creatable and master.show_create_link and master.has_perm('create'):
|
||||
<once-button type="is-primary"
|
||||
tag="a" href="${url('{}.create'.format(route_prefix))}"
|
||||
icon-left="plus"
|
||||
style="margin-left: 1rem;"
|
||||
text="Create New">
|
||||
</once-button>
|
||||
% endif
|
||||
% elif index_url:
|
||||
<span class="header-text">
|
||||
${h.link_to(index_title, index_url)}
|
||||
</span>
|
||||
% if parent_url is not Undefined:
|
||||
<span class="header-text">
|
||||
»
|
||||
</span>
|
||||
<span class="header-text">
|
||||
${h.link_to(parent_title, parent_url)}
|
||||
</span>
|
||||
% elif instance_url is not Undefined:
|
||||
<span class="header-text">
|
||||
»
|
||||
</span>
|
||||
<span class="header-text">
|
||||
${h.link_to(instance_title, instance_url)}
|
||||
</span>
|
||||
% elif master.creatable and master.show_create_link and master.has_perm('create'):
|
||||
% if not request.matched_route.name.endswith('.create'):
|
||||
<once-button type="is-primary"
|
||||
tag="a" href="${url('{}.create'.format(route_prefix))}"
|
||||
icon-left="plus"
|
||||
style="margin-left: 1rem;"
|
||||
text="Create New">
|
||||
</once-button>
|
||||
% endif
|
||||
% endif
|
||||
% if master.viewing and grid_index:
|
||||
${grid_index_nav()}
|
||||
% endif
|
||||
% else:
|
||||
<span class="header-text">
|
||||
${index_title}
|
||||
</span>
|
||||
% endif
|
||||
% elif index_title:
|
||||
% if index_url:
|
||||
<span class="header-text">
|
||||
${h.link_to(index_title, index_url)}
|
||||
</span>
|
||||
% else:
|
||||
<span class="header-text">
|
||||
${index_title}
|
||||
</span>
|
||||
% endif
|
||||
% endif
|
||||
</div>
|
||||
|
||||
% if expose_db_picker is not Undefined and expose_db_picker:
|
||||
<div class="level-item">
|
||||
<p>DB:</p>
|
||||
</div>
|
||||
<div class="level-item">
|
||||
${h.form(url('change_db_engine'), ref='dbPickerForm')}
|
||||
${h.csrf_token(request)}
|
||||
${h.hidden('engine_type', value=master.engine_type_key)}
|
||||
<div class="select">
|
||||
${h.select('dbkey', db_picker_selected, db_picker_options, **{'@change': 'changeDB()'})}
|
||||
</div>
|
||||
${h.end_form()}
|
||||
</div>
|
||||
% endif
|
||||
|
||||
</div><!-- level-left -->
|
||||
<div class="level-right">
|
||||
|
||||
## Quickie Lookup
|
||||
% if quickie is not Undefined and quickie and request.has_perm(quickie.perm):
|
||||
<div class="level-item">
|
||||
${h.form(quickie.url, method="get")}
|
||||
<div class="level">
|
||||
<div class="level-right">
|
||||
<div class="level-item">
|
||||
<b-input name="entry"
|
||||
placeholder="${quickie.placeholder}"
|
||||
autocomplete="off">
|
||||
</b-input>
|
||||
</div>
|
||||
<div class="level-item">
|
||||
<button type="submit" class="button is-primary">
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-search"></i>
|
||||
</span>
|
||||
<span>Lookup</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
${h.end_form()}
|
||||
</div>
|
||||
% endif
|
||||
|
||||
% if master and master.configurable and master.has_perm('configure'):
|
||||
% if not request.matched_route.name.endswith('.configure'):
|
||||
<div class="level-item">
|
||||
<once-button type="is-primary"
|
||||
tag="a"
|
||||
href="${url('{}.configure'.format(route_prefix))}"
|
||||
icon-left="cog"
|
||||
text="${(configure_button_title or "Configure") if configure_button_title is not Undefined else "Configure"}">
|
||||
</once-button>
|
||||
</div>
|
||||
% endif
|
||||
% endif
|
||||
|
||||
## Theme Picker
|
||||
% if expose_theme_picker and request.has_perm('common.change_app_theme'):
|
||||
<div class="level-item">
|
||||
${h.form(url('change_theme'), method="post", ref='themePickerForm')}
|
||||
${h.csrf_token(request)}
|
||||
Theme:
|
||||
<div class="theme-picker">
|
||||
<div class="select">
|
||||
${h.select('theme', theme, theme_picker_options, **{'@change': 'changeTheme()'})}
|
||||
</div>
|
||||
</div>
|
||||
${h.end_form()}
|
||||
</div>
|
||||
% endif
|
||||
|
||||
<div class="level-item">
|
||||
<page-help
|
||||
% if can_edit_help:
|
||||
@configure-fields-help="configureFieldsHelp = true"
|
||||
% endif
|
||||
>
|
||||
</page-help>
|
||||
</div>
|
||||
|
||||
## Feedback Button / Dialog
|
||||
% if request.has_perm('common.feedback'):
|
||||
<feedback-form
|
||||
action="${url('feedback')}"
|
||||
:message="feedbackMessage">
|
||||
</feedback-form>
|
||||
% endif
|
||||
|
||||
</div><!-- level-right -->
|
||||
</nav><!-- level -->
|
||||
</header>
|
||||
|
||||
## Page Title
|
||||
% if capture(self.content_title):
|
||||
<section id="content-title" class="hero is-primary">
|
||||
<div class="level">
|
||||
<div class="level-left">
|
||||
<div class="level-item">
|
||||
<h1 class="title" v-html="contentTitleHTML"></h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
${self.render_instance_header_buttons()}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
% endif
|
||||
|
||||
<div class="content-wrapper">
|
||||
|
||||
## Page Body
|
||||
<section id="page-body">
|
||||
|
||||
% if request.session.peek_flash('error'):
|
||||
% for error in request.session.pop_flash('error'):
|
||||
<b-notification type="is-warning">
|
||||
${error}
|
||||
</b-notification>
|
||||
% endfor
|
||||
% endif
|
||||
|
||||
% if request.session.peek_flash('warning'):
|
||||
% for msg in request.session.pop_flash('warning'):
|
||||
<b-notification type="is-warning">
|
||||
${msg}
|
||||
</b-notification>
|
||||
% endfor
|
||||
% endif
|
||||
|
||||
% if request.session.peek_flash():
|
||||
% for msg in request.session.pop_flash():
|
||||
<b-notification type="is-info">
|
||||
${msg}
|
||||
</b-notification>
|
||||
% endfor
|
||||
% endif
|
||||
|
||||
${self.render_this_page_component()}
|
||||
</section>
|
||||
|
||||
## Footer
|
||||
<footer class="footer">
|
||||
<div class="content">
|
||||
${base_meta.footer()}
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</div><!-- content-wrapper -->
|
||||
</div>
|
||||
</script>
|
||||
|
||||
${page_help.render_template()}
|
||||
|
||||
<script type="text/x-template" id="feedback-template">
|
||||
<div>
|
||||
|
||||
<div class="level-item">
|
||||
<b-button type="is-primary"
|
||||
@click="showFeedback()"
|
||||
icon-pack="fas"
|
||||
icon-left="fas fa-comment">
|
||||
Feedback
|
||||
</b-button>
|
||||
</div>
|
||||
|
||||
<b-modal has-modal-card
|
||||
:active.sync="showDialog">
|
||||
<div class="modal-card">
|
||||
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">User Feedback</p>
|
||||
</header>
|
||||
|
||||
<section class="modal-card-body">
|
||||
<p class="block">
|
||||
Questions, suggestions, comments, complaints, etc.
|
||||
<span class="red">regarding this website</span> are
|
||||
welcome and may be submitted below.
|
||||
</p>
|
||||
|
||||
<b-field label="User Name">
|
||||
<b-input v-model="userName"
|
||||
% if request.user:
|
||||
disabled
|
||||
% endif
|
||||
>
|
||||
</b-input>
|
||||
</b-field>
|
||||
|
||||
<b-field label="Referring URL">
|
||||
<b-input
|
||||
v-model="referrer"
|
||||
disabled="true">
|
||||
</b-input>
|
||||
</b-field>
|
||||
|
||||
<b-field label="Message">
|
||||
<b-input type="textarea"
|
||||
v-model="message"
|
||||
ref="textarea">
|
||||
</b-input>
|
||||
</b-field>
|
||||
|
||||
% if request.rattail_config.getbool('tailbone', 'feedback_allows_reply'):
|
||||
<div class="level">
|
||||
<div class="level-left">
|
||||
<div class="level-item">
|
||||
<b-checkbox v-model="pleaseReply"
|
||||
@input="pleaseReplyChanged">
|
||||
Please email me back{{ pleaseReply ? " at: " : "" }}
|
||||
</b-checkbox>
|
||||
</div>
|
||||
<div class="level-item" v-show="pleaseReply">
|
||||
<b-input v-model="userEmail"
|
||||
ref="userEmail">
|
||||
</b-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
|
||||
</section>
|
||||
|
||||
<footer class="modal-card-foot">
|
||||
<b-button @click="showDialog = false">
|
||||
Cancel
|
||||
</b-button>
|
||||
<once-button type="is-primary"
|
||||
@click="sendFeedback()"
|
||||
:disabled="!message.trim()"
|
||||
text="Send Message">
|
||||
</once-button>
|
||||
</footer>
|
||||
</div>
|
||||
</b-modal>
|
||||
|
||||
</div>
|
||||
</script>
|
||||
|
||||
${tailbone_autocomplete_template()}
|
||||
${multi_file_upload.render_template()}
|
||||
</%def>
|
||||
|
||||
<%def name="render_this_page_component()">
|
||||
<this-page @change-content-title="changeContentTitle"
|
||||
% if can_edit_help:
|
||||
:configure-fields-help="configureFieldsHelp"
|
||||
% endif
|
||||
>
|
||||
</this-page>
|
||||
</%def>
|
||||
|
||||
<%def name="render_navbar_end()">
|
||||
<div class="navbar-end">
|
||||
${self.render_user_menu()}
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
<%def name="render_user_menu()">
|
||||
% if request.user:
|
||||
<div class="navbar-item has-dropdown is-hoverable">
|
||||
% if messaging_enabled:
|
||||
<a class="navbar-link ${'root-user' if request.is_root else ''}">${request.user}${" ({})".format(inbox_count) if inbox_count else ''}</a>
|
||||
% else:
|
||||
<a class="navbar-link ${'root-user' if request.is_root else ''}">${request.user}</a>
|
||||
% endif
|
||||
<div class="navbar-dropdown">
|
||||
% if request.is_root:
|
||||
${h.link_to("Stop being root", url('stop_root'), class_='navbar-item root-user')}
|
||||
% elif request.is_admin:
|
||||
${h.link_to("Become root", url('become_root'), class_='navbar-item root-user')}
|
||||
% endif
|
||||
% if messaging_enabled:
|
||||
${h.link_to("Messages{}".format(" ({})".format(inbox_count) if inbox_count else ''), url('messages.inbox'), class_='navbar-item')}
|
||||
% endif
|
||||
${h.link_to("Change Password", url('change_password'), class_='navbar-item')}
|
||||
${h.link_to("Edit Preferences", url('my.preferences'), class_='navbar-item')}
|
||||
${h.link_to("Logout", url('logout'), class_='navbar-item')}
|
||||
</div>
|
||||
</div>
|
||||
% else:
|
||||
${h.link_to("Login", url('login'), class_='navbar-item')}
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
<%def name="render_instance_header_buttons()">
|
||||
${self.render_crud_header_buttons()}
|
||||
${self.render_prevnext_header_buttons()}
|
||||
</%def>
|
||||
|
||||
<%def name="render_crud_header_buttons()">
|
||||
% if master and master.viewing:
|
||||
## TODO: is there a better way to check if viewing parent?
|
||||
% if parent_instance is Undefined:
|
||||
% if master.editable and instance_editable and master.has_perm('edit'):
|
||||
<div class="level-item">
|
||||
<once-button tag="a" href="${action_url('edit', instance)}"
|
||||
icon-left="edit"
|
||||
text="Edit This">
|
||||
</once-button>
|
||||
</div>
|
||||
% endif
|
||||
% if master.cloneable and master.has_perm('clone'):
|
||||
<div class="level-item">
|
||||
<once-button tag="a" href="${action_url('clone', instance)}"
|
||||
icon-left="object-ungroup"
|
||||
text="Clone This">
|
||||
</once-button>
|
||||
</div>
|
||||
% endif
|
||||
% if master.deletable and instance_deletable and master.has_perm('delete'):
|
||||
<div class="level-item">
|
||||
<once-button tag="a" href="${action_url('delete', instance)}"
|
||||
type="is-danger"
|
||||
icon-left="trash"
|
||||
text="Delete This">
|
||||
</once-button>
|
||||
</div>
|
||||
% endif
|
||||
% else:
|
||||
## viewing row
|
||||
% if instance_deletable and master.has_perm('delete_row'):
|
||||
<div class="level-item">
|
||||
<once-button tag="a" href="${action_url('delete', instance)}"
|
||||
type="is-danger"
|
||||
icon-left="trash"
|
||||
text="Delete This">
|
||||
</once-button>
|
||||
</div>
|
||||
% endif
|
||||
% endif
|
||||
% elif master and master.editing:
|
||||
% if master.viewable and master.has_perm('view'):
|
||||
<div class="level-item">
|
||||
<once-button tag="a" href="${action_url('view', instance)}"
|
||||
icon-left="eye"
|
||||
text="View This">
|
||||
</once-button>
|
||||
</div>
|
||||
% endif
|
||||
% if master.deletable and instance_deletable and master.has_perm('delete'):
|
||||
<div class="level-item">
|
||||
<once-button tag="a" href="${action_url('delete', instance)}"
|
||||
type="is-danger"
|
||||
icon-left="trash"
|
||||
text="Delete This">
|
||||
</once-button>
|
||||
</div>
|
||||
% endif
|
||||
% elif master and master.deleting:
|
||||
% if master.viewable and master.has_perm('view'):
|
||||
<div class="level-item">
|
||||
<once-button tag="a" href="${action_url('view', instance)}"
|
||||
icon-left="eye"
|
||||
text="View This">
|
||||
</once-button>
|
||||
</div>
|
||||
% endif
|
||||
% if master.editable and instance_editable and master.has_perm('edit'):
|
||||
<div class="level-item">
|
||||
<once-button tag="a" href="${action_url('edit', instance)}"
|
||||
icon-left="edit"
|
||||
text="Edit This">
|
||||
</once-button>
|
||||
</div>
|
||||
% endif
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
<%def name="render_prevnext_header_buttons()">
|
||||
% if show_prev_next is not Undefined and show_prev_next:
|
||||
% if prev_url:
|
||||
<div class="level-item">
|
||||
<b-button tag="a" href="${prev_url}"
|
||||
icon-pack="fas"
|
||||
icon-left="arrow-left">
|
||||
Older
|
||||
</b-button>
|
||||
</div>
|
||||
% else:
|
||||
<div class="level-item">
|
||||
<b-button tag="a" href="#"
|
||||
disabled
|
||||
icon-pack="fas"
|
||||
icon-left="arrow-left">
|
||||
Older
|
||||
</b-button>
|
||||
</div>
|
||||
% endif
|
||||
% if next_url:
|
||||
<div class="level-item">
|
||||
<b-button tag="a" href="${next_url}"
|
||||
icon-pack="fas"
|
||||
icon-left="arrow-right">
|
||||
Newer
|
||||
</b-button>
|
||||
</div>
|
||||
% else:
|
||||
<div class="level-item">
|
||||
<b-button tag="a" href="#"
|
||||
disabled
|
||||
icon-pack="fas"
|
||||
icon-left="arrow-right">
|
||||
Newer
|
||||
</b-button>
|
||||
</div>
|
||||
% endif
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
<%def name="declare_whole_page_vars()">
|
||||
${page_help.declare_vars()}
|
||||
${multi_file_upload.declare_vars()}
|
||||
${h.javascript_link(request.static_url('tailbone:static/themes/falafel/js/tailbone.feedback.js') + '?ver={}'.format(tailbone.__version__))}
|
||||
<script type="text/javascript">
|
||||
|
||||
let WholePage = {
|
||||
template: '#whole-page-template',
|
||||
mixins: [FormPosterMixin],
|
||||
computed: {
|
||||
|
||||
globalSearchFilteredData() {
|
||||
if (!this.globalSearchTerm.length) {
|
||||
return this.globalSearchData
|
||||
}
|
||||
return this.globalSearchData.filter((option) => {
|
||||
return option.label.toLowerCase().indexOf(this.globalSearchTerm.toLowerCase()) >= 0
|
||||
})
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
mounted() {
|
||||
window.addEventListener('keydown', this.globalKey)
|
||||
for (let hook of this.mountedHooks) {
|
||||
hook(this)
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('keydown', this.globalKey)
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
changeContentTitle(newTitle) {
|
||||
this.contentTitleHTML = newTitle
|
||||
},
|
||||
|
||||
% if expose_db_picker is not Undefined and expose_db_picker:
|
||||
changeDB() {
|
||||
this.$refs.dbPickerForm.submit()
|
||||
},
|
||||
% endif
|
||||
|
||||
% if expose_theme_picker and request.has_perm('common.change_app_theme'):
|
||||
changeTheme() {
|
||||
this.$refs.themePickerForm.submit()
|
||||
},
|
||||
% endif
|
||||
|
||||
globalKey(event) {
|
||||
|
||||
// Ctrl+8 opens global search
|
||||
if (event.target.tagName == 'BODY') {
|
||||
if (event.ctrlKey && event.key == '8') {
|
||||
this.globalSearchInit()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
globalSearchInit() {
|
||||
this.globalSearchTerm = ''
|
||||
this.globalSearchActive = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.globalSearchAutocomplete.focus()
|
||||
})
|
||||
},
|
||||
|
||||
globalSearchKeydown(event) {
|
||||
|
||||
// ESC will dismiss searchbox
|
||||
if (event.which == 27) {
|
||||
this.globalSearchActive = false
|
||||
}
|
||||
},
|
||||
|
||||
globalSearchSelect(option) {
|
||||
location.href = option.url
|
||||
},
|
||||
|
||||
toggleNestedMenu(hash) {
|
||||
const key = 'menu_' + hash + '_shown'
|
||||
this[key] = !this[key]
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
let WholePageData = {
|
||||
contentTitleHTML: ${json.dumps(capture(self.content_title))|n},
|
||||
feedbackMessage: "",
|
||||
|
||||
% if can_edit_help:
|
||||
configureFieldsHelp: false,
|
||||
% endif
|
||||
|
||||
globalSearchActive: false,
|
||||
globalSearchTerm: '',
|
||||
globalSearchData: ${json.dumps(global_search_data)|n},
|
||||
|
||||
mountedHooks: [],
|
||||
}
|
||||
|
||||
## declare nested menu visibility toggle flags
|
||||
% for topitem in menus:
|
||||
% if topitem['is_menu']:
|
||||
% for item in topitem['items']:
|
||||
% if item['is_menu']:
|
||||
WholePageData.menu_${id(item)}_shown = false
|
||||
% endif
|
||||
% endfor
|
||||
% endif
|
||||
% endfor
|
||||
|
||||
</script>
|
||||
</%def>
|
||||
|
||||
<%def name="modify_whole_page_vars()">
|
||||
<script type="text/javascript">
|
||||
|
||||
FeedbackFormData.referrer = location.href
|
||||
|
||||
% if request.user:
|
||||
FeedbackFormData.userUUID = ${json.dumps(request.user.uuid)|n}
|
||||
FeedbackFormData.userName = ${json.dumps(six.text_type(request.user))|n}
|
||||
% endif
|
||||
|
||||
</script>
|
||||
</%def>
|
||||
|
||||
<%def name="finalize_whole_page_vars()">
|
||||
## NOTE: if you override this, must use <script> tags
|
||||
</%def>
|
||||
|
||||
<%def name="make_whole_page_component()">
|
||||
${self.declare_whole_page_vars()}
|
||||
${self.modify_whole_page_vars()}
|
||||
${self.finalize_whole_page_vars()}
|
||||
|
||||
${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.autocomplete.js') + '?ver={}'.format(tailbone.__version__))}
|
||||
|
||||
${page_help.make_component()}
|
||||
${multi_file_upload.make_component()}
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
FeedbackForm.data = function() { return FeedbackFormData }
|
||||
|
||||
Vue.component('feedback-form', FeedbackForm)
|
||||
|
||||
WholePage.data = function() { return WholePageData }
|
||||
|
||||
Vue.component('whole-page', WholePage)
|
||||
|
||||
</script>
|
||||
</%def>
|
||||
|
||||
<%def name="make_whole_page_app()">
|
||||
<script type="text/javascript">
|
||||
|
||||
new Vue({
|
||||
el: '#whole-page-app'
|
||||
})
|
||||
|
||||
</script>
|
||||
</%def>
|
||||
|
||||
<%def name="wtfield(form, name, **kwargs)">
|
||||
<div class="field-wrapper${' error' if form[name].errors else ''}">
|
||||
<label for="${name}">${form[name].label}</label>
|
||||
<div class="field">
|
||||
${form[name](**kwargs)}
|
||||
</div>
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
<%def name="simple_field(label, value)">
|
||||
## TODO: keep this? only used by personal profile view currently
|
||||
## (although could be useful for any readonly scenario)
|
||||
<div class="field-wrapper">
|
||||
<div class="field-row">
|
||||
<label>${label}</label>
|
||||
<div class="field">
|
||||
${'' if value is None else value}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</%def>
|
||||
${parent.body()}
|
||||
|
|
|
@ -1,219 +1,4 @@
|
|||
## -*- coding: utf-8; -*-
|
||||
<%namespace file="/base.mako" import="core_javascript" />
|
||||
<%namespace file="/base.mako" import="core_styles" />
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
|
||||
<title>${initial_msg or "Working"}...</title>
|
||||
${core_javascript()}
|
||||
${core_styles()}
|
||||
${self.extra_styles()}
|
||||
</head>
|
||||
<body style="height: 100%;">
|
||||
<%inherit file="tailbone:templates/progress.mako" />
|
||||
|
||||
<div id="whole-page-app">
|
||||
<whole-page></whole-page>
|
||||
</div>
|
||||
|
||||
<script type="text/x-template" id="whole-page-template">
|
||||
|
||||
<section class="hero is-fullheight">
|
||||
<div class="hero-body">
|
||||
<div class="container">
|
||||
|
||||
<div style="display: flex;">
|
||||
<div style="flex-grow: 1;"></div>
|
||||
<div>
|
||||
|
||||
<p class="block">
|
||||
{{ progressMessage }} ... {{ totalDisplay }}
|
||||
</p>
|
||||
|
||||
<div class="level">
|
||||
|
||||
<div class="level-item">
|
||||
<b-progress size="is-large"
|
||||
style="width: 400px;"
|
||||
:max="progressMax"
|
||||
:value="progressValue"
|
||||
show-value
|
||||
format="percent"
|
||||
precision="0">
|
||||
</b-progress>
|
||||
</div>
|
||||
|
||||
% if can_cancel:
|
||||
<div class="level-item"
|
||||
style="margin-left: 2rem;">
|
||||
<b-button v-show="canCancel"
|
||||
@click="cancelProgress()"
|
||||
:disabled="cancelingProgress"
|
||||
icon-pack="fas"
|
||||
icon-left="ban">
|
||||
{{ cancelingProgress ? "Canceling, please wait..." : "Cancel" }}
|
||||
</b-button>
|
||||
</div>
|
||||
% endif
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div style="flex-grow: 1;"></div>
|
||||
</div>
|
||||
|
||||
${self.after_progress()}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
let WholePage = {
|
||||
template: '#whole-page-template',
|
||||
|
||||
computed: {
|
||||
|
||||
totalDisplay() {
|
||||
|
||||
% if can_cancel:
|
||||
if (!this.stillInProgress && !this.cancelingProgress) {
|
||||
% else:
|
||||
if (!this.stillInProgress) {
|
||||
% endif
|
||||
return "done!"
|
||||
}
|
||||
|
||||
if (this.progressMaxDisplay) {
|
||||
return `(${'$'}{this.progressMaxDisplay} total)`
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
|
||||
// fetch first progress data, one second from now
|
||||
setTimeout(() => {
|
||||
this.updateProgress()
|
||||
}, 1000)
|
||||
|
||||
// custom logic if applicable
|
||||
this.mountedCustom()
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
mountedCustom() {},
|
||||
|
||||
updateProgress() {
|
||||
|
||||
this.$http.get(this.progressURL).then(response => {
|
||||
|
||||
if (response.data.error) {
|
||||
// errors stop the show, we redirect to "cancel" page
|
||||
location.href = '${cancel_url}'
|
||||
|
||||
} else {
|
||||
|
||||
if (response.data.complete || response.data.maximum) {
|
||||
this.progressMessage = response.data.message
|
||||
this.progressMaxDisplay = response.data.maximum_display
|
||||
|
||||
if (response.data.complete) {
|
||||
this.progressValue = this.progressMax
|
||||
this.stillInProgress = false
|
||||
% if can_cancel:
|
||||
this.canCancel = false
|
||||
% endif
|
||||
|
||||
location.href = response.data.success_url
|
||||
|
||||
} else {
|
||||
this.progressValue = response.data.value
|
||||
this.progressMax = response.data.maximum
|
||||
}
|
||||
}
|
||||
|
||||
// custom logic if applicable
|
||||
this.updateProgressCustom(response)
|
||||
|
||||
if (this.stillInProgress) {
|
||||
|
||||
// fetch progress data again, in one second from now
|
||||
setTimeout(() => {
|
||||
this.updateProgress()
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
updateProgressCustom(response) {},
|
||||
|
||||
% if can_cancel:
|
||||
|
||||
cancelProgress() {
|
||||
|
||||
if (confirm("Do you really wish to cancel this operation?")) {
|
||||
|
||||
this.cancelingProgress = true
|
||||
this.stillInProgress = false
|
||||
|
||||
let params = {cancel_msg: ${json.dumps(cancel_msg)|n}}
|
||||
this.$http.get(this.cancelURL, {params: params}).then(response => {
|
||||
location.href = ${json.dumps(cancel_url)|n}
|
||||
})
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
% endif
|
||||
}
|
||||
}
|
||||
|
||||
let WholePageData = {
|
||||
|
||||
progressURL: '${url('progress', key=progress.key, _query={'sessiontype': progress.session.type})}',
|
||||
progressMessage: "${(initial_msg or "Working").replace('"', '\\"')} (please wait)",
|
||||
progressMax: null,
|
||||
progressMaxDisplay: null,
|
||||
progressValue: null,
|
||||
stillInProgress: true,
|
||||
|
||||
% if can_cancel:
|
||||
canCancel: true,
|
||||
cancelURL: '${url('progress.cancel', key=progress.key, _query={'sessiontype': progress.session.type})}',
|
||||
cancelingProgress: false,
|
||||
% endif
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
${self.modify_whole_page_vars()}
|
||||
${self.make_whole_page_app()}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<%def name="extra_styles()"></%def>
|
||||
|
||||
<%def name="after_progress()"></%def>
|
||||
|
||||
<%def name="modify_whole_page_vars()"></%def>
|
||||
|
||||
<%def name="make_whole_page_app()">
|
||||
<script type="text/javascript">
|
||||
|
||||
WholePage.data = function() { return WholePageData }
|
||||
|
||||
Vue.component('whole-page', WholePage)
|
||||
|
||||
new Vue({
|
||||
el: '#whole-page-app'
|
||||
})
|
||||
|
||||
</script>
|
||||
</%def>
|
||||
${parent.body()}
|
||||
|
|
|
@ -1,69 +1,4 @@
|
|||
## -*- coding: utf-8; -*-
|
||||
<%inherit file="/progress.mako" />
|
||||
|
||||
<%def name="extra_styles()">
|
||||
${parent.extra_styles()}
|
||||
<style type="text/css">
|
||||
|
||||
.progress-with-textout {
|
||||
border: 1px solid Black;
|
||||
line-height: 1.2;
|
||||
margin-top: 1rem;
|
||||
overflow: auto;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
</style>
|
||||
</%def>
|
||||
|
||||
<%def name="after_progress()">
|
||||
<!-- <div ref="stdout" class="stdout"></div> -->
|
||||
|
||||
<div ref="textout"
|
||||
class="progress-with-textout is-family-monospace is-size-7">
|
||||
<span v-for="line in progressOutput"
|
||||
:key="line.key"
|
||||
v-html="line.text">
|
||||
</span>
|
||||
|
||||
## nb. we auto-scroll down to "see" this element
|
||||
<div ref="seeme"></div>
|
||||
</div>
|
||||
|
||||
</%def>
|
||||
|
||||
<%def name="modify_whole_page_vars()">
|
||||
<script type="text/javascript">
|
||||
|
||||
WholePageData.progressURL = '${url('upgrades.execute_progress', uuid=instance.uuid)}'
|
||||
WholePageData.progressOutput = []
|
||||
WholePageData.progressOutputCounter = 0
|
||||
|
||||
WholePage.methods.mountedCustom = function() {
|
||||
|
||||
// grow the textout area to fill most of screen
|
||||
let textout = this.$refs.textout
|
||||
let height = window.innerHeight - textout.offsetTop - 100
|
||||
textout.style.height = height + 'px'
|
||||
}
|
||||
|
||||
WholePage.methods.updateProgressCustom = function(response) {
|
||||
if (response.data.stdout) {
|
||||
|
||||
// add lines to textout area
|
||||
this.progressOutput.push({
|
||||
key: ++this.progressOutputCounter,
|
||||
text: response.data.stdout})
|
||||
|
||||
// scroll down to end of textout area
|
||||
this.$nextTick(() => {
|
||||
this.$refs.seeme.scrollIntoView({behavior: 'smooth'})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
</%def>
|
||||
|
||||
<%inherit file="tailbone:templates/upgrade.mako" />
|
||||
|
||||
${parent.body()}
|
||||
|
|
|
@ -1,86 +1,69 @@
|
|||
## -*- 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 {
|
||||
|
||||
if (data.stdout) {
|
||||
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) {
|
||||
stillInProgress = false;
|
||||
$('#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');
|
||||
}
|
||||
}
|
||||
|
||||
if (stillInProgress) {
|
||||
// fetch progress data again, in one second from now
|
||||
setTimeout(function() {
|
||||
update_progress();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</%def>
|
||||
|
||||
<%def name="extra_styles()">
|
||||
${parent.extra_styles()}
|
||||
<style type="text/css">
|
||||
#wrapper {
|
||||
top: 6em;
|
||||
}
|
||||
.stdout {
|
||||
|
||||
.progress-with-textout {
|
||||
border: 1px solid Black;
|
||||
height: 500px;
|
||||
margin-left: 4.5%;
|
||||
line-height: 1.2;
|
||||
margin-top: 1rem;
|
||||
overflow: auto;
|
||||
padding: 4px;
|
||||
position: absolute;
|
||||
top: 10em;
|
||||
white-space: nowrap;
|
||||
width: 90%;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
</style>
|
||||
</%def>
|
||||
|
||||
<%def name="after_progress()">
|
||||
<div class="stdout"></div>
|
||||
<!-- <div ref="stdout" class="stdout"></div> -->
|
||||
|
||||
<div ref="textout"
|
||||
class="progress-with-textout is-family-monospace is-size-7">
|
||||
<span v-for="line in progressOutput"
|
||||
:key="line.key"
|
||||
v-html="line.text">
|
||||
</span>
|
||||
|
||||
## nb. we auto-scroll down to "see" this element
|
||||
<div ref="seeme"></div>
|
||||
</div>
|
||||
|
||||
</%def>
|
||||
|
||||
<%def name="modify_whole_page_vars()">
|
||||
<script type="text/javascript">
|
||||
|
||||
WholePageData.progressURL = '${url('upgrades.execute_progress', uuid=instance.uuid)}'
|
||||
WholePageData.progressOutput = []
|
||||
WholePageData.progressOutputCounter = 0
|
||||
|
||||
WholePage.methods.mountedCustom = function() {
|
||||
|
||||
// grow the textout area to fill most of screen
|
||||
let textout = this.$refs.textout
|
||||
let height = window.innerHeight - textout.offsetTop - 100
|
||||
textout.style.height = height + 'px'
|
||||
}
|
||||
|
||||
WholePage.methods.updateProgressCustom = function(response) {
|
||||
if (response.data.stdout) {
|
||||
|
||||
// add lines to textout area
|
||||
this.progressOutput.push({
|
||||
key: ++this.progressOutputCounter,
|
||||
text: response.data.stdout})
|
||||
|
||||
// scroll down to end of textout area
|
||||
this.$nextTick(() => {
|
||||
this.$refs.seeme.scrollIntoView({behavior: 'smooth'})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
</%def>
|
||||
|
||||
|
||||
${parent.body()}
|
||||
|
|
Loading…
Reference in a new issue