Web: also let Vue Router track the active task
This basically does the same as 63ac7287321a101c3f601eeb151be73154ef7720 but then for tasks.
This commit is contained in:
parent
6b9d7dba6d
commit
cc10d3e4bb
@ -28,7 +28,8 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
_handleTaskActionPromise(promise, description) {
|
_handleTaskActionPromise(promise, description) {
|
||||||
const numTasks = this.tasks.numSelected;
|
// const numTasks = this.tasks.numSelected;
|
||||||
|
const numTasks = 1;
|
||||||
return promise
|
return promise
|
||||||
.then(() => {
|
.then(() => {
|
||||||
let message;
|
let message;
|
||||||
|
@ -16,9 +16,10 @@ import { useTasks } from '@/stores/tasks';
|
|||||||
import TaskActionsBar from '@/components/TaskActionsBar.vue'
|
import TaskActionsBar from '@/components/TaskActionsBar.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
emits: ["selectedTaskChange"],
|
emits: ["tableRowClicked"],
|
||||||
props: [
|
props: [
|
||||||
"jobID", // ID of the job of which the tasks are shown here.
|
"jobID", // ID of the job of which the tasks are shown here.
|
||||||
|
"taskID", // The active task.
|
||||||
],
|
],
|
||||||
components: {
|
components: {
|
||||||
TaskActionsBar,
|
TaskActionsBar,
|
||||||
@ -27,7 +28,11 @@ export default {
|
|||||||
const options = {
|
const options = {
|
||||||
// See pkg/api/flamenco-manager.yaml, schemas Task and TaskUpdate.
|
// See pkg/api/flamenco-manager.yaml, schemas Task and TaskUpdate.
|
||||||
columns: [
|
columns: [
|
||||||
{ formatter: "rowSelection", titleFormatter: "rowSelection", hozAlign: "center", headerHozAlign: "center", headerSort: false },
|
// { formatter: "rowSelection", titleFormatter: "rowSelection", hozAlign: "center", headerHozAlign: "center", headerSort: false },
|
||||||
|
{
|
||||||
|
title: "ID", field: "id", headerSort: false,
|
||||||
|
formatter: (cell) => cell.getData().id.substr(0, 8),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'Status', field: 'status', sorter: 'string',
|
title: 'Status', field: 'status', sorter: 'string',
|
||||||
formatter(cell, formatterParams) { // eslint-disable-line no-unused-vars
|
formatter(cell, formatterParams) { // eslint-disable-line no-unused-vars
|
||||||
@ -53,7 +58,7 @@ export default {
|
|||||||
{ column: "updated", dir: "desc" },
|
{ column: "updated", dir: "desc" },
|
||||||
],
|
],
|
||||||
data: [], // Will be filled via a Flamenco API request.
|
data: [], // Will be filled via a Flamenco API request.
|
||||||
selectable: 1, // Only allow a single row to be selected at a time.
|
selectable: false, // The active task is tracked by click events.
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
options: options,
|
options: options,
|
||||||
@ -65,16 +70,28 @@ export default {
|
|||||||
// tasksTableVue.processTaskUpdate({id: "ad0a5a00-5cb8-4e31-860a-8a405e75910e", status: "heyy", updated: DateTime.local().toISO(), previous_status: "uuuuh", name: "Updated manually"});
|
// tasksTableVue.processTaskUpdate({id: "ad0a5a00-5cb8-4e31-860a-8a405e75910e", status: "heyy", updated: DateTime.local().toISO(), previous_status: "uuuuh", name: "Updated manually"});
|
||||||
// tasksTableVue.processTaskUpdate({id: "ad0a5a00-5cb8-4e31-860a-8a405e75910e", status: "heyy", updated: DateTime.local().toISO()});
|
// tasksTableVue.processTaskUpdate({id: "ad0a5a00-5cb8-4e31-860a-8a405e75910e", status: "heyy", updated: DateTime.local().toISO()});
|
||||||
window.tasksTableVue = this;
|
window.tasksTableVue = this;
|
||||||
|
|
||||||
|
// Set the `rowFormatter` here (instead of with the rest of the options
|
||||||
|
// above) as it needs to refer to `this`, which isn't available in the
|
||||||
|
// `data` function.
|
||||||
|
this.options.rowFormatter = (row) => {
|
||||||
|
const data = row.getData();
|
||||||
|
const isActive = (data.id === this.taskID);
|
||||||
|
row.getElement().classList.toggle("active-row", isActive);
|
||||||
|
};
|
||||||
|
|
||||||
this.tabulator = new Tabulator('#flamenco_task_list', this.options);
|
this.tabulator = new Tabulator('#flamenco_task_list', this.options);
|
||||||
this.tabulator.on("rowSelected", this.onRowSelected);
|
this.tabulator.on("rowClick", this.onRowClick);
|
||||||
this.tabulator.on("rowDeselected", this.onRowDeselected);
|
|
||||||
this.tabulator.on("tableBuilt", this.fetchTasks);
|
this.tabulator.on("tableBuilt", this.fetchTasks);
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
jobID() {
|
jobID() {
|
||||||
this.onRowDeselected([]);
|
|
||||||
this.fetchTasks();
|
this.fetchTasks();
|
||||||
},
|
},
|
||||||
|
taskID(oldID, newID) {
|
||||||
|
this._reformatRow(oldID);
|
||||||
|
this._reformatRow(newID);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onReconnected() {
|
onReconnected() {
|
||||||
@ -103,7 +120,6 @@ export default {
|
|||||||
// "Down-cast" to TaskUpdate to only get those fields, just for debugging things:
|
// "Down-cast" to TaskUpdate to only get those fields, just for debugging things:
|
||||||
// let tasks = data.tasks.map((j) => API.TaskUpdate.constructFromObject(j));
|
// let tasks = data.tasks.map((j) => API.TaskUpdate.constructFromObject(j));
|
||||||
this.tabulator.setData(data.tasks);
|
this.tabulator.setData(data.tasks);
|
||||||
this._restoreRowSelection();
|
|
||||||
},
|
},
|
||||||
processTaskUpdate(taskUpdate) {
|
processTaskUpdate(taskUpdate) {
|
||||||
// updateData() will only overwrite properties that are actually set on
|
// updateData() will only overwrite properties that are actually set on
|
||||||
@ -112,27 +128,22 @@ export default {
|
|||||||
.then(this.sortData);
|
.then(this.sortData);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Selection handling.
|
onRowClick(event, row) {
|
||||||
onRowSelected(selectedRow) {
|
// Take a copy of the data, so that it's decoupled from the tabulator data
|
||||||
const selectedData = selectedRow.getData();
|
// store. There were some issues where navigating to another job would
|
||||||
this._storeRowSelection([selectedData]);
|
// overwrite the old job's ID, and this prevents that.
|
||||||
this.$emit("selectedTaskChange", selectedData);
|
const rowData = plain(row.getData());
|
||||||
},
|
this.$emit("tableRowClicked", rowData);
|
||||||
onRowDeselected(deselectedRow) {
|
|
||||||
this._storeRowSelection([]);
|
|
||||||
this.$emit("selectedTaskChange", null);
|
|
||||||
},
|
|
||||||
_storeRowSelection(selectedData) {
|
|
||||||
const selectedTaskIDs = selectedData.map((row) => row.id);
|
|
||||||
localStorage.setItem("selectedTaskIDs", selectedTaskIDs);
|
|
||||||
},
|
|
||||||
_restoreRowSelection() {
|
|
||||||
// const selectedTaskIDs = localStorage.getItem('selectedTaskIDs');
|
|
||||||
// if (!selectedTaskIDs) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// this.tabulator.selectRow(selectedTaskIDs);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_reformatRow(jobID) {
|
||||||
|
// Use tab.rowManager.findRow() instead of `tab.getRow()` as the latter
|
||||||
|
// logs a warning when the row cannot be found.
|
||||||
|
const row = this.tabulator.rowManager.findRow(jobID);
|
||||||
|
if (!row) return
|
||||||
|
if (row.reformat) row.reformat();
|
||||||
|
else if (row.reinitialize) row.reinitialize(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -9,7 +9,7 @@ const router = createRouter({
|
|||||||
component: () => import('../views/IndexView.vue'),
|
component: () => import('../views/IndexView.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/jobs/:jobID?',
|
path: '/jobs/:jobID?/:taskID?',
|
||||||
name: 'jobs',
|
name: 'jobs',
|
||||||
component: () => import('../views/JobsView.vue'),
|
component: () => import('../views/JobsView.vue'),
|
||||||
props: true,
|
props: true,
|
||||||
|
@ -10,8 +10,6 @@ const jobsAPI = new API.JobsApi(apiClient);
|
|||||||
// See https://pinia.vuejs.org/core-concepts/
|
// See https://pinia.vuejs.org/core-concepts/
|
||||||
export const useTasks = defineStore('tasks', {
|
export const useTasks = defineStore('tasks', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
/** @type {API.Task[]} */
|
|
||||||
selectedTasks: [],
|
|
||||||
/** @type {API.Task} */
|
/** @type {API.Task} */
|
||||||
activeTask: null,
|
activeTask: null,
|
||||||
/**
|
/**
|
||||||
@ -21,9 +19,6 @@ export const useTasks = defineStore('tasks', {
|
|||||||
activeTaskID: "",
|
activeTaskID: "",
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
numSelected() {
|
|
||||||
return this.selectedTasks.length;
|
|
||||||
},
|
|
||||||
canCancel() {
|
canCancel() {
|
||||||
return this._anyTaskWithStatus(["queued", "active", "soft-failed"])
|
return this._anyTaskWithStatus(["queued", "active", "soft-failed"])
|
||||||
},
|
},
|
||||||
@ -32,25 +27,20 @@ export const useTasks = defineStore('tasks', {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
// Selection of tasks.
|
setActiveTaskID(taskID) {
|
||||||
setSelectedTask(task) {
|
this.$patch({
|
||||||
|
activeTask: {id: taskID},
|
||||||
|
activeTaskID: taskID,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
setActiveTask(task) {
|
||||||
this.$patch({
|
this.$patch({
|
||||||
selectedTasks: [task],
|
|
||||||
activeTask: task,
|
activeTask: task,
|
||||||
activeTaskID: task.id,
|
activeTaskID: task.id,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
setSelectedTasks(tasks) {
|
|
||||||
const activeTask =tasks[tasks.length-1]; // Last-selected is the active one.
|
|
||||||
this.$patch({
|
|
||||||
selectedTasks: tasks,
|
|
||||||
activeTask: activeTask,
|
|
||||||
activeTaskID: activeTask.id,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
deselectAllTasks() {
|
deselectAllTasks() {
|
||||||
this.$patch({
|
this.$patch({
|
||||||
selectedTasks: [],
|
|
||||||
activeTask: null,
|
activeTask: null,
|
||||||
activeTaskID: "",
|
activeTaskID: "",
|
||||||
});
|
});
|
||||||
@ -75,7 +65,8 @@ export const useTasks = defineStore('tasks', {
|
|||||||
* @returns bool indicating whether there is a selected task with any of the given statuses.
|
* @returns bool indicating whether there is a selected task with any of the given statuses.
|
||||||
*/
|
*/
|
||||||
_anyTaskWithStatus(statuses) {
|
_anyTaskWithStatus(statuses) {
|
||||||
return this.selectedTasks.reduce((foundTask, task) => (foundTask || statuses.includes(task.status)), false);
|
return !!this.activeTask && !!this.activeTask.status && statuses.includes(this.activeTask.status);
|
||||||
|
// return this.selectedTasks.reduce((foundTask, task) => (foundTask || statuses.includes(task.status)), false);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -84,8 +75,12 @@ export const useTasks = defineStore('tasks', {
|
|||||||
* @returns a Promise for the API request.
|
* @returns a Promise for the API request.
|
||||||
*/
|
*/
|
||||||
_setTaskStatus(newStatus) {
|
_setTaskStatus(newStatus) {
|
||||||
|
if (!this.activeTaskID) {
|
||||||
|
console.warn(`_setTaskStatus(${newStatus}) impossible, no active task ID`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const statuschange = new API.TaskStatusChange(newStatus, "requested from web interface");
|
const statuschange = new API.TaskStatusChange(newStatus, "requested from web interface");
|
||||||
return jobsAPI.setTaskStatus(this.activeTask.id, statuschange);
|
return jobsAPI.setTaskStatus(this.activeTaskID, statuschange);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col col-2">
|
<div class="col col-2">
|
||||||
<job-details :jobData="jobs.activeJob" />
|
<job-details :jobData="jobs.activeJob" />
|
||||||
<tasks-table v-if="jobID" ref="tasksTable" :jobID="jobID" @selectedTaskChange="onSelectedTaskChanged" />
|
<tasks-table v-if="jobID" ref="tasksTable" :jobID="jobID" :taskID="taskID" @tableRowClicked="onTableTaskClicked" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col col-3">
|
<div class="col col-3">
|
||||||
<task-details :taskData="tasks.activeTask" />
|
<task-details :taskData="tasks.activeTask" />
|
||||||
@ -33,7 +33,7 @@ import UpdateListener from '@/components/UpdateListener.vue'
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'JobsView',
|
name: 'JobsView',
|
||||||
props: ["jobID"], // provided by Vue Router.
|
props: ["jobID", "taskID"], // provided by Vue Router.
|
||||||
components: {
|
components: {
|
||||||
JobsTable, JobDetails, TaskDetails, TasksTable, NotificationBar, UpdateListener,
|
JobsTable, JobDetails, TaskDetails, TasksTable, NotificationBar, UpdateListener,
|
||||||
},
|
},
|
||||||
@ -47,19 +47,26 @@ export default {
|
|||||||
mounted() {
|
mounted() {
|
||||||
window.jobsView = this;
|
window.jobsView = this;
|
||||||
this._fetchJob(this.jobID);
|
this._fetchJob(this.jobID);
|
||||||
|
this._fetchTask(this.taskID);
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
jobID(newJobID, oldJobID) {
|
jobID(newJobID, oldJobID) {
|
||||||
this._fetchJob(newJobID);
|
this._fetchJob(newJobID);
|
||||||
},
|
},
|
||||||
|
taskID(newTaskID, oldTaskID) {
|
||||||
|
this._fetchTask(newTaskID);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onTableJobClicked(rowData) {
|
onTableJobClicked(rowData) {
|
||||||
this._routeToJob(rowData.id);
|
this._routeToJob(rowData.id);
|
||||||
},
|
},
|
||||||
|
onTableTaskClicked(rowData) {
|
||||||
|
this._routeToTask(rowData.id);
|
||||||
|
},
|
||||||
|
|
||||||
onSelectedTaskChanged(taskSummary) {
|
onSelectedTaskChanged(taskSummary) {
|
||||||
if (!taskSummary) { // There is no selected task.
|
if (!taskSummary) { // There is no active task.
|
||||||
this.tasks.deselectAllTasks();
|
this.tasks.deselectAllTasks();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -67,7 +74,7 @@ export default {
|
|||||||
const jobsAPI = new API.JobsApi(apiClient);
|
const jobsAPI = new API.JobsApi(apiClient);
|
||||||
jobsAPI.fetchTask(taskSummary.id)
|
jobsAPI.fetchTask(taskSummary.id)
|
||||||
.then((task) => {
|
.then((task) => {
|
||||||
this.tasks.setSelectedTask(task);
|
this.tasks.setActiveTask(task);
|
||||||
// Forward the full task to Tabulator, so that that gets updated too.
|
// Forward the full task to Tabulator, so that that gets updated too.
|
||||||
if (this.$refs.tasksTable)
|
if (this.$refs.tasksTable)
|
||||||
this.$refs.tasksTable.processTaskUpdate(task);
|
this.$refs.tasksTable.processTaskUpdate(task);
|
||||||
@ -86,13 +93,33 @@ export default {
|
|||||||
this._fetchJob(this.jobID);
|
this._fetchJob(this.jobID);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event handler for SocketIO task updates.
|
||||||
|
* @param {API.SocketIOTaskUpdate} taskUpdate
|
||||||
|
*/
|
||||||
|
onSioTaskUpdate(taskUpdate) {
|
||||||
|
if (this.$refs.tasksTable)
|
||||||
|
this.$refs.tasksTable.processTaskUpdate(taskUpdate);
|
||||||
|
if (this.taskID == taskUpdate.id)
|
||||||
|
this._fetchTask(this.taskID);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} jobID job ID to navigate to, can be empty string for "no active job".
|
* @param {string} jobID job ID to navigate to, can be empty string for "no active job".
|
||||||
*/
|
*/
|
||||||
_routeToJob(jobID) {
|
_routeToJob(jobID) {
|
||||||
this.$router.push({ name: 'jobs', params: { jobID: jobID } });
|
const route = { name: 'jobs', params: { jobID: jobID } };
|
||||||
|
console.log("routing to job", route.params);
|
||||||
|
this.$router.push(route);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @param {string} taskID task ID to navigate to within this job, can be
|
||||||
|
* empty string for "no active task".
|
||||||
|
*/
|
||||||
|
_routeToTask(taskID) {
|
||||||
|
const route = { name: 'jobs', params: { jobID: this.jobID, taskID: taskID } };
|
||||||
|
console.log("routing to task", route.params);
|
||||||
|
this.$router.push(route);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -115,14 +142,22 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event handler for SocketIO task updates.
|
* Fetch task info and set the active task once it's received.
|
||||||
* @param {API.SocketIOTaskUpdate} taskUpdate
|
* @param {string} taskID task ID, can be empty string for "no task".
|
||||||
*/
|
*/
|
||||||
onSioTaskUpdate(taskUpdate) {
|
_fetchTask(taskID) {
|
||||||
if (this.$refs.tasksTable)
|
if (!taskID) {
|
||||||
this.$refs.tasksTable.processTaskUpdate(taskUpdate);
|
this.tasks.deselectAllTasks();
|
||||||
if (this.tasks.activeTaskID == taskUpdate.id)
|
return;
|
||||||
this.onSelectedTaskChanged(taskUpdate);
|
}
|
||||||
|
|
||||||
|
const jobsAPI = new API.JobsApi(apiClient);
|
||||||
|
return jobsAPI.fetchTask(taskID)
|
||||||
|
.then((task) => {
|
||||||
|
this.tasks.setActiveTask(task);
|
||||||
|
// Forward the full task to Tabulator, so that that gets updated too.
|
||||||
|
this.$refs.tasksTable.processTaskUpdate(task);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onChatMessage(message) {
|
onChatMessage(message) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user