feat(TRUEREF-0020): add job status page with pause/resume/cancel controls
- Extend indexing_jobs schema to support 'paused' and 'cancelled' status
- Add JobQueue methods: pauseJob(), resumeJob(), cancelJob()
- Create POST /api/v1/jobs/[id]/{pause,resume,cancel} endpoints
- Implement /admin/jobs page with auto-refresh (3s polling)
- Add JobStatusBadge component with color-coded status display
- Action buttons appear contextually based on job status
- Optimistic UI updates with error handling
- All 477 existing tests pass, no regressions
This commit is contained in:
45
src/routes/api/v1/jobs/[id]/cancel/+server.ts
Normal file
45
src/routes/api/v1/jobs/[id]/cancel/+server.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* POST /api/v1/jobs/:id/cancel — cancel a job.
|
||||
*/
|
||||
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getClient } from '$lib/server/db/client.js';
|
||||
import { IndexingJobMapper } from '$lib/server/mappers/indexing-job.mapper.js';
|
||||
import { JobQueue } from '$lib/server/pipeline/job-queue.js';
|
||||
import { handleServiceError, NotFoundError, InvalidInputError } from '$lib/server/utils/validation.js';
|
||||
|
||||
export const POST: RequestHandler = ({ params }) => {
|
||||
try {
|
||||
const db = getClient();
|
||||
const queue = new JobQueue(db);
|
||||
|
||||
const job = queue.getJob(params.id);
|
||||
if (!job) throw new NotFoundError(`Job ${params.id} not found`);
|
||||
|
||||
const success = queue.cancelJob(params.id);
|
||||
if (!success) {
|
||||
throw new InvalidInputError(
|
||||
`Cannot cancel job ${params.id} - job is already done or failed`
|
||||
);
|
||||
}
|
||||
|
||||
// Fetch updated job
|
||||
const updated = queue.getJob(params.id)!;
|
||||
|
||||
return json({ success: true, job: IndexingJobMapper.toDto(updated) });
|
||||
} catch (err) {
|
||||
return handleServiceError(err);
|
||||
}
|
||||
};
|
||||
|
||||
export const OPTIONS: RequestHandler = () => {
|
||||
return new Response(null, {
|
||||
status: 204,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
|
||||
}
|
||||
});
|
||||
};
|
||||
45
src/routes/api/v1/jobs/[id]/pause/+server.ts
Normal file
45
src/routes/api/v1/jobs/[id]/pause/+server.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* POST /api/v1/jobs/:id/pause — pause a running or queued job.
|
||||
*/
|
||||
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getClient } from '$lib/server/db/client.js';
|
||||
import { IndexingJobMapper } from '$lib/server/mappers/indexing-job.mapper.js';
|
||||
import { JobQueue } from '$lib/server/pipeline/job-queue.js';
|
||||
import { handleServiceError, NotFoundError, InvalidInputError } from '$lib/server/utils/validation.js';
|
||||
|
||||
export const POST: RequestHandler = ({ params }) => {
|
||||
try {
|
||||
const db = getClient();
|
||||
const queue = new JobQueue(db);
|
||||
|
||||
const job = queue.getJob(params.id);
|
||||
if (!job) throw new NotFoundError(`Job ${params.id} not found`);
|
||||
|
||||
const success = queue.pauseJob(params.id);
|
||||
if (!success) {
|
||||
throw new InvalidInputError(
|
||||
`Cannot pause job ${params.id} - only queued or running jobs can be paused`
|
||||
);
|
||||
}
|
||||
|
||||
// Fetch updated job
|
||||
const updated = queue.getJob(params.id)!;
|
||||
|
||||
return json({ success: true, job: IndexingJobMapper.toDto(updated) });
|
||||
} catch (err) {
|
||||
return handleServiceError(err);
|
||||
}
|
||||
};
|
||||
|
||||
export const OPTIONS: RequestHandler = () => {
|
||||
return new Response(null, {
|
||||
status: 204,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
|
||||
}
|
||||
});
|
||||
};
|
||||
43
src/routes/api/v1/jobs/[id]/resume/+server.ts
Normal file
43
src/routes/api/v1/jobs/[id]/resume/+server.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* POST /api/v1/jobs/:id/resume — resume a paused job.
|
||||
*/
|
||||
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getClient } from '$lib/server/db/client.js';
|
||||
import { IndexingJobMapper } from '$lib/server/mappers/indexing-job.mapper.js';
|
||||
import { JobQueue } from '$lib/server/pipeline/job-queue.js';
|
||||
import { handleServiceError, NotFoundError, InvalidInputError } from '$lib/server/utils/validation.js';
|
||||
|
||||
export const POST: RequestHandler = ({ params }) => {
|
||||
try {
|
||||
const db = getClient();
|
||||
const queue = new JobQueue(db);
|
||||
|
||||
const job = queue.getJob(params.id);
|
||||
if (!job) throw new NotFoundError(`Job ${params.id} not found`);
|
||||
|
||||
const success = queue.resumeJob(params.id);
|
||||
if (!success) {
|
||||
throw new InvalidInputError(`Cannot resume job ${params.id} - only paused jobs can be resumed`);
|
||||
}
|
||||
|
||||
// Fetch updated job
|
||||
const updated = queue.getJob(params.id)!;
|
||||
|
||||
return json({ success: true, job: IndexingJobMapper.toDto(updated) });
|
||||
} catch (err) {
|
||||
return handleServiceError(err);
|
||||
}
|
||||
};
|
||||
|
||||
export const OPTIONS: RequestHandler = () => {
|
||||
return new Response(null, {
|
||||
status: 204,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
|
||||
}
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user