| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 | package analyticsimport (	"context"	"fmt"	"sort"	"github.com/0xJacky/Nginx-UI/internal/nginx_log/searcher")func (s *service) GetLogEntriesStats(ctx context.Context, req *searcher.SearchRequest) (*EntriesStats, error) {	if req == nil {		return nil, fmt.Errorf("request cannot be nil")	}	// Ensure facets are included for stats calculation	req.IncludeFacets = true	req.FacetFields = []string{"status", "method", "path_exact", "ip", "user_agent"}	req.FacetSize = 10 // Top 10 for lists	result, err := s.searcher.Search(ctx, req)	if err != nil {		return nil, fmt.Errorf("failed to search logs for entries stats: %w", err)	}	stats := &EntriesStats{		TotalEntries:   int64(result.TotalHits),		StatusCodeDist: make(map[string]int),		MethodDist:     make(map[string]int),		TopPaths:       make([]KeyValue, 0),		TopIPs:         make([]KeyValue, 0),		TopUserAgents:  make([]KeyValue, 0),	}	if result.Facets != nil {		if statusFacet, ok := result.Facets["status"]; ok {			for _, term := range statusFacet.Terms {				stats.StatusCodeDist[term.Term] = term.Count			}		}		if methodFacet, ok := result.Facets["method"]; ok {			for _, term := range methodFacet.Terms {				stats.MethodDist[term.Term] = term.Count			}		}		if pathFacet, ok := result.Facets["path_exact"]; ok {			for _, term := range pathFacet.Terms {				stats.TopPaths = append(stats.TopPaths, KeyValue{Key: term.Term, Value: term.Count})			}		}		if ipFacet, ok := result.Facets["ip"]; ok {			for _, term := range ipFacet.Terms {				stats.TopIPs = append(stats.TopIPs, KeyValue{Key: term.Term, Value: term.Count})			}		}		if uaFacet, ok := result.Facets["user_agent"]; ok {			for _, term := range uaFacet.Terms {				stats.TopUserAgents = append(stats.TopUserAgents, KeyValue{Key: term.Term, Value: term.Count})			}		}	}	// Populate stats if available	if result.Stats != nil {		stats.BytesStats = &BytesStatistics{			Total:   result.Stats.TotalBytes,			Average: result.Stats.AvgBytes,			Min:     result.Stats.MinBytes,			Max:     result.Stats.MaxBytes,		}		stats.ResponseTimeStats = &ResponseTimeStatistics{			Average: result.Stats.AvgReqTime,			Min:     result.Stats.MinReqTime,			Max:     result.Stats.MaxReqTime,		}	}	return stats, nil}// getTopKeyValuesFromMap is a helper to convert a map of counts to a sorted KeyValue slice.func getTopKeyValuesFromMap(counts map[string]int, limit int) []KeyValue {	kvs := make([]KeyValue, 0, len(counts))	for k, v := range counts {		kvs = append(kvs, KeyValue{Key: k, Value: v})	}	sort.Slice(kvs, func(i, j int) bool {		return kvs[i].Value > kvs[j].Value	})	if limit > 0 && len(kvs) > limit {		return kvs[:limit]	}	return kvs}func (s *service) GetTopPaths(ctx context.Context, req *TopListRequest) ([]KeyValue, error) {	if req == nil {		return nil, fmt.Errorf("request cannot be nil")	}	if err := s.ValidateTimeRange(req.StartTime, req.EndTime); err != nil {		return nil, err	}	searchReq := &searcher.SearchRequest{		StartTime:     &req.StartTime,		EndTime:       &req.EndTime,		LogPaths:      []string{req.LogPath},		Limit:         0, // We only need facets		IncludeFacets: true,		FacetFields:   []string{"path_exact"},		FacetSize:     req.Limit,		UseCache:      true,	}	result, err := s.searcher.Search(ctx, searchReq)	if err != nil {		return nil, fmt.Errorf("failed to get top paths: %w", err)	}	topPaths := make([]KeyValue, 0)	if result.Facets != nil {		if pathFacet, ok := result.Facets["path_exact"]; ok {			for _, term := range pathFacet.Terms {				topPaths = append(topPaths, KeyValue{Key: term.Term, Value: term.Count})			}		}	}	return topPaths, nil}func (s *service) GetTopIPs(ctx context.Context, req *TopListRequest) ([]KeyValue, error) {	if req == nil {		return nil, fmt.Errorf("request cannot be nil")	}	if err := s.ValidateTimeRange(req.StartTime, req.EndTime); err != nil {		return nil, err	}	searchReq := &searcher.SearchRequest{		StartTime:     &req.StartTime,		EndTime:       &req.EndTime,		LogPaths:      []string{req.LogPath},		Limit:         0,		IncludeFacets: true,		FacetFields:   []string{"ip"},		FacetSize:     req.Limit,		UseCache:      true,	}	result, err := s.searcher.Search(ctx, searchReq)	if err != nil {		return nil, fmt.Errorf("failed to get top IPs: %w", err)	}	topIPs := make([]KeyValue, 0)	if result.Facets != nil {		if ipFacet, ok := result.Facets["ip"]; ok {			for _, term := range ipFacet.Terms {				topIPs = append(topIPs, KeyValue{Key: term.Term, Value: term.Count})			}		}	}	return topIPs, nil}func (s *service) GetTopUserAgents(ctx context.Context, req *TopListRequest) ([]KeyValue, error) {	if req == nil {		return nil, fmt.Errorf("request cannot be nil")	}	if err := s.ValidateTimeRange(req.StartTime, req.EndTime); err != nil {		return nil, err	}	searchReq := &searcher.SearchRequest{		StartTime:     &req.StartTime,		EndTime:       &req.EndTime,		LogPaths:      []string{req.LogPath},		Limit:         0,		IncludeFacets: true,		FacetFields:   []string{"user_agent"},		FacetSize:     req.Limit,		UseCache:      true,	}	result, err := s.searcher.Search(ctx, searchReq)	if err != nil {		return nil, fmt.Errorf("failed to get top user agents: %w", err)	}	topUserAgents := make([]KeyValue, 0)	if result.Facets != nil {		if uaFacet, ok := result.Facets["user_agent"]; ok {			for _, term := range uaFacet.Terms {				topUserAgents = append(topUserAgents, KeyValue{Key: term.Term, Value: term.Count})			}		}	}	return topUserAgents, nil}
 |