import { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import { createSSHKey, deleteSSHKey, listSSHKeys, signPayload, verifySignature, type SSHKey } from "../api/ssh_keys"; import { Icon } from "../components/icon"; export const SSHKeysPage = () => { const navigate = useNavigate(); const [keys, setKeys] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [newKeyName, setNewKeyName] = useState(""); const [generating, setGenerating] = useState(false); const [signPayloads, setSignPayloads] = useState>({}); const [signatures, setSignatures] = useState>({}); const [signing, setSigning] = useState>({}); const [verifyPayloads, setVerifyPayloads] = useState>({}); const [verifySignatures, setVerifySignatures] = useState>({}); const [verifyResults, setVerifyResults] = useState>({}); const [verifying, setVerifying] = useState>({}); useEffect(() => { loadKeys(); }, []); async function loadKeys() { try { setLoading(true); const data = await listSSHKeys(); setKeys(data); setError(null); } catch { setError("Failed to load SSH keys"); } finally { setLoading(false); } } async function handleGenerate(e: React.FormEvent) { e.preventDefault(); if (!newKeyName.trim()) return; try { setGenerating(true); await createSSHKey({ name: newKeyName.trim() }); setNewKeyName(""); await loadKeys(); } catch { setError("Failed to generate SSH key"); } finally { setGenerating(false); } } async function handleDelete(keyId: string) { if (!confirm("Are you sure you want to delete this SSH key?")) return; try { await deleteSSHKey(keyId); await loadKeys(); } catch { setError("Failed to delete SSH key"); } } function copyToClipboard(text: string) { navigator.clipboard.writeText(text); } async function handleSign(keyId: string) { const payload = signPayloads[keyId]; if (!payload?.trim()) return; try { setSigning((prev) => ({ ...prev, [keyId]: true })); const result = await signPayload(keyId, { payload: payload.trim() }); setSignatures((prev) => ({ ...prev, [keyId]: result.signature })); setError(null); } catch { setError("Failed to sign payload"); } finally { setSigning((prev) => ({ ...prev, [keyId]: false })); } } async function handleVerify(keyId: string) { const payload = verifyPayloads[keyId]; const signature = verifySignatures[keyId]; if (!payload?.trim() || !signature?.trim()) return; try { setVerifying((prev) => ({ ...prev, [keyId]: true })); const result = await verifySignature(keyId, { payload: payload.trim(), signature: signature.trim(), }); setVerifyResults((prev) => ({ ...prev, [keyId]: result.valid })); setError(null); } catch { setError("Failed to verify signature"); } finally { setVerifying((prev) => ({ ...prev, [keyId]: false })); } } if (loading) return
Loading...
; return (

Settings

SSH Keys

{error &&
{error}
}
setNewKeyName(e.target.value)} placeholder="e.g., GitHub Work" required />
{keys.length === 0 ? (

No SSH keys yet. Generate one above.

) : ( keys.map((key) => (

{key.name}

Created: {new Date(key.created_at).toLocaleDateString()}
{key.public_key.substring(0, 50)}...

Sign Payload