wraith/frontend/components/vault/KeyImportDialog.vue
Vantz Stockwell 19183ee546 feat: vault management UI — SSH key import + credential CRUD
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 17:30:59 -04:00

126 lines
4.7 KiB
Vue

<script setup lang="ts">
const props = defineProps<{
visible: boolean
}>()
const emit = defineEmits<{
'update:visible': [boolean]
imported: []
}>()
const vault = useVault()
const form = ref({
name: '',
privateKey: '',
publicKey: '',
passphrase: '',
})
const saving = ref(false)
const error = ref('')
function close() {
emit('update:visible', false)
form.value = { name: '', privateKey: '', publicKey: '', passphrase: '' }
error.value = ''
}
async function handleSubmit() {
if (!form.value.name.trim() || !form.value.privateKey.trim()) {
error.value = 'Name and private key are required.'
return
}
saving.value = true
error.value = ''
try {
await vault.importKey({
name: form.value.name.trim(),
privateKey: form.value.privateKey.trim(),
publicKey: form.value.publicKey.trim() || undefined,
passphrase: form.value.passphrase || undefined,
})
emit('imported')
close()
} catch (e: any) {
error.value = e?.data?.message || 'Import failed.'
} finally {
saving.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) => {
form.value[field] = e.target?.result as string
}
reader.readAsText(file)
}
</script>
<template>
<Teleport to="body">
<div v-if="visible" class="fixed inset-0 z-50 flex items-center justify-center">
<div class="absolute inset-0 bg-black/60" @click="close" />
<div class="relative bg-gray-800 rounded-lg border border-gray-700 w-full max-w-lg mx-4 p-6 shadow-xl">
<h3 class="text-lg font-semibold text-white mb-4">Import SSH Key</h3>
<div v-if="error" class="mb-4 p-3 bg-red-900/50 border border-red-700 rounded text-red-300 text-sm">
{{ error }}
</div>
<div class="space-y-4">
<div>
<label class="block text-sm text-gray-400 mb-1">Key Name <span class="text-red-400">*</span></label>
<input v-model="form.name" type="text" placeholder="e.g. my-server-key"
class="w-full bg-gray-900 text-white px-3 py-2 rounded border border-gray-600 focus:border-wraith-500 focus:outline-none text-sm" />
</div>
<div>
<label class="block text-sm text-gray-400 mb-1">Private Key <span class="text-red-400">*</span></label>
<textarea v-model="form.privateKey" rows="6" placeholder="-----BEGIN RSA PRIVATE KEY-----..."
class="w-full bg-gray-900 text-white px-3 py-2 rounded border border-gray-600 focus:border-wraith-500 focus:outline-none text-sm font-mono resize-none" />
<div class="mt-1 flex items-center gap-2">
<label class="text-xs text-gray-500 cursor-pointer hover:text-gray-300">
<input type="file" class="hidden" accept=".pem,.key,id_rsa,id_ed25519,id_ecdsa"
@change="handleFileUpload('privateKey', $event)" />
Upload from file
</label>
</div>
</div>
<div>
<label class="block text-sm text-gray-400 mb-1">Public Key <span class="text-gray-600">(optional)</span></label>
<textarea v-model="form.publicKey" rows="2" placeholder="ssh-rsa AAAA..."
class="w-full bg-gray-900 text-white px-3 py-2 rounded border border-gray-600 focus:border-wraith-500 focus:outline-none text-sm font-mono resize-none" />
<div class="mt-1">
<label class="text-xs text-gray-500 cursor-pointer hover:text-gray-300">
<input type="file" class="hidden" accept=".pub"
@change="handleFileUpload('publicKey', $event)" />
Upload from file
</label>
</div>
</div>
<div>
<label class="block text-sm text-gray-400 mb-1">Passphrase <span class="text-gray-600">(optional)</span></label>
<input v-model="form.passphrase" type="password" placeholder="Leave blank if unencrypted"
class="w-full bg-gray-900 text-white px-3 py-2 rounded border border-gray-600 focus:border-wraith-500 focus:outline-none text-sm" />
</div>
</div>
<div class="flex justify-end gap-3 mt-6">
<button @click="close" class="px-4 py-2 text-sm text-gray-400 hover:text-white rounded border border-gray-600 hover:border-gray-400">
Cancel
</button>
<button @click="handleSubmit" :disabled="saving"
class="px-4 py-2 text-sm bg-wraith-600 hover:bg-wraith-700 text-white rounded disabled:opacity-50">
{{ saving ? 'Importing...' : 'Import Key' }}
</button>
</div>
</div>
</div>
</Teleport>
</template>