新增Windows代理程序支持
This commit is contained in:
BIN
agent-windows/agent-windows.exe
Executable file
BIN
agent-windows/agent-windows.exe
Executable file
Binary file not shown.
2
agent-windows/build.sh
Normal file
2
agent-windows/build.sh
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
GOOS=windows GOARCH=amd64
|
||||||
|
go build -v -o agent-windows.exe
|
||||||
19
agent-windows/go.mod
Normal file
19
agent-windows/go.mod
Normal 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
23
agent-windows/go.sum
Normal 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
1256
agent-windows/main.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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}"
|
||||||
|
|||||||
@@ -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),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 为了确保返回的结构与前端期望的一致,我们需要将查询结果转换为期望的格式
|
// 为了确保返回的结构与前端期望的一致,我们需要将查询结果转换为期望的格式
|
||||||
|
|||||||
@@ -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.
Binary file not shown.
2164
backend/server.log
2164
backend/server.log
File diff suppressed because it is too large
Load Diff
@@ -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}"
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
<!-- 设备编辑/添加模态框 -->
|
<!-- 设备编辑/添加模态框 -->
|
||||||
|
|||||||
@@ -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>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化状态卡片
|
// 初始化状态卡片
|
||||||
|
|||||||
@@ -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进行测试
|
// 使用client1(Token认证)进行查询
|
||||||
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 ===")
|
||||||
|
// 使用client1(Token认证)进行查询
|
||||||
|
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
32
backend/test_performance.sh
Executable 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"
|
||||||
Reference in New Issue
Block a user