|
@@ -130,22 +130,29 @@
|
|
<div class="api-names-header">
|
|
<div class="api-names-header">
|
|
<h4 class="section-title">API名称选择</h4>
|
|
<h4 class="section-title">API名称选择</h4>
|
|
<div class="api-names-actions">
|
|
<div class="api-names-actions">
|
|
- <span class="selected-count">已选择: {{ exportForm.apiNames.length }} 个</span>
|
|
|
|
- <a-button size="small" type="link" @click="selectAllApiNames">全选</a-button>
|
|
|
|
- <a-button size="small" type="link" @click="clearAllApiNames">清空</a-button>
|
|
|
|
|
|
+ <span class="selected-count">已选择: {{ exportForm.apiNames.length }} / {{ totalApiCount }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
+ <div class="api-quick-filters">
|
|
|
|
+ <a-button size="small" @click="selectApiNamesByCategory('tcp_udp_web')">TCP/UDP/Web相关</a-button>
|
|
|
|
+ <a-button size="small" @click="selectApiNamesByCategory('blacklist_whitelist')">黑白名单</a-button>
|
|
|
|
+ <a-button size="small" type="dashed" @click="selectAllApiNames">全选</a-button>
|
|
|
|
+ <a-button size="small" type="dashed" danger @click="clearAllApiNames">清空</a-button>
|
|
|
|
+ </div>
|
|
<div class="api-names-grid">
|
|
<div class="api-names-grid">
|
|
<a-checkbox-group v-model:value="exportForm.apiNames" class="custom-checkbox-group">
|
|
<a-checkbox-group v-model:value="exportForm.apiNames" class="custom-checkbox-group">
|
|
- <div class="checkbox-grid">
|
|
|
|
- <a-checkbox
|
|
|
|
- v-for="option in apiNameOptions"
|
|
|
|
- :key="option.value"
|
|
|
|
- :value="option.value"
|
|
|
|
- class="checkbox-item"
|
|
|
|
- >
|
|
|
|
- {{ option.label }}
|
|
|
|
- </a-checkbox>
|
|
|
|
|
|
+ <div v-for="category in categorizedApiOptions" :key="category.name" class="api-category">
|
|
|
|
+ <div class="api-category-title">{{ category.name }}</div>
|
|
|
|
+ <div class="checkbox-grid">
|
|
|
|
+ <a-checkbox
|
|
|
|
+ v-for="option in category.options"
|
|
|
|
+ :key="option.value"
|
|
|
|
+ :value="option.value"
|
|
|
|
+ class="checkbox-item"
|
|
|
|
+ >
|
|
|
|
+ {{ option.label }}
|
|
|
|
+ </a-checkbox>
|
|
|
|
+ </div>
|
|
</div>
|
|
</div>
|
|
</a-checkbox-group>
|
|
</a-checkbox-group>
|
|
</div>
|
|
</div>
|
|
@@ -230,7 +237,7 @@
|
|
</template>
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
-import { ref, onMounted } from 'vue';
|
|
|
|
|
|
+import { ref, onMounted, computed } from 'vue';
|
|
import { message } from 'ant-design-vue';
|
|
import { message } from 'ant-design-vue';
|
|
import dayjs from 'dayjs';
|
|
import dayjs from 'dayjs';
|
|
import { getWafLogList, getApiDescriptions, exportWafLog } from '~/api/waf/waflog.js';
|
|
import { getWafLogList, getApiDescriptions, exportWafLog } from '~/api/waf/waflog.js';
|
|
@@ -278,7 +285,6 @@ const columns = [
|
|
const fetchData = () => {
|
|
const fetchData = () => {
|
|
loading.value = true;
|
|
loading.value = true;
|
|
|
|
|
|
- // 构造请求参数,并处理特殊类型
|
|
|
|
const params = {
|
|
const params = {
|
|
requestIp: queryParam.value.requestIp || '',
|
|
requestIp: queryParam.value.requestIp || '',
|
|
uid: queryParam.value.uid ? parseInt(queryParam.value.uid) : '',
|
|
uid: queryParam.value.uid ? parseInt(queryParam.value.uid) : '',
|
|
@@ -294,10 +300,7 @@ const fetchData = () => {
|
|
order: 'desc'
|
|
order: 'desc'
|
|
};
|
|
};
|
|
|
|
|
|
- console.log('发送WAF日志查询参数:', params);
|
|
|
|
-
|
|
|
|
getWafLogList(params).then(response => {
|
|
getWafLogList(params).then(response => {
|
|
- // 根据实际返回格式处理数据
|
|
|
|
if (response && response.code === 0 && response.data) {
|
|
if (response && response.code === 0 && response.data) {
|
|
const apiData = response.data;
|
|
const apiData = response.data;
|
|
if (apiData.records && Array.isArray(apiData.records)) {
|
|
if (apiData.records && Array.isArray(apiData.records)) {
|
|
@@ -308,12 +311,10 @@ const fetchData = () => {
|
|
} else {
|
|
} else {
|
|
dataSource.value = [];
|
|
dataSource.value = [];
|
|
pagination.value.total = 0;
|
|
pagination.value.total = 0;
|
|
- console.warn('响应中无WAF日志数据记录');
|
|
|
|
}
|
|
}
|
|
} else {
|
|
} else {
|
|
dataSource.value = [];
|
|
dataSource.value = [];
|
|
pagination.value.total = 0;
|
|
pagination.value.total = 0;
|
|
- console.error('无效的WAF日志响应格式或响应错误', response);
|
|
|
|
}
|
|
}
|
|
loading.value = false;
|
|
loading.value = false;
|
|
}).catch((error) => {
|
|
}).catch((error) => {
|
|
@@ -348,10 +349,13 @@ const resetSearch = () => {
|
|
|
|
|
|
const detailModalRef = ref(null);
|
|
const detailModalRef = ref(null);
|
|
|
|
|
|
|
|
+const goToInfo = (id) => {
|
|
|
|
+ detailModalRef.value?.open(id);
|
|
|
|
+};
|
|
|
|
+
|
|
// 导出相关
|
|
// 导出相关
|
|
const exportModalVisible = ref(false);
|
|
const exportModalVisible = ref(false);
|
|
const exportLoading = ref(false);
|
|
const exportLoading = ref(false);
|
|
-const apiNameOptions = ref([]);
|
|
|
|
const advancedFilterKey = ref([]);
|
|
const advancedFilterKey = ref([]);
|
|
const exportForm = ref({
|
|
const exportForm = ref({
|
|
id: '',
|
|
id: '',
|
|
@@ -368,32 +372,71 @@ const exportForm = ref({
|
|
endTime: null
|
|
endTime: null
|
|
});
|
|
});
|
|
|
|
|
|
-// 判断是否为TCP、UDP、Web相关的API
|
|
|
|
-const isTcpUdpWebRelated = (apiName) => {
|
|
|
|
- const keywords = ['tcp', 'udp', 'web', '游戏盾', '转发', '网关', '限流', '防护', '规则', '策略'];
|
|
|
|
- const lowerApiName = apiName.toLowerCase();
|
|
|
|
- return keywords.some(keyword => lowerApiName.includes(keyword.toLowerCase()));
|
|
|
|
|
|
+const categorizedApiOptions = ref([]);
|
|
|
|
+const totalApiCount = computed(() => {
|
|
|
|
+ return categorizedApiOptions.value.reduce((acc, category) => acc + category.options.length, 0);
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+// API 名称关键字分类
|
|
|
|
+const apiNameCategories = {
|
|
|
|
+ tcp_udp_web: { name: 'TCP/UDP/Web 相关', keywords: ['tcp', 'udp', '转发', 'web', '网站', 'cdn', 'host', '域名', '游戏盾', '网关', 'gateway'] },
|
|
|
|
+ blacklist_whitelist: { name: '黑白名单', keywords: ['ip', '黑白名单', 'waf', '防护', '规则', '策略', 'cc'] },
|
|
};
|
|
};
|
|
|
|
|
|
-const goToInfo = (id) => {
|
|
|
|
- // 打开模态框而不是跳转页面
|
|
|
|
- detailModalRef.value?.open(id);
|
|
|
|
|
|
+const categorizeApiOptions = (options) => {
|
|
|
|
+ const categories = {
|
|
|
|
+ tcp_udp_web: { name: apiNameCategories.tcp_udp_web.name, options: [] },
|
|
|
|
+ blacklist_whitelist: { name: apiNameCategories.blacklist_whitelist.name, options: [] },
|
|
|
|
+ other: { name: '其他', options: [] }
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ const allKeywords = new Set([
|
|
|
|
+ ...apiNameCategories.tcp_udp_web.keywords,
|
|
|
|
+ ...apiNameCategories.blacklist_whitelist.keywords
|
|
|
|
+ ]);
|
|
|
|
+
|
|
|
|
+ options.forEach(option => {
|
|
|
|
+ const lowerLabel = option.label.toLowerCase();
|
|
|
|
+ if (apiNameCategories.tcp_udp_web.keywords.some(kw => lowerLabel.includes(kw))) {
|
|
|
|
+ categories.tcp_udp_web.options.push(option);
|
|
|
|
+ } else if (apiNameCategories.blacklist_whitelist.keywords.some(kw => lowerLabel.includes(kw))) {
|
|
|
|
+ categories.blacklist_whitelist.options.push(option);
|
|
|
|
+ } else {
|
|
|
|
+ categories.other.options.push(option);
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ return Object.values(categories).filter(c => c.options.length > 0);
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+// 根据分类选择API名称
|
|
|
|
+const selectApiNamesByCategory = (categoryKey) => {
|
|
|
|
+ const category = categorizedApiOptions.value.find(c => c.name === apiNameCategories[categoryKey].name);
|
|
|
|
+ if (category) {
|
|
|
|
+ exportForm.value.apiNames = category.options.map(opt => opt.value);
|
|
|
|
+ } else {
|
|
|
|
+ exportForm.value.apiNames = [];
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 默认选中TCP, UDP, Web相关
|
|
|
|
+const setDefaultApiSelection = () => {
|
|
|
|
+ selectApiNamesByCategory('tcp_udp_web');
|
|
};
|
|
};
|
|
|
|
|
|
// 显示导出弹窗
|
|
// 显示导出弹窗
|
|
const showExportModal = async () => {
|
|
const showExportModal = async () => {
|
|
try {
|
|
try {
|
|
- // 获取API描述映射
|
|
|
|
const response = await getApiDescriptions();
|
|
const response = await getApiDescriptions();
|
|
if (response && response.code === 0 && response.data) {
|
|
if (response && response.code === 0 && response.data) {
|
|
- // 将API描述映射转换为checkbox选项格式,只显示中文名称
|
|
|
|
- apiNameOptions.value = Object.entries(response.data).map(([key, value]) => ({
|
|
|
|
- label: value, // 只显示中文名称
|
|
|
|
- value: value // 传给后端的值也是中文名称
|
|
|
|
|
|
+ const allOptions = Object.entries(response.data).map(([key, value]) => ({
|
|
|
|
+ label: value,
|
|
|
|
+ value: value
|
|
}));
|
|
}));
|
|
|
|
+ categorizedApiOptions.value = categorizeApiOptions(allOptions);
|
|
}
|
|
}
|
|
|
|
|
|
- // 重置表单
|
|
|
|
exportForm.value = {
|
|
exportForm.value = {
|
|
id: '',
|
|
id: '',
|
|
uid: '',
|
|
uid: '',
|
|
@@ -409,17 +452,11 @@ const showExportModal = async () => {
|
|
endTime: null
|
|
endTime: null
|
|
};
|
|
};
|
|
|
|
|
|
- // 默认选中TCP、UDP、Web相关的API
|
|
|
|
- if (apiNameOptions.value.length > 0) {
|
|
|
|
- const defaultSelected = apiNameOptions.value
|
|
|
|
- .filter(option => isTcpUdpWebRelated(option.label))
|
|
|
|
- .map(option => option.value);
|
|
|
|
- exportForm.value.apiNames = defaultSelected;
|
|
|
|
|
|
+ if (totalApiCount.value > 0) {
|
|
|
|
+ setDefaultApiSelection();
|
|
}
|
|
}
|
|
|
|
|
|
- // 重置高级筛选折叠状态
|
|
|
|
advancedFilterKey.value = [];
|
|
advancedFilterKey.value = [];
|
|
-
|
|
|
|
exportModalVisible.value = true;
|
|
exportModalVisible.value = true;
|
|
} catch (error) {
|
|
} catch (error) {
|
|
console.error('获取API描述失败:', error);
|
|
console.error('获取API描述失败:', error);
|
|
@@ -430,7 +467,6 @@ const showExportModal = async () => {
|
|
// 时间范围快捷选择
|
|
// 时间范围快捷选择
|
|
const setTimeRange = (range) => {
|
|
const setTimeRange = (range) => {
|
|
const now = dayjs();
|
|
const now = dayjs();
|
|
-
|
|
|
|
switch (range) {
|
|
switch (range) {
|
|
case 'today':
|
|
case 'today':
|
|
exportForm.value.startTime = now.startOf('day');
|
|
exportForm.value.startTime = now.startOf('day');
|
|
@@ -452,30 +488,28 @@ const setTimeRange = (range) => {
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
-// 清空时间范围
|
|
|
|
const clearTimeRange = () => {
|
|
const clearTimeRange = () => {
|
|
exportForm.value.startTime = null;
|
|
exportForm.value.startTime = null;
|
|
exportForm.value.endTime = null;
|
|
exportForm.value.endTime = null;
|
|
};
|
|
};
|
|
|
|
|
|
-// 全选API名称
|
|
|
|
const selectAllApiNames = () => {
|
|
const selectAllApiNames = () => {
|
|
- exportForm.value.apiNames = apiNameOptions.value.map(option => option.value);
|
|
|
|
|
|
+ let allNames = [];
|
|
|
|
+ categorizedApiOptions.value.forEach(category => {
|
|
|
|
+ allNames.push(...category.options.map(opt => opt.value));
|
|
|
|
+ });
|
|
|
|
+ exportForm.value.apiNames = allNames;
|
|
};
|
|
};
|
|
|
|
|
|
-// 清空API名称选择
|
|
|
|
const clearAllApiNames = () => {
|
|
const clearAllApiNames = () => {
|
|
exportForm.value.apiNames = [];
|
|
exportForm.value.apiNames = [];
|
|
};
|
|
};
|
|
|
|
|
|
-// 处理导出
|
|
|
|
const handleExport = async () => {
|
|
const handleExport = async () => {
|
|
try {
|
|
try {
|
|
exportLoading.value = true;
|
|
exportLoading.value = true;
|
|
|
|
|
|
- // 构造导出参数,只传递有值的参数
|
|
|
|
const params = {};
|
|
const params = {};
|
|
-
|
|
|
|
if (exportForm.value.id) params.id = parseInt(exportForm.value.id);
|
|
if (exportForm.value.id) params.id = parseInt(exportForm.value.id);
|
|
if (exportForm.value.uid) params.uid = parseInt(exportForm.value.uid);
|
|
if (exportForm.value.uid) params.uid = parseInt(exportForm.value.uid);
|
|
if (exportForm.value.name) params.name = exportForm.value.name;
|
|
if (exportForm.value.name) params.name = exportForm.value.name;
|
|
@@ -499,12 +533,8 @@ const handleExport = async () => {
|
|
params.endTime = dayjs(exportForm.value.endTime).format('YYYY-MM-DD HH:mm:ss');
|
|
params.endTime = dayjs(exportForm.value.endTime).format('YYYY-MM-DD HH:mm:ss');
|
|
}
|
|
}
|
|
|
|
|
|
- console.log('导出参数:', params);
|
|
|
|
-
|
|
|
|
- // 调用导出接口
|
|
|
|
const response = await exportWafLog(params);
|
|
const response = await exportWafLog(params);
|
|
|
|
|
|
- // 处理文件下载
|
|
|
|
const blob = new Blob([response], {
|
|
const blob = new Blob([response], {
|
|
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
});
|
|
});
|
|
@@ -527,7 +557,6 @@ const handleExport = async () => {
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
-// 取消导出
|
|
|
|
const handleExportCancel = () => {
|
|
const handleExportCancel = () => {
|
|
exportModalVisible.value = false;
|
|
exportModalVisible.value = false;
|
|
};
|
|
};
|
|
@@ -542,7 +571,6 @@ onMounted(() => {
|
|
margin-bottom: 24px;
|
|
margin-bottom: 24px;
|
|
}
|
|
}
|
|
|
|
|
|
-/* 表格样式优化 */
|
|
|
|
:deep(.ant-table-body) {
|
|
:deep(.ant-table-body) {
|
|
overflow-x: auto !important;
|
|
overflow-x: auto !important;
|
|
}
|
|
}
|
|
@@ -572,7 +600,6 @@ onMounted(() => {
|
|
background: #a8a8a8;
|
|
background: #a8a8a8;
|
|
}
|
|
}
|
|
|
|
|
|
-/* 导出弹窗样式 */
|
|
|
|
.export-form-container {
|
|
.export-form-container {
|
|
max-height: 70vh;
|
|
max-height: 70vh;
|
|
overflow-y: auto;
|
|
overflow-y: auto;
|
|
@@ -604,7 +631,6 @@ onMounted(() => {
|
|
color: #262626;
|
|
color: #262626;
|
|
}
|
|
}
|
|
|
|
|
|
-/* 时间范围选择区域 */
|
|
|
|
.time-range-section {
|
|
.time-range-section {
|
|
background: #fafafa;
|
|
background: #fafafa;
|
|
border: 1px solid #f0f0f0;
|
|
border: 1px solid #f0f0f0;
|
|
@@ -630,7 +656,6 @@ onMounted(() => {
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
}
|
|
}
|
|
|
|
|
|
-/* API名称选择区域 */
|
|
|
|
.api-names-section {
|
|
.api-names-section {
|
|
background: #fafafa;
|
|
background: #fafafa;
|
|
border: 1px solid #f0f0f0;
|
|
border: 1px solid #f0f0f0;
|
|
@@ -646,6 +671,13 @@ onMounted(() => {
|
|
margin-bottom: 12px;
|
|
margin-bottom: 12px;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+.api-quick-filters {
|
|
|
|
+ display: flex;
|
|
|
|
+ gap: 8px;
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
+}
|
|
|
|
+
|
|
.api-names-actions {
|
|
.api-names-actions {
|
|
display: flex;
|
|
display: flex;
|
|
align-items: center;
|
|
align-items: center;
|
|
@@ -655,11 +687,10 @@ onMounted(() => {
|
|
.selected-count {
|
|
.selected-count {
|
|
font-size: 12px;
|
|
font-size: 12px;
|
|
color: #666;
|
|
color: #666;
|
|
- margin-right: 8px;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
.api-names-grid {
|
|
.api-names-grid {
|
|
- max-height: 200px;
|
|
|
|
|
|
+ max-height: 250px;
|
|
overflow-y: auto;
|
|
overflow-y: auto;
|
|
border: 1px solid #e8e8e8;
|
|
border: 1px solid #e8e8e8;
|
|
border-radius: 6px;
|
|
border-radius: 6px;
|
|
@@ -689,9 +720,21 @@ onMounted(() => {
|
|
width: 100%;
|
|
width: 100%;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+.api-category + .api-category {
|
|
|
|
+ margin-top: 16px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.api-category-title {
|
|
|
|
+ font-weight: 500;
|
|
|
|
+ color: #555;
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
+ padding-bottom: 6px;
|
|
|
|
+ border-bottom: 1px solid #f0f0f0;
|
|
|
|
+}
|
|
|
|
+
|
|
.checkbox-grid {
|
|
.checkbox-grid {
|
|
display: grid;
|
|
display: grid;
|
|
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
|
|
|
|
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
|
gap: 8px 16px;
|
|
gap: 8px 16px;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -709,7 +752,6 @@ onMounted(() => {
|
|
background-color: #f5f5f5;
|
|
background-color: #f5f5f5;
|
|
}
|
|
}
|
|
|
|
|
|
-/* 高级筛选折叠面板样式 */
|
|
|
|
:deep(.ant-collapse-ghost .ant-collapse-item) {
|
|
:deep(.ant-collapse-ghost .ant-collapse-item) {
|
|
border: 1px solid #f0f0f0;
|
|
border: 1px solid #f0f0f0;
|
|
border-radius: 8px;
|
|
border-radius: 8px;
|
|
@@ -732,7 +774,6 @@ onMounted(() => {
|
|
padding: 16px;
|
|
padding: 16px;
|
|
}
|
|
}
|
|
|
|
|
|
-/* 导出弹窗整体样式 */
|
|
|
|
:deep(.export-modal .ant-modal-body) {
|
|
:deep(.export-modal .ant-modal-body) {
|
|
padding: 20px 24px;
|
|
padding: 20px 24px;
|
|
}
|
|
}
|
|
@@ -746,4 +787,4 @@ onMounted(() => {
|
|
font-size: 16px;
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
font-weight: 600;
|
|
}
|
|
}
|
|
-</style>
|
|
|
|
|
|
+</style>
|