Refactor upgrade websocket progress, so "anyone" can join in to see

now while an upgrade is executing, anyone with permission can "view"
the upgrade and see the same progress the executor is seeing
This commit is contained in:
Lance Edgar 2022-08-20 18:55:33 -05:00
parent 18cec49a86
commit e93063a344
3 changed files with 204 additions and 127 deletions

View file

@ -40,6 +40,7 @@
<%def name="extra_styles()">
${parent.extra_styles()}
% if master.has_perm('execute'):
<style type="text/css">
.progress-with-textout {
border: 1px solid Black;
@ -48,65 +49,13 @@
padding: 1rem;
}
</style>
% endif
</%def>
<%def name="render_this_page()">
${parent.render_this_page()}
% if master.has_perm('execute'):
${h.form(master.get_action_url('declare_failure', instance), ref='declareFailureForm')}
${h.csrf_token(request)}
${h.end_form()}
% endif
</%def>
<%def name="render_buefy_form()">
<div class="form">
<${form.component}
% if master.has_perm('execute'):
@declare-failure="declareFailure"
% endif
>
</${form.component}>
</div>
</%def>
<%def name="render_form_buttons()">
% if not instance.executed and instance.status_code == enum.UPGRADE_STATUS_PENDING and master.has_perm('execute'):
<div class="buttons">
% if instance.enabled and not instance.executing:
% if use_buefy and expose_websockets:
<b-button type="is-primary"
icon-pack="fas"
icon-left="arrow-circle-right"
:disabled="upgradeExecuting"
@click="executeUpgrade()">
{{ upgradeExecuting ? "Working, please wait..." : "Execute this upgrade" }}
</b-button>
% elif use_buefy:
${h.form(url('{}.execute'.format(route_prefix), uuid=instance.uuid), **{'@submit': 'submitForm'})}
${h.csrf_token(request)}
<b-button type="is-primary"
native-type="submit"
icon-pack="fas"
icon-left="arrow-circle-right"
:disabled="formSubmitting">
{{ formSubmitting ? "Working, please wait..." : "Execute this upgrade" }}
</b-button>
${h.end_form()}
% else:
${h.form(url('{}.execute'.format(route_prefix), uuid=instance.uuid), class_='autodisable')}
${h.csrf_token(request)}
${h.submit('execute', "Execute this upgrade", class_='button is-primary')}
${h.end_form()}
% endif
% elif instance.enabled:
<button type="button" class="button is-primary" disabled="disabled" title="This upgrade is currently executing">Execute this upgrade</button>
% else:
<button type="button" class="button is-primary" disabled="disabled" title="This upgrade is not enabled">Execute this upgrade</button>
% endif
</div>
% if expose_websockets and master.has_perm('execute'):
<b-modal :active.sync="upgradeExecuting"
full-screen
:can-cancel="false">
@ -116,7 +65,10 @@
<div class="level">
<div class="level-item has-text-centered"
style="display: flex; flex-direction: column;">
<p class="block">Upgrading (please wait) ...</p>
<p class="block">
Upgrading (please wait) ...
{{ executeUpgradeComplete ? "DONE!" : "" }}
</p>
<b-progress size="is-large"
style="width: 400px;"
## :value="80"
@ -151,7 +103,64 @@
</div>
</div>
</b-modal>
% endif
% if master.has_perm('execute'):
${h.form(master.get_action_url('declare_failure', instance), ref='declareFailureForm')}
${h.csrf_token(request)}
${h.end_form()}
% endif
</%def>
<%def name="render_buefy_form()">
<div class="form">
<${form.component}
% if expose_websockets and master.has_perm('execute'):
@execute-upgrade-click="executeUpgrade"
:upgrade-executing="upgradeExecuting"
@declare-failure-click="declareFailureClick"
:declare-failure-submitting="declareFailureSubmitting"
% endif
>
</${form.component}>
</div>
</%def>
<%def name="render_form_buttons()">
% if instance_executable and master.has_perm('execute'):
<div class="buttons">
% if instance.enabled and not instance.executing:
% if use_buefy and expose_websockets:
<b-button type="is-primary"
icon-pack="fas"
icon-left="arrow-circle-right"
:disabled="upgradeExecuting"
@click="$emit('execute-upgrade-click')">
{{ upgradeExecuting ? "Working, please wait..." : "Execute this upgrade" }}
</b-button>
% elif use_buefy:
${h.form(url('{}.execute'.format(route_prefix), uuid=instance.uuid), **{'@submit': 'submitForm'})}
${h.csrf_token(request)}
<b-button type="is-primary"
native-type="submit"
icon-pack="fas"
icon-left="arrow-circle-right"
:disabled="formSubmitting">
{{ formSubmitting ? "Working, please wait..." : "Execute this upgrade" }}
</b-button>
${h.end_form()}
% else:
${h.form(url('{}.execute'.format(route_prefix), uuid=instance.uuid), class_='autodisable')}
${h.csrf_token(request)}
${h.submit('execute', "Execute this upgrade", class_='button is-primary')}
${h.end_form()}
% endif
% elif instance.enabled:
<button type="button" class="button is-primary" disabled="disabled" title="This upgrade is currently executing">Execute this upgrade</button>
% else:
<button type="button" class="button is-primary" disabled="disabled" title="This upgrade is not enabled">Execute this upgrade</button>
% endif
</div>
% endif
</%def>
@ -165,29 +174,44 @@
% if expose_websockets:
TailboneFormData.upgradeExecuting = ${json.dumps(instance.executing)|n}
TailboneFormData.progressOutput = []
TailboneFormData.progressOutputCounter = 0
ThisPageData.ws = null
TailboneForm.methods.executeUpgrade = function() {
this.upgradeExecuting = true
//////////////////////////////
// execute upgrade
//////////////////////////////
TailboneForm.props.upgradeExecuting = {
type: Boolean,
default: false,
}
ThisPageData.upgradeExecuting = false
ThisPageData.progressOutput = []
ThisPageData.progressOutputCounter = 0
ThisPageData.executeUpgradeComplete = false
ThisPage.methods.adjustTextoutHeight = function() {
// grow the textout area to fill most of screen
this.$nextTick(() => {
let textout = this.$refs.textout
let height = window.innerHeight - textout.offsetTop - 50
textout.style.height = height + 'px'
})
}
let url = '${master.get_action_url('execute', instance)}'
this.submitForm(url, {ws: true}, response => {
ThisPage.methods.showExecuteDialog = function() {
this.upgradeExecuting = true
this.$nextTick(() => {
this.adjustTextoutHeight()
})
}
ThisPage.methods.establishWebsocket = function() {
## TODO: should be a cleaner way to get this url?
url = '${request.route_url('ws.upgrades.execution_progress', _query={'uuid': instance.uuid})}'
url = url.replace(/^https?:/, 'wss:')
this.ws = new WebSocket(url)
let that = this
## TODO: add support for this here?
// this.ws.onclose = (event) => {
@ -207,13 +231,16 @@
if (data.complete) {
// upgrade has completed; reload page to view result
this.executeUpgradeComplete = true
this.$nextTick(() => {
location.reload()
})
} else if (data.stdout) {
// add lines to textout area
that.progressOutput.push({
key: ++that.progressOutputCounter,
this.progressOutput.push({
key: ++this.progressOutputCounter,
text: data.stdout})
// scroll down to end of textout area
@ -222,12 +249,36 @@
})
}
}
}
% if instance.executing:
ThisPage.mounted = function() {
this.showExecuteDialog()
this.establishWebsocket()
}
% endif
% if instance_executable:
ThisPage.methods.executeUpgrade = function() {
this.showExecuteDialog()
let url = '${master.get_action_url('execute', instance)}'
this.submitForm(url, {ws: true}, response => {
this.establishWebsocket()
})
}
% endif
% else:
## no websockets
//////////////////////////////
// execute upgrade
//////////////////////////////
TailboneFormData.formSubmitting = false
TailboneForm.methods.submitForm = function() {
@ -236,18 +287,27 @@
% endif
TailboneFormData.declareFailureSubmitting = false
//////////////////////////////
// declare failure
//////////////////////////////
TailboneForm.props.declareFailureSubmitting = {
type: Boolean,
default: false,
}
TailboneForm.methods.declareFailureClick = function() {
if (confirm("Really declare this upgrade a failure?")) {
this.declareFailureSubmitting = true
this.$emit('declare-failure')
}
this.$emit('declare-failure-click')
}
ThisPage.methods.declareFailure = function() {
ThisPageData.declareFailureSubmitting = false
ThisPage.methods.declareFailureClick = function() {
if (confirm("Really declare this upgrade a failure?")) {
this.declareFailureSubmitting = true
this.$refs.declareFailureForm.submit()
}
}
% endif

View file

@ -1063,6 +1063,8 @@ class MasterView(View):
'instance_deletable': self.deletable_instance(instance),
'form': form,
}
if self.executable:
context['instance_executable'] = self.executable_instance(instance)
if hasattr(form, 'make_deform_form'):
context['dform'] = form.make_deform_form()
@ -1784,6 +1786,14 @@ class MasterView(View):
elif importer.allow_create:
return importer.create_object(key, host_data)
def executable_instance(self, instance):
"""
Returns boolean indicating whether or not the given instance
can be considered "executable". Returns ``True`` by default;
override as necessary.
"""
return True
def execute(self):
"""
Execute an object.

View file

@ -475,6 +475,13 @@ class UpgradeView(MasterView):
# key = '{}.execute'.format(self.get_grid_key())
# return SessionProgress(self.request, key, session_type='file')
def executable_instance(self, upgrade):
if upgrade.executed:
return False
if upgrade.status_code != self.enum.UPGRADE_STATUS_PENDING:
return False
return True
def execute_instance(self, upgrade, user, progress=None, **kwargs):
app = self.get_rattail_app()
session = app.get_session(upgrade)