feat(tracker-web): factory dropdown routes job to the selected factory's product
New Job form: pick a Factory (2 hardcoded for this machine) -> the job is submitted to that factory's product (submitJob productId override) and the dashboard view switches to it so the job is visible. Confirmation shows factory + product. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
c239abeec9
commit
e9c1714c13
@ -48,6 +48,13 @@ const FLEET_REPOS = [
|
||||
];
|
||||
const FLEET_BASE_BRANCH = 'main';
|
||||
|
||||
// Factories set up on this machine. Selecting one routes the job to that factory's
|
||||
// product (the factory polling that product claims it).
|
||||
const FLEET_FACTORIES = [
|
||||
{ id: 'mac-1', label: 'mac-1 — LysnrAI (autoship + PR)', productId: 'lysnrai' },
|
||||
{ id: 'mac-2', label: 'mac-2 — ChronoMind (human gate + PR)', productId: 'chronomind' },
|
||||
];
|
||||
|
||||
export default function FleetJobsPage() {
|
||||
const { token } = useAuth();
|
||||
const [jobs, setJobs] = useState<FleetJob[]>([]);
|
||||
@ -56,6 +63,7 @@ export default function FleetJobsPage() {
|
||||
|
||||
// New-job form state
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const [factoryId, setFactoryId] = useState(FLEET_FACTORIES[0].id);
|
||||
const [body, setBody] = useState('');
|
||||
const [priority, setPriority] = useState<'critical' | 'high' | 'medium' | 'low'>('high');
|
||||
const [caps, setCaps] = useState('build');
|
||||
@ -97,24 +105,32 @@ export default function FleetJobsPage() {
|
||||
.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,
|
||||
...(repo
|
||||
? {
|
||||
repo,
|
||||
baseBranch: FLEET_BASE_BRANCH,
|
||||
...(verifyCmd.trim() ? { verify: verifyCmd.trim() } : {}),
|
||||
...(autoMerge ? { autoMerge: true } : {}),
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
const factory = FLEET_FACTORIES.find(f => f.id === factoryId) ?? FLEET_FACTORIES[0];
|
||||
const { job } = await submitJob(
|
||||
{
|
||||
idempotencyKey: `ui-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
||||
bodyMd: body.trim(),
|
||||
priority,
|
||||
capabilities,
|
||||
...(repo
|
||||
? {
|
||||
repo,
|
||||
baseBranch: FLEET_BASE_BRANCH,
|
||||
...(verifyCmd.trim() ? { verify: verifyCmd.trim() } : {}),
|
||||
...(autoMerge ? { autoMerge: true } : {}),
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
factory.productId
|
||||
);
|
||||
// Route the dashboard view to the factory's product so the job is visible.
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem('tracker_selected_product', factory.productId);
|
||||
}
|
||||
setSubmitMsg({
|
||||
ok: true,
|
||||
text:
|
||||
`Submitted ${job.id} (stage: ${job.stage})` +
|
||||
`Submitted ${job.id} to ${factory.id} (${factory.productId}, stage: ${job.stage})` +
|
||||
(repo
|
||||
? ` — PR mode: ${repo}@${FLEET_BASE_BRANCH}${verifyCmd.trim() ? ' +verify' : ''}${autoMerge ? ' +auto-merge' : ''}`
|
||||
: ' — no PR (plain job)') +
|
||||
@ -128,7 +144,7 @@ export default function FleetJobsPage() {
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
}, [body, caps, priority, repo, verifyCmd, autoMerge, refresh]);
|
||||
}, [body, caps, priority, repo, verifyCmd, autoMerge, factoryId, refresh]);
|
||||
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
@ -147,6 +163,23 @@ export default function FleetJobsPage() {
|
||||
</button>
|
||||
{showForm && (
|
||||
<div className="space-y-3 border-t px-4 py-4">
|
||||
<div>
|
||||
<label htmlFor="job-factory" className="mb-1 block text-sm font-medium">
|
||||
Factory
|
||||
</label>
|
||||
<select
|
||||
id="job-factory"
|
||||
value={factoryId}
|
||||
onChange={e => setFactoryId(e.target.value)}
|
||||
className="rounded border bg-background px-2 py-1 text-sm"
|
||||
>
|
||||
{FLEET_FACTORIES.map(f => (
|
||||
<option key={f.id} value={f.id}>
|
||||
{f.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="job-body" className="mb-1 block text-sm font-medium">
|
||||
Task (markdown)
|
||||
|
||||
@ -207,9 +207,14 @@ export interface SubmitJobBody {
|
||||
autoMerge?: boolean;
|
||||
}
|
||||
|
||||
/** Submit a new fleet job. Returns the created job. */
|
||||
export async function submitJob(body: SubmitJobBody): Promise<{ job: FleetJob }> {
|
||||
return apiFetch(`/jobs`, { method: 'POST', body: JSON.stringify(body) });
|
||||
/** Submit a new fleet job. Optionally target a specific product (factory's product),
|
||||
* overriding the dashboard's selected product for this submission. */
|
||||
export async function submitJob(
|
||||
body: SubmitJobBody,
|
||||
productId?: string
|
||||
): Promise<{ job: FleetJob }> {
|
||||
const headers = productId ? { 'x-product-id': productId } : undefined;
|
||||
return apiFetch(`/jobs`, { method: 'POST', body: JSON.stringify(body), headers });
|
||||
}
|
||||
|
||||
/** WIP checkpoint a factory carries across lease re-assignments (server schema). */
|
||||
|
||||
Loading…
Reference in New Issue
Block a user