From b2b4709abe14467da6f08741307d13b631cd9b7d Mon Sep 17 00:00:00 2001 From: Samuel James <143277412+SamuelSJames@users.noreply.github.com> Date: Sat, 20 Jun 2026 08:11:32 -0400 Subject: [PATCH] Add file upload for SSH private key and certificate fields (#15) * Add editable display-name field to generic integrations Lets users set a custom name for Proxmox, Docker, AWS, Remote Desktop, Netbird, Cloudflare, Uptime Kuma, and Weather integrations, separate from the host/IP field, mirroring the SSH host rename pattern. Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4 * Surface the new-integration name field as a labeled input The name field for new generic integrations was a faint header input with only placeholder text, easy to miss. Move it into the form grid as a proper labeled "Name" field next to the other connection fields. Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4 * Add file upload for SSH private key and certificate fields Lets users pick a key file from disk (e.g. ~/.ssh) instead of pasting its contents into the Private Key / OPKSSH Certificate fields. * Fix SSH private key paste corrupting multi-line PEM format Private Key and Certificate fields were single-line elements, which strip newlines on paste and corrupt PEM-formatted keys (causing 'Unsupported key format' errors). Render them as multi-line textareas instead so pasted keys keep their line breaks. --------- Co-authored-by: Claude --- src/pages/Settings.tsx | 47 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index 1f8b6b6..a7dcfc0 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -35,7 +35,7 @@ const accentColors = [ { name: 'Red', color: '#E74C3C' }, ] -type FieldDef = { key: string; label: string; secret?: boolean; hint?: string; placeholder?: string } +type FieldDef = { key: string; label: string; secret?: boolean; hint?: string; placeholder?: string; file?: boolean } const integrationTypeDefs: { type: string; name: string; multiInstance?: boolean; fields: FieldDef[] }[] = [ { type: 'proxmox', name: 'Proxmox', multiInstance: true, fields: [ @@ -83,9 +83,9 @@ const sshFields: FieldDef[] = [ { key: 'port', label: 'Port (default 22)' }, { key: 'username', label: 'Username' }, { key: 'password', label: 'Password', secret: true }, - { key: 'privateKey', label: 'Private Key (PEM)', secret: true }, + { key: 'privateKey', label: 'Private Key (PEM)', secret: true, file: true }, { key: 'passphrase', label: 'Key Passphrase', secret: true }, - { key: 'certificate', label: 'OPKSSH Certificate (id_key-cert.pub)', secret: true }, + { key: 'certificate', label: 'OPKSSH Certificate (id_key-cert.pub)', secret: true, file: true }, ] const cardBase: React.CSSProperties = { @@ -385,6 +385,7 @@ function SshHostsSection() { const [busy, setBusy] = useState>(new Set()) const [newDrafts, setNewDrafts] = useState<{ key: number; values: Record }[]>([]) const nextNewKey = useRef(-1) + const fileInputRefs = useRef>({}) useEffect(() => { refresh() @@ -546,12 +547,50 @@ function SshHostsSection() { const isRevealed = revealed.has(key) const savedValue = f.secret ? '' : existing?.config[f.key] ?? '' const value = values[f.key] ?? savedValue + + if (f.file) { + return ( +
+ +
+