added admin page and audit log viewing
This commit is contained in:
156
internal/view/adminview/audit_log_detail.templ
Normal file
156
internal/view/adminview/audit_log_detail.templ
Normal file
@@ -0,0 +1,156 @@
|
||||
package adminview
|
||||
|
||||
import "git.haelnorr.com/h/oslstats/internal/db"
|
||||
import "fmt"
|
||||
import "time"
|
||||
import "encoding/json"
|
||||
|
||||
templ AuditLogDetail(log *db.AuditLog) {
|
||||
<!-- Modal overlay -->
|
||||
<div
|
||||
class="fixed inset-0 bg-crust/80 flex items-center justify-center z-50 p-4"
|
||||
x-data="{ show: true }"
|
||||
x-show="show"
|
||||
x-transition:enter="transition ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="transition ease-in duration-150"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
@click.self="show = false; setTimeout(() => document.getElementById('modal-container').innerHTML = '', 200)"
|
||||
>
|
||||
<!-- Modal content -->
|
||||
<div
|
||||
class="bg-base border border-surface1 rounded-lg max-w-2xl w-full max-h-[90vh] overflow-y-auto"
|
||||
x-transition:enter="transition ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0 scale-95"
|
||||
x-transition:enter-end="opacity-100 scale-100"
|
||||
x-transition:leave="transition ease-in duration-150"
|
||||
x-transition:leave-start="opacity-100 scale-100"
|
||||
x-transition:leave-end="opacity-0 scale-95"
|
||||
>
|
||||
<!-- Header -->
|
||||
<div class="flex justify-between items-center p-6 border-b border-surface1">
|
||||
<h2 class="text-xl font-bold text-text">Audit Log Details</h2>
|
||||
<button
|
||||
@click="show = false; setTimeout(() => document.getElementById('modal-container').innerHTML = '', 200)"
|
||||
class="text-subtext0 hover:text-text transition"
|
||||
>
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<!-- Body -->
|
||||
<div class="p-6 space-y-4">
|
||||
<!-- ID -->
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-subtext0 mb-1">ID</label>
|
||||
<p class="text-text">{ fmt.Sprintf("%d", log.ID) }</p>
|
||||
</div>
|
||||
<!-- User -->
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-subtext0 mb-1">User</label>
|
||||
<p class="text-text">
|
||||
if log.User != nil {
|
||||
{ log.User.Username } <span class="text-subtext1 text-sm">(ID: { fmt.Sprintf("%d", log.UserID) })</span>
|
||||
} else {
|
||||
<span class="text-subtext1 italic">Unknown User (ID: { fmt.Sprintf("%d", log.UserID) })</span>
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
<!-- Timestamp -->
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-subtext0 mb-1">Timestamp</label>
|
||||
<p class="text-text">{ formatDetailTimestamp(log.CreatedAt) }</p>
|
||||
</div>
|
||||
<!-- Action -->
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-subtext0 mb-1">Action</label>
|
||||
<p class="text-text font-mono">{ log.Action }</p>
|
||||
</div>
|
||||
<!-- Resource Type -->
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-subtext0 mb-1">Resource Type</label>
|
||||
<p class="text-text">{ log.ResourceType }</p>
|
||||
</div>
|
||||
<!-- Resource ID -->
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-subtext0 mb-1">Resource ID</label>
|
||||
<p class="text-text font-mono">
|
||||
if log.ResourceID != nil {
|
||||
{ *log.ResourceID }
|
||||
} else {
|
||||
<span class="text-subtext1 italic">N/A</span>
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
<!-- Result -->
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-subtext0 mb-1">Result</label>
|
||||
<div>
|
||||
@resultBadge(log.Result)
|
||||
</div>
|
||||
</div>
|
||||
<!-- Error Message (if applicable) -->
|
||||
if log.ErrorMessage != nil && *log.ErrorMessage != "" {
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-subtext0 mb-1">Error Message</label>
|
||||
<div class="bg-red/10 border border-red/30 rounded p-3">
|
||||
<p class="text-red font-mono text-sm">{ *log.ErrorMessage }</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<!-- IP Address -->
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-subtext0 mb-1">IP Address</label>
|
||||
<p class="text-text font-mono">{ log.IPAddress }</p>
|
||||
</div>
|
||||
<!-- User Agent -->
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-subtext0 mb-1">User Agent</label>
|
||||
<p class="text-text text-sm break-all">{ log.UserAgent }</p>
|
||||
</div>
|
||||
<!-- Details JSON -->
|
||||
if log.Details != nil && len(log.Details) > 0 && string(log.Details) != "null" {
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-subtext0 mb-1">Details</label>
|
||||
<div class="bg-mantle border border-surface1 rounded p-3 overflow-x-auto">
|
||||
<pre class="text-text text-xs font-mono whitespace-pre-wrap">{ formatJSON(log.Details) }</pre>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<!-- Footer -->
|
||||
<div class="flex justify-end gap-2 p-6 border-t border-surface1">
|
||||
<button
|
||||
@click="show = false; setTimeout(() => document.getElementById('modal-container').innerHTML = '', 200)"
|
||||
class="px-4 py-2 bg-surface1 hover:bg-surface2 text-text rounded font-medium transition hover:cursor-pointer"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
func formatDetailTimestamp(unixTime int64) string {
|
||||
t := time.Unix(unixTime, 0)
|
||||
return t.Format("Monday, January 2, 2006 at 3:04:05 PM MST")
|
||||
}
|
||||
|
||||
func formatJSON(raw []byte) string {
|
||||
if len(raw) == 0 || string(raw) == "null" {
|
||||
return "No details available"
|
||||
}
|
||||
// Pretty print the JSON
|
||||
var obj interface{}
|
||||
if err := json.Unmarshal(raw, &obj); err != nil {
|
||||
return string(raw)
|
||||
}
|
||||
pretty, err := json.MarshalIndent(obj, "", " ")
|
||||
if err != nil {
|
||||
return string(raw)
|
||||
}
|
||||
return string(pretty)
|
||||
}
|
||||
Reference in New Issue
Block a user