实现日志收集功能
This commit is contained in:
@@ -1,9 +1,8 @@
|
|||||||
{
|
{
|
||||||
"server_url": "http://10.35.10.12:8080/api",
|
"server_url": "http://10.35.10.12:8080/api",
|
||||||
"id": "agent-fnos1",
|
"id": "agent-1764858612504948034-3525156",
|
||||||
"name": "fnos1",
|
"name": "yunc",
|
||||||
"device_id": "agent-fnos1",
|
"token": "84f0657f9075f63e3964aeb7e3a9e59b",
|
||||||
"token": "eea3ffc9b3bb6b2a9f2e5bf228a2c7db",
|
|
||||||
"interval": "10s",
|
"interval": "10s",
|
||||||
"debug": true,
|
"debug": true,
|
||||||
"api_port": 8081
|
"api_port": 8081
|
||||||
|
|||||||
7121
agent/agent.log
Normal file
7121
agent/agent.log
Normal file
File diff suppressed because it is too large
Load Diff
189
agent/main.go
189
agent/main.go
@@ -10,6 +10,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -23,13 +24,12 @@ import (
|
|||||||
// Config Agent配置
|
// Config Agent配置
|
||||||
type Config struct {
|
type Config struct {
|
||||||
ServerURL string `json:"server_url"`
|
ServerURL string `json:"server_url"`
|
||||||
ID string `json:"id"` // Agent唯一标识,自动生成
|
ID string `json:"id"` // Agent唯一标识,自动生成
|
||||||
Name string `json:"name"` // Agent显示名称
|
Name string `json:"name"` // Agent显示名称
|
||||||
DeviceID string `json:"device_id"` // 向后兼容,保留
|
Token string `json:"token"` // 设备认证令牌
|
||||||
Token string `json:"token"` // 设备认证令牌
|
Interval string `json:"interval"` // 采集间隔
|
||||||
Interval string `json:"interval"` // 采集间隔
|
Debug bool `json:"debug"` // 调试模式
|
||||||
Debug bool `json:"debug"` // 调试模式
|
APIPort int `json:"api_port"` // API端口
|
||||||
APIPort int `json:"api_port"` // API端口
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetworkInterfaceMetrics 网卡监控指标
|
// NetworkInterfaceMetrics 网卡监控指标
|
||||||
@@ -92,10 +92,9 @@ type Metrics struct {
|
|||||||
RxRate uint64 `json:"rx_rate"` // 所有网卡实时接收速率总和 (bytes/s)
|
RxRate uint64 `json:"rx_rate"` // 所有网卡实时接收速率总和 (bytes/s)
|
||||||
TxRate uint64 `json:"tx_rate"` // 所有网卡实时发送速率总和 (bytes/s)
|
TxRate uint64 `json:"tx_rate"` // 所有网卡实时发送速率总和 (bytes/s)
|
||||||
// 设备信息字段
|
// 设备信息字段
|
||||||
DeviceID string `json:"device_id"` // 设备ID
|
AgentID string `json:"agent_id"` // Agent唯一标识
|
||||||
AgentID string `json:"agent_id"` // Agent唯一标识
|
Name string `json:"name"` // 设备名称
|
||||||
Name string `json:"name"` // 设备名称
|
IP string `json:"ip"` // 设备IP地址
|
||||||
IP string `json:"ip"` // 设备IP地址
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 全局配置
|
// 全局配置
|
||||||
@@ -183,10 +182,9 @@ func initConfig() {
|
|||||||
// 默认配置
|
// 默认配置
|
||||||
config = Config{
|
config = Config{
|
||||||
ServerURL: "http://localhost:8080/api",
|
ServerURL: "http://localhost:8080/api",
|
||||||
ID: "", // 自动生成
|
ID: "", // 自动生成
|
||||||
Name: "", // 自动生成或从配置读取
|
Name: "", // 自动生成或从配置读取
|
||||||
DeviceID: "default", // 向后兼容,保留
|
Token: "", // 设备认证令牌,从配置或环境变量读取
|
||||||
Token: "", // 设备认证令牌,从配置或环境变量读取
|
|
||||||
Interval: "10s",
|
Interval: "10s",
|
||||||
Debug: false, // 默认非调试模式
|
Debug: false, // 默认非调试模式
|
||||||
APIPort: 8081, // 默认API端口8081
|
APIPort: 8081, // 默认API端口8081
|
||||||
@@ -217,9 +215,9 @@ func initConfig() {
|
|||||||
|
|
||||||
// 打印配置信息
|
// 打印配置信息
|
||||||
if config.Debug {
|
if config.Debug {
|
||||||
log.Printf("Agent ID: %s, Name: %s, DeviceID: %s, Debug: %v, API Port: %d", config.ID, config.Name, config.DeviceID, config.Debug, config.APIPort)
|
log.Printf("Agent ID: %s, Name: %s, Debug: %v, API Port: %d", config.ID, config.Name, config.Debug, config.APIPort)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Agent ID: %s, Name: %s, DeviceID: %s", config.ID, config.Name, config.DeviceID)
|
log.Printf("Agent ID: %s, Name: %s", config.ID, config.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,10 +235,6 @@ func loadFromEnv() {
|
|||||||
config.Name = name
|
config.Name = name
|
||||||
}
|
}
|
||||||
|
|
||||||
if deviceID := os.Getenv("AGENT_DEVICE_ID"); deviceID != "" {
|
|
||||||
config.DeviceID = deviceID
|
|
||||||
}
|
|
||||||
|
|
||||||
if token := os.Getenv("AGENT_TOKEN"); token != "" {
|
if token := os.Getenv("AGENT_TOKEN"); token != "" {
|
||||||
config.Token = token
|
config.Token = token
|
||||||
}
|
}
|
||||||
@@ -356,10 +350,6 @@ func readConfigFile() {
|
|||||||
config.Name = fileConfig.Name
|
config.Name = fileConfig.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
if fileConfig.DeviceID != "" {
|
|
||||||
config.DeviceID = fileConfig.DeviceID
|
|
||||||
}
|
|
||||||
|
|
||||||
if fileConfig.Token != "" {
|
if fileConfig.Token != "" {
|
||||||
config.Token = fileConfig.Token
|
config.Token = fileConfig.Token
|
||||||
}
|
}
|
||||||
@@ -746,6 +736,116 @@ func collectDiskDetails() ([]DiskDetailMetrics, error) {
|
|||||||
return diskDetails, nil
|
return diskDetails, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 采集系统日志
|
||||||
|
func collectLogs() ([]LogEntry, error) {
|
||||||
|
// 日志文件路径
|
||||||
|
logFile := "/var/log/messages"
|
||||||
|
log.Printf("Attempting to collect logs from %s", logFile)
|
||||||
|
|
||||||
|
// 检查文件是否存在和权限
|
||||||
|
fileInfo, err := os.Stat(logFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to stat log file %s: %v", logFile, err)
|
||||||
|
return nil, fmt.Errorf("failed to stat log file %s: %w", logFile, err)
|
||||||
|
}
|
||||||
|
log.Printf("Log file %s exists, size: %d bytes", logFile, fileInfo.Size())
|
||||||
|
|
||||||
|
// 打开日志文件
|
||||||
|
file, err := os.Open(logFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to open log file %s: %v", logFile, err)
|
||||||
|
return nil, fmt.Errorf("failed to open log file %s: %w", logFile, err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// 读取文件末尾内容
|
||||||
|
const maxReadSize = 1024 * 1024 // 最多读取1MB
|
||||||
|
readSize := maxReadSize
|
||||||
|
if fileInfo.Size() < int64(maxReadSize) {
|
||||||
|
readSize = int(fileInfo.Size())
|
||||||
|
}
|
||||||
|
log.Printf("Reading %d bytes from log file", readSize)
|
||||||
|
|
||||||
|
buf := make([]byte, readSize)
|
||||||
|
bytesRead, err := file.ReadAt(buf, fileInfo.Size()-int64(readSize))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to read log file: %v", err)
|
||||||
|
return nil, fmt.Errorf("failed to read log file: %w", err)
|
||||||
|
}
|
||||||
|
log.Printf("Successfully read %d bytes from log file", bytesRead)
|
||||||
|
|
||||||
|
// 分割日志行
|
||||||
|
lines := bytes.Split(buf[:bytesRead], []byte("\n"))
|
||||||
|
log.Printf("Found %d lines in log file", len(lines))
|
||||||
|
|
||||||
|
// 创建日志条目切片
|
||||||
|
logs := make([]LogEntry, 0, 50) // 最多保存50条日志
|
||||||
|
|
||||||
|
// 从后往前解析日志行
|
||||||
|
for i := len(lines) - 1; i >= 0 && len(logs) < 50; i-- {
|
||||||
|
line := bytes.TrimSpace(lines[i])
|
||||||
|
if len(line) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打印前5行日志行,用于调试
|
||||||
|
if i >= len(lines)-5 {
|
||||||
|
log.Printf("Processing log line %d: %s", i, string(line))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用字符串处理,更方便处理空格
|
||||||
|
lineStr := string(line)
|
||||||
|
|
||||||
|
// 使用strings.Fields分割日志行,自动处理连续空格
|
||||||
|
fields := strings.Fields(lineStr)
|
||||||
|
log.Printf("Line %d fields: %v, length: %d", i, fields, len(fields))
|
||||||
|
if len(fields) < 6 {
|
||||||
|
log.Printf("Skipping line %d: not enough fields (%d) after splitting", i, len(fields))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建时间字符串:月份 日期 时间
|
||||||
|
timeStr := fmt.Sprintf("%s %s %s", fields[0], fields[1], fields[2])
|
||||||
|
log.Printf("Line %d timeStr: '%s'", i, timeStr)
|
||||||
|
|
||||||
|
// 解析时间
|
||||||
|
t, err := time.Parse("Jan 2 15:04:05", timeStr)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Skipping line %d: failed to parse time '%s': %v", i, timeStr, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置当前年份
|
||||||
|
year, _, _ := time.Now().Date()
|
||||||
|
t = time.Date(year, t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), 0, time.Local)
|
||||||
|
|
||||||
|
// 寻找第一个冒号,用于分割source和message
|
||||||
|
colonIndex := strings.Index(lineStr, ": ")
|
||||||
|
if colonIndex == -1 {
|
||||||
|
log.Printf("Skipping line %d: no colon found to split source and message", i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析source和message
|
||||||
|
source := lineStr[:colonIndex]
|
||||||
|
message := lineStr[colonIndex+2:]
|
||||||
|
|
||||||
|
// 创建日志条目
|
||||||
|
logEntry := LogEntry{
|
||||||
|
Sequence: len(logs) + 1,
|
||||||
|
Source: source,
|
||||||
|
Time: t,
|
||||||
|
Message: message,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加到日志切片(注意顺序,后面的日志先解析,所以需要插入到前面)
|
||||||
|
logs = append([]LogEntry{logEntry}, logs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Successfully collected %d logs", len(logs))
|
||||||
|
return logs, nil
|
||||||
|
}
|
||||||
|
|
||||||
func collectMetrics() (*Metrics, error) {
|
func collectMetrics() (*Metrics, error) {
|
||||||
metrics := &Metrics{}
|
metrics := &Metrics{}
|
||||||
|
|
||||||
@@ -753,11 +853,6 @@ func collectMetrics() (*Metrics, error) {
|
|||||||
metrics.Network = make(map[string]NetworkInterfaceMetrics)
|
metrics.Network = make(map[string]NetworkInterfaceMetrics)
|
||||||
|
|
||||||
// 设置设备信息
|
// 设置设备信息
|
||||||
deviceID := config.DeviceID
|
|
||||||
if deviceID == "" {
|
|
||||||
deviceID = config.ID
|
|
||||||
}
|
|
||||||
metrics.DeviceID = deviceID
|
|
||||||
metrics.AgentID = config.ID
|
metrics.AgentID = config.ID
|
||||||
metrics.Name = config.Name
|
metrics.Name = config.Name
|
||||||
// 尝试获取本机IP地址
|
// 尝试获取本机IP地址
|
||||||
@@ -825,6 +920,15 @@ func collectMetrics() (*Metrics, error) {
|
|||||||
metrics.RxRate = rxRate
|
metrics.RxRate = rxRate
|
||||||
metrics.TxRate = txRate
|
metrics.TxRate = txRate
|
||||||
|
|
||||||
|
// 采集系统日志
|
||||||
|
logs, err := collectLogs()
|
||||||
|
if err != nil {
|
||||||
|
// 日志采集失败时使用空切片
|
||||||
|
log.Printf("Failed to collect logs: %v, using empty slice", err)
|
||||||
|
logs = make([]LogEntry, 0)
|
||||||
|
}
|
||||||
|
metrics.Logs = logs
|
||||||
|
|
||||||
return metrics, nil
|
return metrics, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -849,12 +953,6 @@ func sendMetrics(metricsList []*Metrics) error {
|
|||||||
|
|
||||||
// 设置请求头
|
// 设置请求头
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
// 使用DeviceID作为设备唯一标识,与设备管理中的ID匹配
|
|
||||||
deviceID := config.DeviceID
|
|
||||||
if deviceID == "" {
|
|
||||||
deviceID = config.ID
|
|
||||||
}
|
|
||||||
req.Header.Set("X-Device-ID", deviceID)
|
|
||||||
// 设置Agent名称
|
// 设置Agent名称
|
||||||
req.Header.Set("X-Agent-Name", config.Name)
|
req.Header.Set("X-Agent-Name", config.Name)
|
||||||
// 设置设备认证令牌
|
// 设置设备认证令牌
|
||||||
@@ -928,16 +1026,15 @@ func startHTTPServer() {
|
|||||||
disk, _ := collectDisk()
|
disk, _ := collectDisk()
|
||||||
|
|
||||||
status := map[string]interface{}{
|
status := map[string]interface{}{
|
||||||
"status": "running",
|
"status": "running",
|
||||||
"agent_id": config.ID,
|
"agent_id": config.ID,
|
||||||
"name": config.Name,
|
"name": config.Name,
|
||||||
"device_id": config.DeviceID,
|
"debug": config.Debug,
|
||||||
"debug": config.Debug,
|
"interval": config.Interval,
|
||||||
"interval": config.Interval,
|
"cpu": cpu,
|
||||||
"cpu": cpu,
|
"cpu_hz": cpuHz,
|
||||||
"cpu_hz": cpuHz,
|
"memory": memory,
|
||||||
"memory": memory,
|
"disk": disk,
|
||||||
"disk": disk,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|||||||
Binary file not shown.
BIN
backend/backend
BIN
backend/backend
Binary file not shown.
@@ -8,6 +8,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -37,12 +38,13 @@ func RegisterRoutes(r *gin.Engine) {
|
|||||||
// 监控数据路由
|
// 监控数据路由
|
||||||
metrics := api.Group("/metrics")
|
metrics := api.Group("/metrics")
|
||||||
{
|
{
|
||||||
metrics.GET("/cpu", GetCPUMetrics)
|
metrics.GET("/cpu", GetCPUMetrics) // 添加CPU信息查询端点
|
||||||
metrics.GET("/memory", GetMemoryMetrics)
|
metrics.GET("/memory", GetMemoryMetrics) // 添加内存信息查询端点
|
||||||
metrics.GET("/disk", GetDiskMetrics)
|
metrics.GET("/disk", GetDiskMetrics) // 添加磁盘信息查询端点
|
||||||
metrics.GET("/network", GetNetworkMetrics)
|
metrics.GET("/network", GetNetworkMetrics) // 添加网络信息查询端点
|
||||||
metrics.GET("/processes", GetProcessMetrics) // 添加进程信息查询端点
|
metrics.GET("/processes", GetProcessMetrics) // 添加进程信息查询端点
|
||||||
metrics.GET("/disk_details", GetDiskDetails) // 添加磁盘详细信息查询端点
|
metrics.GET("/disk_details", GetDiskDetails) // 添加磁盘详细信息查询端点
|
||||||
|
metrics.GET("/logs", GetLogs) // 添加系统日志查询端点
|
||||||
// 添加POST端点,接收Agent发送的指标数据
|
// 添加POST端点,接收Agent发送的指标数据
|
||||||
metrics.POST("/", HandleMetricsPost)
|
metrics.POST("/", HandleMetricsPost)
|
||||||
}
|
}
|
||||||
@@ -98,6 +100,13 @@ type DiskDetailMetrics struct {
|
|||||||
Description string `json:"description"` // 设备描述
|
Description string `json:"description"` // 设备描述
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LogMetrics 系统日志指标
|
||||||
|
type LogMetrics struct {
|
||||||
|
Source string `json:"source"` // 日志来源
|
||||||
|
Time string `json:"time"` // 日志时间
|
||||||
|
Message string `json:"message"` // 日志内容
|
||||||
|
}
|
||||||
|
|
||||||
// NetworkInterfaceMetrics 网卡监控指标
|
// NetworkInterfaceMetrics 网卡监控指标
|
||||||
type NetworkInterfaceMetrics struct {
|
type NetworkInterfaceMetrics struct {
|
||||||
BytesSent uint64 `json:"bytes_sent"` // 发送速率 (bytes/s)
|
BytesSent uint64 `json:"bytes_sent"` // 发送速率 (bytes/s)
|
||||||
@@ -106,6 +115,14 @@ type NetworkInterfaceMetrics struct {
|
|||||||
RxBytes uint64 `json:"rx_bytes"` // 累计接收字节数
|
RxBytes uint64 `json:"rx_bytes"` // 累计接收字节数
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LogEntry 系统日志条目
|
||||||
|
type LogEntry struct {
|
||||||
|
Sequence int `json:"sequence"` // 日志序号
|
||||||
|
Source string `json:"source"` // 来源
|
||||||
|
Time time.Time `json:"time"` // 发生时间
|
||||||
|
Message string `json:"message"` // 内容
|
||||||
|
}
|
||||||
|
|
||||||
// MetricsRequest 指标请求结构
|
// MetricsRequest 指标请求结构
|
||||||
type MetricsRequest struct {
|
type MetricsRequest struct {
|
||||||
CPU float64 `json:"cpu"`
|
CPU float64 `json:"cpu"`
|
||||||
@@ -115,6 +132,7 @@ type MetricsRequest struct {
|
|||||||
DiskDetails []DiskDetailMetrics `json:"disk_details"` // 磁盘详细信息
|
DiskDetails []DiskDetailMetrics `json:"disk_details"` // 磁盘详细信息
|
||||||
Network map[string]NetworkInterfaceMetrics `json:"network"`
|
Network map[string]NetworkInterfaceMetrics `json:"network"`
|
||||||
Processes []ProcessMetrics `json:"processes"` // 进程信息
|
Processes []ProcessMetrics `json:"processes"` // 进程信息
|
||||||
|
Logs []LogEntry `json:"logs"` // 系统日志
|
||||||
RxTotal uint64 `json:"rx_total"` // 所有网卡累计接收字节数总和
|
RxTotal uint64 `json:"rx_total"` // 所有网卡累计接收字节数总和
|
||||||
TxTotal uint64 `json:"tx_total"` // 所有网卡累计发送字节数总和
|
TxTotal uint64 `json:"tx_total"` // 所有网卡累计发送字节数总和
|
||||||
RxRate uint64 `json:"rx_rate"` // 所有网卡实时接收速率总和 (bytes/s)
|
RxRate uint64 `json:"rx_rate"` // 所有网卡实时接收速率总和 (bytes/s)
|
||||||
@@ -303,6 +321,14 @@ func HandleMetricsPost(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 写入日志数据
|
||||||
|
for _, logEntry := range req.Logs {
|
||||||
|
if err := globalStorage.WriteLogMetric(writeCtx, deviceID, logEntry.Sequence, logEntry.Source, logEntry.Time, logEntry.Message, baseTags); err != nil {
|
||||||
|
// 只记录警告,不影响后续指标处理
|
||||||
|
log.Printf("Warning: Failed to write log for device %s: %v", deviceID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 广播指标更新消息,只广播最后一个指标
|
// 广播指标更新消息,只广播最后一个指标
|
||||||
if i == len(metricsList)-1 {
|
if i == len(metricsList)-1 {
|
||||||
// 准备广播的磁盘使用率数据(兼容旧格式)
|
// 准备广播的磁盘使用率数据(兼容旧格式)
|
||||||
@@ -559,8 +585,8 @@ func GetNetworkMetrics(c *gin.Context) {
|
|||||||
receivedPoints, err2 := globalStorage.QueryMetrics(context.Background(), deviceID, "network_received", startTime, endTime)
|
receivedPoints, err2 := globalStorage.QueryMetrics(context.Background(), deviceID, "network_received", startTime, endTime)
|
||||||
|
|
||||||
// 查询发送和接收的累积总流量指标
|
// 查询发送和接收的累积总流量指标
|
||||||
txBytesPoints, err3 := globalStorage.QueryMetrics(context.Background(), deviceID, "network_total_tx_bytes", startTime, endTime)
|
txBytesPoints, err3 := globalStorage.QueryMetrics(context.Background(), deviceID, "network_tx_bytes", startTime, endTime)
|
||||||
rxBytesPoints, err4 := globalStorage.QueryMetrics(context.Background(), deviceID, "network_total_rx_bytes", startTime, endTime)
|
rxBytesPoints, err4 := globalStorage.QueryMetrics(context.Background(), deviceID, "network_rx_bytes", startTime, endTime)
|
||||||
|
|
||||||
// 处理错误
|
// 处理错误
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
@@ -918,8 +944,12 @@ func GetAllDevices(c *gin.Context) {
|
|||||||
func GetProcessMetrics(c *gin.Context) {
|
func GetProcessMetrics(c *gin.Context) {
|
||||||
// 获取查询参数
|
// 获取查询参数
|
||||||
deviceID := c.Query("device_id") // 不使用默认值,空值表示查询所有设备
|
deviceID := c.Query("device_id") // 不使用默认值,空值表示查询所有设备
|
||||||
startTime := c.DefaultQuery("start_time", "-1h")
|
startTime := c.DefaultQuery("start_time", "-24h")
|
||||||
endTime := c.DefaultQuery("end_time", "now()")
|
endTime := c.DefaultQuery("end_time", "now()")
|
||||||
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||||
|
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
|
||||||
|
sortBy := c.DefaultQuery("sort_by", "cpu")
|
||||||
|
sortOrder := c.DefaultQuery("sort_order", "desc")
|
||||||
|
|
||||||
// 查询数据
|
// 查询数据
|
||||||
processes, err := globalStorage.QueryProcessMetrics(context.Background(), deviceID, startTime, endTime)
|
processes, err := globalStorage.QueryProcessMetrics(context.Background(), deviceID, startTime, endTime)
|
||||||
@@ -927,13 +957,23 @@ func GetProcessMetrics(c *gin.Context) {
|
|||||||
// 只记录警告,返回空数据
|
// 只记录警告,返回空数据
|
||||||
log.Printf("Warning: Failed to query process metrics: %v", err)
|
log.Printf("Warning: Failed to query process metrics: %v", err)
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"data": []map[string]interface{}{},
|
"data": []ProcessMetrics{},
|
||||||
|
"page": page,
|
||||||
|
"limit": limit,
|
||||||
|
"total": 0,
|
||||||
|
"sort_by": sortBy,
|
||||||
|
"sort_order": sortOrder,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"data": processes,
|
"data": processes,
|
||||||
|
"page": page,
|
||||||
|
"limit": limit,
|
||||||
|
"total": len(processes),
|
||||||
|
"sort_by": sortBy,
|
||||||
|
"sort_order": sortOrder,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -941,7 +981,7 @@ func GetProcessMetrics(c *gin.Context) {
|
|||||||
func GetDiskDetails(c *gin.Context) {
|
func GetDiskDetails(c *gin.Context) {
|
||||||
// 获取查询参数
|
// 获取查询参数
|
||||||
deviceID := c.Query("device_id") // 不使用默认值,空值表示查询所有设备
|
deviceID := c.Query("device_id") // 不使用默认值,空值表示查询所有设备
|
||||||
startTime := c.DefaultQuery("start_time", "-1h")
|
startTime := c.DefaultQuery("start_time", "-24h")
|
||||||
endTime := c.DefaultQuery("end_time", "now()")
|
endTime := c.DefaultQuery("end_time", "now()")
|
||||||
|
|
||||||
// 查询数据
|
// 查询数据
|
||||||
@@ -950,7 +990,7 @@ func GetDiskDetails(c *gin.Context) {
|
|||||||
// 只记录警告,返回空数据
|
// 只记录警告,返回空数据
|
||||||
log.Printf("Warning: Failed to query disk details: %v", err)
|
log.Printf("Warning: Failed to query disk details: %v", err)
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"data": []map[string]interface{}{},
|
"data": []DiskDetailMetrics{},
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -959,3 +999,62 @@ func GetDiskDetails(c *gin.Context) {
|
|||||||
"data": diskDetails,
|
"data": diskDetails,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetLogs 获取系统日志
|
||||||
|
func GetLogs(c *gin.Context) {
|
||||||
|
// 获取查询参数
|
||||||
|
deviceID := c.Query("device_id") // 不使用默认值,空值表示查询所有设备
|
||||||
|
startTime := c.DefaultQuery("start_time", "-24h")
|
||||||
|
endTime := c.DefaultQuery("end_time", "now()")
|
||||||
|
|
||||||
|
// 查询数据
|
||||||
|
logData, err := globalStorage.QueryLogMetrics(context.Background(), deviceID, startTime, endTime)
|
||||||
|
if err != nil {
|
||||||
|
// 只记录警告,返回空数据
|
||||||
|
log.Printf("Warning: Failed to query logs: %v", err)
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"data": []LogMetrics{},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为前端需要的格式
|
||||||
|
logs := make([]LogMetrics, 0, len(logData))
|
||||||
|
for _, log := range logData {
|
||||||
|
// 将time.Time转换为字符串
|
||||||
|
timeValue, ok := log["time"].(time.Time)
|
||||||
|
var timeStr string
|
||||||
|
if ok {
|
||||||
|
timeStr = timeValue.Format(time.RFC3339)
|
||||||
|
} else {
|
||||||
|
// 如果不是time.Time类型,尝试转换
|
||||||
|
if timeStrVal, ok := log["time"].(string); ok {
|
||||||
|
timeStr = timeStrVal
|
||||||
|
} else {
|
||||||
|
timeStr = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取其他字段
|
||||||
|
source := ""
|
||||||
|
if sourceVal, ok := log["source"].(string); ok {
|
||||||
|
source = sourceVal
|
||||||
|
}
|
||||||
|
|
||||||
|
message := ""
|
||||||
|
if messageVal, ok := log["message"].(string); ok {
|
||||||
|
message = messageVal
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加到结果列表
|
||||||
|
logs = append(logs, LogMetrics{
|
||||||
|
Source: source,
|
||||||
|
Time: timeStr,
|
||||||
|
Message: message,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"data": logs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -228,6 +228,29 @@ func (s *Storage) WriteDiskDetailMetric(ctx context.Context, deviceID, diskDevic
|
|||||||
return s.writeData(ctx, "disk_details", allTags, fields, deviceID, "disk_detail")
|
return s.writeData(ctx, "disk_details", allTags, fields, deviceID, "disk_detail")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteLogMetric 写入日志指标
|
||||||
|
func (s *Storage) WriteLogMetric(ctx context.Context, deviceID string, sequence int, source string, time time.Time, message string, tags map[string]string) error {
|
||||||
|
// 创建标签映射,合并原有标签和新标签
|
||||||
|
allTags := make(map[string]string)
|
||||||
|
// 复制原有标签
|
||||||
|
for k, v := range tags {
|
||||||
|
allTags[k] = v
|
||||||
|
}
|
||||||
|
// 添加设备ID标签
|
||||||
|
allTags["device_id"] = deviceID
|
||||||
|
// 添加日志来源标签
|
||||||
|
allTags["source"] = source
|
||||||
|
|
||||||
|
// 创建字段映射
|
||||||
|
fields := map[string]interface{}{
|
||||||
|
"sequence": sequence,
|
||||||
|
"message": message,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用新的writeData方法
|
||||||
|
return s.writeData(ctx, "logs", allTags, fields, deviceID, "log")
|
||||||
|
}
|
||||||
|
|
||||||
// QueryMetrics 查询监控指标
|
// QueryMetrics 查询监控指标
|
||||||
func (s *Storage) QueryMetrics(ctx context.Context, deviceID, metricType, startTime, endTime string) ([]MetricPoint, error) {
|
func (s *Storage) QueryMetrics(ctx context.Context, deviceID, metricType, startTime, endTime string) ([]MetricPoint, error) {
|
||||||
queryAPI := s.client.QueryAPI(s.org)
|
queryAPI := s.client.QueryAPI(s.org)
|
||||||
@@ -526,6 +549,67 @@ func (s *Storage) QueryProcessMetrics(ctx context.Context, deviceID string, star
|
|||||||
return processes, nil
|
return processes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QueryLogMetrics 查询日志指标
|
||||||
|
func (s *Storage) QueryLogMetrics(ctx context.Context, deviceID string, startTime, endTime string) ([]map[string]interface{}, error) {
|
||||||
|
queryAPI := s.client.QueryAPI(s.org)
|
||||||
|
|
||||||
|
// 构建查询语句
|
||||||
|
query := `from(bucket: "` + s.bucket + `")
|
||||||
|
|> range(start: ` + startTime + `, stop: ` + endTime + `)
|
||||||
|
|> filter(fn: (r) => r["_measurement"] == "logs")`
|
||||||
|
|
||||||
|
// 如果指定了设备ID,添加设备ID过滤
|
||||||
|
if deviceID != "" {
|
||||||
|
query += `
|
||||||
|
|> filter(fn: (r) => r["device_id"] == "` + deviceID + `")`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按时间倒序排列,获取最新的日志
|
||||||
|
query += `
|
||||||
|
|> sort(columns: ["_time"], desc: true)
|
||||||
|
|> limit(n: 100)` // 限制返回100条最新日志
|
||||||
|
|
||||||
|
// 执行查询
|
||||||
|
queryResult, err := queryAPI.Query(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer queryResult.Close()
|
||||||
|
|
||||||
|
// 存储日志数据
|
||||||
|
logs := make([]map[string]interface{}, 0)
|
||||||
|
|
||||||
|
// 处理查询结果
|
||||||
|
for queryResult.Next() {
|
||||||
|
if queryResult.TableChanged() {
|
||||||
|
// 表结构变化,跳过
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取记录
|
||||||
|
record := queryResult.Record()
|
||||||
|
|
||||||
|
// 构建日志数据
|
||||||
|
logData := map[string]interface{}{
|
||||||
|
"time": record.Time(),
|
||||||
|
"device_id": record.ValueByKey("device_id"),
|
||||||
|
"source": record.ValueByKey("source"),
|
||||||
|
"sequence": record.ValueByKey("sequence"),
|
||||||
|
"message": record.ValueByKey("message"),
|
||||||
|
"agent_name": record.ValueByKey("agent_name"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加到日志列表
|
||||||
|
logs = append(logs, logData)
|
||||||
|
}
|
||||||
|
|
||||||
|
if queryResult.Err() != nil {
|
||||||
|
return nil, queryResult.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
return logs, nil
|
||||||
|
}
|
||||||
|
|
||||||
// QueryDiskDetails 查询磁盘详细信息
|
// QueryDiskDetails 查询磁盘详细信息
|
||||||
func (s *Storage) QueryDiskDetails(ctx context.Context, deviceID string, startTime, endTime string) ([]map[string]interface{}, error) {
|
func (s *Storage) QueryDiskDetails(ctx context.Context, deviceID string, startTime, endTime string) ([]map[string]interface{}, error) {
|
||||||
queryAPI := s.client.QueryAPI(s.org)
|
queryAPI := s.client.QueryAPI(s.org)
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ func main() {
|
|||||||
// 启动服务器
|
// 启动服务器
|
||||||
addr := fmt.Sprintf(":%d", cfg.Server.Port)
|
addr := fmt.Sprintf(":%d", cfg.Server.Port)
|
||||||
log.Printf("Server starting on %s", addr)
|
log.Printf("Server starting on %s", addr)
|
||||||
log.Printf("Static files served from /root/monitor/frontend")
|
log.Printf("Static files served from ./static")
|
||||||
if err := r.Run(addr); err != nil {
|
if err := r.Run(addr); err != nil {
|
||||||
log.Fatalf("Failed to start server: %v", err)
|
log.Fatalf("Failed to start server: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -360,7 +360,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<br>
|
||||||
<!-- 进程信息展示 -->
|
<!-- 进程信息展示 -->
|
||||||
<div id="processInfoContainer" class="mt-8">
|
<div id="processInfoContainer" class="mt-8">
|
||||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">进程信息</h3>
|
<h3 class="text-lg font-semibold text-gray-900 mb-4">进程信息</h3>
|
||||||
@@ -478,8 +478,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 系统日志 图表 -->
|
<!-- 系统日志 信息 -->
|
||||||
<div id="logChartContainer" class="chart-container h-80 hidden">
|
<div id="logInfoContainer" class="mt-8 hidden">
|
||||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">系统日志</h3>
|
<h3 class="text-lg font-semibold text-gray-900 mb-4">系统日志</h3>
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table id="logTable" class="min-w-full bg-white rounded-lg overflow-hidden shadow-md">
|
<table id="logTable" class="min-w-full bg-white rounded-lg overflow-hidden shadow-md">
|
||||||
@@ -496,6 +496,8 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 日志信息分页容器 -->
|
||||||
|
<div id="logPaginationContainer" class="mt-4"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 缩放控件已移动到各个图表容器内 -->
|
<!-- 缩放控件已移动到各个图表容器内 -->
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ let state = {
|
|||||||
customStartTime: '',
|
customStartTime: '',
|
||||||
customEndTime: '',
|
customEndTime: '',
|
||||||
currentInterval: '3m', // 固定10分钟区间
|
currentInterval: '3m', // 固定10分钟区间
|
||||||
|
currentDeviceID: '', // 当前选中的服务器ID
|
||||||
historyMetrics: {}, // 存储历史指标数据
|
historyMetrics: {}, // 存储历史指标数据
|
||||||
autoRefreshEnabled: false, // 自动刷新开关状态,默认关闭
|
autoRefreshEnabled: false, // 自动刷新开关状态,默认关闭
|
||||||
lastMetricsUpdate: null, // 记录上次指标更新时间
|
lastMetricsUpdate: null, // 记录上次指标更新时间
|
||||||
@@ -101,12 +102,22 @@ function updateAutoRefreshInterval() {
|
|||||||
// 初始化自定义时间范围
|
// 初始化自定义时间范围
|
||||||
function initCustomTimeRange() {
|
function initCustomTimeRange() {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
// 默认显示过去24小时
|
// 默认显示过去1小时
|
||||||
const twentyFourHoursAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
const oneHourAgo = new Date(now.getTime() - 1 * 60 * 60 * 1000);
|
||||||
|
|
||||||
// 直接使用ISO字符串,包含完整的时区信息
|
// 直接使用ISO字符串,包含完整的时区信息
|
||||||
state.customStartTime = twentyFourHoursAgo.toISOString();
|
state.customStartTime = oneHourAgo.toISOString();
|
||||||
state.customEndTime = now.toISOString();
|
state.customEndTime = now.toISOString();
|
||||||
|
|
||||||
|
// 更新日期选择器输入框的值
|
||||||
|
const startTimeInput = document.getElementById('customStartTime');
|
||||||
|
const endTimeInput = document.getElementById('customEndTime');
|
||||||
|
|
||||||
|
if (startTimeInput && endTimeInput) {
|
||||||
|
// 将ISO字符串转换为datetime-local格式(YYYY-MM-DDTHH:MM)
|
||||||
|
startTimeInput.value = oneHourAgo.toISOString().slice(0, 16);
|
||||||
|
endTimeInput.value = now.toISOString().slice(0, 16);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 页面切换
|
// 页面切换
|
||||||
@@ -125,9 +136,13 @@ function switchPage() {
|
|||||||
if (hash === '#servers') {
|
if (hash === '#servers') {
|
||||||
showContent('serversContent');
|
showContent('serversContent');
|
||||||
loadAllServers();
|
loadAllServers();
|
||||||
|
// 清除当前设备ID,避免在服务器列表页显示特定服务器的数据
|
||||||
|
state.currentDeviceID = '';
|
||||||
} else if (hash === '#devices') {
|
} else if (hash === '#devices') {
|
||||||
showContent('devicesContent');
|
showContent('devicesContent');
|
||||||
loadDeviceManagementList();
|
loadDeviceManagementList();
|
||||||
|
// 清除当前设备ID
|
||||||
|
state.currentDeviceID = '';
|
||||||
} else if (hash === '#serverMonitor' || hash.startsWith('#serverMonitor/')) {
|
} else if (hash === '#serverMonitor' || hash.startsWith('#serverMonitor/')) {
|
||||||
showContent('serverMonitorContent');
|
showContent('serverMonitorContent');
|
||||||
|
|
||||||
@@ -137,6 +152,9 @@ function switchPage() {
|
|||||||
deviceId = hash.split('/')[1];
|
deviceId = hash.split('/')[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 直接设置当前设备ID,确保loadMetrics能使用正确的设备ID
|
||||||
|
state.currentDeviceID = deviceId;
|
||||||
|
|
||||||
// 加载服务器信息
|
// 加载服务器信息
|
||||||
if (deviceId) {
|
if (deviceId) {
|
||||||
loadServerInfo(deviceId);
|
loadServerInfo(deviceId);
|
||||||
@@ -146,6 +164,8 @@ function switchPage() {
|
|||||||
} else {
|
} else {
|
||||||
showContent('homeContent');
|
showContent('homeContent');
|
||||||
loadHomeData();
|
loadHomeData();
|
||||||
|
// 清除当前设备ID
|
||||||
|
state.currentDeviceID = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -733,7 +753,7 @@ function initDetailedCharts() {
|
|||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
wheel: {
|
wheel: {
|
||||||
enabled: true,
|
enabled: true
|
||||||
},
|
},
|
||||||
pinch: {
|
pinch: {
|
||||||
enabled: true
|
enabled: true
|
||||||
@@ -802,7 +822,7 @@ function initDetailedCharts() {
|
|||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
wheel: {
|
wheel: {
|
||||||
enabled: true,
|
enabled: true
|
||||||
},
|
},
|
||||||
pinch: {
|
pinch: {
|
||||||
enabled: true
|
enabled: true
|
||||||
@@ -872,7 +892,7 @@ function initDetailedCharts() {
|
|||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
wheel: {
|
wheel: {
|
||||||
enabled: true,
|
enabled: true
|
||||||
},
|
},
|
||||||
pinch: {
|
pinch: {
|
||||||
enabled: true
|
enabled: true
|
||||||
@@ -950,11 +970,10 @@ function initDetailedCharts() {
|
|||||||
label: function(context) {
|
label: function(context) {
|
||||||
const label = context.dataset.label || '';
|
const label = context.dataset.label || '';
|
||||||
const value = context.parsed.y;
|
const value = context.parsed.y;
|
||||||
const rawValue = context.raw.y;
|
|
||||||
// 第二行显示实际值
|
// 第二行显示实际值
|
||||||
return [
|
return [
|
||||||
`${label}: ${value} MB`,
|
`${label}: ${value} MB`,
|
||||||
`实际值: ${rawValue} MB`
|
`实际值: ${value} MB`
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1043,11 +1062,10 @@ function initDetailedCharts() {
|
|||||||
label: function(context) {
|
label: function(context) {
|
||||||
const label = context.dataset.label || '';
|
const label = context.dataset.label || '';
|
||||||
const value = context.parsed.y;
|
const value = context.parsed.y;
|
||||||
const rawValue = context.raw.y;
|
|
||||||
// 第二行显示实际值
|
// 第二行显示实际值
|
||||||
return [
|
return [
|
||||||
`${label}: ${value} MB/s`,
|
`${label}: ${value} MB/s`,
|
||||||
`实际值: ${rawValue} MB/s`
|
`实际值: ${value} MB/s`
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1196,7 +1214,18 @@ async function fetchMetric(metricType, aggregation = 'average') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
return data.data;
|
|
||||||
|
// 确保返回的数据是数组格式
|
||||||
|
if (metricType === 'disk') {
|
||||||
|
// 磁盘数据可能是对象格式,需要特殊处理
|
||||||
|
return data.data || {};
|
||||||
|
} else if (metricType === 'network') {
|
||||||
|
// 网络数据可能是对象格式,需要特殊处理
|
||||||
|
return data.data || {};
|
||||||
|
} else {
|
||||||
|
// 其他数据应该是数组格式
|
||||||
|
return Array.isArray(data.data) ? data.data : [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 格式化时间,统一格式避免图表误解
|
// 格式化时间,统一格式避免图表误解
|
||||||
@@ -1259,6 +1288,9 @@ async function loadMetrics() {
|
|||||||
statusIndicator.className = 'w-2 h-2 bg-red-500 rounded-full animate-pulse';
|
statusIndicator.className = 'w-2 h-2 bg-red-500 rounded-full animate-pulse';
|
||||||
lastRefreshTime.textContent = `上次刷新: 失败`;
|
lastRefreshTime.textContent = `上次刷新: 失败`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 即使发生错误,也要尝试初始化图表,避免页面空白
|
||||||
|
initDetailedCharts();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1450,6 +1482,9 @@ function handleWebSocketMessage(message) {
|
|||||||
// 加载服务器信息
|
// 加载服务器信息
|
||||||
async function loadServerInfo(deviceId) {
|
async function loadServerInfo(deviceId) {
|
||||||
try {
|
try {
|
||||||
|
// 将设备ID存储到全局状态
|
||||||
|
state.currentDeviceID = deviceId;
|
||||||
|
|
||||||
const response = await fetch(`${API_BASE_URL}/devices/${deviceId}`);
|
const response = await fetch(`${API_BASE_URL}/devices/${deviceId}`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Failed to fetch server info');
|
throw new Error('Failed to fetch server info');
|
||||||
@@ -1464,6 +1499,9 @@ async function loadServerInfo(deviceId) {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load server info:', error);
|
console.error('Failed to load server info:', error);
|
||||||
|
// 即使请求失败,也要将设备ID存储到全局状态
|
||||||
|
state.currentDeviceID = deviceId;
|
||||||
|
|
||||||
// 使用模拟数据
|
// 使用模拟数据
|
||||||
const serverInfoDisplay = document.getElementById('serverInfoDisplay');
|
const serverInfoDisplay = document.getElementById('serverInfoDisplay');
|
||||||
if (serverInfoDisplay) {
|
if (serverInfoDisplay) {
|
||||||
@@ -1475,6 +1513,12 @@ async function loadServerInfo(deviceId) {
|
|||||||
// 处理指标更新
|
// 处理指标更新
|
||||||
function handleMetricsUpdate(message) {
|
function handleMetricsUpdate(message) {
|
||||||
const { device_id, metrics } = message;
|
const { device_id, metrics } = message;
|
||||||
|
|
||||||
|
// 只处理当前选中设备的WebSocket消息
|
||||||
|
if (state.currentDeviceID && device_id !== state.currentDeviceID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 格式化数据,确保updateStatusCards函数能正确处理
|
// 格式化数据,确保updateStatusCards函数能正确处理
|
||||||
const formattedMetrics = {
|
const formattedMetrics = {
|
||||||
cpu: metrics.cpu,
|
cpu: metrics.cpu,
|
||||||
@@ -1855,12 +1899,18 @@ function updateHistoryMetrics(metrics) {
|
|||||||
|
|
||||||
// 更新图表数据
|
// 更新图表数据
|
||||||
function updateCharts(cpuData, memoryData, diskData, networkData) {
|
function updateCharts(cpuData, memoryData, diskData, networkData) {
|
||||||
|
// 确保数据格式正确
|
||||||
|
const safeCpuData = Array.isArray(cpuData) ? cpuData : [];
|
||||||
|
const safeMemoryData = Array.isArray(memoryData) ? memoryData : [];
|
||||||
|
const safeDiskData = typeof diskData === 'object' && diskData !== null ? diskData : {};
|
||||||
|
const safeNetworkData = typeof networkData === 'object' && networkData !== null ? networkData : {};
|
||||||
|
|
||||||
// 保存待处理的图表更新数据
|
// 保存待处理的图表更新数据
|
||||||
state.pendingChartUpdate = {
|
state.pendingChartUpdate = {
|
||||||
cpuData,
|
cpuData: safeCpuData,
|
||||||
memoryData,
|
memoryData: safeMemoryData,
|
||||||
diskData,
|
diskData: safeDiskData,
|
||||||
networkData
|
networkData: safeNetworkData
|
||||||
};
|
};
|
||||||
|
|
||||||
// 使用节流机制更新图表
|
// 使用节流机制更新图表
|
||||||
@@ -2619,18 +2669,7 @@ function bindEvents() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化自定义时间输入框
|
|
||||||
const now = new Date();
|
|
||||||
const startTimeInput = document.getElementById('customStartTime');
|
|
||||||
const endTimeInput = document.getElementById('customEndTime');
|
|
||||||
if (startTimeInput && endTimeInput) {
|
|
||||||
// 默认显示过去24小时
|
|
||||||
const twentyFourHoursAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
|
||||||
|
|
||||||
// 显示本地时间格式,YYYY-MM-DDTHH:MM
|
|
||||||
startTimeInput.value = twentyFourHoursAgo.toISOString().slice(0, 16);
|
|
||||||
endTimeInput.value = now.toISOString().slice(0, 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 缩放控件事件处理
|
// 缩放控件事件处理
|
||||||
const zoomOutBtn = document.getElementById('zoomOutBtn');
|
const zoomOutBtn = document.getElementById('zoomOutBtn');
|
||||||
@@ -2722,27 +2761,49 @@ async function loadNetworkInterfaces() {
|
|||||||
// 更新当前时间范围显示
|
// 更新当前时间范围显示
|
||||||
const updateTimeRangeDisplay = () => {
|
const updateTimeRangeDisplay = () => {
|
||||||
let displayText = '';
|
let displayText = '';
|
||||||
switch(state.currentTimeRange) {
|
|
||||||
case '30m':
|
// 计算实际的开始时间和结束时间
|
||||||
displayText = '过去30分钟';
|
let startTime, endTime;
|
||||||
break;
|
|
||||||
case '1h':
|
if (state.customStartTime && state.customEndTime) {
|
||||||
displayText = '过去1小时';
|
// 使用自定义时间范围
|
||||||
break;
|
startTime = new Date(state.customStartTime);
|
||||||
case '2h':
|
endTime = new Date(state.customEndTime);
|
||||||
displayText = '过去2小时';
|
displayText = `${formatTime(state.customStartTime)} 至 ${formatTime(state.customEndTime)}`;
|
||||||
break;
|
} else {
|
||||||
case '6h':
|
// 使用预设时间范围
|
||||||
displayText = '过去6小时';
|
const now = new Date();
|
||||||
break;
|
endTime = now;
|
||||||
case '12h':
|
|
||||||
displayText = '过去12小时';
|
// 根据预设时间范围计算开始时间
|
||||||
break;
|
switch(state.currentTimeRange) {
|
||||||
case '24h':
|
case '30m':
|
||||||
displayText = '过去24小时';
|
startTime = new Date(now.getTime() - 30 * 60 * 1000);
|
||||||
break;
|
displayText = `${formatTime(startTime.toISOString())} 至 ${formatTime(endTime.toISOString())}`;
|
||||||
default:
|
break;
|
||||||
displayText = '自定义时间范围';
|
case '1h':
|
||||||
|
startTime = new Date(now.getTime() - 60 * 60 * 1000);
|
||||||
|
displayText = `${formatTime(startTime.toISOString())} 至 ${formatTime(endTime.toISOString())}`;
|
||||||
|
break;
|
||||||
|
case '2h':
|
||||||
|
startTime = new Date(now.getTime() - 2 * 60 * 60 * 1000);
|
||||||
|
displayText = `${formatTime(startTime.toISOString())} 至 ${formatTime(endTime.toISOString())}`;
|
||||||
|
break;
|
||||||
|
case '6h':
|
||||||
|
startTime = new Date(now.getTime() - 6 * 60 * 60 * 1000);
|
||||||
|
displayText = `${formatTime(startTime.toISOString())} 至 ${formatTime(endTime.toISOString())}`;
|
||||||
|
break;
|
||||||
|
case '12h':
|
||||||
|
startTime = new Date(now.getTime() - 12 * 60 * 60 * 1000);
|
||||||
|
displayText = `${formatTime(startTime.toISOString())} 至 ${formatTime(endTime.toISOString())}`;
|
||||||
|
break;
|
||||||
|
case '24h':
|
||||||
|
startTime = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
||||||
|
displayText = `${formatTime(startTime.toISOString())} 至 ${formatTime(endTime.toISOString())}`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
displayText = '自定义时间范围';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新所有图表的时间范围显示
|
// 更新所有图表的时间范围显示
|
||||||
@@ -2897,11 +2958,15 @@ function initChartTabs() {
|
|||||||
const activeContainer = document.getElementById(`${tabId}ChartContainer`);
|
const activeContainer = document.getElementById(`${tabId}ChartContainer`);
|
||||||
if (activeContainer) {
|
if (activeContainer) {
|
||||||
activeContainer.classList.remove('hidden');
|
activeContainer.classList.remove('hidden');
|
||||||
|
console.log(`Shown chart container: ${tabId}ChartContainer`);
|
||||||
|
} else {
|
||||||
|
console.error(`Chart container not found: ${tabId}ChartContainer`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示/隐藏进程信息和磁盘详细信息
|
// 显示/隐藏进程信息、磁盘详细信息和系统日志
|
||||||
const processInfoContainer = document.getElementById('processInfoContainer');
|
const processInfoContainer = document.getElementById('processInfoContainer');
|
||||||
const diskDetailsContainer = document.getElementById('diskDetailsContainer');
|
const diskDetailsContainer = document.getElementById('diskDetailsContainer');
|
||||||
|
const logInfoContainer = document.getElementById('logInfoContainer');
|
||||||
|
|
||||||
// 隐藏所有附加信息容器
|
// 隐藏所有附加信息容器
|
||||||
if (processInfoContainer) {
|
if (processInfoContainer) {
|
||||||
@@ -2910,6 +2975,9 @@ function initChartTabs() {
|
|||||||
if (diskDetailsContainer) {
|
if (diskDetailsContainer) {
|
||||||
diskDetailsContainer.classList.add('hidden');
|
diskDetailsContainer.classList.add('hidden');
|
||||||
}
|
}
|
||||||
|
if (logInfoContainer) {
|
||||||
|
logInfoContainer.classList.add('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
// 根据选项卡显示相应的附加信息
|
// 根据选项卡显示相应的附加信息
|
||||||
if (tabId === 'cpu') {
|
if (tabId === 'cpu') {
|
||||||
@@ -2926,6 +2994,13 @@ function initChartTabs() {
|
|||||||
// 加载磁盘详细信息
|
// 加载磁盘详细信息
|
||||||
loadDiskDetails();
|
loadDiskDetails();
|
||||||
}
|
}
|
||||||
|
} else if (tabId === 'logs') {
|
||||||
|
// 显示系统日志
|
||||||
|
if (logInfoContainer) {
|
||||||
|
logInfoContainer.classList.remove('hidden');
|
||||||
|
// 加载系统日志
|
||||||
|
loadSystemLogs();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示/隐藏网卡选择下拉框
|
// 显示/隐藏网卡选择下拉框
|
||||||
@@ -2941,15 +3016,64 @@ function initChartTabs() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// 初始状态:根据当前选中的选项卡显示/隐藏网卡选择下拉框
|
// 初始状态:根据当前选中的选项卡显示/隐藏网卡选择下拉框和加载数据
|
||||||
const activeTab = document.querySelector('.chart-tab.active');
|
const activeTab = document.querySelector('.chart-tab.active');
|
||||||
const interfaceContainer = document.getElementById('interfaceSelectorContainer');
|
const interfaceContainer = document.getElementById('interfaceSelectorContainer');
|
||||||
if (activeTab && interfaceContainer) {
|
if (activeTab) {
|
||||||
const tabId = activeTab.dataset.tab;
|
const tabId = activeTab.dataset.tab;
|
||||||
if (tabId === 'network' || tabId === 'speed') {
|
|
||||||
interfaceContainer.classList.remove('hidden');
|
// 显示当前选中的图表容器
|
||||||
|
const activeContainer = document.getElementById(`${tabId}ChartContainer`);
|
||||||
|
if (activeContainer) {
|
||||||
|
activeContainer.classList.remove('hidden');
|
||||||
} else {
|
} else {
|
||||||
interfaceContainer.classList.add('hidden');
|
console.error(`Initial chart container not found: ${tabId}ChartContainer`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示/隐藏进程信息、磁盘详细信息和系统日志
|
||||||
|
const processInfoContainer = document.getElementById('processInfoContainer');
|
||||||
|
const diskDetailsContainer = document.getElementById('diskDetailsContainer');
|
||||||
|
const logInfoContainer = document.getElementById('logInfoContainer');
|
||||||
|
|
||||||
|
// 隐藏所有附加信息容器
|
||||||
|
if (processInfoContainer) {
|
||||||
|
processInfoContainer.classList.add('hidden');
|
||||||
|
}
|
||||||
|
if (diskDetailsContainer) {
|
||||||
|
diskDetailsContainer.classList.add('hidden');
|
||||||
|
}
|
||||||
|
if (logInfoContainer) {
|
||||||
|
logInfoContainer.classList.add('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据选项卡显示相应的附加信息
|
||||||
|
if (tabId === 'cpu') {
|
||||||
|
// 显示进程信息
|
||||||
|
if (processInfoContainer) {
|
||||||
|
processInfoContainer.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
loadProcessInfo();
|
||||||
|
} else if (tabId === 'disk') {
|
||||||
|
// 显示磁盘详细信息
|
||||||
|
if (diskDetailsContainer) {
|
||||||
|
diskDetailsContainer.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
loadDiskDetails();
|
||||||
|
} else if (tabId === 'logs') {
|
||||||
|
// 显示系统日志
|
||||||
|
if (logInfoContainer) {
|
||||||
|
logInfoContainer.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
loadSystemLogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示/隐藏网卡选择下拉框
|
||||||
|
if (interfaceContainer) {
|
||||||
|
if (tabId === 'network' || tabId === 'speed') {
|
||||||
|
interfaceContainer.classList.remove('hidden');
|
||||||
|
} else {
|
||||||
|
interfaceContainer.classList.add('hidden');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2974,7 +3098,18 @@ let processPagination = {
|
|||||||
itemsPerPage: 5,
|
itemsPerPage: 5,
|
||||||
totalItems: 0,
|
totalItems: 0,
|
||||||
totalPages: 0,
|
totalPages: 0,
|
||||||
allProcesses: []
|
allProcesses: [],
|
||||||
|
lastDeviceID: '' // 上次请求数据的设备ID
|
||||||
|
};
|
||||||
|
|
||||||
|
// 系统日志分页状态
|
||||||
|
let logPagination = {
|
||||||
|
currentPage: 1,
|
||||||
|
itemsPerPage: 5,
|
||||||
|
totalItems: 0,
|
||||||
|
totalPages: 0,
|
||||||
|
allLogs: [],
|
||||||
|
lastDeviceID: '' // 上次请求数据的设备ID
|
||||||
};
|
};
|
||||||
|
|
||||||
// 加载进程信息
|
// 加载进程信息
|
||||||
@@ -2991,7 +3126,16 @@ async function loadProcessInfo(page = 1) {
|
|||||||
params.append('device_id', state.currentDeviceID);
|
params.append('device_id', state.currentDeviceID);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果是第一次加载,获取全部数据
|
// 检查设备ID是否变化
|
||||||
|
if (processPagination.lastDeviceID !== state.currentDeviceID) {
|
||||||
|
// 设备ID变化,清空旧数据
|
||||||
|
processPagination.allProcesses = [];
|
||||||
|
processPagination.totalItems = 0;
|
||||||
|
processPagination.totalPages = 0;
|
||||||
|
processPagination.currentPage = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是第一次加载或设备ID变化,获取全部数据
|
||||||
if (processPagination.allProcesses.length === 0) {
|
if (processPagination.allProcesses.length === 0) {
|
||||||
// 发送请求
|
// 发送请求
|
||||||
const response = await fetch(`${API_BASE_URL}/metrics/processes?${params.toString()}`);
|
const response = await fetch(`${API_BASE_URL}/metrics/processes?${params.toString()}`);
|
||||||
@@ -3003,6 +3147,8 @@ async function loadProcessInfo(page = 1) {
|
|||||||
processPagination.allProcesses = data.data || [];
|
processPagination.allProcesses = data.data || [];
|
||||||
processPagination.totalItems = processPagination.allProcesses.length;
|
processPagination.totalItems = processPagination.allProcesses.length;
|
||||||
processPagination.totalPages = Math.ceil(processPagination.totalItems / processPagination.itemsPerPage);
|
processPagination.totalPages = Math.ceil(processPagination.totalItems / processPagination.itemsPerPage);
|
||||||
|
// 更新上次请求数据的设备ID
|
||||||
|
processPagination.lastDeviceID = state.currentDeviceID;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新当前页码
|
// 更新当前页码
|
||||||
@@ -3072,7 +3218,8 @@ let diskPagination = {
|
|||||||
itemsPerPage: 5,
|
itemsPerPage: 5,
|
||||||
totalItems: 0,
|
totalItems: 0,
|
||||||
totalPages: 0,
|
totalPages: 0,
|
||||||
allDisks: []
|
allDisks: [],
|
||||||
|
lastDeviceID: '' // 上次请求数据的设备ID
|
||||||
};
|
};
|
||||||
|
|
||||||
// 创建分页控件
|
// 创建分页控件
|
||||||
@@ -3221,7 +3368,16 @@ async function loadDiskDetails(page = 1) {
|
|||||||
params.append('device_id', state.currentDeviceID);
|
params.append('device_id', state.currentDeviceID);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果是第一次加载,获取全部数据
|
// 检查设备ID是否变化
|
||||||
|
if (diskPagination.lastDeviceID !== state.currentDeviceID) {
|
||||||
|
// 设备ID变化,清空旧数据
|
||||||
|
diskPagination.allDisks = [];
|
||||||
|
diskPagination.totalItems = 0;
|
||||||
|
diskPagination.totalPages = 0;
|
||||||
|
diskPagination.currentPage = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是第一次加载或设备ID变化,获取全部数据
|
||||||
if (diskPagination.allDisks.length === 0) {
|
if (diskPagination.allDisks.length === 0) {
|
||||||
// 发送请求
|
// 发送请求
|
||||||
const response = await fetch(`${API_BASE_URL}/metrics/disk_details?${params.toString()}`);
|
const response = await fetch(`${API_BASE_URL}/metrics/disk_details?${params.toString()}`);
|
||||||
@@ -3233,6 +3389,8 @@ async function loadDiskDetails(page = 1) {
|
|||||||
diskPagination.allDisks = data.data || [];
|
diskPagination.allDisks = data.data || [];
|
||||||
diskPagination.totalItems = diskPagination.allDisks.length;
|
diskPagination.totalItems = diskPagination.allDisks.length;
|
||||||
diskPagination.totalPages = Math.ceil(diskPagination.totalItems / diskPagination.itemsPerPage);
|
diskPagination.totalPages = Math.ceil(diskPagination.totalItems / diskPagination.itemsPerPage);
|
||||||
|
// 更新上次请求数据的设备ID
|
||||||
|
diskPagination.lastDeviceID = state.currentDeviceID;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新当前页码
|
// 更新当前页码
|
||||||
@@ -3319,9 +3477,15 @@ async function loadDiskDetails(page = 1) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 加载系统日志
|
// 加载系统日志
|
||||||
async function loadSystemLogs() {
|
async function loadSystemLogs(page = 1) {
|
||||||
|
console.log('loadSystemLogs function called');
|
||||||
const logTableBody = document.getElementById('logTableBody');
|
const logTableBody = document.getElementById('logTableBody');
|
||||||
if (!logTableBody) return;
|
const logPaginationContainer = document.getElementById('logPaginationContainer');
|
||||||
|
|
||||||
|
if (!logTableBody) {
|
||||||
|
console.error('logTableBody element not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 构建查询参数
|
// 构建查询参数
|
||||||
@@ -3330,46 +3494,86 @@ async function loadSystemLogs() {
|
|||||||
params.append('device_id', state.currentDeviceID);
|
params.append('device_id', state.currentDeviceID);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送请求
|
// 检查设备ID是否变化
|
||||||
const response = await fetch(`${API_BASE_URL}/metrics/logs?${params.toString()}`);
|
if (logPagination.lastDeviceID !== state.currentDeviceID) {
|
||||||
if (!response.ok) {
|
// 设备ID变化,清空旧数据
|
||||||
throw new Error('Failed to fetch system logs');
|
logPagination.allLogs = [];
|
||||||
|
logPagination.totalItems = 0;
|
||||||
|
logPagination.totalPages = 0;
|
||||||
|
logPagination.currentPage = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
// 如果是第一次加载或设备ID变化,获取全部数据
|
||||||
const logs = data.data;
|
if (logPagination.allLogs.length === 0) {
|
||||||
|
console.log('Fetching logs from:', `${API_BASE_URL}/metrics/logs?${params.toString()}`);
|
||||||
|
// 发送请求
|
||||||
|
const response = await fetch(`${API_BASE_URL}/metrics/logs?${params.toString()}`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
logPagination.allLogs = data.data || [];
|
||||||
|
logPagination.totalItems = logPagination.allLogs.length;
|
||||||
|
logPagination.totalPages = Math.ceil(logPagination.totalItems / logPagination.itemsPerPage);
|
||||||
|
// 更新上次请求数据的设备ID
|
||||||
|
logPagination.lastDeviceID = state.currentDeviceID;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新当前页码
|
||||||
|
logPagination.currentPage = page;
|
||||||
|
|
||||||
// 清空表格
|
// 清空表格
|
||||||
logTableBody.innerHTML = '';
|
logTableBody.innerHTML = '';
|
||||||
|
|
||||||
if (logs.length === 0) {
|
if (logPagination.totalItems === 0) {
|
||||||
// 没有日志数据,显示提示
|
// 没有日志数据,显示提示
|
||||||
logTableBody.innerHTML = `
|
logTableBody.innerHTML = `
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="4" class="px-6 py-4 text-center text-gray-500">暂无系统日志</td>
|
<td colspan="4" class="px-6 py-4 text-center text-gray-500">暂无系统日志</td>
|
||||||
</tr>
|
</tr>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
// 隐藏分页控件
|
||||||
|
if (logPaginationContainer) {
|
||||||
|
logPaginationContainer.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 计算分页数据
|
||||||
|
const startIndex = (logPagination.currentPage - 1) * logPagination.itemsPerPage;
|
||||||
|
const endIndex = Math.min(startIndex + logPagination.itemsPerPage, logPagination.totalItems);
|
||||||
|
const paginatedLogs = logPagination.allLogs.slice(startIndex, endIndex);
|
||||||
|
|
||||||
// 填充表格数据
|
// 填充表格数据
|
||||||
logs.forEach((log, index) => {
|
paginatedLogs.forEach((log, index) => {
|
||||||
const row = document.createElement('tr');
|
const row = document.createElement('tr');
|
||||||
row.innerHTML = `
|
row.innerHTML = `
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${index + 1}</td>
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${startIndex + index + 1}</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 font-medium">${log.source || 'System'}</td>
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 font-medium">${log.source || 'System'}</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${log.time || new Date().toLocaleString()}</td>
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${log.time || new Date().toLocaleString()}</td>
|
||||||
<td class="px-6 py-4 whitespace-normal text-sm text-gray-500">${log.message || 'No message'}</td>
|
<td class="px-6 py-4 whitespace-normal text-sm text-gray-500">${log.message || 'No message'}</td>
|
||||||
`;
|
`;
|
||||||
logTableBody.appendChild(row);
|
logTableBody.appendChild(row);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 创建分页控件
|
||||||
|
if (logPaginationContainer) {
|
||||||
|
createPaginationControls(logPaginationContainer, logPagination, loadSystemLogs);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading system logs:', error);
|
console.error('Error loading system logs:', error);
|
||||||
logTableBody.innerHTML = `
|
logTableBody.innerHTML = `
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="4" class="px-6 py-4 text-center text-red-500">加载系统日志失败</td>
|
<td colspan="4" class="px-6 py-4 text-center text-red-500">加载系统日志失败: ${error.message}</td>
|
||||||
</tr>
|
</tr>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
if (logPaginationContainer) {
|
||||||
|
logPaginationContainer.innerHTML = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3379,11 +3583,56 @@ function handleHashChange() {
|
|||||||
if (hash === '#serverMonitor' || hash.startsWith('#serverMonitor/')) {
|
if (hash === '#serverMonitor' || hash.startsWith('#serverMonitor/')) {
|
||||||
// 延迟一下,确保DOM已经渲染完成
|
// 延迟一下,确保DOM已经渲染完成
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
loadMetrics();
|
// 加载当前选项卡对应的数据
|
||||||
// 加载进程信息(如果当前是CPU选项卡)
|
|
||||||
const activeTab = document.querySelector('.chart-tab.active');
|
const activeTab = document.querySelector('.chart-tab.active');
|
||||||
if (activeTab && activeTab.dataset.tab === 'cpu') {
|
if (activeTab) {
|
||||||
loadProcessInfo();
|
const tabId = activeTab.dataset.tab;
|
||||||
|
|
||||||
|
// 显示/隐藏附加信息容器
|
||||||
|
const processInfoContainer = document.getElementById('processInfoContainer');
|
||||||
|
const diskDetailsContainer = document.getElementById('diskDetailsContainer');
|
||||||
|
const logInfoContainer = document.getElementById('logInfoContainer');
|
||||||
|
|
||||||
|
// 隐藏所有附加信息容器
|
||||||
|
if (processInfoContainer) {
|
||||||
|
processInfoContainer.classList.add('hidden');
|
||||||
|
}
|
||||||
|
if (diskDetailsContainer) {
|
||||||
|
diskDetailsContainer.classList.add('hidden');
|
||||||
|
}
|
||||||
|
if (logInfoContainer) {
|
||||||
|
logInfoContainer.classList.add('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tabId === 'logs') {
|
||||||
|
// 显示系统日志
|
||||||
|
if (logInfoContainer) {
|
||||||
|
logInfoContainer.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
// 加载系统日志
|
||||||
|
loadSystemLogs();
|
||||||
|
} else {
|
||||||
|
// 加载其他监控数据
|
||||||
|
loadMetrics();
|
||||||
|
|
||||||
|
// 根据选项卡加载附加信息
|
||||||
|
if (tabId === 'cpu') {
|
||||||
|
// 显示进程信息
|
||||||
|
if (processInfoContainer) {
|
||||||
|
processInfoContainer.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
loadProcessInfo();
|
||||||
|
} else if (tabId === 'disk') {
|
||||||
|
// 显示磁盘详细信息
|
||||||
|
if (diskDetailsContainer) {
|
||||||
|
diskDetailsContainer.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
loadDiskDetails();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果没有找到激活的选项卡,默认加载metrics
|
||||||
|
loadMetrics();
|
||||||
}
|
}
|
||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
|
|||||||
10
test_agent.json
Normal file
10
test_agent.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"server_url": "http://localhost:8080/api",
|
||||||
|
"id": "test-agent",
|
||||||
|
"name": "Test Agent",
|
||||||
|
"device_id": "test-device",
|
||||||
|
"token": "bb30bfaee01bf7b541bbefe422f72645",
|
||||||
|
"interval": "5s",
|
||||||
|
"debug": true,
|
||||||
|
"api_port": 8081
|
||||||
|
}
|
||||||
11
test_metrics.sh
Executable file
11
test_metrics.sh
Executable file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 测试发送单个指标对象
|
||||||
|
echo "测试发送单个指标对象:"
|
||||||
|
curl -v -X POST -H "Content-Type: application/json" -H "X-Device-ID: test-device" -H "X-Agent-Name: test-agent" -H "X-Device-Token: test-token" -d '{"cpu": 50.5, "memory": 30.2, "disk": {":/": 45.6}, "network": {"eth0": {"bytes_sent": 1000, "bytes_received": 2000}}}' http://localhost:8080/api/metrics/
|
||||||
|
|
||||||
|
echo "\n---\n"
|
||||||
|
|
||||||
|
# 测试发送指标数组
|
||||||
|
echo "测试发送指标数组:"
|
||||||
|
curl -v -X POST -H "Content-Type: application/json" -H "X-Device-ID: test-device" -H "X-Agent-Name: test-agent" -H "X-Device-Token: test-token" -d '[{"cpu": 50.5, "memory": 30.2, "disk": {":/": 45.6}, "network": {"eth0": {"bytes_sent": 1000, "bytes_received": 2000}}}, {"cpu": 60.5, "memory": 40.2, "disk": {":/": 55.6}, "network": {"eth0": {"bytes_sent": 2000, "bytes_received": 3000}}}]' http://localhost:8080/api/metrics/
|
||||||
Reference in New Issue
Block a user