feat: 页面配置+风险规则右侧改为可编辑表格双向同步JSON编辑器
This commit is contained in:
@@ -43,20 +43,34 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="flex:1;overflow:auto;min-height:0">
|
<div style="flex:1;overflow:auto;min-height:0">
|
||||||
<div v-if="!parsed.length" style="text-align:center;padding:40px;color:#909399">该类型暂无规则</div>
|
<div style="font-size:13px;font-weight:500;margin-bottom:4px">双向可视化</div>
|
||||||
<el-table v-else :data="parsed" stripe border style="font-size:13px" :row-style="{height:'42px'}">
|
<el-table :data="parsed" stripe border style="font-size:13px" :row-style="{height:'42px'}" v-if="parsed.length">
|
||||||
<el-table-column label="级别" width="80">
|
<el-table-column label="级别" width="80">
|
||||||
<template #default="s"><el-tag :type="s.row.level==='CRITICAL'?'danger':'warning'" size="small">{{ s.row.level }}</el-tag></template>
|
<template #default="s"><el-select v-model="s.row.level" size="small" @change="sync"><el-option v-for="x in ['CRITICAL','WARN','INFO']" :key="x" :label="x" :value="x"/></el-select></template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="type" label="类型" width="90" />
|
<el-table-column label="类型" width="90">
|
||||||
<el-table-column prop="name" label="名称" min-width="180" />
|
<template #default="s"><el-select v-model="s.row.type" size="small" @change="sync"><el-option v-for="x in ['KILL','TRAFFIC','RATIO','LOGIN','HD']" :key="x" :label="x" :value="x"/></el-select></template>
|
||||||
<el-table-column label="条件" width="180">
|
|
||||||
<template #default="s">{{ s.row.metric }} {{ condText(s.row.condition) }} {{ s.row.threshold }} {{ s.row.unit||'' }}</template>
|
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="适用" width="130">
|
<el-table-column prop="name" label="名称" min-width="160">
|
||||||
<template #default="s">{{ appText(s.row) || '-' }}</template>
|
<template #default="s"><el-input v-model="s.row.name" size="small" @input="sync"/></template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="metric" label="指标" width="120">
|
||||||
|
<template #default="s"><el-input v-model="s.row.metric" size="small" @input="sync"/></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" @input="sync"/></template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="unit" label="单位" width="60">
|
||||||
|
<template #default="s"><el-input v-model="s.row.unit" size="small" @input="sync"/></template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="条件" width="70">
|
||||||
|
<template #default="s"><el-select v-model="s.row.condition" size="small" @change="sync"><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 width="50">
|
||||||
|
<template #default="s"><el-button size="small" type="danger" text @click="remove(s.$index)">×</el-button></template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
<div v-else style="text-align:center;padding:40px;color:#909399">该类型暂无规则</div>
|
||||||
<div style="margin-top:8px"><el-button size="small" @click="addRule">+ 添加</el-button></div>
|
<div style="margin-top:8px"><el-button size="small" @click="addRule">+ 添加</el-button></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -69,8 +83,6 @@ import api from '../api'; import { Refresh } from '@element-plus/icons-vue'
|
|||||||
const loading = ref(false), saving = ref(false), sites = ref([]), rulesMap = ref({})
|
const loading = ref(false), saving = ref(false), sites = ref([]), rulesMap = ref({})
|
||||||
const editType = ref(''), editorJson = ref('[]')
|
const editType = ref(''), editorJson = ref('[]')
|
||||||
const fmt = (t) => t ? (t.substring(0,19)||'').replace('T',' ') : '-'
|
const fmt = (t) => t ? (t.substring(0,19)||'').replace('T',' ') : '-'
|
||||||
const condText = (c) => ({GT:'>',LT:'<',GTE:'>=',LTE:'<=',EQ:'='})[c]||c
|
|
||||||
const appText = (r) => r.applicable ? Object.entries(r.applicable).map(([k,v])=>k+'>='+v).join(', ') : null
|
|
||||||
const parsed = computed(() => { try { return JSON.parse(editorJson.value) } catch { return [] } })
|
const parsed = computed(() => { try { return JSON.parse(editorJson.value) } catch { return [] } })
|
||||||
const merged = computed(() => (Array.isArray(sites.value)?sites.value:[]).map(s => ({
|
const merged = computed(() => (Array.isArray(sites.value)?sites.value:[]).map(s => ({
|
||||||
siteId: s.siteId, names: s.names, siteType: s.siteType,
|
siteId: s.siteId, names: s.names, siteType: s.siteType,
|
||||||
@@ -98,7 +110,9 @@ const editRule = async (row) => {
|
|||||||
try { const r = await api.raw.get('/admin/risk-rules/'+row.siteType); editorJson.value = (r?.data||r)?.rules ? JSON.stringify((r.data||r).rules, null, 2) : '[]' }
|
try { const r = await api.raw.get('/admin/risk-rules/'+row.siteType); editorJson.value = (r?.data||r)?.rules ? JSON.stringify((r.data||r).rules, null, 2) : '[]' }
|
||||||
catch { editorJson.value = '[]' }
|
catch { editorJson.value = '[]' }
|
||||||
}
|
}
|
||||||
const addRule = () => { parsed.value.push({type:'TRAFFIC',name:'',metric:'',threshold:0,unit:'DAY',condition:'GT',level:'WARN'}); editorJson.value = JSON.stringify(parsed.value, null, 2) }
|
const sync = () => { editorJson.value = JSON.stringify(parsed.value, null, 2) }
|
||||||
|
const remove = (idx) => { parsed.value.splice(idx,1); sync() }
|
||||||
|
const addRule = () => { parsed.value.push({type:'TRAFFIC',name:'',metric:'',threshold:0,unit:'DAY',condition:'GT',level:'WARN'}); sync() }
|
||||||
const saveRules = async () => {
|
const saveRules = async () => {
|
||||||
saving.value = true
|
saving.value = true
|
||||||
try { await api.raw.post('/risk-rules/'+editType.value, { rules: parsed.value }); ElMessage.success('已保存'); editType.value=''; fetchAll() }
|
try { await api.raw.post('/risk-rules/'+editType.value, { rules: parsed.value }); ElMessage.success('已保存'); editType.value=''; fetchAll() }
|
||||||
|
|||||||
@@ -42,15 +42,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="flex:1;overflow:auto;min-height:0">
|
<div style="flex:1;overflow:auto;min-height:0">
|
||||||
<div v-if="!parsed.length" style="text-align:center;padding:40px;color:#909399">暂无页面入口</div>
|
<div style="font-size:13px;font-weight:500;margin-bottom:4px">双向可视化</div>
|
||||||
<el-table v-else :data="parsed" stripe border style="font-size:13px" :row-style="{height:'42px'}">
|
<el-table :data="parsed" stripe border style="font-size:13px" :row-style="{height:'42px'}" v-if="parsed.length">
|
||||||
<el-table-column prop="key" label="标识" width="100" />
|
<el-table-column prop="key" label="key" width="100">
|
||||||
<el-table-column prop="label" label="名称" width="110" />
|
<template #default="s"><el-input v-model="s.row.key" size="small" @input="syncToJson"/></template>
|
||||||
<el-table-column prop="method" label="方法" width="70" />
|
</el-table-column>
|
||||||
<el-table-column label="完整URL" min-width="250">
|
<el-table-column prop="label" label="名称" width="110">
|
||||||
<template #default="s">{{ resolveUrl(s.row.url) }}</template>
|
<template #default="s"><el-input v-model="s.row.label" size="small" @input="syncToJson"/></template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="method" label="方法" width="80">
|
||||||
|
<template #default="s"><el-select v-model="s.row.method" size="small" @change="syncToJson"><el-option label="GET" value="GET"/><el-option label="POST" value="POST"/></el-select></template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="URL" min-width="200">
|
||||||
|
<template #default="s"><el-input v-model="s.row.url" size="small" @input="syncToJson"/></template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column width="50">
|
||||||
|
<template #default="s"><el-button size="small" type="danger" text @click="remove(s.$index)">×</el-button></template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
<div v-else style="text-align:center;padding:40px;color:#909399">暂无页面入口</div>
|
||||||
<div style="margin-top:8px"><el-button size="small" @click="addEntry">+ 添加</el-button></div>
|
<div style="margin-top:8px"><el-button size="small" @click="addEntry">+ 添加</el-button></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -93,7 +103,9 @@ const editPage = async (row) => {
|
|||||||
catch { editorJson.value = '[]' }
|
catch { editorJson.value = '[]' }
|
||||||
}
|
}
|
||||||
|
|
||||||
const addEntry = () => { parsed.value.push({key:'',label:'',url:'{base}',method:'GET'}); editorJson.value = JSON.stringify(parsed.value, null, 2) }
|
const syncToJson = () => { editorJson.value = JSON.stringify(parsed.value, null, 2) }
|
||||||
|
const remove = (idx) => { parsed.value.splice(idx,1); syncToJson() }
|
||||||
|
const addEntry = () => { parsed.value.push({key:'',label:'',url:'{base}',method:'GET'}); syncToJson() }
|
||||||
const savePages = async () => {
|
const savePages = async () => {
|
||||||
saving.value = true
|
saving.value = true
|
||||||
try { await api.raw.put('/sites/'+editSiteId.value+'/pages', { entries: parsed.value }); ElMessage.success('已保存'); editSiteId.value=''; fetchAll() }
|
try { await api.raw.put('/sites/'+editSiteId.value+'/pages', { entries: parsed.value }); ElMessage.success('已保存'); editSiteId.value=''; fetchAll() }
|
||||||
|
|||||||
Reference in New Issue
Block a user