feat: ✨ edit identities
This commit is contained in:
+141
-7
@@ -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,13 +618,22 @@ 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>
|
||||||
<button
|
<div class="flex gap-1">
|
||||||
type="button"
|
<button
|
||||||
class="shrink-0 text-red-400/70 hover:underline"
|
type="button"
|
||||||
@click="deleteIdentityRow(i.id)"
|
class="shrink-0 text-slate-400/70 hover:text-slate-300 hover:underline"
|
||||||
>
|
@click="openEditIdentity(i)"
|
||||||
×
|
>
|
||||||
</button>
|
Edit
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="shrink-0 text-red-400/70 hover:underline"
|
||||||
|
@click="deleteIdentityRow(i.id)"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</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"
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user