feat: implement VPN platform management and real protocol services
This commit is contained in:
commit
58c036c34c
45 files changed
+9244
No files matched your search
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<el-card class="page-card" shadow="never" v-loading="loading">
|
||||
<template #header>
|
||||
<div style="display:flex;justify-content:space-between;align-items:center">
|
||||
<b>编辑专线</b>
|
||||
<el-button @click="router.push('/tunnels')">返回列表</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-alert type="info" :closable="false" style="margin-bottom:18px"
|
||||
title="密码、PSK、Token 等敏感字段不会回显;留空表示继续使用原值。" />
|
||||
|
||||
<el-form v-if="form" label-width="170px" label-position="left" style="max-width:720px">
|
||||
<el-form-item label="专线名称" required>
|
||||
<el-input v-model="form.name" />
|
||||
</el-form-item>
|
||||
<el-form-item label="协议类型">
|
||||
<el-tag :color="protocolColor(protocol)" effect="dark" style="border:none">{{ protocolDisplay(protocol) }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item v-for="field in visibleFields" :key="field.key" :label="field.label" :required="field.required">
|
||||
<el-input-number v-if="field.type === 'number'" v-model="form.config[field.key]" :min="0" :max="65535" />
|
||||
<el-select v-else-if="field.type === 'select'" v-model="form.config[field.key]" style="width:100%">
|
||||
<el-option v-for="option in field.options" :key="option" :label="option" :value="option" />
|
||||
</el-select>
|
||||
<el-input v-else-if="field.type === 'password'" v-model="form.config[field.key]" type="password"
|
||||
show-password :placeholder="field.placeholder || '留空保持原值'" />
|
||||
<el-input v-else v-model="form.config[field.key]" :placeholder="field.placeholder || field.default" />
|
||||
<div v-if="field.tip" class="field-tip">{{ field.tip }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="到期时间">
|
||||
<el-date-picker v-model="form.expire_at" type="datetime" clearable placeholder="留空为永久" style="width:100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="自动启动"><el-switch v-model="form.auto_start" /></el-form-item>
|
||||
<el-form-item label="备注"><el-input v-model="form.remark" type="textarea" :rows="3" /></el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="saving" @click="save">保存配置</el-button>
|
||||
<el-button @click="router.push('/tunnels')">取消</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import api from '../api'
|
||||
import { CUSTOM_FIELDS, PROTOCOL_META, protocolColor, protocolDisplay } from '../protocols'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const loading = ref(true)
|
||||
const saving = ref(false)
|
||||
const protocol = ref('')
|
||||
const form = ref(null)
|
||||
|
||||
const fields = computed(() => PROTOCOL_META[protocol.value]?.fields || CUSTOM_FIELDS)
|
||||
const visibleFields = computed(() => fields.value.filter(field => {
|
||||
if (!field.showIf) return true
|
||||
return Object.entries(field.showIf).every(([key, value]) => Array.isArray(value)
|
||||
? value.includes(form.value.config[key])
|
||||
: form.value.config[key] === value)
|
||||
}))
|
||||
|
||||
function parseConfig(raw) {
|
||||
try { return JSON.parse(raw || '{}') } catch { return {} }
|
||||
}
|
||||
|
||||
async function load() {
|
||||
try {
|
||||
const { data } = await api.get(`/tunnels/${route.params.id}`)
|
||||
const tunnel = data.tunnel
|
||||
protocol.value = tunnel.protocol
|
||||
const config = parseConfig(tunnel.config)
|
||||
for (const field of (PROTOCOL_META[protocol.value]?.fields || CUSTOM_FIELDS)) {
|
||||
if (config[field.key] === undefined && field.default !== undefined) config[field.key] = field.default
|
||||
}
|
||||
form.value = {
|
||||
name: tunnel.name,
|
||||
remark: tunnel.remark,
|
||||
auto_start: tunnel.auto_start,
|
||||
expire_at: tunnel.expire_at,
|
||||
config,
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error(error.response?.data?.error || '加载专线失败')
|
||||
router.push('/tunnels')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function save() {
|
||||
if (!form.value.name) return ElMessage.warning('请填写专线名称')
|
||||
saving.value = true
|
||||
try {
|
||||
await api.put(`/tunnels/${route.params.id}`, form.value)
|
||||
ElMessage.success('配置已保存')
|
||||
router.push('/tunnels')
|
||||
} catch (error) {
|
||||
ElMessage.error(error.response?.data?.error || '保存失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.field-tip { margin-top: 4px; color: #909399; font-size: 12px; line-height: 1.4; }
|
||||
</style>
|
||||
Reference in new issue
Block a user