"use client"; import { useState } from "react"; import { Trash2 } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import type { CredentialBody, CredentialType, PublicCredential, } from "@/lib/api"; // Shared, scope-agnostic credential UI components. The same form/row // renders both the global Credentials page and the per-agent overrides // section — the only difference is which API the parent wires into // `onSubmit`. export type CredentialRowProps = { cred: PublicCredential; onEdit: () => void; onDelete: () => void; /** Optional badge string ("inherited", "override", etc.) shown next to the type. */ scopeLabel?: string; }; export function CredentialRow({ cred, onEdit, onDelete, scopeLabel }: CredentialRowProps) { return (
{cred.name} {cred.type} {scopeLabel && {scopeLabel}}
); } export function CredentialSummary({ cred }: { cred: PublicCredential }) { if (cred.type === "aws") { return (
region: {cred.awsRegion}
account: {cred.awsAccountId}
role: {cred.awsCrossAccountRoleArn}
); } if (cred.type === "gcp") { return (
project: {cred.gcpProjectId}
{cred.gcpLocation &&
location: {cred.gcpLocation}
}
SA JSON: {cred.hasServiceAccount ? "set" : "not set"}
); } if (cred.type === "kv") { return (
keys: {(cred.kvKeys ?? []).join(", ") || "(none)"}
); } return null; } export type CredentialFormProps = { initial: PublicCredential | null; /** Called with the full request body (sealed server-side). Throws on failure. */ onSubmit: (body: CredentialBody) => Promise; onCancel: () => void; onError: (msg: string | null) => void; }; export function CredentialForm({ initial, onSubmit, onCancel, onError }: CredentialFormProps) { const [name, setName] = useState(initial?.name ?? ""); const [type, setType] = useState(initial?.type ?? "aws"); const [awsRegion, setAwsRegion] = useState(initial?.awsRegion ?? ""); const [awsAccountId, setAwsAccountId] = useState(initial?.awsAccountId ?? ""); const [awsRoleArn, setAwsRoleArn] = useState(initial?.awsCrossAccountRoleArn ?? ""); const [gcpProjectId, setGcpProjectId] = useState(initial?.gcpProjectId ?? ""); const [gcpLocation, setGcpLocation] = useState(initial?.gcpLocation ?? "us-central1"); const [gcpSaJson, setGcpSaJson] = useState(""); const seedKv: [string, string][] = initial?.type === "kv" && initial.kvKeys ? initial.kvKeys.map((k) => [k, ""]) : [["", ""]]; const [kvEntries, setKvEntries] = useState<[string, string][]>(seedKv); const [saving, setSaving] = useState(false); const isEdit = !!initial; async function handleSave() { onError(null); setSaving(true); try { const trimmedName = name.trim(); if (!trimmedName) throw new Error("name is required"); const body: CredentialBody = { name: trimmedName, type }; if (type === "aws") { body.awsRegion = awsRegion.trim(); body.awsAccountId = awsAccountId.trim(); body.awsCrossAccountRoleArn = awsRoleArn.trim(); } else if (type === "gcp") { body.gcpProjectId = gcpProjectId.trim(); body.gcpLocation = gcpLocation.trim() || undefined; if (gcpSaJson.trim()) body.gcpServiceAccountJson = gcpSaJson; } else if (type === "kv") { const kv: Record = {}; for (const [k, v] of kvEntries) { const key = k.trim(); if (key) kv[key] = v; } body.kv = kv; } await onSubmit(body); } catch (e) { onError(e instanceof Error ? e.message : "save failed"); } finally { setSaving(false); } } return (
setName(e.target.value)} placeholder="prod-aws" disabled={isEdit} className="font-mono text-xs" /> {isEdit && (

Name is immutable. Delete and re-create to rename.

)}
{type === "aws" && (
setAwsRegion(e.target.value)} placeholder="us-east-1" className="font-mono text-xs" />
setAwsAccountId(e.target.value)} placeholder="123456789012" className="font-mono text-xs" />
setAwsRoleArn(e.target.value)} placeholder="arn:aws:iam::123456789012:role/FlowDeployRole" className="font-mono text-xs" />

The role’s trust policy must allow the Flow host’s identity to sts:AssumeRole. Permissions needed: ECR + IAM + bedrock-agentcore.

)} {type === "gcp" && (
setGcpProjectId(e.target.value)} placeholder="my-gcp-project" className="font-mono text-xs" />
setGcpLocation(e.target.value)} placeholder="us-central1" className="font-mono text-xs" />