update 2
This commit is contained in:
@@ -64,6 +64,23 @@
|
|||||||
<version>0.1.55</version>
|
<version>0.1.55</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</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>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import java.util.*;
|
|||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import java.util.concurrent.Semaphore;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@RestController
|
@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")
|
@GetMapping("/server")
|
||||||
public List<Server> getAllServers() {
|
public List<Server> getAllServers() {
|
||||||
@@ -138,4 +131,178 @@ public class ApiServerV1 {
|
|||||||
public void deleteServer(@RequestBody Server server) {
|
public void deleteServer(@RequestBody Server server) {
|
||||||
serverDao.delete(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;
|
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 {
|
public static class CpuInfo {
|
||||||
private String modelName;
|
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"
|
v-model:value="selectedMonitorServers"
|
||||||
mode="multiple"
|
mode="multiple"
|
||||||
placeholder="请选择要监控的服务器"
|
placeholder="请选择要监控的服务器"
|
||||||
style="width: 800px; margin-right: 16px;"
|
style="width: 600px; margin-right: 16px;"
|
||||||
@change="loadMonitorData"
|
@change="loadMonitorData"
|
||||||
>
|
>
|
||||||
<a-select-option v-for="server in servers" :key="server.id" :value="server.id">
|
<a-select-option v-for="server in servers" :key="server.id" :value="server.id">
|
||||||
{{ server.name }} ({{ server.ipAddress }})
|
{{ server.name }} ({{ server.ipAddress }})
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</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
|
<a-button
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="loadMonitorData"
|
@click="loadMonitorData"
|
||||||
@@ -511,12 +520,21 @@
|
|||||||
>
|
>
|
||||||
刷新数据
|
刷新数据
|
||||||
</a-button>
|
</a-button>
|
||||||
|
|
||||||
<a-button
|
<a-button
|
||||||
@click="showMonitor = false"
|
@click="closeMonitor"
|
||||||
style="margin-left: 8px;"
|
style="margin-left: 8px;"
|
||||||
>
|
>
|
||||||
关闭监控
|
关闭监控
|
||||||
</a-button>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -532,11 +550,11 @@
|
|||||||
<div class="process-list-header">
|
<div class="process-list-header">
|
||||||
<h3>进程排行</h3>
|
<h3>进程排行</h3>
|
||||||
<div class="process-sort-controls">
|
<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="cpuPercent">按CPU排序</a-select-option>
|
||||||
<a-select-option value="memoryPercent">按内存排序</a-select-option>
|
<a-select-option value="memoryPercent">按内存排序</a-select-option>
|
||||||
</a-select>
|
</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="desc">降序</a-select-option>
|
||||||
<a-select-option value="asc">升序</a-select-option>
|
<a-select-option value="asc">升序</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -572,7 +590,7 @@
|
|||||||
|
|
||||||
<!-- 监控大屏切换按钮 -->
|
<!-- 监控大屏切换按钮 -->
|
||||||
<div v-else style="margin-top: 16px; text-align: center;">
|
<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>
|
<template #icon>
|
||||||
<BarChartOutlined />
|
<BarChartOutlined />
|
||||||
</template>
|
</template>
|
||||||
@@ -659,6 +677,7 @@ import {ref, reactive, onMounted, computed, onUnmounted, nextTick} from 'vue';
|
|||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { PlusOutlined, CopyOutlined } from '@ant-design/icons-vue';
|
import { PlusOutlined, CopyOutlined } from '@ant-design/icons-vue';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
// 状态管理
|
// 状态管理
|
||||||
const servers = ref([]);
|
const servers = ref([]);
|
||||||
@@ -673,6 +692,7 @@ const sortField = ref('name');
|
|||||||
const sortOrder = ref('asc');
|
const sortOrder = ref('asc');
|
||||||
const localSearchName = ref('');
|
const localSearchName = ref('');
|
||||||
const showMonitor = ref(true);
|
const showMonitor = ref(true);
|
||||||
|
const showChartPoints = ref(false);
|
||||||
|
|
||||||
const deviceInfoModalVisible = ref(false);
|
const deviceInfoModalVisible = ref(false);
|
||||||
document.title = 'ReisaAdmin';
|
document.title = 'ReisaAdmin';
|
||||||
@@ -851,6 +871,7 @@ const columns = [
|
|||||||
|
|
||||||
// 初始化加载数据
|
// 初始化加载数据
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
openMonitor()
|
||||||
fetchServers();
|
fetchServers();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1156,11 +1177,14 @@ const closeDeviceInfoModal = () => {
|
|||||||
deviceInfoModalVisible.value = false;
|
deviceInfoModalVisible.value = false;
|
||||||
stopDeviceInfoRefreshTimer();
|
stopDeviceInfoRefreshTimer();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 大屏监控相关状态
|
// 大屏监控相关状态
|
||||||
const monitorVisible = ref(false);
|
const monitorVisible = ref(false);
|
||||||
const selectedMonitorServers = ref([]); // 多选的服务器
|
const selectedMonitorServers = ref([]); // 多选的服务器
|
||||||
const monitorHistoryData = ref({}); // 存储各服务器的历史数据
|
const monitorHistoryData = ref({}); // 存储各服务器的历史数据
|
||||||
const monitorLoading = ref(false);
|
const monitorLoading = ref(false);
|
||||||
|
const monitorRefreshTimer = ref(null); // 监控数据自动刷新定时器
|
||||||
|
const monitorTimeRange = ref([]); // 监控时间范围
|
||||||
|
|
||||||
// 图表实例引用
|
// 图表实例引用
|
||||||
const cpuChartRef = ref(null);
|
const cpuChartRef = ref(null);
|
||||||
@@ -1196,15 +1220,45 @@ const sortProcesses = () => {
|
|||||||
|
|
||||||
// 打开监控大屏
|
// 打开监控大屏
|
||||||
const openMonitor = () => {
|
const openMonitor = () => {
|
||||||
monitorVisible.value = true;
|
showMonitor.value = true;
|
||||||
selectedMonitorServers.value = [];
|
selectedMonitorServers.value = [];
|
||||||
monitorHistoryData.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) => {
|
const fetchServerHistory = async (serverIp) => {
|
||||||
try {
|
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;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error(`获取服务器 ${serverIp} 历史数据失败: ${error.message}`);
|
message.error(`获取服务器 ${serverIp} 历史数据失败: ${error.message}`);
|
||||||
@@ -1212,9 +1266,8 @@ const fetchServerHistory = async (serverIp) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 加载选中服务器的历史数据
|
// 修改 loadMonitorData 函数以使用新的多IP API
|
||||||
const loadMonitorData = async () => {
|
const loadMonitorData = async () => {
|
||||||
allProcesses.value = [];
|
|
||||||
if (selectedMonitorServers.value.length === 0) {
|
if (selectedMonitorServers.value.length === 0) {
|
||||||
monitorHistoryData.value = {};
|
monitorHistoryData.value = {};
|
||||||
allProcesses.value = [];
|
allProcesses.value = [];
|
||||||
@@ -1224,29 +1277,60 @@ const loadMonitorData = async () => {
|
|||||||
|
|
||||||
monitorLoading.value = true;
|
monitorLoading.value = true;
|
||||||
try {
|
try {
|
||||||
const promises = selectedMonitorServers.value.map(serverId => {
|
// 限制同时监控的服务器数量
|
||||||
const server = servers.value.find(s => s.id === serverId);
|
const limitedServers = selectedMonitorServers.value.slice(0, 99999);
|
||||||
return server ? fetchServerHistory(server.ipAddress) : Promise.resolve([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
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 newData = {};
|
||||||
const processes = [];
|
const processes = [];
|
||||||
selectedMonitorServers.value.forEach((serverId, index) => {
|
|
||||||
|
limitedServers.forEach((serverId, index) => {
|
||||||
const server = servers.value.find(s => s.id === serverId);
|
const server = servers.value.find(s => s.id === serverId);
|
||||||
if (server) {
|
if (server && results[server.ipAddress]) {
|
||||||
newData[serverId] = {
|
newData[serverId] = {
|
||||||
serverName: server.name,
|
serverName: server.name,
|
||||||
serverIp: server.ipAddress,
|
serverIp: server.ipAddress,
|
||||||
history: results[index]
|
history: results[server.ipAddress]
|
||||||
};
|
};
|
||||||
|
|
||||||
// 收集所有进程数据
|
// 限制每个服务器的进程数据数量
|
||||||
results[index].forEach(historyItem => {
|
let serverProcesses = [];
|
||||||
if (historyItem.processes) {
|
if (results[server.ipAddress].length > 0) {
|
||||||
historyItem.processes.forEach(process => {
|
const latestData = results[server.ipAddress][0];
|
||||||
|
if (latestData.processes) {
|
||||||
|
// 只取前50个进程
|
||||||
|
serverProcesses = latestData.processes.slice(0, 50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
serverProcesses.forEach(process => {
|
||||||
processes.push({
|
processes.push({
|
||||||
...process,
|
...process,
|
||||||
serverName: server.name,
|
serverName: server.name,
|
||||||
@@ -1255,11 +1339,9 @@ const loadMonitorData = async () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
monitorHistoryData.value = newData;
|
monitorHistoryData.value = newData;
|
||||||
allProcesses.value = processes;
|
allProcesses.value = processes.slice(0, 100); // 限制总进程数
|
||||||
drawCharts();
|
drawCharts();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('加载监控数据失败: ' + error.message);
|
message.error('加载监控数据失败: ' + error.message);
|
||||||
@@ -1268,6 +1350,7 @@ const loadMonitorData = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// 绘制图表
|
// 绘制图表
|
||||||
const drawCharts = () => {
|
const drawCharts = () => {
|
||||||
// 确保DOM已更新
|
// 确保DOM已更新
|
||||||
@@ -1276,11 +1359,29 @@ const drawCharts = () => {
|
|||||||
drawMemoryChart();
|
drawMemoryChart();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
import { Chart, registerables } from 'chart.js';
|
import { Chart, registerables } from 'chart.js';
|
||||||
import 'chartjs-adapter-date-fns';
|
import 'chartjs-adapter-date-fns';
|
||||||
// 注册Chart.js的所有组件
|
// 注册Chart.js的所有组件
|
||||||
Chart.register(...registerables);
|
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 = () => {
|
const drawCPUChart = () => {
|
||||||
if (!cpuChartRef.value) return;
|
if (!cpuChartRef.value) return;
|
||||||
|
|
||||||
@@ -1295,18 +1396,25 @@ const drawCPUChart = () => {
|
|||||||
const datasets = [];
|
const datasets = [];
|
||||||
Object.keys(monitorHistoryData.value).forEach(serverId => {
|
Object.keys(monitorHistoryData.value).forEach(serverId => {
|
||||||
const serverData = monitorHistoryData.value[serverId];
|
const serverData = monitorHistoryData.value[serverId];
|
||||||
const data = serverData.history.map(item => ({
|
let data = serverData.history.map(item => ({
|
||||||
x: new Date(item.timestamp),
|
x: new Date(item.timestamp),
|
||||||
y: item.cpuInfo?.usagePercent || 0
|
y: item.cpuInfo?.usagePercent || 0
|
||||||
})).reverse(); // 图表从左到右时间顺序
|
})).reverse(); // 图表从左到右时间顺序
|
||||||
|
|
||||||
|
// 当数据量过大时进行采样处理
|
||||||
|
if (data.length > 50) {
|
||||||
|
data = sampleData(data, 50);
|
||||||
|
}
|
||||||
|
|
||||||
datasets.push({
|
datasets.push({
|
||||||
label: serverData.serverName,
|
label: serverData.serverName,
|
||||||
data: data,
|
data: data,
|
||||||
borderColor: getColorByIndex(datasets.length),
|
borderColor: getColorByIndex(datasets.length),
|
||||||
backgroundColor: getColorByIndex(datasets.length, 0.1),
|
backgroundColor: getColorByIndex(datasets.length, 0.1),
|
||||||
tension: 0.1,
|
tension: 0.4, // 增加曲线平滑度
|
||||||
fill: false
|
fill: true,
|
||||||
|
pointRadius: showChartPoints.value ? 3 : 0, // 根据开关控制点的显示
|
||||||
|
pointHoverRadius: showChartPoints.value ? 5 : 0 // 悬停时点的大小
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1318,13 +1426,14 @@ const drawCPUChart = () => {
|
|||||||
options: {
|
options: {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
maintainAspectRatio: false,
|
maintainAspectRatio: false,
|
||||||
|
animation: false, // 禁用动画以提高性能
|
||||||
scales: {
|
scales: {
|
||||||
x: {
|
x: {
|
||||||
type: 'time',
|
type: 'time',
|
||||||
time: {
|
time: {
|
||||||
unit: 'minute',
|
unit: 'minute',
|
||||||
displayFormats: {
|
displayFormats: {
|
||||||
minute: 'HH:mm'
|
minute: 'MM-dd HH:mm'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
@@ -1355,7 +1464,7 @@ const drawCPUChart = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 绘制内存使用率图表
|
// 同样需要修改 drawMemoryChart 函数中的点显示逻辑
|
||||||
const drawMemoryChart = () => {
|
const drawMemoryChart = () => {
|
||||||
if (!memoryChartRef.value) return;
|
if (!memoryChartRef.value) return;
|
||||||
|
|
||||||
@@ -1370,18 +1479,25 @@ const drawMemoryChart = () => {
|
|||||||
const datasets = [];
|
const datasets = [];
|
||||||
Object.keys(monitorHistoryData.value).forEach(serverId => {
|
Object.keys(monitorHistoryData.value).forEach(serverId => {
|
||||||
const serverData = monitorHistoryData.value[serverId];
|
const serverData = monitorHistoryData.value[serverId];
|
||||||
const data = serverData.history.map(item => ({
|
let data = serverData.history.map(item => ({
|
||||||
x: new Date(item.timestamp),
|
x: new Date(item.timestamp),
|
||||||
y: item.memoryInfo?.usagePercent || 0
|
y: item.memoryInfo?.usagePercent || 0
|
||||||
})).reverse();
|
})).reverse();
|
||||||
|
|
||||||
|
// 当数据量过大时进行采样处理
|
||||||
|
if (data.length > 50) {
|
||||||
|
data = sampleData(data, 50);
|
||||||
|
}
|
||||||
|
|
||||||
datasets.push({
|
datasets.push({
|
||||||
label: serverData.serverName,
|
label: serverData.serverName,
|
||||||
data: data,
|
data: data,
|
||||||
borderColor: getColorByIndex(datasets.length),
|
borderColor: getColorByIndex(datasets.length),
|
||||||
backgroundColor: getColorByIndex(datasets.length, 0.1),
|
backgroundColor: getColorByIndex(datasets.length, 0.1),
|
||||||
tension: 0.1,
|
tension: 0.4, // 增加曲线平滑度
|
||||||
fill: false
|
fill: true,
|
||||||
|
pointRadius: showChartPoints.value ? 3 : 0, // 根据开关控制点的显示
|
||||||
|
pointHoverRadius: showChartPoints.value ? 5 : 0 // 悬停时点的大小
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1393,13 +1509,14 @@ const drawMemoryChart = () => {
|
|||||||
options: {
|
options: {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
maintainAspectRatio: false,
|
maintainAspectRatio: false,
|
||||||
|
animation: false, // 禁用动画以提高性能
|
||||||
scales: {
|
scales: {
|
||||||
x: {
|
x: {
|
||||||
type: 'time',
|
type: 'time',
|
||||||
time: {
|
time: {
|
||||||
unit: 'minute',
|
unit: 'minute',
|
||||||
displayFormats: {
|
displayFormats: {
|
||||||
minute: 'HH:mm'
|
minute: 'MM-dd HH:mm'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
@@ -1445,7 +1562,7 @@ const getColorByIndex = (index, alpha = 1) => {
|
|||||||
|
|
||||||
// 关闭监控大屏
|
// 关闭监控大屏
|
||||||
const closeMonitor = () => {
|
const closeMonitor = () => {
|
||||||
monitorVisible.value = false;
|
showMonitor.value = false;
|
||||||
// 销毁图表实例
|
// 销毁图表实例
|
||||||
if (cpuChartInstance) {
|
if (cpuChartInstance) {
|
||||||
cpuChartInstance.destroy();
|
cpuChartInstance.destroy();
|
||||||
@@ -1455,12 +1572,35 @@ const closeMonitor = () => {
|
|||||||
memoryChartInstance.destroy();
|
memoryChartInstance.destroy();
|
||||||
memoryChartInstance = null;
|
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(() => {
|
onUnmounted(() => {
|
||||||
closeMonitor();
|
closeMonitor();
|
||||||
|
|
||||||
stopDeviceInfoRefreshTimer();
|
stopDeviceInfoRefreshTimer();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -1531,7 +1671,10 @@ onUnmounted(() => {
|
|||||||
color: #1d2129;
|
color: #1d2129;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
.chart-controls {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
.add-button {
|
.add-button {
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
|
|||||||
Reference in New Issue
Block a user