Files
LokiStack/backend/controllers/collector.go
T
opsx c8d026adc7
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
feat(collectors): DaemonSet 不再填日志路径,改为「只采集哪些命名空间」多选
用户指出的三个问题,逐条核实后的结论:

1) 「采集日志路径」对 DaemonSet 确实是多余的 —— 确认后端模板里写死了
   `replacement: /var/log/pods/*$1/*.log`(按 pod UID 精确匹配容器标准输出),
   表单里填的 logPath 对 DaemonSet 完全不参与渲染。
   → 选择 DaemonSet 时隐藏该字段,提交时强制置空。

2) 「可以指定采集多个命名空间」—— 后端**早就支持**:
   models.Collector.TargetNamespaces + promtailNamespaceFilter(),
   空数组 = 全集群。只是**前端新建弹框没暴露**(编辑弹框里有)。
   → 新建弹框补上多选 chips(全集群 / 各命名空间可点选),并补齐前后端透传:
     dto.CreateCollectorRequest.TargetNamespaces
     controller → services.CreateCollectorRequest.TargetNamespaces
     models.Collector.TargetNamespaces
   之前前端即使传了也会被 Go 静默丢弃(无该字段)。

3) 「命名空间」字段语义已在上一提交补了分类型说明:
   - DaemonSet:采集器自身部署在哪个命名空间(不影响采什么)
   - Sidecar:目标应用 Deployment 所在命名空间

新增 TestDaemonsetTargetNamespacesFilter:
  nil → 不生成 namespaces 白名单(全集群)
  ["astralx"] → 生成且包含 astralx
  ["astralx","default"] → 两个都在

=== 实测(d45dfa18ecc5)===
  前端文案「只采集这些命名空间」「不选 = 采集整个集群…」均已进包
  渲染测试 3 种输入全部符合预期
  注:本次 docker push latest 遇到 Docker Hub 502,v0.1 成功而 latest 仍指向旧镜像,
     导致首次滚动后 Pod 仍是旧代码;重推 latest + 强制重建 Pod 后确认
     Pod digest = d45dfa18ecc5 —— 记录这个坑:push 失败必须核对三方 digest。
2026-10-30 16:03:24 +00:00

159 lines
4.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package controllers
import (
"strconv"
"lokistack/dto"
"lokistack/models"
"lokistack/services"
"lokistack/utils"
"github.com/gin-gonic/gin"
)
type CollectorController struct {
service *services.CollectorService
}
func NewCollectorController() *CollectorController {
return &CollectorController{service: services.NewCollectorService()}
}
func (ctrl *CollectorController) Create(c *gin.Context) {
var req dto.CreateCollectorRequest
if !utils.BindAndValidate(c, &req) {
return
}
collector, err := ctrl.service.Create(&services.CreateCollectorRequest{
Name: req.Name, Type: req.Type, LokiDeploymentID: req.LokiDeploymentID,
HostID: req.HostID, ClusterID: req.ClusterID, TargetDeployment: req.TargetDeployment,
Namespace: req.Namespace, LogPath: req.LogPath, Labels: req.Labels,
ScrapeJobs: req.ScrapeJobs, TargetNamespaces: req.TargetNamespaces,
})
if err != nil {
utils.Error(c, 400, err.Error())
return
}
utils.Success(c, models.SanitizedJSON(collector))
}
func (ctrl *CollectorController) Get(c *gin.Context) {
id, err := utils.IntParam(c, "id")
if err != nil {
utils.BadRequest(c, "ID 无效")
return
}
collector, err := ctrl.service.Get(uint(id))
if err != nil {
utils.NotFound(c, "采集器不存在")
return
}
utils.Success(c, models.SanitizedJSON(collector))
}
func (ctrl *CollectorController) List(c *gin.Context) {
page, pageSize := utils.Pagination(c)
collectors, total, err := ctrl.service.List(page, pageSize)
if err != nil {
utils.Error(c, 500, err.Error())
return
}
utils.Success(c, utils.PageData{
List: models.SanitizedJSON(collectors), Total: total, Page: page, PageSize: pageSize,
})
}
func (ctrl *CollectorController) Update(c *gin.Context) {
id, err := utils.IntParam(c, "id")
if err != nil {
utils.BadRequest(c, "ID 无效")
return
}
var updates map[string]interface{}
if !utils.BindAndValidate(c, &updates) {
return
}
if err := ctrl.service.Update(uint(id), updates); err != nil {
utils.Error(c, 400, err.Error())
return
}
utils.Success(c, gin.H{"message": "更新成功"})
}
func (ctrl *CollectorController) Delete(c *gin.Context) {
id, err := utils.IntParam(c, "id")
if err != nil {
utils.BadRequest(c, "ID 无效")
return
}
if err := ctrl.service.Delete(uint(id)); err != nil {
utils.Error(c, 400, err.Error())
return
}
utils.Success(c, gin.H{"message": "删除成功"})
}
func (ctrl *CollectorController) Install(c *gin.Context) {
id, err := utils.IntParam(c, "id")
if err != nil {
utils.BadRequest(c, "ID 无效")
return
}
go ctrl.service.Install(uint(id))
utils.Success(c, gin.H{"message": "已开始部署采集器"})
}
// Preview 渲染「当前配置 + 本次改动」的 promtail.yaml,只预览不落库。
// 供结构化编辑器里的「预览 YAML」按钮使用:用户能先看到将要写到目标机的配置,
// 保存前就发现路径/正则写错,而不是点了部署才发现采集起不来。
func (ctrl *CollectorController) Preview(c *gin.Context) {
id, err := utils.IntParam(c, "id")
if err != nil {
utils.BadRequest(c, "ID 无效")
return
}
var updates map[string]interface{}
if !utils.BindAndValidate(c, &updates) {
return
}
out, err := ctrl.service.PreviewConfig(uint(id), updates)
if err != nil {
utils.Error(c, 400, err.Error())
return
}
utils.Success(c, out)
}
func (ctrl *CollectorController) Uninstall(c *gin.Context) {
id, err := utils.IntParam(c, "id")
if err != nil {
utils.BadRequest(c, "ID 无效")
return
}
go ctrl.service.Uninstall(uint(id))
utils.Success(c, gin.H{"message": "已开始卸载采集器"})
}
// Logs 返回采集器最近的运行日志(主机:docker logs / journalctl;K8s:Pod 日志)。
// 主机模式下采集器日志原来只能登机器看 docker logs / journalctl,容器化后这是
// 顺带拿到的能力,排「采集器起没起来、在报什么错」不用再 SSH。
func (ctrl *CollectorController) Logs(c *gin.Context) {
id, err := utils.IntParam(c, "id")
if err != nil {
utils.BadRequest(c, "ID 无效")
return
}
tail := 200
if v := c.Query("tail"); v != "" {
if n, convErr := strconv.Atoi(v); convErr == nil && n > 0 {
tail = n
}
}
out, err := ctrl.service.CollectorLogs(uint(id), tail)
if err != nil {
utils.Error(c, 400, err.Error())
return
}
utils.Success(c, out)
}