Event log: only the newest event expanded, and only the 50 most recent (closes #349)
check / check (push) Successful in 3m17s

The event log now loads only the 50 newest events, limited in its query, and only the newest starts expanded; the rest start collapsed. Paging is removed rather than capped, since 50 events never need a second page: the Previous and Next links, the `page` query parameter and the page number Replay and Resubmit carried back are gone, and a `?page=` left in an old link shows the 50 newest. A webhook with more than 50 events reads "50 most recent of N events" beside the heading. Comments and a README line that called `page` the only query parameter the service reads now name `next` and `notice`.

Model: opus-5-5
This commit was merged in pull request #488.
This commit is contained in:
2026-10-03 03:43:27 +02:00
parent 17e6c85dd8
commit ea8384f4a2
17 changed files with 157 additions and 185 deletions
+15 -54
View File
@@ -1113,30 +1113,17 @@ func (h *Handlers) HandleSourceLogs() http.HandlerFunc {
return
}
page := h.parsePage(r)
evts, total, ok := h.loadEventsWithDeliveries(
w, r, webhook, targets, page,
w, r, webhook, targets,
)
if !ok {
return
}
totalPages := int(total) / paginationPerPage
if int(total)%paginationPerPage != 0 {
totalPages++
}
data := map[string]any{
tmplKeyWebhook: &webhook,
"Events": evts,
"Page": page,
"TotalPages": totalPages,
"TotalEvents": total,
"HasPrev": page > 1,
"HasNext": page < totalPages,
"PrevPage": page - 1,
"NextPage": page + 1,
}
h.renderTemplate(w, r, "source_logs.html", data)
@@ -1196,15 +1183,11 @@ func (h *Handlers) loadTargetMap(
return targetMap, nil
}
// parsePage extracts a page number from the query string.
func (h *Handlers) parsePage(r *http.Request) int {
return pageOrFirst(r.URL.Query().Get("page"))
}
// loadEventsWithDeliveries loads paginated events and their
// deliveries from the per-webhook database. Events come back
// as capped projections rather than database.Event rows: see
// eventLogColumns for why the cut happens in SQL.
// loadEventsWithDeliveries loads the recentEventLimit newest events
// and their deliveries from the per-webhook database, and the total
// number of events stored. Events come back as capped projections
// rather than database.Event rows: see eventLogColumns for why the
// cut happens in SQL.
//
// The bool reports whether the load succeeded. It is false
// once this has answered the request with an error, and the
@@ -1214,7 +1197,6 @@ func (h *Handlers) loadEventsWithDeliveries(
r *http.Request,
webhook database.Webhook,
targetMap map[string]eventLogTarget,
page int,
) ([]EventLogView, int64, bool) {
if !h.dbMgr.DBExists(webhook.ID) {
return nil, 0, true
@@ -1229,9 +1211,7 @@ func (h *Handlers) loadEventsWithDeliveries(
return nil, 0, false
}
rows, totalEvents := loadEventLogRows(
webhookDB, webhook.ID, page,
)
rows, totalEvents := loadEventLogRows(webhookDB, webhook.ID)
result, ok := h.eventLogViews(
w, r, webhookDB, webhook.ID, rows, targetMap,
@@ -1304,10 +1284,11 @@ func (h *Handlers) eventLogViews(
return result, true
}
// loadEventLogRows reads one page of the event log projection, newest
// first, and the total number of events the pager counts against.
// loadEventLogRows reads the event log projection of the
// recentEventLimit newest events, newest first, and the total number
// of events stored.
func loadEventLogRows(
webhookDB *gorm.DB, webhookID string, page int,
webhookDB *gorm.DB, webhookID string,
) ([]eventLogRow, int64) {
var totalEvents int64
@@ -1321,9 +1302,7 @@ func loadEventLogRows(
eventLogColumns, maxRenderedBodyBytes,
).Where(
"webhook_id = ?", webhookID,
).Order("created_at DESC").Offset(
(page - 1) * paginationPerPage,
).Limit(paginationPerPage).Find(&rows)
).Order("created_at DESC").Limit(recentEventLimit).Find(&rows)
return rows, totalEvents
}
@@ -1332,9 +1311,9 @@ func loadEventLogRows(
// events have been resubmitted from it.
//
// One grouped query covers the page rather than one query per event.
// A page holds paginationPerPage ids, far below SQLite's bound
// parameter ceiling, so it needs no chunking as the delivery result
// load does.
// The page shows at most recentEventLimit events, far below SQLite's
// bound parameter ceiling, so it needs no chunking as the delivery
// result load does.
func resubmitCounts(
webhookDB *gorm.DB, eventIDs []string,
) (map[string]int, error) {
@@ -1760,24 +1739,6 @@ func (h *Handlers) setTargetFromForm(
return "", nil
}
// pageOrFirst parses a paginated page number, answering 1 for
// anything empty, unparseable or out of range.
//
// Falling back rather than rejecting is correct here and only here:
// a page number is where to send the browser next, not configuration
// the operator is storing, and the actions that submit one have
// already completed by the time it is read — answering 400 would
// report a failure that did not happen. Anything an operator SETS
// must be validated instead; see parseMaxRetries.
func pageOrFirst(s string) int {
v, err := strconv.Atoi(strings.TrimSpace(s))
if err != nil || v < 1 {
return 1
}
return v
}
// targetFormInput carries the raw values of a target form. Both the
// create and the edit path fill one and hand it to setTargetFromForm,
// so neither can come to validate a target differently from the