275 lines
7.1 KiB
Go
275 lines
7.1 KiB
Go
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
|
|
}
|