feat: 工具配置增强、站点拨测与日志中心多项改进
- 工具配置:新增卸载 API/UI、Go 工具链、流水线构建自动安装依赖 - 修复 ListTools VerifyTool 递归栈溢出导致获取工具配置失败 - Node.js Alpine 安装改用 unofficial-builds musl 预编译包与 index.json 解析 LTS - 站点拨测:详情页路由、告警自动恢复、并发拨测与默认可观测频率 - 日志中心:RBAC、查询构建器、ES/Loki 增强与审计 request/response 展示 - 发布配置:构建节点、流水线远程执行、Deploy RBAC 导航拆分
This commit is contained in:
1 parent
cde7bbe540
commit
7e1c8f583c
84 files changed
+7729
-1507
No files matched your search
@@ -0,0 +1,224 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"ezops/model"
|
||||
"ezops/pkg/remoteexec"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// ListBuildNodes 构建节点列表 // Logan.Li
|
||||
func (h *PipelineHandler) ListBuildNodes(c *gin.Context) {
|
||||
masterCfg := h.ensureBuildMasterConfig()
|
||||
var nodes []model.BuildNode
|
||||
h.db.Order("id ASC").Find(&nodes)
|
||||
|
||||
// 填充运行时负载与服务器信息
|
||||
nodeMu.Lock()
|
||||
for i := range nodes {
|
||||
nodes[i].RunningCount = nodeRunningCount[nodes[i].ID]
|
||||
var srv model.Server
|
||||
if err := h.db.First(&srv, nodes[i].ServerID).Error; err == nil {
|
||||
nodes[i].Hostname = srv.Hostname
|
||||
nodes[i].IP = srv.IP
|
||||
}
|
||||
}
|
||||
masterRunning := nodeRunningCount[0]
|
||||
nodeMu.Unlock()
|
||||
|
||||
c.JSON(http.StatusOK, model.Response{
|
||||
Code: 200,
|
||||
Data: map[string]interface{}{
|
||||
"list": nodes,
|
||||
"total": len(nodes),
|
||||
"master": map[string]interface{}{
|
||||
"name": "Master",
|
||||
"max_executors": masterCfg.MaxExecutors,
|
||||
"running_count": masterRunning,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// CreateBuildNode 创建构建节点 // Logan.Li
|
||||
func (h *PipelineHandler) CreateBuildNode(c *gin.Context) {
|
||||
var req model.CreateBuildNodeRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "参数错误: " + err.Error()})
|
||||
return
|
||||
}
|
||||
req.Name = strings.TrimSpace(req.Name)
|
||||
if req.Name == "" {
|
||||
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "节点名称不能为空"})
|
||||
return
|
||||
}
|
||||
var srv model.Server
|
||||
if err := h.db.First(&srv, req.ServerID).Error; err != nil {
|
||||
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "关联服务器不存在"})
|
||||
return
|
||||
}
|
||||
var dup int64
|
||||
h.db.Model(&model.BuildNode{}).Where("name = ?", req.Name).Count(&dup)
|
||||
if dup > 0 {
|
||||
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "节点名称已存在"})
|
||||
return
|
||||
}
|
||||
|
||||
node := model.BuildNode{
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
ServerID: req.ServerID,
|
||||
Labels: req.Labels,
|
||||
MaxExecutors: req.MaxExecutors,
|
||||
WorkspacePath: req.WorkspacePath,
|
||||
Enabled: true,
|
||||
}
|
||||
if node.MaxExecutors <= 0 {
|
||||
node.MaxExecutors = 2
|
||||
}
|
||||
if strings.TrimSpace(node.WorkspacePath) == "" {
|
||||
node.WorkspacePath = "/data/aiops-workspace"
|
||||
}
|
||||
|
||||
if err := h.db.Create(&node).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "创建失败"})
|
||||
return
|
||||
}
|
||||
node.Hostname = srv.Hostname
|
||||
node.IP = srv.IP
|
||||
c.JSON(http.StatusOK, model.Response{Code: 200, Message: "创建成功", Data: node})
|
||||
}
|
||||
|
||||
// UpdateBuildNode 更新构建节点 // Logan.Li
|
||||
func (h *PipelineHandler) UpdateBuildNode(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
var node model.BuildNode
|
||||
if err := h.db.First(&node, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, model.Response{Code: 404, Message: "节点不存在"})
|
||||
return
|
||||
}
|
||||
var req model.UpdateBuildNodeRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "参数错误"})
|
||||
return
|
||||
}
|
||||
updates := map[string]interface{}{}
|
||||
if req.Name != "" {
|
||||
updates["name"] = strings.TrimSpace(req.Name)
|
||||
}
|
||||
if req.Description != "" {
|
||||
updates["description"] = req.Description
|
||||
}
|
||||
if req.ServerID != nil {
|
||||
var srv model.Server
|
||||
if err := h.db.First(&srv, *req.ServerID).Error; err != nil {
|
||||
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "关联服务器不存在"})
|
||||
return
|
||||
}
|
||||
updates["server_id"] = *req.ServerID
|
||||
}
|
||||
if req.Labels != "" {
|
||||
updates["labels"] = req.Labels
|
||||
}
|
||||
if req.MaxExecutors > 0 {
|
||||
updates["max_executors"] = req.MaxExecutors
|
||||
}
|
||||
if req.WorkspacePath != "" {
|
||||
updates["workspace_path"] = req.WorkspacePath
|
||||
}
|
||||
if req.Enabled != nil {
|
||||
updates["enabled"] = *req.Enabled
|
||||
}
|
||||
if len(updates) == 0 {
|
||||
c.JSON(http.StatusOK, model.Response{Code: 200, Data: node})
|
||||
return
|
||||
}
|
||||
if err := h.db.Model(&node).Updates(updates).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "更新失败"})
|
||||
return
|
||||
}
|
||||
h.db.First(&node, id)
|
||||
c.JSON(http.StatusOK, model.Response{Code: 200, Message: "更新成功", Data: node})
|
||||
}
|
||||
|
||||
// DeleteBuildNode 删除构建节点 // Logan.Li
|
||||
func (h *PipelineHandler) DeleteBuildNode(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
var inUse int64
|
||||
h.db.Model(&model.Pipeline{}).Where("build_node_id = ?", id).Count(&inUse)
|
||||
if inUse > 0 {
|
||||
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "仍有流水线绑定此节点,请先解除关联"})
|
||||
return
|
||||
}
|
||||
if err := h.db.Delete(&model.BuildNode{}, id).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "删除失败"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, model.Response{Code: 200, Message: "删除成功"})
|
||||
}
|
||||
|
||||
// TestBuildNode 测试构建节点 SSH 连通性 // Logan.Li
|
||||
func (h *PipelineHandler) TestBuildNode(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
var node model.BuildNode
|
||||
if err := h.db.First(&node, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, model.Response{Code: 404, Message: "节点不存在"})
|
||||
return
|
||||
}
|
||||
var srv model.Server
|
||||
if err := h.db.First(&srv, node.ServerID).Error; err != nil {
|
||||
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "关联服务器不存在"})
|
||||
return
|
||||
}
|
||||
cfg, err := buildSSHConfig(h.db, srv)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, model.Response{Code: 200, Data: map[string]interface{}{
|
||||
"success": false, "message": err.Error(),
|
||||
}})
|
||||
return
|
||||
}
|
||||
result, err := remoteexec.Exec(cfg, "echo aiops-node-ok && uname -a", 15*time.Second)
|
||||
if err != nil || !result.Success {
|
||||
msg := ""
|
||||
if result != nil {
|
||||
msg = result.Error
|
||||
if msg == "" {
|
||||
msg = result.Output
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
msg = err.Error()
|
||||
}
|
||||
c.JSON(http.StatusOK, model.Response{Code: 200, Data: map[string]interface{}{
|
||||
"success": false, "message": msg,
|
||||
}})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, model.Response{Code: 200, Data: map[string]interface{}{
|
||||
"success": true, "message": strings.TrimSpace(result.Output),
|
||||
}})
|
||||
}
|
||||
|
||||
// UpdateBuildMaster 更新 Master 内置节点最大并发(1=串行按触发顺序排队) // Logan.Li
|
||||
func (h *PipelineHandler) UpdateBuildMaster(c *gin.Context) {
|
||||
var req model.UpdateBuildMasterRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "参数错误: " + err.Error()})
|
||||
return
|
||||
}
|
||||
cfg := h.setMasterMaxExecutors(req.MaxExecutors)
|
||||
nodeMu.Lock()
|
||||
running := nodeRunningCount[masterNodeID]
|
||||
nodeMu.Unlock()
|
||||
c.JSON(http.StatusOK, model.Response{
|
||||
Code: 200, Message: "已更新",
|
||||
Data: map[string]interface{}{
|
||||
"name": "Master", "max_executors": cfg.MaxExecutors, "running_count": running,
|
||||
},
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user