add book_count feature, migrate backoffice to server actions, fix healthcheck

This commit is contained in:
2026-03-05 21:29:48 +01:00
parent 3a96f6ba36
commit ef8a755a83
13 changed files with 222 additions and 79 deletions

View File

@@ -1,16 +1,43 @@
import { listJobs } from "../../lib/api";
import { revalidatePath } from "next/cache";
import { listJobs, fetchLibraries, rebuildIndex, cancelJob, IndexJobDto, LibraryDto } from "../../lib/api";
export const dynamic = "force-dynamic";
export default async function JobsPage() {
const jobs = await listJobs().catch(() => []);
const [jobs, libraries] = await Promise.all([
listJobs().catch(() => [] as IndexJobDto[]),
fetchLibraries().catch(() => [] as LibraryDto[])
]);
const libraryMap = new Map(libraries.map(l => [l.id, l.name]));
async function triggerRebuild(formData: FormData) {
"use server";
const libraryId = formData.get("library_id") as string;
await rebuildIndex(libraryId || undefined);
revalidatePath("/jobs");
}
async function cancelJobAction(formData: FormData) {
"use server";
const id = formData.get("id") as string;
await cancelJob(id);
revalidatePath("/jobs");
}
return (
<>
<h1>Index Jobs</h1>
<div className="card">
<form action="/jobs/rebuild" method="post">
<input name="library_id" placeholder="optional library UUID" />
<form action={triggerRebuild}>
<select name="library_id" defaultValue="">
<option value="">All libraries</option>
{libraries.map((lib) => (
<option key={lib.id} value={lib.id}>
{lib.name}
</option>
))}
</select>
<button type="submit">Queue Rebuild</button>
</form>
</div>
@@ -18,20 +45,34 @@ export default async function JobsPage() {
<thead>
<tr>
<th>ID</th>
<th>Library</th>
<th>Type</th>
<th>Status</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{jobs.map((job) => (
<tr key={job.id}>
<td>
<code>{job.id}</code>
<code>{job.id.slice(0, 8)}</code>
</td>
<td>{job.library_id ? libraryMap.get(job.library_id) || job.library_id.slice(0, 8) : "—"}</td>
<td>{job.type}</td>
<td>{job.status}</td>
<td>{job.created_at}</td>
<td>
<span className={`status-${job.status}`}>{job.status}</span>
{job.error_opt && <span className="error-hint" title={job.error_opt}>!</span>}
</td>
<td>{new Date(job.created_at).toLocaleString()}</td>
<td>
{job.status === "pending" || job.status === "running" ? (
<form action={cancelJobAction}>
<input type="hidden" name="id" value={job.id} />
<button type="submit" className="cancel-btn">Cancel</button>
</form>
) : null}
</td>
</tr>
))}
</tbody>

View File

@@ -1,17 +0,0 @@
import { revalidatePath } from "next/cache";
import { NextResponse } from "next/server";
import { apiFetch } from "../../../lib/api";
export async function POST(req: Request) {
const form = await req.formData();
const libraryId = String(form.get("library_id") || "").trim();
const body = libraryId ? { library_id: libraryId } : {};
await apiFetch("/index/rebuild", {
method: "POST",
body: JSON.stringify(body)
}).catch(() => null);
revalidatePath("/jobs");
return NextResponse.redirect(new URL("/jobs", req.url));
}