基本功能已初步完善
This commit is contained in:
@@ -23,6 +23,7 @@ func AdminDashboardSummary(c *gin.Context) {
|
||||
var totalOrders int64
|
||||
var pendingOrders int64
|
||||
var pendingTickets int64
|
||||
var commissionPendingTotal int64
|
||||
var onlineUsers int64
|
||||
var onlineNodes int64
|
||||
var onlineDevices int64
|
||||
@@ -30,6 +31,10 @@ func AdminDashboardSummary(c *gin.Context) {
|
||||
database.DB.Model(&model.User{}).Count(&totalUsers)
|
||||
database.DB.Model(&model.Order{}).Count(&totalOrders)
|
||||
database.DB.Model(&model.Order{}).Where("status = ?", 0).Count(&pendingOrders)
|
||||
database.DB.Model(&model.Order{}).
|
||||
Where("status = ? AND commission_status = ?", 3, 0).
|
||||
Select("COALESCE(SUM(commission_balance), 0)").
|
||||
Scan(&commissionPendingTotal)
|
||||
database.DB.Model(&model.Ticket{}).Where("status = ?", 0).Count(&pendingTickets)
|
||||
database.DB.Model(&model.Server{}).Where("show = ?", true).Count(&onlineNodes) // Simplified online check
|
||||
|
||||
@@ -75,25 +80,28 @@ func AdminDashboardSummary(c *gin.Context) {
|
||||
userGrowth := calculateGrowth(currentMonthNewUsers, lastMonthNewUsers)
|
||||
|
||||
Success(c, gin.H{
|
||||
"server_time": now,
|
||||
"todayIncome": todayIncome,
|
||||
"dayIncomeGrowth": dayIncomeGrowth,
|
||||
"currentMonthIncome": currentMonthIncome,
|
||||
"lastMonthIncome": lastMonthIncome,
|
||||
"monthIncomeGrowth": monthIncomeGrowth,
|
||||
"currentMonthNewUsers": currentMonthNewUsers,
|
||||
"totalUsers": totalUsers,
|
||||
"activeUsers": totalUsers, // Placeholder for valid subscription count
|
||||
"userGrowth": userGrowth,
|
||||
"onlineUsers": onlineUsers,
|
||||
"onlineDevices": onlineDevices,
|
||||
"ticketPendingTotal": pendingTickets,
|
||||
"onlineNodes": onlineNodes,
|
||||
"todayTraffic": todayTraffic,
|
||||
"monthTraffic": monthTraffic,
|
||||
"totalTraffic": totalTraffic,
|
||||
"secure_path": service.GetAdminSecurePath(),
|
||||
"app_name": service.MustGetString("app_name", "XBoard"),
|
||||
"server_time": now,
|
||||
"todayIncome": todayIncome,
|
||||
"dayIncomeGrowth": dayIncomeGrowth,
|
||||
"currentMonthIncome": currentMonthIncome,
|
||||
"lastMonthIncome": lastMonthIncome,
|
||||
"monthIncomeGrowth": monthIncomeGrowth,
|
||||
"totalOrders": totalOrders,
|
||||
"pendingOrders": pendingOrders,
|
||||
"currentMonthNewUsers": currentMonthNewUsers,
|
||||
"totalUsers": totalUsers,
|
||||
"activeUsers": totalUsers, // Placeholder for valid subscription count
|
||||
"userGrowth": userGrowth,
|
||||
"commissionPendingTotal": commissionPendingTotal,
|
||||
"onlineUsers": onlineUsers,
|
||||
"onlineDevices": onlineDevices,
|
||||
"ticketPendingTotal": pendingTickets,
|
||||
"onlineNodes": onlineNodes,
|
||||
"todayTraffic": todayTraffic,
|
||||
"monthTraffic": monthTraffic,
|
||||
"totalTraffic": totalTraffic,
|
||||
"secure_path": service.GetAdminSecurePath(),
|
||||
"app_name": service.MustGetString("app_name", "XBoard"),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -107,7 +115,6 @@ func calculateGrowth(current, previous int64) float64 {
|
||||
return float64(current-previous) / float64(previous) * 100.0
|
||||
}
|
||||
|
||||
|
||||
func AdminPlansFetch(c *gin.Context) {
|
||||
var plans []model.Plan
|
||||
if err := database.DB.Order("sort ASC, id DESC").Find(&plans).Error; err != nil {
|
||||
@@ -248,11 +255,10 @@ func AdminPlanSort(c *gin.Context) {
|
||||
Success(c, true)
|
||||
}
|
||||
|
||||
|
||||
func AdminOrdersFetch(c *gin.Context) {
|
||||
params := getFetchParams(c)
|
||||
page := parsePositiveInt(params["page"], 1)
|
||||
perPage := parsePositiveInt(params["per_page"], 50)
|
||||
page := parsePositiveInt(firstString(params["page"], params["current"]), 1)
|
||||
perPage := parsePositiveInt(firstString(params["per_page"], params["pageSize"]), 50)
|
||||
keyword := strings.TrimSpace(params["keyword"])
|
||||
statusFilter := strings.TrimSpace(params["status"])
|
||||
|
||||
@@ -277,15 +283,28 @@ func AdminOrdersFetch(c *gin.Context) {
|
||||
}
|
||||
|
||||
userEmails := loadUserEmailMap(extractOrderUserIDs(orders))
|
||||
inviteEmails := loadUserEmailMap(extractOrderInviteUserIDs(orders))
|
||||
items := make([]gin.H, 0, len(orders))
|
||||
for _, order := range orders {
|
||||
item := normalizeOrder(order)
|
||||
item["user_email"] = userEmails[order.UserID]
|
||||
item["user"] = gin.H{
|
||||
"id": order.UserID,
|
||||
"email": userEmails[order.UserID],
|
||||
}
|
||||
if order.InviteUserID != nil {
|
||||
item["invite_user"] = gin.H{
|
||||
"id": *order.InviteUserID,
|
||||
"email": inviteEmails[*order.InviteUserID],
|
||||
}
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
Success(c, gin.H{
|
||||
"list": items,
|
||||
"list": items,
|
||||
"data": items,
|
||||
"total": total,
|
||||
"filters": gin.H{
|
||||
"keyword": keyword,
|
||||
"status": statusFilter,
|
||||
@@ -388,7 +407,6 @@ func AdminCouponDrop(c *gin.Context) {
|
||||
Success(c, true)
|
||||
}
|
||||
|
||||
|
||||
func AdminOrderPaid(c *gin.Context) {
|
||||
var payload struct {
|
||||
TradeNo string `json:"trade_no"`
|
||||
@@ -421,23 +439,17 @@ func AdminOrderPaid(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Update user
|
||||
var user model.User
|
||||
if err := tx.Where("id = ?", order.ID).First(&user).Error; err == nil {
|
||||
// Calculate expiration and traffic
|
||||
// Simplified logic: set plan and transfer_enable
|
||||
updates := map[string]any{
|
||||
"plan_id": order.PlanID,
|
||||
"updated_at": now,
|
||||
}
|
||||
if order.Plan != nil {
|
||||
updates["transfer_enable"] = order.Plan.TransferEnable
|
||||
}
|
||||
if err := tx.Model(&model.User{}).Where("id = ?", order.UserID).Updates(updates).Error; err != nil {
|
||||
tx.Rollback()
|
||||
Fail(c, http.StatusInternalServerError, "failed to update user")
|
||||
return
|
||||
}
|
||||
updates := map[string]any{
|
||||
"plan_id": order.PlanID,
|
||||
"updated_at": now,
|
||||
}
|
||||
if order.Plan != nil {
|
||||
updates["transfer_enable"] = order.Plan.TransferEnable
|
||||
}
|
||||
if err := tx.Model(&model.User{}).Where("id = ?", order.UserID).Updates(updates).Error; err != nil {
|
||||
tx.Rollback()
|
||||
Fail(c, http.StatusInternalServerError, "failed to update user")
|
||||
return
|
||||
}
|
||||
|
||||
tx.Commit()
|
||||
@@ -527,19 +539,19 @@ func AdminUserUpdate(c *gin.Context) {
|
||||
|
||||
now := time.Now().Unix()
|
||||
values := map[string]any{
|
||||
"email": payload["email"],
|
||||
"password": payload["password"],
|
||||
"balance": payload["balance"],
|
||||
"commission_type": payload["commission_type"],
|
||||
"commission_rate": payload["commission_rate"],
|
||||
"email": payload["email"],
|
||||
"password": payload["password"],
|
||||
"balance": payload["balance"],
|
||||
"commission_type": payload["commission_type"],
|
||||
"commission_rate": payload["commission_rate"],
|
||||
"commission_balance": payload["commission_balance"],
|
||||
"group_id": payload["group_id"],
|
||||
"plan_id": payload["plan_id"],
|
||||
"speed_limit": payload["speed_limit"],
|
||||
"device_limit": payload["device_limit"],
|
||||
"expired_at": payload["expired_at"],
|
||||
"remarks": payload["remarks"],
|
||||
"updated_at": now,
|
||||
"group_id": payload["group_id"],
|
||||
"plan_id": payload["plan_id"],
|
||||
"speed_limit": payload["speed_limit"],
|
||||
"device_limit": payload["device_limit"],
|
||||
"expired_at": payload["expired_at"],
|
||||
"remarks": payload["remarks"],
|
||||
"updated_at": now,
|
||||
}
|
||||
|
||||
// Remove nil values to avoid overwriting with defaults if not provided
|
||||
@@ -564,8 +576,8 @@ func AdminUserUpdate(c *gin.Context) {
|
||||
|
||||
func AdminUsersFetch(c *gin.Context) {
|
||||
params := getFetchParams(c)
|
||||
page := parsePositiveInt(params["page"], 1)
|
||||
perPage := parsePositiveInt(params["per_page"], 50)
|
||||
page := parsePositiveInt(firstString(params["page"], params["current"]), 1)
|
||||
perPage := parsePositiveInt(firstString(params["per_page"], params["pageSize"]), 50)
|
||||
keyword := strings.TrimSpace(params["keyword"])
|
||||
|
||||
query := database.DB.Model(&model.User{}).Preload("Plan").Order("id DESC")
|
||||
@@ -591,6 +603,28 @@ func AdminUsersFetch(c *gin.Context) {
|
||||
}
|
||||
deviceMap := service.GetUsersDevices(userIDs)
|
||||
|
||||
realnameStatusByUserID := make(map[int]string, len(userIDs))
|
||||
if len(userIDs) > 0 {
|
||||
var records []model.RealNameAuth
|
||||
if err := database.DB.Select("user_id", "status").Where("user_id IN ?", userIDs).Find(&records).Error; err == nil {
|
||||
for _, record := range records {
|
||||
realnameStatusByUserID[int(record.UserID)] = record.Status
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shadowByParentID := make(map[int]model.User, len(userIDs))
|
||||
if len(userIDs) > 0 {
|
||||
var shadowUsers []model.User
|
||||
if err := database.DB.Select("id", "parent_id", "email").Where("parent_id IN ?", userIDs).Find(&shadowUsers).Error; err == nil {
|
||||
for _, shadow := range shadowUsers {
|
||||
if shadow.ParentID != nil {
|
||||
shadowByParentID[*shadow.ParentID] = shadow
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
groupNames := loadServerGroupNameMap()
|
||||
items := make([]gin.H, 0, len(users))
|
||||
for _, user := range users {
|
||||
@@ -599,37 +633,70 @@ func AdminUsersFetch(c *gin.Context) {
|
||||
onlineIP = strings.Join(ips, ", ")
|
||||
}
|
||||
|
||||
realnameStatus := realnameStatusByUserID[user.ID]
|
||||
if realnameStatus == "" {
|
||||
realnameStatus = "unverified"
|
||||
}
|
||||
|
||||
ipv6Shadow, hasIPv6Shadow := shadowByParentID[user.ID]
|
||||
ipv6ShadowID := 0
|
||||
if hasIPv6Shadow {
|
||||
ipv6ShadowID = ipv6Shadow.ID
|
||||
}
|
||||
|
||||
items = append(items, gin.H{
|
||||
"id": user.ID,
|
||||
"email": user.Email,
|
||||
"balance": user.Balance,
|
||||
"group_id": intValue(user.GroupID),
|
||||
"group_name": groupNames[intFromPointer(user.GroupID)],
|
||||
"plan_id": intValue(user.PlanID),
|
||||
"plan_name": planName(user.Plan),
|
||||
"transfer_enable": user.TransferEnable,
|
||||
"u": user.U,
|
||||
"d": user.D,
|
||||
"banned": user.Banned,
|
||||
"is_admin": user.IsAdmin,
|
||||
"is_staff": user.IsStaff,
|
||||
"device_limit": intValue(user.DeviceLimit),
|
||||
"online_count": intValue(user.OnlineCount),
|
||||
"expired_at": int64Value(user.ExpiredAt),
|
||||
"last_login_at": int64Value(user.LastLoginAt),
|
||||
"last_login_ip": user.LastLoginIP,
|
||||
"online_ip": onlineIP,
|
||||
"created_at": user.CreatedAt,
|
||||
"updated_at": user.UpdatedAt,
|
||||
"remarks": stringValue(user.Remarks),
|
||||
"commission_type": user.CommissionType,
|
||||
"commission_rate": intValue(user.CommissionRate),
|
||||
"id": user.ID,
|
||||
"email": user.Email,
|
||||
"parent_id": intValue(user.ParentID),
|
||||
"is_shadow_user": user.ParentID != nil,
|
||||
"balance": user.Balance,
|
||||
"uuid": user.UUID,
|
||||
"token": user.Token,
|
||||
"group_id": intValue(user.GroupID),
|
||||
"group_name": groupNames[intFromPointer(user.GroupID)],
|
||||
"group": gin.H{
|
||||
"id": intValue(user.GroupID),
|
||||
"name": groupNames[intFromPointer(user.GroupID)],
|
||||
},
|
||||
"plan_id": intValue(user.PlanID),
|
||||
"plan_name": planName(user.Plan),
|
||||
"plan": gin.H{
|
||||
"id": intValue(user.PlanID),
|
||||
"name": planName(user.Plan),
|
||||
},
|
||||
"transfer_enable": user.TransferEnable,
|
||||
"u": user.U,
|
||||
"d": user.D,
|
||||
"total_used": user.U + user.D,
|
||||
"banned": user.Banned,
|
||||
"is_admin": user.IsAdmin,
|
||||
"is_staff": user.IsStaff,
|
||||
"device_limit": intValue(user.DeviceLimit),
|
||||
"online_count": intValue(user.OnlineCount),
|
||||
"expired_at": int64Value(user.ExpiredAt),
|
||||
"next_reset_at": int64Value(user.NextResetAt),
|
||||
"last_login_at": int64Value(user.LastLoginAt),
|
||||
"last_login_ip": user.LastLoginIP,
|
||||
"online_ip": onlineIP,
|
||||
"online_ip_count": len(deviceMap[user.ID]),
|
||||
"realname_status": realnameStatus,
|
||||
"realname_label": realNameStatusLabel(realnameStatus),
|
||||
"ipv6_shadow_id": ipv6ShadowID,
|
||||
"ipv6_shadow_email": firstString(ipv6Shadow.Email, service.IPv6ShadowEmail(user.Email)),
|
||||
"ipv6_enabled": hasIPv6Shadow,
|
||||
"created_at": user.CreatedAt,
|
||||
"updated_at": user.UpdatedAt,
|
||||
"remarks": stringValue(user.Remarks),
|
||||
"commission_type": user.CommissionType,
|
||||
"commission_rate": intValue(user.CommissionRate),
|
||||
"commission_balance": user.CommissionBalance,
|
||||
})
|
||||
}
|
||||
|
||||
Success(c, gin.H{
|
||||
"list": items,
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"list": items,
|
||||
"data": items,
|
||||
"total": total,
|
||||
"filters": gin.H{
|
||||
"keyword": keyword,
|
||||
},
|
||||
@@ -689,7 +756,9 @@ func AdminTicketsFetch(c *gin.Context) {
|
||||
}
|
||||
|
||||
Success(c, gin.H{
|
||||
"list": items,
|
||||
"list": items,
|
||||
"data": items,
|
||||
"total": total,
|
||||
"filters": gin.H{
|
||||
"keyword": keyword,
|
||||
},
|
||||
@@ -746,6 +815,16 @@ func extractOrderUserIDs(orders []model.Order) []int {
|
||||
return ids
|
||||
}
|
||||
|
||||
func extractOrderInviteUserIDs(orders []model.Order) []int {
|
||||
ids := make([]int, 0, len(orders))
|
||||
for _, order := range orders {
|
||||
if order.InviteUserID != nil {
|
||||
ids = append(ids, *order.InviteUserID)
|
||||
}
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func extractTicketUserIDs(tickets []model.Ticket) []int {
|
||||
ids := make([]int, 0, len(tickets))
|
||||
for _, ticket := range tickets {
|
||||
|
||||
Reference in New Issue
Block a user