import { useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { copyToClipboard } from "@/lib/clipboard"; import { Copy, Info, Lock, Upload, X } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/button"; import { Input } from "@/components/input"; import { PasswordInput } from "@/components/password-input"; import { SectionCard } from "@/components/section-card"; import { createCredential, generateKeyPair, generatePublicKeyFromPrivate, updateCredential, } from "@/main-axios"; import type { Credential } from "@/types/ui-types"; type CredentialAuthType = Credential["type"]; type CredentialWithCertificate = Credential & { certPublicKey?: string }; export function CredentialEditorView({ credential, activeTab, onBack, onSave, }: { credential: Credential | null; activeTab: string; onBack: () => void; onSave: (saved: Record) => void; }) { const [credForm, setCredForm] = useState(() => ({ name: credential?.name ?? "", username: credential?.username ?? "", folder: credential?.folder ?? "", description: credential?.description ?? "", tags: credential?.tags ?? ([] as string[]), tagInput: "", type: credential?.type ?? "password", value: credential?.value ?? "", publicKey: credential?.publicKey ?? "", passphrase: credential?.passphrase ?? "", certPublicKey: (credential as CredentialWithCertificate | null)?.certPublicKey ?? "", })); const { t } = useTranslation(); const [generatingKey, setGeneratingKey] = useState(false); const [generatingPublicKey, setGeneratingPublicKey] = useState(false); const credFileInputRef = useRef(null); const certFileInputRef = useRef(null); const setCredField = ( k: K, v: (typeof credForm)[K], ) => setCredForm((p) => ({ ...p, [k]: v })); const [saving, setSaving] = useState(false); const handleSave = async () => { if (!credForm.name.trim()) { toast.error(t("hosts.credentialNameRequired")); return; } setSaving(true); try { const data = { name: credForm.name, username: credForm.username, folder: credForm.folder || null, description: credForm.description || null, tags: credForm.tags, authType: credForm.type, password: credForm.type === "password" ? credForm.value : null, key: credForm.type === "key" ? credForm.value === "existing_key" ? undefined : credForm.value || null : null, publicKey: credForm.type === "key" ? credForm.publicKey : null, certPublicKey: credForm.type === "key" ? credForm.certPublicKey || null : null, keyPassword: credForm.type === "key" ? credForm.passphrase === "existing_key_password" ? undefined : credForm.passphrase || null : null, }; const saved = credential ? await updateCredential(Number(credential.id), data) : await createCredential(data); toast.success( credential ? t("hosts.credentialUpdated") : t("hosts.credentialCreated"), ); window.dispatchEvent(new CustomEvent("termix:credentials-changed")); onSave(saved); } catch (err) { const msg = err instanceof Error ? err.message : null; toast.error(msg || t("hosts.failedToSaveCredential")); } finally { setSaving(false); } }; const type = credForm.type; return (
{activeTab === "general" && ( } >
setCredField("name", e.target.value)} />
setCredField("folder", e.target.value)} />
setCredField("description", e.target.value)} />
{credForm.tags.map((tag) => ( {tag} ))} setCredField("tagInput", e.target.value)} onKeyDown={(e) => { if ( (e.key === " " || e.key === "Enter") && credForm.tagInput.trim() ) { e.preventDefault(); const tag = credForm.tagInput.trim(); if (!credForm.tags.includes(tag)) setCredField("tags", [...credForm.tags, tag]); setCredField("tagInput", ""); } else if ( e.key === "Backspace" && !credForm.tagInput && credForm.tags.length > 0 ) { setCredField("tags", credForm.tags.slice(0, -1)); } }} />
)} {activeTab === "auth" && ( } >
{["password", "key"].map((m) => ( ))}
setCredField("username", e.target.value)} />
{type === "password" && (
setCredField("value", e.target.value)} />
)} {type === "key" && (

{t("hosts.generateKeyPairTitle")}

{t("hosts.generateKeyPairDescription")}

{[ { label: "Ed25519", type: "ssh-ed25519" }, { label: "ECDSA (nistp256)", type: "ecdsa-sha2-nistp256", }, { label: "RSA (2048)", type: "ssh-rsa", bits: 2048 }, ].map(({ label, type: keyType, bits }) => ( ))}
{ const file = e.target.files?.[0]; if (!file) return; const text = await file.text(); setCredField("value", text.trim()); e.target.value = ""; }} /> {credForm.value === "existing_key" && (
{t("hosts.keySaved")} — {t("hosts.keyReplaceNotice")}
)}