diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx index 64082c6..a547e1c 100644 --- a/frontend/src/pages/Settings.tsx +++ b/frontend/src/pages/Settings.tsx @@ -597,7 +597,15 @@ function MachineEditor({ ); } -function SSHKeyManager({ sshKeys }: { sshKeys: SSHKey[] }) { +function SSHKeyManager({ + sshKeys, + selectedKeyId, + onSelectKeyId, +}: { + sshKeys: SSHKey[]; + selectedKeyId: string; + onSelectKeyId: (id: string) => void; +}) { const saveKey = useSaveSSHKey(); const generateKey = useGenerateSSHKey(); const deleteKey = useDeleteSSHKey(); @@ -610,8 +618,12 @@ function SSHKeyManager({ sshKeys }: { sshKeys: SSHKey[] }) { passphrase: "", notes: "", }); + const selectedKey = useMemo( + () => sshKeys.find((key) => key.id === selectedKeyId) ?? null, + [sshKeys, selectedKeyId], + ); const editing = Boolean(draft.id); - const clear = () => + const clear = () => { setDraft({ id: null, name: "", @@ -621,33 +633,124 @@ function SSHKeyManager({ sshKeys }: { sshKeys: SSHKey[] }) { fingerprint: "", notes: "", }); + onSelectKeyId(""); + }; return ( - - - - + { + clear(); }} > - - - SSH keys - + New key + + } + > + + {sshKeys.length === 0 && ( + - Import or generate reusable keys for SSH machines. + No SSH keys saved yet. + )} + {sshKeys.map((key) => { + const active = key.id === selectedKeyId; + return ( + { + onSelectKeyId(key.id); + setDraft({ + id: key.id, + name: key.name, + private_key: "", + passphrase: "", + public_key: key.public_key, + fingerprint: key.fingerprint, + notes: key.notes, + }); + }} + sx={{ + display: "grid", + gridTemplateColumns: "minmax(0, 1fr) auto", + gap: 1, + px: 1.25, + py: 1.1, + borderTop: 1, + borderColor: "divider", + cursor: "pointer", + width: "100%", + bgcolor: active + ? "action.selected" + : "background.paper", + "&:hover .rail-edit": { opacity: 1 }, + }} + > + + + {key.name} + + + {key.private_key_set ? "key saved" : "no key"} ยท{" "} + {key.usage_count} machine + {key.usage_count === 1 ? "" : "s"} + + + { + onSelectKeyId(key.id); + setDraft({ + id: key.id, + name: key.name, + private_key: "", + passphrase: "", + public_key: key.public_key, + fingerprint: key.fingerprint, + notes: key.notes, + }); + }} + /> + + ); + })} + + + - + ) : undefined + } + > + - setDraft((current) => ({ ...current, name: e.target.value })) + setDraft((current) => ({ + ...current, + name: e.target.value, + })) } /> @@ -683,7 +789,10 @@ function SSHKeyManager({ sshKeys }: { sshKeys: SSHKey[] }) { label="Notes" value={draft.notes} onChange={(e) => - setDraft((current) => ({ ...current, notes: e.target.value })) + setDraft((current) => ({ + ...current, + notes: e.target.value, + })) } /> @@ -752,113 +861,70 @@ function SSHKeyManager({ sshKeys }: { sshKeys: SSHKey[] }) { + {selectedKey && ( + + )} {saveKey.error && ( {String(saveKey.error)} )} - {sshKeys.length > 0 ? ( - - {sshKeys.map((key) => ( - - - - - - - {key.name} - - - {key.passphrase_set && ( - - )} - - - {key.notes && ( - - {key.notes} - - )} - - - Fingerprint - - - {key.fingerprint || "Unavailable"} - - - - - Public key - - - {key.public_key || "Unavailable"} - - - - - - - - - - ))} - - ) : ( - No SSH keys saved yet. + {selectedKey && ( + + + + Fingerprint + + + {selectedKey.fingerprint || "Unavailable"} + + + + + Public key + + + {selectedKey.public_key || "Unavailable"} + + + )} - - + + ); } function ResetLocalDatabaseCard() { @@ -989,6 +1055,7 @@ export function Settings() { const testMachineSSH = useTestMonitoringMachineSSH(); const [tab, setTab] = useState("machines"); const [deleteMachineId, setDeleteMachineId] = useState(null); + const [selectedSSHKeyId, setSelectedSSHKeyId] = useState(""); const [sshValidationMessage, setSSHValidationMessage] = useState(""); const [sshValidationError, setSSHValidationError] = useState(""); const [sshValidationStatus, setSSHValidationStatus] = useState(""); @@ -1395,7 +1462,13 @@ export function Settings() { ) : null} )} - {tab === "ssh-keys" && } + {tab === "ssh-keys" && ( + + )} {tab === "danger" && }