swarm-ui: fill create-agent's empty column with an explanatory panel

mara's follow-up on the field-alignment fix: narrowing the form panel
left a lot of bare space next to it, and asked for something that
fills it while helping a new user understand what the page does.

Adds a second panel beside the form (stacks under it on a narrow
viewport) explaining the job chain '/api/agents' actually queues:
an authelia identity, then a forge config repo — no container exists
yet after this page, and deploying one onto the chosen hive is a
separate step that isn't wired up server-side.
This commit is contained in:
iris 2026-08-19 14:28:38 +02:00 committed by mara
commit fc65811d9f
2 changed files with 99 additions and 54 deletions

View file

@ -1,21 +1,42 @@
/* <CreateAgentPage> the create-agent form. Input/button chrome now
comes from the shared `ui/` form kit (`TextField`/`SelectField`/
`Button`); this file only owns the page's own layout + copy. Column,
not row: with a second field (hive) added, a row layout put fields of
different natural widths on one baseline and looked misaligned
mara: "make it a col". Reuses the same base16 slots (../theme.css)
every other component draws from.
not row inside the form itself: with a second field (hive) added, a
row layout put fields of different natural widths on one baseline and
looked misaligned mara: "make it a col". Reuses the same base16
slots (../theme.css) every other component draws from.
`.create-agent-page` caps the panel's own width: `Panel` has no width
opinion of its own, so left unconstrained it filled `.shell-body`'s
full 60em column a lot of bare panel to the right of a ~16em-wide
form, the "weird empty space" mara flagged alongside the field
misalignment. 24em roughly matches the fields' own 16em cap
(../ui/form-field/FormField.css) plus the panel's 1em body padding on
each side and a little breathing room, so the panel reads as sized to
its content instead of to the page column. */
`.create-agent-page` caps the whole layout's width: `Panel` has no
width opinion of its own, so left unconstrained a single form panel
filled `.shell-body`'s full 60em column a lot of bare panel to the
right of a ~16em-wide form, the "weird empty space" mara flagged
alongside the original field misalignment. The follow-up ask was to
fill that space with something that explains the page rather than
just narrowing it further `.create-agent-layout` puts the form
beside a second, explanatory panel instead. `.create-agent-form-col`
keeps the same ~24em cap the single-panel version had (fields' own
16em cap, ../ui/form-field/FormField.css, plus body padding and a
little breathing room); `.create-agent-info-col` takes the rest of
the row and wraps under the form on a narrow viewport (`flex-wrap`,
no separate media query needed). */
.create-agent-page {
max-width: 24em;
max-width: 44em;
}
.create-agent-layout {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
gap: 1.5em;
}
.create-agent-form-col {
flex: 0 1 24em;
}
.create-agent-info-col {
flex: 1 1 16em;
}
.create-agent-info-glyph {
margin: 0 0 0.5em;
font-size: 2em;
}
.create-agent-intro {
margin: 0 0 1.5em;

View file

@ -126,47 +126,71 @@ export function CreateAgentPage() {
return (
<div class="create-agent-page">
<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>
{hivesError && <ApiErrorPanel context="failed to load the hive list" problem={hivesError} />}
<form class="create-agent-form" onSubmit={submit}>
<TextField
id="agent-name"
label="agent name"
value={name}
pattern={NAME_PATTERN}
title="1-63 chars: lowercase letters, digits, hyphens"
required
onInput={setName}
/>
<SelectField
id="agent-hive"
label="hive"
value={hive}
onChange={setHive}
options={hiveOptions}
required
disabled={!hivesReady}
placeholder={
!hives ? 'loading…' : hives.length === 0 ? 'no hives configured' : 'select a hive'
}
/>
<Button variant="primary" type="submit" disabled={result.status === 'submitting' || !hivesReady}>
{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' && (
<ApiErrorPanel context="failed to queue" problem={result.problem} />
)}
</Panel>
<div class="create-agent-layout">
<div class="create-agent-form-col">
<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>
{hivesError && (
<ApiErrorPanel context="failed to load the hive list" problem={hivesError} />
)}
<form class="create-agent-form" onSubmit={submit}>
<TextField
id="agent-name"
label="agent name"
value={name}
pattern={NAME_PATTERN}
title="1-63 chars: lowercase letters, digits, hyphens"
required
onInput={setName}
/>
<SelectField
id="agent-hive"
label="hive"
value={hive}
onChange={setHive}
options={hiveOptions}
required
disabled={!hivesReady}
placeholder={
!hives ? 'loading…' : hives.length === 0 ? 'no hives configured' : 'select a hive'
}
/>
<Button
variant="primary"
type="submit"
disabled={result.status === 'submitting' || !hivesReady}
>
{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' && (
<ApiErrorPanel context="failed to queue" problem={result.problem} />
)}
</Panel>
</div>
<div class="create-agent-info-col">
<Panel title="what this creates">
<p class="create-agent-info-glyph" aria-hidden="true">
🪪
</p>
<p>
This queues a small chain of jobs, not a running agent: first an authelia identity
at the swarm level, then a config repo on the forge with the operator added as a
collaborator. It reserves everything an agent needs before any container exists
actually deploying one onto the hive you pick above is a separate step that isn't
wired up yet.
</p>
</Panel>
</div>
</div>
</div>
);
}