mara: the route should reflect creating an agent, and stay separate from a future agent list page. Renamed AgentsPage -> CreateAgentPage (file, component, css classes) and moved the route from /agents to /create-agent - flat, not /agents/new, since index.html's relative asset links only resolve correctly one path segment deep (filed separately as a real bug, not fixed here). Leaves the bare /agents path free for a future roster page.
105 lines
4.1 KiB
TypeScript
105 lines
4.1 KiB
TypeScript
// <CreateAgentPage> — minimal agent creation form, POSTs to
|
|
// swarm-controller's `POST /api/agents`. That endpoint only queues a
|
|
// `CreateIdentity` job and returns its node id — the identity isn't
|
|
// guaranteed to exist yet when this page gets a response — so this
|
|
// page shows the queued confirmation and links to the Jobs page to
|
|
// watch it settle, rather than duplicating JobqGraph's per-node
|
|
// polling here. Scope matches the originating issue exactly: name
|
|
// field only, no forge/deploy options (those aren't wired server-side
|
|
// yet either).
|
|
//
|
|
// Lives at `/create-agent`, not `/agents` — a future agent *roster*
|
|
// (listing existing agents) is the natural owner of the bare `/agents`
|
|
// path, and this creation form is a distinct action from that list,
|
|
// not a variant of it. Flat, not `/agents/new`: `index.html`'s asset
|
|
// links are relative (`static/main.js`), which only resolve correctly
|
|
// one path segment deep — a real bug, filed separately rather than
|
|
// fixed here, but reason enough to avoid a nested route today.
|
|
//
|
|
// First real form in this package — no shared form kit exists yet, so
|
|
// the input/button styling below is scoped to `CreateAgentPage.css`
|
|
// rather than promoted into `ui/`. Promote the day a second page needs
|
|
// one, same "don't build ahead of a second caller" rule `Panel`'s own
|
|
// comment states.
|
|
import { useState } from 'preact/hooks';
|
|
import { Link } from 'wouter-preact';
|
|
import { Panel } from '../ui/panel/Panel.js';
|
|
import './CreateAgentPage.css';
|
|
|
|
interface CreateAgentResponse {
|
|
node_id: number;
|
|
}
|
|
|
|
type SubmitState =
|
|
| { status: 'idle' }
|
|
| { status: 'submitting' }
|
|
| { status: 'done'; nodeId: number }
|
|
| { status: 'error'; message: string };
|
|
|
|
// Client-side mirror of `hive_types::Ident` (1-63 chars of `[a-z0-9-]`) —
|
|
// a `pattern` hint only, not a substitute for the server's own
|
|
// validation; a name this rejects would fail server-side anyway, so
|
|
// catching it before a round-trip is a pure UX win, not a new gate.
|
|
const NAME_PATTERN = '[a-z0-9-]{1,63}';
|
|
|
|
export function CreateAgentPage() {
|
|
const [name, setName] = useState('');
|
|
const [result, setResult] = useState<SubmitState>({ status: 'idle' });
|
|
|
|
async function submit(e: Event) {
|
|
e.preventDefault();
|
|
setResult({ status: 'submitting' });
|
|
try {
|
|
const r = await fetch('/api/agents', {
|
|
method: 'POST',
|
|
headers: { 'content-type': 'application/json' },
|
|
body: JSON.stringify({ name }),
|
|
});
|
|
if (!r.ok) throw new Error((await r.text()) || `http ${r.status}`);
|
|
const data = (await r.json()) as CreateAgentResponse;
|
|
setResult({ status: 'done', nodeId: data.node_id });
|
|
setName('');
|
|
} catch (err) {
|
|
setResult({ status: 'error', message: String(err) });
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Panel title="create agent">
|
|
<p class="create-agent-intro">
|
|
Create a new agent's swarm-level identity. This only queues the job — check{' '}
|
|
<Link href="/jobs">jobs</Link> to watch it settle.
|
|
</p>
|
|
<form class="create-agent-form" onSubmit={submit}>
|
|
<div class="create-agent-field">
|
|
<label class="create-agent-label" for="agent-name">
|
|
agent name
|
|
</label>
|
|
<input
|
|
id="agent-name"
|
|
class="create-agent-input"
|
|
type="text"
|
|
value={name}
|
|
pattern={NAME_PATTERN}
|
|
title="1-63 chars: lowercase letters, digits, hyphens"
|
|
required
|
|
onInput={(e) => setName(e.currentTarget.value)}
|
|
/>
|
|
</div>
|
|
<button class="create-agent-submit" type="submit" disabled={result.status === 'submitting'}>
|
|
{result.status === 'submitting' ? 'creating…' : 'create'}
|
|
</button>
|
|
</form>
|
|
{result.status === 'done' && (
|
|
<p class="create-agent-result create-agent-result-ok">
|
|
queued as job node #{result.nodeId} — <Link href="/jobs">watch it in jobs</Link>
|
|
</p>
|
|
)}
|
|
{result.status === 'error' && (
|
|
<p class="create-agent-result create-agent-result-error">
|
|
failed to queue: {result.message}
|
|
</p>
|
|
)}
|
|
</Panel>
|
|
);
|
|
}
|