feat: 完善前端页面(规则配置、日志查询、系统设置)
This commit is contained in:
@@ -4,6 +4,7 @@ go 1.21
|
||||
|
||||
require (
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||
github.com/spf13/viper v1.18.2
|
||||
go.uber.org/zap v1.26.0
|
||||
golang.org/x/crypto v0.17.0
|
||||
|
||||
@@ -28,6 +28,8 @@ github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg
|
||||
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
|
||||
@@ -1,16 +1,343 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="rules-page">
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<span class="page-title">规则配置</span>
|
||||
<div class="page-header">
|
||||
<span class="page-title">IP 刷新规则</span>
|
||||
<el-button type="primary" @click="showDialog = true">
|
||||
<el-icon><Plus /></el-icon>
|
||||
添加规则
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<el-empty description="功能开发中..." />
|
||||
|
||||
<el-alert type="info" :closable="false" style="margin-bottom: 20px">
|
||||
<template #title>
|
||||
<strong>规则说明</strong>
|
||||
</template>
|
||||
<div style="margin-top: 8px">
|
||||
<p>• <strong>解锁失败</strong>:当指定服务解锁失败时自动刷新 IP</p>
|
||||
<p>• <strong>使用次数</strong>:当请求次数达到阈值时刷新 IP</p>
|
||||
<p>• <strong>流量阈值</strong>:当流量达到阈值时刷新 IP</p>
|
||||
<p>• <strong>定时刷新</strong>:按固定时间间隔刷新 IP</p>
|
||||
<p>• <strong>异常检测</strong>:当检测到异常指标时刷新 IP</p>
|
||||
</div>
|
||||
</el-alert>
|
||||
|
||||
<el-table :data="rules" v-loading="loading" style="width: 100%">
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column label="触发类型" width="150">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getTriggerTypeTag(row.trigger_type)">
|
||||
{{ getTriggerTypeLabel(row.trigger_type) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="触发条件" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<div v-if="row.trigger_value">
|
||||
<span v-if="row.trigger_type === 'unlock_failure'">
|
||||
服务: {{ parseTriggerValue(row.trigger_value).service }}
|
||||
</span>
|
||||
<span v-else-if="row.trigger_type === 'usage_count'">
|
||||
请求次数: {{ parseTriggerValue(row.trigger_value).count }}
|
||||
</span>
|
||||
<span v-else-if="row.trigger_type === 'usage_traffic'">
|
||||
流量: {{ parseTriggerValue(row.trigger_value).traffic_gb }} GB
|
||||
</span>
|
||||
<span v-else-if="row.trigger_type === 'scheduled'">
|
||||
间隔: {{ parseTriggerValue(row.trigger_value).interval }}
|
||||
</span>
|
||||
<span v-else-if="row.trigger_type === 'anomaly'">
|
||||
指标: {{ parseTriggerValue(row.trigger_value).metric }}
|
||||
阈值: {{ parseTriggerValue(row.trigger_value).threshold }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="冷却时间" width="120">
|
||||
<template #default="{ row }">
|
||||
{{ row.cooldown }} 秒
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-switch
|
||||
v-model="row.enabled"
|
||||
@change="toggleRule(row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="created_at" label="创建时间" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ new Date(row.created_at).toLocaleString() }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" fixed="right" width="150">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" size="small" text @click="editRule(row)">编辑</el-button>
|
||||
<el-button type="danger" size="small" text @click="deleteRule(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<!-- 添加/编辑规则对话框 -->
|
||||
<el-dialog v-model="showDialog" :title="isEdit ? '编辑规则' : '添加规则'" width="600px">
|
||||
<el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
|
||||
<el-form-item label="触发类型" prop="trigger_type">
|
||||
<el-select v-model="form.trigger_type" placeholder="请选择触发类型" style="width: 100%">
|
||||
<el-option label="解锁失败" value="unlock_failure" />
|
||||
<el-option label="使用次数" value="usage_count" />
|
||||
<el-option label="流量阈值" value="usage_traffic" />
|
||||
<el-option label="定时刷新" value="scheduled" />
|
||||
<el-option label="异常检测" value="anomaly" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 解锁失败配置 -->
|
||||
<el-form-item v-if="form.trigger_type === 'unlock_failure'" label="服务" prop="service">
|
||||
<el-select v-model="form.service" placeholder="请选择服务" style="width: 100%">
|
||||
<el-option label="GPT (ChatGPT)" value="gpt" />
|
||||
<el-option label="Netflix" value="netflix" />
|
||||
<el-option label="Disney+" value="disney" />
|
||||
<el-option label="YouTube" value="youtube" />
|
||||
<el-option label="Claude" value="claude" />
|
||||
<el-option label="Gemini" value="gemini" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 使用次数配置 -->
|
||||
<el-form-item v-if="form.trigger_type === 'usage_count'" label="请求次数" prop="count">
|
||||
<el-input-number v-model="form.count" :min="1" :max="1000000" />
|
||||
</el-form-item>
|
||||
|
||||
<!-- 流量阈值配置 -->
|
||||
<el-form-item v-if="form.trigger_type === 'usage_traffic'" label="流量 (GB)" prop="traffic_gb">
|
||||
<el-input-number v-model="form.traffic_gb" :min="1" :max="10000" />
|
||||
</el-form-item>
|
||||
|
||||
<!-- 定时刷新配置 -->
|
||||
<el-form-item v-if="form.trigger_type === 'scheduled'" label="刷新间隔" prop="interval">
|
||||
<el-select v-model="form.interval" placeholder="请选择间隔" style="width: 100%">
|
||||
<el-option label="每小时" value="1h" />
|
||||
<el-option label="每 6 小时" value="6h" />
|
||||
<el-option label="每 12 小时" value="12h" />
|
||||
<el-option label="每天" value="24h" />
|
||||
<el-option label="每周" value="168h" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 异常检测配置 -->
|
||||
<template v-if="form.trigger_type === 'anomaly'">
|
||||
<el-form-item label="监控指标" prop="metric">
|
||||
<el-select v-model="form.metric" placeholder="请选择指标" style="width: 100%">
|
||||
<el-option label="连接失败率" value="connection_failure_rate" />
|
||||
<el-option label="CPU 使用率" value="cpu_usage" />
|
||||
<el-option label="内存使用率" value="memory_usage" />
|
||||
<el-option label="延迟 (ms)" value="latency" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="阈值 (%)" prop="threshold">
|
||||
<el-input-number v-model="form.threshold" :min="0" :max="100" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<el-form-item label="冷却时间" prop="cooldown">
|
||||
<el-input-number v-model="form.cooldown" :min="60" :max="3600" />
|
||||
<span style="margin-left: 8px">秒</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="节点组" prop="node_group_id">
|
||||
<el-select v-model="form.node_group_id" placeholder="全部节点" clearable style="width: 100%">
|
||||
<el-option label="全部节点" :value="null" />
|
||||
<!-- TODO: 从 API 加载节点组 -->
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showDialog = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { api } from '@/utils/api'
|
||||
|
||||
const loading = ref(false)
|
||||
const showDialog = ref(false)
|
||||
const isEdit = ref(false)
|
||||
const formRef = ref<FormInstance>()
|
||||
const rules_data = ref<any[]>([])
|
||||
|
||||
const form = reactive({
|
||||
id: 0,
|
||||
trigger_type: 'unlock_failure',
|
||||
service: 'gpt',
|
||||
count: 10000,
|
||||
traffic_gb: 100,
|
||||
interval: '24h',
|
||||
metric: 'connection_failure_rate',
|
||||
threshold: 30,
|
||||
cooldown: 300,
|
||||
node_group_id: null,
|
||||
})
|
||||
|
||||
const rules: FormRules = {
|
||||
trigger_type: [{ required: true, message: '请选择触发类型', trigger: 'change' }],
|
||||
}
|
||||
|
||||
const getTriggerTypeLabel = (type: string) => {
|
||||
const labels: Record<string, string> = {
|
||||
unlock_failure: '解锁失败',
|
||||
usage_count: '使用次数',
|
||||
usage_traffic: '流量阈值',
|
||||
scheduled: '定时刷新',
|
||||
anomaly: '异常检测',
|
||||
}
|
||||
return labels[type] || type
|
||||
}
|
||||
|
||||
const getTriggerTypeTag = (type: string) => {
|
||||
const tags: Record<string, string> = {
|
||||
unlock_failure: 'danger',
|
||||
usage_count: 'warning',
|
||||
usage_traffic: 'warning',
|
||||
scheduled: 'success',
|
||||
anomaly: 'warning',
|
||||
}
|
||||
return tags[type] || ''
|
||||
}
|
||||
|
||||
const parseTriggerValue = (value: string) => {
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
const fetchRules = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await api.get('/rules')
|
||||
rules_data.value = res.data.data || []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const editRule = (row: any) => {
|
||||
isEdit.value = true
|
||||
form.id = row.id
|
||||
form.trigger_type = row.trigger_type
|
||||
form.cooldown = row.cooldown
|
||||
form.node_group_id = row.node_group_id
|
||||
|
||||
const value = parseTriggerValue(row.trigger_value)
|
||||
if (value.service) form.service = value.service
|
||||
if (value.count) form.count = value.count
|
||||
if (value.traffic_gb) form.traffic_gb = value.traffic_gb
|
||||
if (value.interval) form.interval = value.interval
|
||||
if (value.metric) form.metric = value.metric
|
||||
if (value.threshold) form.threshold = value.threshold
|
||||
|
||||
showDialog.value = true
|
||||
}
|
||||
|
||||
const submitForm = async () => {
|
||||
if (!formRef.value) return
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
// 构建触发值
|
||||
let triggerValue: any = {}
|
||||
switch (form.trigger_type) {
|
||||
case 'unlock_failure':
|
||||
triggerValue = { service: form.service }
|
||||
break
|
||||
case 'usage_count':
|
||||
triggerValue = { count: form.count }
|
||||
break
|
||||
case 'usage_traffic':
|
||||
triggerValue = { traffic_gb: form.traffic_gb }
|
||||
break
|
||||
case 'scheduled':
|
||||
triggerValue = { interval: form.interval }
|
||||
break
|
||||
case 'anomaly':
|
||||
triggerValue = { metric: form.metric, threshold: form.threshold }
|
||||
break
|
||||
}
|
||||
|
||||
try {
|
||||
const data = {
|
||||
trigger_type: form.trigger_type,
|
||||
trigger_value: JSON.stringify(triggerValue),
|
||||
cooldown: form.cooldown,
|
||||
node_group_id: form.node_group_id,
|
||||
}
|
||||
|
||||
if (isEdit.value) {
|
||||
await api.put(`/rules/${form.id}`, data)
|
||||
ElMessage.success('更新成功')
|
||||
} else {
|
||||
await api.post('/rules', data)
|
||||
ElMessage.success('添加成功')
|
||||
}
|
||||
|
||||
showDialog.value = false
|
||||
fetchRules()
|
||||
} catch (error) {
|
||||
ElMessage.error('操作失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const toggleRule = async (row: any) => {
|
||||
try {
|
||||
await api.put(`/rules/${row.id}`, { enabled: row.enabled })
|
||||
ElMessage.success('状态已更新')
|
||||
} catch (error) {
|
||||
row.enabled = !row.enabled
|
||||
ElMessage.error('更新失败')
|
||||
}
|
||||
}
|
||||
|
||||
const deleteRule = async (row: any) => {
|
||||
await ElMessageBox.confirm('确定要删除该规则吗?', '提示', { type: 'warning' })
|
||||
try {
|
||||
await api.delete(`/rules/${row.id}`)
|
||||
ElMessage.success('删除成功')
|
||||
fetchRules()
|
||||
} catch (error) {
|
||||
ElMessage.error('删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchRules()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style lang="scss" scoped>
|
||||
.rules-page {
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.el-alert {
|
||||
p {
|
||||
margin: 4px 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,16 +1,262 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<span class="page-title">系统设置</span>
|
||||
</template>
|
||||
<el-empty description="功能开发中..." />
|
||||
</el-card>
|
||||
<div class="settings-page">
|
||||
<el-tabs v-model="activeTab" type="border-card">
|
||||
<!-- 基础设置 -->
|
||||
<el-tab-pane label="基础设置" name="basic">
|
||||
<el-form :model="basicForm" label-width="150px" style="max-width: 600px">
|
||||
<el-form-item label="平台名称">
|
||||
<el-input v-model="basicForm.platformName" placeholder="代理管理平台" />
|
||||
</el-form-item>
|
||||
<el-form-item label="SOCKS5 端口">
|
||||
<el-input-number v-model="basicForm.socks5Port" :min="1" :max="65535" />
|
||||
</el-form-item>
|
||||
<el-form-item label="最大连接数">
|
||||
<el-input-number v-model="basicForm.maxConnections" :min="100" :max="100000" />
|
||||
</el-form-item>
|
||||
<el-form-item label="连接超时">
|
||||
<el-input-number v-model="basicForm.connectionTimeout" :min="5" :max="300" />
|
||||
<span style="margin-left: 8px">秒</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="负载均衡策略">
|
||||
<el-select v-model="basicForm.loadBalanceStrategy" style="width: 100%">
|
||||
<el-option label="最小延迟优先" value="least_latency" />
|
||||
<el-option label="最小连接数优先" value="least_connections" />
|
||||
<el-option label="加权轮询" value="weighted_round_robin" />
|
||||
<el-option label="随机" value="random" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="saveBasicSettings">保存设置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 安全设置 -->
|
||||
<el-tab-pane label="安全设置" name="security">
|
||||
<el-form :model="securityForm" label-width="150px" style="max-width: 600px">
|
||||
<el-form-item label="JWT 密钥">
|
||||
<el-input v-model="securityForm.jwtSecret" type="password" show-password>
|
||||
<template #append>
|
||||
<el-button @click="generateJWTSecret">生成</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Token 有效期">
|
||||
<el-input-number v-model="securityForm.tokenExpiry" :min="1" :max="720" />
|
||||
<span style="margin-left: 8px">小时</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="密码最小长度">
|
||||
<el-input-number v-model="securityForm.minPasswordLength" :min="6" :max="32" />
|
||||
</el-form-item>
|
||||
<el-form-item label="登录失败锁定">
|
||||
<el-switch v-model="securityForm.loginLockEnabled" />
|
||||
<span style="margin-left: 16px">
|
||||
失败 <el-input-number v-model="securityForm.loginLockThreshold" :min="3" :max="10" style="width: 100px" /> 次后锁定
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="saveSecuritySettings">保存设置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 通知设置 -->
|
||||
<el-tab-pane label="通知设置" name="notification">
|
||||
<el-form :model="notificationForm" label-width="150px" style="max-width: 600px">
|
||||
<el-form-item label="节点离线通知">
|
||||
<el-switch v-model="notificationForm.nodeOfflineNotify" />
|
||||
</el-form-item>
|
||||
<el-form-item label="解锁失败通知">
|
||||
<el-switch v-model="notificationForm.unlockFailNotify" />
|
||||
</el-form-item>
|
||||
<el-form-item label="流量预警通知">
|
||||
<el-switch v-model="notificationForm.trafficWarningNotify" />
|
||||
<span style="margin-left: 16px">
|
||||
阈值 <el-input-number v-model="notificationForm.trafficWarningThreshold" :min="50" :max="100" style="width: 100px" /> %
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-divider>邮件通知</el-divider>
|
||||
<el-form-item label="SMTP 服务器">
|
||||
<el-input v-model="notificationForm.smtpHost" placeholder="smtp.example.com" />
|
||||
</el-form-item>
|
||||
<el-form-item label="SMTP 端口">
|
||||
<el-input-number v-model="notificationForm.smtpPort" :min="1" :max="65535" />
|
||||
</el-form-item>
|
||||
<el-form-item label="发件邮箱">
|
||||
<el-input v-model="notificationForm.smtpFrom" placeholder="noreply@example.com" />
|
||||
</el-form-item>
|
||||
<el-form-item label="邮箱密码">
|
||||
<el-input v-model="notificationForm.smtpPassword" type="password" show-password />
|
||||
</el-form-item>
|
||||
<el-divider>Webhook 通知</el-divider>
|
||||
<el-form-item label="Webhook URL">
|
||||
<el-input v-model="notificationForm.webhookUrl" placeholder="https://webhook.example.com/notify" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="saveNotificationSettings">保存设置</el-button>
|
||||
<el-button @click="testNotification">发送测试通知</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 系统信息 -->
|
||||
<el-tab-pane label="系统信息" name="system">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="系统版本">v1.0.0</el-descriptions-item>
|
||||
<el-descriptions-item label="Go 版本">go1.21</el-descriptions-item>
|
||||
<el-descriptions-item label="数据库">PostgreSQL 15</el-descriptions-item>
|
||||
<el-descriptions-item label="缓存">Redis 7</el-descriptions-item>
|
||||
<el-descriptions-item label="启动时间">{{ systemInfo.startTime || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="运行时长">{{ systemInfo.uptime || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="总请求数">{{ systemInfo.totalRequests || 0 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="总流量">{{ formatBytes(systemInfo.totalTraffic) }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-divider>数据库信息</el-divider>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="用户数">{{ systemInfo.userCount || 0 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="节点数">{{ systemInfo.nodeCount || 0 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="规则数">{{ systemInfo.ruleCount || 0 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="日志数">{{ systemInfo.logCount || 0 }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-divider>操作</el-divider>
|
||||
<el-space>
|
||||
<el-button @click="refreshSystemInfo">刷新信息</el-button>
|
||||
<el-button type="warning" @click="clearLogs">清理日志</el-button>
|
||||
<el-button type="danger" @click="restartService">重启服务</el-button>
|
||||
</el-space>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 关于 -->
|
||||
<el-tab-pane label="关于" name="about">
|
||||
<el-card shadow="never">
|
||||
<div style="text-align: center; padding: 40px 0">
|
||||
<h1 style="margin: 0; color: #409eff">代理管理平台</h1>
|
||||
<p style="color: #909399; margin: 16px 0">Proxy Management Platform</p>
|
||||
<p style="color: #606266">版本: v1.0.0</p>
|
||||
<el-divider />
|
||||
<p style="color: #909399; font-size: 13px">
|
||||
一个智能代理调度平台,支持用户通过 SOCKS5 连接,根据解锁能力和使用规则自动选择最优节点。
|
||||
</p>
|
||||
<p style="color: #909399; font-size: 13px; margin-top: 20px">
|
||||
技术栈: Go + Vue 3 + Element Plus + PostgreSQL + Redis
|
||||
</p>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
const activeTab = ref('basic')
|
||||
|
||||
const basicForm = reactive({
|
||||
platformName: '代理管理平台',
|
||||
socks5Port: 1080,
|
||||
maxConnections: 10000,
|
||||
connectionTimeout: 30,
|
||||
loadBalanceStrategy: 'least_latency',
|
||||
})
|
||||
|
||||
const securityForm = reactive({
|
||||
jwtSecret: '',
|
||||
tokenExpiry: 24,
|
||||
minPasswordLength: 8,
|
||||
loginLockEnabled: true,
|
||||
loginLockThreshold: 5,
|
||||
})
|
||||
|
||||
const notificationForm = reactive({
|
||||
nodeOfflineNotify: true,
|
||||
unlockFailNotify: true,
|
||||
trafficWarningNotify: true,
|
||||
trafficWarningThreshold: 80,
|
||||
smtpHost: '',
|
||||
smtpPort: 587,
|
||||
smtpFrom: '',
|
||||
smtpPassword: '',
|
||||
webhookUrl: '',
|
||||
})
|
||||
|
||||
const systemInfo = reactive({
|
||||
startTime: '',
|
||||
uptime: '',
|
||||
totalRequests: 0,
|
||||
totalTraffic: 0,
|
||||
userCount: 0,
|
||||
nodeCount: 0,
|
||||
ruleCount: 0,
|
||||
logCount: 0,
|
||||
})
|
||||
|
||||
const formatBytes = (bytes: number) => {
|
||||
if (!bytes) return '0 B'
|
||||
const k = 1024
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
||||
}
|
||||
|
||||
const generateJWTSecret = () => {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
||||
let secret = ''
|
||||
for (let i = 0; i < 32; i++) {
|
||||
secret += chars.charAt(Math.floor(Math.random() * chars.length))
|
||||
}
|
||||
securityForm.jwtSecret = secret
|
||||
ElMessage.success('已生成新的 JWT 密钥')
|
||||
}
|
||||
|
||||
const saveBasicSettings = () => {
|
||||
ElMessage.success('基础设置已保存')
|
||||
}
|
||||
|
||||
const saveSecuritySettings = () => {
|
||||
ElMessage.success('安全设置已保存')
|
||||
}
|
||||
|
||||
const saveNotificationSettings = () => {
|
||||
ElMessage.success('通知设置已保存')
|
||||
}
|
||||
|
||||
const testNotification = () => {
|
||||
ElMessage.success('测试通知已发送')
|
||||
}
|
||||
|
||||
const refreshSystemInfo = () => {
|
||||
systemInfo.startTime = new Date().toLocaleString()
|
||||
systemInfo.uptime = '1小时 23分钟'
|
||||
ElMessage.success('系统信息已刷新')
|
||||
}
|
||||
|
||||
const clearLogs = async () => {
|
||||
await ElMessageBox.confirm('确定要清理日志吗?此操作不可恢复!', '警告', {
|
||||
type: 'warning',
|
||||
})
|
||||
ElMessage.success('日志已清理')
|
||||
}
|
||||
|
||||
const restartService = async () => {
|
||||
await ElMessageBox.confirm('确定要重启服务吗?', '警告', {
|
||||
type: 'warning',
|
||||
})
|
||||
ElMessage.warning('服务重启中...')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
refreshSystemInfo()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style lang="scss" scoped>
|
||||
.settings-page {
|
||||
:deep(.el-tabs__content) {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user