diff --git a/ui/src/app/api/jobs/[jobID]/delete/route.ts b/ui/src/app/api/jobs/[jobID]/delete/route.ts new file mode 100644 index 00000000..618e33f4 --- /dev/null +++ b/ui/src/app/api/jobs/[jobID]/delete/route.ts @@ -0,0 +1,32 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { PrismaClient } from '@prisma/client'; +import { getTrainingFolder } from '@/server/settings'; +import path from 'path'; +import fs from 'fs'; + +const prisma = new PrismaClient(); + +export async function GET(request: NextRequest, { params }: { params: { jobID: string } }) { + const { jobID } = await params; + + const job = await prisma.job.findUnique({ + where: { id: jobID }, + }); + + if (!job) { + return NextResponse.json({ error: 'Job not found' }, { status: 404 }); + } + + const trainingRoot = await getTrainingFolder(); + const trainingFolder = path.join(trainingRoot, job.name); + + if (fs.existsSync(trainingFolder)) { + fs.rmdirSync(trainingFolder, { recursive: true }); + } + + await prisma.job.delete({ + where: { id: jobID }, + }); + + return NextResponse.json(job); +} diff --git a/ui/src/components/JobsTable.tsx b/ui/src/components/JobsTable.tsx index 7949e732..5f3b4dc6 100644 --- a/ui/src/components/JobsTable.tsx +++ b/ui/src/components/JobsTable.tsx @@ -2,6 +2,10 @@ import useJobsList from '@/hooks/useJobsList'; import Link from 'next/link'; import UniversalTable, { TableColumn } from '@/components/UniversalTable'; import { JobConfig } from '@/types'; +import { Eye, Trash2, Pen, Play, Pause } from 'lucide-react'; +import { Button } from '@headlessui/react'; +import { openConfirm } from '@/components/ConfirmModal'; +import { startJob, stopJob, deleteJob, getAvaliableJobActions } from '@/utils/jobs'; interface JobsTableProps {} @@ -62,6 +66,78 @@ export default function JobsTable(props: JobsTableProps) { key: 'info', className: 'truncate max-w-xs', }, + { + title: 'Actions', + key: 'actions', + className: 'text-right', + render: row => { + const { canDelete, canEdit, canStop, canStart } = getAvaliableJobActions(row); + return ( +
+ {canStart && ( + + )} + {canStop && ( + + )} + + + + {canEdit && ( + + + + )} + {canDelete && ( + + )} +
+ ); + }, + }, ]; return ; diff --git a/ui/src/utils/jobs.ts b/ui/src/utils/jobs.ts index 883953a4..1e30da1d 100644 --- a/ui/src/utils/jobs.ts +++ b/ui/src/utils/jobs.ts @@ -1,3 +1,6 @@ +import { JobConfig } from '@/types'; +import { Job } from '@prisma/client'; + export const startJob = (jobID: string) => { return new Promise((resolve, reject) => { fetch(`/api/jobs/${jobID}/start`) @@ -27,3 +30,36 @@ export const stopJob = (jobID: string) => { }); }); }; + +export const deleteJob = (jobID: string) => { + return new Promise((resolve, reject) => { + fetch(`/api/jobs/${jobID}/delete`) + .then(res => res.json()) + .then(data => { + console.log('Job deleted:', data); + resolve(); + }) + .catch(error => { + console.error('Error deleting job:', error); + reject(error); + }); + }); +}; + +export const getJobConfig = (job: Job) => { + return JSON.parse(job.job_config) as JobConfig; +}; + +export const getAvaliableJobActions = (job: Job) => { + const jobConfig = getJobConfig(job); + const isStopping = job.stop && job.status === 'running'; + const canDelete = ['completed', 'stopped', 'error'].includes(job.status) && !isStopping; + const canEdit = ['completed', 'stopped', 'error'].includes(job.status) && !isStopping; + const canStop = job.status === 'running' && !isStopping; + let canStart = ['stopped', 'error'].includes(job.status) && !isStopping; + // can resume if more steps were added + if (job.status === 'completed' && jobConfig.config.process[0].train.steps > job.step && !isStopping) { + canStart = true; + } + return { canDelete, canEdit, canStop, canStart }; +};