3x-ui/web/service/reports.go

165 lines
4.3 KiB
Go

package service
import (
"fmt"
"time"
"github.com/mhsanaei/3x-ui/v2/database/model"
"github.com/mhsanaei/3x-ui/v2/logger"
)
// ReportsService handles client usage reports
type ReportsService struct {
inboundService InboundService
analyticsService AnalyticsService
}
// ClientReport represents a client usage report
type ClientReport struct {
Email string `json:"email"`
Period string `json:"period"`
StartDate time.Time `json:"start_date"`
EndDate time.Time `json:"end_date"`
TotalUp int64 `json:"total_up"`
TotalDown int64 `json:"total_down"`
TotalTraffic int64 `json:"total_traffic"`
QuotaUsed float64 `json:"quota_used_percent"`
ActiveDays int `json:"active_days"`
TopCountries []string `json:"top_countries"`
Recommendations []string `json:"recommendations"`
}
// GenerateClientReport generates a usage report for a client
func (s *ReportsService) GenerateClientReport(email string, period string) (*ClientReport, error) {
// Get period dates
now := time.Now()
var startDate, endDate time.Time
switch period {
case "weekly":
startDate = now.AddDate(0, 0, -7)
endDate = now
case "monthly":
startDate = now.AddDate(0, -1, 0)
endDate = now
default:
startDate = now.AddDate(0, 0, -7)
endDate = now
}
// Get client data
inbounds, err := s.inboundService.GetAllInbounds()
if err != nil {
return nil, err
}
var client *model.Client
for i := range inbounds {
inbound := inbounds[i]
clients, _ := s.inboundService.GetClients(inbound)
for j := range clients {
if clients[j].Email == email {
client = &clients[j]
break
}
}
if client != nil {
break
}
}
if client == nil {
return nil, fmt.Errorf("client not found: %s", email)
}
// Calculate traffic (simplified - in production, get from analytics)
report := &ClientReport{
Email: email,
Period: period,
StartDate: startDate,
EndDate: endDate,
TotalUp: 0, // Get from analytics
TotalDown: 0, // Get from analytics
}
report.TotalTraffic = report.TotalUp + report.TotalDown
// Calculate quota usage
if client.TotalGB > 0 {
report.QuotaUsed = float64(report.TotalTraffic) / float64(client.TotalGB*1024*1024*1024) * 100
}
// Generate recommendations
report.Recommendations = s.generateRecommendations(report, client)
return report, nil
}
// generateRecommendations generates usage recommendations
func (s *ReportsService) generateRecommendations(report *ClientReport, client *model.Client) []string {
recommendations := make([]string, 0)
if report.QuotaUsed > 80 {
recommendations = append(recommendations, "You are using more than 80% of your quota. Consider upgrading your plan.")
}
if report.ActiveDays < 3 {
recommendations = append(recommendations, "Low activity detected. Your VPN connection may need attention.")
}
if client.ExpiryTime > 0 && time.Now().UnixMilli() > client.ExpiryTime-7*24*3600*1000 {
recommendations = append(recommendations, "Your subscription expires soon. Please renew to avoid service interruption.")
}
return recommendations
}
// SendWeeklyReports sends weekly reports to all clients
func (s *ReportsService) SendWeeklyReports() error {
inbounds, err := s.inboundService.GetAllInbounds()
if err != nil {
return err
}
for i := range inbounds {
inbound := inbounds[i]
clients, _ := s.inboundService.GetClients(inbound)
for _, client := range clients {
_, err := s.GenerateClientReport(client.Email, "weekly")
if err != nil {
logger.Warningf("Failed to generate report for %s: %v", client.Email, err)
continue
}
// Send report (implement email/telegram sending)
logger.Infof("Generated weekly report for %s", client.Email)
}
}
return nil
}
// SendMonthlyReports sends monthly reports to all clients
func (s *ReportsService) SendMonthlyReports() error {
inbounds, err := s.inboundService.GetAllInbounds()
if err != nil {
return err
}
for i := range inbounds {
inbound := inbounds[i]
clients, _ := s.inboundService.GetClients(inbound)
for _, client := range clients {
_, err := s.GenerateClientReport(client.Email, "monthly")
if err != nil {
logger.Warningf("Failed to generate report for %s: %v", client.Email, err)
continue
}
// Send report
logger.Infof("Generated monthly report for %s", client.Email)
}
}
return nil
}