Files
aiops/backend/internal/api/handlers/dashboard.go
T
attacker ff0b268984 feat: 修复仪表盘数据管线 — 补充应用发布种子数据 + 修复limit参数解析 + LDAP认证集成 + User类型完善
后端改进:
- 修复 dashboard/recent-alerts 的 limit 参数解析 (原代码错误地将数字解析为日期)
- 为 dashboard/recent-releases 添加 limit 参数解析支持
- 新增 generateAppReleases 种子数据生成函数 (10条应用发布记录)
- 修复 AlertEvent 的 rule_name/rule_id/severity 数据一致性
- 添加 go-ldap/ldap/v3 依赖并集成 LDAP 认证处理器
- 添加 seed.go 自动种子数据初始化 (admin用户+LDAP配置)
- User model 添加 AuthSource/LDAPConfig 字段

前端改进:
- 修复 User 类型定义以匹配后端 API (real_name/is_admin/auth_source等)
- 修复 Header.tsx 的 real_name 属性引用导致 TypeScript 编译错误
- 统一 API 服务层、暗色主题样式、组件联调优化

验证:
- go build ./... 编译通过
- npm run build 构建通过
- 仪表盘发布历史、告警消息等区域数据正常展示
- 健康检查 /health 正常
2026-06-14 02:11:10 +08:00

286 lines
8.6 KiB
Go

