feat: Agent采集uptime, 后端持久化kernel/uptime/diskIO, 前端磁盘BPS+IOPS展示

- Agent: system.go 添加 readUptime(), SystemStat 携带 Uptime
- Backend: Server 模型新增 Kernel 字段, ReceiveReport 持久化 kernel+uptime
- Backend: /agent/latest 返回 kernel/uptime/port_info/disk_io_info/disk_total
- Frontend: 磁盘监控改为 BPS+IOPS (4张卡片), 系统信息修复内核/运行时长/监听端口/磁盘容量
- 图表单位从 MB/s 改为 KB/s, 添加 formatBPS 工具函数
This commit is contained in:
attacker committed 2026-06-19 01:10:00 +00:00
1 parent 31c2e338a3
commit 5949ec5d0c
48 files changed
+2182 -956

No files matched your search

+33 -3
View File
@@ -14,7 +14,7 @@ import (
// 批量操作域:批量导入 + 批量删除 // Logan.Li
// BatchCreateServers 批量导入服务器 // Logan.Li
// 接收 {servers:[...], region_id, auth_type},逐条校验并创建,跳过重复(主机名或IP已存在)
// 接收 {servers:[...], region_id, auth_type, credential_id},逐条校验并创建,跳过重复(主机名或IP已存在)
func (h *ServerHandler) BatchCreateServers(c *gin.Context) {
var req models.BatchCreateServerRequest
if err := c.ShouldBindJSON(&req); err != nil {
@@ -30,6 +30,28 @@ func (h *ServerHandler) BatchCreateServers(c *gin.Context) {
authType = "password"
}
// 如果指定了凭据,提前解密 // Logan.Li
var decryptedPwd, decryptedKey string
if req.CredentialID != nil && *req.CredentialID > 0 {
var cred models.Credential
if err := h.db.First(&cred, *req.CredentialID).Error; err != nil {
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "凭据不存在"})
return
}
plain, err := h.crypt.Decrypt(cred.Content)
if err != nil {
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "凭据解密失败"})
return
}
if cred.Type == "ssh_key" {
decryptedKey = plain
authType = "key"
} else {
decryptedPwd = plain
authType = "password"
}
}
result := models.BatchCreateResult{Errors: []string{}}
for i, item := range req.Servers {
hostname := strings.TrimSpace(item.Hostname)
@@ -63,6 +85,8 @@ func (h *ServerHandler) BatchCreateServers(c *gin.Context) {
Port: port,
Username: username,
AuthType: authType,
Password: decryptedPwd,
SSHKey: decryptedKey,
OS: item.OS,
CPU: item.CPU,
Memory: item.Memory * 1024, // GB → MB
@@ -111,7 +135,13 @@ func (h *ServerHandler) BatchDeleteServers(c *gin.Context) {
var servers []models.Server
h.db.Where("id IN ?", req.IDs).Find(&servers)
if err := h.db.Delete(&models.Server{}, req.IDs).Error; err != nil {
// 硬删除:先清理每台服务器的 Agent 上报和聚合数据
for _, s := range servers {
h.db.Unscoped().Where("hostname = ?", s.Hostname).Delete(&models.AgentReport{})
h.db.Unscoped().Where("hostname = ?", s.Hostname).Delete(&models.MetricsAggregate{})
}
if err := h.db.Unscoped().Delete(&models.Server{}, req.IDs).Error; err != nil {
c.JSON(http.StatusInternalServerError, models.Response{
Code: 500,
Message: "删除失败",
@@ -126,6 +156,6 @@ func (h *ServerHandler) BatchDeleteServers(c *gin.Context) {
c.JSON(http.StatusOK, models.Response{
Code: 200,
Message: "删除成功",
Message: "服务器已彻底删除",
})
}