259 lines
6.8 KiB
Go
259 lines
6.8 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"
|
|
"git.haelnorr.com/h/timefmt"
|
|
"github.com/pkg/errors"
|
|
"github.com/uptrace/bun"
|
|
)
|
|
|
|
// AdminAuditLogsPage renders the full admin dashboard page with audit logs section (GET request)
|
|
func AdminAuditLogsPage(s *hws.Server, conn *db.DB) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
pageOpts, ok := db.GetPageOpts(s, w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
var logs *db.List[db.AuditLog]
|
|
var users []*db.User
|
|
var actions []string
|
|
var resourceTypes []string
|
|
|
|
if ok := conn.WithReadTx(s, w, r, func(ctx context.Context, tx bun.Tx) (bool, error) {
|
|
var err error
|
|
|
|
// 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 the full audit logs list with filters (POST request for HTMX)
|
|
func AdminAuditLogsList(s *hws.Server, conn *db.DB) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
pageOpts, ok := db.GetPageOpts(s, w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
var logs *db.List[db.AuditLog]
|
|
var users []*db.User
|
|
var actions []string
|
|
var resourceTypes []string
|
|
|
|
if ok := conn.WithReadTx(s, w, r, func(ctx context.Context, tx bun.Tx) (bool, error) {
|
|
var err error
|
|
|
|
// 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")
|
|
}
|
|
|
|
// 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 returns only the results container (table + pagination) for HTMX updates
|
|
func AdminAuditLogsFilter(s *hws.Server, conn *db.DB) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
pageOpts, ok := db.GetPageOpts(s, w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
var logs *db.List[db.AuditLog]
|
|
|
|
if ok := conn.WithReadTx(s, w, r, func(ctx context.Context, tx bun.Tx) (bool, error) {
|
|
var err error
|
|
|
|
// 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
|
|
}
|
|
|
|
// Return only the results container, not the full page with filters
|
|
renderSafely(adminview.AuditLogsResults(logs), s, r, w)
|
|
})
|
|
}
|
|
|
|
// AdminAuditLogDetail shows details for a single audit log entry
|
|
func AdminAuditLogDetail(s *hws.Server, conn *db.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 := conn.WithReadTx(s, w, r, func(ctx context.Context, tx bun.Tx) (bool, error) {
|
|
var err error
|
|
log, err = db.GetAuditLogByID(ctx, tx, id)
|
|
if err != nil {
|
|
if db.IsBadRequest(err) {
|
|
throw.NotFound(s, w, r, r.URL.Path)
|
|
return false, nil
|
|
}
|
|
return false, errors.Wrap(err, "db.GetAuditLogByID")
|
|
}
|
|
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)
|
|
filters, ok := buildAuditFilters(g, s, w, r)
|
|
return filters, ok
|
|
}
|
|
|
|
// 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()
|
|
|
|
userIDs := g.IntList("user_id").Values()
|
|
actions := g.StringList("action").Values()
|
|
resourceTypes := g.StringList("resource_type").Values()
|
|
results := g.StringList("result").Values()
|
|
format := timefmt.NewBuilder().DayNumeric2().Slash().
|
|
MonthNumeric2().Slash().Year4().Build()
|
|
startDate := g.Time("start_date", format).Optional().Value
|
|
endDate := g.Time("end_date", format).Optional().Value
|
|
|
|
if !g.ValidateAndError(s, w, r) {
|
|
return nil, false
|
|
}
|
|
|
|
if len(userIDs) > 0 {
|
|
filters.UserIDs(userIDs)
|
|
}
|
|
if len(actions) > 0 {
|
|
filters.Actions(actions)
|
|
}
|
|
if len(resourceTypes) > 0 {
|
|
filters.ResourceTypes(resourceTypes)
|
|
}
|
|
if len(results) > 0 {
|
|
filters.Results(results)
|
|
}
|
|
|
|
if !startDate.IsZero() {
|
|
filters.DateRange(startDate.Unix(), 0)
|
|
}
|
|
if !endDate.IsZero() {
|
|
endOfDay := endDate.Add(23*time.Hour + 59*time.Minute + 59*time.Second)
|
|
filters.DateRange(0, endOfDay.Unix())
|
|
}
|
|
|
|
return filters, true
|
|
}
|