From e2c04186d1008c3ce1ada1863ede166ef80f7946 Mon Sep 17 00:00:00 2001 From: Vantz Stockwell Date: Fri, 13 Mar 2026 09:30:13 -0400 Subject: [PATCH] fix: inline vault modals to fix rendering issue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same root cause as host/group dialogs — components in subdirectories with Teleport don't render at runtime in Nuxt SPA builds. Inlined CredentialForm and KeyImportDialog directly into their parent pages. Co-Authored-By: Claude Opus 4.6 --- frontend/pages/vault/credentials.vue | 144 +++++++++++++++++++++++++-- frontend/pages/vault/keys.vue | 119 +++++++++++++++++++++- 2 files changed, 254 insertions(+), 9 deletions(-) diff --git a/frontend/pages/vault/credentials.vue b/frontend/pages/vault/credentials.vue index a5f2411..fda6a25 100644 --- a/frontend/pages/vault/credentials.vue +++ b/frontend/pages/vault/credentials.vue @@ -8,6 +8,18 @@ const showForm = ref(false) const editingCred = ref(null) const deletingId = ref(null) +// Inline credential form state +const credForm = ref({ + name: '', + type: 'password' as 'password' | 'ssh_key', + username: '', + password: '', + sshKeyId: null as number | null, + domain: '', +}) +const credSaving = ref(false) +const credError = ref('') + async function load() { loading.value = true try { @@ -24,14 +36,65 @@ async function load() { function openCreate() { editingCred.value = null + credForm.value = { name: '', type: 'password', username: '', password: '', sshKeyId: null, domain: '' } + credError.value = '' showForm.value = true } function openEdit(cred: any) { editingCred.value = cred + credForm.value = { + name: cred.name || '', + type: cred.type || 'password', + username: cred.username || '', + password: '', + sshKeyId: cred.sshKeyId || null, + domain: cred.domain || '', + } + credError.value = '' showForm.value = true } +function closeForm() { + showForm.value = false + editingCred.value = null +} + +async function handleCredSubmit() { + if (!credForm.value.name.trim()) { + credError.value = 'Name is required.' + return + } + credSaving.value = true + credError.value = '' + try { + const payload: any = { + name: credForm.value.name.trim(), + type: credForm.value.type, + username: credForm.value.username.trim() || undefined, + domain: credForm.value.domain.trim() || undefined, + } + if (credForm.value.type === 'password' && credForm.value.password) { + payload.password = credForm.value.password + } + if (credForm.value.type === 'ssh_key' && credForm.value.sshKeyId) { + payload.sshKeyId = credForm.value.sshKeyId + } + + if (editingCred.value) { + await vault.updateCredential(editingCred.value.id, payload) + } else { + await vault.createCredential(payload) + } + await load() + closeForm() + } catch (e: any) { + credError.value = e?.data?.message || 'Save failed.' + } finally { + credSaving.value = false + } +} + async function confirmDelete(cred: any) { if (!confirm(`Delete credential "${cred.name}"?`)) return deletingId.value = cred.id @@ -109,11 +172,80 @@ onMounted(load) - + +
+
+
+

+ {{ editingCred ? 'Edit Credential' : 'New Credential' }} +

+ +
+ {{ credError }} +
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +

+ No SSH keys in vault yet. + Import one first. +

+
+ +
+ + +
+
+ +
+ + +
+
+
diff --git a/frontend/pages/vault/keys.vue b/frontend/pages/vault/keys.vue index 3574202..a08c4a6 100644 --- a/frontend/pages/vault/keys.vue +++ b/frontend/pages/vault/keys.vue @@ -6,6 +6,16 @@ const loading = ref(true) const showImport = ref(false) const deletingId = ref(null) +// Inline import form state +const importForm = ref({ + name: '', + privateKey: '', + publicKey: '', + passphrase: '', +}) +const importSaving = ref(false) +const importError = ref('') + async function load() { loading.value = true try { @@ -15,6 +25,49 @@ async function load() { } } +function openImport() { + importForm.value = { name: '', privateKey: '', publicKey: '', passphrase: '' } + importError.value = '' + showImport.value = true +} + +function closeImport() { + showImport.value = false +} + +async function handleImportSubmit() { + if (!importForm.value.name.trim() || !importForm.value.privateKey.trim()) { + importError.value = 'Name and private key are required.' + return + } + importSaving.value = true + importError.value = '' + try { + await vault.importKey({ + name: importForm.value.name.trim(), + privateKey: importForm.value.privateKey.trim(), + publicKey: importForm.value.publicKey.trim() || undefined, + passphrase: importForm.value.passphrase || undefined, + }) + await load() + closeImport() + } catch (e: any) { + importError.value = e?.data?.message || 'Import failed.' + } finally { + importSaving.value = false + } +} + +function handleFileUpload(field: 'privateKey' | 'publicKey', event: Event) { + const file = (event.target as HTMLInputElement).files?.[0] + if (!file) return + const reader = new FileReader() + reader.onload = (e) => { + importForm.value[field] = e.target?.result as string + } + reader.readAsText(file) +} + async function confirmDelete(key: any) { if (!confirm(`Delete SSH key "${key.name}"? This cannot be undone.`)) return deletingId.value = key.id @@ -41,7 +94,7 @@ onMounted(load) /

SSH Keys

- @@ -51,7 +104,7 @@ onMounted(load)

No SSH keys yet.

-
@@ -92,6 +145,66 @@ onMounted(load) - + +
+
+
+

Import SSH Key

+ +
+ {{ importError }} +
+ +
+
+ + +
+ +
+ +