Files
ssh-manager/frontend/src/components/ConnectionForm.vue

239 lines
8.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import type { Connection, ConnectionCreateRequest, AuthType } from '../api/connections'
import { X } from 'lucide-vue-next'
const props = defineProps<{
connection: Connection | null
onSave?: (data: ConnectionCreateRequest) => Promise<void>
}>()
const emit = defineEmits<{
save: [data: ConnectionCreateRequest]
close: []
}>()
const name = ref('')
const host = ref('')
const port = ref(22)
const username = ref('')
const authType = ref<AuthType>('PASSWORD')
const password = ref('')
const privateKey = ref('')
const passphrase = ref('')
const isEdit = computed(() => !!props.connection)
watch(
() => props.connection,
(c) => {
if (c) {
name.value = c.name
host.value = c.host
port.value = c.port
username.value = c.username
authType.value = c.authType
password.value = ''
privateKey.value = ''
passphrase.value = ''
} else {
name.value = ''
host.value = ''
port.value = 22
username.value = ''
authType.value = 'PASSWORD'
password.value = ''
privateKey.value = ''
passphrase.value = ''
}
},
{ immediate: true }
)
const error = ref('')
const loading = ref(false)
async function handleSubmit() {
error.value = ''
if (!name.value.trim()) {
error.value = '请填写名称'
return
}
if (!host.value.trim()) {
error.value = '请填写主机'
return
}
if (!username.value.trim()) {
error.value = '请填写用户名'
return
}
if (authType.value === 'PASSWORD' && !isEdit.value && !password.value) {
error.value = '请填写密码'
return
}
if (authType.value === 'PRIVATE_KEY' && !isEdit.value && !privateKey.value.trim()) {
error.value = '请填写私钥'
return
}
loading.value = true
error.value = ''
const data: ConnectionCreateRequest = {
name: name.value.trim(),
host: host.value.trim(),
port: port.value,
username: username.value.trim(),
authType: authType.value,
}
if (authType.value === 'PASSWORD' && password.value) {
data.password = password.value
}
if (authType.value === 'PRIVATE_KEY') {
if (privateKey.value.trim()) data.privateKey = privateKey.value.trim()
if (passphrase.value) data.passphrase = passphrase.value
}
try {
if (props.onSave) {
await props.onSave(data)
} else {
emit('save', data)
}
} catch (e: unknown) {
const err = e as { response?: { data?: { message?: string } } }
error.value = err.response?.data?.message || '保存失败'
} finally {
loading.value = false
}
}
</script>
<template>
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4" @click.self="emit('close')">
<div
class="w-full max-w-lg bg-slate-800 rounded-xl border border-slate-700 shadow-xl"
role="dialog"
aria-modal="true"
aria-labelledby="form-title"
>
<div class="flex items-center justify-between p-4 border-b border-slate-700">
<h2 id="form-title" class="text-lg font-semibold text-slate-100">
{{ isEdit ? '编辑连接' : '新建连接' }}
</h2>
<button
@click="emit('close')"
class="p-2 rounded-lg text-slate-400 hover:bg-slate-700 hover:text-slate-100 transition-colors duration-200 cursor-pointer"
aria-label="关闭"
>
<X class="w-5 h-5" aria-hidden="true" />
</button>
</div>
<form @submit.prevent="handleSubmit" class="p-4 space-y-4">
<div>
<label for="name" class="block text-sm font-medium text-slate-300 mb-1">名称</label>
<input
id="name"
v-model="name"
type="text"
class="w-full px-4 py-2.5 rounded-lg bg-slate-700 border border-slate-600 text-slate-100 focus:outline-none focus:ring-2 focus:ring-cyan-500"
placeholder="我的服务器"
/>
</div>
<div class="grid grid-cols-3 gap-4">
<div class="col-span-2">
<label for="host" class="block text-sm font-medium text-slate-300 mb-1">主机</label>
<input
id="host"
v-model="host"
type="text"
class="w-full px-4 py-2.5 rounded-lg bg-slate-700 border border-slate-600 text-slate-100 focus:outline-none focus:ring-2 focus:ring-cyan-500"
placeholder="192.168.1.1"
/>
</div>
<div>
<label for="port" class="block text-sm font-medium text-slate-300 mb-1">端口</label>
<input
id="port"
v-model.number="port"
type="number"
min="1"
max="65535"
class="w-full px-4 py-2.5 rounded-lg bg-slate-700 border border-slate-600 text-slate-100 focus:outline-none focus:ring-2 focus:ring-cyan-500"
/>
</div>
</div>
<div>
<label for="username" class="block text-sm font-medium text-slate-300 mb-1">用户名</label>
<input
id="username"
v-model="username"
type="text"
class="w-full px-4 py-2.5 rounded-lg bg-slate-700 border border-slate-600 text-slate-100 focus:outline-none focus:ring-2 focus:ring-cyan-500"
placeholder="root"
/>
</div>
<div>
<label class="block text-sm font-medium text-slate-300 mb-2">认证方式</label>
<div class="flex gap-4">
<label class="flex items-center gap-2 cursor-pointer">
<input v-model="authType" type="radio" value="PASSWORD" class="rounded" />
<span class="text-slate-300">密码</span>
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input v-model="authType" type="radio" value="PRIVATE_KEY" class="rounded" />
<span class="text-slate-300">私钥</span>
</label>
</div>
</div>
<div v-if="authType === 'PASSWORD'">
<label for="password" class="block text-sm font-medium text-slate-300 mb-1">
密码 {{ isEdit ? '(留空则不修改)' : '' }}
</label>
<input
id="password"
v-model="password"
type="password"
class="w-full px-4 py-2.5 rounded-lg bg-slate-700 border border-slate-600 text-slate-100 focus:outline-none focus:ring-2 focus:ring-cyan-500"
:placeholder="isEdit ? '••••••••' : ''"
/>
</div>
<div v-if="authType === 'PRIVATE_KEY'" class="space-y-2">
<label for="privateKey" class="block text-sm font-medium text-slate-300 mb-1">
私钥 {{ isEdit ? '(留空则不修改)' : '' }}
</label>
<textarea
id="privateKey"
v-model="privateKey"
rows="6"
class="w-full px-4 py-2.5 rounded-lg bg-slate-700 border border-slate-600 text-slate-100 font-mono text-sm focus:outline-none focus:ring-2 focus:ring-cyan-500"
placeholder="-----BEGIN OPENSSH PRIVATE KEY-----&#10;...&#10;-----END OPENSSH PRIVATE KEY-----"
></textarea>
<label for="passphrase" class="block text-sm font-medium text-slate-300 mb-1">私钥口令可选</label>
<input
id="passphrase"
v-model="passphrase"
type="password"
class="w-full px-4 py-2.5 rounded-lg bg-slate-700 border border-slate-600 text-slate-100 focus:outline-none focus:ring-2 focus:ring-cyan-500"
/>
</div>
<p v-if="error" class="text-sm text-red-400">{{ error }}</p>
<div class="flex justify-end gap-2 pt-2">
<button
type="button"
@click="emit('close')"
class="px-4 py-2 rounded-lg text-slate-300 hover:bg-slate-700 transition-colors duration-200 cursor-pointer"
>
取消
</button>
<button
type="submit"
:disabled="loading"
class="px-4 py-2 rounded-lg bg-cyan-600 hover:bg-cyan-500 text-white font-medium transition-colors duration-200 disabled:opacity-50 cursor-pointer"
>
{{ isEdit ? '更新' : '创建' }}
</button>
</div>
</form>
</div>
</div>
</template>