package controllers import ( "context" "encoding/json" "strconv" "strings" "time" "opsxloki/dto" "opsxloki/models" "opsxloki/services" "opsxloki/utils" "github.com/gin-gonic/gin" ) func (ctrl *LogsController) Context(c *gin.Context) { var req dto.LogContextRequest if !utils.BindAndValidate(c, &req) { return } src := services.GetLogSource(req.SourceID) if src == nil { utils.BadRequest(c, "数据源不存在") return } ts, err := strconv.ParseInt(req.TimestampNs, 10, 64) if err != nil || ts < 1 { utils.BadRequest(c, "时间戳无效") return } parts := make([]string, 0, len(req.Labels)) for key, value := range req.Labels { if !services.ValidLogLabelName(key) { utils.BadRequest(c, "标签名无效") return } encoded, _ := json.Marshal(value) parts = append(parts, key+"="+string(encoded)) } query, err := services.AuthorizedLogQuery(src, c.GetUint("userId"), c.GetString("role") == "admin", "{"+strings.Join(parts, ",")+"}") if err != nil { utils.Forbidden(c, err.Error()) return } span := int64(5 * time.Minute) before, err := services.QueryLokiLogs(c.Request.Context(), src, query, ts-span, ts-1, 1, "backward", "") if err != nil { utils.Error(c, 502, err.Error()) return } after, err := services.QueryLokiLogs(c.Request.Context(), src, query, ts+1, ts+span, 1, "forward", "") if err != nil { utils.Error(c, 502, err.Error()) return } utils.Success(c, gin.H{"before": before.Entries, "after": after.Entries}) } // LogsController 日志查询代理 + 数据源管理 type LogsController struct{} func NewLogsController() *LogsController { return &LogsController{} } // ==================== 数据源 ==================== func (ctrl *LogsController) ListSources(c *gin.Context) { list := services.ListVisibleLogSources(c.GetUint("userId"), c.GetString("role") == "admin") utils.Success(c, utils.PageData{List: list, Total: int64(len(list)), Page: 1, PageSize: 100}) } func (ctrl *LogsController) CreateSource(c *gin.Context) { var req dto.LogSourceRequest if !utils.BindAndValidate(c, &req) { return } if req.Name == "" || req.TargetType == "" { utils.BadRequest(c, "名称与类型必填") return } if err := services.ValidateSourceAccess(req.Access); err != nil { utils.BadRequest(c, err.Error()) return } src := &models.LogSource{ Name: req.Name, TargetType: req.TargetType, Namespace: req.Namespace, Service: req.Service, Port: req.Port, URL: req.URL, AuthType: req.AuthType, BasicUser: req.BasicUser, BasicPass: req.BasicPass, BearerToken: req.BearerToken, Headers: req.Headers, SkipTLSVerify: req.SkipTLSVerify, IsDefault: req.IsDefault, Access: req.Access, } if req.TargetType == "cluster-svc" { if req.ClusterID > 0 { id := req.ClusterID src.ClusterID = &id } else { utils.BadRequest(c, "cluster-svc 类型必须选择集群") return } } created, err := services.CreateLogSource(src) if err != nil { utils.Error(c, 400, err.Error()) return } utils.Success(c, created.Sanitized()) } func (ctrl *LogsController) UpdateSource(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { utils.BadRequest(c, "ID 无效") return } var updates map[string]interface{} if !utils.BindAndValidate(c, &updates) { return } if err := services.UpdateLogSource(uint(id), updates); err != nil { utils.Error(c, 400, err.Error()) return } utils.Success(c, gin.H{"message": "updated"}) } func (ctrl *LogsController) DeleteSource(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { utils.BadRequest(c, "ID 无效") return } if err := services.DeleteLogSource(uint(id)); err != nil { utils.Error(c, 400, err.Error()) return } utils.Success(c, gin.H{"message": "deleted"}) } // TestSource 数据源连通性测试(按 id 或表单参数) func (ctrl *LogsController) TestSource(c *gin.Context) { var req dto.LogSourceRequest if !utils.BindAndValidate(c, &req) { return } var src *models.LogSource if req.ID > 0 { src = services.GetLogSource(req.ID) if src == nil { utils.NotFound(c, "数据源不存在") return } } else { src = &models.LogSource{ TargetType: req.TargetType, Namespace: req.Namespace, Service: req.Service, Port: req.Port, URL: req.URL, AuthType: req.AuthType, BasicUser: req.BasicUser, BasicPass: req.BasicPass, BearerToken: req.BearerToken, Headers: req.Headers, SkipTLSVerify: req.SkipTLSVerify, } if req.TargetType == "cluster-svc" && req.ClusterID > 0 { id := req.ClusterID src.ClusterID = &id } } utils.Success(c, services.TestLogSource(c.Request.Context(), src)) } // ==================== 查询代理 ==================== func (ctrl *LogsController) Query(c *gin.Context) { var req dto.LogQueryRequest if !utils.BindAndValidate(c, &req) { return } src := services.GetLogSource(req.SourceID) if src == nil { utils.BadRequest(c, "数据源不存在") return } query, err := services.AuthorizedLogQuery(src, c.GetUint("userId"), c.GetString("role") == "admin", req.Query) if err != nil { utils.Forbidden(c, err.Error()) return } result, err := services.QueryLokiLogs(c.Request.Context(), src, query, req.Start, req.End, req.Limit, req.Direction, req.Cursor) if err != nil { utils.Error(c, 502, err.Error()) return } utils.Success(c, result) } func (ctrl *LogsController) Labels(c *gin.Context) { id, _ := strconv.ParseUint(c.Query("sourceId"), 10, 64) src := services.GetLogSource(uint(id)) if src == nil { utils.BadRequest(c, "数据源不存在") return } if _, err := services.AuthorizedLogQuery(src, c.GetUint("userId"), c.GetString("role") == "admin", "{}"); err != nil { utils.Forbidden(c, err.Error()) return } start, _ := strconv.ParseInt(c.Query("start"), 10, 64) end, _ := strconv.ParseInt(c.Query("end"), 10, 64) labels, err := services.LokiLabels(c.Request.Context(), src, start, end) if err != nil { utils.Error(c, 502, err.Error()) return } utils.Success(c, labels) } func (ctrl *LogsController) LabelValues(c *gin.Context) { id, _ := strconv.ParseUint(c.Query("sourceId"), 10, 64) src := services.GetLogSource(uint(id)) if src == nil { utils.BadRequest(c, "数据源不存在") return } start, _ := strconv.ParseInt(c.Query("start"), 10, 64) end, _ := strconv.ParseInt(c.Query("end"), 10, 64) requestedQuery := c.Query("query") if strings.TrimSpace(requestedQuery) == "" { requestedQuery = "{}" } query, err := services.AuthorizedLogQuery(src, c.GetUint("userId"), c.GetString("role") == "admin", requestedQuery) if err != nil { utils.Forbidden(c, err.Error()) return } values, err := services.LokiLabelValues(c.Request.Context(), src, c.Param("name"), query, start, end) if err != nil { utils.Error(c, 502, err.Error()) return } utils.Success(c, values) } // Health Loki /ready 探活(查询页状态点) func (ctrl *LogsController) Health(c *gin.Context) { id, _ := strconv.ParseUint(c.Query("sourceId"), 10, 64) src := services.GetLogSource(uint(id)) if src == nil { utils.BadRequest(c, "数据源不存在") return } if _, err := services.AuthorizedLogQuery(src, c.GetUint("userId"), c.GetString("role") == "admin", "{}"); err != nil { utils.Forbidden(c, err.Error()) return } ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second) defer cancel() utils.Success(c, gin.H{"ready": services.LokiReady(ctx, src)}) } func (ctrl *LogsController) Histogram(c *gin.Context) { var req dto.LogQueryRequest if !utils.BindAndValidate(c, &req) { return } src := services.GetLogSource(req.SourceID) if src == nil { utils.BadRequest(c, "数据源不存在") return } query, err := services.AuthorizedLogQuery(src, c.GetUint("userId"), c.GetString("role") == "admin", req.Query) if err != nil { utils.Forbidden(c, err.Error()) return } buckets, err := services.LokiHistogram(c.Request.Context(), src, query, req.Start, req.End) if err != nil { utils.Error(c, 502, err.Error()) return } utils.Success(c, buckets) }