added pagination to audit logs
This commit is contained in:
@@ -50,31 +50,31 @@ func NewAuditLogFilter() *AuditLogFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *AuditLogFilter) UserID(id int) *AuditLogFilter {
|
func (a *AuditLogFilter) UserID(id int) *AuditLogFilter {
|
||||||
a.Add("al.user_id", "=", id)
|
a.Equals("al.user_id", id)
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AuditLogFilter) Action(action string) *AuditLogFilter {
|
func (a *AuditLogFilter) Action(action string) *AuditLogFilter {
|
||||||
a.Add("al.action", "=", action)
|
a.Equals("al.action", action)
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AuditLogFilter) ResourceType(resourceType string) *AuditLogFilter {
|
func (a *AuditLogFilter) ResourceType(resourceType string) *AuditLogFilter {
|
||||||
a.Add("al.resource_type", "=", resourceType)
|
a.Equals("al.resource_type", resourceType)
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AuditLogFilter) Result(result string) *AuditLogFilter {
|
func (a *AuditLogFilter) Result(result string) *AuditLogFilter {
|
||||||
a.Add("al.result", "=", result)
|
a.Equals("al.result", result)
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AuditLogFilter) DateRange(start, end int64) *AuditLogFilter {
|
func (a *AuditLogFilter) DateRange(start, end int64) *AuditLogFilter {
|
||||||
if start > 0 {
|
if start > 0 {
|
||||||
a.Add("al.created_at", ">=", start)
|
a.GreaterEqualThan("al.created_at", start)
|
||||||
}
|
}
|
||||||
if end > 0 {
|
if end > 0 {
|
||||||
a.Add("al.created_at", "<=", end)
|
a.LessEqualThan("al.created_at", end)
|
||||||
}
|
}
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
@@ -83,7 +83,7 @@ func (a *AuditLogFilter) DateRange(start, end int64) *AuditLogFilter {
|
|||||||
func GetAuditLogs(ctx context.Context, tx bun.Tx, pageOpts *PageOpts, filters *AuditLogFilter) (*List[AuditLog], error) {
|
func GetAuditLogs(ctx context.Context, tx bun.Tx, pageOpts *PageOpts, filters *AuditLogFilter) (*List[AuditLog], error) {
|
||||||
defaultPageOpts := &PageOpts{
|
defaultPageOpts := &PageOpts{
|
||||||
Page: 1,
|
Page: 1,
|
||||||
PerPage: 50,
|
PerPage: 15,
|
||||||
Order: bun.OrderDesc,
|
Order: bun.OrderDesc,
|
||||||
OrderBy: "created_at",
|
OrderBy: "created_at",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,11 +20,21 @@ type List[T any] struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Filter struct {
|
type Filter struct {
|
||||||
Field string
|
Field string
|
||||||
Value any
|
Value any
|
||||||
Operator string
|
Comparator Comparator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Comparator string
|
||||||
|
|
||||||
|
const (
|
||||||
|
Equal Comparator = "="
|
||||||
|
Less Comparator = "<"
|
||||||
|
LessEqual Comparator = "<="
|
||||||
|
Greater Comparator = ">"
|
||||||
|
GreaterEqual Comparator = ">="
|
||||||
|
)
|
||||||
|
|
||||||
type ListFilter struct {
|
type ListFilter struct {
|
||||||
filters []Filter
|
filters []Filter
|
||||||
}
|
}
|
||||||
@@ -33,8 +43,24 @@ func NewListFilter() *ListFilter {
|
|||||||
return &ListFilter{[]Filter{}}
|
return &ListFilter{[]Filter{}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *ListFilter) Add(field, operator string, value any) {
|
func (f *ListFilter) Equals(field string, value any) {
|
||||||
f.filters = append(f.filters, Filter{field, value, "="})
|
f.filters = append(f.filters, Filter{field, value, Equal})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ListFilter) LessThan(field string, value any) {
|
||||||
|
f.filters = append(f.filters, Filter{field, value, Less})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ListFilter) LessEqualThan(field string, value any) {
|
||||||
|
f.filters = append(f.filters, Filter{field, value, LessEqual})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ListFilter) GreaterThan(field string, value any) {
|
||||||
|
f.filters = append(f.filters, Filter{field, value, Greater})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ListFilter) GreaterEqualThan(field string, value any) {
|
||||||
|
f.filters = append(f.filters, Filter{field, value, GreaterEqual})
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetList[T any](tx bun.Tx) *listgetter[T] {
|
func GetList[T any](tx bun.Tx) *listgetter[T] {
|
||||||
@@ -63,7 +89,7 @@ func (l *listgetter[T]) Relation(name string, apply ...func(*bun.SelectQuery) *b
|
|||||||
|
|
||||||
func (l *listgetter[T]) Filter(filters ...Filter) *listgetter[T] {
|
func (l *listgetter[T]) Filter(filters ...Filter) *listgetter[T] {
|
||||||
for _, filter := range filters {
|
for _, filter := range filters {
|
||||||
l.q = l.q.Where("? ? ?", bun.Ident(filter.Field), bun.Safe(filter.Operator), filter.Value)
|
l.q = l.q.Where("? ? ?", bun.Ident(filter.Field), bun.Safe(filter.Comparator), filter.Value)
|
||||||
}
|
}
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,6 @@
|
|||||||
--radius-xl: 0.75rem;
|
--radius-xl: 0.75rem;
|
||||||
--ease-in: cubic-bezier(0.4, 0, 1, 1);
|
--ease-in: cubic-bezier(0.4, 0, 1, 1);
|
||||||
--ease-out: cubic-bezier(0, 0, 0.2, 1);
|
--ease-out: cubic-bezier(0, 0, 0.2, 1);
|
||||||
--blur-sm: 8px;
|
|
||||||
--default-transition-duration: 150ms;
|
--default-transition-duration: 150ms;
|
||||||
--default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
--default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
--default-font-family: var(--font-sans);
|
--default-font-family: var(--font-sans);
|
||||||
@@ -1031,11 +1030,6 @@
|
|||||||
.filter {
|
.filter {
|
||||||
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|
||||||
}
|
}
|
||||||
.backdrop-blur-sm {
|
|
||||||
--tw-backdrop-blur: blur(var(--blur-sm));
|
|
||||||
-webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
|
|
||||||
backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
|
|
||||||
}
|
|
||||||
.transition {
|
.transition {
|
||||||
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, content-visibility, overlay, pointer-events;
|
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, content-visibility, overlay, pointer-events;
|
||||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||||
@@ -1170,16 +1164,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.hover\:bg-red\/80 {
|
|
||||||
&:hover {
|
|
||||||
@media (hover: hover) {
|
|
||||||
background-color: var(--red);
|
|
||||||
@supports (color: color-mix(in lab, red, red)) {
|
|
||||||
background-color: color-mix(in oklab, var(--red) 80%, transparent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.hover\:bg-sapphire\/75 {
|
.hover\:bg-sapphire\/75 {
|
||||||
&:hover {
|
&:hover {
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
@@ -1283,11 +1267,6 @@
|
|||||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.focus\:ring-blue {
|
|
||||||
&:focus {
|
|
||||||
--tw-ring-color: var(--blue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.focus\:ring-mauve {
|
.focus\:ring-mauve {
|
||||||
&:focus {
|
&:focus {
|
||||||
--tw-ring-color: var(--mauve);
|
--tw-ring-color: var(--mauve);
|
||||||
@@ -1897,42 +1876,6 @@
|
|||||||
syntax: "*";
|
syntax: "*";
|
||||||
inherits: false;
|
inherits: false;
|
||||||
}
|
}
|
||||||
@property --tw-backdrop-blur {
|
|
||||||
syntax: "*";
|
|
||||||
inherits: false;
|
|
||||||
}
|
|
||||||
@property --tw-backdrop-brightness {
|
|
||||||
syntax: "*";
|
|
||||||
inherits: false;
|
|
||||||
}
|
|
||||||
@property --tw-backdrop-contrast {
|
|
||||||
syntax: "*";
|
|
||||||
inherits: false;
|
|
||||||
}
|
|
||||||
@property --tw-backdrop-grayscale {
|
|
||||||
syntax: "*";
|
|
||||||
inherits: false;
|
|
||||||
}
|
|
||||||
@property --tw-backdrop-hue-rotate {
|
|
||||||
syntax: "*";
|
|
||||||
inherits: false;
|
|
||||||
}
|
|
||||||
@property --tw-backdrop-invert {
|
|
||||||
syntax: "*";
|
|
||||||
inherits: false;
|
|
||||||
}
|
|
||||||
@property --tw-backdrop-opacity {
|
|
||||||
syntax: "*";
|
|
||||||
inherits: false;
|
|
||||||
}
|
|
||||||
@property --tw-backdrop-saturate {
|
|
||||||
syntax: "*";
|
|
||||||
inherits: false;
|
|
||||||
}
|
|
||||||
@property --tw-backdrop-sepia {
|
|
||||||
syntax: "*";
|
|
||||||
inherits: false;
|
|
||||||
}
|
|
||||||
@property --tw-duration {
|
@property --tw-duration {
|
||||||
syntax: "*";
|
syntax: "*";
|
||||||
inherits: false;
|
inherits: false;
|
||||||
@@ -1988,15 +1931,6 @@
|
|||||||
--tw-drop-shadow-color: initial;
|
--tw-drop-shadow-color: initial;
|
||||||
--tw-drop-shadow-alpha: 100%;
|
--tw-drop-shadow-alpha: 100%;
|
||||||
--tw-drop-shadow-size: initial;
|
--tw-drop-shadow-size: initial;
|
||||||
--tw-backdrop-blur: initial;
|
|
||||||
--tw-backdrop-brightness: initial;
|
|
||||||
--tw-backdrop-contrast: initial;
|
|
||||||
--tw-backdrop-grayscale: initial;
|
|
||||||
--tw-backdrop-hue-rotate: initial;
|
|
||||||
--tw-backdrop-invert: initial;
|
|
||||||
--tw-backdrop-opacity: initial;
|
|
||||||
--tw-backdrop-saturate: initial;
|
|
||||||
--tw-backdrop-sepia: initial;
|
|
||||||
--tw-duration: initial;
|
--tw-duration: initial;
|
||||||
--tw-ease: initial;
|
--tw-ease: initial;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ func formatJSON(raw []byte) string {
|
|||||||
return "No details available"
|
return "No details available"
|
||||||
}
|
}
|
||||||
// Pretty print the JSON
|
// Pretty print the JSON
|
||||||
var obj interface{}
|
var obj any
|
||||||
if err := json.Unmarshal(raw, &obj); err != nil {
|
if err := json.Unmarshal(raw, &obj); err != nil {
|
||||||
return string(raw)
|
return string(raw)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,11 +184,12 @@ templ AuditLogsResults(logs *db.List[db.AuditLog]) {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
if totalPages > 1 {
|
if totalPages > 1 {
|
||||||
<div class="flex justify-center gap-2">
|
<div class="flex justify-center gap-2 mt-4">
|
||||||
if logs.PageOpts.Page > 1 {
|
if logs.PageOpts.Page > 1 {
|
||||||
<button
|
<button
|
||||||
hx-post={ fmt.Sprintf("/admin/audit?page=%d", logs.PageOpts.Page-1) }
|
hx-post={ fmt.Sprintf("/admin/audit/filter?page=%d", logs.PageOpts.Page-1) }
|
||||||
hx-target="#admin-content"
|
hx-target="#audit-results"
|
||||||
|
hx-include="#audit-filters-form"
|
||||||
class="px-4 py-2 bg-surface1 hover:bg-surface2 text-text rounded font-medium transition hover:cursor-pointer"
|
class="px-4 py-2 bg-surface1 hover:bg-surface2 text-text rounded font-medium transition hover:cursor-pointer"
|
||||||
>
|
>
|
||||||
Previous
|
Previous
|
||||||
@@ -199,8 +200,9 @@ templ AuditLogsResults(logs *db.List[db.AuditLog]) {
|
|||||||
</span>
|
</span>
|
||||||
if logs.PageOpts.Page < totalPages {
|
if logs.PageOpts.Page < totalPages {
|
||||||
<button
|
<button
|
||||||
hx-post={ fmt.Sprintf("/admin/audit?page=%d", logs.PageOpts.Page+1) }
|
hx-post={ fmt.Sprintf("/admin/audit/filter?page=%d", logs.PageOpts.Page+1) }
|
||||||
hx-target="#admin-content"
|
hx-target="#audit-results"
|
||||||
|
hx-include="#audit-filters-form"
|
||||||
class="px-4 py-2 bg-surface1 hover:bg-surface2 text-text rounded font-medium transition hover:cursor-pointer"
|
class="px-4 py-2 bg-surface1 hover:bg-surface2 text-text rounded font-medium transition hover:cursor-pointer"
|
||||||
>
|
>
|
||||||
Next
|
Next
|
||||||
|
|||||||
Reference in New Issue
Block a user