diff --git a/management/internals/modules/reverseproxy/accesslogs/interface.go b/management/internals/modules/reverseproxy/accesslogs/interface.go index 3b92b04e0..af5b0c806 100644 --- a/management/internals/modules/reverseproxy/accesslogs/interface.go +++ b/management/internals/modules/reverseproxy/accesslogs/interface.go @@ -6,5 +6,5 @@ import ( type Manager interface { SaveAccessLog(ctx context.Context, proxyLog *AccessLogEntry) error - GetAllAccessLogs(ctx context.Context, accountID, userID string) ([]*AccessLogEntry, error) + GetAllAccessLogs(ctx context.Context, accountID, userID string, filter AccessLogFilter) ([]*AccessLogEntry, int64, error) } diff --git a/management/internals/modules/reverseproxy/accesslogs/manager/api.go b/management/internals/modules/reverseproxy/accesslogs/manager/api.go index a0e5ad84c..8c3bbdc39 100644 --- a/management/internals/modules/reverseproxy/accesslogs/manager/api.go +++ b/management/internals/modules/reverseproxy/accesslogs/manager/api.go @@ -30,7 +30,11 @@ func (h *handler) getAccessLogs(w http.ResponseWriter, r *http.Request) { return } - logs, err := h.manager.GetAllAccessLogs(r.Context(), userAuth.AccountId, userAuth.UserId) + // 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) if err != nil { util.WriteError(r.Context(), err, w) return @@ -41,5 +45,22 @@ func (h *handler) getAccessLogs(w http.ResponseWriter, r *http.Request) { apiLogs = append(apiLogs, *log.ToAPIResponse()) } - util.WriteJSONObject(r.Context(), w, apiLogs) + // Return paginated response + response := &api.ProxyAccessLogsResponse{ + Data: apiLogs, + Page: filter.Page, + PageSize: filter.PageSize, + TotalRecords: int(totalCount), + TotalPages: getTotalPageCount(int(totalCount), filter.PageSize), + } + + util.WriteJSONObject(r.Context(), w, response) +} + +// getTotalPageCount calculates the total number of pages +func getTotalPageCount(totalCount, pageSize int) int { + if pageSize <= 0 { + return 0 + } + return (totalCount + pageSize - 1) / pageSize } diff --git a/management/internals/modules/reverseproxy/accesslogs/manager/manager.go b/management/internals/modules/reverseproxy/accesslogs/manager/manager.go index 319e401be..087069cc0 100644 --- a/management/internals/modules/reverseproxy/accesslogs/manager/manager.go +++ b/management/internals/modules/reverseproxy/accesslogs/manager/manager.go @@ -55,20 +55,20 @@ func (m *managerImpl) SaveAccessLog(ctx context.Context, logEntry *accesslogs.Ac return nil } -// GetAllAccessLogs retrieves all access logs for an account -func (m *managerImpl) GetAllAccessLogs(ctx context.Context, accountID, userID string) ([]*accesslogs.AccessLogEntry, error) { +// 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) { ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Read) if err != nil { - return nil, status.NewPermissionValidationError(err) + return nil, 0, status.NewPermissionValidationError(err) } if !ok { - return nil, status.NewPermissionDeniedError() + return nil, 0, status.NewPermissionDeniedError() } - logs, err := m.store.GetAccountAccessLogs(ctx, store.LockingStrengthNone, accountID) + logs, totalCount, err := m.store.GetAccountAccessLogs(ctx, store.LockingStrengthNone, accountID, filter) if err != nil { - return nil, err + return nil, 0, err } - return logs, nil + return logs, totalCount, nil } diff --git a/management/server/store/sql_store.go b/management/server/store/sql_store.go index 048cd8962..c432ce7e3 100644 --- a/management/server/store/sql_store.go +++ b/management/server/store/sql_store.go @@ -5061,14 +5061,27 @@ func (s *SqlStore) CreateAccessLog(ctx context.Context, logEntry *accesslogs.Acc return nil } -// GetAccountAccessLogs retrieves all access logs for a given account -func (s *SqlStore) GetAccountAccessLogs(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*accesslogs.AccessLogEntry, error) { +// GetAccountAccessLogs retrieves access logs for a given account with pagination +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). + Model(&accesslogs.AccessLogEntry{}). + Where(accountIDCondition, accountID) + + if err := countQuery.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). Order("timestamp DESC"). - Limit(1000) + Limit(filter.GetLimit()). + Offset(filter.GetOffset()) if lockStrength != LockingStrengthNone { query = query.Clauses(clause.Locking{Strength: string(lockStrength)}) @@ -5077,10 +5090,10 @@ func (s *SqlStore) GetAccountAccessLogs(ctx context.Context, lockStrength Lockin result := query.Find(&logs) if result.Error != nil { log.WithContext(ctx).Errorf("failed to get access logs from store: %v", result.Error) - return nil, status.Errorf(status.Internal, "failed to get access logs from store") + return nil, 0, status.Errorf(status.Internal, "failed to get access logs from store") } - return logs, nil + return logs, totalCount, nil } func (s *SqlStore) GetReverseProxyTargetByTargetID(ctx context.Context, lockStrength LockingStrength, accountID string, targetID string) (*reverseproxy.Target, error) { diff --git a/management/server/store/store.go b/management/server/store/store.go index 94e5a50b9..6afad460f 100644 --- a/management/server/store/store.go +++ b/management/server/store/store.go @@ -266,7 +266,7 @@ type Store interface { DeleteCustomDomain(ctx context.Context, accountID string, domainID string) error CreateAccessLog(ctx context.Context, log *accesslogs.AccessLogEntry) error - GetAccountAccessLogs(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*accesslogs.AccessLogEntry, error) + GetAccountAccessLogs(ctx context.Context, lockStrength LockingStrength, accountID string, filter accesslogs.AccessLogFilter) ([]*accesslogs.AccessLogEntry, int64, error) GetReverseProxyTargetByTargetID(ctx context.Context, lockStrength LockingStrength, accountID string, targetID string) (*reverseproxy.Target, error) } diff --git a/shared/management/http/api/openapi.yml b/shared/management/http/api/openapi.yml index 0c9a168d4..644617f4f 100644 --- a/shared/management/http/api/openapi.yml +++ b/shared/management/http/api/openapi.yml @@ -2773,6 +2773,36 @@ components: - path - duration_ms - status_code + ProxyAccessLogsResponse: + type: object + properties: + data: + type: array + description: List of proxy access log entries + items: + $ref: "#/components/schemas/ProxyAccessLog" + page: + type: integer + description: Current page number + example: 1 + page_size: + type: integer + description: Number of items per page + example: 50 + total_records: + type: integer + description: Total number of log records available + example: 523 + total_pages: + type: integer + description: Total number of pages available + example: 11 + required: + - data + - page + - page_size + - total_records + - total_pages IdentityProviderType: type: string description: Type of identity provider @@ -6341,17 +6371,31 @@ paths: /api/events/proxy: get: summary: List all Reverse Proxy Access Logs - description: Returns a list of all reverse proxy access log entries + description: Returns a paginated list of all reverse proxy access log entries tags: [ Events ] + parameters: + - in: query + name: page + schema: + type: integer + default: 1 + minimum: 1 + description: Page number for pagination (1-indexed) + - in: query + name: page_size + schema: + type: integer + default: 50 + minimum: 1 + maximum: 100 + description: Number of items per page (max 100) responses: "200": - description: List of reverse proxy access logs + description: Paginated list of reverse proxy access logs content: application/json: schema: - type: array - items: - $ref: "#/components/schemas/ProxyAccessLog" + $ref: "#/components/schemas/ProxyAccessLogsResponse" '401': "$ref": "#/components/responses/requires_authentication" '403': diff --git a/shared/management/http/api/types.gen.go b/shared/management/http/api/types.gen.go index 53a6f0070..df59b129a 100644 --- a/shared/management/http/api/types.gen.go +++ b/shared/management/http/api/types.gen.go @@ -1950,6 +1950,24 @@ type ProxyAccessLog struct { UserId *string `json:"user_id,omitempty"` } +// ProxyAccessLogsResponse defines model for ProxyAccessLogsResponse. +type ProxyAccessLogsResponse struct { + // Data List of proxy access log entries + Data []ProxyAccessLog `json:"data"` + + // Page Current page number + Page int `json:"page"` + + // PageSize Number of items per page + PageSize int `json:"page_size"` + + // TotalPages Total number of pages available + TotalPages int `json:"total_pages"` + + // TotalRecords Total number of log records available + TotalRecords int `json:"total_records"` +} + // ProxyCluster A proxy cluster represents a group of proxy nodes serving the same address type ProxyCluster struct { // Address Cluster address used for CNAME targets @@ -2655,6 +2673,15 @@ type GetApiEventsNetworkTrafficParamsConnectionType string // GetApiEventsNetworkTrafficParamsDirection defines parameters for GetApiEventsNetworkTraffic. type GetApiEventsNetworkTrafficParamsDirection string +// GetApiEventsProxyParams defines parameters for GetApiEventsProxy. +type GetApiEventsProxyParams struct { + // Page Page number for pagination (1-indexed) + Page *int `form:"page,omitempty" json:"page,omitempty"` + + // PageSize Number of items per page (max 100) + PageSize *int `form:"page_size,omitempty" json:"page_size,omitempty"` +} + // GetApiGroupsParams defines parameters for GetApiGroups. type GetApiGroupsParams struct { // Name Filter groups by name (exact match)