Ports app.js's renderNeedsLoginIdle/renderLoginInProgress as real
functional Preact UI, not just a StatusChip label: a start-login
button (POST login/start), the OAuth-URL link once claude emits it,
a code-paste form (POST login/code, masked + reveal toggle) with
cancel (POST login/cancel), and the streamed process output. Without
this, an agent needing re-login (post-/logout, credential rotation)
had no web-UI path back in through the new page — the gap I flagged
before the cutover; mara: "1 - do it now".
Reuses agent.css's existing .agent-status-overlay/.status-needs-login/
.btn-login/.loginform*/.diff rules verbatim (same class names as the
old markup), matching Header.tsx's precedent — no new CSS file needed.
Added SessionView to types.ts (mirrors hive-agent's SessionView struct
exactly: url/output/finished/exit_note) and lib/loginAction.ts (same
{ok,detail} fetch shape as modelEffort.ts — same-origin POSTs, no CORS
concerns like pauseAction.ts's dashboard-origin actions).
Screenshot-verified all 3 states (idle, in-progress-with-url, finished
with error) against a mock server.
28 lines
1.4 KiB
TypeScript
28 lines
1.4 KiB
TypeScript
// POST `/login/start` / `/login/code` / `/login/cancel` — same-origin
|
|
// (this agent's own backend, `hive-agent/src/web_ui/auth.rs`), so a
|
|
// plain `fetch` is safe (unlike `pauseAction.ts`'s dashboard-origin
|
|
// actions). Same `{ ok, detail }` shape and `redirect: 'manual'` /
|
|
// broad-2xx-4xx-treated-as-ok contract as `modelEffort.ts` — none of
|
|
// these three routes actually redirect today, but the same tolerant
|
|
// check costs nothing and keeps every same-origin POST in this package
|
|
// behaving identically.
|
|
async function post(path: string, body?: URLSearchParams): Promise<{ ok: boolean; detail?: string }> {
|
|
try {
|
|
const resp = await fetch(path, {
|
|
method: 'POST',
|
|
headers: body ? { 'Content-Type': 'application/x-www-form-urlencoded' } : undefined,
|
|
body,
|
|
redirect: 'manual',
|
|
});
|
|
const ok = resp.ok || resp.type === 'opaqueredirect' || (resp.status >= 200 && resp.status < 400);
|
|
if (ok) return { ok: true };
|
|
const detail = await resp.text().catch(() => '');
|
|
return { ok: false, detail: `http ${resp.status}${detail ? ' — ' + detail : ''}` };
|
|
} catch (err) {
|
|
return { ok: false, detail: err instanceof Error ? err.message : String(err) };
|
|
}
|
|
}
|
|
|
|
export const postLoginStart = () => post('login/start');
|
|
export const postLoginCode = (code: string) => post('login/code', new URLSearchParams({ code }));
|
|
export const postLoginCancel = () => post('login/cancel');
|