Files
LokiStack/backend/services/deployment_config.go
T
opsx f3733f0bfc feat: 品牌统一 LokiStack + 全站中英 i18n + 用户批量删除 + 主题定时切换与浅色侧栏
- 品牌: Go 模块/数据文件/二进制/构建部署资产全部 opsx→lokistack;前端 localStorage 命名空间迁移(读旧写新,含按用户 id 前缀迁移);修复 nginx upstream 与 Makefile 引用已改名服务的断裂
- i18n: react-i18next 落地,common + 11 个页面域命名空间全站中英(菜单/顶栏/登录/列表页/表单/确认弹窗/状态徽章);设置页语言即时切换;修复词典自注册导致的循环导入白屏
- 用户管理: 批量删除(POST /users/batch-delete,不删自己/最后管理员,部分失败不阻断)+ 行复选/全选/批量工具条
- 主题: 浅色侧栏(中性灰激活态)、四态模式(浅色/深色/跟随系统/定时切换,按时区判定)、顶栏太阳月亮二态、设置页紧凑分段选择器
- UI: 登录记住用户名、CardActionButton 统一五页卡片操作钮、shadcn Badge 替换手写徽章、语义色修正
- 清理: 移除主机 Web 终端(前后端)、k8s-helm/Helm 遗留代码与字段、README 重写精简
- 修复: 侧栏与日志告警 order 重复、路由标题 i18n key 未翻译、词典双逗号等
2026-10-27 17:11:38 +08:00

125 lines
3.5 KiB
Go

package services
import (
"context"
"fmt"
"strings"
"gopkg.in/yaml.v3"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"time"
)
type LiveLokiConfig struct {
Location string `json:"location"`
YAML string `json:"yaml"`
}
// GetLiveConfig 读取实际运行的 Loki 配置。仅返回 loki.yaml,不返回整个 ConfigMap 元数据。
func (s *DeploymentService) GetLiveConfig(ctx context.Context, id uint) (*LiveLokiConfig, error) {
d, err := s.Get(id)
if err != nil {
return nil, err
}
var raw, location string
switch d.Mode {
case "k8s-yaml":
if d.Cluster == nil {
return nil, fmt.Errorf("部署未关联 Kubernetes 集群")
}
client, err := clusterClient(d.Cluster, 30*time.Second)
if err != nil {
return nil, err
}
ns := d.Namespace
if ns == "" {
ns = "log"
}
configMap, err := client.CoreV1().ConfigMaps(ns).Get(ctx, "loki-config", metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("无法读取 %s/loki-config: %w", ns, err)
}
raw = configMap.Data["loki.yaml"]
location = fmt.Sprintf("ConfigMap %s/loki-config → loki.yaml", ns)
case "host-binary":
if d.Host == nil {
return nil, fmt.Errorf("部署未关联主机")
}
client, err := NewSSHClientWithAuth(HostSSHAuth(d.Host))
if err != nil {
return nil, fmt.Errorf("连接部署主机失败")
}
defer client.Close()
output, _, err := client.Execute(ctx, "cat /etc/loki/loki.yaml")
if err != nil {
return nil, fmt.Errorf("无法读取主机上的 /etc/loki/loki.yaml")
}
raw = output
location = "/etc/loki/loki.yaml"
case "host-docker":
if d.Host == nil {
return nil, fmt.Errorf("部署未关联主机")
}
client, err := NewSSHClientWithAuth(HostSSHAuth(d.Host))
if err != nil {
return nil, fmt.Errorf("连接部署主机失败")
}
defer client.Close()
location = dockerConfigPath(d.ID)
output, _, err := client.Execute(ctx, "cat "+location)
if err != nil {
// 历史版本路径兜底(重部署一次后目录即迁移到新命名)
location = legacyDockerConfigDir(d.ID) + "/loki.yaml"
if output, _, err = client.Execute(ctx, "cat "+location); err != nil {
return nil, fmt.Errorf("无法读取主机上的 Loki 配置")
}
}
raw = output
default:
return nil, fmt.Errorf("当前部署模式暂不支持查看运行配置")
}
if strings.TrimSpace(raw) == "" {
return nil, fmt.Errorf("运行配置为空或尚未部署")
}
redacted, err := redactLokiYAML(raw)
if err != nil {
return nil, fmt.Errorf("运行配置无法安全展示:YAML 格式无效")
}
return &LiveLokiConfig{Location: location, YAML: redacted}, nil
}
func redactLokiYAML(source string) (string, error) {
var document yaml.Node
if err := yaml.Unmarshal([]byte(source), &document); err != nil {
return "", err
}
redactYAMLNode(&document)
output, err := yaml.Marshal(&document)
return string(output), err
}
func redactYAMLNode(node *yaml.Node) {
if node.Kind == yaml.ScalarNode && strings.HasPrefix(node.Value, "ENC:") {
node.Value = "********"
return
}
if node.Kind == yaml.MappingNode {
for i := 0; i+1 < len(node.Content); i += 2 {
key, value := node.Content[i], node.Content[i+1]
switch strings.ToLower(key.Value) {
case "access_key_id", "secret_access_key", "account_key", "password", "token", "client_secret":
value.Kind = yaml.ScalarNode
value.Tag = "!!str"
value.Value = "********"
value.Content = nil
continue
}
redactYAMLNode(value)
}
return
}
for _, child := range node.Content {
redactYAMLNode(child)
}
}