swarm UI: show the accounts linked to each agent
GET /api/hives/{hive}/agents/{agent}/linked-accounts returns one row per
account linked to the agent, as kind, name and host: each matrix account
under swarm/agents/<agent>/matrix (with its homeserver, and the agent's own
`main` marked reserved), each forge label under swarm/agents/<agent>/forge
(with its url), and github when swarm/agents/<agent>/github-token exists
(host github.com, which is not stored). No credential field is in the
response type.
Listing those two directories needs a new controller grant: `list` on
secret/metadata/swarm/agents/+/matrix and .../+/forge only, pinned in
bao-grants.nix as the only metadata stanzas under agents/ beside the queue
revocation. Checked against a dev OpenBao 2.6.3: the grant lists those two
directories and is refused on agents/, agents/<agent>/, and a leaf.
The swarm UI agent detail panel shows all rows under "accounts"; the table
view's matrix column shows the matrix rows. The link badges stay.
Refs #4855
This commit is contained in:
parent
6fd91b0e69
commit
3380c1915f
13 changed files with 577 additions and 30 deletions
|
|
@ -33,6 +33,7 @@ import { LinkIcon } from "@hive/shared/icons.js";
|
|||
import { AgentCard } from "./AgentCard.js";
|
||||
import { AgentTermPreview } from "./AgentTermPreview.js";
|
||||
import { FRESHNESS, type AgentRow } from "./AgentTypes.js";
|
||||
import { LinkedAccounts } from "./LinkedAccounts.js";
|
||||
import { Button } from "../../ui/button/Button.js";
|
||||
import { ConfirmDialog } from "../../ui/confirm-dialog/ConfirmDialog.js";
|
||||
import { Dialog } from "../../ui/dialog/Dialog.js";
|
||||
|
|
@ -158,6 +159,22 @@ export function AgentsPage() {
|
|||
const [matrixTarget, setMatrixTarget] = useState<AgentRow | null>(null);
|
||||
// The row showing the "link a forge account" dialog, same shape.
|
||||
const [forgeTarget, setForgeTarget] = useState<AgentRow | null>(null);
|
||||
// Bumped whenever a link dialog closes; every `LinkedAccounts` refetches
|
||||
// on a change, so a newly linked account shows without a reload.
|
||||
const [linkVersion, setLinkVersion] = useState(0);
|
||||
const linkClosed = () => setLinkVersion((v) => v + 1);
|
||||
const closeMatrix = () => {
|
||||
setMatrixTarget(null);
|
||||
linkClosed();
|
||||
};
|
||||
const closeForge = () => {
|
||||
setForgeTarget(null);
|
||||
linkClosed();
|
||||
};
|
||||
const closeGithub = () => {
|
||||
setGithubTarget(null);
|
||||
linkClosed();
|
||||
};
|
||||
// Which agent the detail panel shows, *by name* — not the `AgentRow`
|
||||
// object itself. Storing the row would snapshot it at selection time;
|
||||
// `rows` replaces its whole array on every `refresh()` and every
|
||||
|
|
@ -374,18 +391,28 @@ export function AgentsPage() {
|
|||
key: "matrix",
|
||||
header: "matrix",
|
||||
render: (a) => (
|
||||
<Badge
|
||||
variant="quiet"
|
||||
icon={<LinkIcon />}
|
||||
value="link account"
|
||||
onClick={a.hive ? () => setMatrixTarget(a) : undefined}
|
||||
disabled={!a.hive}
|
||||
title={
|
||||
a.hive
|
||||
? `link a matrix account to ${a.name}`
|
||||
: "no hive on record for this agent — nothing to link against"
|
||||
}
|
||||
/>
|
||||
<>
|
||||
{a.hive ? (
|
||||
<LinkedAccounts
|
||||
hive={a.hive}
|
||||
agent={a.name}
|
||||
version={linkVersion}
|
||||
kinds={["matrix"]}
|
||||
/>
|
||||
) : null}
|
||||
<Badge
|
||||
variant="quiet"
|
||||
icon={<LinkIcon />}
|
||||
value="link account"
|
||||
onClick={a.hive ? () => setMatrixTarget(a) : undefined}
|
||||
disabled={!a.hive}
|
||||
title={
|
||||
a.hive
|
||||
? `link a matrix account to ${a.name}`
|
||||
: "no hive on record for this agent — nothing to link against"
|
||||
}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
|
|
@ -569,6 +596,18 @@ export function AgentsPage() {
|
|||
"—"
|
||||
)}
|
||||
</dd>
|
||||
<dt>accounts</dt>
|
||||
<dd>
|
||||
{detailTarget.hive ? (
|
||||
<LinkedAccounts
|
||||
hive={detailTarget.hive}
|
||||
agent={detailTarget.name}
|
||||
version={linkVersion}
|
||||
/>
|
||||
) : (
|
||||
"—"
|
||||
)}
|
||||
</dd>
|
||||
</dl>
|
||||
<div class="ui-agent-detail-actions">
|
||||
<Badge
|
||||
|
|
@ -641,7 +680,7 @@ export function AgentsPage() {
|
|||
</Dialog>
|
||||
<Dialog
|
||||
open={matrixTarget !== null}
|
||||
onClose={() => setMatrixTarget(null)}
|
||||
onClose={closeMatrix}
|
||||
label="link a matrix account"
|
||||
plain
|
||||
>
|
||||
|
|
@ -652,13 +691,13 @@ export function AgentsPage() {
|
|||
<LinkMatrixAccountForm
|
||||
hive={matrixTarget.hive}
|
||||
agent={matrixTarget.name}
|
||||
onClose={() => setMatrixTarget(null)}
|
||||
onClose={closeMatrix}
|
||||
/>
|
||||
) : null}
|
||||
</Dialog>
|
||||
<Dialog
|
||||
open={forgeTarget !== null}
|
||||
onClose={() => setForgeTarget(null)}
|
||||
onClose={closeForge}
|
||||
label="link a forge account"
|
||||
plain
|
||||
>
|
||||
|
|
@ -666,13 +705,13 @@ export function AgentsPage() {
|
|||
<LinkForgeAccountForm
|
||||
hive={forgeTarget.hive}
|
||||
agent={forgeTarget.name}
|
||||
onClose={() => setForgeTarget(null)}
|
||||
onClose={closeForge}
|
||||
/>
|
||||
) : null}
|
||||
</Dialog>
|
||||
<Dialog
|
||||
open={githubTarget !== null}
|
||||
onClose={() => setGithubTarget(null)}
|
||||
onClose={closeGithub}
|
||||
label="link a github account"
|
||||
plain
|
||||
>
|
||||
|
|
@ -680,7 +719,7 @@ export function AgentsPage() {
|
|||
<LinkGithubAccountForm
|
||||
hive={githubTarget.hive}
|
||||
agent={githubTarget.name}
|
||||
onClose={() => setGithubTarget(null)}
|
||||
onClose={closeGithub}
|
||||
/>
|
||||
) : null}
|
||||
</Dialog>
|
||||
|
|
|
|||
Loading…
Reference in a new issue