add pagination for access logs

This commit is contained in:
pascal
2026-02-11 14:41:52 +01:00
parent 55b8d89a79
commit fb4cc37a4a
7 changed files with 126 additions and 21 deletions

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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) {

View File

@@ -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)
}

View File

@@ -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':

View File

@@ -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)