Watch
0
0
Fork
You've already forked hyperhive
0

swarm ui: a log in button in the claude login row of an agent's settings

An agent with no stored login, or one to log in again, can be logged in from
the settings panel instead of waiting for its turns to fail with a login needed
badge first. The button opens the same dialog as the card's.
This commit is contained in:
iris 2026-11-15 15:11:41 +01:00 • committed by mara
commit e9ddfd61ec
3 changed files with 34 additions and 5 deletions

View file

@ -20,6 +20,11 @@ A started login waits 15 minutes for its code, then stops. Starting another logi
for the same agent replaces the running one. A refused code fails the login, and
the dialog shows what claude printed.
You don't have to wait for the agent to fail first: the **claude login** row in the
card's settings has a **log in** button too, which reads **log in again** once the swarm has a
login for the agent, so a fresh agent or a forced relogin starts there. It opens the
same dialog.
The agent's own web UI login (`/login`) still works, unchanged. It goes away once
you have confirmed the swarm login works.

View file

@ -71,8 +71,9 @@ the code back. When the secret store has the login, the dialog closes and a
short toast in the top-right corner says so. The credentials go to the agent through the secret store
([`claude-login.md`](../../2-handbook/swarm/claude-login.md)). The agent's own web UI login still
works too. The settings section of an opened card has a **claude login** row: when
the swarm stored the agent's login, or that none is, with a **delete login**
action that asks first.
the swarm stored the agent's login, or that none is, with a **log in** button
(**log in again** when the swarm holds one) that opens the same dialog, and a **delete
login** action that asks first.
A card shows **updating** when the agent's status snapshot has `updating` set.
The hive sets it when an update stops a running container and clears it when

View file

@ -1,13 +1,15 @@
// <AgentClaudeLogin> — the swarm's stored claude login for one agent: when it
// was written (`GET …/claude-login`, read from the secret store's metadata),
// or that none is, and a delete that removes every version of it
// (`DELETE …/claude-login/stored`) after a confirmation.
// or that none is, a button that logs the agent in (or in again) without
// waiting for its turns to fail first, and a delete that removes every version
// of it (`DELETE …/claude-login/stored`) after a confirmation.
//
// Its state belongs to one agent: callers key it by agent name.
import { useCallback, useEffect, useState } from "preact/hooks";
import { ApiErrorPanel } from "@hive/shared/api-error-panel.js";
import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
import { Badge } from "@hive/shared/badge.js";
import { ClaudeLoginDialog } from "../claude-login/ClaudeLoginDialog.js";
import { ConfirmDialog } from "../../ui/confirm-dialog/ConfirmDialog.js";
import { Hint } from "../../ui/hint/Hint.js";
import { RelativeTime } from "../../ui/relative-time/RelativeTime.js";
@ -26,6 +28,7 @@ export function AgentClaudeLogin({ agent }: { agent: string }) {
const [confirming, setConfirming] = useState(false);
const [deleting, setDeleting] = useState(false);
const [deleteError, setDeleteError] = useState<ProblemDetails | null>(null);
const [loggingIn, setLoggingIn] = useState(false);
const load = useCallback(async () => {
try {
@ -75,17 +78,37 @@ export function AgentClaudeLogin({ agent }: { agent: string }) {
return errorPanel ?? <Hint as="span">…</Hint>;
}
const at = storedAtMs(login);
const loginBadge = (
<Badge
variant="quiet"
value={login.stored_at === null ? "log in" : "log in again"}
title={`log ${agent} in to claude from here`}
onClick={() => setLoggingIn(true)}
/>
);
return (
<>
{errorPanel}
{loggingIn ? (
<ClaudeLoginDialog
agent={agent}
onClose={() => {
setLoggingIn(false);
void load();
}}
/>
) : null}
{login.stored_at === null ? (
<Hint as="span">none stored</Hint>
<>
<Hint as="span">none stored</Hint> {loginBadge}
</>
) : (
<>
<span title={login.stored_at}>
stored{" "}
{at === null ? login.stored_at : <RelativeTime epochMs={at} />}
</span>{" "}
{loginBadge}{" "}
<Badge
variant="quiet"
value="delete login"