swarm-ui: split pages/agents into two pages and the agent components
pages/agents held the roster page, the terminal page and every component under them. It keeps the two pages; the components move to src/agent, by concern: roster (card, wanted menu, actions), detail (panel body, dialog, inference, linked accounts, link menu) and terminal (preview, input, subagents, stream hooks, favicon), with the shared row type and icon at the top. Moves and import rewrites only.
This commit is contained in:
parent
fd6953ed7c
commit
74e588415b
1 changed files with 28 additions and 25 deletions
124
frontend/packages/swarm-ui/src/agent/roster/WantedMenu.tsx
Normal file
124
frontend/packages/swarm-ui/src/agent/roster/WantedMenu.tsx
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
// <WantedMenu> — the "wanted" state control: one badge showing the
|
||||
// current declaration, opening a `Dropdown` with the four explicit
|
||||
// states — replaces the old toggle-badge-plus-separate-destroy-badge
|
||||
// pair. Explicit options also fix a real bug the toggle had: for an
|
||||
// undeclared row, the toggle inferred a target as "the opposite of
|
||||
// `snapshot.running`", so a click on an undeclared-but-running agent
|
||||
// silently declared it *offline* rather than making its real state
|
||||
// explicit — a dropdown just lets "up" be picked directly regardless
|
||||
// of what's inferred, no flip-to-the-opposite-of-a-guess involved
|
||||
// (mara: "when no state is declared, i want to set it to online").
|
||||
// Presentational only, same split as `StatusChips`'s
|
||||
// `StatusMenu`: the caller owns what each selection actually does.
|
||||
//
|
||||
// "paused" has no separate resume option here — selecting "up" from a
|
||||
// paused row is the resume, same PUT either way (`AgentsPage`'s
|
||||
// `declareState`, `hive-c0re`'s `decide_pause` backend counterpart,
|
||||
// treats a declared `Up` with the marker still set as "clear it"). A
|
||||
// dedicated "resume" entry would just be a second spelling of the
|
||||
// option already above it.
|
||||
import { useRef, useState } from "preact/hooks";
|
||||
import { Badge, type BadgeTone } from "@hive/shared/badge.js";
|
||||
import { Dropdown, type DropdownOption } from "@hive/shared/dropdown.js";
|
||||
import type { AgentRow } from "../AgentTypes.js";
|
||||
|
||||
export function WantedMenu({
|
||||
row,
|
||||
pending,
|
||||
onSelectUp,
|
||||
onSelectOffline,
|
||||
onSelectPaused,
|
||||
onDestroy,
|
||||
showDestroy = true,
|
||||
portal = false,
|
||||
}: {
|
||||
row: AgentRow;
|
||||
pending: boolean;
|
||||
onSelectUp: (row: AgentRow) => void;
|
||||
onSelectOffline: (row: AgentRow) => void;
|
||||
onSelectPaused: (row: AgentRow) => void;
|
||||
onDestroy?: (row: AgentRow) => void;
|
||||
/**
|
||||
* The card view (see `AgentCard`) passes `false` here — its
|
||||
* quick-access menu keeps to the three everyday states, so "destroy"
|
||||
* can't be reached by an extra click off a state that's already open.
|
||||
* `AgentsPage`'s detail-panel `WantedMenu` keeps the default (`true`)
|
||||
* — mara: "destroy is already available via wanted state", i.e. the
|
||||
* full menu there, not a separate button.
|
||||
*/
|
||||
showDestroy?: boolean;
|
||||
/**
|
||||
* Forwarded to `Dropdown`'s own `portal` (see its file-top comment) —
|
||||
* only the table's "wanted" column needs it, to escape
|
||||
* `.ui-table-scroll`'s clip on the table's last row (the bug `Dropdown`'s
|
||||
* own `portal` prop exists to fix in the first place).
|
||||
* Neither the card list nor the detail panel has a clipping ancestor,
|
||||
* and inside the detail panel specifically `portal` is actively wrong:
|
||||
* a `position: fixed` element appended to `document.body` renders
|
||||
* *behind* an open native `<dialog>` (the dialog is promoted to the
|
||||
* browser's top layer, which composites above ordinary body content
|
||||
* regardless of z-index) — found while screenshotting this exact
|
||||
* dropdown open inside the detail panel, only the portion extending
|
||||
* past the dialog's own edge was visible.
|
||||
*/
|
||||
portal?: boolean;
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const anchorRef = useRef<HTMLDivElement>(null);
|
||||
const destroyed = row.wanted === "destroyed";
|
||||
const tone: BadgeTone =
|
||||
row.wanted === "up"
|
||||
? "positive"
|
||||
: row.wanted === "paused"
|
||||
? "warning"
|
||||
: destroyed
|
||||
? "negative"
|
||||
: "neutral";
|
||||
const options: DropdownOption[] = [
|
||||
{ value: "up", label: "up" },
|
||||
{ value: "paused", label: "paused" },
|
||||
{ value: "offline", label: "offline" },
|
||||
...(showDestroy
|
||||
? [{ value: "destroy", label: "destroy", danger: true }]
|
||||
: []),
|
||||
];
|
||||
|
||||
return (
|
||||
<div class="ui-dropdown-anchor" ref={anchorRef}>
|
||||
<Badge
|
||||
tone={tone}
|
||||
value={pending ? "…" : (row.wanted ?? "no declaration")}
|
||||
// `destroyed` blocks opening the menu at all — same UX-only
|
||||
// guard as before (real enforcement is server-side, see
|
||||
// `declareState`'s caller), just applied to the trigger instead
|
||||
// of a `disabled` toggle badge.
|
||||
onClick={row.hive && !destroyed ? () => setOpen((o) => !o) : undefined}
|
||||
expanded={open}
|
||||
disabled={pending || !row.hive || destroyed}
|
||||
title={
|
||||
destroyed
|
||||
? `${row.name} is destroyed — redeploy via "+ agent" to bring it back`
|
||||
: row.hive
|
||||
? `declare a new state for ${row.name}`
|
||||
: "no hive on record for this agent — nothing to declare against"
|
||||
}
|
||||
/>
|
||||
<Dropdown
|
||||
open={open}
|
||||
portal={portal}
|
||||
options={options}
|
||||
activeValue={row.wanted ?? undefined}
|
||||
label={`declare ${row.name}`}
|
||||
onSelect={(value) => {
|
||||
setOpen(false);
|
||||
if (value === "up") onSelectUp(row);
|
||||
else if (value === "offline") onSelectOffline(row);
|
||||
else if (value === "paused") onSelectPaused(row);
|
||||
else onDestroy?.(row);
|
||||
}}
|
||||
onClose={() => setOpen(false)}
|
||||
anchorRef={anchorRef}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue