2025-06-13 20:30:29 +01:00
<!DOCTYPE html>
< html th:lang = "${#locale.language}" th:dir = "#{language.direction}" th:data-language = "${#locale.toString()}" xmlns:th = "https://www.thymeleaf.org" >
< head >
< th:block th:insert = "~{fragments/common :: head(title='Audit Dashboard', header='Audit Dashboard')}" > < / th:block >
<!-- Include Chart.js for visualizations -->
< script th:src = "@{/js/thirdParty/chart.umd.min.js}" > < / script >
2025-06-15 01:15:12 +01:00
<!-- Include custom CSS -->
< link rel = "stylesheet" th:href = "@{/css/audit-dashboard.css}" / >
2025-06-13 20:30:29 +01:00
< / head >
< body >
< div id = "page-container" >
< div id = "content-wrap" >
2025-06-16 00:10:10 +01:00
< th:block th:insert = "~{fragments/navbar.html :: navbar}" > < / th:block >
2025-06-13 20:30:29 +01:00
< div class = "container-fluid mt-4" >
2025-06-16 00:10:10 +01:00
< h1 class = "mb-4" th:text = "#{audit.dashboard.title}" > Audit Dashboard< / h1 >
2025-06-13 20:30:29 +01:00
<!-- System Status Card -->
< div class = "card dashboard-card mb-4" >
< div class = "card-header" >
2025-06-16 00:10:10 +01:00
< h2 class = "h5 mb-0" th:text = "#{audit.dashboard.systemStatus}" > Audit System Status< / h2 >
2025-06-13 20:30:29 +01:00
< / div >
< div class = "card-body" >
< div class = "row" >
< div class = "col-md-3" >
< div class = "stat-card" >
2025-06-16 00:10:10 +01:00
< div class = "stat-label" th:text = "#{audit.dashboard.status}" > Status< / div >
2025-06-13 20:30:29 +01:00
< div class = "stat-number" >
2025-06-16 00:10:10 +01:00
< span th:if = "${auditEnabled}" class = "text-success" th:text = "#{audit.dashboard.enabled}" > Enabled< / span >
< span th:unless = "${auditEnabled}" class = "text-danger" th:text = "#{audit.dashboard.disabled}" > Disabled< / span >
2025-06-13 20:30:29 +01:00
< / div >
< / div >
< / div >
< div class = "col-md-3" >
< div class = "stat-card" >
2025-06-16 00:10:10 +01:00
< div class = "stat-label" th:text = "#{audit.dashboard.currentLevel}" > Current Level< / div >
2025-06-13 20:30:29 +01:00
< div class = "stat-number" >
< span th:class = "'level-indicator level-' + ${auditLevelInt}" th:text = "${auditLevel}" > STANDARD< / span >
< / div >
< / div >
< / div >
< div class = "col-md-3" >
< div class = "stat-card" >
2025-06-16 00:10:10 +01:00
< div class = "stat-label" th:text = "#{audit.dashboard.retentionPeriod}" > Retention Period< / div >
< div class = "stat-number" th:text = "${retentionDays} + ' ' + #{audit.dashboard.days}" > 90 days< / div >
2025-06-13 20:30:29 +01:00
< / div >
< / div >
< div class = "col-md-3" >
< div class = "stat-card" >
2025-06-16 00:10:10 +01:00
< div class = "stat-label" th:text = "#{audit.dashboard.totalEvents}" > Total Events< / div >
2025-06-13 20:30:29 +01:00
< div class = "stat-number" id = "total-events" > -< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
<!-- Tabs for different sections -->
< ul class = "nav nav-tabs" id = "auditTabs" role = "tablist" >
< li class = "nav-item" role = "presentation" >
2025-06-16 00:10:10 +01:00
< button class = "nav-link active" id = "dashboard-tab" data-bs-toggle = "tab" data-bs-target = "#dashboard" type = "button" role = "tab" aria-controls = "dashboard" aria-selected = "true" th:text = "#{audit.dashboard.tab.dashboard}" > Dashboard< / button >
2025-06-13 20:30:29 +01:00
< / li >
< li class = "nav-item" role = "presentation" >
2025-06-16 00:10:10 +01:00
< button class = "nav-link" id = "events-tab" data-bs-toggle = "tab" data-bs-target = "#events" type = "button" role = "tab" aria-controls = "events" aria-selected = "false" th:text = "#{audit.dashboard.tab.events}" > Audit Events< / button >
2025-06-13 20:30:29 +01:00
< / li >
< li class = "nav-item" role = "presentation" >
2025-06-16 00:10:10 +01:00
< button class = "nav-link" id = "export-tab" data-bs-toggle = "tab" data-bs-target = "#export" type = "button" role = "tab" aria-controls = "export" aria-selected = "false" th:text = "#{audit.dashboard.tab.export}" > Export< / button >
2025-06-13 20:30:29 +01:00
< / li >
< li class = "nav-item" role = "presentation" >
< / li >
< / ul >
< div class = "tab-content" id = "auditTabsContent" >
<!-- Dashboard Tab -->
< div class = "tab-pane fade show active" id = "dashboard" role = "tabpanel" aria-labelledby = "dashboard-tab" >
< div class = "row mt-4" >
< div class = "col-md-6" >
< div class = "card dashboard-card" >
< div class = "card-header d-flex justify-content-between align-items-center" >
2025-06-16 00:10:10 +01:00
< h3 class = "h5 mb-0" th:text = "#{audit.dashboard.eventsByType}" > Events by Type< / h3 >
2025-06-13 20:30:29 +01:00
< div class = "btn-group" >
2025-06-16 00:10:10 +01:00
< button class = "btn btn-sm btn-outline-secondary" onclick = "loadStats(7)" th:text = "#{audit.dashboard.period.7days}" > 7 Days< / button >
< button class = "btn btn-sm btn-outline-secondary" onclick = "loadStats(30)" th:text = "#{audit.dashboard.period.30days}" > 30 Days< / button >
< button class = "btn btn-sm btn-outline-secondary" onclick = "loadStats(90)" th:text = "#{audit.dashboard.period.90days}" > 90 Days< / button >
2025-06-13 20:30:29 +01:00
< / div >
< / div >
< div class = "card-body" >
< div class = "chart-container position-relative" >
< div class = "loading-overlay" id = "type-chart-loading" >
< div class = "spinner-border text-primary" role = "status" >
2025-06-16 00:10:10 +01:00
< span class = "visually-hidden" th:text = "#{loading}" > Loading...< / span >
2025-06-13 20:30:29 +01:00
< / div >
< / div >
< canvas id = "typeChart" > < / canvas >
< / div >
< / div >
< / div >
< / div >
< div class = "col-md-6" >
< div class = "card dashboard-card" >
< div class = "card-header" >
2025-06-16 00:10:10 +01:00
< h3 class = "h5 mb-0" th:text = "#{audit.dashboard.eventsByUser}" > Events by User< / h3 >
2025-06-13 20:30:29 +01:00
< / div >
< div class = "card-body" >
< div class = "chart-container position-relative" >
< div class = "loading-overlay" id = "user-chart-loading" >
< div class = "spinner-border text-primary" role = "status" >
2025-06-16 00:10:10 +01:00
< span class = "visually-hidden" th:text = "#{loading}" > Loading...< / span >
2025-06-13 20:30:29 +01:00
< / div >
< / div >
< canvas id = "userChart" > < / canvas >
< / div >
< / div >
< / div >
< / div >
< / div >
< div class = "row mt-4" >
< div class = "col-12" >
< div class = "card dashboard-card" >
< div class = "card-header" >
2025-06-16 00:10:10 +01:00
< h3 class = "h5 mb-0" th:text = "#{audit.dashboard.eventsOverTime}" > Events Over Time< / h3 >
2025-06-13 20:30:29 +01:00
< / div >
< div class = "card-body" >
< div class = "chart-container position-relative" >
< div class = "loading-overlay" id = "time-chart-loading" >
< div class = "spinner-border text-primary" role = "status" >
2025-06-16 00:10:10 +01:00
< span class = "visually-hidden" th:text = "#{loading}" > Loading...< / span >
2025-06-13 20:30:29 +01:00
< / div >
< / div >
< canvas id = "timeChart" > < / canvas >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
<!-- Events Tab -->
< div class = "tab-pane fade" id = "events" role = "tabpanel" aria-labelledby = "events-tab" >
< div class = "card dashboard-card mt-4" >
< div class = "card-header" >
2025-06-16 00:10:10 +01:00
< h3 class = "h5 mb-0" th:text = "#{audit.dashboard.auditEvents}" > Audit Events< / h3 >
2025-06-13 20:30:29 +01:00
< / div >
< div class = "card-body" >
<!-- Filters -->
< div class = "card filter-card" >
< div class = "row" >
< div class = "col-md-3" >
< div class = "mb-3" >
2025-06-16 00:10:10 +01:00
< label for = "typeFilter" class = "form-label" th:text = "#{audit.dashboard.filter.eventType}" > Event Type< / label >
2025-06-15 01:15:12 +01:00
< select class = "form-select" id = "typeFilter" >
2025-06-16 00:10:10 +01:00
< option value = "" th:text = "#{audit.dashboard.filter.allEventTypes}" > All event types< / option >
2025-06-15 01:15:12 +01:00
<!-- Will be populated from API -->
< / select >
2025-06-13 20:30:29 +01:00
< / div >
< / div >
< div class = "col-md-3" >
< div class = "mb-3" >
2025-06-16 00:10:10 +01:00
< label for = "principalFilter" class = "form-label" th:text = "#{audit.dashboard.filter.user}" > User< / label >
< input type = "text" class = "form-control" id = "principalFilter" th:placeholder = "#{audit.dashboard.filter.userPlaceholder}" placeholder = "Filter by user" >
2025-06-13 20:30:29 +01:00
< / div >
< / div >
< div class = "col-md-3" >
< div class = "mb-3" >
2025-06-16 00:10:10 +01:00
< label for = "startDateFilter" class = "form-label" th:text = "#{audit.dashboard.filter.startDate}" > Start Date< / label >
2025-06-13 20:30:29 +01:00
< input type = "date" class = "form-control" id = "startDateFilter" >
< / div >
< / div >
< div class = "col-md-3" >
< div class = "mb-3" >
2025-06-16 00:10:10 +01:00
< label for = "endDateFilter" class = "form-label" th:text = "#{audit.dashboard.filter.endDate}" > End Date< / label >
2025-06-13 20:30:29 +01:00
< input type = "date" class = "form-control" id = "endDateFilter" >
< / div >
< / div >
< / div >
< div class = "row" >
< div class = "col-12" >
2025-06-16 00:10:10 +01:00
< button id = "applyFilters" class = "btn btn-primary" th:text = "#{audit.dashboard.filter.apply}" > Apply Filters< / button >
< button id = "resetFilters" class = "btn btn-secondary" th:text = "#{reset}" > Reset< / button >
2025-06-13 20:30:29 +01:00
< / div >
< / div >
< / div >
<!-- Event Table -->
< div class = "table-responsive position-relative" >
< div class = "loading-overlay" id = "table-loading" >
< div class = "spinner-border text-primary" role = "status" >
2025-06-16 00:10:10 +01:00
< span class = "visually-hidden" th:text = "#{loading}" > Loading...< / span >
2025-06-13 20:30:29 +01:00
< / div >
< / div >
< table class = "table table-striped table-hover audit-table" >
< thead >
< tr >
2025-06-16 00:10:10 +01:00
< th th:text = "#{audit.dashboard.table.id}" > ID< / th >
< th th:text = "#{audit.dashboard.table.time}" > Time< / th >
< th th:text = "#{audit.dashboard.table.user}" > User< / th >
< th th:text = "#{audit.dashboard.table.type}" > Type< / th >
< th th:text = "#{audit.dashboard.table.details}" > Details< / th >
2025-06-13 20:30:29 +01:00
< / tr >
< / thead >
< tbody id = "auditTableBody" >
<!-- Table rows will be populated by JavaScript -->
< / tbody >
< / table >
< / div >
<!-- Pagination -->
< div class = "pagination-container" >
< div >
2025-06-16 00:10:10 +01:00
< span th:text = "#{audit.dashboard.pagination.show}" > Show< / span >
2025-06-13 20:30:29 +01:00
< select id = "pageSizeSelect" class = "form-select form-select-sm d-inline-block w-auto mx-2" >
< option value = "10" > 10< / option >
< option value = "20" selected > 20< / option >
< option value = "50" > 50< / option >
< option value = "100" > 100< / option >
< / select >
2025-06-16 00:10:10 +01:00
< span th:text = "#{audit.dashboard.pagination.entries}" > entries< / span >
< span class = "mx-3" th:text = "#{audit.dashboard.pagination.pageInfo1}" > Page < / span > < span id = "currentPage" > 1< / span > < span th:text = "#{audit.dashboard.pagination.pageInfo2}" > of< / span > < span id = "totalPages" > 1< / span > (< span th:text = "#{audit.dashboard.pagination.totalRecords}" > Total records:< / span > < span id = "totalRecords" > 0< / span > )
2025-06-13 20:30:29 +01:00
< / div >
< nav aria-label = "Audit events pagination" >
2025-06-15 01:15:12 +01:00
< div class = "btn-group" role = "group" aria-label = "Pagination" >
< button type = "button" class = "btn btn-outline-primary" id = "page-first" > « < / button >
< button type = "button" class = "btn btn-outline-primary" id = "page-prev" > ‹ < / button >
< span class = "btn btn-outline-secondary disabled" id = "page-indicator" > Page 1 of 1< / span >
< button type = "button" class = "btn btn-outline-primary" id = "page-next" > › < / button >
< button type = "button" class = "btn btn-outline-primary" id = "page-last" > » < / button >
< / div >
2025-06-13 20:30:29 +01:00
< / nav >
< / div >
< / div >
< / div >
<!-- Event Details Modal -->
< div class = "modal fade" id = "eventDetailsModal" tabindex = "-1" aria-labelledby = "eventDetailsModalLabel" aria-hidden = "true" >
2025-06-16 23:56:17 +01:00
< div class = "modal-dialog modal-lg modal-dialog-centered" >
2025-06-13 20:30:29 +01:00
< div class = "modal-content" >
< div class = "modal-header" >
2025-06-16 00:10:10 +01:00
< h5 class = "modal-title" id = "eventDetailsModalLabel" th:text = "#{audit.dashboard.modal.eventDetails}" > Event Details< / h5 >
< button type = "button" class = "btn-close" data-bs-dismiss = "modal" th:aria-label = "#{close}" aria-label = "Close" > < / button >
2025-06-13 20:30:29 +01:00
< / div >
< div class = "modal-body" >
< div class = "row mb-3" >
< div class = "col-md-4" >
2025-06-16 00:10:10 +01:00
< strong th:text = "#{audit.dashboard.modal.id} + ':'" > ID:< / strong > < span id = "modal-id" > < / span >
2025-06-13 20:30:29 +01:00
< / div >
< div class = "col-md-4" >
2025-06-16 00:10:10 +01:00
< strong th:text = "#{audit.dashboard.modal.user} + ':'" > User:< / strong > < span id = "modal-principal" > < / span >
2025-06-13 20:30:29 +01:00
< / div >
< div class = "col-md-4" >
2025-06-16 00:10:10 +01:00
< strong th:text = "#{audit.dashboard.modal.type} + ':'" > Type:< / strong > < span id = "modal-type" > < / span >
2025-06-13 20:30:29 +01:00
< / div >
< / div >
< div class = "row mb-3" >
< div class = "col-md-12" >
2025-06-16 00:10:10 +01:00
< strong th:text = "#{audit.dashboard.modal.time} + ':'" > Time:< / strong > < span id = "modal-timestamp" > < / span >
2025-06-13 20:30:29 +01:00
< / div >
< / div >
< div class = "row" >
< div class = "col-md-12" >
2025-06-16 00:10:10 +01:00
< strong th:text = "#{audit.dashboard.modal.data} + ':'" > Data:< / strong >
2025-06-13 20:30:29 +01:00
< div class = "json-viewer" id = "modal-data" > < / div >
< / div >
< / div >
< / div >
< div class = "modal-footer" >
2025-06-16 00:10:10 +01:00
< button type = "button" class = "btn btn-secondary" data-bs-dismiss = "modal" th:text = "#{close}" > Close< / button >
2025-06-13 20:30:29 +01:00
< / div >
< / div >
< / div >
< / div >
< / div >
<!-- Export Tab -->
< div class = "tab-pane fade" id = "export" role = "tabpanel" aria-labelledby = "export-tab" >
< div class = "card dashboard-card mt-4" >
< div class = "card-header" >
2025-06-16 00:10:10 +01:00
< h3 class = "h5 mb-0" th:text = "#{audit.dashboard.export.title}" > Export Audit Data< / h3 >
2025-06-13 20:30:29 +01:00
< / div >
< div class = "card-body" >
<!-- Export Filters -->
< div class = "card filter-card" >
< div class = "row" >
< div class = "col-md-3" >
< div class = "mb-3" >
2025-06-16 00:10:10 +01:00
< label for = "exportTypeFilter" class = "form-label" th:text = "#{audit.dashboard.filter.eventType}" > Event Type< / label >
2025-06-15 01:15:12 +01:00
< select class = "form-select" id = "exportTypeFilter" >
2025-06-16 00:10:10 +01:00
< option value = "" th:text = "#{audit.dashboard.filter.allEventTypes}" > All event types< / option >
2025-06-15 01:15:12 +01:00
<!-- Will be populated from API -->
< / select >
2025-06-13 20:30:29 +01:00
< / div >
< / div >
< div class = "col-md-3" >
< div class = "mb-3" >
2025-06-16 00:10:10 +01:00
< label for = "exportPrincipalFilter" class = "form-label" th:text = "#{audit.dashboard.filter.user}" > User< / label >
< input type = "text" class = "form-control" id = "exportPrincipalFilter" th:placeholder = "#{audit.dashboard.filter.userPlaceholder}" placeholder = "Filter by user" >
2025-06-13 20:30:29 +01:00
< / div >
< / div >
< div class = "col-md-3" >
< div class = "mb-3" >
2025-06-16 00:10:10 +01:00
< label for = "exportStartDateFilter" class = "form-label" th:text = "#{audit.dashboard.filter.startDate}" > Start Date< / label >
2025-06-13 20:30:29 +01:00
< input type = "date" class = "form-control" id = "exportStartDateFilter" >
< / div >
< / div >
< div class = "col-md-3" >
< div class = "mb-3" >
2025-06-16 00:10:10 +01:00
< label for = "exportEndDateFilter" class = "form-label" th:text = "#{audit.dashboard.filter.endDate}" > End Date< / label >
2025-06-13 20:30:29 +01:00
< input type = "date" class = "form-control" id = "exportEndDateFilter" >
< / div >
< / div >
< / div >
< div class = "row mt-3" >
< div class = "col-md-6" >
2025-06-16 00:10:10 +01:00
< h5 th:text = "#{audit.dashboard.export.format}" > Export Format< / h5 >
2025-06-15 01:15:12 +01:00
< div >
< label class = "btn btn-outline-primary" style = "margin-right: 10px;" >
< input type = "radio" name = "exportFormat" id = "formatCSV" value = "csv" checked style = "margin-right: 5px;" >
2025-06-16 00:10:10 +01:00
< span th:text = "#{audit.dashboard.export.csv}" > CSV (Comma Separated Values)< / span >
2025-06-13 20:30:29 +01:00
< / label >
2025-06-15 01:15:12 +01:00
< label class = "btn btn-outline-primary" >
< input type = "radio" name = "exportFormat" id = "formatJSON" value = "json" style = "margin-right: 5px;" >
2025-06-16 00:10:10 +01:00
< span th:text = "#{audit.dashboard.export.json}" > JSON (JavaScript Object Notation)< / span >
2025-06-13 20:30:29 +01:00
< / label >
< / div >
< / div >
< div class = "col-md-6" >
< button id = "exportButton" class = "btn btn-primary mt-4" >
2025-06-16 00:10:10 +01:00
< i class = "bi bi-download" > < / i > < span th:text = "#{audit.dashboard.export.button}" > Export Data< / span >
2025-06-13 20:30:29 +01:00
< / button >
2025-06-15 01:15:12 +01:00
< button id = "resetExportFilters" class = "btn btn-secondary mt-4 ms-2" >
2025-06-16 00:10:10 +01:00
< span th:text = "#{audit.dashboard.filter.reset}" > Reset Filters< / span >
2025-06-15 01:15:12 +01:00
< / button >
2025-06-13 20:30:29 +01:00
< / div >
< / div >
< / div >
< div class = "alert alert-info mt-3" >
2025-06-16 00:10:10 +01:00
< h5 th:text = "#{audit.dashboard.export.infoTitle}" > Export Information< / h5 >
< p th:text = "#{audit.dashboard.export.infoDesc1}" > The export will include all audit events matching the selected filters. For large datasets, the export may take a few moments to generate.< / p >
< p th:text = "#{audit.dashboard.export.infoDesc2}" > Exported data will include:< / p >
2025-06-13 20:30:29 +01:00
< ul >
2025-06-16 00:10:10 +01:00
< li th:text = "#{audit.dashboard.export.infoItem1}" > Event ID< / li >
< li th:text = "#{audit.dashboard.export.infoItem2}" > User< / li >
< li th:text = "#{audit.dashboard.export.infoItem3}" > Event Type< / li >
< li th:text = "#{audit.dashboard.export.infoItem4}" > Timestamp< / li >
< li th:text = "#{audit.dashboard.export.infoItem5}" > Event Data< / li >
2025-06-13 20:30:29 +01:00
< / ul >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
<!-- Bootstrap JS is loaded by the common fragments -->
< script th:src = "@{/js/thirdParty/jquery.min.js}" > < / script >
< script th:src = "@{/js/thirdParty/bootstrap.min.js}" > < / script >
2025-06-16 00:10:10 +01:00
<!-- Internationalization data for JavaScript -->
< script th:inline = "javascript" >
window.i18n = {
loading: /*[[#{loading}]]*/ 'Loading...',
noEventsFound: /*[[#{audit.dashboard.js.noEventsFound}]]*/ 'No audit events found matching the current filters',
errorLoading: /*[[#{audit.dashboard.js.errorLoading}]]*/ 'Error loading data:',
errorRendering: /*[[#{audit.dashboard.js.errorRendering}]]*/ 'Error rendering table:',
loadingPage: /*[[#{audit.dashboard.js.loadingPage}]]*/ 'Loading page',
eventsByType: /*[[#{audit.dashboard.eventsByType}]]*/ 'Events by Type',
eventsByUser: /*[[#{audit.dashboard.eventsByUser}]]*/ 'Events by User',
2025-06-16 23:56:17 +01:00
eventsOverTime: /*[[#{audit.dashboard.eventsOverTime}]]*/ 'Events Over Time',
viewDetails: /*[[#{audit.dashboard.table.viewDetails}]]*/ 'View Details'
2025-06-16 00:10:10 +01:00
};
< / script >
2025-06-15 01:15:12 +01:00
<!-- Load custom JavaScript -->
< script th:src = "@{/js/audit/dashboard.js}" > < / script >
2025-06-13 20:30:29 +01:00
< / div >
< / div >
2025-06-16 00:10:10 +01:00
< th:block th:insert = "~{fragments/footer.html :: footer}" > < / th:block >
2025-06-13 20:30:29 +01:00
< / div >
< / body >
< / html >