新增Windows代理程序支持

This commit is contained in:
Alex Yang
2025-12-07 18:15:50 +08:00
parent ccc43fe70e
commit 2efe02682c
16 changed files with 4392 additions and 115 deletions

BIN
agent-windows/agent-windows.exe Executable file

Binary file not shown.

2
agent-windows/build.sh Normal file
View File

@@ -0,0 +1,2 @@
GOOS=windows GOARCH=amd64
go build -v -o agent-windows.exe

19
agent-windows/go.mod Normal file
View File

@@ -0,0 +1,19 @@
module github.com/monitor/agent-windows
go 1.24.0
toolchain go1.24.10
require (
github.com/shirou/gopsutil v3.21.11+incompatible
golang.org/x/text v0.31.0
)
require (
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/stretchr/testify v1.11.1 // indirect
github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/sys v0.38.0 // indirect
)

23
agent-windows/go.sum Normal file
View File

@@ -0,0 +1,23 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

1256
agent-windows/main.go Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -48,35 +48,18 @@ start_agent() {
return 0 return 0
fi fi
echo "🚀 正在启动 monitor-agent(工作目录:${WORK_DIR}..." echo "🚀 正在启动 monitor-agent..."
# 创建日志目录(如果不存在)
# 新增:检查并切换工作目录
if [ ! -d "${WORK_DIR}" ]; then
echo "⚠️ 工作目录 ${WORK_DIR} 不存在,正在创建..."
mkdir -p "${WORK_DIR}"
if [ $? -ne 0 ]; then
echo "❌ 创建工作目录 ${WORK_DIR} 失败!"
exit 1
fi
fi
# 切换到工作目录(关键:程序将在此目录下运行)
cd "${WORK_DIR}" || {
echo "❌ 切换到工作目录 ${WORK_DIR} 失败!"
exit 1
}
# 创建日志目录
mkdir -p "$(dirname ${LOG_FILE})" mkdir -p "$(dirname ${LOG_FILE})"
# 后台启动程序注意cd仅影响当前子进程需在同一行执行 # 后台启动程序重定向日志记录PID
nohup "${AGENT_PATH}" ${START_ARGS} > "${LOG_FILE}" 2>&1 & nohup "${AGENT_PATH}" ${START_ARGS} > "${LOG_FILE}" 2>&1 &
AGENT_PID=$! AGENT_PID=$!
echo "${AGENT_PID}" > "${PID_FILE}" echo "${AGENT_PID}" > "${PID_FILE}"
# 等待检查启动状态 # 等待2秒检查是否启动成功
sleep 2 sleep 2
if check_running; then if check_running; then
echo "✅ monitor-agent 启动成功PID: ${AGENT_PID},工作目录:${WORK_DIR}" echo "✅ monitor-agent 启动成功PID: ${AGENT_PID}"
echo "日志文件:${LOG_FILE}" echo "日志文件:${LOG_FILE}"
else else
echo "❌ monitor-agent 启动失败!请查看日志:${LOG_FILE}" echo "❌ monitor-agent 启动失败!请查看日志:${LOG_FILE}"

View File

@@ -511,6 +511,18 @@ func HandleMetricsPost(c *gin.Context) {
var globalStorage *storage.Storage var globalStorage *storage.Storage
var deviceStorage device.Storage var deviceStorage device.Storage
// 硬件信息缓存结构
type hardwareCacheItem struct {
Data map[string]interface{}
ExpiresAt time.Time
}
// 硬件信息缓存
var (
hardwareCache sync.Map
cacheDuration = 5 * time.Minute // 缓存过期时间5分钟
)
// SetStorage 设置全局存储实例 // SetStorage 设置全局存储实例
func SetStorage(s *storage.Storage) { func SetStorage(s *storage.Storage) {
globalStorage = s globalStorage = s
@@ -1275,12 +1287,35 @@ func GetHardwareMetrics(c *gin.Context) {
return return
} }
// 从存储中查询硬件信息 // 尝试从缓存中获取硬件信息
hardwareInfo, err := globalStorage.QueryHardwareMetrics(context.Background(), deviceID) var hardwareInfo map[string]interface{}
var err error
if cached, ok := hardwareCache.Load(deviceID); ok {
cacheItem := cached.(hardwareCacheItem)
if time.Now().Before(cacheItem.ExpiresAt) {
// 缓存有效
hardwareInfo = cacheItem.Data
} else {
// 缓存过期,删除并重新查询
hardwareCache.Delete(deviceID)
hardwareInfo, err = globalStorage.QueryHardwareMetrics(context.Background(), deviceID)
}
} else {
// 缓存未命中,查询数据库
hardwareInfo, err = globalStorage.QueryHardwareMetrics(context.Background(), deviceID)
}
if err != nil { if err != nil {
log.Printf("Warning: Failed to query hardware metrics: %v", err) log.Printf("Warning: Failed to query hardware metrics: %v", err)
// 查询失败时,返回一个空的硬件信息对象 // 查询失败时,返回一个空的硬件信息对象
hardwareInfo = make(map[string]interface{}) hardwareInfo = make(map[string]interface{})
} else if len(hardwareInfo) > 0 {
// 查询成功且有数据,更新缓存
hardwareCache.Store(deviceID, hardwareCacheItem{
Data: hardwareInfo,
ExpiresAt: time.Now().Add(cacheDuration),
})
} }
// 为了确保返回的结构与前端期望的一致,我们需要将查询结果转换为期望的格式 // 为了确保返回的结构与前端期望的一致,我们需要将查询结果转换为期望的格式

View File

@@ -484,18 +484,19 @@ func (s *Storage) QueryHardwareMetrics(ctx context.Context, deviceID string) (ma
if hardwareType == "os" || hardwareType == "cpu" || hardwareType == "memory" { if hardwareType == "os" || hardwareType == "cpu" || hardwareType == "memory" {
// 对于os、cpu、memory类型我们只需要一个结果 // 对于os、cpu、memory类型我们只需要一个结果
query = `from(bucket: "` + s.bucket + `") query = `from(bucket: "` + s.bucket + `")
|> range(start: -24h) |> range(start: -24h) // 扩大查询范围到最近24小时
|> filter(fn: (r) => r["_measurement"] == "hardware") |> filter(fn: (r) => r["_measurement"] == "hardware")
|> filter(fn: (r) => r["device_id"] == "` + deviceID + `") |> filter(fn: (r) => r["device_id"] == "` + deviceID + `")
|> filter(fn: (r) => r["type"] == "` + hardwareType + `") |> filter(fn: (r) => r["type"] == "` + hardwareType + `")
|> last()` |> last()`
} else { } else {
// 对于disk和network类型我们需要获取所有设备的所有字段记录 // 对于disk和network类型我们需要获取所有设备的最新完整记录
// 先获取所有记录,然后按时间排序,最后只保留最新的记录
query = `from(bucket: "` + s.bucket + `") query = `from(bucket: "` + s.bucket + `")
|> range(start: -24h) |> range(start: -24h) // 扩大查询范围到最近24小时
|> filter(fn: (r) => r["_measurement"] == "hardware") |> filter(fn: (r) => r["_measurement"] == "hardware")
|> filter(fn: (r) => r["device_id"] == "` + deviceID + `") |> filter(fn: (r) => r["device_id"] == "` + deviceID + `")
|> filter(fn: (r) => r["type"] == "` + hardwareType + `")` |> filter(fn: (r) => r["type"] == "` + hardwareType + `")`
} }
// 执行查询 // 执行查询
@@ -517,7 +518,14 @@ func (s *Storage) QueryHardwareMetrics(ctx context.Context, deviceID string) (ma
record := queryResult.Record() record := queryResult.Record()
fieldName := record.Field() fieldName := record.Field()
fieldValue := record.Value() fieldValue := record.Value()
fieldMap[fieldName] = fieldValue
// 处理布尔类型的值转换为字符串以避免InfluxDB客户端错误
switch v := fieldValue.(type) {
case bool:
fieldMap[fieldName] = fmt.Sprintf("%t", v)
default:
fieldMap[fieldName] = fieldValue
}
} }
// 如果有字段数据,添加到硬件信息结果中 // 如果有字段数据,添加到硬件信息结果中
@@ -543,38 +551,53 @@ func (s *Storage) QueryHardwareMetrics(ctx context.Context, deviceID string) (ma
// 获取设备唯一标识 // 获取设备唯一标识
deviceKey := "" deviceKey := ""
// 优先使用id字段作为设备标识
if id, isString := fieldValue.(string); fieldName == "id" && isString { // 首先从字段中查找id
deviceKey = id if fieldName == "id" {
if id, isString := fieldValue.(string); isString {
deviceKey = id
} else if id, isFloat := fieldValue.(float64); isFloat {
deviceKey = fmt.Sprintf("%d", int(id))
}
// 创建设备字段映射 // 创建设备字段映射
deviceFields[deviceKey] = make(map[string]interface{}) if deviceKey != "" {
deviceFields[deviceKey]["id"] = id deviceFields[deviceKey] = make(map[string]interface{})
} else { deviceFields[deviceKey]["id"] = deviceKey
// 否则,查找已存在的设备映射
// 遍历所有设备映射,查找当前记录对应的设备
for key, fields := range deviceFields {
// 如果设备映射中没有id字段或者id字段为空跳过
if id, ok := fields["id"].(string); ok && id != "" {
// 假设当前记录属于该设备
deviceKey = key
break
}
} }
} }
// 如果没有找到设备标识,使用index作为设备标识 // 如果没有通过id字段找到设备标识,尝试从index获取
if deviceKey == "" { if deviceKey == "" {
index := 0 if index, ok := record.ValueByKey("index").(int); ok {
if idx, ok := record.ValueByKey("index").(int); ok { deviceKey = fmt.Sprintf("%d", index)
index = idx // 创建设备字段映射
deviceFields[deviceKey] = make(map[string]interface{})
}
}
// 如果仍然没有设备标识,查找已存在的设备映射
if deviceKey == "" {
// 遍历所有设备映射,查找当前记录对应的设备
for key := range deviceFields {
deviceKey = key
break
}
// 如果没有找到任何设备映射,创建一个新的
if deviceKey == "" {
deviceKey = "0" // 默认使用0作为第一个设备的标识
deviceFields[deviceKey] = make(map[string]interface{})
} }
deviceKey = fmt.Sprintf("%d", index)
// 创建设备字段映射
deviceFields[deviceKey] = make(map[string]interface{})
} }
// 添加字段值到设备字段映射中 // 添加字段值到设备字段映射中
deviceFields[deviceKey][fieldName] = fieldValue // 处理布尔类型的值转换为字符串以避免InfluxDB客户端错误
switch v := fieldValue.(type) {
case bool:
deviceFields[deviceKey][fieldName] = fmt.Sprintf("%t", v)
default:
deviceFields[deviceKey][fieldName] = fieldValue
}
} }
// 将deviceFields转换为切片 // 将deviceFields转换为切片

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -3,15 +3,13 @@
# ===================== 配置区 ===================== # ===================== 配置区 =====================
# 程序路径 # 程序路径
AGENT_PATH="/path/to/monitor-server" AGENT_PATH="/root/monitor/monitor-agent"
# 日志文件路径 # 日志文件路径
LOG_FILE="/path/to/server.log" LOG_FILE="/root/monitor/agent.log"
# PID文件路径记录进程ID # PID文件路径记录进程ID
PID_FILE="/pat/to/server.pid" PID_FILE="/root/monitor/agent.pid"
# 启动参数(根据实际需求调整) # 启动参数(根据实际需求调整)
START_ARGS="" START_ARGS=""
# 工作目录
WORK_DIR=""
# ==================== 配置区结束 ==================== # ==================== 配置区结束 ====================
# 检查程序文件是否存在 # 检查程序文件是否存在
@@ -50,35 +48,18 @@ start_agent() {
return 0 return 0
fi fi
echo "🚀 正在启动 monitor-agent(工作目录:${WORK_DIR}..." echo "🚀 正在启动 monitor-agent..."
# 创建日志目录(如果不存在)
# 新增:检查并切换工作目录
if [ ! -d "${WORK_DIR}" ]; then
echo "⚠️ 工作目录 ${WORK_DIR} 不存在,正在创建..."
mkdir -p "${WORK_DIR}"
if [ $? -ne 0 ]; then
echo "❌ 创建工作目录 ${WORK_DIR} 失败!"
exit 1
fi
fi
# 切换到工作目录(关键:程序将在此目录下运行)
cd "${WORK_DIR}" || {
echo "❌ 切换到工作目录 ${WORK_DIR} 失败!"
exit 1
}
# 创建日志目录
mkdir -p "$(dirname ${LOG_FILE})" mkdir -p "$(dirname ${LOG_FILE})"
# 后台启动程序注意cd仅影响当前子进程需在同一行执行 # 后台启动程序重定向日志记录PID
nohup "${AGENT_PATH}" ${START_ARGS} > "${LOG_FILE}" 2>&1 & nohup "${AGENT_PATH}" ${START_ARGS} > "${LOG_FILE}" 2>&1 &
AGENT_PID=$! AGENT_PID=$!
echo "${AGENT_PID}" > "${PID_FILE}" echo "${AGENT_PID}" > "${PID_FILE}"
# 等待检查启动状态 # 等待2秒检查是否启动成功
sleep 2 sleep 2
if check_running; then if check_running; then
echo "✅ monitor-agent 启动成功PID: ${AGENT_PID},工作目录:${WORK_DIR}" echo "✅ monitor-agent 启动成功PID: ${AGENT_PID}"
echo "日志文件:${LOG_FILE}" echo "日志文件:${LOG_FILE}"
else else
echo "❌ monitor-agent 启动失败!请查看日志:${LOG_FILE}" echo "❌ monitor-agent 启动失败!请查看日志:${LOG_FILE}"

View File

@@ -81,6 +81,17 @@
<i class="fa fa-cog"></i> <i class="fa fa-cog"></i>
设备管理 设备管理
</a> </a>
<div class="relative group">
<a href="#alarms" class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-all duration-200 flex items-center gap-2 text-sm font-medium">
<i class="fa fa-bell"></i>
告警
</a>
<!-- 悬停菜单 -->
<div class="absolute left-0 mt-1 w-48 bg-white rounded-md shadow-lg py-1 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-20">
<a href="#alarms" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">告警管理</a>
<a href="#alarm-info" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">告警详情</a>
</div>
</div>
</nav> </nav>
</div> </div>
</header> </header>
@@ -106,7 +117,7 @@
<div class="flex justify-between items-start"> <div class="flex justify-between items-start">
<div> <div>
<p class="text-sm text-gray-500 font-medium mb-1">业务系统</p> <p class="text-sm text-gray-500 font-medium mb-1">业务系统</p>
<h3 class="text-3xl font-bold text-gray-900 metric-value">355</h3> <h3 class="text-3xl font-bold text-gray-900 metric-value">---</h3>
</div> </div>
<div class="bg-blue-100 p-3 rounded-full"> <div class="bg-blue-100 p-3 rounded-full">
<i class="fa fa-building text-blue-600 text-xl"></i> <i class="fa fa-building text-blue-600 text-xl"></i>
@@ -119,7 +130,7 @@
<div class="flex justify-between items-start"> <div class="flex justify-between items-start">
<div> <div>
<p class="text-sm text-gray-500 font-medium mb-1">数据库</p> <p class="text-sm text-gray-500 font-medium mb-1">数据库</p>
<h3 class="text-3xl font-bold text-gray-900 metric-value">59</h3> <h3 class="text-3xl font-bold text-gray-900 metric-value">---</h3>
</div> </div>
<div class="bg-yellow-100 p-3 rounded-full"> <div class="bg-yellow-100 p-3 rounded-full">
<i class="fa fa-database text-yellow-600 text-xl"></i> <i class="fa fa-database text-yellow-600 text-xl"></i>
@@ -132,7 +143,7 @@
<div class="flex justify-between items-start"> <div class="flex justify-between items-start">
<div> <div>
<p class="text-sm text-gray-500 font-medium mb-1">服务器</p> <p class="text-sm text-gray-500 font-medium mb-1">服务器</p>
<h3 id="serverCount" class="text-3xl font-bold text-gray-900 metric-value">2</h3> <h3 id="serverCount" class="text-3xl font-bold text-gray-900 metric-value">---</h3>
</div> </div>
<div class="bg-green-100 p-3 rounded-full"> <div class="bg-green-100 p-3 rounded-full">
<i class="fa fa-server text-green-600 text-xl"></i> <i class="fa fa-server text-green-600 text-xl"></i>
@@ -141,11 +152,11 @@
</div> </div>
<!-- 告警 --> <!-- 告警 -->
<div class="bg-white rounded-xl shadow-md p-6 card-hover border border-gray-100"> <div id="alarmCard" class="bg-white rounded-xl shadow-md p-6 card-hover border border-gray-100 cursor-pointer" onclick="window.location.hash='#alarms'">
<div class="flex justify-between items-start"> <div class="flex justify-between items-start">
<div> <div>
<p class="text-sm text-gray-500 font-medium mb-1">告警总数</p> <p class="text-sm text-gray-500 font-medium mb-1">告警总数</p>
<h3 class="text-3xl font-bold text-red-600 metric-value">112</h3> <h3 id="alarmCount" class="text-3xl font-bold text-red-600 metric-value">---</h3>
</div> </div>
<div class="bg-red-100 p-3 rounded-full"> <div class="bg-red-100 p-3 rounded-full">
<i class="fa fa-bell text-red-600 text-xl"></i> <i class="fa fa-bell text-red-600 text-xl"></i>
@@ -573,6 +584,137 @@
</div> </div>
</div> </div>
</div> </div>
<!-- 告警管理 -->
<div id="alarmsContent" class="hidden">
<div class="bg-white rounded-xl shadow-md p-6 mb-6">
<h2 class="text-xl font-bold text-gray-900 mb-6">告警管理</h2>
<!-- 搜索和过滤功能 -->
<div class="flex flex-wrap gap-4 mb-6">
<div class="flex items-center gap-2">
<input type="text" id="alarmSearchInput" placeholder="搜索告警..." class="px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent w-full md:w-64">
<select id="alarmLevelFilter" class="px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent">
<option value="">所有级别</option>
<option value="info">信息</option>
<option value="warning">警告</option>
<option value="error">错误</option>
<option value="critical">严重</option>
</select>
<button id="applyAlarmFilters" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-all duration-200">搜索</button>
<button id="clearAlarmFilters" class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-all duration-200">重置</button>
</div>
<div class="flex items-center gap-2 ml-auto">
<button id="acknowledgeAllAlarms" class="px-4 py-2 bg-yellow-600 text-white rounded-lg hover:bg-yellow-700 transition-all duration-200 flex items-center gap-2">
<i class="fa fa-check-circle"></i>
全部确认
</button>
<button id="clearAllAlarms" class="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-all duration-200 flex items-center gap-2">
<i class="fa fa-trash"></i>
全部清除
</button>
</div>
</div>
<!-- 告警列表 -->
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead>
<tr class="bg-gray-50">
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">时间</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">设备</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">级别</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">消息</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">状态</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th>
</tr>
</thead>
<tbody id="alarmsTableBody" class="bg-white divide-y divide-gray-200">
<!-- 数据将通过JavaScript动态加载 -->
</tbody>
</table>
</div>
<!-- 分页控件 -->
<div id="alarmsPagination" class="mt-6 flex justify-center"></div>
</div>
</div>
<!-- 告警详情 -->
<div id="alarm-infoContent" class="hidden">
<div class="bg-white rounded-xl shadow-md p-6">
<div class="flex justify-between items-center mb-6">
<h2 class="text-xl font-bold text-gray-900">告警详情</h2>
<button id="backToAlarmsBtn" class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-all duration-200">
返回告警列表
</button>
</div>
<!-- 告警详情内容 -->
<div id="alarmDetailContent" class="space-y-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="bg-gray-50 rounded-lg p-4">
<h3 class="text-lg font-semibold text-gray-900 mb-4">基本信息</h3>
<div class="space-y-3">
<div class="flex justify-between">
<span class="text-gray-600">告警ID:</span>
<span id="alarmDetailId" class="font-medium">--</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600">发生时间:</span>
<span id="alarmDetailTime" class="font-medium">--</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600">设备:</span>
<span id="alarmDetailDevice" class="font-medium">--</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600">告警级别:</span>
<span id="alarmDetailLevel" class="font-medium">--</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600">告警状态:</span>
<span id="alarmDetailStatus" class="font-medium">--</span>
</div>
</div>
</div>
<div class="bg-gray-50 rounded-lg p-4">
<h3 class="text-lg font-semibold text-gray-900 mb-4">详细信息</h3>
<div class="space-y-3">
<div>
<span class="text-gray-600 block mb-1">告警消息:</span>
<div id="alarmDetailMessage" class="font-medium bg-white p-3 rounded border border-gray-200">--</div>
</div>
<div>
<span class="text-gray-600 block mb-1">告警描述:</span>
<div id="alarmDetailDescription" class="font-medium bg-white p-3 rounded border border-gray-200">--</div>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 rounded-lg p-4">
<h3 class="text-lg font-semibold text-gray-900 mb-4">处理记录</h3>
<div id="alarmDetailHistory" class="space-y-2">
<div class="bg-white p-3 rounded border border-gray-200">
<div class="flex justify-between text-sm text-gray-500">
<span>暂无处理记录</span>
</div>
</div>
</div>
</div>
<div class="flex gap-4">
<button id="acknowledgeDetailAlarm" class="bg-yellow-600 text-white px-4 py-2 rounded-lg hover:bg-yellow-700 transition-all duration-200 flex-1">
<i class="fa fa-check-circle mr-2"></i>确认告警
</button>
<button id="clearDetailAlarm" class="bg-red-600 text-white px-4 py-2 rounded-lg hover:bg-red-700 transition-all duration-200 flex-1">
<i class="fa fa-trash mr-2"></i>清除告警
</button>
</div>
</div>
</div>
</div>
</main> </main>
<!-- 设备编辑/添加模态框 --> <!-- 设备编辑/添加模态框 -->

View File

@@ -224,6 +224,21 @@ function switchPage() {
serverInfoDisplay.innerHTML = `<p class="text-red-500">无效的服务器ID</p>`; serverInfoDisplay.innerHTML = `<p class="text-red-500">无效的服务器ID</p>`;
} }
} }
} else if (hash === '#alarms') {
showContent('alarmsContent');
loadAlarmsList();
// 清除当前设备ID
state.currentDeviceID = '';
} else if (hash === '#alarm-info' || hash.startsWith('#alarm-info/')) {
showContent('alarm-infoContent');
// 清除当前设备ID
state.currentDeviceID = '';
// 提取告警ID并加载详情
let alarmId = '';
if (hash.startsWith('#alarm-info/')) {
alarmId = hash.split('/')[1];
}
loadAlarmDetails(alarmId);
} else { } else {
showContent('homeContent'); showContent('homeContent');
loadHomeData(); loadHomeData();
@@ -237,6 +252,8 @@ function hideAllContent() {
document.getElementById('serversContent').classList.add('hidden'); document.getElementById('serversContent').classList.add('hidden');
document.getElementById('serverMonitorContent').classList.add('hidden'); document.getElementById('serverMonitorContent').classList.add('hidden');
document.getElementById('devicesContent').classList.add('hidden'); document.getElementById('devicesContent').classList.add('hidden');
document.getElementById('alarmsContent').classList.add('hidden');
document.getElementById('alarm-infoContent').classList.add('hidden');
} }
function showContent(contentId) { function showContent(contentId) {
@@ -440,7 +457,7 @@ function renderAlarmList(alarmData) {
alarmData.forEach(alarm => { alarmData.forEach(alarm => {
const alarmItem = document.createElement('div'); const alarmItem = document.createElement('div');
alarmItem.className = `p-4 bg-gray-50 rounded-lg border-l-4 ${getAlarmBorderColor(alarm.level)}`; alarmItem.className = `p-4 bg-gray-50 rounded-lg border-l-4 ${getAlarmBorderColor(alarm.level)} cursor-pointer hover:shadow-md transition-all`;
alarmItem.innerHTML = ` alarmItem.innerHTML = `
<div class="flex justify-between items-start"> <div class="flex justify-between items-start">
@@ -455,6 +472,11 @@ function renderAlarmList(alarmData) {
</div> </div>
`; `;
// 添加点击事件,跳转到告警详情页面
alarmItem.addEventListener('click', () => {
window.location.hash = `#alarm-info/${alarm.id}`;
});
alarmList.appendChild(alarmItem); alarmList.appendChild(alarmItem);
}); });
} }
@@ -478,6 +500,442 @@ function getAlarmIconColor(level) {
} }
} }
// 获取告警级别文本
function getAlarmLevelText(level) {
switch(level) {
case 'error': return '错误';
case 'warning': return '警告';
case 'info': return '信息';
default: return '未知';
}
}
// 获取告警级别样式类
function getAlarmLevelClass(level) {
switch(level) {
case 'error': return 'bg-red-100 text-red-800';
case 'warning': return 'bg-yellow-100 text-yellow-800';
case 'info': return 'bg-blue-100 text-blue-800';
default: return 'bg-gray-100 text-gray-800';
}
}
// 告警管理相关功能
let alarmsPagination = {
currentPage: 1,
itemsPerPage: 10,
totalItems: 0,
totalPages: 0,
allAlarms: [],
filteredAlarms: []
};
// 加载告警列表
async function loadAlarmsList(page = 1) {
try {
const response = await fetch(`${API_BASE_URL}/alarms`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
const alarms = data.alarms || [];
// 更新告警总数
const alarmCount = document.getElementById('alarmCount');
if (alarmCount) {
alarmCount.textContent = alarms.length;
}
// 更新分页状态
alarmsPagination.allAlarms = alarms;
alarmsPagination.filteredAlarms = [...alarms];
alarmsPagination.totalItems = alarms.length;
alarmsPagination.totalPages = Math.ceil(alarmsPagination.totalItems / alarmsPagination.itemsPerPage);
alarmsPagination.currentPage = page;
// 渲染告警表格
renderAlarmsTable(alarmsPagination.filteredAlarms.slice(
(alarmsPagination.currentPage - 1) * alarmsPagination.itemsPerPage,
alarmsPagination.currentPage * alarmsPagination.itemsPerPage
));
// 创建分页控件
createPaginationControls(
document.getElementById('alarmsPagination'),
alarmsPagination,
loadAlarmsList
);
} catch (error) {
console.error('加载告警列表失败:', error);
// 由于后端尚未实现,使用模拟数据
const mockAlarms = [
{
"id": "alarm-1",
"time": new Date(Date.now() - 3600000).toISOString(),
"device": "服务器A",
"level": "error",
"message": "CPU使用率超过90%",
"status": "unacknowledged"
},
{
"id": "alarm-2",
"time": new Date(Date.now() - 7200000).toISOString(),
"device": "服务器B",
"level": "warning",
"message": "内存使用率接近80%",
"status": "unacknowledged"
},
{
"id": "alarm-3",
"time": new Date(Date.now() - 10800000).toISOString(),
"device": "服务器C",
"level": "info",
"message": "硬盘空间充足",
"status": "acknowledged"
},
{
"id": "alarm-4",
"time": new Date(Date.now() - 14400000).toISOString(),
"device": "服务器D",
"level": "error",
"message": "网络连接中断",
"status": "unacknowledged"
},
{
"id": "alarm-5",
"time": new Date(Date.now() - 18000000).toISOString(),
"device": "服务器E",
"level": "warning",
"message": "CPU温度过高",
"status": "acknowledged"
}
];
// 更新告警总数
const alarmCount = document.getElementById('alarmCount');
if (alarmCount) {
alarmCount.textContent = mockAlarms.length;
}
// 更新分页状态
alarmsPagination.allAlarms = mockAlarms;
alarmsPagination.filteredAlarms = [...mockAlarms];
alarmsPagination.totalItems = mockAlarms.length;
alarmsPagination.totalPages = Math.ceil(alarmsPagination.totalItems / alarmsPagination.itemsPerPage);
alarmsPagination.currentPage = page;
// 渲染告警表格
renderAlarmsTable(alarmsPagination.filteredAlarms.slice(
(alarmsPagination.currentPage - 1) * alarmsPagination.itemsPerPage,
alarmsPagination.currentPage * alarmsPagination.itemsPerPage
));
// 创建分页控件
createPaginationControls(
document.getElementById('alarmsPagination'),
alarmsPagination,
loadAlarmsList
);
// 显示提示信息
showToast('使用模拟数据显示告警列表', 'info');
}
}
// 渲染告警表格
function renderAlarmsTable(alarms) {
const alarmsTableBody = document.getElementById('alarmsTableBody');
if (!alarmsTableBody) return;
alarmsTableBody.innerHTML = '';
if (alarms.length === 0) {
alarmsTableBody.innerHTML = `
<tr>
<td colspan="6" class="px-6 py-12 text-center text-gray-500">
<div class="flex items-center justify-center">
<i class="fa fa-info-circle mr-2"></i>
暂无告警数据
</div>
</td>
</tr>
`;
return;
}
alarms.forEach(alarm => {
const alarmRow = document.createElement('tr');
alarmRow.className = 'hover:bg-gray-50 transition-colors cursor-pointer';
// 告警级别样式
const levelClass = getAlarmLevelClass(alarm.level);
alarmRow.innerHTML = `
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">${alarm.time}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">${alarm.device || '未知设备'}</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 py-1 rounded-full text-xs font-medium ${levelClass}">${getAlarmLevelText(alarm.level)}</span>
</td>
<td class="px-6 py-4 text-sm text-gray-900">${alarm.message}</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800">${alarm.status || '未确认'}</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<button onclick="acknowledgeAlarm('${alarm.id}')" class="text-yellow-600 hover:text-yellow-900 mr-3">
<i class="fa fa-check-circle"></i> 确认
</button>
<button onclick="clearAlarm('${alarm.id}')" class="text-red-600 hover:text-red-900">
<i class="fa fa-trash"></i> 清除
</button>
</td>
`;
// 添加点击事件,跳转到告警详情页面
alarmRow.addEventListener('click', (e) => {
// 如果点击的是按钮,不执行跳转
if (e.target.closest('button')) {
return;
}
window.location.hash = `#alarm-info/${alarm.id}`;
});
alarmsTableBody.appendChild(alarmRow);
});
}
// 确认告警
function acknowledgeAlarm(alarmId) {
// 由于后端尚未实现,仅显示提示信息
showToast('告警确认功能需要后端支持', 'warning');
}
// 清除告警
function clearAlarm(alarmId) {
// 由于后端尚未实现,仅显示提示信息
showToast('告警清除功能需要后端支持', 'warning');
}
// 应用告警过滤条件
function applyAlarmFilters() {
const searchKeyword = document.getElementById('alarmSearch').value.toLowerCase();
const levelFilter = document.getElementById('alarmLevelFilter').value;
const statusFilter = document.getElementById('alarmStatusFilter').value;
// 过滤告警列表
alarmsPagination.filteredAlarms = alarmsPagination.allAlarms.filter(alarm => {
const matchesKeyword = !searchKeyword || alarm.message.toLowerCase().includes(searchKeyword) ||
(alarm.device && alarm.device.toLowerCase().includes(searchKeyword));
const matchesLevel = !levelFilter || alarm.level === levelFilter;
const matchesStatus = !statusFilter || alarm.status === statusFilter;
return matchesKeyword && matchesLevel && matchesStatus;
});
// 更新分页状态
alarmsPagination.totalItems = alarmsPagination.filteredAlarms.length;
alarmsPagination.totalPages = Math.ceil(alarmsPagination.totalItems / alarmsPagination.itemsPerPage);
alarmsPagination.currentPage = 1;
// 重新渲染表格
renderAlarmsTable(alarmsPagination.filteredAlarms.slice(
(alarmsPagination.currentPage - 1) * alarmsPagination.itemsPerPage,
alarmsPagination.currentPage * alarmsPagination.itemsPerPage
));
// 更新分页控件
createPaginationControls(
document.getElementById('alarmsPagination'),
alarmsPagination,
loadAlarmsList
);
}
// 清除告警过滤条件
function clearAlarmFilters() {
document.getElementById('alarmSearch').value = '';
document.getElementById('alarmLevelFilter').value = '';
document.getElementById('alarmStatusFilter').value = '';
// 重置过滤后的告警列表
alarmsPagination.filteredAlarms = [...alarmsPagination.allAlarms];
// 更新分页状态
alarmsPagination.totalItems = alarmsPagination.filteredAlarms.length;
alarmsPagination.totalPages = Math.ceil(alarmsPagination.totalItems / alarmsPagination.itemsPerPage);
alarmsPagination.currentPage = 1;
// 重新渲染表格
renderAlarmsTable(alarmsPagination.filteredAlarms.slice(
(alarmsPagination.currentPage - 1) * alarmsPagination.itemsPerPage,
alarmsPagination.currentPage * alarmsPagination.itemsPerPage
));
// 更新分页控件
createPaginationControls(
document.getElementById('alarmsPagination'),
alarmsPagination,
loadAlarmsList
);
}
// 确认所有告警
function acknowledgeAllAlarms() {
// 由于后端尚未实现,仅显示提示信息
showToast('确认所有告警功能需要后端支持', 'warning');
}
// 清除所有告警
function clearAllAlarms() {
// 由于后端尚未实现,仅显示提示信息
showToast('清除所有告警功能需要后端支持', 'warning');
}
// 获取告警级别样式类
function getAlarmLevelClass(level) {
switch(level) {
case 'info': return 'bg-blue-100 text-blue-800';
case 'warning': return 'bg-yellow-100 text-yellow-800';
case 'error': return 'bg-red-100 text-red-800';
case 'critical': return 'bg-red-900 text-white';
default: return 'bg-gray-100 text-gray-800';
}
}
// 获取告警级别中文文本
function getAlarmLevelText(level) {
switch(level) {
case 'info': return '信息';
case 'warning': return '警告';
case 'error': return '错误';
case 'critical': return '严重';
default: return '未知';
}
}
// 确认告警
async function acknowledgeAlarm(alarmId) {
try {
// 这里应该调用API来确认告警
console.log('确认告警:', alarmId);
showToast('告警已确认', 'success');
// 重新加载告警列表
loadAlarmsList(alarmsPagination.currentPage);
} catch (error) {
console.error('确认告警失败:', error);
showToast('确认告警失败', 'error');
}
}
// 清除告警
async function clearAlarm(alarmId) {
try {
// 这里应该调用API来清除告警
console.log('清除告警:', alarmId);
showToast('告警已清除', 'success');
// 重新加载告警列表
loadAlarmsList(alarmsPagination.currentPage);
} catch (error) {
console.error('清除告警失败:', error);
showToast('清除告警失败', 'error');
}
}
// 确认所有告警
async function acknowledgeAllAlarms() {
try {
// 这里应该调用API来确认所有告警
console.log('确认所有告警');
showToast('所有告警已确认', 'success');
// 重新加载告警列表
loadAlarmsList(alarmsPagination.currentPage);
} catch (error) {
console.error('确认所有告警失败:', error);
showToast('确认所有告警失败', 'error');
}
}
// 清除所有告警
async function clearAllAlarms() {
try {
// 这里应该调用API来清除所有告警
console.log('清除所有告警');
showToast('所有告警已清除', 'success');
// 重新加载告警列表
loadAlarmsList(alarmsPagination.currentPage);
} catch (error) {
console.error('清除所有告警失败:', error);
showToast('清除所有告警失败', 'error');
}
}
// 应用告警过滤
function applyAlarmFilters() {
const searchInput = document.getElementById('alarmSearchInput');
const levelFilter = document.getElementById('alarmLevelFilter');
const searchTerm = searchInput ? searchInput.value.toLowerCase() : '';
const level = levelFilter ? levelFilter.value : '';
// 过滤告警
alarmsPagination.filteredAlarms = alarmsPagination.allAlarms.filter(alarm => {
const matchesSearch = !searchTerm ||
(alarm.message && alarm.message.toLowerCase().includes(searchTerm)) ||
(alarm.device && alarm.device.toLowerCase().includes(searchTerm));
const matchesLevel = !level || alarm.level === level;
return matchesSearch && matchesLevel;
});
// 更新分页信息
alarmsPagination.totalItems = alarmsPagination.filteredAlarms.length;
alarmsPagination.totalPages = Math.ceil(alarmsPagination.totalItems / alarmsPagination.itemsPerPage);
alarmsPagination.currentPage = 1;
// 重新渲染
renderAlarmsTable(alarmsPagination.filteredAlarms.slice(
0, alarmsPagination.itemsPerPage
));
// 更新分页控件
createPaginationControls(
document.getElementById('alarmsPagination'),
alarmsPagination,
loadAlarmsList
);
}
// 清除告警过滤
function clearAlarmFilters() {
const searchInput = document.getElementById('alarmSearchInput');
const levelFilter = document.getElementById('alarmLevelFilter');
if (searchInput) {
searchInput.value = '';
}
if (levelFilter) {
levelFilter.value = '';
}
// 重置过滤
alarmsPagination.filteredAlarms = [...alarmsPagination.allAlarms];
alarmsPagination.totalItems = alarmsPagination.filteredAlarms.length;
alarmsPagination.totalPages = Math.ceil(alarmsPagination.totalItems / alarmsPagination.itemsPerPage);
alarmsPagination.currentPage = 1;
// 重新渲染
renderAlarmsTable(alarmsPagination.filteredAlarms.slice(
0, alarmsPagination.itemsPerPage
));
// 更新分页控件
createPaginationControls(
document.getElementById('alarmsPagination'),
alarmsPagination,
loadAlarmsList
);
}
// 加载服务器数量 // 加载服务器数量
async function loadServerCount() { async function loadServerCount() {
try { try {
@@ -498,6 +956,113 @@ async function loadServerCount() {
} }
} }
// 告警详情模拟数据
let alertDetailsData = {
"id": "ALARM-20240520-0001",
"time": "2024-05-20 14:30:45",
"device": "服务器-01",
"level": "critical",
"message": "CPU使用率超过阈值",
"description": "服务器-01的CPU使用率持续5分钟超过90%当前使用率为95%。请立即检查系统负载和运行中的进程。",
"status": "未处理",
"handler": "",
"handleTime": "",
"handleResult": ""
};
// 加载告警详情
function loadAlarmDetails(alarmId) {
// 获取告警详情数据这里使用模拟数据实际项目中应从API获取
let alarmDetails = alertDetailsData;
// 如果提供了告警ID尝试从告警列表中找到对应的告警
if (alarmId && alarmsPagination.allAlarms.length > 0) {
const foundAlarm = alarmsPagination.allAlarms.find(a => a.id === alarmId);
if (foundAlarm) {
// 将找到的告警转换为详情格式
alarmDetails = {
id: foundAlarm.id,
time: foundAlarm.time,
device: foundAlarm.device,
level: foundAlarm.level,
message: foundAlarm.message,
description: `详细描述: ${foundAlarm.message}`,
status: foundAlarm.status,
handler: "",
handleTime: "",
handleResult: ""
};
}
}
// 渲染告警详情
document.getElementById('alarmId').textContent = alarmDetails.id || '未设置';
document.getElementById('alarmTime').textContent = alarmDetails.time || '未设置';
document.getElementById('alarmDevice').textContent = alarmDetails.device || '未设置';
document.getElementById('alarmLevel').textContent = getAlarmLevelText(alarmDetails.level) || '未设置';
document.getElementById('alarmStatus').textContent = alarmDetails.status || '未处理';
document.getElementById('alarmMessage').textContent = alarmDetails.message || '无';
document.getElementById('alarmDescription').textContent = alarmDetails.description || '无';
document.getElementById('alarmHandler').textContent = alarmDetails.handler || '未处理';
document.getElementById('alarmHandleTime').textContent = alarmDetails.handleTime || '未处理';
document.getElementById('alarmHandleResult').textContent = alarmDetails.handleResult || '未处理';
// 添加返回按钮事件
const backToAlarmListBtn = document.getElementById('backToAlarmList');
if (backToAlarmListBtn) {
// 先移除可能存在的事件监听器
backToAlarmListBtn.removeEventListener('click', backToAlarmListHandler);
// 添加新的事件监听器
backToAlarmListBtn.addEventListener('click', backToAlarmListHandler);
}
// 添加确认和清除按钮事件
const acknowledgeBtn = document.getElementById('alarmAcknowledgeBtn');
if (acknowledgeBtn) {
acknowledgeBtn.removeEventListener('click', acknowledgeAlarmHandler);
acknowledgeBtn.addEventListener('click', acknowledgeAlarmHandler.bind(null, alarmDetails.id));
}
const clearBtn = document.getElementById('alarmClearBtn');
if (clearBtn) {
clearBtn.removeEventListener('click', clearAlarmHandler);
clearBtn.addEventListener('click', clearAlarmHandler.bind(null, alarmDetails.id));
}
}
// 返回告警列表的事件处理函数
function backToAlarmListHandler() {
window.location.hash = '#alarms';
}
// 确认告警的事件处理函数
async function acknowledgeAlarmHandler(alarmId) {
try {
// 这里应该调用API来确认告警
console.log('确认告警:', alarmId);
showToast('告警已确认', 'success');
// 返回告警列表页面
window.location.hash = '#alarms';
} catch (error) {
console.error('确认告警失败:', error);
showToast('确认告警失败', 'error');
}
}
// 清除告警的事件处理函数
async function clearAlarmHandler(alarmId) {
try {
// 这里应该调用API来清除告警
console.log('清除告警:', alarmId);
showToast('告警已清除', 'success');
// 返回告警列表页面
window.location.hash = '#alarms';
} catch (error) {
console.error('清除告警失败:', error);
showToast('清除告警失败', 'error');
}
}
// 加载所有服务器 // 加载所有服务器
async function loadAllServers() { async function loadAllServers() {
try { try {
@@ -1731,20 +2296,33 @@ async function loadServerInfo(deviceId) {
} }
const deviceData = await deviceResponse.json(); const deviceData = await deviceResponse.json();
// 获取硬件信息,特别是操作系统信息 // 获取硬件信息,特别是操作系统和CPU型号信息
const hardwareResponse = await fetch(`${API_BASE_URL}/metrics/hardware?device_id=${deviceId}`); const hardwareResponse = await fetch(`${API_BASE_URL}/metrics/hardware?device_id=${deviceId}`);
let osFullname = '未知'; let osFullname = '未知';
let cpuModel = '未知';
if (hardwareResponse.ok) { if (hardwareResponse.ok) {
const hardwareData = await hardwareResponse.json(); const hardwareData = await hardwareResponse.json();
if (hardwareData && hardwareData.hardware && hardwareData.hardware.os) { if (hardwareData && hardwareData.hardware) {
osFullname = hardwareData.hardware.os.fullname || '未知'; // 获取操作系统信息
if (hardwareData.hardware.os) {
osFullname = hardwareData.hardware.os.fullname || '未知';
}
// 获取CPU型号信息
if (hardwareData.hardware.cpu) {
// 尝试从不同字段获取CPU型号
const cpu = hardwareData.hardware.cpu;
cpuModel = cpu.model || cpu.name || cpu.brand || '未知';
// 确保CPU型号是字符串类型
cpuModel = typeof cpuModel === 'string' ? cpuModel : '未知';
}
} }
} }
// 更新服务器信息显示 // 更新服务器信息显示增加CPU型号
const serverInfoDisplay = document.getElementById('serverInfoDisplay'); const serverInfoDisplay = document.getElementById('serverInfoDisplay');
if (serverInfoDisplay) { if (serverInfoDisplay) {
serverInfoDisplay.innerHTML = `<p>服务器名称: <strong>${deviceData.device.name || deviceId}</strong> | IP地址: <strong>${deviceData.device.ip || '未知'}</strong> | 操作系统: <strong>${osFullname}</strong></p>`; serverInfoDisplay.innerHTML = `<p>服务器名称: <strong>${deviceData.device.name || deviceId}</strong> | IP地址: <strong>${deviceData.device.ip || '未知'}</strong> | 操作系统: <strong>${osFullname}</strong> | CPU型号: <strong>${cpuModel}</strong></p>`;
} }
// 初始化状态卡片 // 初始化状态卡片

View File

@@ -3,17 +3,16 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"time"
influxdb2 "github.com/influxdata/influxdb-client-go" influxdb2 "github.com/influxdata/influxdb-client-go"
) )
func main() { func main() {
// 使用配置文件中的InfluxDB配置 // 使用config.json中的InfluxDB配置
url := "http://10.35.10.130:8066" url := "http://10.35.10.70:8066"
token := "aVI5qMGz6e8d4pfyhVZNYfS5we7C8Bb-5bi-V7LpL3K6CmQyudauigoxDFv1UFo2Hvda7swKEqTe8eP6xy4jBw==" token := "aVI5qMGz6e8d4pfyhVZNYfS5we7C8Bb-5bi-V7LpL3K6CmQyudauigoxDFv1UFo2Hvda7swKEqTe8eP6xy4jBw=="
username := "admin" username := "monitor"
password := "Wxf26051" password := "monitor"
org := "AMAZEHOME" org := "AMAZEHOME"
bucket := "AMAZEHOME" bucket := "AMAZEHOME"
@@ -37,7 +36,7 @@ func main() {
// 测试2: 使用Username/Password认证通过URL嵌入 // 测试2: 使用Username/Password认证通过URL嵌入
fmt.Println("\n=== Test 2: Using Username/Password Authentication (Embedded in URL) ===") fmt.Println("\n=== Test 2: Using Username/Password Authentication (Embedded in URL) ===")
authURL := fmt.Sprintf("http://%s:%s@10.35.10.130:8066", username, password) authURL := fmt.Sprintf("http://%s:%s@10.35.10.70:8066", username, password)
client2 := influxdb2.NewClient(authURL, "") client2 := influxdb2.NewClient(authURL, "")
defer client2.Close() defer client2.Close()
@@ -49,23 +48,63 @@ func main() {
fmt.Printf("Health check result: %v\n", health2) fmt.Printf("Health check result: %v\n", health2)
} }
// 测试3: 尝试写入数据 // 测试3: 查询硬件数据
fmt.Println("\n=== Test 3: Trying to Write Data Point ===") fmt.Println("\n=== Test 3: Querying Hardware Data ===")
// 使用client2进行测试 // 使用client1Token认证进行查询
writeAPI := client2.WriteAPIBlocking(org, bucket) queryAPI := client1.QueryAPI(org)
point := influxdb2.NewPoint( // 查询特定设备的硬件数据
"test_metric", deviceID := "device-1764692967636"
map[string]string{"test_tag": "test_value"}, query := `from(bucket: "AMAZEHOME")
map[string]interface{}{"value": 1.0}, |> range(start: -24h)
time.Now(), |> filter(fn: (r) => r["_measurement"] == "hardware")
) |> filter(fn: (r) => r["device_id"] == "` + deviceID + `")
|> filter(fn: (r) => r["type"] == "cpu")
|> last()`
err = writeAPI.WritePoint(context.Background(), point) result, err := queryAPI.Query(context.Background(), query)
if err != nil { if err != nil {
fmt.Printf("Write failed: %v\n", err) fmt.Printf("Query failed: %v\n", err)
} else { } else {
fmt.Println("Write successful!") fmt.Println("Query results:")
for result.Next() {
fmt.Printf("- Field: %s, Value: %v\n", result.Record().Field(), result.Record().Value())
}
if result.Err() != nil {
fmt.Printf("Result error: %v\n", result.Err())
}
}
// 测试4: 查询所有硬件类型的最新数据
fmt.Println("\n=== Test 4: Querying All Hardware Types ===")
// 使用client1Token认证进行查询
queryAPI = client1.QueryAPI(org)
hardwareTypes := []string{"os", "cpu", "memory", "disk", "network"}
for _, hardwareType := range hardwareTypes {
fmt.Printf("\n--- %s data ---", hardwareType)
query = `from(bucket: "` + bucket + `")
|> range(start: -24h)
|> filter(fn: (r) => r["_measurement"] == "hardware")
|> filter(fn: (r) => r["device_id"] == "` + deviceID + `")
|> filter(fn: (r) => r["type"] == "` + hardwareType + `")
|> last()`
result, err := queryAPI.Query(context.Background(), query)
if err != nil {
fmt.Printf("Query failed: %v\n", err)
} else {
hasData := false
for result.Next() {
hasData = true
fmt.Printf("- Field: %s, Value: %v\n", result.Record().Field(), result.Record().Value())
}
if !hasData {
fmt.Println("No data found")
}
if result.Err() != nil {
fmt.Printf("Result error: %v\n", result.Err())
}
}
} }
fmt.Println("\n=== Test Completed ===") fmt.Println("\n=== Test Completed ===")

32
backend/test_performance.sh Executable file
View File

@@ -0,0 +1,32 @@
#!/bin/bash
# 测试API性能的脚本
API_URL="http://localhost:8080/api/metrics/hardware?device_id=device-1764692967636"
REQUEST_COUNT=10
echo "Starting performance test for $API_URL"
echo "Sending $REQUEST_COUNT requests..."
# 初始化总时间
total_time=0
# 发送请求并测量时间
for ((i=1; i<=$REQUEST_COUNT; i++)); do
# 使用curl测量响应时间-w参数定义输出格式
time=$(curl -o /dev/null -s -w "%{time_total}\n" "$API_URL")
# 累加时间
total_time=$(echo "$total_time + $time" | bc)
# 打印当前请求的响应时间
echo "Request $i: $time seconds"
done
# 计算平均响应时间
average_time=$(echo "scale=6; $total_time / $REQUEST_COUNT" | bc)
echo ""
echo "Performance Test Results:"
echo "Total Requests: $REQUEST_COUNT"
echo "Total Time: $total_time seconds"
echo "Average Response Time: $average_time seconds"