拆分系统监测模块并统一运维化文案

This commit is contained in:
Cheng Zhou
2026-06-11 11:06:59 +08:00
parent b7484c8e01
commit 1886765db8
21 changed files with 222 additions and 143 deletions
+6 -2
View File
@@ -28,6 +28,10 @@
<el-icon><Document /></el-icon>
<span>{{ TEXT.menu.auditLogs }}</span>
</el-menu-item>
<el-menu-item v-if="isAdmin" index="/admin/system-monitoring">
<el-icon><DataAnalysis /></el-icon>
<span>{{ TEXT.menu.systemMonitoring }}</span>
</el-menu-item>
<el-sub-menu v-if="canAccessAdminPermissions" index="permissions">
<template #title>
<el-icon><Key /></el-icon>
@@ -35,7 +39,6 @@
</template>
<el-menu-item index="/admin/permissions/system">系统级权限</el-menu-item>
<el-menu-item index="/admin/permissions/project">项目权限配置</el-menu-item>
<el-menu-item index="/admin/permissions/monitoring">权限监控</el-menu-item>
</el-sub-menu>
</el-menu-item-group>
@@ -235,7 +238,7 @@ import { fetchSites } from "../api/sites";
import { TEXT } from "../locales";
import {
User, Suitcase, House, Calendar, Flag,
CircleCheck, Box, Coin, Notebook, Document, ArrowDown, SwitchButton, Files, Key
CircleCheck, Box, Coin, Notebook, Document, ArrowDown, SwitchButton, Files, Key, DataAnalysis
} from "@element-plus/icons-vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { getProjectRoutePermission, hasProjectPermission, projectRouteLandingPaths } from "../utils/projectRoutePermissions";
@@ -301,6 +304,7 @@ const activeMenu = computed(() => {
if (path.startsWith("/knowledge/instruction-files")) return "/knowledge/instruction-files";
if (path.startsWith("/projects/")) return "/admin/projects";
if (path.startsWith("/admin/projects/")) return "/admin/projects";
if (path.startsWith("/admin/system-monitoring") || path.startsWith("/admin/permission-monitoring")) return "/admin/system-monitoring";
if (path.startsWith("/admin/permissions/")) return path;
return path;
});
@@ -0,0 +1,22 @@
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
const readSource = () => readFileSync(resolve(__dirname, "./Layout.vue"), "utf8");
describe("layout system monitoring navigation", () => {
it("shows system monitoring as an admin-only peer module outside permission management", () => {
const source = readSource();
const permissionMenuStart = source.indexOf('<el-sub-menu v-if="canAccessAdminPermissions" index="permissions">');
const permissionMenuEnd = source.indexOf("</el-sub-menu>", permissionMenuStart);
const permissionMenu = source.slice(permissionMenuStart, permissionMenuEnd);
expect(source).toContain('v-if="isAdmin" index="/admin/system-monitoring"');
expect(source).toContain("{{ TEXT.menu.systemMonitoring }}");
expect(permissionMenu).not.toContain("/admin/system-monitoring");
expect(permissionMenu).not.toContain("/admin/permission-monitoring");
expect(permissionMenu).not.toContain("/admin/permissions/monitoring");
expect(source).not.toContain('index="/admin/permissions/monitoring"');
expect(source).toContain('return "/admin/system-monitoring";');
});
});
@@ -21,13 +21,25 @@ describe("PermissionIpLocations", () => {
expect(source).toContain("VChart");
expect(source).toContain("chinaMapOption");
expect(source).toContain("用户分布");
expect(source).toContain("访问来源分布");
expect(source).toContain("访问次数");
expect(source).toContain("访问用户数");
expect(source).toContain("来源 IP 数");
expect(source).toContain("summary");
});
it("uses source-analysis copy instead of IP-location tab wording", () => {
const source = readSource();
expect(source).toContain("访问来源分析");
expect(source).toContain("来源分布");
expect(source).toContain("热点来源地");
expect(source).not.toContain("IP 属地分析");
expect(source).not.toContain("中国地图");
expect(source).not.toContain("热点属地");
expect(source).not.toContain("用户分布图");
});
it("uses the standard geojson.cn China map data instead of generated rectangles", () => {
const source = readSource();
const mapSource = readMapSource();
@@ -3,7 +3,7 @@
<section class="ip-hero">
<div class="ip-locations-toolbar">
<div class="toolbar-left">
<span class="toolbar-title">IP 属地分析</span>
<span class="toolbar-title">访问来源分析</span>
<span class="toolbar-desc">查看访问来源的地理分布</span>
</div>
<div class="toolbar-actions">
@@ -37,7 +37,7 @@
<div class="map-panel">
<div class="section-head">
<div class="section-title-group">
<h4>中国地图</h4>
<h4>来源分布</h4>
<p>访问来源地理分布热力图</p>
</div>
<el-tag type="info" effect="plain" round size="small">{{ currentPeriodLabel }}</el-tag>
@@ -48,7 +48,7 @@
<aside class="rank-panel">
<div class="section-head compact">
<div class="section-title-group">
<h4>热点</h4>
<h4>热点来源</h4>
<p>按访问次数排序</p>
</div>
</div>
@@ -219,7 +219,7 @@ const chinaMapOption = computed(() => ({
},
series: [
{
name: "用户分布",
name: "访问来源分布",
type: "map",
map: "ctms-china",
roam: false,
@@ -77,4 +77,28 @@ describe("PermissionMonitoring.vue", () => {
expect(source).toContain("isAdmin?: boolean");
expect(source).toContain(':show-security-log="props.isAdmin"');
});
it("uses system monitoring tab names and operation-oriented overview copy", () => {
const source = readFileSync(resolve(__dirname, "./PermissionMonitoring.vue"), "utf8");
expect(source).toContain('label="运行概览"');
expect(source).toContain('label="性能趋势"');
expect(source).toContain('label="访问审计"');
expect(source).toContain('label="来源分析"');
expect(source).toContain('label: "今日监测事件"');
expect(source).toContain('label: "每分钟事件"');
expect(source).toContain('label: "今日通过率"');
expect(source).toContain('label: "历史事件总数"');
expect(source).toContain("异常拒绝排行");
expect(source).not.toContain('label="实时概览"');
expect(source).not.toContain('label="趋势分析"');
expect(source).not.toContain('label="访问日志"');
expect(source).not.toContain('label="IP属地"');
expect(source).not.toContain('label: "今日总检查"');
expect(source).not.toContain('label: "每分钟请求"');
expect(source).not.toContain('label: "今日允许率"');
expect(source).not.toContain('label: "历史总记录"');
expect(source).not.toContain("被拒绝最多的权限");
});
});
@@ -1,7 +1,7 @@
<template>
<div class="permission-monitoring">
<el-tabs v-model="activeTab" @tab-change="onTabChange">
<el-tab-pane label="实时概览" name="overview">
<el-tab-pane label="运行概览" name="overview">
<div v-if="statsSummary" class="stats-summary">
<div v-for="card in summaryCards" :key="card.label" class="summary-card" :class="card.tone">
<div class="summary-icon">
@@ -83,7 +83,7 @@
<div v-if="topDenied && topDenied.length > 0" class="denied-card">
<div class="card-header">
<h3>被拒绝最多的权限</h3>
<h3>异常拒绝排行</h3>
<el-tag effect="plain" size="small" type="info"> 7 </el-tag>
</div>
<div class="denied-list">
@@ -121,15 +121,15 @@
</div>
</el-tab-pane>
<el-tab-pane label="趋势分析" name="trends">
<el-tab-pane label="性能趋势" name="trends">
<PermissionTrendCharts ref="trendChartsRef" />
</el-tab-pane>
<el-tab-pane label="访问日志" name="logs">
<el-tab-pane label="访问审计" name="logs">
<PermissionAccessLogs ref="accessLogsRef" :show-security-log="props.isAdmin" />
</el-tab-pane>
<el-tab-pane label="IP属地" name="ip-locations">
<el-tab-pane label="来源分析" name="ip-locations">
<PermissionIpLocations ref="ipLocationsRef" />
</el-tab-pane>
</el-tabs>
@@ -226,19 +226,19 @@ const summaryCards = computed(() => {
if (!statsSummary.value) return [];
return [
{
label: "今日总检查",
label: "今日监测事件",
value: statsSummary.value.today.total_checks.toLocaleString(),
tone: "blue",
icon: IconCheck,
},
{
label: "每分钟请求",
label: "每分钟事件",
value: statsSummary.value.last_hour.requests_per_minute,
tone: "cyan",
icon: IconActivity,
},
{
label: "今日允许率",
label: "今日通过率",
value: `${statsSummary.value.today.allow_rate}%`,
tone: statsSummary.value.today.allow_rate >= 80 ? "green" : "warning",
icon: IconPercent,
@@ -250,7 +250,7 @@ const summaryCards = computed(() => {
icon: IconClock,
},
{
label: "历史总记录",
label: "历史事件总数",
value: statsSummary.value.total_logs.toLocaleString(),
tone: "violet",
icon: IconDatabase,
@@ -0,0 +1,25 @@
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
const readSource = () => readFileSync(resolve(__dirname, "./PermissionTrendCharts.vue"), "utf8");
describe("PermissionTrendCharts", () => {
it("uses operation-oriented trend copy", () => {
const source = readSource();
expect(source).toContain("系统性能趋势");
expect(source).toContain("查看系统运行监测指标的变化趋势");
expect(source).toContain("监测事件趋势");
expect(source).toContain("响应时间趋势");
expect(source).toContain("缓存命中率");
expect(source).toContain("异常拒绝率趋势");
expect(source).toContain('name: "异常拒绝率"');
expect(source).not.toContain("数据趋势");
expect(source).not.toContain("查看权限系统各项指标的变化趋势");
expect(source).not.toContain("检查量趋势");
expect(source).not.toContain("<h4>拒绝率趋势</h4>");
expect(source).not.toContain('name: "拒绝率"');
});
});
@@ -2,8 +2,8 @@
<div class="trend-charts">
<div class="trend-toolbar">
<div class="toolbar-left">
<span class="toolbar-title">数据趋势</span>
<span class="toolbar-desc">查看权限系统各项指标的变化趋势</span>
<span class="toolbar-title">系统性能趋势</span>
<span class="toolbar-desc">查看系统运行监测指标的变化趋势</span>
</div>
<el-radio-group v-model="period" size="small" @change="loadData">
<el-radio-button value="24h">24小时</el-radio-button>
@@ -18,7 +18,7 @@
<div class="chart-icon blue">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>
</div>
<h4>检查量趋势</h4>
<h4>监测事件趋势</h4>
</div>
<v-chart :option="checksChartOption" autoresize class="chart" />
</div>
@@ -45,7 +45,7 @@
<div class="chart-icon danger">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="4.93" y1="4.93" x2="19.07" y2="19.07"/></svg>
</div>
<h4>拒绝率趋势</h4>
<h4>异常拒绝率趋势</h4>
</div>
<v-chart :option="denyRateOption" autoresize class="chart" />
</div>
@@ -181,7 +181,7 @@ const denyRateOption = computed(() => ({
yAxis: { type: "value", max: 100, name: "%", splitLine: { lineStyle: { color: "#f1f5f9" } }, axisLabel: { color: "#64748b" } },
series: [
{
name: "拒绝率",
name: "异常拒绝率",
type: "line",
smooth: true,
symbol: "circle",