import { useEffect, useRef, useState } from "react"; import { useLocation, useNavigate, useParams } from "react-router-dom"; import { LuEye, LuEyeOff, LuPencil, LuPlus, LuTrash2 } from "react-icons/lu"; import { errorMessage } from "../api/client.js"; import { fetchHttpAuthLiterals, useDeleteHttpAuth, useHttpAuths, } from "../api/hooks.js"; import { AuthEditorModal } from "../components/AuthEditorModal.jsx"; import { formatTime } from "../lib/format.jsx"; function CredDisplay({ field, fieldKey, authId, cache, onRevealed }) { const [open, setOpen] = useState(false); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); if (!field || field.source === "missing") { return —; } if (field.source === "secret") { return ( secret:{field.secret} ); } if (field.source === "kv") { const ref = field.namespace ? `kv:${field.namespace}/${field.kv}` : `kv:${field.kv}`; return {ref}; } const revealed = cache?.[fieldKey]; const shown = open && typeof revealed === "string"; async function toggle() { if (open) { setOpen(false); return; } if (typeof revealed === "string") { setOpen(true); return; } setLoading(true); setError(null); try { const data = await fetchHttpAuthLiterals(authId); onRevealed?.(data.literals ?? {}); setOpen(true); } catch (err) { setError(errorMessage(err)); } finally { setLoading(false); } } return ( {shown ? revealed : "***"} {error ? {error} : null} ); } function CredentialsCell({ auth, cache, onRevealed }) { const cfg = auth.config ?? {}; if (auth.type === "bearer") { return ( ); } if (auth.type === "basic") { return ( user pass ); } return ( {cfg.header ?? "?"} ); } export function AuthProfilesPage() { const navigate = useNavigate(); const location = useLocation(); const { name: routeName } = useParams(); const isNewRoute = /\/auth\/new\/?$/.test(location.pathname); const isEditRoute = Boolean(routeName) && !isNewRoute; const { data: auths = [], isLoading } = useHttpAuths(); const del = useDeleteHttpAuth(); const [editor, setEditor] = useState(null); const [confirmDelete, setConfirmDelete] = useState(null); /** @type {[Record>, Function]} */ const [revealCache, setRevealCache] = useState({}); const openedRouteKey = useRef(null); function closeEditor() { setEditor(null); openedRouteKey.current = null; if (isNewRoute || isEditRoute) { navigate("/auth", { replace: true }); } } useEffect(() => { if (!isNewRoute) return; if (openedRouteKey.current === "new") return; openedRouteKey.current = "new"; setEditor({ mode: "add" }); }, [isNewRoute]); useEffect(() => { if (!isEditRoute) { if (!isNewRoute) openedRouteKey.current = null; return; } if (isLoading) return; const key = `edit:${routeName}`; if (openedRouteKey.current === key) return; openedRouteKey.current = key; const auth = auths.find((a) => a.name === routeName); if (!auth) { setEditor({ mode: "add" }); return; } setEditor({ mode: "edit", auth }); }, [isEditRoute, isNewRoute, routeName, isLoading, auths]); return (

Auth

HTTP trigger auth profiles. Rename freely — workflows keep working via a stable id. Add them to a trigger from the workflow editor dropdown. Secrets are managed on the Secrets page; KV values stay in KV.

{isLoading ? ( ) : auths.length === 0 ? (

No auth profiles yet.

) : (
{auths.map((a) => ( ))}
Name Type Credentials Updated
{a.name} {a.type} setRevealCache((prev) => ({ ...prev, [a.id]: literals })) } /> {formatTime(a.updated_at)}
)} {editor ? ( { const id = saved?.id || editor.auth?.id; if (!id) return; setRevealCache((prev) => { const next = { ...prev }; delete next[id]; return next; }); }} /> ) : null} {confirmDelete ? (

Delete {confirmDelete.name}?

Workflows that reference this profile will fail auth until updated.

{del.isError ? (

{errorMessage(del.error)}

) : null}
) : null}
); }