From d84efb8c9a3f0a27d5cbcad8b20407893b526744 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Thu, 21 Apr 2022 16:53:20 +0200 Subject: [PATCH] Web: implement 'Cancel Job' button The button calls the 'setJobStatus' API endpoint to set the job status to `cancel-requested`. --- web/app/src/App.vue | 7 +++---- web/app/src/components/JobActionsBar.vue | 19 ++++++++++++++++++- web/app/src/stores/jobs.js | 22 ++++++++++++++++++++-- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/web/app/src/App.vue b/web/app/src/App.vue index 935d3d7d..f550e033 100644 --- a/web/app/src/App.vue +++ b/web/app/src/App.vue @@ -149,7 +149,7 @@ export default { --footer-height: 25px; --grid-gap: 4px; - --action-bar-height: 3em; + --action-bar-height: 2em; } html, @@ -232,12 +232,11 @@ footer { section.action-bar { height: var(--action-bar-height); + display: flex; + flex-direction: row; } section.action-bar button.action { - display: flex; - flex-direction: column; - align-items: center; padding: 0.1rem 0.75rem; border-radius: 0.3rem; border: thin solid white; diff --git a/web/app/src/components/JobActionsBar.vue b/web/app/src/components/JobActionsBar.vue index eadd51ad..903d08c8 100644 --- a/web/app/src/components/JobActionsBar.vue +++ b/web/app/src/components/JobActionsBar.vue @@ -1,6 +1,7 @@ @@ -10,7 +11,6 @@ import { useNotifs } from '@/stores/notifications'; export default { name: "JobActionsBar", - events: ["actionDone", "apiError"], data: () => ({ jobs: useJobs(), notifs: useNotifs(), @@ -29,6 +29,23 @@ export default { this.notifs.add(`Error: ${errorMsg}`); }) }, + onButtonCancel() { + const numJobs = this.jobs.numSelected; + this.jobs.cancelJobs() + .then(() => { + let message; + if (numJobs == 1) { + message = `Job marked for cancellation`; + } else { + message = `${numJobs} jobs marked for cancellation`; + } + this.notifs.add(message); + }) + .catch((error) => { + const errorMsg = JSON.stringify(error); // TODO: handle API errors better. + this.notifs.add(`Error: ${errorMsg}`); + }) + }, } } diff --git a/web/app/src/stores/jobs.js b/web/app/src/stores/jobs.js index fa326883..20138615 100644 --- a/web/app/src/stores/jobs.js +++ b/web/app/src/stores/jobs.js @@ -3,6 +3,9 @@ import { defineStore } from 'pinia' import * as urls from '@/urls' import * as API from '@/manager-api'; +const apiClient = new API.ApiClient(urls.api()); +const jobsAPI = new API.JobsApi(apiClient); + // 'use' prefix is idiomatic for Pinia stores. // See https://pinia.vuejs.org/core-concepts/ export const useJobs = defineStore('jobs', { @@ -17,7 +20,10 @@ export const useJobs = defineStore('jobs', { return this.selectedJobs.length; }, canDelete() { - return this._anyJobWithStatus(["queued", "paused", "failed", "completed"]) + return this._anyJobWithStatus(["queued", "paused", "failed", "completed", "canceled"]) + }, + canCancel() { + return this._anyJobWithStatus(["queued", "active"]) }, }, actions: { @@ -35,13 +41,25 @@ export const useJobs = defineStore('jobs', { this.activeJob = null; }, - // Actions on the selected jobs. + /** + * Actions on the selected jobs. + * + * All the action functions return a promise that resolves when the action has been performed. + * + * TODO: actually have these work on all selected jobs. For simplicity, the + * code now assumes that only the active job needs to be operated on. + */ deleteJobs() { const deletionPromise = new Promise( (resolutionFunc, rejectionFunc) => { rejectionFunc({code: 327, message: "deleting jobs is not implemented in JS yet"}); }); return deletionPromise; }, + cancelJobs() { + const statuschange = new API.JobStatusChange("cancel-requested", "requested from web interface"); + return jobsAPI.setJobStatus(this.activeJob.id, statuschange); + }, + // Internal methods. _anyJobWithStatus(statuses) { return this.selectedJobs.reduce((foundJob, job) => (foundJob || statuses.includes(job.status)), false);