完善权限监控与安全中心
This commit is contained in:
@@ -1,7 +1,13 @@
|
||||
<template>
|
||||
<div class="permission-monitoring">
|
||||
<el-tabs v-model="activeTab" @tab-change="onTabChange">
|
||||
<div class="permission-monitoring" :class="{ 'is-source-tab': activeTab === 'ip-locations' }">
|
||||
<el-tabs v-model="activeTab" class="soybean-monitoring-tabs" @tab-change="onTabChange">
|
||||
<el-tab-pane label="运行概览" name="overview">
|
||||
<template #label>
|
||||
<span class="soybean-tab-label">
|
||||
<component :is="IconCheck" />
|
||||
<span>运行概览</span>
|
||||
</span>
|
||||
</template>
|
||||
<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">
|
||||
@@ -122,22 +128,52 @@
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="性能趋势" name="trends">
|
||||
<template #label>
|
||||
<span class="soybean-tab-label">
|
||||
<component :is="IconActivity" />
|
||||
<span>性能趋势</span>
|
||||
</span>
|
||||
</template>
|
||||
<PermissionTrendCharts ref="trendChartsRef" />
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane v-if="props.isAdmin" label="安全中心" name="security">
|
||||
<template #label>
|
||||
<span class="soybean-tab-label">
|
||||
<component :is="IconShield" />
|
||||
<span>安全中心</span>
|
||||
</span>
|
||||
</template>
|
||||
<SecurityCenter v-if="props.isAdmin" ref="securityCenterRef" />
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="访问审计" name="logs">
|
||||
<template #label>
|
||||
<span class="soybean-tab-label">
|
||||
<component :is="IconDatabase" />
|
||||
<span>访问审计</span>
|
||||
</span>
|
||||
</template>
|
||||
<PermissionAccessLogs ref="accessLogsRef" :show-security-log="props.isAdmin" />
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="来源分析" name="ip-locations">
|
||||
<PermissionIpLocations ref="ipLocationsRef" />
|
||||
<template #label>
|
||||
<span class="soybean-tab-label">
|
||||
<component :is="IconPercent" />
|
||||
<span>来源分析</span>
|
||||
</span>
|
||||
</template>
|
||||
<div class="ip-locations-pane">
|
||||
<PermissionIpLocations ref="ipLocationsRef" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, h, onMounted } from "vue";
|
||||
import { ref, computed, h, nextTick, onMounted } from "vue";
|
||||
import type {
|
||||
PermissionMetricsResponse,
|
||||
AlertsResponse,
|
||||
@@ -155,6 +191,7 @@ import {
|
||||
import PermissionTrendCharts from "./PermissionTrendCharts.vue";
|
||||
import PermissionAccessLogs from "./PermissionAccessLogs.vue";
|
||||
import PermissionIpLocations from "./PermissionIpLocations.vue";
|
||||
import SecurityCenter from "./SecurityCenter.vue";
|
||||
|
||||
const IconCheck = () => h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2" }, [
|
||||
h("polyline", { points: "20 6 9 17 4 12" }),
|
||||
@@ -176,6 +213,9 @@ const IconDatabase = () => h("svg", { viewBox: "0 0 24 24", fill: "none", stroke
|
||||
h("path", { d: "M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" }),
|
||||
h("path", { d: "M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" }),
|
||||
]);
|
||||
const IconShield = () => h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2" }, [
|
||||
h("path", { d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" }),
|
||||
]);
|
||||
|
||||
const activeTab = ref("overview");
|
||||
const props = withDefaults(defineProps<{ isAdmin?: boolean }>(), {
|
||||
@@ -189,7 +229,13 @@ const statsSummary = ref<StatsSummaryResponse | null>(null);
|
||||
|
||||
const trendChartsRef = ref<InstanceType<typeof PermissionTrendCharts>>();
|
||||
const accessLogsRef = ref<InstanceType<typeof PermissionAccessLogs>>();
|
||||
const ipLocationsRef = ref<InstanceType<typeof PermissionIpLocations>>();
|
||||
type IpLocationsExpose = {
|
||||
refresh: () => void | Promise<void>;
|
||||
resize: () => void | Promise<void>;
|
||||
};
|
||||
|
||||
const ipLocationsRef = ref<IpLocationsExpose>();
|
||||
const securityCenterRef = ref<InstanceType<typeof SecurityCenter>>();
|
||||
|
||||
const formatTime = (timestamp: number): string => {
|
||||
return new Date(timestamp * 1000).toLocaleString("zh-CN");
|
||||
@@ -273,13 +319,18 @@ const loadOverviewData = async () => {
|
||||
if (summaryRes.status === "fulfilled") statsSummary.value = summaryRes.value.data;
|
||||
};
|
||||
|
||||
const onTabChange = (tab: string) => {
|
||||
const onTabChange = async (tab: string) => {
|
||||
if (tab === "overview") loadOverviewData();
|
||||
if (tab === "ip-locations") {
|
||||
await nextTick();
|
||||
await ipLocationsRef.value?.resize();
|
||||
}
|
||||
};
|
||||
|
||||
const refresh = () => {
|
||||
if (activeTab.value === "overview") loadOverviewData();
|
||||
else if (activeTab.value === "trends") trendChartsRef.value?.refresh();
|
||||
else if (activeTab.value === "security") securityCenterRef.value?.refresh();
|
||||
else if (activeTab.value === "logs") accessLogsRef.value?.refresh();
|
||||
else if (activeTab.value === "ip-locations") ipLocationsRef.value?.refresh();
|
||||
};
|
||||
@@ -294,11 +345,144 @@ defineExpose({ refresh });
|
||||
--mon-ink: #1a2332;
|
||||
--mon-muted: #64748b;
|
||||
--mon-border: #e2e8f0;
|
||||
--mon-tab-active: #6c63ff;
|
||||
--mon-tab-active-bg: #f0edff;
|
||||
--mon-radius: 16px;
|
||||
--mon-shadow: 0 1px 3px rgba(0, 0, 0, 0.04), 0 4px 12px rgba(0, 0, 0, 0.03);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.soybean-monitoring-tabs {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.soybean-monitoring-tabs :deep(.el-tabs__header) {
|
||||
height: 56px;
|
||||
margin: 0 0 16px;
|
||||
padding: 0 24px;
|
||||
background: #ffffff;
|
||||
border-top: 1px solid var(--mon-border);
|
||||
border-bottom: 1px solid var(--mon-border);
|
||||
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);
|
||||
}
|
||||
|
||||
.soybean-monitoring-tabs :deep(.el-tabs__nav-wrap) {
|
||||
height: 56px;
|
||||
}
|
||||
|
||||
.soybean-monitoring-tabs :deep(.el-tabs__nav-wrap::after),
|
||||
.soybean-monitoring-tabs :deep(.el-tabs__active-bar) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.soybean-monitoring-tabs :deep(.el-tabs__nav-scroll),
|
||||
.soybean-monitoring-tabs :deep(.el-tabs__nav) {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.soybean-monitoring-tabs :deep(.el-tabs__nav) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.soybean-monitoring-tabs :deep(.el-tabs__item) {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
height: 36px;
|
||||
margin: 0 4px;
|
||||
padding: 0 18px;
|
||||
border-radius: 10px;
|
||||
color: #303133;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
transition: color 0.2s ease, background 0.2s ease;
|
||||
}
|
||||
|
||||
:deep(.soybean-monitoring-tabs.el-tabs--top > .el-tabs__header .el-tabs__item:nth-child(2)) {
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
:deep(.soybean-monitoring-tabs.el-tabs--top > .el-tabs__header .el-tabs__item:last-child) {
|
||||
padding-right: 18px;
|
||||
}
|
||||
|
||||
.soybean-monitoring-tabs :deep(.el-tabs__item:not(.is-active)::after) {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: -5px;
|
||||
width: 1px;
|
||||
height: 16px;
|
||||
background: #dcdfe6;
|
||||
}
|
||||
|
||||
.soybean-monitoring-tabs :deep(.el-tabs__item:last-child::after),
|
||||
.soybean-monitoring-tabs :deep(.el-tabs__item.is-active + .el-tabs__item::after) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.soybean-monitoring-tabs :deep(.el-tabs__item:hover) {
|
||||
color: var(--mon-tab-active);
|
||||
}
|
||||
|
||||
.soybean-monitoring-tabs :deep(.el-tabs__item.is-active) {
|
||||
background: var(--mon-tab-active-bg);
|
||||
color: var(--mon-tab-active);
|
||||
}
|
||||
|
||||
.soybean-monitoring-tabs :deep(.el-tabs__content) {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.permission-monitoring.is-source-tab .soybean-monitoring-tabs :deep(.el-tabs__content) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.soybean-monitoring-tabs :deep(.el-tab-pane) {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.soybean-monitoring-tabs :deep(.el-tab-pane[aria-hidden="false"]) {
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.ip-locations-pane {
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.soybean-tab-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.soybean-tab-label svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.stats-summary {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
|
||||
Reference in New Issue
Block a user