Add Cloud Console cancel action and cancelled status
- Widen TaskStatus to include "cancelled"; add it to TasksView's STATUSES filter dropdown. - Add TaskCancellationResponse type and cancelTask(taskId) to api.ts. - Add a Cancel button to TasksView's task detail panel, gated on tasks:submit and a non-terminal task status; updates the displayed status on success and surfaces errors via the existing error path. - Extract the cancellability rule into a pure taskCancellation.ts module (mirroring taskProgress.ts/plannerHistory.ts) with unit tests, since the project has no Vue component-mounting test setup. Task 6/9 of task-cancellation change.
This commit is contained in:
@@ -3,6 +3,7 @@ import { computed, onMounted, ref, watch } from "vue";
|
||||
import { LoaderCircle, RefreshCw } from "@lucide/vue";
|
||||
import {
|
||||
CloudApiError,
|
||||
cancelTask,
|
||||
getTaskAttempts,
|
||||
getTaskPlannerDecisions,
|
||||
listTasks,
|
||||
@@ -20,6 +21,7 @@ import type {
|
||||
PlannerDecisionItem,
|
||||
} from "../types";
|
||||
import { formatTaskProgress } from "../taskProgress";
|
||||
import { canCancelTask } from "../taskCancellation";
|
||||
import { computePlannerHistoryState } from "../plannerHistory";
|
||||
|
||||
const props = defineProps<{ canSubmit: boolean }>();
|
||||
@@ -30,6 +32,7 @@ const STATUSES: TaskStatus[] = [
|
||||
"dispatched",
|
||||
"done",
|
||||
"failed",
|
||||
"cancelled",
|
||||
];
|
||||
|
||||
const statusFilter = ref<TaskStatus | "">("");
|
||||
@@ -55,6 +58,7 @@ const devices = ref<DeviceRecord[]>([]);
|
||||
const plannerDecisions = ref<PlannerDecisionItem[]>([]);
|
||||
const plannerLoading = ref(false);
|
||||
const plannerError = ref("");
|
||||
const cancelling = ref(false);
|
||||
const availableDevices = computed(() =>
|
||||
devices.value.filter((device) => device.host_id === submitHostId.value),
|
||||
);
|
||||
@@ -185,6 +189,21 @@ async function selectTask(task: TaskListItem) {
|
||||
}
|
||||
}
|
||||
|
||||
async function cancelSelectedTask() {
|
||||
if (!selectedTask.value) return;
|
||||
cancelling.value = true;
|
||||
errorMessage.value = "";
|
||||
try {
|
||||
const response = await cancelTask(selectedTask.value.id);
|
||||
selectedTask.value = { ...selectedTask.value, status: response.status };
|
||||
await refresh();
|
||||
} catch (err) {
|
||||
handleError(err, "failed to cancel task");
|
||||
} finally {
|
||||
cancelling.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleError(err: unknown, fallback: string) {
|
||||
if (err instanceof CloudApiError) {
|
||||
errorMessage.value = err.message;
|
||||
@@ -257,6 +276,11 @@ const selectedTaskProgress = computed(() =>
|
||||
selectedTask.value ? formatTaskProgress(selectedTask.value) : null,
|
||||
);
|
||||
|
||||
const canCancelSelectedTask = computed(
|
||||
() =>
|
||||
!!selectedTask.value && canCancelTask(selectedTask.value.status, props.canSubmit),
|
||||
);
|
||||
|
||||
const selectedHostTransport = computed<"direct" | "cloud" | null>(() => {
|
||||
if (!selectedTask.value?.assigned_host_id) return null;
|
||||
const host = hosts.value.find(
|
||||
@@ -417,6 +441,13 @@ function formatArguments(args: Record<string, unknown>): string {
|
||||
<h2>
|
||||
Task <code>{{ selectedTask.id.slice(0, 8) }}</code>
|
||||
</h2>
|
||||
<button
|
||||
v-if="canCancelSelectedTask"
|
||||
:disabled="cancelling"
|
||||
@click="cancelSelectedTask"
|
||||
>
|
||||
{{ cancelling ? "Cancelling…" : "Cancel" }}
|
||||
</button>
|
||||
<button @click="clearSelection">Back to list</button>
|
||||
</div>
|
||||
<p class="muted">
|
||||
|
||||
Reference in New Issue
Block a user