add more filters

This commit is contained in:
pascal
2026-02-11 16:23:52 +01:00
parent bf48044e5c
commit d069145bd1
6 changed files with 208 additions and 18 deletions

View File

@@ -3,6 +3,7 @@ package accesslogs
import (
"net/http"
"strconv"
"time"
)
const (
@@ -18,21 +19,33 @@ type AccessLogFilter struct {
Page int
// PageSize is the number of records per page
PageSize int
// Filtering parameters
SourceIP *string // Filter by source IP address
Host *string // Filter by host header
Path *string // Filter by request path (supports LIKE pattern)
UserID *string // Filter by authenticated user ID
UserEmail *string // Filter by user email (requires user lookup)
UserName *string // Filter by user name (requires user lookup)
Method *string // Filter by HTTP method
StatusCode *int // Filter by HTTP status code
StartDate *time.Time // Filter by timestamp >= start_date
EndDate *time.Time // Filter by timestamp <= end_date
}
// ParseFromRequest parses pagination parameters from HTTP request query parameters
// ParseFromRequest parses pagination and filter parameters from HTTP request query parameters
func (f *AccessLogFilter) ParseFromRequest(r *http.Request) {
// Parse page number (default: 1)
queryParams := r.URL.Query()
f.Page = 1
if pageStr := r.URL.Query().Get("page"); pageStr != "" {
if pageStr := queryParams.Get("page"); pageStr != "" {
if page, err := strconv.Atoi(pageStr); err == nil && page > 0 {
f.Page = page
}
}
// Parse page size (default: DefaultPageSize, max: MaxPageSize)
f.PageSize = DefaultPageSize
if pageSizeStr := r.URL.Query().Get("page_size"); pageSizeStr != "" {
if pageSizeStr := queryParams.Get("page_size"); pageSizeStr != "" {
if pageSize, err := strconv.Atoi(pageSizeStr); err == nil && pageSize > 0 {
f.PageSize = pageSize
if f.PageSize > MaxPageSize {
@@ -40,6 +53,54 @@ func (f *AccessLogFilter) ParseFromRequest(r *http.Request) {
}
}
}
if sourceIP := queryParams.Get("source_ip"); sourceIP != "" {
f.SourceIP = &sourceIP
}
if host := queryParams.Get("host"); host != "" {
f.Host = &host
}
if path := queryParams.Get("path"); path != "" {
f.Path = &path
}
if userID := queryParams.Get("user_id"); userID != "" {
f.UserID = &userID
}
if userEmail := queryParams.Get("user_email"); userEmail != "" {
f.UserEmail = &userEmail
}
if userName := queryParams.Get("user_name"); userName != "" {
f.UserName = &userName
}
if method := queryParams.Get("method"); method != "" {
f.Method = &method
}
if statusCodeStr := queryParams.Get("status_code"); statusCodeStr != "" {
if statusCode, err := strconv.Atoi(statusCodeStr); err == nil && statusCode > 0 {
f.StatusCode = &statusCode
}
}
if startDate := queryParams.Get("start_date"); startDate != "" {
parsedStartDate, err := time.Parse(time.RFC3339, startDate)
if err == nil {
f.StartDate = &parsedStartDate
}
}
if endDate := queryParams.Get("end_date"); endDate != "" {
parsedEndDate, err := time.Parse(time.RFC3339, endDate)
if err == nil {
f.EndDate = &parsedEndDate
}
}
}
// GetOffset calculates the database offset for pagination

View File

@@ -6,5 +6,5 @@ import (
type Manager interface {
SaveAccessLog(ctx context.Context, proxyLog *AccessLogEntry) error
GetAllAccessLogs(ctx context.Context, accountID, userID string, filter AccessLogFilter) ([]*AccessLogEntry, int64, error)
GetAllAccessLogs(ctx context.Context, accountID, userID string, filter *AccessLogFilter) ([]*AccessLogEntry, int64, error)
}

View File

@@ -30,11 +30,10 @@ func (h *handler) getAccessLogs(w http.ResponseWriter, r *http.Request) {
return
}
// Parse pagination parameters from request
var filter accesslogs.AccessLogFilter
filter.ParseFromRequest(r)
logs, totalCount, err := h.manager.GetAllAccessLogs(r.Context(), userAuth.AccountId, userAuth.UserId, filter)
logs, totalCount, err := h.manager.GetAllAccessLogs(r.Context(), userAuth.AccountId, userAuth.UserId, &filter)
if err != nil {
util.WriteError(r.Context(), err, w)
return
@@ -45,7 +44,6 @@ func (h *handler) getAccessLogs(w http.ResponseWriter, r *http.Request) {
apiLogs = append(apiLogs, *log.ToAPIResponse())
}
// Return paginated response
response := &api.ProxyAccessLogsResponse{
Data: apiLogs,
Page: filter.Page,

View File

@@ -2,6 +2,7 @@ package manager
import (
"context"
"strings"
log "github.com/sirupsen/logrus"
@@ -55,8 +56,8 @@ func (m *managerImpl) SaveAccessLog(ctx context.Context, logEntry *accesslogs.Ac
return nil
}
// GetAllAccessLogs retrieves access logs for an account with pagination
func (m *managerImpl) GetAllAccessLogs(ctx context.Context, accountID, userID string, filter accesslogs.AccessLogFilter) ([]*accesslogs.AccessLogEntry, int64, error) {
// GetAllAccessLogs retrieves access logs for an account with pagination and filtering
func (m *managerImpl) GetAllAccessLogs(ctx context.Context, accountID, userID string, filter *accesslogs.AccessLogFilter) ([]*accesslogs.AccessLogEntry, int64, error) {
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Read)
if err != nil {
return nil, 0, status.NewPermissionValidationError(err)
@@ -65,10 +66,43 @@ func (m *managerImpl) GetAllAccessLogs(ctx context.Context, accountID, userID st
return nil, 0, status.NewPermissionDeniedError()
}
logs, totalCount, err := m.store.GetAccountAccessLogs(ctx, store.LockingStrengthNone, accountID, filter)
if err := m.resolveUserFilters(ctx, accountID, filter); err != nil {
log.WithContext(ctx).Warnf("failed to resolve user filters: %v", err)
}
logs, totalCount, err := m.store.GetAccountAccessLogs(ctx, store.LockingStrengthNone, accountID, *filter)
if err != nil {
return nil, 0, err
}
return logs, totalCount, nil
}
// resolveUserFilters converts user email/name filters to user ID filter
func (m *managerImpl) resolveUserFilters(ctx context.Context, accountID string, filter *accesslogs.AccessLogFilter) error {
if filter.UserEmail == nil && filter.UserName == nil {
return nil
}
users, err := m.store.GetAccountUsers(ctx, store.LockingStrengthNone, accountID)
if err != nil {
return err
}
var matchingUserIDs []string
for _, user := range users {
if filter.UserEmail != nil && strings.Contains(strings.ToLower(user.Email), strings.ToLower(*filter.UserEmail)) {
matchingUserIDs = append(matchingUserIDs, user.Id)
continue
}
if filter.UserName != nil && strings.Contains(strings.ToLower(user.Name), strings.ToLower(*filter.UserName)) {
matchingUserIDs = append(matchingUserIDs, user.Id)
}
}
if len(matchingUserIDs) > 0 {
filter.UserID = &matchingUserIDs[0]
}
return nil
}

View File

@@ -5061,24 +5061,28 @@ func (s *SqlStore) CreateAccessLog(ctx context.Context, logEntry *accesslogs.Acc
return nil
}
// GetAccountAccessLogs retrieves access logs for a given account with pagination
// GetAccountAccessLogs retrieves access logs for a given account with pagination and filtering
func (s *SqlStore) GetAccountAccessLogs(ctx context.Context, lockStrength LockingStrength, accountID string, filter accesslogs.AccessLogFilter) ([]*accesslogs.AccessLogEntry, int64, error) {
var logs []*accesslogs.AccessLogEntry
var totalCount int64
// Count total records for pagination metadata
countQuery := s.db.WithContext(ctx).
baseQuery := s.db.WithContext(ctx).
Model(&accesslogs.AccessLogEntry{}).
Where(accountIDCondition, accountID)
if err := countQuery.Count(&totalCount).Error; err != nil {
baseQuery = s.applyAccessLogFilters(baseQuery, filter)
if err := baseQuery.Count(&totalCount).Error; err != nil {
log.WithContext(ctx).Errorf("failed to count access logs: %v", err)
return nil, 0, status.Errorf(status.Internal, "failed to count access logs")
}
// Query with pagination
query := s.db.WithContext(ctx).
Where(accountIDCondition, accountID).
Where(accountIDCondition, accountID)
query = s.applyAccessLogFilters(query, filter)
query = query.
Order("timestamp DESC").
Limit(filter.GetLimit()).
Offset(filter.GetOffset())
@@ -5096,6 +5100,44 @@ func (s *SqlStore) GetAccountAccessLogs(ctx context.Context, lockStrength Lockin
return logs, totalCount, nil
}
// applyAccessLogFilters applies filter conditions to the query
func (s *SqlStore) applyAccessLogFilters(query *gorm.DB, filter accesslogs.AccessLogFilter) *gorm.DB {
if filter.SourceIP != nil {
query = query.Where("source_ip = ?", *filter.SourceIP)
}
if filter.Host != nil {
query = query.Where("host = ?", *filter.Host)
}
if filter.Path != nil {
// Support LIKE pattern for path filtering
query = query.Where("path LIKE ?", "%"+*filter.Path+"%")
}
if filter.UserID != nil {
query = query.Where("user_id = ?", *filter.UserID)
}
if filter.Method != nil {
query = query.Where("method = ?", *filter.Method)
}
if filter.StatusCode != nil {
query = query.Where("status_code = ?", *filter.StatusCode)
}
if filter.StartDate != nil {
query = query.Where("timestamp >= ?", *filter.StartDate)
}
if filter.EndDate != nil {
query = query.Where("timestamp <= ?", *filter.EndDate)
}
return query
}
func (s *SqlStore) GetReverseProxyTargetByTargetID(ctx context.Context, lockStrength LockingStrength, accountID string, targetID string) (*reverseproxy.Target, error) {
tx := s.db
if lockStrength != LockingStrengthNone {

View File

@@ -6389,6 +6389,61 @@ paths:
minimum: 1
maximum: 100
description: Number of items per page (max 100)
- in: query
name: source_ip
schema:
type: string
description: Filter by source IP address
- in: query
name: host
schema:
type: string
description: Filter by host header
- in: query
name: path
schema:
type: string
description: Filter by request path (supports partial matching)
- in: query
name: user_id
schema:
type: string
description: Filter by authenticated user ID
- in: query
name: user_email
schema:
type: string
description: Filter by user email (partial matching)
- in: query
name: user_name
schema:
type: string
description: Filter by user name (partial matching)
- in: query
name: method
schema:
type: string
enum: [GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS]
description: Filter by HTTP method
- in: query
name: status_code
schema:
type: integer
minimum: 100
maximum: 599
description: Filter by HTTP status code
- in: query
name: start_date
schema:
type: string
format: date-time
description: Filter by timestamp >= start_date (RFC3339 format)
- in: query
name: end_date
schema:
type: string
format: date-time
description: Filter by timestamp <= end_date (RFC3339 format)
responses:
"200":
description: Paginated list of reverse proxy access logs