package webui import ( "net/http" "sort" "time" "mailgoserver/internal/db" ) const perPage = 50 // logs mirrors logs.py's logs(): type=all|emails|auth, page (mostly cosmetic in "all" // mode, matching the Python version's own quirk where "all" mode's pagination isn't // real pagination — see the route inventory). Scoped admins get the fetched page // filtered down to their domains in-memory (these tables have no domain_id column to // filter in SQL — see accessibleDomainNames) — pagination counts stay based on the // unfiltered page, same imprecision the "all" mode already had before scoping existed. func (a *App) logs(w http.ResponseWriter, r *http.Request) { allowedNames, isGlobal, err := a.accessibleDomainNames(r) if err != nil { setFlash(w, "error", "Error loading logs") } emailAllowed := func(e db.EmailLog) bool { return isGlobal || allowedNames[emailDomain(e.MailFrom)] } authAllowed := func(au db.AuthLog) bool { return isGlobal || allowedNames[authLogDomain(au.Identifier)] } filterType := r.URL.Query().Get("type") if filterType == "" { filterType = "all" } page := atoi(r.URL.Query().Get("page")) if page < 1 { page = 1 } offset := (page - 1) * perPage switch filterType { case "emails": fetched, err := a.DB.ListEmailLogsPage(offset, perPage) if err != nil { setFlash(w, "error", "Error loading logs") } var emails []db.EmailLog for _, e := range fetched { if emailAllowed(e) { emails = append(emails, e) } } recipientMap, attachMap := a.buildLogMaps(emails) a.render(w, r, "logs.html", M{ "active": "logs", "logs": emails, "filter_type": filterType, "page": page, "has_next": len(fetched) == perPage, "has_prev": page > 1, "recipient_logs_map": recipientMap, "attachments_map": attachMap, }) case "auth": fetched, err := a.DB.ListAuthLogsPage(offset, perPage) if err != nil { setFlash(w, "error", "Error loading logs") } var auths []db.AuthLog for _, au := range fetched { if authAllowed(au) { auths = append(auths, au) } } a.render(w, r, "logs.html", M{ "active": "logs", "logs": auths, "filter_type": filterType, "page": page, "has_next": len(fetched) == perPage, "has_prev": page > 1, }) default: half := perPage / 2 fetchedEmails, _ := a.DB.ListEmailLogsPage(0, half) fetchedAuths, _ := a.DB.ListAuthLogsPage(0, half) recipientMap, _ := a.buildLogMaps(fetchedEmails) type combinedEntry struct { M M At time.Time } var combined []combinedEntry for _, e := range fetchedEmails { if !emailAllowed(e) { continue } combined = append(combined, combinedEntry{M: M{"type": "email", "data": e, "recipients": recipientMap[e.ID]}, At: e.Timestamp}) } for _, au := range fetchedAuths { if !authAllowed(au) { continue } combined = append(combined, combinedEntry{M: M{"type": "auth", "data": au}, At: au.CreatedAt}) } sort.SliceStable(combined, func(i, j int) bool { return combined[i].At.After(combined[j].At) }) if len(combined) > perPage { combined = combined[:perPage] } var logs []M for _, c := range combined { logs = append(logs, c.M) } a.render(w, r, "logs.html", M{ "active": "logs", "logs": logs, "filter_type": filterType, "page": page, "has_next": len(logs) > perPage, "has_prev": page > 1, }) } } func (a *App) buildLogMaps(emails []db.EmailLog) (map[int64][]db.EmailRecipientLog, map[int64][]db.EmailAttachment) { recipientMap := map[int64][]db.EmailRecipientLog{} attachMap := map[int64][]db.EmailAttachment{} for _, e := range emails { recs, _ := a.DB.ListRecipientLogsForEmail(e.ID) recipientMap[e.ID] = recs atts, _ := a.DB.ListAttachmentsForEmail(e.ID) attachMap[e.ID] = atts } return recipientMap, attachMap }