feat(api): add server-side pagination and sorting to event logs API
- update event logs API to support pagination and sorting via headers - modify event logs page to handle new API response structure - implement debounced search functionality for improved UX - adjust total count display to reflect actual number of logs
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -307,13 +308,17 @@ func handleLogout(w http.ResponseWriter, r *http.Request, secProvider security.S
|
||||
|
||||
// QueryRequest represents a unified query request
|
||||
type QueryRequest struct {
|
||||
Action string `json:"action"` // "list", "get", "create", "update", "delete"
|
||||
Table string `json:"table"` // Table name (e.g., "users", "hooks")
|
||||
ID string `json:"id,omitempty"` // For get/update/delete
|
||||
Data map[string]interface{} `json:"data,omitempty"` // For create/update
|
||||
Filters map[string]interface{} `json:"filters,omitempty"` // For list
|
||||
Limit int `json:"limit,omitempty"`
|
||||
Offset int `json:"offset,omitempty"`
|
||||
Action string `json:"action"` // "list", "get", "create", "update", "delete"
|
||||
Table string `json:"table"` // Table name (e.g., "users", "hooks")
|
||||
ID string `json:"id,omitempty"` // For get/update/delete
|
||||
Data map[string]interface{} `json:"data,omitempty"` // For create/update
|
||||
Filters map[string]interface{} `json:"filters,omitempty"` // For list — exact match
|
||||
Search string `json:"search,omitempty"` // For list — LIKE across SearchColumns
|
||||
SearchColumns []string `json:"search_columns,omitempty"` // Columns to apply Search against
|
||||
OrderBy string `json:"order_by,omitempty"` // Column to order by
|
||||
OrderDir string `json:"order_dir,omitempty"` // "ASC" or "DESC"
|
||||
Limit int `json:"limit,omitempty"`
|
||||
Offset int `json:"offset,omitempty"`
|
||||
}
|
||||
|
||||
// handleQuery handles unified query requests
|
||||
@@ -348,24 +353,45 @@ func handleQuery(w http.ResponseWriter, r *http.Request, db *bun.DB, secProvider
|
||||
}
|
||||
}
|
||||
|
||||
// applySearchAndFilters applies exact-match filters and LIKE search to a select query.
|
||||
func applySearchAndFilters(query *bun.SelectQuery, req QueryRequest) *bun.SelectQuery {
|
||||
for key, value := range req.Filters {
|
||||
query = query.Where("? = ?", bun.Ident(key), value)
|
||||
}
|
||||
if req.Search != "" && len(req.SearchColumns) > 0 {
|
||||
query = query.WhereGroup(" AND ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||
for i, col := range req.SearchColumns {
|
||||
if i == 0 {
|
||||
q = q.Where("? LIKE ?", bun.Ident(col), "%"+req.Search+"%")
|
||||
} else {
|
||||
q = q.WhereOr("? LIKE ?", bun.Ident(col), "%"+req.Search+"%")
|
||||
}
|
||||
}
|
||||
return q
|
||||
})
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
// handleQueryList lists records from a table
|
||||
func handleQueryList(w http.ResponseWriter, r *http.Request, db *bun.DB, req QueryRequest, userCtx *security.UserContext) {
|
||||
// Get model registry to find the model
|
||||
registry := getModelForTable(req.Table)
|
||||
if registry == nil {
|
||||
http.Error(w, "Table not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// Create slice to hold results
|
||||
results := registry()
|
||||
|
||||
// Build query
|
||||
query := db.NewSelect().Model(results)
|
||||
query = applySearchAndFilters(query, req)
|
||||
|
||||
// Apply filters
|
||||
for key, value := range req.Filters {
|
||||
query = query.Where("? = ?", bun.Ident(key), value)
|
||||
// Apply ordering
|
||||
if req.OrderBy != "" {
|
||||
dir := "ASC"
|
||||
if strings.ToUpper(req.OrderDir) == "DESC" {
|
||||
dir = "DESC"
|
||||
}
|
||||
query = query.OrderExpr("? "+dir, bun.Ident(req.OrderBy))
|
||||
}
|
||||
|
||||
// Apply limit/offset
|
||||
@@ -376,9 +402,8 @@ func handleQueryList(w http.ResponseWriter, r *http.Request, db *bun.DB, req Que
|
||||
query = query.Offset(req.Offset)
|
||||
}
|
||||
|
||||
// Execute query
|
||||
if err := query.Scan(r.Context()); err != nil {
|
||||
http.Error(w, fmt.Sprintf("Query failed: %v", err), http.StatusInternalServerError)
|
||||
http.Error(w, "Query failed", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
2
pkg/serverembed/dist/index.html
vendored
2
pkg/serverembed/dist/index.html
vendored
@@ -5,7 +5,7 @@
|
||||
<link rel="icon" type="image/svg+xml" href="/ui/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>web</title>
|
||||
<script type="module" crossorigin src="/ui/assets/index-CExXKuWO.js"></script>
|
||||
<script type="module" crossorigin src="/ui/assets/index-Cj4Q_Qgu.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/ui/assets/index-Bfia8Lvm.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Reference in New Issue
Block a user