mirror of https://gitee.com/godoos/godoos.git
9 changed files with 1355 additions and 236 deletions
@ -0,0 +1,204 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import { notifyError } from '@/util/msg'; |
||||
|
import { Plus } from '@element-plus/icons-vue' |
||||
|
import { ref, computed } from "vue"; |
||||
|
|
||||
|
interface ProxyItem { |
||||
|
id: number; |
||||
|
port: string; |
||||
|
domain: string; |
||||
|
type: string; |
||||
|
name: string; |
||||
|
serverAddr: string; |
||||
|
serverPort: string; |
||||
|
} |
||||
|
const localKey = "godoos_net_proxy" |
||||
|
const getProxies = (): ProxyItem[] => { |
||||
|
const proxies = localStorage.getItem(localKey); |
||||
|
return proxies ? JSON.parse(proxies) : []; |
||||
|
}; |
||||
|
|
||||
|
const saveProxies = (proxies: ProxyItem[]) => { |
||||
|
localStorage.setItem(localKey, JSON.stringify(proxies)); |
||||
|
}; |
||||
|
|
||||
|
const proxies = ref<ProxyItem[]>(getProxies()); |
||||
|
const proxyData = ref<ProxyItem>({ |
||||
|
id: Date.now(), |
||||
|
type: "http", |
||||
|
name: "", |
||||
|
port: "", |
||||
|
domain: "", |
||||
|
serverAddr: "", |
||||
|
serverPort: "", |
||||
|
}); |
||||
|
const proxyDialogShow = ref(false); |
||||
|
const isEditing = ref(false); |
||||
|
const pwdRef = ref<any>(null); |
||||
|
const types = ref([ |
||||
|
{ label: 'HTTP', value: 'http' }, |
||||
|
{ label: '静态文件访问', value: 'file' }, |
||||
|
{ label: '点对点穿透', value: 'p2p' }, |
||||
|
{ label: 'SSH', value: 'ssh' }, |
||||
|
{ label: 'SOCKS5', value: 'socks5' }, |
||||
|
|
||||
|
]) |
||||
|
|
||||
|
const addProxy = () => { |
||||
|
if (pwdRef.value.validate()) { |
||||
|
const isNameDuplicate = proxies.value.some(p => p.name === proxyData.value.name); |
||||
|
if (isNameDuplicate) { |
||||
|
notifyError("代理名称已存在"); |
||||
|
return; |
||||
|
} |
||||
|
proxies.value.push({ ...proxyData.value }); |
||||
|
saveProxies(proxies.value); |
||||
|
proxyDialogShow.value = false; |
||||
|
proxyData.value = { id: Date.now(), port: "", domain: "", type: "http", name: "", serverAddr: "", serverPort: "" }; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const editProxy = (proxy: ProxyItem) => { |
||||
|
proxyData.value = { ...proxy }; |
||||
|
isEditing.value = true; |
||||
|
proxyDialogShow.value = true; |
||||
|
}; |
||||
|
|
||||
|
const updateProxy = () => { |
||||
|
if (pwdRef.value.validate()) { |
||||
|
const index = proxies.value.findIndex(p => p.id === proxyData.value.id); |
||||
|
if (index !== -1) { |
||||
|
const isNameDuplicate = proxies.value.some((p, i) => p.name === proxyData.value.name && i !== index); |
||||
|
if (isNameDuplicate) { |
||||
|
notifyError("代理名称已存在"); |
||||
|
return; |
||||
|
} |
||||
|
proxies.value[index] = { ...proxyData.value }; |
||||
|
saveProxies(proxies.value); |
||||
|
proxyDialogShow.value = false; |
||||
|
proxyData.value = { id: Date.now(), port: "", domain: "", type: "http", name: "", serverAddr: "", serverPort: "" }; |
||||
|
isEditing.value = false; |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const deleteProxy = (id: number) => { |
||||
|
proxies.value = proxies.value.filter(p => p.id !== id); |
||||
|
saveProxies(proxies.value); |
||||
|
}; |
||||
|
|
||||
|
const saveProxy = () => { |
||||
|
pwdRef.value.validate((valid: boolean) => { |
||||
|
if (valid) { |
||||
|
if (isEditing.value) { |
||||
|
updateProxy(); |
||||
|
} else { |
||||
|
addProxy(); |
||||
|
} |
||||
|
} else { |
||||
|
console.log('表单验证失败'); |
||||
|
} |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
const proxyRules = { |
||||
|
name: [ |
||||
|
{ required: true, message: '代理名称不能为空', trigger: 'blur' }, |
||||
|
{ min: 1, max: 20, message: '代理名称长度在 1 到 20 个字符', trigger: 'blur' }, |
||||
|
{ pattern: /^[a-zA-Z]+$/, message: '代理名称只能包含英文字符', trigger: 'blur' } |
||||
|
], |
||||
|
port: [ |
||||
|
{ required: true, message: '端口不能为空', trigger: 'blur' }, |
||||
|
{ pattern: /^(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{1,3}|[0-9])$/, message: '请输入有效的端口号(1-65535)', trigger: 'blur' } |
||||
|
], |
||||
|
domain: [ |
||||
|
{ required: true, message: '代理域名不能为空', trigger: 'blur' }, |
||||
|
{ pattern: /^(https?:\/\/)?(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(:\d{1,5})?(\/[^\s]*)?$/, message: '请输入有效的域名格式', trigger: 'blur' } |
||||
|
], |
||||
|
serverAddr: [ |
||||
|
{ required: true, message: '服务器地址不能为空', trigger: 'blur' }, |
||||
|
{ pattern: /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/, message: '请输入有效的IP地址格式', trigger: 'blur' } |
||||
|
], |
||||
|
serverPort: [ |
||||
|
{ required: true, message: '服务器端口不能为空', trigger: 'blur' }, |
||||
|
{ pattern: /^(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{1,3}|[0-9])$/, message: '请输入有效的服务器端口号(1-65535)', trigger: 'blur' } |
||||
|
] |
||||
|
}; |
||||
|
|
||||
|
const pageSize = 10; |
||||
|
const currentPage = ref(1); |
||||
|
|
||||
|
const paginatedProxies = computed(() => { |
||||
|
const start = (currentPage.value - 1) * pageSize; |
||||
|
const end = start + pageSize; |
||||
|
return proxies.value.slice(start, end); |
||||
|
}); |
||||
|
|
||||
|
const totalPages = computed(() => Math.ceil(proxies.value.length / pageSize)); |
||||
|
|
||||
|
const nextPage = () => { |
||||
|
if (currentPage.value < totalPages.value) { |
||||
|
currentPage.value++; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const prevPage = () => { |
||||
|
if (currentPage.value > 1) { |
||||
|
currentPage.value--; |
||||
|
} |
||||
|
}; |
||||
|
</script> |
||||
|
<template> |
||||
|
<div> |
||||
|
<el-row justify="end"> |
||||
|
<el-button type="primary" :icon="Plus" circle @click="proxyDialogShow = true" /> |
||||
|
</el-row> |
||||
|
<el-table :data="paginatedProxies" style="width: 98%;border:none"> |
||||
|
<el-table-column prop="name" label="名称" width="100" /> |
||||
|
<el-table-column prop="type" label="类型" width="80" /> |
||||
|
<el-table-column prop="port" label="本地端口" width="80" /> |
||||
|
<el-table-column prop="domain" label="代理域名" width="180" /> |
||||
|
<el-table-column label="操作"> |
||||
|
<template #default="scope"> |
||||
|
<el-button size="small" @click="editProxy(scope.row)">编辑</el-button> |
||||
|
<el-button size="small" type="danger" @click="deleteProxy(scope.row.id)">删除</el-button> |
||||
|
</template> |
||||
|
</el-table-column> |
||||
|
</el-table> |
||||
|
<el-pagination v-if="totalPages > 1" layout="prev, pager, next" :total="getProxies().length" |
||||
|
:page-size="pageSize" v-model:current-page="currentPage" @next-click="nextPage" @prev-click="prevPage" /> |
||||
|
<el-dialog v-model="proxyDialogShow" :title="isEditing ? '编辑代理' : '添加代理'" width="400px"> |
||||
|
<span> |
||||
|
<el-form :model="proxyData" :rules="proxyRules" ref="pwdRef"> |
||||
|
<el-form-item label="代理类型" prop="type"> |
||||
|
<el-select v-model="proxyData.type" placeholder="代理类型"> |
||||
|
<el-option v-for="type in types" :key="type.value" :label="type.label" |
||||
|
:value="type.value" /> |
||||
|
</el-select> |
||||
|
</el-form-item> |
||||
|
<el-form-item label="代理名称" prop="name"> |
||||
|
<el-input v-model="proxyData.name" /> |
||||
|
</el-form-item> |
||||
|
|
||||
|
<el-form-item label="本地端口" prop="port"> |
||||
|
<el-input v-model="proxyData.port" /> |
||||
|
</el-form-item> |
||||
|
<el-form-item label="代理域名" prop="domain"> |
||||
|
<el-input v-model="proxyData.domain" /> |
||||
|
</el-form-item> |
||||
|
<el-form-item label="服务器地址" prop="serverAddr"> |
||||
|
<el-input v-model="proxyData.serverAddr" /> |
||||
|
</el-form-item> |
||||
|
<el-form-item label="服务器端口" prop="serverPort"> |
||||
|
<el-input v-model="proxyData.serverPort" /> |
||||
|
</el-form-item> |
||||
|
<el-form-item> |
||||
|
<el-button type="primary" @click="saveProxy" style="margin: 0 auto;"> |
||||
|
确认 |
||||
|
</el-button> |
||||
|
</el-form-item> |
||||
|
</el-form> |
||||
|
</span> |
||||
|
</el-dialog> |
||||
|
</div> |
||||
|
</template> |
@ -1,204 +1,546 @@ |
|||||
<script setup lang="ts"> |
<script setup lang="ts"> |
||||
import { notifyError } from '@/util/msg'; |
import { useProxyStore } from "@/stores/proxy"; |
||||
import { Plus } from '@element-plus/icons-vue' |
import { notifyError, notifySuccess } from "@/util/msg"; |
||||
import { ref, computed } from "vue"; |
import { Plus } from "@element-plus/icons-vue"; |
||||
|
import { ref } from "vue"; |
||||
interface ProxyItem { |
|
||||
id: number; |
const proxyStore = useProxyStore(); |
||||
port: string; |
|
||||
domain: string; |
const proxyDialogShow = ref(false); |
||||
type: string; |
const isEditing = ref(false); |
||||
name: string; |
const pwdRef = ref<any>(null); |
||||
serverAddr: string; |
const internalPortDialogVisible = ref(false); |
||||
serverPort: string; |
|
||||
} |
// 定义表单验证规则 |
||||
const localKey = "godoos_net_proxy" |
const proxyRules = { |
||||
const getProxies = (): ProxyItem[] => { |
name: [{ required: true, message: "请输入代理名称", trigger: "blur" }], |
||||
const proxies = localStorage.getItem(localKey); |
port: [ |
||||
return proxies ? JSON.parse(proxies) : []; |
{ required: true, message: "请输入端口", trigger: "blur" }, |
||||
}; |
{ type: "number", message: "端口必须是数字", trigger: "blur" }, |
||||
|
], |
||||
const saveProxies = (proxies: ProxyItem[]) => { |
domain: [{ required: true, message: "请输入域名", trigger: "blur" }], |
||||
localStorage.setItem(localKey, JSON.stringify(proxies)); |
// 其他字段的验证规则... |
||||
}; |
}; |
||||
|
|
||||
const proxies = ref<ProxyItem[]>(getProxies()); |
const addProxy = () => { |
||||
const proxyData = ref<ProxyItem>({ |
if (pwdRef.value.validate()) { |
||||
id: Date.now(), |
// const isNameDuplicate = proxyStore.proxies.some( |
||||
type: "http", |
// (p) => p.name === proxyStore.proxyData.name |
||||
name: "", |
// ); |
||||
port: "", |
// if (isNameDuplicate) { |
||||
domain: "", |
// notifyError("代理名称已存在"); |
||||
serverAddr: "", |
// return; |
||||
serverPort: "", |
// } |
||||
}); |
// proxyStore.addProxy({ ...proxyStore.proxyData }); |
||||
const proxyDialogShow = ref(false); |
// proxyDialogShow.value = false; |
||||
const isEditing = ref(false); |
// proxyStore.resetProxyData(); |
||||
const pwdRef = ref<any>(null); |
// 调用 store 中的 createFrpcConfig 方法 |
||||
const types = ref([ |
proxyStore |
||||
{ label: 'HTTP', value: 'http' }, |
.createFrpcConfig() |
||||
{ label: '静态文件访问', value: 'file' }, |
.then(() => { |
||||
{ label: '点对点穿透', value: 'p2p' }, |
proxyDialogShow.value = false; |
||||
{ label: 'SSH', value: 'ssh' }, |
proxyStore.resetProxyData(); |
||||
{ label: 'SOCKS5', value: 'socks5' }, |
notifySuccess("代理配置已成功创建"); |
||||
|
}) |
||||
]) |
.catch((error) => { |
||||
|
notifyError(`创建代理配置失败: ${error.message}`); |
||||
const addProxy = () => { |
}); |
||||
if (pwdRef.value.validate()) { |
} |
||||
const isNameDuplicate = proxies.value.some(p => p.name === proxyData.value.name); |
}; |
||||
if (isNameDuplicate) { |
|
||||
notifyError("代理名称已存在"); |
const updateProxy = () => { |
||||
return; |
if (pwdRef.value.validate()) { |
||||
} |
const index = proxyStore.proxies.findIndex( |
||||
proxies.value.push({ ...proxyData.value }); |
(p) => p.id === proxyStore.proxyData.id |
||||
saveProxies(proxies.value); |
); |
||||
proxyDialogShow.value = false; |
if (index !== -1) { |
||||
proxyData.value = { id: Date.now(), port: "", domain: "", type: "http", name: "", serverAddr: "", serverPort: "" }; |
const isNameDuplicate = proxyStore.proxies.some( |
||||
} |
(p, i) => |
||||
}; |
p.name === proxyStore.proxyData.name && i !== index |
||||
|
); |
||||
const editProxy = (proxy: ProxyItem) => { |
if (isNameDuplicate) { |
||||
proxyData.value = { ...proxy }; |
notifyError("代理名称已存在"); |
||||
isEditing.value = true; |
return; |
||||
proxyDialogShow.value = true; |
} |
||||
}; |
proxyStore.updateProxy({ ...proxyStore.proxyData }); |
||||
|
proxyDialogShow.value = false; |
||||
const updateProxy = () => { |
proxyStore.resetProxyData(); |
||||
if (pwdRef.value.validate()) { |
isEditing.value = false; |
||||
const index = proxies.value.findIndex(p => p.id === proxyData.value.id); |
} |
||||
if (index !== -1) { |
} |
||||
const isNameDuplicate = proxies.value.some((p, i) => p.name === proxyData.value.name && i !== index); |
}; |
||||
if (isNameDuplicate) { |
|
||||
notifyError("代理名称已存在"); |
const saveProxy = () => { |
||||
return; |
pwdRef.value.validate((valid: boolean) => { |
||||
} |
if (valid) { |
||||
proxies.value[index] = { ...proxyData.value }; |
if (isEditing.value) { |
||||
saveProxies(proxies.value); |
updateProxy(); |
||||
proxyDialogShow.value = false; |
} else { |
||||
proxyData.value = { id: Date.now(), port: "", domain: "", type: "http", name: "", serverAddr: "", serverPort: "" }; |
addProxy(); |
||||
isEditing.value = false; |
} |
||||
} |
} else { |
||||
} |
console.log("表单验证失败"); |
||||
}; |
} |
||||
|
}); |
||||
const deleteProxy = (id: number) => { |
}; |
||||
proxies.value = proxies.value.filter(p => p.id !== id); |
|
||||
saveProxies(proxies.value); |
const openInternalPortDialog = () => { |
||||
}; |
internalPortDialogVisible.value = true; |
||||
|
}; |
||||
const saveProxy = () => { |
|
||||
pwdRef.value.validate((valid: boolean) => { |
const selectPort = (port: number) => { |
||||
if (valid) { |
console.log(`Selected port: ${port}`); |
||||
if (isEditing.value) { |
internalPortDialogVisible.value = false; |
||||
updateProxy(); |
}; |
||||
} else { |
|
||||
addProxy(); |
const proxyTypes = ref([ |
||||
} |
"http", |
||||
} else { |
"https", |
||||
console.log('表单验证失败'); |
"tcp", |
||||
} |
"udp", |
||||
}); |
"stcp", |
||||
}; |
"xtcp", |
||||
|
"sudp", |
||||
const proxyRules = { |
]); |
||||
name: [ |
|
||||
{ required: true, message: '代理名称不能为空', trigger: 'blur' }, |
const stcpModels = ref([ |
||||
{ min: 1, max: 20, message: '代理名称长度在 1 到 20 个字符', trigger: 'blur' }, |
{ label: "访问者", value: "visitors" }, |
||||
{ pattern: /^[a-zA-Z]+$/, message: '代理名称只能包含英文字符', trigger: 'blur' } |
{ label: "被访问者", value: "visited" }, |
||||
], |
]); |
||||
port: [ |
|
||||
{ required: true, message: '端口不能为空', trigger: 'blur' }, |
const handleSelectFile = (type: number, ext: string[]) => { |
||||
{ pattern: /^(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{1,3}|[0-9])$/, message: '请输入有效的端口号(1-65535)', trigger: 'blur' } |
ipcRenderer.invoke("file.selectFile", ext).then((r) => { |
||||
], |
switch (type) { |
||||
domain: [ |
case 1: |
||||
{ required: true, message: '代理域名不能为空', trigger: 'blur' }, |
proxyStore.proxyData.https2httpCaFile = r[0]; |
||||
{ pattern: /^(https?:\/\/)?(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(:\d{1,5})?(\/[^\s]*)?$/, message: '请输入有效的域名格式', trigger: 'blur' } |
break; |
||||
], |
case 2: |
||||
serverAddr: [ |
proxyStore.proxyData.https2httpKeyFile = r[0]; |
||||
{ required: true, message: '服务器地址不能为空', trigger: 'blur' }, |
break; |
||||
{ pattern: /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/, message: '请输入有效的IP地址格式', trigger: 'blur' } |
} |
||||
], |
}); |
||||
serverPort: [ |
}; |
||||
{ required: true, message: '服务器端口不能为空', trigger: 'blur' }, |
|
||||
{ pattern: /^(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{1,3}|[0-9])$/, message: '请输入有效的服务器端口号(1-65535)', trigger: 'blur' } |
|
||||
] |
|
||||
}; |
|
||||
|
|
||||
const pageSize = 10; |
|
||||
const currentPage = ref(1); |
|
||||
|
|
||||
const paginatedProxies = computed(() => { |
|
||||
const start = (currentPage.value - 1) * pageSize; |
|
||||
const end = start + pageSize; |
|
||||
return proxies.value.slice(start, end); |
|
||||
}); |
|
||||
|
|
||||
const totalPages = computed(() => Math.ceil(proxies.value.length / pageSize)); |
|
||||
|
|
||||
const nextPage = () => { |
|
||||
if (currentPage.value < totalPages.value) { |
|
||||
currentPage.value++; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
const prevPage = () => { |
|
||||
if (currentPage.value > 1) { |
|
||||
currentPage.value--; |
|
||||
} |
|
||||
}; |
|
||||
</script> |
</script> |
||||
|
|
||||
<template> |
<template> |
||||
<div> |
<div> |
||||
<el-row justify="end"> |
<el-row justify="end"> |
||||
<el-button type="primary" :icon="Plus" circle @click="proxyDialogShow = true" /> |
<el-button |
||||
</el-row> |
type="primary" |
||||
<el-table :data="paginatedProxies" style="width: 98%;border:none"> |
:icon="Plus" |
||||
<el-table-column prop="name" label="名称" width="100" /> |
circle |
||||
<el-table-column prop="type" label="类型" width="80" /> |
@click="proxyDialogShow = true" |
||||
<el-table-column prop="port" label="本地端口" width="80" /> |
/> |
||||
<el-table-column prop="domain" label="代理域名" width="180" /> |
</el-row> |
||||
<el-table-column label="操作"> |
<el-table |
||||
<template #default="scope"> |
:data="proxyStore.proxies" |
||||
<el-button size="small" @click="editProxy(scope.row)">编辑</el-button> |
style="width: 98%; border: none" |
||||
<el-button size="small" type="danger" @click="deleteProxy(scope.row.id)">删除</el-button> |
> |
||||
</template> |
<el-table-column |
||||
</el-table-column> |
prop="name" |
||||
</el-table> |
label="名称" |
||||
<el-pagination v-if="totalPages > 1" layout="prev, pager, next" :total="getProxies().length" |
width="100" |
||||
:page-size="pageSize" v-model:current-page="currentPage" @next-click="nextPage" @prev-click="prevPage" /> |
/> |
||||
<el-dialog v-model="proxyDialogShow" :title="isEditing ? '编辑代理' : '添加代理'" width="400px"> |
<el-table-column |
||||
<span> |
prop="type" |
||||
<el-form :model="proxyData" :rules="proxyRules" ref="pwdRef"> |
label="类型" |
||||
<el-form-item label="代理类型" prop="type"> |
width="80" |
||||
<el-select v-model="proxyData.type" placeholder="代理类型"> |
/> |
||||
<el-option v-for="type in types" :key="type.value" :label="type.label" |
<el-table-column |
||||
:value="type.value" /> |
prop="port" |
||||
</el-select> |
label="本地端口" |
||||
</el-form-item> |
width="80" |
||||
<el-form-item label="代理名称" prop="name"> |
/> |
||||
<el-input v-model="proxyData.name" /> |
<el-table-column |
||||
</el-form-item> |
prop="domain" |
||||
|
label="代理域名" |
||||
<el-form-item label="本地端口" prop="port"> |
width="180" |
||||
<el-input v-model="proxyData.port" /> |
/> |
||||
</el-form-item> |
<el-table-column label="操作"> |
||||
<el-form-item label="代理域名" prop="domain"> |
<template #default="scope"> |
||||
<el-input v-model="proxyData.domain" /> |
<el-button |
||||
</el-form-item> |
size="small" |
||||
<el-form-item label="服务器地址" prop="serverAddr"> |
@click="editProxy(scope.row)" |
||||
<el-input v-model="proxyData.serverAddr" /> |
>编辑</el-button |
||||
</el-form-item> |
> |
||||
<el-form-item label="服务器端口" prop="serverPort"> |
<el-button |
||||
<el-input v-model="proxyData.serverPort" /> |
size="small" |
||||
</el-form-item> |
type="danger" |
||||
<el-form-item> |
@click="deleteProxy(scope.row.id)" |
||||
<el-button type="primary" @click="saveProxy" style="margin: 0 auto;"> |
>删除</el-button |
||||
确认 |
> |
||||
</el-button> |
</template> |
||||
</el-form-item> |
</el-table-column> |
||||
</el-form> |
</el-table> |
||||
</span> |
<el-pagination |
||||
</el-dialog> |
v-if="proxyStore.totalPages > 1" |
||||
</div> |
layout="prev, pager, next" |
||||
|
:total="proxyStore.proxies.length" |
||||
|
:page-size="proxyStore.pageSize" |
||||
|
v-model:current-page="proxyStore.currentPage" |
||||
|
@next-click="proxyStore.nextPage" |
||||
|
@prev-click="proxyStore.prevPage" |
||||
|
/> |
||||
|
|
||||
|
<!-- 代理配置抽屉 --> |
||||
|
<el-drawer |
||||
|
v-model="proxyDialogShow" |
||||
|
:title="isEditing ? '编辑代理' : '添加代理'" |
||||
|
direction="rtl" |
||||
|
size="60%" |
||||
|
@close="proxyStore.resetProxyData" |
||||
|
> |
||||
|
<el-form |
||||
|
:model="proxyStore.proxyData" |
||||
|
:rules="proxyRules" |
||||
|
ref="pwdRef" |
||||
|
label-position="top" |
||||
|
> |
||||
|
<!-- 代理类型选择 --> |
||||
|
<el-form-item |
||||
|
label="代理类型:" |
||||
|
prop="type" |
||||
|
> |
||||
|
<el-radio-group v-model="proxyStore.proxyData.type"> |
||||
|
<el-radio-button |
||||
|
v-for="type in proxyTypes" |
||||
|
:key="type" |
||||
|
:value="type" |
||||
|
>{{ type }}</el-radio-button |
||||
|
> |
||||
|
</el-radio-group> |
||||
|
</el-form-item> |
||||
|
|
||||
|
<!-- HTTP/HTTPS模式 --> |
||||
|
<template |
||||
|
v-if=" |
||||
|
proxyStore.proxyData.type === 'http' || |
||||
|
proxyStore.proxyData.type === 'https' || |
||||
|
proxyStore.proxyData.type === 'tcp' || |
||||
|
proxyStore.proxyData.type === 'udp' || |
||||
|
proxyStore.proxyData.type === 'stcp' || |
||||
|
proxyStore.proxyData.type === 'xtcp' || |
||||
|
proxyStore.proxyData.type === 'sudp' |
||||
|
" |
||||
|
> |
||||
|
<el-form-item |
||||
|
label="代理名称:" |
||||
|
prop="name" |
||||
|
> |
||||
|
<el-input |
||||
|
v-model="proxyStore.proxyData.name" |
||||
|
placeholder="代理名称" |
||||
|
/> |
||||
|
</el-form-item> |
||||
|
<el-row |
||||
|
v-if=" |
||||
|
proxyStore.proxyData.type === 'http' || |
||||
|
proxyStore.proxyData.type === 'https' || |
||||
|
proxyStore.proxyData.type === 'tcp' || |
||||
|
proxyStore.proxyData.type === 'udp' |
||||
|
" |
||||
|
:gutter="20" |
||||
|
> |
||||
|
<el-col :span="12"> |
||||
|
<el-form-item |
||||
|
label="内网地址:" |
||||
|
prop="serverAddr" |
||||
|
> |
||||
|
<el-input |
||||
|
v-model="proxyStore.proxyData.serverAddr" |
||||
|
placeholder="内网地址" |
||||
|
/> |
||||
|
</el-form-item> |
||||
|
</el-col> |
||||
|
<el-col :span="8"> |
||||
|
<el-form-item |
||||
|
label="端口地址:" |
||||
|
prop="port" |
||||
|
> |
||||
|
<el-input-number |
||||
|
v-model="proxyStore.proxyData.port" |
||||
|
:min="1" |
||||
|
:max="65535" |
||||
|
/> |
||||
|
</el-form-item> |
||||
|
</el-col> |
||||
|
</el-row> |
||||
|
<el-form-item |
||||
|
v-if=" |
||||
|
proxyStore.proxyData.type === 'http' || |
||||
|
proxyStore.proxyData.type === 'https' |
||||
|
" |
||||
|
label="子域名:" |
||||
|
prop="domain" |
||||
|
> |
||||
|
<el-input |
||||
|
v-model="proxyStore.proxyData.domain" |
||||
|
placeholder="subdomain" |
||||
|
/> |
||||
|
</el-form-item> |
||||
|
<el-form-item |
||||
|
v-if=" |
||||
|
proxyStore.proxyData.type === 'http' || |
||||
|
proxyStore.proxyData.type === 'https' |
||||
|
" |
||||
|
label="自定义域名:" |
||||
|
prop="customDomain" |
||||
|
> |
||||
|
<el-row |
||||
|
v-for="(domain, index) in proxyStore.customDomains" |
||||
|
:key="index" |
||||
|
:gutter="24" |
||||
|
> |
||||
|
<el-col :span="12"> |
||||
|
<el-input |
||||
|
v-model="proxyStore.customDomains[index]" |
||||
|
placeholder="example.com" |
||||
|
/> |
||||
|
</el-col> |
||||
|
<el-col :span="5"> |
||||
|
<el-button |
||||
|
type="primary" |
||||
|
icon="Plus" |
||||
|
style="width: 80px" |
||||
|
@click="proxyStore.addCustomDomain" |
||||
|
>添加</el-button |
||||
|
> |
||||
|
</el-col> |
||||
|
<el-col :span="5"> |
||||
|
<el-button |
||||
|
type="primary" |
||||
|
icon="Plus" |
||||
|
style="width: 80px" |
||||
|
@click=" |
||||
|
proxyStore.removeCustomDomain(index) |
||||
|
" |
||||
|
>删除</el-button |
||||
|
> |
||||
|
</el-col> |
||||
|
</el-row> |
||||
|
</el-form-item> |
||||
|
<el-form-item |
||||
|
v-if="proxyStore.proxyData.type === 'http'" |
||||
|
label="HTTP基本认证:" |
||||
|
prop="httpAuth" |
||||
|
> |
||||
|
<el-switch v-model="proxyStore.proxyData.httpAuth" /> |
||||
|
</el-form-item> |
||||
|
<el-form-item |
||||
|
v-if="proxyStore.proxyData.httpAuth" |
||||
|
label="认证用户名:" |
||||
|
prop="authUsername" |
||||
|
> |
||||
|
<el-input |
||||
|
v-model="proxyStore.proxyData.authUsername" |
||||
|
placeholder="username" |
||||
|
/> |
||||
|
</el-form-item> |
||||
|
<el-form-item |
||||
|
v-if="proxyStore.proxyData.httpAuth" |
||||
|
label="认证密码:" |
||||
|
prop="authPassword" |
||||
|
> |
||||
|
<el-input |
||||
|
v-model="proxyStore.proxyData.authPassword" |
||||
|
type="password" |
||||
|
placeholder="password" |
||||
|
/> |
||||
|
</el-form-item> |
||||
|
<el-form-item |
||||
|
v-if="proxyStore.proxyData.type === 'https'" |
||||
|
label="证书文件:" |
||||
|
prop="https2httpCaFile" |
||||
|
> |
||||
|
<el-input |
||||
|
v-model="proxyStore.proxyData.https2httpCaFile" |
||||
|
placeholder="点击选择证书文件" |
||||
|
readonly |
||||
|
@click="handleSelectFile(1, ['crt', 'pem'])" |
||||
|
/> |
||||
|
</el-form-item> |
||||
|
<el-form-item |
||||
|
v-if="proxyStore.proxyData.type === 'https'" |
||||
|
label="密钥文件:" |
||||
|
prop="https2httpKeyFile" |
||||
|
> |
||||
|
<el-input |
||||
|
v-model="proxyStore.proxyData.https2httpKeyFile" |
||||
|
placeholder="点击选择密钥文件" |
||||
|
readonly |
||||
|
@click="handleSelectFile(2, ['key'])" |
||||
|
/> |
||||
|
</el-form-item> |
||||
|
</template> |
||||
|
|
||||
|
<el-form-item |
||||
|
v-if=" |
||||
|
proxyStore.proxyData.type === 'tcp' || |
||||
|
proxyStore.proxyData.type === 'udp' |
||||
|
" |
||||
|
label="外网端口:" |
||||
|
prop="remotePort" |
||||
|
> |
||||
|
<el-input-number |
||||
|
v-model="proxyStore.proxyData.remotePort" |
||||
|
:min="1" |
||||
|
:max="65535" |
||||
|
/> |
||||
|
</el-form-item> |
||||
|
|
||||
|
<!-- STCP/XTCP/SUDP模式 --> |
||||
|
<template |
||||
|
v-if=" |
||||
|
proxyStore.proxyData.type === 'stcp' || |
||||
|
proxyStore.proxyData.type === 'xtcp' || |
||||
|
proxyStore.proxyData.type === 'sudp' |
||||
|
" |
||||
|
> |
||||
|
<el-row :gutter="22"> |
||||
|
<el-col :span="14"> |
||||
|
<el-form-item |
||||
|
label="STCP模式:" |
||||
|
prop="stcpModel" |
||||
|
> |
||||
|
<el-radio-group |
||||
|
v-model="proxyStore.proxyData.stcpModel" |
||||
|
> |
||||
|
<el-radio |
||||
|
v-for="model in stcpModels" |
||||
|
:key="model.value" |
||||
|
:value="model.value" |
||||
|
>{{ model.label }}</el-radio |
||||
|
> |
||||
|
</el-radio-group> |
||||
|
</el-form-item> |
||||
|
</el-col> |
||||
|
<el-col :span="10"> |
||||
|
<el-form-item |
||||
|
label="共享密钥:" |
||||
|
prop="secretKey" |
||||
|
> |
||||
|
<el-input |
||||
|
v-model="proxyStore.proxyData.secretKey" |
||||
|
type="password" |
||||
|
placeholder="密钥" |
||||
|
/> |
||||
|
</el-form-item> |
||||
|
</el-col> |
||||
|
</el-row> |
||||
|
|
||||
|
<!-- 被访问者代理名称 --> |
||||
|
<el-form-item |
||||
|
v-if=" |
||||
|
proxyStore.proxyData.type === 'stcp' || |
||||
|
proxyStore.proxyData.type === 'xtcp' || |
||||
|
proxyStore.proxyData.type === 'sudp' |
||||
|
" |
||||
|
label="被访问者代理名称:" |
||||
|
prop="visitedName" |
||||
|
> |
||||
|
<el-input |
||||
|
v-model="proxyStore.proxyData.visitedName" |
||||
|
placeholder="被访问者代理名称" |
||||
|
/> |
||||
|
</el-form-item> |
||||
|
|
||||
|
<template |
||||
|
v-if=" |
||||
|
proxyStore.proxyData.type === 'stcp' || |
||||
|
proxyStore.proxyData.type === 'xtcp' || |
||||
|
proxyStore.proxyData.type === 'sudp' |
||||
|
" |
||||
|
> |
||||
|
<el-row :gutter="20"> |
||||
|
<el-col :span="10"> |
||||
|
<el-form-item |
||||
|
label="绑定地址:" |
||||
|
prop="bindAddr" |
||||
|
> |
||||
|
<el-input |
||||
|
v-model="proxyStore.proxyData.bindAddr" |
||||
|
placeholder="127.0.0.1" |
||||
|
/> |
||||
|
</el-form-item> |
||||
|
</el-col> |
||||
|
<el-col :span="10"> |
||||
|
<el-form-item |
||||
|
label="绑定端口:" |
||||
|
prop="bindPort" |
||||
|
> |
||||
|
<el-input-number |
||||
|
v-model="proxyStore.proxyData.bindPort" |
||||
|
:min="1" |
||||
|
:max="65535" |
||||
|
/> |
||||
|
</el-form-item> |
||||
|
</el-col> |
||||
|
</el-row> |
||||
|
</template> |
||||
|
<template v-if="proxyStore.proxyData.type === 'xtcp'"> |
||||
|
<el-row :gutter="20"> |
||||
|
<el-col :span="10"> |
||||
|
<el-form-item |
||||
|
label="回退代理名称:" |
||||
|
prop="fallbackTo" |
||||
|
> |
||||
|
<el-input |
||||
|
v-model=" |
||||
|
proxyStore.proxyData.fallbackTo |
||||
|
" |
||||
|
placeholder="回退代理名称" |
||||
|
/> |
||||
|
</el-form-item> |
||||
|
</el-col> |
||||
|
<el-col :span="10"> |
||||
|
<el-form-item |
||||
|
label="回退超时毫秒:" |
||||
|
prop="fallbackTimeoutMs" |
||||
|
> |
||||
|
<el-input-number |
||||
|
v-model=" |
||||
|
proxyStore.proxyData |
||||
|
.fallbackTimeoutMs |
||||
|
" |
||||
|
:min="0" |
||||
|
/> |
||||
|
</el-form-item> |
||||
|
</el-col> |
||||
|
</el-row> |
||||
|
<!-- 保持隧道开启 --> |
||||
|
<el-form-item |
||||
|
label="保持隧道开启:" |
||||
|
prop="keepAlive" |
||||
|
> |
||||
|
<el-switch |
||||
|
v-model="proxyStore.proxyData.keepAlive" |
||||
|
/> |
||||
|
</el-form-item> |
||||
|
</template> |
||||
|
</template> |
||||
|
|
||||
|
<!-- 保存和取消按钮 --> |
||||
|
<el-row justify="start"> |
||||
|
<el-button |
||||
|
type="primary" |
||||
|
@click="saveProxy" |
||||
|
style="width: 100px" |
||||
|
>保存</el-button |
||||
|
> |
||||
|
<el-button |
||||
|
type="primary" |
||||
|
style="width: 100px" |
||||
|
@click="proxyDialogShow = false" |
||||
|
>取消</el-button |
||||
|
> |
||||
|
</el-row> |
||||
|
</el-form> |
||||
|
</el-drawer> |
||||
|
</div> |
||||
</template> |
</template> |
||||
|
|
||||
|
<style scoped> |
||||
|
:deep(.el-drawer) { |
||||
|
width: 60% !important; |
||||
|
} |
||||
|
</style> |
||||
|
@ -0,0 +1,182 @@ |
|||||
|
import { defineStore } from 'pinia'; |
||||
|
import { ref } from 'vue'; |
||||
|
|
||||
|
export interface ProxyItem { |
||||
|
port: string; |
||||
|
domain: string; |
||||
|
type: string; |
||||
|
name: string; |
||||
|
serverAddr: string; |
||||
|
serverPort: string; |
||||
|
httpAuth: boolean; |
||||
|
authUsername: string; |
||||
|
authPassword: string; |
||||
|
https2http: boolean; |
||||
|
https2httpCaFile: string; |
||||
|
https2httpKeyFile: string; |
||||
|
remotePort?: string; |
||||
|
stcpModel?: string; |
||||
|
secretKey?: string; |
||||
|
visitedName?: string; |
||||
|
bindAddr?: string; |
||||
|
bindPort?: string; |
||||
|
fallbackTo?: string; |
||||
|
fallbackTimeoutMs?: number; |
||||
|
keepAlive?: boolean; |
||||
|
} |
||||
|
|
||||
|
export const useProxyStore = defineStore('proxyStore', () => { |
||||
|
const localKey = "godoos_net_proxy"; |
||||
|
|
||||
|
const proxies = ref<ProxyItem[]>(getProxies()); |
||||
|
const customDomains = ref<string[]>([""]); |
||||
|
const proxyData = ref<ProxyItem>(createNewProxyData()); |
||||
|
|
||||
|
function createNewProxyData(): ProxyItem { |
||||
|
return { |
||||
|
type: "http", |
||||
|
name: "", |
||||
|
port: "", |
||||
|
domain: "", |
||||
|
serverAddr: "", |
||||
|
serverPort: "", |
||||
|
httpAuth: false, |
||||
|
authUsername: "", |
||||
|
authPassword: "", |
||||
|
https2http: false, |
||||
|
https2httpCaFile: "", |
||||
|
https2httpKeyFile: "", |
||||
|
remotePort: "", |
||||
|
stcpModel: "", |
||||
|
secretKey: "", |
||||
|
visitedName: "", |
||||
|
bindAddr: "", |
||||
|
bindPort: "", |
||||
|
fallbackTo: "", |
||||
|
fallbackTimeoutMs: 0, |
||||
|
keepAlive: false, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
function getProxies(): ProxyItem[] { |
||||
|
const proxies = localStorage.getItem(localKey); |
||||
|
return proxies ? JSON.parse(proxies) : []; |
||||
|
} |
||||
|
|
||||
|
function saveProxies(proxies: ProxyItem[]) { |
||||
|
localStorage.setItem(localKey, JSON.stringify(proxies)); |
||||
|
} |
||||
|
|
||||
|
const addProxy = (proxy: ProxyItem) => { |
||||
|
proxies.value.push(proxy); |
||||
|
saveProxies(proxies.value); |
||||
|
}; |
||||
|
|
||||
|
const updateProxy = (updatedProxy: ProxyItem) => { |
||||
|
const index = proxies.value.findIndex(p => p.id === updatedProxy.id); |
||||
|
if (index !== -1) { |
||||
|
proxies.value[index] = updatedProxy; |
||||
|
saveProxies(proxies.value); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const deleteProxy = (id: number) => { |
||||
|
proxies.value = proxies.value.filter(p => p.id !== id); |
||||
|
saveProxies(proxies.value); |
||||
|
}; |
||||
|
|
||||
|
const addCustomDomain = () => { |
||||
|
customDomains.value.push(""); |
||||
|
}; |
||||
|
|
||||
|
const removeCustomDomain = (index: number) => { |
||||
|
if (customDomains.value.length > 1) { |
||||
|
customDomains.value.splice(index, 1); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const resetProxyData = () => { |
||||
|
proxyData.value = createNewProxyData(); |
||||
|
}; |
||||
|
|
||||
|
const createFrpcConfig = async () => { |
||||
|
const url = "http://localhost:56780/frpc/create"; |
||||
|
const requestData = { |
||||
|
config: { |
||||
|
ServerAddr: proxyData.value.serverAddr, |
||||
|
ServerPort: Number(proxyData.value.serverPort), |
||||
|
AuthMethod: "token", |
||||
|
AuthToken: "your-auth-token", |
||||
|
User: "your-username", |
||||
|
MetaToken: "your-meta-token", |
||||
|
TransportHeartbeatInterval: 30, |
||||
|
TransportHeartbeatTimeout: 90, |
||||
|
LogLevel: "info", |
||||
|
LogMaxDays: 3, |
||||
|
WebPort: 7500, |
||||
|
TlsConfigEnable: true, |
||||
|
TlsConfigCertFile: "/path/to/cert/file", |
||||
|
TlsConfigKeyFile: "/path/to/key/file", |
||||
|
TlsConfigTrustedCaFile: "/path/to/ca/file", |
||||
|
TlsConfigServerName: "server-name", |
||||
|
ProxyConfigEnable: true, |
||||
|
ProxyConfigProxyUrl: "http://proxy.example.com" |
||||
|
}, |
||||
|
proxies: [ |
||||
|
{ |
||||
|
Name: proxyData.value.name, |
||||
|
Type: proxyData.value.type, |
||||
|
LocalIp: proxyData.value.serverAddr, |
||||
|
LocalPort: Number(proxyData.value.port), |
||||
|
RemotePort: Number(proxyData.value.remotePort), |
||||
|
CustomDomains: customDomains.value, |
||||
|
Subdomain: proxyData.value.domain, |
||||
|
BasicAuth: proxyData.value.httpAuth, |
||||
|
HttpUser: proxyData.value.authUsername, |
||||
|
HttpPassword: proxyData.value.authPassword, |
||||
|
StcpModel: proxyData.value.stcpModel, |
||||
|
ServerName: "server1", |
||||
|
BindAddr: proxyData.value.bindAddr, |
||||
|
BindPort: Number(proxyData.value.bindPort), |
||||
|
FallbackTo: proxyData.value.fallbackTo, |
||||
|
FallbackTimeoutMs: Number(proxyData.value.fallbackTimeoutMs), |
||||
|
SecretKey: proxyData.value.secretKey |
||||
|
} |
||||
|
] |
||||
|
}; |
||||
|
|
||||
|
try { |
||||
|
const response = await fetch(url, { |
||||
|
method: "POST", |
||||
|
headers: { |
||||
|
"Content-Type": "application/json" |
||||
|
}, |
||||
|
body: JSON.stringify(requestData) |
||||
|
}); |
||||
|
|
||||
|
if (!response.ok) { |
||||
|
throw new Error(`HTTP error! status: ${response.status}`); |
||||
|
} |
||||
|
|
||||
|
const data = await response.json(); |
||||
|
console.log("Response data:", data); |
||||
|
// 可以在这里添加成功通知
|
||||
|
} catch (error) { |
||||
|
console.error("Error:", error); |
||||
|
// 可以在这里添加错误通知
|
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
return { |
||||
|
proxies, |
||||
|
customDomains, |
||||
|
proxyData, |
||||
|
addProxy, |
||||
|
updateProxy, |
||||
|
deleteProxy, |
||||
|
addCustomDomain, |
||||
|
removeCustomDomain, |
||||
|
resetProxyData, |
||||
|
createFrpcConfig, |
||||
|
}; |
||||
|
}); |
Loading…
Reference in new issue