feat: complete Loki query, insights, settings and deployment workflows

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.
This commit is contained in:
logan committed 2026-10-25 21:28:32 +08:00
1 parent f9c6c1d826
commit 2d73661dd1
127 files changed
+8117 -2876

No files matched your search

+42
View File
@@ -0,0 +1,42 @@
package models
import "time"
// AlertRule 日志告警规则:对选定数据源周期执行 LogQL 计数,超过阈值即通知。
// 参照运维侧 app-alert.py 的两类模式:
//
// critical(出现即报,threshold=1)/ surge(突增才报,自定义阈值),统一收敛为「N 条 / 窗口 > 阈值」。
type AlertRule struct {
ID uint `json:"id"`
Name string `json:"name"`
SourceID uint `json:"sourceId"` // LogSource
ChannelIDs []uint `json:"channelIds"` // NotifyChannel 列表(告警扇出到多个群)
Query string `json:"query"` // 完整 LogQL({app="x"} |= "FATAL")
WindowMin int `json:"windowMin"` // 统计窗口(分钟)
Threshold int `json:"threshold"` // 窗口内条数 > 阈值触发
Enabled bool `json:"enabled"`
CooldownMin int `json:"cooldownMin"` // 同规则告警冷却(分钟,默认 60)
LastFiredAt time.Time `json:"lastFiredAt"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
func (r *AlertRule) Sanitized() map[string]interface{} {
return map[string]interface{}{
"id": r.ID, "name": r.Name, "sourceId": r.SourceID, "channelIds": r.ChannelIDs,
"query": r.Query, "windowMin": r.WindowMin, "threshold": r.Threshold,
"enabled": r.Enabled, "cooldownMin": r.CooldownMin,
"lastFiredAt": r.LastFiredAt, "createdAt": r.CreatedAt, "updatedAt": r.UpdatedAt,
}
}
// AlertDelivery 告警发送历史(飞书 webhook 投递记录)
type AlertDelivery struct {
ID uint `json:"id"`
RuleID uint `json:"ruleId"`
RuleName string `json:"ruleName"`
Count int `json:"count"` // 窗口内命中条数
Status string `json:"status"` // sent | failed | cooldown-skipped
Reason string `json:"reason"`
CreatedAt time.Time `json:"createdAt"`
}
+4
View File
@@ -17,6 +17,8 @@ type LokiDeployment struct {
Cluster *Cluster `json:"cluster,omitempty"`
HostID *uint `json:"hostID,omitempty"`
Host *Host `json:"host,omitempty"`
ListenIP string `json:"listenIP,omitempty"`
ListenPort int `json:"listenPort,omitempty"`
Namespace string `json:"namespace"`
StorageConfigID uint `json:"storageConfigID"`
StorageConfig *StorageConfig `json:"storageConfig,omitempty"`
@@ -58,6 +60,8 @@ func (d *LokiDeployment) Sanitized() map[string]interface{} {
"mode": d.Mode,
"clusterID": d.ClusterID,
"hostID": d.HostID,
"listenIP": d.ListenIP,
"listenPort": d.ListenPort,
"namespace": d.Namespace,
"storageConfigID": d.StorageConfigID,
"authEnabled": d.AuthEnabled,
+2 -1
View File
@@ -24,6 +24,7 @@ type LogSource struct {
Headers map[string]string `json:"headers,omitempty"`
SkipTLSVerify bool `json:"skipTLSVerify"`
IsDefault bool `json:"isDefault"`
Managed bool `json:"managed"` // 平台部署自动注册(固定不可编辑)
Access []LogSourceAccess `json:"access,omitempty"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
@@ -34,7 +35,7 @@ func (s *LogSource) Sanitized() map[string]interface{} {
"id": s.ID, "name": s.Name, "targetType": s.TargetType,
"clusterID": s.ClusterID, "namespace": s.Namespace, "service": s.Service, "port": s.Port,
"url": s.URL, "authType": s.AuthType, "basicUser": s.BasicUser,
"hasBasicPass": s.BasicPass != "", "isDefault": s.IsDefault,
"hasBasicPass": s.BasicPass != "", "isDefault": s.IsDefault, "managed": s.Managed,
"hasBearerToken": s.BearerToken != "", "headerNames": headerNames(s.Headers), "skipTLSVerify": s.SkipTLSVerify,
"access": s.Access,
"createdAt": s.CreatedAt, "updatedAt": s.UpdatedAt,
+23
View File
@@ -0,0 +1,23 @@
package models
import "time"
// NotifyChannel 通知通道:一个飞书/Lark 群机器人 webhook。
// 支持多通道对应不同告警群(如「应用告警群」「Nginx 告警群」),告警规则各自关联。
type NotifyChannel struct {
ID uint `json:"id"`
Name string `json:"name"`
Webhook string `json:"webhook"` // ENC: 加密存储,不外发
SigningSecret string `json:"signingSecret"` // ENC: 可选签名校验
Enabled bool `json:"enabled"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
func (c *NotifyChannel) Sanitized() map[string]interface{} {
return map[string]interface{}{
"id": c.ID, "name": c.Name,
"hasWebhook": c.Webhook != "", "hasSecret": c.SigningSecret != "",
"enabled": c.Enabled, "createdAt": c.CreatedAt, "updatedAt": c.UpdatedAt,
}
}
+12 -11
View File
@@ -20,16 +20,17 @@ type StorageConfig struct {
func (s *StorageConfig) Sanitized() map[string]interface{} {
return map[string]interface{}{
"id": s.ID,
"name": s.Name,
"storageType": s.StorageType,
"endpoint": s.Endpoint,
"bucket": s.Bucket,
"region": s.Region,
"pathPrefix": s.PathPrefix,
"insecure": s.Insecure,
"isDefault": s.IsDefault,
"createdAt": s.CreatedAt,
"updatedAt": s.UpdatedAt,
"id": s.ID,
"name": s.Name,
"storageType": s.StorageType,
"endpoint": s.Endpoint,
"bucket": s.Bucket,
"region": s.Region,
"pathPrefix": s.PathPrefix,
"insecure": s.Insecure,
"isDefault": s.IsDefault,
"hasSecretKey": s.SecretKey != "",
"createdAt": s.CreatedAt,
"updatedAt": s.UpdatedAt,
}
}