package handlers import ( "context" "net/http" "strconv" "time" "git.haelnorr.com/h/golib/hws" "git.haelnorr.com/h/oslstats/internal/db" "git.haelnorr.com/h/oslstats/internal/throw" "git.haelnorr.com/h/oslstats/internal/validation" adminview "git.haelnorr.com/h/oslstats/internal/view/adminview" "github.com/pkg/errors" "github.com/uptrace/bun" ) // AdminAuditLogsPage renders the full admin dashboard page with audit logs section func AdminAuditLogsPage(s *hws.Server, conn *bun.DB) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var logs *db.List[db.AuditLog] var users []*db.User var actions []string var resourceTypes []string if ok := db.WithReadTx(s, w, r, conn, func(ctx context.Context, tx bun.Tx) (bool, error) { var err error // Get page options from query pageOpts := pageOptsFromQuery(s, w, r) if pageOpts == nil { return false, nil } // Get filters from query filters, ok := getAuditFiltersFromQuery(s, w, r) if !ok { return false, nil } // Get audit logs logs, err = db.GetAuditLogs(ctx, tx, pageOpts, filters) if err != nil { return false, errors.Wrap(err, "db.GetAuditLogs") } // Get all users for filter dropdown usersList, err := db.GetUsers(ctx, tx, nil) if err != nil { return false, errors.Wrap(err, "db.GetUsers") } users = usersList.Items // Get unique actions actions, err = db.GetUniqueActions(ctx, tx) if err != nil { return false, errors.Wrap(err, "db.GetUniqueActions") } // Get unique resource types resourceTypes, err = db.GetUniqueResourceTypes(ctx, tx) if err != nil { return false, errors.Wrap(err, "db.GetUniqueResourceTypes") } return true, nil }); !ok { return } renderSafely(adminview.AuditLogsPage(logs, users, actions, resourceTypes), s, r, w) }) } // AdminAuditLogsList shows audit logs (HTMX content replacement - full section with filters) func AdminAuditLogsList(s *hws.Server, conn *bun.DB) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var logs *db.List[db.AuditLog] var users []*db.User var actions []string var resourceTypes []string if ok := db.WithReadTx(s, w, r, conn, func(ctx context.Context, tx bun.Tx) (bool, error) { var err error // Get page options from form pageOpts := pageOptsFromForm(s, w, r) if pageOpts == nil { return false, nil } // No filters for initial section load filters := db.NewAuditLogFilter() // Get audit logs logs, err = db.GetAuditLogs(ctx, tx, pageOpts, filters) if err != nil { return false, errors.Wrap(err, "db.GetAuditLogs") } // Get all users for filter dropdown usersList, err := db.GetUsers(ctx, tx, nil) if err != nil { return false, errors.Wrap(err, "db.GetUsers") } users = usersList.Items // Get unique actions actions, err = db.GetUniqueActions(ctx, tx) if err != nil { return false, errors.Wrap(err, "db.GetUniqueActions") } // Get unique resource types resourceTypes, err = db.GetUniqueResourceTypes(ctx, tx) if err != nil { return false, errors.Wrap(err, "db.GetUniqueResourceTypes") } return true, nil }); !ok { return } renderSafely(adminview.AuditLogsList(logs, users, actions, resourceTypes), s, r, w) }) } // AdminAuditLogsFilter handles filter requests and returns only the results table func AdminAuditLogsFilter(s *hws.Server, conn *bun.DB) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var logs *db.List[db.AuditLog] if ok := db.WithReadTx(s, w, r, conn, func(ctx context.Context, tx bun.Tx) (bool, error) { var err error // Get page options from form pageOpts := pageOptsFromForm(s, w, r) if pageOpts == nil { return false, nil } // Get filters from form filters, ok := getAuditFiltersFromForm(s, w, r) if !ok { return false, nil } // Get audit logs logs, err = db.GetAuditLogs(ctx, tx, pageOpts, filters) if err != nil { return false, errors.Wrap(err, "db.GetAuditLogs") } return true, nil }); !ok { return } renderSafely(adminview.AuditLogsResults(logs), s, r, w) }) } // AdminAuditLogDetail shows details for a single audit log entry func AdminAuditLogDetail(s *hws.Server, conn *bun.DB) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Get ID from path idStr := r.PathValue("id") if idStr == "" { throw.BadRequest(s, w, r, "Missing audit log ID", nil) return } id, err := strconv.Atoi(idStr) if err != nil { throw.BadRequest(s, w, r, "Invalid audit log ID", err) return } var log *db.AuditLog if ok := db.WithReadTx(s, w, r, conn, func(ctx context.Context, tx bun.Tx) (bool, error) { var err error log, err = db.GetAuditLogByID(ctx, tx, id) if err != nil { return false, errors.Wrap(err, "db.GetAuditLogByID") } if log == nil { throw.NotFound(s, w, r, r.URL.Path) return false, nil } return true, nil }); !ok { return } renderSafely(adminview.AuditLogDetail(log), s, r, w) }) } // getAuditFiltersFromQuery extracts audit log filters from query string func getAuditFiltersFromQuery(s *hws.Server, w http.ResponseWriter, r *http.Request) (*db.AuditLogFilter, bool) { g := validation.NewQueryGetter(r) return buildAuditFilters(g, s, w, r) } // getAuditFiltersFromForm extracts audit log filters from form data func getAuditFiltersFromForm(s *hws.Server, w http.ResponseWriter, r *http.Request) (*db.AuditLogFilter, bool) { g, ok := validation.ParseFormOrError(s, w, r) if !ok { return nil, false } return buildAuditFilters(g, s, w, r) } // buildAuditFilters builds audit log filters from a validation.Getter func buildAuditFilters(g validation.Getter, s *hws.Server, w http.ResponseWriter, r *http.Request) (*db.AuditLogFilter, bool) { filters := db.NewAuditLogFilter() // User ID filter (optional) userID := g.Int("user_id").Optional().Min(1).Value // Action filter (optional) action := g.String("action").TrimSpace().Optional().Value // Resource Type filter (optional) resourceType := g.String("resource_type").TrimSpace().Optional().Value // Result filter (optional) result := g.String("result").TrimSpace().Optional().AllowedValues([]string{"success", "denied", "error"}).Value // Date range filter (optional) startDateStr := g.String("start_date").TrimSpace().Optional().Value endDateStr := g.String("end_date").TrimSpace().Optional().Value // Validate if !g.ValidateAndError(s, w, r) { return nil, false } // Apply filters if userID > 0 { filters.UserID(userID) } if action != "" { filters.Action(action) } if resourceType != "" { filters.ResourceType(resourceType) } if result != "" { filters.Result(result) } // Parse and apply date range if startDateStr != "" { if startDate, err := time.Parse("2006-01-02", startDateStr); err == nil { filters.DateRange(startDate.Unix(), 0) } } if endDateStr != "" { if endDate, err := time.Parse("2006-01-02", endDateStr); err == nil { // Set to end of day endOfDay := endDate.Add(23*time.Hour + 59*time.Minute + 59*time.Second) filters.DateRange(0, endOfDay.Unix()) } } return filters, true }