diff --git a/backend/internal/api/handlers/sql_audit.go b/backend/internal/api/handlers/sql_audit.go deleted file mode 100644 index d84d06c..0000000 --- a/backend/internal/api/handlers/sql_audit.go +++ /dev/null @@ -1,435 +0,0 @@ -package handlers - -import ( - "fmt" - "net/http" - "regexp" - "strings" - "time" - - "ezops/internal/models" - - "github.com/gin-gonic/gin" - "gorm.io/gorm" -) - -// SQLAuditHandler SQL审计处理器 // Logan.Li -type SQLAuditHandler struct { - db *gorm.DB -} - -func NewSQLAuditHandler(db *gorm.DB) *SQLAuditHandler { - return &SQLAuditHandler{db: db} -} - -// ListRecords 获取审计记录列表 // Logan.Li -func (h *SQLAuditHandler) ListRecords(c *gin.Context) { - // 分页 - page := 1 - pageSize := 50 - if c.Query("page") != "" { - fmt.Sscanf(c.Query("page"), "%d", &page) - } - if c.Query("page_size") != "" { - fmt.Sscanf(c.Query("page_size"), "%d", &pageSize) - } - if page < 1 { - page = 1 - } - if pageSize < 1 || pageSize > 500 { - pageSize = 50 - } - - query := h.db.Model(&models.SQLAuditRecord{}) - - // 筛选 - if status := c.Query("status"); status != "" { - query = query.Where("status = ?", status) - } - if risk := c.Query("risk_level"); risk != "" { - query = query.Where("risk_level = ?", risk) - } - if keyword := c.Query("keyword"); keyword != "" { - query = query.Where("sql_content LIKE ? OR database_name LIKE ? OR executor LIKE ?", - "%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%") - } - - var total int64 - query.Count(&total) - - var records []models.SQLAuditRecord - query.Order("created_at DESC").Offset((page - 1) * pageSize).Limit(pageSize).Find(&records) - - c.JSON(http.StatusOK, models.Response{ - Code: 200, - Message: "success", - Data: models.ListResponse{ - List: records, - Total: total, - }, - }) -} - -// GetRecord 获取审计记录详情 // Logan.Li -func (h *SQLAuditHandler) GetRecord(c *gin.Context) { - id := c.Param("id") - var record models.SQLAuditRecord - if err := h.db.First(&record, id).Error; err != nil { - c.JSON(http.StatusNotFound, models.Response{Code: 404, Message: "记录不存在"}) - return - } - c.JSON(http.StatusOK, models.Response{Code: 200, Message: "success", Data: record}) -} - -// AnalyzeSQL 分析SQL并创建审计记录 // Logan.Li -func (h *SQLAuditHandler) AnalyzeSQL(c *gin.Context) { - var req models.AnalyzeSQLRequest - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "参数错误"}) - return - } - if strings.TrimSpace(req.SQL) == "" { - c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "SQL不能为空"}) - return - } - - sqlUpper := strings.ToUpper(strings.TrimSpace(req.SQL)) - - // 判断 SQL 类型 - sqlType := detectSQLType(sqlUpper) - - // 提取表名 - tableName := extractTableName(sqlUpper) - - // 风险分析 - riskLevel, matchedRules := h.analyzeRisk(sqlUpper) - - // 获取当前用户 - username := c.GetString("username") - if username == "" { - username = "system" - } - - rulesJSON := "[]" - if len(matchedRules) > 0 { - rulesJSON = fmt.Sprintf(`["%s"]`, strings.Join(matchedRules, `","`)) - } - - record := models.SQLAuditRecord{ - SQLContent: req.SQL, - SQLType: sqlType, - DatabaseName: req.DatabaseName, - TargetTable: tableName, - RiskLevel: riskLevel, - RiskRules: rulesJSON, - Status: "pending", - Executor: username, - Application: req.Application, - SourceIP: c.ClientIP(), - } - - if err := h.db.Create(&record).Error; err != nil { - c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "创建审计记录失败"}) - return - } - - c.JSON(http.StatusOK, models.Response{ - Code: 200, - Message: "分析完成", - Data: models.AnalyzeSQLResponse{ - ID: int64(record.ID), - RiskLevel: riskLevel, - RiskRules: matchedRules, - SQLType: sqlType, - TableName: tableName, - }, - }) -} - -// ApproveRecord 审批通过 // Logan.Li -func (h *SQLAuditHandler) ApproveRecord(c *gin.Context) { - id := c.Param("id") - username := c.GetString("username") - if username == "" { - username = "admin" - } - - result := h.db.Model(&models.SQLAuditRecord{}).Where("id = ? AND status = ?", id, "pending"). - Updates(map[string]interface{}{"status": "approved", "approver": username}) - if result.Error != nil { - c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "审批失败"}) - return - } - if result.RowsAffected == 0 { - c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "记录不存在或非待审批状态"}) - return - } - c.JSON(http.StatusOK, models.Response{Code: 200, Message: "审批通过"}) -} - -// RejectRecord 审批驳回 // Logan.Li -func (h *SQLAuditHandler) RejectRecord(c *gin.Context) { - id := c.Param("id") - username := c.GetString("username") - if username == "" { - username = "admin" - } - - result := h.db.Model(&models.SQLAuditRecord{}).Where("id = ? AND status = ?", id, "pending"). - Updates(map[string]interface{}{"status": "rejected", "approver": username}) - if result.Error != nil { - c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "驳回失败"}) - return - } - if result.RowsAffected == 0 { - c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "记录不存在或非待审批状态"}) - return - } - c.JSON(http.StatusOK, models.Response{Code: 200, Message: "已驳回"}) -} - -// ExecuteRecord 执行SQL(模拟) // Logan.Li -func (h *SQLAuditHandler) ExecuteRecord(c *gin.Context) { - id := c.Param("id") - - var record models.SQLAuditRecord - if err := h.db.First(&record, id).Error; err != nil { - c.JSON(http.StatusNotFound, models.Response{Code: 404, Message: "记录不存在"}) - return - } - - if record.Status != "approved" { - c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "只能执行已审批的SQL"}) - return - } - - now := time.Now() - // 模拟执行(真实环境需连接目标数据库执行) - h.db.Model(&record).Updates(map[string]interface{}{ - "status": "executed", - "execute_time": now, - "affected_rows": 0, - "execution_time": 10, - }) - - c.JSON(http.StatusOK, models.Response{Code: 200, Message: "SQL执行成功"}) -} - -// DeleteRecord 删除审计记录 // Logan.Li -func (h *SQLAuditHandler) DeleteRecord(c *gin.Context) { - id := c.Param("id") - if err := h.db.Delete(&models.SQLAuditRecord{}, id).Error; err != nil { - c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "删除失败"}) - return - } - c.JSON(http.StatusOK, models.Response{Code: 200, Message: "已删除"}) -} - -// ==================== 审计规则管理 ==================== - -// ListRules 获取审计规则列表 // Logan.Li -func (h *SQLAuditHandler) ListRules(c *gin.Context) { - var rules []models.SQLAuditRule - h.db.Order("created_at DESC").Find(&rules) - - c.JSON(http.StatusOK, models.Response{ - Code: 200, - Message: "success", - Data: models.ListResponse{ - List: rules, - Total: int64(len(rules)), - }, - }) -} - -// CreateRule 创建审计规则 // Logan.Li -func (h *SQLAuditHandler) CreateRule(c *gin.Context) { - var rule models.SQLAuditRule - if err := c.ShouldBindJSON(&rule); err != nil { - c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "参数错误"}) - return - } - if rule.Name == "" { - c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "规则名称不能为空"}) - return - } - if rule.Severity == "" { - rule.Severity = "medium" - } - rule.Enabled = true - - if err := h.db.Create(&rule).Error; err != nil { - c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "创建规则失败"}) - return - } - c.JSON(http.StatusOK, models.Response{Code: 200, Message: "规则已创建", Data: rule}) -} - -// UpdateRule 更新审计规则 // Logan.Li -func (h *SQLAuditHandler) UpdateRule(c *gin.Context) { - id := c.Param("id") - var rule models.SQLAuditRule - if err := h.db.First(&rule, id).Error; err != nil { - c.JSON(http.StatusNotFound, models.Response{Code: 404, Message: "规则不存在"}) - return - } - - var req models.SQLAuditRule - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "参数错误"}) - return - } - - h.db.Model(&rule).Updates(map[string]interface{}{ - "name": req.Name, - "description": req.Description, - "pattern": req.Pattern, - "severity": req.Severity, - "category": req.Category, - "enabled": req.Enabled, - }) - - c.JSON(http.StatusOK, models.Response{Code: 200, Message: "规则已更新", Data: rule}) -} - -// DeleteRule 删除审计规则 // Logan.Li -func (h *SQLAuditHandler) DeleteRule(c *gin.Context) { - id := c.Param("id") - if err := h.db.Delete(&models.SQLAuditRule{}, id).Error; err != nil { - c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "删除失败"}) - return - } - c.JSON(http.StatusOK, models.Response{Code: 200, Message: "规则已删除"}) -} - -// ToggleRule 启用/禁用规则 // Logan.Li -func (h *SQLAuditHandler) ToggleRule(c *gin.Context) { - id := c.Param("id") - var rule models.SQLAuditRule - if err := h.db.First(&rule, id).Error; err != nil { - c.JSON(http.StatusNotFound, models.Response{Code: 404, Message: "规则不存在"}) - return - } - h.db.Model(&rule).Update("enabled", !rule.Enabled) - c.JSON(http.StatusOK, models.Response{Code: 200, Message: "状态已更新"}) -} - -// ==================== 辅助函数 ==================== - -// detectSQLType 检测SQL类型 // Logan.Li -func detectSQLType(sqlUpper string) string { - switch { - case strings.HasPrefix(sqlUpper, "DROP"): - return "DDL-DROP" - case strings.HasPrefix(sqlUpper, "TRUNCATE"): - return "DDL-TRUNCATE" - case strings.HasPrefix(sqlUpper, "ALTER"): - return "DDL-ALTER" - case strings.HasPrefix(sqlUpper, "CREATE"): - return "DDL-CREATE" - case strings.HasPrefix(sqlUpper, "DELETE"): - return "DML-DELETE" - case strings.HasPrefix(sqlUpper, "UPDATE"): - return "DML-UPDATE" - case strings.HasPrefix(sqlUpper, "INSERT"): - return "DML-INSERT" - case strings.HasPrefix(sqlUpper, "SELECT"): - return "DQL" - case strings.HasPrefix(sqlUpper, "GRANT"), strings.HasPrefix(sqlUpper, "REVOKE"): - return "DCL" - default: - return "OTHER" - } -} - -// extractTableName 提取表名 // Logan.Li -func extractTableName(sqlUpper string) string { - // 匹配 FROM/INTO/UPDATE/TABLE 后面的表名 - patterns := []string{ - `FROM\s+(\w+)`, - `INTO\s+(\w+)`, - `UPDATE\s+(\w+)`, - `TABLE\s+(\w+)`, - `JOIN\s+(\w+)`, - } - for _, p := range patterns { - re := regexp.MustCompile(p) - m := re.FindStringSubmatch(sqlUpper) - if len(m) > 1 { - return strings.ToLower(m[1]) - } - } - return "" -} - -// analyzeRisk 分析SQL风险等级 // Logan.Li -func (h *SQLAuditHandler) analyzeRisk(sqlUpper string) (string, []string) { - // 从数据库加载启用的规则 - var rules []models.SQLAuditRule - h.db.Where("enabled = ?", true).Find(&rules) - - // 如果没有数据库规则,使用内置规则 - if len(rules) == 0 { - rules = builtinRules() - } - - var matched []string - maxSeverity := "safe" - - for _, rule := range rules { - if matchRule(sqlUpper, rule) { - matched = append(matched, rule.Name) - if severityRank(rule.Severity) > severityRank(maxSeverity) { - maxSeverity = rule.Severity - } - } - } - - return maxSeverity, matched -} - -// matchRule 检查SQL是否匹配规则 // Logan.Li -func matchRule(sqlUpper string, rule models.SQLAuditRule) bool { - // 按 category 快速匹配 - if rule.Category != "" { - if strings.Contains(sqlUpper, strings.ToUpper(rule.Category)) { - return true - } - } - // 按 pattern 正则匹配 - if rule.Pattern != "" { - if re, err := regexp.Compile("(?i)" + rule.Pattern); err == nil { - if re.MatchString(sqlUpper) { - return true - } - } - } - return false -} - -// severityRank 风险等级排序 // Logan.Li -func severityRank(s string) int { - switch s { - case "high": - return 4 - case "medium": - return 3 - case "low": - return 2 - default: - return 1 - } -} - -// builtinRules 内置审计规则 // Logan.Li -func builtinRules() []models.SQLAuditRule { - return []models.SQLAuditRule{ - {Name: "DROP TABLE", Category: "DROP TABLE", Severity: "high", Description: "删除表操作,不可恢复"}, - {Name: "TRUNCATE TABLE", Category: "TRUNCATE", Severity: "high", Description: "清空表数据"}, - {Name: "DELETE 无 WHERE", Pattern: `DELETE\s+FROM\s+\w+\s*(?:;|$)`, Severity: "high", Description: "无条件的DELETE操作"}, - {Name: "UPDATE 无 WHERE", Pattern: `UPDATE\s+\w+\s+SET.*(?:;|$)`, Severity: "high", Description: "无条件的UPDATE操作"}, - {Name: "ALTER TABLE", Category: "ALTER", Severity: "medium", Description: "修改表结构"}, - {Name: "DROP DATABASE", Category: "DROP DATABASE", Severity: "high", Description: "删除数据库"}, - {Name: "GRANT/REVOKE", Category: "GRANT", Severity: "medium", Description: "权限变更操作"}, - } -} diff --git a/backend/internal/api/routes/routes.go b/backend/internal/api/routes/routes.go index 7b6c7ca..c7b09a2 100644 --- a/backend/internal/api/routes/routes.go +++ b/backend/internal/api/routes/routes.go @@ -371,24 +371,6 @@ func Register(r *gin.Engine, db *gorm.DB, log *zap.Logger, cfg *config.Config) { audit.POST("/logs/clear", auditHandler.ClearAuditLogs) } - // SQL审计路由 // Logan.Li - sqlAuditHandler := handlers.NewSQLAuditHandler(db) - sqlAudit := authorized.Group("/sql-audit") - { - sqlAudit.GET("/records", sqlAuditHandler.ListRecords) - sqlAudit.GET("/records/:id", sqlAuditHandler.GetRecord) - sqlAudit.POST("/analyze", sqlAuditHandler.AnalyzeSQL) - sqlAudit.POST("/records/:id/approve", sqlAuditHandler.ApproveRecord) - sqlAudit.POST("/records/:id/reject", sqlAuditHandler.RejectRecord) - sqlAudit.POST("/records/:id/execute", sqlAuditHandler.ExecuteRecord) - sqlAudit.DELETE("/records/:id", sqlAuditHandler.DeleteRecord) - sqlAudit.GET("/rules", sqlAuditHandler.ListRules) - sqlAudit.POST("/rules", sqlAuditHandler.CreateRule) - sqlAudit.PUT("/rules/:id", sqlAuditHandler.UpdateRule) - sqlAudit.DELETE("/rules/:id", sqlAuditHandler.DeleteRule) - sqlAudit.POST("/rules/:id/toggle", sqlAuditHandler.ToggleRule) - } - // Agent 上报路由(无需 JWT) agentHandler := handlers.NewAgentHandler(db) taskHandler := handlers.NewAgentTaskHandler(db) diff --git a/backend/internal/models/sql_audit.go b/backend/internal/models/sql_audit.go deleted file mode 100644 index 8450c49..0000000 --- a/backend/internal/models/sql_audit.go +++ /dev/null @@ -1,64 +0,0 @@ -package models - -import ( - "time" - - "gorm.io/gorm" -) - -// SQLAuditRecord SQL审计记录 // Logan.Li -type SQLAuditRecord struct { - ID uint `gorm:"primarykey" json:"id"` - SQLContent string `gorm:"type:text;not null" json:"sql_content"` - SQLType string `gorm:"size:50" json:"sql_type"` // DDL/DML/DQL/DCL - DatabaseName string `gorm:"size:255" json:"database_name"` - TargetTable string `gorm:"column:table_name;size:255" json:"table_name"` - RiskLevel string `gorm:"size:20;default:safe" json:"risk_level"` // high/medium/low/safe - RiskRules string `gorm:"type:text" json:"risk_rules"` // JSON 数组 - Status string `gorm:"size:20;default:pending" json:"status"` // pending/approved/rejected/executed - Executor string `gorm:"size:255" json:"executor"` - Approver string `gorm:"size:255" json:"approver"` - ExecuteTime *time.Time `json:"execute_time"` - AffectedRows int64 `gorm:"default:0" json:"affected_rows"` - ExecutionTime int64 `gorm:"default:0" json:"execution_time"` // 毫秒 - SourceIP string `gorm:"size:100" json:"source_ip"` - Application string `gorm:"size:255" json:"application"` - TicketID string `gorm:"size:100" json:"ticket_id"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` -} - -func (SQLAuditRecord) TableName() string { return "sql_audit_records" } - -// SQLAuditRule SQL审计规则 // Logan.Li -type SQLAuditRule struct { - ID uint `gorm:"primarykey" json:"id"` - Name string `gorm:"size:255;not null" json:"name"` - Description string `gorm:"type:text" json:"description"` - Pattern string `gorm:"type:text" json:"pattern"` // 正则/关键词匹配 - Enabled bool `gorm:"default:true" json:"enabled"` - Severity string `gorm:"size:20;default:medium" json:"severity"` // high/medium/low - Category string `gorm:"size:100" json:"category"` // DDL/DML/DROP/TRUNCATE/etc - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` -} - -func (SQLAuditRule) TableName() string { return "sql_audit_rules" } - -// AnalyzeSQLRequest SQL分析请求 // Logan.Li -type AnalyzeSQLRequest struct { - SQL string `json:"sql"` - DatabaseName string `json:"database_name"` - Application string `json:"application"` -} - -// AnalyzeSQLResponse SQL分析响应 // Logan.Li -type AnalyzeSQLResponse struct { - ID int64 `json:"id"` - RiskLevel string `json:"risk_level"` - RiskRules []string `json:"risk_rules"` - SQLType string `json:"sql_type"` - TableName string `json:"table_name"` -} diff --git a/backend/pkg/database/database.go b/backend/pkg/database/database.go index 13df5a1..ce320b4 100644 --- a/backend/pkg/database/database.go +++ b/backend/pkg/database/database.go @@ -100,8 +100,6 @@ func AutoMigrate(db *gorm.DB) error { &models.AnsibleInventory{}, &models.LogSource{}, &models.LogEntry{}, - &models.SQLAuditRecord{}, - &models.SQLAuditRule{}, &models.TopologyLayout{}, // 拓扑布局保存 // Logan.Li ) } diff --git a/frontend/src/components/audit/SQLAuditManager.tsx b/frontend/src/components/audit/SQLAuditManager.tsx deleted file mode 100644 index 2876857..0000000 --- a/frontend/src/components/audit/SQLAuditManager.tsx +++ /dev/null @@ -1,629 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { - Search, Filter, Eye, Download, Shield, AlertTriangle, CheckCircle, XCircle, - Database, FileText, Clock, User, Server, Code, AlertOctagon, RotateCcw, - ChevronDown, ChevronUp, Play, Pause, Settings, Trash2, RefreshCw, History -} from 'lucide-react'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Badge } from '@/components/ui/badge'; -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { - Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter -} from '@/components/ui/dialog'; -import { - Select, SelectContent, SelectItem, SelectTrigger, SelectValue -} from '@/components/ui/select'; -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; -import { Textarea } from '@/components/ui/textarea'; -import { Switch } from '@/components/ui/switch'; -import { Label } from '@/components/ui/label'; -import { Separator } from '@/components/ui/separator'; -import { toast } from 'sonner'; -import { sqlAuditApi } from '@/services/api'; - -interface SQLAuditRecord { - id: string; - sqlContent: string; - sqlType: string; - databaseName: string; - tableName: string; - riskLevel: 'high' | 'medium' | 'low' | 'safe'; - riskRules: string[]; - status: 'pending' | 'approved' | 'rejected' | 'executed'; - executor: string; - approver?: string; - executeTime?: string; - createdAt: string; - affectedRows?: number; - executionTime?: number; - sourceIP: string; - application: string; - ticketId?: string; -} - -interface AuditRule { - id: string; - name: string; - description: string; - enabled: boolean; - severity: 'high' | 'medium' | 'low'; - category: string; -} - -const mapRecord = (r: any): SQLAuditRecord => ({ - id: String(r.id), - sqlContent: r.sql_content || '', - sqlType: r.sql_type || '', - databaseName: r.database_name || '', - tableName: r.table_name || '', - riskLevel: (r.risk_level || 'safe') as SQLAuditRecord['riskLevel'], - riskRules: (() => { try { return JSON.parse(r.risk_rules || '[]'); } catch { return []; } })(), - status: (r.status || 'pending') as SQLAuditRecord['status'], - executor: r.executor || '', - approver: r.approver || '', - executeTime: r.execute_time || undefined, - createdAt: r.created_at ? new Date(r.created_at).toLocaleString('zh-CN') : '', - affectedRows: r.affected_rows || 0, - executionTime: r.execution_time || undefined, - sourceIP: r.source_ip || '', - application: r.application || '', - ticketId: r.ticket_id || '', -}); - -const mapRule = (r: any): AuditRule => ({ - id: String(r.id), - name: r.name || '', - description: r.description || '', - enabled: r.enabled ?? true, - severity: (r.severity || 'medium') as AuditRule['severity'], - category: r.category || '', -}); - -const SQLAuditManager: React.FC = () => { - const [records, setRecords] = useState([]); - const [rules, setRules] = useState([]); - const [loading, setLoading] = useState(false); - const [searchQuery, setSearchQuery] = useState(''); - const [riskFilter, setRiskFilter] = useState('all'); - const [statusFilter, setStatusFilter] = useState('all'); - const [selectedRecord, setSelectedRecord] = useState(null); - const [detailOpen, setDetailOpen] = useState(false); - const [newAuditOpen, setNewAuditOpen] = useState(false); - const [rulesOpen, setRulesOpen] = useState(false); - const [sqlContent, setSqlContent] = useState(''); - const [activeTab, setActiveTab] = useState('records'); - - useEffect(() => { - fetchRecords(); - fetchRules(); - }, []); - - const fetchRecords = async () => { - setLoading(true); - try { - const res = await sqlAuditApi.getRecords(); - setRecords((res.list || []).map(mapRecord)); - } catch (error) { - toast.error('获取SQL审计记录失败'); - } finally { - setLoading(false); - } - }; - - const fetchRules = async () => { - try { - const res = await sqlAuditApi.getRules(); - setRules((res.list || []).map(mapRule)); - } catch (error) { - toast.error('获取审计规则失败'); - } - }; - - const handleAnalyzeSQL = async () => { - if (!sqlContent.trim()) { - toast.error('请输入SQL语句'); - return; - } - try { - await sqlAuditApi.analyzeSQL({ sql: sqlContent }); - toast.success('SQL分析完成'); - setNewAuditOpen(false); - setSqlContent(''); - fetchRecords(); - } catch (error) { - toast.error('SQL分析失败'); - } - }; - - const handleApprove = async (id: string) => { - try { - await sqlAuditApi.approve(id); - toast.success('SQL已审批通过'); - fetchRecords(); - } catch (error) { - toast.error('审批失败'); - } - }; - - const handleReject = async (id: string) => { - try { - await sqlAuditApi.reject(id); - toast.success('SQL已驳回'); - fetchRecords(); - } catch (error) { - toast.error('驳回失败'); - } - }; - - const handleExecute = async (id: string) => { - try { - await sqlAuditApi.execute(id); - toast.success('SQL执行成功'); - fetchRecords(); - } catch (error) { - toast.error('SQL执行失败'); - } - }; - - const toggleRule = async (ruleId: string) => { - try { - await sqlAuditApi.toggleRule(ruleId); - fetchRules(); - toast.success('规则状态已更新'); - } catch (error) { - toast.error('更新规则失败'); - } - }; - - const getRiskBadge = (level: string) => { - switch (level) { - case 'high': - return 高危; - case 'medium': - return 中危; - case 'low': - return 低危; - case 'safe': - return 安全; - default: - return 未知; - } - }; - - const getStatusBadge = (status: string) => { - switch (status) { - case 'pending': - return 待审批; - case 'approved': - return 已批准; - case 'rejected': - return 已驳回; - case 'executed': - return 已执行; - default: - return 未知; - } - }; - - const filteredRecords = records.filter(record => { - const matchSearch = - record.sqlContent.toLowerCase().includes(searchQuery.toLowerCase()) || - record.databaseName.toLowerCase().includes(searchQuery.toLowerCase()) || - record.executor.toLowerCase().includes(searchQuery.toLowerCase()); - const matchRisk = riskFilter === 'all' || record.riskLevel === riskFilter; - const matchStatus = statusFilter === 'all' || record.status === statusFilter; - return matchSearch && matchRisk && matchStatus; - }); - - const stats = { - total: records.length, - highRisk: records.filter(r => r.riskLevel === 'high').length, - pending: records.filter(r => r.status === 'pending').length, - executed: records.filter(r => r.status === 'executed').length - }; - - return ( -
- {/* 统计卡片 */} -
- - -
-
-

