mirror of
https://github.com/ostris/ai-toolkit.git
synced 2026-01-26 16:39:47 +00:00
Added controls to the jobs table
This commit is contained in:
32
ui/src/app/api/jobs/[jobID]/delete/route.ts
Normal file
32
ui/src/app/api/jobs/[jobID]/delete/route.ts
Normal file
@@ -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);
|
||||
}
|
||||
@@ -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 (
|
||||
<div>
|
||||
{canStart && (
|
||||
<Button
|
||||
onClick={async () => {
|
||||
if (!canStart) return;
|
||||
await startJob(row.id);
|
||||
refreshJobs();
|
||||
}}
|
||||
className={`ml-2 opacity-100`}
|
||||
>
|
||||
<Play />
|
||||
</Button>
|
||||
)}
|
||||
{canStop && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (!canStop) return;
|
||||
openConfirm({
|
||||
title: 'Stop Job',
|
||||
message: `Are you sure you want to stop the job "${row.name}"? You CAN resume later.`,
|
||||
type: 'info',
|
||||
confirmText: 'Stop',
|
||||
onConfirm: async () => {
|
||||
await stopJob(row.id);
|
||||
refreshJobs();
|
||||
},
|
||||
});
|
||||
}}
|
||||
className={`ml-2 opacity-100`}
|
||||
>
|
||||
<Pause />
|
||||
</Button>
|
||||
)}
|
||||
<Link href={`/jobs/${row.id}`} className="ml-2 text-gray-200 hover:text-gray-100 inline-block">
|
||||
<Eye />
|
||||
</Link>
|
||||
{canEdit && (
|
||||
<Link href={`/jobs/new?id=${row.id}`} className="ml-2 hover:text-gray-100 inline-block">
|
||||
<Pen />
|
||||
</Link>
|
||||
)}
|
||||
{canDelete && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (!canDelete) return;
|
||||
openConfirm({
|
||||
title: 'Delete Job',
|
||||
message: `Are you sure you want to delete the job "${row.name}"? This will also permanently remove it from your disk.`,
|
||||
type: 'warning',
|
||||
confirmText: 'Delete',
|
||||
onConfirm: async () => {
|
||||
await deleteJob(row.id);
|
||||
refreshJobs();
|
||||
},
|
||||
});
|
||||
}}
|
||||
className={`ml-2 opacity-100`}
|
||||
>
|
||||
<Trash2 />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return <UniversalTable columns={columns} rows={jobs} isLoading={isLoading} onRefresh={refreshJobs} />;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { JobConfig } from '@/types';
|
||||
import { Job } from '@prisma/client';
|
||||
|
||||
export const startJob = (jobID: string) => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
fetch(`/api/jobs/${jobID}/start`)
|
||||
@@ -27,3 +30,36 @@ export const stopJob = (jobID: string) => {
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteJob = (jobID: string) => {
|
||||
return new Promise<void>((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 };
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user