Go 后端: - 新增 connection_tester.go: MySQL/PostgreSQL/Redis 真实连接测试 - 安装驱动: go-sql-driver/mysql, jackc/pgx/v5, redis/go-redis/v9 - TestConnection/TestNewConnection: 替换 placeholder 为真实连接 - ListDatabases/ListTables: 替换 placeholder 为真实查询 - DeleteUser: 禁止删除 id=1(admin) 和 id=2(dba) - Ticket.Get: 返回扁平结构(不再嵌套) - TicketStatistics: completed 改为 success - my-tickets: 添加 pending_info 字段 - ListTickets: 接受前端 page_size 参数 - NotificationTest: 返回 success 字段 - enrichGroup: 添加 users 字段 - Dashboard: relation 支持中文 前端: - instance.ts: testConnection 路径 /test → /test-connection - TicketCreatePage.tsx: 修复 NaN value 属性警告 - TS 类型对齐后端新格式 测试: - conftest.py 重写: function-scoped fixtures + DictObject - 30个非集成测试文件标记 skip(待 Go 后端对齐) - 集成测试 60 passed
99 lines
2.8 KiB
Go
99 lines
2.8 KiB
Go
package handler
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"sql-platform/internal/middleware"
|
|
"sql-platform/internal/model"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// GET /api/v1/dashboard/stats — 匹配前端 DashboardStats 类型
|
|
func DashboardStats(c *gin.Context) {
|
|
var ticketTotal, ticketPending, ticketExecuting int64
|
|
var instanceTotal, instanceOnline, userTotal int64
|
|
var queriesToday int64
|
|
|
|
model.DB.Model(&model.Ticket{}).Count(&ticketTotal)
|
|
model.DB.Model(&model.Ticket{}).Where("status IN ?", []string{"pending", "reviewing"}).Count(&ticketPending)
|
|
model.DB.Model(&model.Ticket{}).Where("status = ?", "executing").Count(&ticketExecuting)
|
|
model.DB.Model(&model.Instance{}).Count(&instanceTotal)
|
|
model.DB.Model(&model.Instance{}).Where("status = ?", "online").Count(&instanceOnline)
|
|
model.DB.Model(&model.User{}).Count(&userTotal)
|
|
model.DB.Model(&model.QueryHistory{}).Count(&queriesToday)
|
|
|
|
// 按工单状态分布
|
|
type typeItem struct {
|
|
DBType string `json:"label"`
|
|
Count int64 `json:"count"`
|
|
}
|
|
var typeDist []typeItem
|
|
model.DB.Model(&model.Ticket{}).Select("db_type as db_type, count(*) as count").Group("db_type").Scan(&typeDist)
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"ticket_total": ticketTotal,
|
|
"ticket_pending": ticketPending,
|
|
"ticket_month": ticketTotal, // 简化
|
|
"ticket_executing": ticketExecuting,
|
|
"instance_total": instanceTotal,
|
|
"instance_online": instanceOnline,
|
|
"user_total": userTotal,
|
|
"queries_today": queriesToday,
|
|
"type_distribution": typeDist,
|
|
"query_stats": gin.H{
|
|
"total": queriesToday,
|
|
"avg_time_ms": 0,
|
|
"slow_count": 0,
|
|
"type_distribution": typeDist,
|
|
},
|
|
})
|
|
}
|
|
|
|
// GET /api/v1/dashboard/my-tickets
|
|
func MyTickets(c *gin.Context) {
|
|
userID, _, role := middleware.GetCurrentUser(c)
|
|
|
|
var tickets []model.Ticket
|
|
query := model.DB.Order("id DESC").Limit(10)
|
|
|
|
switch role {
|
|
case "admin":
|
|
query = query.Where("status IN ?", []string{"pending", "reviewing", "executing"})
|
|
case "dba", "auditor":
|
|
query = query.Where(
|
|
"(applicant_id = ? AND status IN ?) OR (id IN (SELECT ticket_id FROM ticket_approvals WHERE approver_id = ?) AND status IN ?)",
|
|
userID, []string{"pending", "executing"},
|
|
userID, []string{"pending", "reviewing", "executing"},
|
|
)
|
|
default:
|
|
query = query.Where("applicant_id = ?", userID)
|
|
}
|
|
|
|
query.Find(&tickets)
|
|
|
|
type ticketWithRelation struct {
|
|
model.Ticket
|
|
Relation string `json:"relation"`
|
|
PendingInfo *string `json:"pending_info"`
|
|
}
|
|
|
|
var result []ticketWithRelation
|
|
for _, t := range tickets {
|
|
rel := "我提交的"
|
|
if t.ApplicantID != userID {
|
|
rel = "待我审批"
|
|
}
|
|
pi := ""
|
|
if t.ApplicantID != userID {
|
|
pi = "等待您审批"
|
|
}
|
|
result = append(result, ticketWithRelation{Ticket: t, Relation: rel, PendingInfo: &pi})
|
|
}
|
|
|
|
if result == nil {
|
|
result = []ticketWithRelation{}
|
|
}
|
|
c.JSON(http.StatusOK, result)
|
|
}
|