总审计数

-

{stats.total}

-
-
- -
-
-
-
- - -
-
-

高危SQL

-

{stats.highRisk}

-
-
- -
-
-
-
- - -
-
-

待审批

-

{stats.pending}

-
-
- -
-
-
-
- - -
-
-

已执行

-

{stats.executed}

-
-
- -
-
-
-
-
- - - - 审计记录 - 审计规则 - - - - {/* 操作栏 */} -
-
-
- - setSearchQuery(e.target.value)} - className="pl-10" - /> -
- - -
-
- - -
-
- - {/* 记录列表 */} - - -
- - - - - - - - - - - - - - {loading ? ( - - - - ) : filteredRecords.length === 0 ? ( - - - - ) : ( - filteredRecords.map((record) => ( - - - - - - - - - - )) - )} - -
SQL内容数据库风险等级状态执行人创建时间操作
- 加载中... -
- 暂无SQL审计记录 -
-
- {record.sqlContent} -
-
- {record.riskRules.map((rule, idx) => ( - {rule} - ))} -
-
-
{record.databaseName}
-
{record.tableName}
-
{getRiskBadge(record.riskLevel)}{getStatusBadge(record.status)}{record.executor}{record.createdAt} -
- - {record.status === 'pending' && ( - <> - - - - )} - {record.status === 'approved' && ( - - )} -
-
-
-
-
-
- - - - - - - SQL审计规则配置 - - - -
- {rules.map((rule) => ( -
-
-
- {rule.name} - - {rule.severity === 'high' ? '高危' : rule.severity === 'medium' ? '中危' : '低危'} - - {rule.category} -
-

{rule.description}

-
-
-
- toggleRule(rule.id)} - /> - -
-
-
- ))} -
-
-
-
-
- - {/* 新建审计对话框 */} - - - - 新建SQL审计 - 输入SQL语句进行风险分析和审计 - -
-
- -