126 lines
4.7 KiB
Vue
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>
|