feat: SSH key file browser — Browse button to load keys from disk
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 2m54s

Adds a file picker next to the Private Key textarea in the credential
dialog. Users can browse for key files (.pem, .key, id_rsa, id_ed25519,
etc.) instead of copy-pasting PEM content. Uses standard FileReader API
— no additional Tauri plugins needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vantz Stockwell 2026-03-24 19:20:00 -04:00
parent 4a0c2c9790
commit f825692ecc

View File

@ -270,10 +270,27 @@
</div> </div>
<div> <div>
<label class="block text-xs text-[var(--wraith-text-secondary)] mb-1">Private Key (PEM)</label> <label class="block text-xs text-[var(--wraith-text-secondary)] mb-1">Private Key (PEM)</label>
<div class="flex gap-2 mb-1">
<button
type="button"
class="px-3 py-1.5 text-xs rounded bg-[#21262d] border border-[#30363d] text-[var(--wraith-text-secondary)] hover:text-[var(--wraith-text-primary)] hover:border-[var(--wraith-accent-blue)] transition-colors cursor-pointer"
@click="browseKeyFile"
>
Browse...
</button>
<span v-if="keyFileName" class="text-xs text-[var(--wraith-text-muted)] self-center truncate">{{ keyFileName }}</span>
</div>
<input
ref="keyFileInputRef"
type="file"
class="hidden"
accept=".pem,.key,.pub,.id_rsa,.id_ed25519,.id_ecdsa,.ppk"
@change="loadKeyFile"
/>
<textarea <textarea
v-model="newCred.privateKeyPEM" v-model="newCred.privateKeyPEM"
rows="5" rows="5"
placeholder="-----BEGIN OPENSSH PRIVATE KEY-----&#10;...&#10;-----END OPENSSH PRIVATE KEY-----" placeholder="Paste key or use Browse to load from file"
class="w-full px-3 py-2 text-sm rounded bg-[#161b22] border border-[#30363d] text-[var(--wraith-text-primary)] placeholder-[var(--wraith-text-muted)] outline-none focus:border-[var(--wraith-accent-blue)] transition-colors resize-none font-mono" class="w-full px-3 py-2 text-sm rounded bg-[#161b22] border border-[#30363d] text-[var(--wraith-text-primary)] placeholder-[var(--wraith-text-muted)] outline-none focus:border-[var(--wraith-accent-blue)] transition-colors resize-none font-mono"
spellcheck="false" spellcheck="false"
/> />
@ -385,6 +402,25 @@ const newCred = ref<NewCredForm>({
passphrase: "", passphrase: "",
}); });
// SSH key file picker
const keyFileInputRef = ref<HTMLInputElement | null>(null);
const keyFileName = ref("");
function browseKeyFile(): void {
keyFileInputRef.value?.click();
}
function loadKeyFile(event: Event): void {
const file = (event.target as HTMLInputElement).files?.[0];
if (!file) return;
keyFileName.value = file.name;
const reader = new FileReader();
reader.onload = () => {
newCred.value.privateKeyPEM = (reader.result as string).trim();
};
reader.readAsText(file);
}
const form = ref<ConnectionForm>({ const form = ref<ConnectionForm>({
name: "", name: "",
hostname: "", hostname: "",
@ -431,6 +467,7 @@ function setProtocol(protocol: "ssh" | "rdp"): void {
function resetNewCredForm(): void { function resetNewCredForm(): void {
newCred.value = { name: "", username: "", password: "", privateKeyPEM: "", passphrase: "" }; newCred.value = { name: "", username: "", password: "", privateKeyPEM: "", passphrase: "" };
newCredError.value = ""; newCredError.value = "";
keyFileName.value = "";
} }
async function deleteSelectedCredential(): Promise<void> { async function deleteSelectedCredential(): Promise<void> {