From 176b778a1f7812c474ef071ef4afc3622a832d08 Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Sun, 31 May 2026 04:53:23 -0700 Subject: [PATCH] feat(tracker-web): submit fleet jobs from the dashboard Add a collapsible 'New Job' form on the fleet jobs page (task body, priority, capabilities) wired to a new fleet-client submitJob() -> POST /fleet/jobs, with inline success/error and auto-refresh. Also add 'ship' to the OperatorAction type for parity with the coordinator. The existing job-detail 'Ship' button already drives the human-gate testing -> shipped transition. Verified: tsc clean; tracker-web suite 230/230. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../src/app/dashboard/fleet/jobs/page.tsx | 109 +++++++++++++++++- .../tracker-web/src/lib/fleet-client.ts | 14 ++- 2 files changed, 121 insertions(+), 2 deletions(-) diff --git a/dashboards/tracker-web/src/app/dashboard/fleet/jobs/page.tsx b/dashboards/tracker-web/src/app/dashboard/fleet/jobs/page.tsx index fc8825ba..d6192338 100644 --- a/dashboards/tracker-web/src/app/dashboard/fleet/jobs/page.tsx +++ b/dashboards/tracker-web/src/app/dashboard/fleet/jobs/page.tsx @@ -3,8 +3,9 @@ import { useEffect, useState, useCallback } from 'react'; import Link from 'next/link'; import { PageHeader } from '@bytelyst/dashboard-components'; +import { Button } from '@/components/ui/Primitives'; import { useAuth } from '@/lib/auth-context'; -import { listJobs, type FleetJob } from '@/lib/fleet-client'; +import { listJobs, submitJob, type FleetJob } from '@/lib/fleet-client'; const STAGES = [ '', @@ -26,6 +27,14 @@ export default function FleetJobsPage() { const [stage, setStage] = useState(''); const [loading, setLoading] = useState(true); + // New-job form state + const [showForm, setShowForm] = useState(false); + const [body, setBody] = useState(''); + const [priority, setPriority] = useState<'low' | 'normal' | 'high'>('high'); + const [caps, setCaps] = useState('build'); + const [submitting, setSubmitting] = useState(false); + const [submitMsg, setSubmitMsg] = useState<{ ok: boolean; text: string } | null>(null); + const refresh = useCallback(async () => { try { const params: Record = { limit: '50' }; @@ -46,10 +55,108 @@ export default function FleetJobsPage() { return () => clearInterval(id); }, [token, refresh]); + const handleSubmit = useCallback(async () => { + if (!body.trim()) { + setSubmitMsg({ ok: false, text: 'Job body is required.' }); + return; + } + setSubmitting(true); + setSubmitMsg(null); + try { + const capabilities = caps + .split(',') + .map(c => c.trim()) + .filter(Boolean); + const { job } = await submitJob({ + idempotencyKey: `ui-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`, + bodyMd: body.trim(), + priority, + capabilities, + }); + setSubmitMsg({ ok: true, text: `Submitted ${job.id} (stage: ${job.stage}).` }); + setBody(''); + await refresh(); + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : 'Submit failed.'; + setSubmitMsg({ ok: false, text: msg }); + } finally { + setSubmitting(false); + } + }, [body, caps, priority, refresh]); + return (
+ {/* New job */} +
+ + {showForm && ( +
+
+ +