Add the Loki query UI, access policies, ingress insights, alert and registry settings, and their backend APIs. Include the related Kubernetes deployment and collector updates required by the integrated application. Validated with go test ./..., npm run build, and npm run lint.
284 lines
8.0 KiB
Go
284 lines
8.0 KiB
Go
package controllers
|
|
|
|
import (
|
|
"context"
|
|
"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
|
|
}
|
|
selector, err := services.ContextLogSelector(req.Labels)
|
|
if err != nil {
|
|
utils.BadRequest(c, err.Error())
|
|
return
|
|
}
|
|
query, err := services.AuthorizedLogQuery(src, c.GetUint("userId"), c.GetString("role") == "admin", selector)
|
|
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, 5, "backward", "")
|
|
if err != nil {
|
|
utils.Error(c, 502, err.Error())
|
|
return
|
|
}
|
|
after, err := services.QueryLokiLogs(c.Request.Context(), src, query, ts+1, ts+span, 5, "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 src := services.GetLogSource(uint(id)); src != nil && src.Managed && updates["isDefault"] == nil {
|
|
utils.BadRequest(c, "平台部署的数据源由系统管理,不可编辑")
|
|
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 src := services.GetLogSource(uint(id)); src != nil && src.Managed {
|
|
utils.BadRequest(c, "平台部署的数据源随部署管理,请先删除对应部署")
|
|
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)
|
|
}
|