feat: edit identities

This commit is contained in:
2026-05-14 11:35:59 +00:00
parent 5bba2947c4
commit 035f871b00
2 changed files with 160 additions and 7 deletions
+134
View File
@@ -37,6 +37,7 @@ const sidebarOpen = ref(true);
let searchDebounceTimer = 0; let searchDebounceTimer = 0;
const showIdentityForm = ref(false); const showIdentityForm = ref(false);
const showEditIdentity = ref(false);
const showHostForm = ref(false); const showHostForm = ref(false);
const showFolderForm = ref(false); const showFolderForm = ref(false);
const showEditHost = ref(false); const showEditHost = ref(false);
@@ -55,6 +56,15 @@ const identityForm = ref({
private_key: "", private_key: "",
key_passphrase: "", key_passphrase: "",
}); });
const editIdentityForm = ref({
id: 0,
label: "",
auth_type: "password" as "password" | "publickey",
ssh_username: "",
password: "",
private_key: "",
key_passphrase: "",
});
const hostForm = ref({ const hostForm = ref({
label: "", label: "",
hostname: "", hostname: "",
@@ -227,6 +237,50 @@ async function submitIdentity() {
await refreshData(); await refreshData();
} }
async function openEditIdentity(i: IdentityRow) {
editIdentityForm.value = {
id: i.id,
label: i.label,
auth_type: i.auth_type,
ssh_username: "",
password: "",
private_key: "",
key_passphrase: "",
};
showEditIdentity.value = true;
}
async function submitEditIdentity() {
const f = editIdentityForm.value;
const body: Partial<{
label: string;
ssh_username: string;
password: string;
private_key: string;
key_passphrase: string;
}> = {
label: f.label.trim(),
};
if (f.ssh_username.trim()) body.ssh_username = f.ssh_username.trim();
if (f.auth_type === "password" && f.password) body.password = f.password;
else if (f.auth_type === "publickey") {
if (f.private_key) body.private_key = f.private_key;
if (f.key_passphrase !== undefined) body.key_passphrase = f.key_passphrase;
}
await api.updateIdentity(f.id, body);
showEditIdentity.value = false;
editIdentityForm.value = {
id: 0,
label: "",
auth_type: "password",
ssh_username: "",
password: "",
private_key: "",
key_passphrase: "",
};
await refreshData();
}
async function submitHost() { async function submitHost() {
const f = hostForm.value; const f = hostForm.value;
await api.createHost({ await api.createHost({
@@ -564,6 +618,14 @@ async function deleteIdentityRow(id: number) {
class="flex items-center justify-between gap-1 py-0.5" class="flex items-center justify-between gap-1 py-0.5"
> >
<span class="truncate">{{ i.label }} ({{ i.auth_type }})</span> <span class="truncate">{{ i.label }} ({{ i.auth_type }})</span>
<div class="flex gap-1">
<button
type="button"
class="shrink-0 text-slate-400/70 hover:text-slate-300 hover:underline"
@click="openEditIdentity(i)"
>
Edit
</button>
<button <button
type="button" type="button"
class="shrink-0 text-red-400/70 hover:underline" class="shrink-0 text-red-400/70 hover:underline"
@@ -571,6 +633,7 @@ async function deleteIdentityRow(id: number) {
> >
× ×
</button> </button>
</div>
</li> </li>
</ul> </ul>
</div> </div>
@@ -764,6 +827,77 @@ async function deleteIdentityRow(id: number) {
</form> </form>
</div> </div>
<div
v-if="showEditIdentity"
class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4"
@click.self="showEditIdentity = false"
>
<form
class="max-h-[90vh] w-full max-w-lg overflow-auto rounded-xl border border-slate-800 bg-surface-raised p-6 shadow-xl"
@submit.prevent="submitEditIdentity"
>
<h2 class="text-lg font-semibold text-white">Edit identity</h2>
<label class="mt-4 block text-xs uppercase text-slate-500">Label</label>
<input
v-model="editIdentityForm.label"
required
class="mt-1 w-full rounded border border-slate-700 bg-surface-overlay px-2 py-1.5 text-sm"
/>
<label class="mt-3 block text-xs uppercase text-slate-500">Auth type</label>
<div class="mt-1 rounded border border-slate-700 bg-surface-overlay px-2 py-1.5 text-sm text-slate-400">
{{ editIdentityForm.auth_type }}
</div>
<p class="mt-1 text-[10px] text-slate-500">Auth type cannot be changed</p>
<label class="mt-3 block text-xs uppercase text-slate-500">SSH username</label>
<input
v-model="editIdentityForm.ssh_username"
placeholder="Leave empty to keep unchanged"
autocomplete="off"
class="mt-1 w-full rounded border border-slate-700 bg-surface-overlay px-2 py-1.5 text-sm"
/>
<template v-if="editIdentityForm.auth_type === 'password'">
<label class="mt-3 block text-xs uppercase text-slate-500">Password (optional)</label>
<input
v-model="editIdentityForm.password"
type="password"
placeholder="Leave empty to keep current password"
class="mt-1 w-full rounded border border-slate-700 bg-surface-overlay px-2 py-1.5 text-sm"
/>
</template>
<template v-else>
<label class="mt-3 block text-xs uppercase text-slate-500">Private key (PEM, optional)</label>
<textarea
v-model="editIdentityForm.private_key"
placeholder="Leave empty to keep current key"
rows="6"
class="mt-1 w-full rounded border border-slate-700 bg-surface-overlay px-2 py-1.5 font-mono text-xs"
/>
<label class="mt-3 block text-xs uppercase text-slate-500">Key passphrase (optional)</label>
<input
v-model="editIdentityForm.key_passphrase"
type="password"
placeholder="Leave empty to remove passphrase"
class="mt-1 w-full rounded border border-slate-700 bg-surface-overlay px-2 py-1.5 text-sm"
/>
</template>
<div class="mt-6 flex justify-end gap-2">
<button
type="button"
class="rounded-lg px-4 py-2 text-sm text-slate-400 hover:bg-slate-800"
@click="showEditIdentity = false"
>
Cancel
</button>
<button
type="submit"
class="rounded-lg bg-accent px-4 py-2 text-sm font-medium text-slate-950"
>
Save
</button>
</div>
</form>
</div>
<div <div
v-if="showHostForm" v-if="showHostForm"
class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4" class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4"
+19
View File
@@ -153,6 +153,25 @@ export const api = {
return handle(res); return handle(res);
}, },
async updateIdentity(
id: number,
body: Partial<{
label: string;
ssh_username: string;
password: string;
private_key: string;
key_passphrase: string;
}>,
): Promise<void> {
const res = await fetch(`/api/identities/${id}`, {
method: "PATCH",
credentials: "include",
headers: jsonHeaders,
body: JSON.stringify(body),
});
await handle(res);
},
async deleteIdentity(id: number): Promise<void> { async deleteIdentity(id: number): Promise<void> {
const res = await fetch(`/api/identities/${id}`, { const res = await fetch(`/api/identities/${id}`, {
method: "DELETE", method: "DELETE",