feat(render+admin): stop a render job (admin, any owner)
Build backend images / build content-svc (push) Failing after 1m0s
Build backend images / build file-svc (push) Failing after 1m3s
Build backend images / build gateway (push) Failing after 1m2s
Build backend images / build identity-svc (push) Failing after 1m20s
Build backend images / build notification-svc (push) Failing after 1m13s
Build backend images / build render-svc (push) Failing after 1m5s
Build backend images / build studio-svc (push) Failing after 1m0s

The render-queue cancel button used the owner-scoped /cancel (WHERE user_id=…),
so an admin couldn't stop another user's job. Added:
- render-svc: POST /v1/renders/:job_id/stop (admin-gated) → store.StopJob cancels
  any in-progress job regardless of owner and frees the assigned node
- admin: render-queue button now "توقف" → /api/admin/renders/{id}/stop (with confirm)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-03 01:39:33 +03:30
parent c7694a9bbf
commit 7f7feabb85
7 changed files with 53 additions and 6 deletions
@@ -0,0 +1,9 @@
import { type NextRequest } from "next/server";
import { adminProxy } from "@/app/api/admin/_adminProxy";
export const runtime = "nodejs";
interface Ctx { params: { jobId: string } }
export async function POST(req: NextRequest, { params }: Ctx) {
return adminProxy(req, `/v1/renders/${params.jobId}/stop`);
}
+6 -4
View File
@@ -48,10 +48,12 @@ export function RenderQueueTable({ jobs }: { jobs: V2RenderJob[] }) {
}
};
const cancelJob = async (jobId: string) => {
const stopJob = async (jobId: string) => {
if (!confirm("این رندر متوقف شود؟")) return;
setLoading((p) => ({ ...p, [jobId]: true }));
try {
await apiFetch(`/api/admin/renders/${jobId}/cancel`, { method: "POST" });
// Admin stop — cancels any user's job (not just the caller's) and frees the node.
await apiFetch(`/api/admin/renders/${jobId}/stop`, { method: "POST" });
router.refresh();
} finally {
setLoading((p) => ({ ...p, [jobId]: false }));
@@ -140,11 +142,11 @@ export function RenderQueueTable({ jobs }: { jobs: V2RenderJob[] }) {
)}
{canCancel && (
<button
onClick={() => cancelJob(job.id)}
onClick={() => stopJob(job.id)}
disabled={loading[job.id]}
className="rounded px-2.5 py-1 text-xs text-red-300 border border-red-500/30 hover:bg-red-500/10 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
>
{t("actionCancel")}
{t("actionStop")}
</button>
)}
</div>