added admin page and audit log viewing
This commit is contained in:
274
internal/handlers/admin_audit.go
Normal file
274
internal/handlers/admin_audit.go
Normal 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
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
25
internal/handlers/admin_permissions.go
Normal file
25
internal/handlers/admin_permissions.go
Normal 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)
|
||||
})
|
||||
}
|
||||
25
internal/handlers/admin_roles.go
Normal file
25
internal/handlers/admin_roles.go
Normal 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)
|
||||
})
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user