308 lines
7.9 KiB
Vue
308 lines
7.9 KiB
Vue
<template>
|
|
<div v-observe-visibility="visibilityChanged" class="uk-margin-remove uk-padding-remove">
|
|
<button
|
|
type="button"
|
|
:disabled="buttonDisabled"
|
|
class="uk-button uk-width-1-1 uk-position-relative"
|
|
:class="buttonClasses"
|
|
@click="handleClick"
|
|
>
|
|
<action-progress-bar :progress="progress" :task-status="taskStatus" :in-button="true" />
|
|
{{ buttonLabel }}
|
|
</button>
|
|
|
|
<action-status-modal
|
|
ref="statusModal"
|
|
:title="submitLabel"
|
|
:log="log"
|
|
:progress="progress"
|
|
:can-terminate="canTerminate"
|
|
:task-running="taskRunning"
|
|
:task-started="taskStarted"
|
|
:task-status="taskStatus"
|
|
@terminateTask="terminateTask"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import ActionProgressBar from "./actionProgressBar.vue";
|
|
import ActionStatusModal from "./actionStatusModal.vue";
|
|
import { markRaw } from "vue";
|
|
import { eventBus } from "../../eventBus.js";
|
|
|
|
export default {
|
|
name: "ActionButton",
|
|
components: {
|
|
ActionProgressBar: markRaw(ActionProgressBar),
|
|
ActionStatusModal: markRaw(ActionStatusModal)
|
|
},
|
|
|
|
props: {
|
|
action: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
thing: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
submitData: {
|
|
type: [Object, Array],
|
|
required: false,
|
|
default: () => ({}),
|
|
},
|
|
pollInterval: {
|
|
type: Number,
|
|
required: false,
|
|
default: 1,
|
|
},
|
|
submitLabel: {
|
|
type: String,
|
|
required: false,
|
|
default: "Submit",
|
|
},
|
|
canTerminate: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: true,
|
|
},
|
|
requiresConfirmation: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: false,
|
|
},
|
|
confirmationMessage: {
|
|
type: String,
|
|
required: false,
|
|
default: "Start task?",
|
|
},
|
|
buttonPrimary: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: true,
|
|
},
|
|
submitOnEvent: {
|
|
type: String,
|
|
required: false,
|
|
default: null,
|
|
},
|
|
modalProgress: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: false,
|
|
},
|
|
isDisabled: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: false,
|
|
},
|
|
},
|
|
|
|
data: function () {
|
|
return {
|
|
taskUrl: null,
|
|
progress: null,
|
|
taskStarted: false,
|
|
taskRunning: false,
|
|
log: [],
|
|
taskStatus: "",
|
|
};
|
|
},
|
|
|
|
computed: {
|
|
buttonDisabled() {
|
|
return this.isDisabled || (this.taskRunning && !this.canTerminate);
|
|
},
|
|
buttonLabel() {
|
|
return this.taskRunning && this.canTerminate ? "Cancel" : this.submitLabel;
|
|
},
|
|
buttonClasses() {
|
|
const classes = [];
|
|
if (this.buttonDisabled) {
|
|
classes.push("uk-button-disabled");
|
|
}
|
|
if (this.taskRunning && this.canTerminate) {
|
|
classes.push("uk-button-danger");
|
|
} else if (this.buttonPrimary) {
|
|
classes.push("uk-button-primary");
|
|
} else {
|
|
classes.push("uk-button-default");
|
|
}
|
|
return classes.join(" ");
|
|
},
|
|
},
|
|
|
|
watch: {
|
|
progress(newval) {
|
|
eventBus.emit("beforeUpdate:progress", newval);
|
|
},
|
|
taskStarted(newval) {
|
|
eventBus.emit("beforeUpdate:taskStarted", newval);
|
|
},
|
|
taskRunning(newval) {
|
|
eventBus.emit("beforeUpdate:taskRunning", newval);
|
|
},
|
|
log(newval) {
|
|
eventBus.emit("beforeUpdate:log", newval);
|
|
},
|
|
taskStatus(newval) {
|
|
eventBus.emit("beforeUpdate:taskStatus", newval);
|
|
},
|
|
},
|
|
|
|
mounted() {
|
|
// Check for already running tasks
|
|
if (this.taskStarted != true) {
|
|
this.checkExistingTasks();
|
|
}
|
|
// A global signal listener to perform the action
|
|
if (this.submitOnEvent) {
|
|
eventBus.on(this.submitOnEvent, () => {
|
|
if (this.isDisabled) return;
|
|
// Bootstrap task if button is not disabled.
|
|
this.bootstrapTask();
|
|
});
|
|
}
|
|
},
|
|
|
|
beforeUnmount() {
|
|
if (this.submitOnEvent) {
|
|
eventBus.off(this.submitOnEvent, () => {
|
|
if (this.isDisabled) return;
|
|
// Bootstrap task if button is not disabled.
|
|
this.bootstrapTask();
|
|
});
|
|
}
|
|
},
|
|
|
|
methods: {
|
|
handleClick() {
|
|
if (this.taskStarted) {
|
|
this.terminateTask();
|
|
} else {
|
|
this.bootstrapTask();
|
|
}
|
|
},
|
|
visibilityChanged(isVisible) {
|
|
if (isVisible && this.taskStarted != true) {
|
|
this.checkExistingTasks();
|
|
}
|
|
},
|
|
|
|
/* Check if an existing task had already started when this mounts.
|
|
*
|
|
* This is called on mounted.
|
|
*
|
|
* It will emit taskStarted if it finds an ongoing task to allow parent components
|
|
* to act as expected if the task is started. It will then poll the action.
|
|
*
|
|
*/
|
|
async checkExistingTasks() {
|
|
let response = await this.findOngoingActions(this.thing, this.action);
|
|
// Exit if response is null, due to an error.
|
|
if (response == null) return;
|
|
// Check for a task that is ongoing.
|
|
// We can't handle multiple tasks ongoing, so this picks the first.
|
|
const ongoingTask = response.data.find((t) => ["pending", "running"].includes(t.status));
|
|
if (ongoingTask) {
|
|
// There is a started task
|
|
this.taskStarted = true;
|
|
eventBus.emit("taskStarted");
|
|
// Find its URL
|
|
const taskUrl = ongoingTask.links.find((t) => t.rel == "self").href;
|
|
this.startPollingTask(ongoingTask.id, taskUrl);
|
|
}
|
|
},
|
|
|
|
bootstrapTask: function () {
|
|
// Starts the process of creating a new Actiont ask
|
|
if (this.requiresConfirmation) {
|
|
this.modalConfirm(this.confirmationMessage).then(
|
|
() => {
|
|
this.startTask();
|
|
},
|
|
() => {},
|
|
);
|
|
} else {
|
|
this.startTask();
|
|
}
|
|
},
|
|
|
|
async startTask() {
|
|
// Starts a new Action task
|
|
eventBus.emit("submit", this.submitData);
|
|
// Send a request to start a task
|
|
this.taskStarted = true;
|
|
eventBus.emit("taskStarted");
|
|
let response;
|
|
try {
|
|
response = await this.invokeAction(
|
|
this.thing,
|
|
this.action,
|
|
this.submitData,
|
|
false, // Stop invokeAction handling the error.
|
|
);
|
|
} catch (error) {
|
|
eventBus.emit("error", error);
|
|
this.onTaskEnd();
|
|
return;
|
|
}
|
|
if (this.modalProgress) {
|
|
this.$refs.statusModal.show();
|
|
}
|
|
// This just starts the polling. No need to await it.
|
|
this.startPollingTask(response.data.id, response.data.href);
|
|
},
|
|
|
|
async startPollingTask(taskId, taskUrl) {
|
|
// Return if taskRunning already set.
|
|
if (this.taskRunning) return;
|
|
// Starts polling an existing Action task
|
|
this.taskUrl = taskUrl;
|
|
// Start the store polling TaskId for success
|
|
this.taskRunning = true;
|
|
eventBus.emit("taskRunning", taskId);
|
|
this.pollUntilComplete(
|
|
taskUrl,
|
|
this.onPollingResponse,
|
|
this.onTaskEnd, // Method to run after task (even if error)
|
|
500, // Interval
|
|
);
|
|
},
|
|
|
|
onTaskEnd: function (response) {
|
|
if (response) {
|
|
this.taskStatus = response.data.status;
|
|
this.log = response.data.log;
|
|
if (response.data.status == "completed") {
|
|
eventBus.emit("response", response.data);
|
|
eventBus.emit("completed", response.data.output);
|
|
} else if (response.data.status == "cancelled") {
|
|
eventBus.emit("cancelled", response.data);
|
|
this.modalNotify(`The action '${this.submitLabel}' was cancelled.`);
|
|
}
|
|
}
|
|
this.taskUrl = null;
|
|
this.taskRunning = false;
|
|
this.taskStarted = false;
|
|
eventBus.emit("finished");
|
|
},
|
|
|
|
onPollingResponse(response) {
|
|
// While the task is still running, we update progress/log,
|
|
// and schedule another poll
|
|
var result = response.data.status;
|
|
this.taskStatus = result;
|
|
this.progress = response.data.progress;
|
|
this.log = response.data.log;
|
|
},
|
|
|
|
terminateTask: function () {
|
|
if (this.taskUrl) {
|
|
this.terminateAction(this.taskUrl);
|
|
}
|
|
},
|
|
},
|
|
};
|
|
</script>
|