added admin page and audit log viewing

This commit is contained in:
2026-02-11 19:07:40 +11:00
parent 2a3f4e4861
commit 4c80165f01
22 changed files with 1298 additions and 155 deletions

View File

@@ -0,0 +1,274 @@
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
}

View File

@@ -11,14 +11,15 @@ import (
"github.com/uptrace/bun"
)
// AdminDashboard renders the full admin dashboard page (defaults to users section)
func AdminDashboard(s *hws.Server, conn *bun.DB) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var users *db.List[db.User]
if ok := db.WithReadTx(s, w, r, conn, func(ctx context.Context, tx bun.Tx) (bool, error) {
var err error
users, err = db.GetUsers(ctx, tx, nil)
users, err = db.GetUsersWithRoles(ctx, tx, nil)
if err != nil {
return false, errors.Wrap(err, "db.GetUsers")
return false, errors.Wrap(err, "db.GetUsersWithRoles")
}
return true, nil
}); !ok {

View File

@@ -0,0 +1,25 @@
package handlers
import (
"net/http"
"git.haelnorr.com/h/golib/hws"
adminview "git.haelnorr.com/h/oslstats/internal/view/adminview"
"github.com/uptrace/bun"
)
// AdminPermissionsPage renders the full admin dashboard page with permissions section
func AdminPermissionsPage(s *hws.Server, conn *bun.DB) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// TODO: Load permissions from database
renderSafely(adminview.PermissionsPage(), s, r, w)
})
}
// AdminPermissionsList shows all permissions (HTMX content replacement)
func AdminPermissionsList(s *hws.Server, conn *bun.DB) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// TODO: Load permissions from database
renderSafely(adminview.PermissionsList(), s, r, w)
})
}

View File

@@ -0,0 +1,25 @@
package handlers
import (
"net/http"
"git.haelnorr.com/h/golib/hws"
adminview "git.haelnorr.com/h/oslstats/internal/view/adminview"
"github.com/uptrace/bun"
)
// AdminRolesPage renders the full admin dashboard page with roles section
func AdminRolesPage(s *hws.Server, conn *bun.DB) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// TODO: Load roles from database
renderSafely(adminview.RolesPage(), s, r, w)
})
}
// AdminRolesList shows all roles (HTMX content replacement)
func AdminRolesList(s *hws.Server, conn *bun.DB) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// TODO: Load roles from database
renderSafely(adminview.RolesList(), s, r, w)
})
}

View File

@@ -11,19 +11,34 @@ import (
"github.com/uptrace/bun"
)
// AdminUsersList shows all users
// AdminUsersPage renders the full admin dashboard page with users section
func AdminUsersPage(s *hws.Server, conn *bun.DB) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var users *db.List[db.User]
if ok := db.WithReadTx(s, w, r, conn, func(ctx context.Context, tx bun.Tx) (bool, error) {
var err error
users, err = db.GetUsersWithRoles(ctx, tx, nil)
if err != nil {
return false, errors.Wrap(err, "db.GetUsersWithRoles")
}
return true, nil
}); !ok {
return
}
renderSafely(adminview.DashboardPage(users), s, r, w)
})
}
// AdminUsersList shows all users (HTMX content replacement)
func AdminUsersList(s *hws.Server, conn *bun.DB) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var users *db.List[db.User]
pageOpts := pageOptsFromForm(s, w, r)
if pageOpts == nil {
return
}
if ok := db.WithReadTx(s, w, r, conn, func(ctx context.Context, tx bun.Tx) (bool, error) {
var err error
users, err = db.GetUsers(ctx, tx, pageOpts)
// Get users with their roles
users, err = db.GetUsersWithRoles(ctx, tx, nil)
if err != nil {
return false, errors.Wrap(err, "db.GetUsers")
return false, errors.Wrap(err, "db.GetUsersWithRoles")
}
return true, nil
}); !ok {