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

The event log now loads the 50 newest events in its query and opens
only the newest on load, as the recent events on a webhook's page
already do. With 50 at most there is never a second page, so paging is
gone: the page links, the `page` query parameter, the page number that
Replay and Resubmit carried back to the log, and `pageOrFirst` with its
test. The count beside the heading reads "50 most recent of N events"
when there are more. The comments and README lines that named `page` as
the query parameter the service reads now name `next` and `notice`.

Model: opus-5-5
This commit is contained in:
2026-10-03 00:53:21 +00:00
committed by sneak
parent bcdd4791ec
commit d7107f1f9e
17 changed files with 157 additions and 185 deletions
+15 -54
View File
@@ -1112,30 +1112,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)
@@ -1195,15 +1182,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
@@ -1213,7 +1196,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
@@ -1228,9 +1210,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,
@@ -1303,10 +1283,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
@@ -1320,9 +1301,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
}
@@ -1331,9 +1310,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) {
@@ -1759,24 +1738,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