feat: 配置管理前端页面(站点页面+风险规则)+路由菜单

This commit is contained in:
mediabot-pt
2026-07-04 23:57:53 +08:00
parent 31800b9643
commit a7734b6b98
3 changed files with 194 additions and 0 deletions

View File

@@ -69,6 +69,12 @@ const routes = [
name: 'AccessResultCheck',
component: () => import('../views/AccessResultCheck.vue'),
meta: { title: '适配校验详情' }
},
{
path: 'config-mgr',
name: 'ConfigMgr',
component: () => import('../views/ConfigMgr.vue'),
meta: { title: '配置管理' }
}
]
}

184
web/src/views/ConfigMgr.vue Normal file
View File

@@ -0,0 +1,184 @@
<template>
<div>
<h2>配置管理</h2>
<el-card style="margin-top:20px">
<el-tabs v-model="activeTab">
<el-tab-pane label="站点页面配置" name="pages">
<div style="margin-bottom:12px;display:flex;gap:8px">
<el-select v-model="editSiteId" placeholder="选择站点" style="width:200px" clearable filterable>
<el-option v-for="s in sites" :key="s.siteId" :label="(s.names||[]).join('/')||s.siteId" :value="s.siteId" />
</el-select>
<el-button @click="loadPages" :loading="loading">加载</el-button>
<el-button @click="newPages">新建</el-button>
</div>
<div v-if="currentPages">
<div style="margin-bottom:8px;display:flex;gap:8px;align-items:center">
<span style="color:#909399">版本: {{ currentPages.version }} | 更新: {{ fmt(currentPages.updatedAt) }}</span>
</div>
<el-table :data="editingEntries" stripe size="small" style="margin-bottom:12px">
<el-table-column prop="key" label="标识" width="100">
<template #default="s"><el-input v-model="s.row.key" size="small" /></template>
</el-table-column>
<el-table-column prop="label" label="名称" width="100">
<template #default="s"><el-input v-model="s.row.label" size="small" /></template>
</el-table-column>
<el-table-column prop="url" label="URL" min-width="180">
<template #default="s"><el-input v-model="s.row.url" size="small" /></template>
</el-table-column>
<el-table-column prop="method" label="方法" width="80">
<template #default="s">
<el-select v-model="s.row.method" size="small"><el-option label="GET" value="GET" /><el-option label="POST" value="POST" /></el-select>
</template>
</el-table-column>
<el-table-column label="操作" width="60">
<template #default="s">
<el-button size="small" type="danger" text @click="editingEntries.splice(s.$index,1)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div style="display:flex;gap:8px">
<el-button size="small" @click="editingEntries.push({key:'',label:'',url:'{base}',method:'GET'})">+ 添加</el-button>
<el-button size="small" type="primary" @click="savePages" :loading="saving">保存</el-button>
</div>
</div>
<div v-else style="padding:40px;text-align:center;color:#909399" v-if="editSiteId">选择站点后加载</div>
<el-divider />
<div style="font-size:13px;font-weight:500;margin-bottom:8px">已配置的站点</div>
<el-table :data="allPages" stripe size="small" v-loading="loadingAll">
<el-table-column prop="siteId" label="站点" width="120" />
<el-table-column prop="version" label="版本" width="80" />
<el-table-column prop="updatedAt" label="更新时间" width="160">
<template #default="s">{{ fmt(s.row.updatedAt) }}</template>
</el-table-column>
<el-table-column label="操作" width="100">
<template #default="s"><el-button size="small" @click="editSiteId=s.row.siteId;loadPages()">编辑</el-button></template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="风险规则" name="rules">
<div style="margin-bottom:12px;display:flex;gap:8px">
<el-select v-model="editSiteType" placeholder="选择站点类型" style="width:200px" clearable>
<el-option v-for="t in siteTypes" :key="t" :label="t" :value="t" />
</el-select>
<el-button @click="loadRules" :loading="loading2">加载</el-button>
<el-button @click="newRules">新建</el-button>
</div>
<div v-if="currentRules">
<div style="margin-bottom:8px;color:#909399">版本: {{ currentRules.version }} | 更新: {{ fmt(currentRules.updatedAt) }}</div>
<el-table :data="editingRules" stripe size="small" style="margin-bottom:12px">
<el-table-column prop="type" label="类型" width="90">
<template #default="s"><el-select v-model="s.row.type" size="small"><el-option v-for="x in ['KILL','TRAFFIC','RATIO','LOGIN']" :key="x" :label="x" :value="x"/></el-select></template>
</el-table-column>
<el-table-column prop="name" label="名称" min-width="160">
<template #default="s"><el-input v-model="s.row.name" size="small" /></template>
</el-table-column>
<el-table-column prop="metric" label="指标" width="140">
<template #default="s"><el-input v-model="s.row.metric" size="small" /></template>
</el-table-column>
<el-table-column prop="threshold" label="阈值" width="70">
<template #default="s"><el-input v-model.number="s.row.threshold" size="small" type="number" /></template>
</el-table-column>
<el-table-column prop="condition" label="条件" width="70">
<template #default="s"><el-select v-model="s.row.condition" size="small"><el-option v-for="x in ['GT','LT','GTE','LTE']" :key="x" :label="x" :value="x"/></el-select></template>
</el-table-column>
<el-table-column prop="level" label="级别" width="90">
<template #default="s"><el-select v-model="s.row.level" size="small"><el-option v-for="x in ['CRITICAL','WARN']" :key="x" :label="x" :value="x"/></el-select></template>
</el-table-column>
<el-table-column label="操作" width="60">
<template #default="s"><el-button size="small" type="danger" text @click="editingRules.splice(s.$index,1)">删除</el-button></template>
</el-table-column>
</el-table>
<div style="display:flex;gap:8px">
<el-button size="small" @click="editingRules.push({type:'TRAFFIC',name:'',metric:'',threshold:0,condition:'GT',level:'WARN'})">+ 添加</el-button>
<el-button size="small" type="primary" @click="saveRules" :loading="saving2">保存</el-button>
</div>
</div>
<div v-else style="padding:40px;text-align:center;color:#909399" v-if="editSiteType">选择类型后加载</div>
<el-divider />
<div style="font-size:13px;font-weight:500;margin-bottom:8px">已配置的规则</div>
<el-table :data="allRules" stripe size="small" v-loading="loadingAll2">
<el-table-column prop="siteType" label="站点类型" width="120" />
<el-table-column prop="version" label="版本" width="80" />
<el-table-column prop="updatedAt" label="更新时间" width="160">
<template #default="s">{{ fmt(s.row.updatedAt) }}</template>
</el-table-column>
<el-table-column label="操作" width="100">
<template #default="s"><el-button size="small" @click="editSiteType=s.row.siteType;loadRules()">编辑</el-button></template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
</el-card>
</div>
</template>
<script setup>
import api from '../api'
const activeTab = ref('pages')
const editSiteId = ref(''), editSiteType = ref('')
const currentPages = ref(null), currentRules = ref(null)
const editingEntries = ref([]), editingRules = ref([])
const allPages = ref([]), allRules = ref([])
const sites = ref([])
const siteTypes = ['nexusphp','mtorrent','gazelle','unit3d']
const loading = ref(false), loading2 = ref(false), loadingAll = ref(false), loadingAll2 = ref(false)
const saving = ref(false), saving2 = ref(false)
const fmt = (t) => t ? t.substring(0,19).replace('T',' ') : '-'
const loadSites = async () => { try { sites.value = (await api.sites.adminList()).data || [] } catch {} }
const loadPages = async () => {
if (!editSiteId.value) return
loading.value = true
try {
const r = await api.get('/admin/site-pages/' + editSiteId.value)
currentPages.value = r.data || r
editingEntries.value = JSON.parse(JSON.stringify((currentPages.value?.entries||[])))
} catch { currentPages.value = null; ElMessage.error('加载失败') }
finally { loading.value = false }
}
const newPages = () => { currentPages.value = { version:'新版本' }; editingEntries.value = [{key:'home',label:'首页',url:'{base}',method:'GET'}] }
const savePages = async () => {
saving.value = true
try {
await api.put('/sites/' + editSiteId.value + '/pages', { entries: editingEntries.value })
ElMessage.success('已保存'); loadPages(); loadAllPages()
} catch { ElMessage.error('保存失败') }
finally { saving.value = false }
}
const loadAllPages = async () => { loadingAll.value = true; try { allPages.value = (await api.get('/admin/site-pages')).data || [] } catch {} finally { loadingAll.value = false } }
const loadRules = async () => {
if (!editSiteType.value) return
loading2.value = true
try {
const r = await api.get('/admin/risk-rules/' + editSiteType.value)
currentRules.value = r.data || r
editingRules.value = JSON.parse(JSON.stringify((currentRules.value?.rules||[])))
} catch { currentRules.value = null; ElMessage.error('加载失败') }
finally { loading2.value = false }
}
const newRules = () => { currentRules.value = { version:1 }; editingRules.value = [{type:'KILL',name:'',metric:'',threshold:0,condition:'GT',level:'CRITICAL'}] }
const saveRules = async () => {
saving2.value = true
try {
await api.post('/risk-rules/' + editSiteType.value, { rules: editingRules.value })
ElMessage.success('已保存'); loadRules(); loadAllRules()
} catch { ElMessage.error('保存失败') }
finally { saving2.value = false }
}
const loadAllRules = async () => { loadingAll2.value = true; try { allRules.value = (await api.get('/admin/risk-rules')).data || [] } catch {} finally { loadingAll2.value = false } }
onMounted(() => { loadSites(); loadAllPages(); loadAllRules() })
</script>

View File

@@ -26,6 +26,10 @@
<el-icon><Connection /></el-icon>
<span>适配验证</span>
</el-menu-item>
<el-menu-item index="/config-mgr">
<el-icon><Setting /></el-icon>
<span>配置管理</span>
</el-menu-item>
<el-menu-item index="/users">
<el-icon><UserFilled /></el-icon>
<span>用户管理</span>