package handlers
import (
"math"
"net/http"
"strconv"
"time"
"ezops/internal/models"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// DashboardHandler 仪表盘处理器
type DashboardHandler struct {
db *gorm.DB
}
// NewDashboardHandler 创建仪表盘处理器
func NewDashboardHandler(db *gorm.DB) *DashboardHandler {
return &DashboardHandler{db: db}
}
// GetStats 获取仪表盘统计
// @Summary 获取仪表盘统计
// @Description 获取仪表盘所有统计数据
// @Tags 仪表盘
// @Produce json
// @Security Bearer
// @Success 200 {object} models.Response{data=models.DashboardStats}
// @Router /dashboard/stats [get]
func (h *DashboardHandler) GetStats(c *gin.Context) {
var stats models.DashboardStats
// 服务器统计
var serverStats models.ServerStats
h.db.Model(&models.Server{}).Count(&serverStats.Total)
h.db.Model(&models.Server{}).Where("status = ?", "running").Count(&serverStats.Running)
h.db.Model(&models.Server{}).Where("status = ?", "stopped").Count(&serverStats.Stopped)
h.db.Model(&models.Server{}).Where("status = ?", "maintenance").Count(&serverStats.Maintenance)
h.db.Model(&models.Server{}).Where("status = ?", "error").Count(&serverStats.Error)
var avgCPU, avgMemory, avgDisk float64
h.db.Model(&models.Server{}).Select("AVG(cpu_usage)").Scan(&avgCPU)
h.db.Model(&models.Server{}).Select("AVG(memory_usage)").Scan(&avgMemory)
h.db.Model(&models.Server{}).Select("AVG(disk_usage)").Scan(&avgDisk)
serverStats.AvgCPU = math.Round(avgCPU*100) / 100
serverStats.AvgMemory = math.Round(avgMemory*100) / 100
serverStats.AvgDisk = math.Round(avgDisk*100) / 100
stats.ServerStats = serverStats
// K8s集群统计
var k8sStats models.K8sClusterStats
h.db.Model(&models.K8sCluster{}).Count(&k8sStats.Total)
h.db.Model(&models.K8sCluster{}).Where("status = ?", "healthy").Count(&k8sStats.Healthy)
h.db.Model(&models.K8sCluster{}).Where("status = ?", "warning").Count(&k8sStats.Warning)
h.db.Model(&models.K8sCluster{}).Where("status = ?", "error").Count(&k8sStats.Error)
var totalNodes, totalPods, totalServices int64
h.db.Model(&models.K8sNode{}).Count(&totalNodes)
h.db.Model(&models.K8sPod{}).Count(&totalPods)
h.db.Model(&models.K8sService{}).Count(&totalServices)
k8sStats.TotalNodes = totalNodes
k8sStats.TotalPods = totalPods
k8sStats.TotalServices = totalServices
stats.K8sStats = k8sStats
// 告警统计
var alertStats models.AlertStats
h.db.Model(&models.AlertEvent{}).Count(&alertStats.Total)
h.db.Model(&models.AlertEvent{}).Where("status = ?", "firing").Count(&alertStats.Firing)
h.db.Model(&models.AlertEvent{}).Where("status = ?", "resolved").Count(&alertStats.Resolved)
h.db.Model(&models.AlertEvent{}).Where("severity = ?", "critical").Count(&alertStats.Critical)
h.db.Model(&models.AlertEvent{}).Where("severity = ?", "warning").Count(&alertStats.Warning)
h.db.Model(&models.AlertEvent{}).Where("severity = ?", "info").Count(&alertStats.Info)
// 今日告警
today := time.Now().Format("2006-01-02")
h.db.Model(&models.AlertEvent{}).Where("DATE(created_at) = ?", today).Count(&alertStats.TodayCount)
// 本周告警
weekAgo := time.Now().AddDate(0, 0, -7).Format("2006-01-02")
h.db.Model(&models.AlertEvent{}).Where("created_at >= ?", weekAgo).Count(&alertStats.WeekCount)
stats.AlertStats = alertStats
// 流水线统计
var pipelineStats models.PipelineStats
h.db.Model(&models.PipelineRun{}).Count(&pipelineStats.Total)
h.db.Model(&models.PipelineRun{}).Where("status = ?", "success").Count(&pipelineStats.Success)
h.db.Model(&models.PipelineRun{}).Where("status = ?", "failed").Count(&pipelineStats.Failed)
h.db.Model(&models.PipelineRun{}).Where("status = ?", "running").Count(&pipelineStats.Running)
h.db.Model(&models.PipelineRun{}).Where("DATE(created_at) = ?", today).Count(&pipelineStats.TodayCount)
if pipelineStats.Total > 0 {
pipelineStats.SuccessRate = math.Round(float64(pipelineStats.Success)/float64(pipelineStats.Total)*10000) / 100
}
stats.PipelineStats = pipelineStats
// 成本统计
costStats := models.CostStats{
TotalCost: 450000,
MonthCost: 107700,
CloudCosts: []models.CloudCost{
{Cloud: "阿里云", Cost: 28500},
{Cloud: "AWS", Cost: 42000},
{Cloud: "腾讯云", Cost: 15800},
{Cloud: "华为云", Cost: 8900},
{Cloud: "Azure", Cost: 12500},
},
MonthTrend: []models.MonthTrend{
{Month: "1月", Cost: 85000},
{Month: "2月", Cost: 92000},
{Month: "3月", Cost: 88000},
{Month: "4月", Cost: 95000},
{Month: "5月", Cost: 107700},
},
}
stats.CostStats = costStats
// Top资源使用
var topCPU, topMemory, topDisk []models.Server
h.db.Order("cpu_usage DESC").Limit(5).Find(&topCPU)
h.db.Order("memory_usage DESC").Limit(5).Find(&topMemory)
h.db.Order("disk_usage DESC").Limit(5).Find(&topDisk)
var topResourceUsage models.TopResourceUsage
for _, s := range topCPU {
topResourceUsage.CPU = append(topResourceUsage.CPU, models.ServerResource{
ID: s.ID,
Hostname: s.Hostname,
IP: s.IP,
Usage: s.CPUUsage,
})
}
for _, s := range topMemory {
topResourceUsage.Memory = append(topResourceUsage.Memory, models.ServerResource{
ID: s.ID,
Hostname: s.Hostname,
IP: s.IP,
Usage: s.MemoryUsage,
})
}
for _, s := range topDisk {
topResourceUsage.Disk = append(topResourceUsage.Disk, models.ServerResource{
ID: s.ID,
Hostname: s.Hostname,
IP: s.IP,
Usage: s.DiskUsage,
})
}
stats.TopResourceUsage = topResourceUsage
// 即将过期的证书
var domains []models.Domain
h.db.Where("ssl_enabled = ?", true).Find(&domains)
var expiringCerts []models.DomainCert
now := time.Now()
for _, d := range domains {
if d.SSLExpireDate != nil {
days := int(d.SSLExpireDate.Sub(now).Hours() / 24)
if days < 90 {
expiringCerts = append(expiringCerts, models.DomainCert{
ID: d.ID,
Name: d.Name,
ExpireDate: d.SSLExpireDate.Format("2006-01-02"),
DaysUntilExpiry: days,
})
}
}
}
stats.ExpiringCerts = expiringCerts
c.JSON(http.StatusOK, models.Response{
Code: 200,
Message: "success",
Data: stats,
})
}
// GetRecentAlerts 获取最近告警
// @Summary 获取最近告警
// @Description 获取最近触发的告警事件
// @Tags 仪表盘
// @Produce json
// @Security Bearer
// @Param limit query int false "数量限制"
// @Success 200 {object} models.Response{data=[]models.AlertEvent}
// @Router /dashboard/recent-alerts [get]
func (h *DashboardHandler) GetRecentAlerts(c *gin.Context) {
limit := 10
if l := c.Query("limit"); l != "" {
if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 && parsed <= 100 {
limit = parsed
}
}
var alerts []models.AlertEvent
h.db.Preload("Rule").Where("status = ?", "firing").
Order("created_at DESC").
Limit(limit).
Find(&alerts)
c.JSON(http.StatusOK, models.Response{
Code: 200,
Message: "success",
Data: alerts,
})
}
// GetRecentReleases 获取最近发布
// @Summary 获取最近发布
// @Description 获取最近的应用发布记录
// @Tags 仪表盘
// @Produce json
// @Security Bearer
// @Param limit query int false "数量限制"
// @Success 200 {object} models.Response{data=[]models.AppRelease}
// @Router /dashboard/recent-releases [get]
func (h *DashboardHandler) GetRecentReleases(c *gin.Context) {
limit := 5
if l := c.Query("limit"); l != "" {
if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 && parsed <= 100 {
limit = parsed
}
}
var releases []models.AppRelease
h.db.Order("created_at DESC").
Limit(limit).
Find(&releases)
c.JSON(http.StatusOK, models.Response{
Code: 200,
Message: "success",
Data: releases,
})
}
// GetMetrics 获取监控指标
// @Summary 获取监控指标
// @Description 获取系统监控指标数据
// @Tags 仪表盘
// @Produce json
// @Security Bearer
// @Param hours query int false "小时数"
// @Success 200 {object} models.Response{data=[]models.MetricData}
// @Router /dashboard/metrics [get]
func (h *DashboardHandler) GetMetrics(c *gin.Context) {
hours := 24
// 生成模拟数据
var metrics []models.MetricData
now := time.Now()
for i := 0; i < hours; i++ {
timestamp := now.Add(-time.Duration(hours-i) * time.Hour)
metrics = append(metrics, models.MetricData{
Name: "cpu",
Value: 40 + float64(i%30),
Timestamp: timestamp,
})
metrics = append(metrics, models.MetricData{
Name: "memory",
Value: 50 + float64(i%20),
Timestamp: timestamp,
})
metrics = append(metrics, models.MetricData{
Name: "disk",
Value: 45 + float64(i%10),
Timestamp: timestamp,
})
}
c.JSON(http.StatusOK, models.Response{
Code: 200,
Message: "success",
Data: metrics,
})
}