301 lines
8.5 KiB
Go
301 lines
8.5 KiB
Go
package api
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"oci-portal/internal/model"
|
|
"oci-portal/internal/service"
|
|
)
|
|
|
|
// logEventHandler 处理回传事件查询与每租户回调地址管理(JWT 组内)。
|
|
type logEventHandler struct {
|
|
svc *service.LogEventService
|
|
}
|
|
|
|
// getWebhook 查询配置的回调地址;未生成时 exists 为 false 且不含 secret。
|
|
//
|
|
// @Summary 查询配置的回调地址
|
|
// @Tags 任务与日志回传
|
|
// @Param id path int true "配置 ID"
|
|
// @Success 200 {object} map[string]any
|
|
// @Security BearerAuth
|
|
// @Router /api/v1/oci-configs/{id}/log-webhook [get]
|
|
func (h *logEventHandler) getWebhook(c *gin.Context) {
|
|
id, ok := pathID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
info, exists, err := h.svc.SecretInfo(c.Request.Context(), id)
|
|
if err != nil {
|
|
respondError(c, err)
|
|
return
|
|
}
|
|
if !exists {
|
|
c.JSON(http.StatusOK, gin.H{"exists": false})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"exists": true, "webhook": info})
|
|
}
|
|
|
|
// ensureWebhook 生成(或幂等返回)配置的回传 secret 与回调路径。
|
|
//
|
|
// @Summary 生成
|
|
// @Tags 任务与日志回传
|
|
// @Param id path int true "配置 ID"
|
|
// @Success 200 {object} map[string]any
|
|
// @Security BearerAuth
|
|
// @Router /api/v1/oci-configs/{id}/log-webhook [post]
|
|
func (h *logEventHandler) ensureWebhook(c *gin.Context) {
|
|
id, ok := pathID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
info, err := h.svc.EnsureSecret(c.Request.Context(), id)
|
|
if err != nil {
|
|
respondError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, info)
|
|
}
|
|
|
|
// revokeWebhook 撤销配置的回传 secret,旧回调地址随即失效。
|
|
//
|
|
// @Summary 撤销配置的回传 secret,旧回调地址随即失效
|
|
// @Tags 任务与日志回传
|
|
// @Param id path int true "配置 ID"
|
|
// @Success 204 "无内容"
|
|
// @Security BearerAuth
|
|
// @Router /api/v1/oci-configs/{id}/log-webhook [delete]
|
|
func (h *logEventHandler) revokeWebhook(c *gin.Context) {
|
|
id, ok := pathID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
if err := h.svc.RevokeSecret(c.Request.Context(), id); err != nil {
|
|
respondError(c, err)
|
|
return
|
|
}
|
|
c.Status(http.StatusNoContent)
|
|
}
|
|
|
|
// list 分页查询回传事件;cfgId 缺省为全部租户。
|
|
//
|
|
// @Summary 回传日志事件列表
|
|
// @Tags 任务与日志回传
|
|
// @Param configId query int false "按配置过滤"
|
|
// @Param q query string false "关键字"
|
|
// @Success 200 {object} map[string]any "items 与 total"
|
|
// @Security BearerAuth
|
|
// @Router /api/v1/log-events [get]
|
|
func (h *logEventHandler) list(c *gin.Context) {
|
|
cfgID, _ := strconv.ParseUint(c.Query("cfgId"), 10, 64)
|
|
page, _ := strconv.Atoi(c.Query("page"))
|
|
pageSize, _ := strconv.Atoi(c.Query("pageSize"))
|
|
items, total, err := h.svc.List(c.Request.Context(), service.LogEventQuery{
|
|
CfgID: uint(cfgID),
|
|
Page: page,
|
|
PageSize: pageSize,
|
|
})
|
|
if err != nil {
|
|
respondError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"items": items, "total": total})
|
|
}
|
|
|
|
// alertRuleRequest 是创建/更新告警规则的请求体。
|
|
type alertRuleRequest struct {
|
|
Name string `json:"name"`
|
|
Enabled bool `json:"enabled"`
|
|
OciConfigID uint `json:"ociConfigId"`
|
|
EventTypes string `json:"eventTypes"`
|
|
SourceIPs string `json:"sourceIps"`
|
|
SourceIPMode string `json:"sourceIpMode"`
|
|
ResourceMatch string `json:"resourceMatch"`
|
|
Threshold int `json:"threshold"`
|
|
WindowMinutes int `json:"windowMinutes"`
|
|
}
|
|
|
|
// toModel 转为规则模型(默认阈值 1)。
|
|
func (r alertRuleRequest) toModel() model.AlertRule {
|
|
if r.Threshold == 0 {
|
|
r.Threshold = 1
|
|
}
|
|
return model.AlertRule{
|
|
Name: r.Name, Enabled: r.Enabled, OciConfigID: r.OciConfigID,
|
|
EventTypes: r.EventTypes, SourceIPs: r.SourceIPs, SourceIPMode: r.SourceIPMode,
|
|
ResourceMatch: r.ResourceMatch, Threshold: r.Threshold, WindowMinutes: r.WindowMinutes,
|
|
}
|
|
}
|
|
|
|
// listAlertRules 返回全部告警规则。
|
|
//
|
|
// @Summary 告警规则列表
|
|
// @Tags 任务与日志回传
|
|
// @Success 200 {object} map[string]any
|
|
// @Security BearerAuth
|
|
// @Router /api/v1/log-events/alert-rules [get]
|
|
func (h *logEventHandler) listAlertRules(c *gin.Context) {
|
|
rules, err := h.svc.ListAlertRules(c.Request.Context())
|
|
if err != nil {
|
|
respondError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"items": rules})
|
|
}
|
|
|
|
// createAlertRule 创建告警规则;字段非法返回 400。
|
|
//
|
|
// @Summary 创建告警规则
|
|
// @Tags 任务与日志回传
|
|
// @Param body body alertRuleRequest true "请求体"
|
|
// @Success 201 {object} map[string]any
|
|
// @Security BearerAuth
|
|
// @Router /api/v1/log-events/alert-rules [post]
|
|
func (h *logEventHandler) createAlertRule(c *gin.Context) {
|
|
var req alertRuleRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
rule, err := h.svc.CreateAlertRule(c.Request.Context(), req.toModel())
|
|
if err != nil {
|
|
respondAlertRuleError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusCreated, rule)
|
|
}
|
|
|
|
// updateAlertRule 整体覆盖告警规则(含启停)。
|
|
//
|
|
// @Summary 更新告警规则
|
|
// @Tags 任务与日志回传
|
|
// @Param ruleId path int true "规则 ID"
|
|
// @Param body body alertRuleRequest true "请求体"
|
|
// @Success 200 {object} map[string]any
|
|
// @Security BearerAuth
|
|
// @Router /api/v1/log-events/alert-rules/{ruleId} [put]
|
|
func (h *logEventHandler) updateAlertRule(c *gin.Context) {
|
|
id, err := strconv.ParseUint(c.Param("ruleId"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "规则 ID 非法"})
|
|
return
|
|
}
|
|
var req alertRuleRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
rule, err := h.svc.UpdateAlertRule(c.Request.Context(), uint(id), req.toModel())
|
|
if err != nil {
|
|
respondAlertRuleError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, rule)
|
|
}
|
|
|
|
// deleteAlertRule 删除告警规则及其命中记录。
|
|
//
|
|
// @Summary 删除告警规则
|
|
// @Tags 任务与日志回传
|
|
// @Param ruleId path int true "规则 ID"
|
|
// @Success 204 "无内容"
|
|
// @Security BearerAuth
|
|
// @Router /api/v1/log-events/alert-rules/{ruleId} [delete]
|
|
func (h *logEventHandler) deleteAlertRule(c *gin.Context) {
|
|
id, err := strconv.ParseUint(c.Param("ruleId"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "规则 ID 非法"})
|
|
return
|
|
}
|
|
if err := h.svc.DeleteAlertRule(c.Request.Context(), uint(id)); err != nil {
|
|
respondError(c, err)
|
|
return
|
|
}
|
|
c.Status(http.StatusNoContent)
|
|
}
|
|
|
|
// respondAlertRuleError 把字段校验错误映射为 400。
|
|
func respondAlertRuleError(c *gin.Context, err error) {
|
|
if errors.Is(err, service.ErrInvalidAlertRule) {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
respondError(c, err)
|
|
}
|
|
|
|
// getRelay 查询 OCI 侧链路状态与关键事件清单。
|
|
//
|
|
// @Summary 查询 OCI 侧链路状态与关键事件清单
|
|
// @Tags 任务与日志回传
|
|
// @Param id path int true "配置 ID"
|
|
// @Success 200 {object} map[string]any
|
|
// @Security BearerAuth
|
|
// @Router /api/v1/oci-configs/{id}/log-relay [get]
|
|
func (h *logEventHandler) getRelay(c *gin.Context) {
|
|
id, ok := pathID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
view, err := h.svc.RelayStatus(c.Request.Context(), id)
|
|
if err != nil {
|
|
respondRelayError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, view)
|
|
}
|
|
|
|
// setupRelay 一键建立回传链路(P1 引导创建);同步执行,前端以长超时等待。
|
|
//
|
|
// @Summary 一键建立回传链路
|
|
// @Tags 任务与日志回传
|
|
// @Param id path int true "配置 ID"
|
|
// @Success 200 {object} map[string]any
|
|
// @Security BearerAuth
|
|
// @Router /api/v1/oci-configs/{id}/log-relay [post]
|
|
func (h *logEventHandler) setupRelay(c *gin.Context) {
|
|
id, ok := pathID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
view, err := h.svc.SetupRelay(c.Request.Context(), id)
|
|
if err != nil {
|
|
respondRelayError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, view)
|
|
}
|
|
|
|
// teardownRelay 逆序销毁链路并撤销 secret。
|
|
//
|
|
// @Summary 逆序销毁链路并撤销 secret
|
|
// @Tags 任务与日志回传
|
|
// @Param id path int true "配置 ID"
|
|
// @Success 204 "无内容"
|
|
// @Security BearerAuth
|
|
// @Router /api/v1/oci-configs/{id}/log-relay [delete]
|
|
func (h *logEventHandler) teardownRelay(c *gin.Context) {
|
|
id, ok := pathID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
if err := h.svc.TeardownRelay(c.Request.Context(), id); err != nil {
|
|
respondRelayError(c, err)
|
|
return
|
|
}
|
|
c.Status(http.StatusNoContent)
|
|
}
|
|
|
|
// respondRelayError 在通用映射之上补充引导创建专属错误:依赖未配置映射 409。
|
|
func respondRelayError(c *gin.Context, err error) {
|
|
if errors.Is(err, service.ErrRelayNotConfigured) {
|
|
c.JSON(http.StatusConflict, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
respondError(c, err)
|
|
}
|