285 lines
8.3 KiB
Go
285 lines
8.3 KiB
Go
package controller
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"ezops/model"
|
|
"ezops/pkg/zabbix"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// ZabbixProxyHandler Zabbix 代理处理器 — 将 Zabbix API 数据转为前端面板可用格式
|
|
type ZabbixProxyHandler struct{}
|
|
|
|
func NewZabbixProxyHandler() *ZabbixProxyHandler {
|
|
return &ZabbixProxyHandler{}
|
|
}
|
|
|
|
// ZabbixHostSummary Zabbix 主机汇总(前端面板数据)
|
|
type ZabbixHostSummary struct {
|
|
HostID string `json:"hostid"`
|
|
Hostname string `json:"hostname"`
|
|
Name string `json:"name"`
|
|
IP string `json:"ip"`
|
|
Available string `json:"available"` // 0=未知 1=可用 2=不可用
|
|
CPUUsage float64 `json:"cpu_usage"`
|
|
MemoryUsage float64 `json:"memory_usage"`
|
|
DiskUsage float64 `json:"disk_usage"`
|
|
Load5 float64 `json:"load5"`
|
|
NetInBPS float64 `json:"net_in_bps"`
|
|
NetOutBPS float64 `json:"net_out_bps"`
|
|
ProblemCount int `json:"problem_count"`
|
|
Uptime int64 `json:"uptime_days"` // 天
|
|
CPUCount int `json:"cpu_count"`
|
|
TotalMemory float64 `json:"total_memory_gb"`
|
|
}
|
|
|
|
// ZabbixConnectReq 连接 Zabbix 请求
|
|
type ZabbixConnectReq struct {
|
|
URL string `json:"url" binding:"required"`
|
|
Username string `json:"username" binding:"required"`
|
|
Password string `json:"password" binding:"required"`
|
|
}
|
|
|
|
// ZabbixConnect 连接 Zabbix 并返回主机摘要
|
|
// POST /api/v1/zabbix/connect
|
|
func (h *ZabbixProxyHandler) Connect(c *gin.Context) {
|
|
var req ZabbixConnectReq
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "参数错误: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
client := zabbix.NewClient(req.URL, req.Username, req.Password)
|
|
ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
if err := client.Auth(ctx); err != nil {
|
|
c.JSON(http.StatusBadGateway, model.Response{Code: 502, Message: "Zabbix 认证失败: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
// 获取 Zabbix 版本
|
|
version, _ := client.GetVersion(ctx)
|
|
|
|
// 获取主机列表(含接口和组信息)
|
|
hosts, err := client.GetHosts(ctx, &zabbix.GetHostsParams{
|
|
Status: 0, // 只获取被监控的主机
|
|
SelectInterfaces: []string{"interfaceid", "hostid", "main", "type", "useip", "ip", "dns", "port"},
|
|
SelectGroups: []string{"groupid", "name"},
|
|
Output: []string{"hostid", "host", "name", "status", "available", "description"},
|
|
})
|
|
if err != nil {
|
|
client.Logout(ctx)
|
|
c.JSON(http.StatusBadGateway, model.Response{Code: 502, Message: "获取 Zabbix 主机失败: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
// 为每台主机查询监控项
|
|
var summaries []ZabbixHostSummary
|
|
itemKeys := []string{
|
|
"system.cpu.util[,idle]", // CPU 空闲率
|
|
"system.cpu.util", // CPU 使用率(备用)
|
|
"vm.memory.size[total]", // 总内存 bytes
|
|
"vm.memory.size[available]", // 可用内存 bytes
|
|
"vfs.fs.size[/,total]", // 总磁盘 bytes
|
|
"vfs.fs.size[/,used]", // 已用磁盘 bytes
|
|
"system.cpu.load[all,avg5]", // 5分钟负载
|
|
"net.if.in[ens18]", // 网络流入
|
|
"net.if.out[ens18]", // 网络流出
|
|
"system.cpu.num", // CPU 数量
|
|
"system.uptime", // 运行时间
|
|
}
|
|
|
|
for _, host := range hosts {
|
|
summary := ZabbixHostSummary{
|
|
HostID: host.HostID,
|
|
Hostname: host.Host,
|
|
Name: host.Name,
|
|
Available: host.Available,
|
|
}
|
|
|
|
// 获取 IP
|
|
for _, iface := range host.Interfaces {
|
|
if iface.Main == "1" && summary.IP == "" {
|
|
if iface.UseIP == "1" && iface.IP != "" {
|
|
summary.IP = iface.IP
|
|
} else if iface.DNS != "" {
|
|
summary.IP = iface.DNS
|
|
}
|
|
}
|
|
}
|
|
|
|
// 获取监控项
|
|
items, err := client.GetItems(ctx, &zabbix.GetItemsParams{
|
|
HostIDs: []string{host.HostID},
|
|
Filter: map[string]interface{}{"key_": itemKeys},
|
|
Output: []string{"itemid", "hostid", "name", "key_", "lastvalue", "units"},
|
|
SortField: []string{"name"},
|
|
})
|
|
if err != nil {
|
|
summaries = append(summaries, summary)
|
|
continue
|
|
}
|
|
|
|
var totalMemBytes, availMemBytes, totalDiskBytes, usedDiskBytes float64
|
|
for _, item := range items {
|
|
val, _ := strconv.ParseFloat(item.LastValue, 64)
|
|
switch item.Key {
|
|
case "system.cpu.util[,idle]":
|
|
summary.CPUUsage = math.Round((100-val)*100) / 100
|
|
case "system.cpu.util":
|
|
if summary.CPUUsage == 0 {
|
|
summary.CPUUsage = math.Round(val*100) / 100
|
|
}
|
|
case "vm.memory.size[total]":
|
|
totalMemBytes = val
|
|
case "vm.memory.size[available]":
|
|
availMemBytes = val
|
|
case "vfs.fs.size[/,total]":
|
|
totalDiskBytes = val
|
|
case "vfs.fs.size[/,used]":
|
|
usedDiskBytes = val
|
|
case "system.cpu.load[all,avg5]":
|
|
summary.Load5 = math.Round(val*100) / 100
|
|
case "net.if.in[ens18]":
|
|
summary.NetInBPS = math.Round(val*100) / 100
|
|
case "net.if.out[ens18]":
|
|
summary.NetOutBPS = math.Round(val*100) / 100
|
|
case "system.cpu.num":
|
|
summary.CPUCount = int(val)
|
|
case "system.uptime":
|
|
summary.Uptime = int64(val) / 86400 // seconds → days
|
|
}
|
|
}
|
|
|
|
// 计算内存使用率
|
|
if totalMemBytes > 0 {
|
|
summary.TotalMemory = math.Round(totalMemBytes/1024/1024/1024*100) / 100 // GB
|
|
if availMemBytes > 0 {
|
|
summary.MemoryUsage = math.Round((totalMemBytes-availMemBytes)/totalMemBytes*10000) / 100
|
|
}
|
|
}
|
|
// 计算磁盘使用率
|
|
if totalDiskBytes > 0 && usedDiskBytes > 0 {
|
|
summary.DiskUsage = math.Round(usedDiskBytes/totalDiskBytes*10000) / 100
|
|
}
|
|
|
|
// 获取问题触发器
|
|
triggers, _ := client.GetTriggersByHost(ctx, host.HostID, true)
|
|
summary.ProblemCount = len(triggers)
|
|
|
|
summaries = append(summaries, summary)
|
|
}
|
|
|
|
client.Logout(ctx)
|
|
|
|
c.JSON(http.StatusOK, model.Response{
|
|
Code: 200,
|
|
Message: "success",
|
|
Data: gin.H{
|
|
"version": version,
|
|
"hosts": summaries,
|
|
"total": len(summaries),
|
|
},
|
|
})
|
|
}
|
|
|
|
// ZabbixHostDetail Zabbix 单主机详情
|
|
// GET /api/v1/zabbix/host/:hostid
|
|
func (h *ZabbixProxyHandler) GetHostDetail(c *gin.Context) {
|
|
hostID := c.Param("hostid")
|
|
if hostID == "" {
|
|
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "缺少主机ID"})
|
|
return
|
|
}
|
|
|
|
// 从 query 参数获取连接信息
|
|
url := c.Query("url")
|
|
username := c.Query("username")
|
|
password := c.Query("password")
|
|
if url == "" || username == "" {
|
|
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "缺少 Zabbix 连接信息"})
|
|
return
|
|
}
|
|
|
|
client := zabbix.NewClient(url, username, password)
|
|
ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
if err := client.Auth(ctx); err != nil {
|
|
c.JSON(http.StatusBadGateway, model.Response{Code: 502, Message: "Zabbix 认证失败: " + err.Error()})
|
|
return
|
|
}
|
|
defer client.Logout(ctx)
|
|
|
|
// 获取主机详情
|
|
host, err := client.GetHostByID(ctx, hostID)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadGateway, model.Response{Code: 502, Message: "获取主机失败: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
// 获取所有监控项
|
|
items, err := client.GetItemsByHost(ctx, hostID)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadGateway, model.Response{Code: 502, Message: "获取监控项失败: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
// 获取问题触发器
|
|
triggers, _ := client.GetTriggersByHost(ctx, hostID, true)
|
|
|
|
c.JSON(http.StatusOK, model.Response{
|
|
Code: 200,
|
|
Message: "success",
|
|
Data: gin.H{
|
|
"host": host,
|
|
"items": items,
|
|
"triggers": triggers,
|
|
"problems": len(triggers),
|
|
},
|
|
})
|
|
}
|
|
|
|
// formatBps 格式化带宽
|
|
func formatBps(bps float64) string {
|
|
if bps == 0 {
|
|
return "—"
|
|
}
|
|
units := []string{"B/s", "KB/s", "MB/s", "GB/s", "TB/s"}
|
|
unitIdx := 0
|
|
val := bps
|
|
for val >= 1024 && unitIdx < len(units)-1 {
|
|
val /= 1024
|
|
unitIdx++
|
|
}
|
|
return fmt.Sprintf("%.1f %s", val, units[unitIdx])
|
|
}
|
|
|
|
// parseZabbixItemSummary 从 items 中提取汇总信息的辅助函数(供内部使用)
|
|
func parseZabbixItemSummary(items []zabbix.Item) map[string]string {
|
|
result := make(map[string]string)
|
|
for _, item := range items {
|
|
key := item.Key
|
|
// 标准化 key 名称
|
|
if strings.Contains(item.Name, "CPU utilization") || key == "system.cpu.util" {
|
|
result["cpu"] = item.LastValue
|
|
}
|
|
if strings.Contains(item.Name, "Memory utilization") || key == "vm.memory.size[pavailable]" {
|
|
result["memory"] = item.LastValue
|
|
}
|
|
if strings.Contains(item.Name, "Space utilization") || key == "/Space utilization/" {
|
|
result["disk"] = item.LastValue
|
|
}
|
|
}
|
|
return result
|
|
}
|