update 2
This commit is contained in:
@@ -64,6 +64,23 @@
|
||||
<version>0.1.55</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>test</id>
|
||||
<activation>
|
||||
<activeByDefault>true</activeByDefault>
|
||||
</activation>
|
||||
<properties>
|
||||
<spring.profiles.active>test</spring.profiles.active>
|
||||
</properties>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>product</id>
|
||||
<properties>
|
||||
<spring.profiles.active>product</spring.profiles.active>
|
||||
</properties>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RestController
|
||||
@@ -71,14 +72,6 @@ public class ApiServerV1 {
|
||||
}
|
||||
}
|
||||
}
|
||||
@GetMapping("/status/history/{ip}")
|
||||
public List<Status> getStatus(@PathVariable String ip, @RequestParam(defaultValue = "0", required = false) int limit) {
|
||||
List<Status> statuses = statusDao.findByHostOrderByTimestampDesc(ip);
|
||||
if (limit > 0) {
|
||||
return statuses.stream().limit(limit).collect(Collectors.toList());
|
||||
}
|
||||
return statuses;
|
||||
}
|
||||
|
||||
@GetMapping("/server")
|
||||
public List<Server> getAllServers() {
|
||||
@@ -138,4 +131,178 @@ public class ApiServerV1 {
|
||||
public void deleteServer(@RequestBody Server server) {
|
||||
serverDao.delete(server);
|
||||
}
|
||||
|
||||
@GetMapping("/status/history/{ip}")
|
||||
public List<Status> getStatus(@PathVariable String ip,
|
||||
@RequestParam(defaultValue = "0000-00-00 00:00:00", required = false) String startDate,
|
||||
@RequestParam(defaultValue = "9999-99-99 99:99:99", required = false) String endDate) {
|
||||
System.out.println("getStatus: " + ip + "开始加载");
|
||||
List<Status> statuses = statusDao.findByHostOrderByTimestampDesc(ip);
|
||||
System.out.println("getStatus: " + ip + "加载完成");
|
||||
// 时间范围过滤
|
||||
LocalDateTime start = parseDateTime(startDate);
|
||||
LocalDateTime end = parseDateTime(endDate);
|
||||
|
||||
List<Status> filteredStatuses = statuses.stream()
|
||||
.filter(status -> status.getTimestamp() != null)
|
||||
.filter(status -> !status.getTimestamp().isBefore(start))
|
||||
.filter(status -> !status.getTimestamp().isAfter(end))
|
||||
.collect(Collectors.toList());
|
||||
// 数据优化 - 移除变化较小的数据点
|
||||
List<Status> optimizedStatuses = optimizeStatusData(filteredStatuses);
|
||||
System.out.println("getStatus: " + ip + "数据优化完成");
|
||||
System.out.print(optimizedStatuses.size());
|
||||
return optimizedStatuses;
|
||||
}
|
||||
@GetMapping("/status/history/all/{ips}")
|
||||
public Map<String,List<Status>> getStatusIps(@PathVariable String ips,
|
||||
@RequestParam(defaultValue = "0000-00-00 00:00:00", required = false) String startDate,
|
||||
@RequestParam(defaultValue = "9999-99-99 99:99:99", required = false) String endDate) {
|
||||
List<String> ipList = Arrays.asList(ips.split(","));
|
||||
Map<String,List<Status>> optimizedStatuses = new ConcurrentHashMap<>();
|
||||
|
||||
// 限制并发数量,避免数据库连接耗尽
|
||||
Semaphore semaphore = new Semaphore(10); // 最多同时处理10个IP
|
||||
|
||||
List<CompletableFuture<Void>> futures = ipList.stream()
|
||||
.map(ip -> CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
semaphore.acquire(); // 获取许可
|
||||
List<Status> statuses = statusDao.findByHostOrderByTimestampDesc(ip);
|
||||
|
||||
// 时间范围过滤
|
||||
LocalDateTime start = parseDateTime(startDate);
|
||||
LocalDateTime end = parseDateTime(endDate);
|
||||
|
||||
List<Status> filteredStatuses = statuses.stream()
|
||||
.filter(status -> status.getTimestamp() != null)
|
||||
.filter(status -> !status.getTimestamp().isBefore(start))
|
||||
.filter(status -> !status.getTimestamp().isAfter(end))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 数据优化 - 移除变化较小的数据点
|
||||
List<Status> optimizedStatuse = optimizeStatusData(filteredStatuses);
|
||||
optimizedStatuses.put(ip, optimizedStatuse);
|
||||
} catch (Exception e) {
|
||||
log.error("Error processing status for IP: {}", ip, e);
|
||||
optimizedStatuses.put(ip, new ArrayList<>());
|
||||
} finally {
|
||||
semaphore.release(); // 释放许可
|
||||
}
|
||||
}))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 等待所有任务完成
|
||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
||||
|
||||
return optimizedStatuses;
|
||||
}
|
||||
|
||||
private LocalDateTime parseDateTime(String dateTimeStr) {
|
||||
try {
|
||||
// 处理默认值情况
|
||||
if ("0000-00-00 00:00:00".equals(dateTimeStr) || "9999-99-99 99:99:99".equals(dateTimeStr)) {
|
||||
return "0000-00-00 00:00:00".equals(dateTimeStr) ?
|
||||
LocalDateTime.MIN : LocalDateTime.MAX;
|
||||
}
|
||||
return LocalDateTime.parse(dateTimeStr.replace(" ", "T"));
|
||||
} catch (Exception e) {
|
||||
return LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
|
||||
private List<Status> optimizeStatusData(List<Status> statuses) {
|
||||
if (statuses.size() <= 2) {
|
||||
return statuses; // 数据点太少无需优化
|
||||
}
|
||||
|
||||
List<Status> result = new ArrayList<>();
|
||||
result.add(statuses.get(0)); // 始终保留第一个点
|
||||
|
||||
// 计算CPU和内存使用率变化的阈值(基于整体数据计算)
|
||||
double cpuThreshold = calculateThreshold(statuses, Status::getCpuUsagePercent);
|
||||
double memoryThreshold = calculateThreshold(statuses, Status::getMemoryUsagePercent);
|
||||
|
||||
// 设置最小阈值,避免过度优化
|
||||
cpuThreshold = Math.max(cpuThreshold, 0.5); // 最小0.5%
|
||||
memoryThreshold = Math.max(memoryThreshold, 0.5); // 最小0.5%
|
||||
|
||||
Status previousStatus = statuses.get(0);
|
||||
|
||||
for (int i = 1; i < statuses.size() - 1; i++) {
|
||||
Status current = statuses.get(i);
|
||||
|
||||
// 检查CPU或内存使用率是否有显著变化
|
||||
boolean significantChange =
|
||||
hasSignificantChange(previousStatus, current, cpuThreshold, memoryThreshold);
|
||||
|
||||
if (significantChange) {
|
||||
result.add(current);
|
||||
previousStatus = current;
|
||||
}
|
||||
}
|
||||
|
||||
// 始终保留最后一个点
|
||||
if (!statuses.isEmpty() && !result.contains(statuses.get(statuses.size() - 1))) {
|
||||
result.add(statuses.get(statuses.size() - 1));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean hasSignificantChange(Status prev, Status curr,
|
||||
double cpuThreshold, double memoryThreshold) {
|
||||
Double prevCpu = prev.getCpuUsagePercent();
|
||||
Double currCpu = curr.getCpuUsagePercent();
|
||||
Double prevMem = prev.getMemoryUsagePercent();
|
||||
Double currMem = curr.getMemoryUsagePercent();
|
||||
|
||||
// 如果任一值为空,则认为有变化
|
||||
if (prevCpu == null || currCpu == null || prevMem == null || currMem == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查变化是否超过阈值
|
||||
return Math.abs(currCpu - prevCpu) >= cpuThreshold ||
|
||||
Math.abs(currMem - prevMem) >= memoryThreshold;
|
||||
}
|
||||
|
||||
// 在Status类中添加辅助方法获取使用率数值
|
||||
private static class StatusExtensions {
|
||||
public static Double getCpuUsagePercent(Status status) {
|
||||
return status.getCpuInfo() != null ? status.getCpuInfo().getUsagePercent() : null;
|
||||
}
|
||||
|
||||
public static Double getMemoryUsagePercent(Status status) {
|
||||
return status.getMemoryInfo() != null ? status.getMemoryInfo().getUsagePercent() : null;
|
||||
}
|
||||
}
|
||||
|
||||
private double calculateThreshold(List<Status> statuses, java.util.function.Function<Status, Double> getter) {
|
||||
// 收集所有有效数值
|
||||
List<Double> values = statuses.stream()
|
||||
.map(getter)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (values.size() < 2) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// 计算相邻数值间的差值
|
||||
List<Double> differences = new ArrayList<>();
|
||||
for (int i = 1; i < values.size(); i++) {
|
||||
differences.add(Math.abs(values.get(i) - values.get(i-1)));
|
||||
}
|
||||
|
||||
// 计算平均差值作为阈值基础
|
||||
double averageDifference = differences.stream()
|
||||
.mapToDouble(Double::doubleValue)
|
||||
.average()
|
||||
.orElse(0.0);
|
||||
|
||||
// 返回平均差值的一半作为阈值,这样可以过滤掉较小的变化
|
||||
return averageDifference / 2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -147,6 +147,20 @@ public class Status {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public Double getCpuUsagePercent() {
|
||||
if (cpuInfo == null) {
|
||||
return 0.0;
|
||||
}
|
||||
return cpuInfo.getUsagePercent();
|
||||
}
|
||||
|
||||
public Double getMemoryUsagePercent() {
|
||||
if (memoryInfo == null) {
|
||||
return 0.0;
|
||||
}
|
||||
return memoryInfo.getUsagePercent();
|
||||
}
|
||||
|
||||
// 内部类定义
|
||||
public static class CpuInfo {
|
||||
private String modelName;
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
spring.application.name=reisaAdminSpring
|
||||
spring.data.mongodb.uri=mongodb://reisaAdmin:nbAC8hi8xdJeBDDT@127.0.0.1:27017/reisaadmin
|
||||
server.port=48102
|
||||
|
||||
spring.data.redis.host=127.0.0.1
|
||||
spring.data.redis.port: 6379
|
||||
@@ -497,13 +497,22 @@
|
||||
v-model:value="selectedMonitorServers"
|
||||
mode="multiple"
|
||||
placeholder="请选择要监控的服务器"
|
||||
style="width: 800px; margin-right: 16px;"
|
||||
style="width: 600px; margin-right: 16px;"
|
||||
@change="loadMonitorData"
|
||||
>
|
||||
<a-select-option v-for="server in servers" :key="server.id" :value="server.id">
|
||||
{{ server.name }} ({{ server.ipAddress }})
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
|
||||
<a-range-picker
|
||||
v-model:value="monitorTimeRange"
|
||||
show-time
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
:placeholder="['开始时间', '结束时间']"
|
||||
style="width: 300px; margin-right: 16px;"
|
||||
@change="handleTimeRangeChange"
|
||||
/>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="loadMonitorData"
|
||||
@@ -511,12 +520,21 @@
|
||||
>
|
||||
刷新数据
|
||||
</a-button>
|
||||
|
||||
<a-button
|
||||
@click="showMonitor = false"
|
||||
@click="closeMonitor"
|
||||
style="margin-left: 8px;"
|
||||
>
|
||||
关闭监控
|
||||
</a-button>
|
||||
<div class="chart-controls" v-if="showMonitor">
|
||||
<a-switch
|
||||
v-model:checked="showChartPoints"
|
||||
checked-children="显示数据点"
|
||||
un-checked-children="隐藏数据点"
|
||||
@change="drawCharts"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -532,11 +550,11 @@
|
||||
<div class="process-list-header">
|
||||
<h3>进程排行</h3>
|
||||
<div class="process-sort-controls">
|
||||
<a-select v-model:value="processSortField" @change="sortProcesses">
|
||||
<a-select v-model:value="processSortField" @change="sortProcesses" style="width: 120px;">
|
||||
<a-select-option value="cpuPercent">按CPU排序</a-select-option>
|
||||
<a-select-option value="memoryPercent">按内存排序</a-select-option>
|
||||
</a-select>
|
||||
<a-select v-model:value="processSortOrder" @change="sortProcesses">
|
||||
<a-select v-model:value="processSortOrder" @change="sortProcesses" style="width: 80px;">
|
||||
<a-select-option value="desc">降序</a-select-option>
|
||||
<a-select-option value="asc">升序</a-select-option>
|
||||
</a-select>
|
||||
@@ -572,7 +590,7 @@
|
||||
|
||||
<!-- 监控大屏切换按钮 -->
|
||||
<div v-else style="margin-top: 16px; text-align: center;">
|
||||
<a-button type="primary" @click="showMonitor = true">
|
||||
<a-button type="primary" @click="openMonitor">
|
||||
<template #icon>
|
||||
<BarChartOutlined />
|
||||
</template>
|
||||
@@ -659,6 +677,7 @@ import {ref, reactive, onMounted, computed, onUnmounted, nextTick} from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import axios from 'axios';
|
||||
import { PlusOutlined, CopyOutlined } from '@ant-design/icons-vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
// 状态管理
|
||||
const servers = ref([]);
|
||||
@@ -673,6 +692,7 @@ const sortField = ref('name');
|
||||
const sortOrder = ref('asc');
|
||||
const localSearchName = ref('');
|
||||
const showMonitor = ref(true);
|
||||
const showChartPoints = ref(false);
|
||||
|
||||
const deviceInfoModalVisible = ref(false);
|
||||
document.title = 'ReisaAdmin';
|
||||
@@ -851,6 +871,7 @@ const columns = [
|
||||
|
||||
// 初始化加载数据
|
||||
onMounted(() => {
|
||||
openMonitor()
|
||||
fetchServers();
|
||||
});
|
||||
|
||||
@@ -1156,11 +1177,14 @@ const closeDeviceInfoModal = () => {
|
||||
deviceInfoModalVisible.value = false;
|
||||
stopDeviceInfoRefreshTimer();
|
||||
};
|
||||
|
||||
// 大屏监控相关状态
|
||||
const monitorVisible = ref(false);
|
||||
const selectedMonitorServers = ref([]); // 多选的服务器
|
||||
const monitorHistoryData = ref({}); // 存储各服务器的历史数据
|
||||
const monitorLoading = ref(false);
|
||||
const monitorRefreshTimer = ref(null); // 监控数据自动刷新定时器
|
||||
const monitorTimeRange = ref([]); // 监控时间范围
|
||||
|
||||
// 图表实例引用
|
||||
const cpuChartRef = ref(null);
|
||||
@@ -1196,15 +1220,45 @@ const sortProcesses = () => {
|
||||
|
||||
// 打开监控大屏
|
||||
const openMonitor = () => {
|
||||
monitorVisible.value = true;
|
||||
showMonitor.value = true;
|
||||
selectedMonitorServers.value = [];
|
||||
monitorHistoryData.value = {};
|
||||
// 设置默认时间范围为最近1小时
|
||||
const endTime = dayjs();
|
||||
const startTime = endTime.subtract(1, 'hour');
|
||||
monitorTimeRange.value = [startTime, endTime];
|
||||
startMonitorRefreshTimer();
|
||||
};
|
||||
|
||||
// 获取服务器历史数据
|
||||
// 处理时间范围变化
|
||||
const handleTimeRangeChange = () => {
|
||||
if (selectedMonitorServers.value.length > 0) {
|
||||
loadMonitorData();
|
||||
}
|
||||
};
|
||||
const formatTime = (timestamp) => {
|
||||
if (!timestamp) return 'N/A';
|
||||
const date = new Date(timestamp);
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
return `${month}-${day} ${hours}:${minutes}`;
|
||||
};
|
||||
|
||||
// 获取服务器历史数据(修改后的版本,支持时间范围)
|
||||
const fetchServerHistory = async (serverIp) => {
|
||||
try {
|
||||
const response = await axios.get(`/api/v1/status/history/${serverIp}?limit=50`);
|
||||
const params = {};
|
||||
|
||||
// 如果设置了时间范围,则添加时间参数
|
||||
if (monitorTimeRange.value && monitorTimeRange.value.length === 2) {
|
||||
const [startTime, endTime] = monitorTimeRange.value;
|
||||
params.startDate = startTime.format('YYYY-MM-DD HH:mm:ss');
|
||||
params.endDate = endTime.format('YYYY-MM-DD HH:mm:ss');
|
||||
}
|
||||
|
||||
const response = await axios.get(`/api/v1/status/history/${serverIp}`, { params });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
message.error(`获取服务器 ${serverIp} 历史数据失败: ${error.message}`);
|
||||
@@ -1212,9 +1266,8 @@ const fetchServerHistory = async (serverIp) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 加载选中服务器的历史数据
|
||||
// 修改 loadMonitorData 函数以使用新的多IP API
|
||||
const loadMonitorData = async () => {
|
||||
allProcesses.value = [];
|
||||
if (selectedMonitorServers.value.length === 0) {
|
||||
monitorHistoryData.value = {};
|
||||
allProcesses.value = [];
|
||||
@@ -1224,29 +1277,60 @@ const loadMonitorData = async () => {
|
||||
|
||||
monitorLoading.value = true;
|
||||
try {
|
||||
const promises = selectedMonitorServers.value.map(serverId => {
|
||||
const server = servers.value.find(s => s.id === serverId);
|
||||
return server ? fetchServerHistory(server.ipAddress) : Promise.resolve([]);
|
||||
});
|
||||
// 限制同时监控的服务器数量
|
||||
const limitedServers = selectedMonitorServers.value.slice(0, 99999);
|
||||
|
||||
const results = await Promise.all(promises);
|
||||
// 获取选中服务器的IP地址
|
||||
const serverIps = limitedServers
|
||||
.map(serverId => {
|
||||
const server = servers.value.find(s => s.id === serverId);
|
||||
return server ? server.ipAddress : null;
|
||||
})
|
||||
.filter(Boolean); // 过滤掉null值
|
||||
|
||||
if (serverIps.length === 0) {
|
||||
monitorHistoryData.value = {};
|
||||
allProcesses.value = [];
|
||||
drawCharts();
|
||||
return;
|
||||
}
|
||||
|
||||
// 构造请求参数
|
||||
const params = {};
|
||||
if (monitorTimeRange.value && monitorTimeRange.value.length === 2) {
|
||||
const [startTime, endTime] = monitorTimeRange.value;
|
||||
params.startDate = startTime.format('YYYY-MM-DD HH:mm:ss');
|
||||
params.endDate = endTime.format('YYYY-MM-DD HH:mm:ss');
|
||||
}
|
||||
|
||||
// 使用新的多IP API接口
|
||||
const response = await axios.get(`/api/v1/status/history/all/${serverIps.join(',')}`, { params });
|
||||
const results = response.data;
|
||||
|
||||
// 整理数据
|
||||
const newData = {};
|
||||
const processes = [];
|
||||
selectedMonitorServers.value.forEach((serverId, index) => {
|
||||
|
||||
limitedServers.forEach((serverId, index) => {
|
||||
const server = servers.value.find(s => s.id === serverId);
|
||||
if (server) {
|
||||
if (server && results[server.ipAddress]) {
|
||||
newData[serverId] = {
|
||||
serverName: server.name,
|
||||
serverIp: server.ipAddress,
|
||||
history: results[index]
|
||||
history: results[server.ipAddress]
|
||||
};
|
||||
|
||||
// 收集所有进程数据
|
||||
results[index].forEach(historyItem => {
|
||||
if (historyItem.processes) {
|
||||
historyItem.processes.forEach(process => {
|
||||
// 限制每个服务器的进程数据数量
|
||||
let serverProcesses = [];
|
||||
if (results[server.ipAddress].length > 0) {
|
||||
const latestData = results[server.ipAddress][0];
|
||||
if (latestData.processes) {
|
||||
// 只取前50个进程
|
||||
serverProcesses = latestData.processes.slice(0, 50);
|
||||
}
|
||||
}
|
||||
|
||||
serverProcesses.forEach(process => {
|
||||
processes.push({
|
||||
...process,
|
||||
serverName: server.name,
|
||||
@@ -1255,11 +1339,9 @@ const loadMonitorData = async () => {
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
monitorHistoryData.value = newData;
|
||||
allProcesses.value = processes;
|
||||
allProcesses.value = processes.slice(0, 100); // 限制总进程数
|
||||
drawCharts();
|
||||
} catch (error) {
|
||||
message.error('加载监控数据失败: ' + error.message);
|
||||
@@ -1268,6 +1350,7 @@ const loadMonitorData = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 绘制图表
|
||||
const drawCharts = () => {
|
||||
// 确保DOM已更新
|
||||
@@ -1276,11 +1359,29 @@ const drawCharts = () => {
|
||||
drawMemoryChart();
|
||||
});
|
||||
};
|
||||
|
||||
import { Chart, registerables } from 'chart.js';
|
||||
import 'chartjs-adapter-date-fns';
|
||||
// 注册Chart.js的所有组件
|
||||
Chart.register(...registerables);
|
||||
// 绘制CPU使用率图表
|
||||
|
||||
// 数据采样函数 - 当数据点过多时减少数据点数量
|
||||
const sampleData = (data, maxPoints = 50) => {
|
||||
if (data.length <= maxPoints) {
|
||||
return data;
|
||||
}
|
||||
|
||||
const sampled = [];
|
||||
const step = Math.ceil(data.length / maxPoints);
|
||||
|
||||
for (let i = 0; i < data.length; i += step) {
|
||||
sampled.push(data[i]);
|
||||
}
|
||||
|
||||
return sampled;
|
||||
};
|
||||
|
||||
// 修改 drawCPUChart 函数
|
||||
const drawCPUChart = () => {
|
||||
if (!cpuChartRef.value) return;
|
||||
|
||||
@@ -1295,18 +1396,25 @@ const drawCPUChart = () => {
|
||||
const datasets = [];
|
||||
Object.keys(monitorHistoryData.value).forEach(serverId => {
|
||||
const serverData = monitorHistoryData.value[serverId];
|
||||
const data = serverData.history.map(item => ({
|
||||
let data = serverData.history.map(item => ({
|
||||
x: new Date(item.timestamp),
|
||||
y: item.cpuInfo?.usagePercent || 0
|
||||
})).reverse(); // 图表从左到右时间顺序
|
||||
|
||||
// 当数据量过大时进行采样处理
|
||||
if (data.length > 50) {
|
||||
data = sampleData(data, 50);
|
||||
}
|
||||
|
||||
datasets.push({
|
||||
label: serverData.serverName,
|
||||
data: data,
|
||||
borderColor: getColorByIndex(datasets.length),
|
||||
backgroundColor: getColorByIndex(datasets.length, 0.1),
|
||||
tension: 0.1,
|
||||
fill: false
|
||||
tension: 0.4, // 增加曲线平滑度
|
||||
fill: true,
|
||||
pointRadius: showChartPoints.value ? 3 : 0, // 根据开关控制点的显示
|
||||
pointHoverRadius: showChartPoints.value ? 5 : 0 // 悬停时点的大小
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1318,13 +1426,14 @@ const drawCPUChart = () => {
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
animation: false, // 禁用动画以提高性能
|
||||
scales: {
|
||||
x: {
|
||||
type: 'time',
|
||||
time: {
|
||||
unit: 'minute',
|
||||
displayFormats: {
|
||||
minute: 'HH:mm'
|
||||
minute: 'MM-dd HH:mm'
|
||||
}
|
||||
},
|
||||
title: {
|
||||
@@ -1355,7 +1464,7 @@ const drawCPUChart = () => {
|
||||
});
|
||||
};
|
||||
|
||||
// 绘制内存使用率图表
|
||||
// 同样需要修改 drawMemoryChart 函数中的点显示逻辑
|
||||
const drawMemoryChart = () => {
|
||||
if (!memoryChartRef.value) return;
|
||||
|
||||
@@ -1370,18 +1479,25 @@ const drawMemoryChart = () => {
|
||||
const datasets = [];
|
||||
Object.keys(monitorHistoryData.value).forEach(serverId => {
|
||||
const serverData = monitorHistoryData.value[serverId];
|
||||
const data = serverData.history.map(item => ({
|
||||
let data = serverData.history.map(item => ({
|
||||
x: new Date(item.timestamp),
|
||||
y: item.memoryInfo?.usagePercent || 0
|
||||
})).reverse();
|
||||
|
||||
// 当数据量过大时进行采样处理
|
||||
if (data.length > 50) {
|
||||
data = sampleData(data, 50);
|
||||
}
|
||||
|
||||
datasets.push({
|
||||
label: serverData.serverName,
|
||||
data: data,
|
||||
borderColor: getColorByIndex(datasets.length),
|
||||
backgroundColor: getColorByIndex(datasets.length, 0.1),
|
||||
tension: 0.1,
|
||||
fill: false
|
||||
tension: 0.4, // 增加曲线平滑度
|
||||
fill: true,
|
||||
pointRadius: showChartPoints.value ? 3 : 0, // 根据开关控制点的显示
|
||||
pointHoverRadius: showChartPoints.value ? 5 : 0 // 悬停时点的大小
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1393,13 +1509,14 @@ const drawMemoryChart = () => {
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
animation: false, // 禁用动画以提高性能
|
||||
scales: {
|
||||
x: {
|
||||
type: 'time',
|
||||
time: {
|
||||
unit: 'minute',
|
||||
displayFormats: {
|
||||
minute: 'HH:mm'
|
||||
minute: 'MM-dd HH:mm'
|
||||
}
|
||||
},
|
||||
title: {
|
||||
@@ -1445,7 +1562,7 @@ const getColorByIndex = (index, alpha = 1) => {
|
||||
|
||||
// 关闭监控大屏
|
||||
const closeMonitor = () => {
|
||||
monitorVisible.value = false;
|
||||
showMonitor.value = false;
|
||||
// 销毁图表实例
|
||||
if (cpuChartInstance) {
|
||||
cpuChartInstance.destroy();
|
||||
@@ -1455,12 +1572,35 @@ const closeMonitor = () => {
|
||||
memoryChartInstance.destroy();
|
||||
memoryChartInstance = null;
|
||||
}
|
||||
stopMonitorRefreshTimer();
|
||||
};
|
||||
|
||||
// 添加启动监控自动刷新函数
|
||||
const startMonitorRefreshTimer = () => {
|
||||
// 先清除已存在的定时器
|
||||
if (monitorRefreshTimer.value) {
|
||||
clearInterval(monitorRefreshTimer.value);
|
||||
}
|
||||
|
||||
// 启动新的定时器,每30秒刷新一次监控数据
|
||||
monitorRefreshTimer.value = setInterval(() => {
|
||||
if (showMonitor.value && selectedMonitorServers.value.length > 0) {
|
||||
loadMonitorData();
|
||||
}
|
||||
}, 30000); // 30秒刷新一次
|
||||
};
|
||||
|
||||
// 添加停止监控自动刷新函数
|
||||
const stopMonitorRefreshTimer = () => {
|
||||
if (monitorRefreshTimer.value) {
|
||||
clearInterval(monitorRefreshTimer.value);
|
||||
monitorRefreshTimer.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
// 在组件卸载时清理定时器
|
||||
onUnmounted(() => {
|
||||
closeMonitor();
|
||||
|
||||
stopDeviceInfoRefreshTimer();
|
||||
});
|
||||
</script>
|
||||
@@ -1531,7 +1671,10 @@ onUnmounted(() => {
|
||||
color: #1d2129;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.chart-controls {
|
||||
margin-bottom: 16px;
|
||||
text-align: right;
|
||||
}
|
||||
.add-button {
|
||||
border-radius: 6px;
|
||||
height: 40px;
|
||||
|
||||
Reference in New Issue
Block a user