From 43c5a1970f63b245bf7125ab247d14c85e6ac534 Mon Sep 17 00:00:00 2001
From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com.>
Date: Mon, 16 Jun 2025 00:10:10 +0100
Subject: [PATCH] i18n
---
.../resources/static/css/audit-dashboard.css | 114 +-
.../resources/static/js/audit/dashboard.js | 297 ++++-
.../main/resources/templates/AUDIT_HELP.md | 42 +
.../resources/templates/audit/dashboard.html | 1063 ++---------------
.../main/resources/messages_en_GB.properties | 77 ++
5 files changed, 583 insertions(+), 1010 deletions(-)
create mode 100644 proprietary/src/main/resources/templates/AUDIT_HELP.md
diff --git a/proprietary/src/main/resources/static/css/audit-dashboard.css b/proprietary/src/main/resources/static/css/audit-dashboard.css
index 15bac161c..87bc3a27c 100644
--- a/proprietary/src/main/resources/static/css/audit-dashboard.css
+++ b/proprietary/src/main/resources/static/css/audit-dashboard.css
@@ -2,6 +2,19 @@
margin-bottom: 20px;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+ background-color: var(--md-sys-color-surface-container);
+ color: var(--md-sys-color-on-surface);
+ border: 1px solid var(--md-sys-color-outline-variant);
+}
+
+.card-header {
+ background-color: var(--md-sys-color-surface-container-high);
+ color: var(--md-sys-color-on-surface);
+ border-bottom: 1px solid var(--md-sys-color-outline-variant);
+}
+
+.card-body {
+ background-color: var(--md-sys-color-surface-container);
}
.stat-card {
text-align: center;
@@ -13,7 +26,7 @@
}
.stat-label {
font-size: 1rem;
- color: #666;
+ color: var(--md-sys-color-on-surface-variant);
}
.chart-container {
position: relative;
@@ -23,6 +36,9 @@
.filter-card {
margin-bottom: 20px;
padding: 15px;
+ background-color: var(--md-sys-color-surface-container-low);
+ border: 1px solid var(--md-sys-color-outline-variant);
+ border-radius: 4px;
}
.loading-overlay {
position: absolute;
@@ -30,7 +46,7 @@
left: 0;
width: 100%;
height: 100%;
- background-color: rgba(255, 255, 255, 0.7);
+ background-color: var(--md-sys-color-surface-container-high, rgba(229, 232, 241, 0.8));
display: flex;
justify-content: center;
align-items: center;
@@ -44,26 +60,42 @@
font-weight: bold;
}
.level-0 {
- background-color: #dc3545; /* Red */
+ background-color: var(--md-sys-color-error, #dc3545); /* Red */
}
.level-1 {
- background-color: #fd7e14; /* Orange */
+ background-color: var(--md-sys-color-secondary, #fd7e14); /* Orange */
}
.level-2 {
- background-color: #28a745; /* Green */
+ background-color: var(--md-nav-section-color-other, #28a745); /* Green */
}
.level-3 {
- background-color: #17a2b8; /* Teal */
+ background-color: var(--md-sys-color-tertiary, #17a2b8); /* Teal */
}
/* Custom data table styling */
.audit-table {
font-size: 0.9rem;
+ color: var(--md-sys-color-on-surface);
+ border-color: var(--md-sys-color-outline-variant);
+}
+
+.audit-table tbody tr {
+ background-color: var(--md-sys-color-surface-container-low);
+}
+
+.audit-table tbody tr:nth-child(even) {
+ background-color: var(--md-sys-color-surface-container);
+}
+
+.audit-table tbody tr:hover {
+ background-color: var(--md-sys-color-surface-container-high);
}
.audit-table th {
- background-color: #f8f9fa;
+ background-color: var(--md-sys-color-surface-container-high);
+ color: var(--md-sys-color-on-surface);
position: sticky;
top: 0;
z-index: 10;
+ font-weight: bold;
}
.table-responsive {
max-height: 600px;
@@ -74,7 +106,8 @@
align-items: center;
margin-top: 15px;
padding: 10px 0;
- border-top: 1px solid #dee2e6;
+ border-top: 1px solid var(--md-sys-color-outline-variant);
+ color: var(--md-sys-color-on-surface);
}
.pagination .page-item.active .page-link {
@@ -93,13 +126,15 @@
background-color: var(--bs-light);
}
.json-viewer {
- background-color: #f8f9fa;
+ background-color: var(--md-sys-color-surface-container-low);
+ color: var(--md-sys-color-on-surface);
border-radius: 4px;
padding: 10px;
max-height: 300px;
overflow-y: auto;
font-family: monospace;
white-space: pre-wrap;
+ border: 1px solid var(--md-sys-color-outline-variant);
}
/* Simple, minimal radio styling - no extras */
@@ -113,14 +148,14 @@
right: 0;
width: 400px;
height: 200px;
- background: rgba(0,0,0,0.8);
- color: #0f0;
+ background: var(--md-sys-color-surface-container-highest, rgba(0,0,0,0.8));
+ color: var(--md-sys-color-tertiary, #0f0);
font-family: monospace;
font-size: 12px;
z-index: 9999;
overflow-y: auto;
padding: 10px;
- border: 1px solid #0f0;
+ border: 1px solid var(--md-sys-color-outline);
display: none; /* Changed to none by default, enable with key command */
}
@@ -128,13 +163,64 @@
label.btn-outline-primary {
cursor: pointer;
transition: all 0.2s;
+ border-color: var(--md-sys-color-primary);
+ color: var(--md-sys-color-primary);
}
label.btn-outline-primary.active {
- background-color: var(--bs-primary);
- color: white;
+ background-color: var(--md-sys-color-primary);
+ color: var(--md-sys-color-on-primary);
+ border-color: var(--md-sys-color-primary);
}
label.btn-outline-primary input[type="radio"] {
cursor: pointer;
+}
+
+/* Modal overrides for dark mode */
+.modal-content {
+ background-color: var(--md-sys-color-surface-container);
+ color: var(--md-sys-color-on-surface);
+ border-color: var(--md-sys-color-outline);
+}
+
+.modal-header {
+ border-bottom-color: var(--md-sys-color-outline-variant);
+}
+
+.modal-footer {
+ border-top-color: var(--md-sys-color-outline-variant);
+}
+
+/* Button overrides for theme consistency */
+.btn-outline-primary {
+ color: var(--md-sys-color-primary);
+ border-color: var(--md-sys-color-primary);
+}
+
+.btn-outline-primary:hover {
+ background-color: var(--md-sys-color-primary);
+ color: var(--md-sys-color-on-primary);
+}
+
+.btn-outline-secondary {
+ color: var(--md-sys-color-secondary);
+ border-color: var(--md-sys-color-secondary);
+}
+
+.btn-outline-secondary:hover {
+ background-color: var(--md-sys-color-secondary);
+ color: var(--md-sys-color-on-secondary);
+}
+
+.btn-primary {
+ background-color: var(--md-sys-color-primary);
+ color: var(--md-sys-color-on-primary);
+ border-color: var(--md-sys-color-primary);
+}
+
+.btn-secondary {
+ background-color: var(--md-sys-color-secondary);
+ color: var(--md-sys-color-on-secondary);
+ border-color: var(--md-sys-color-secondary);
}
\ No newline at end of file
diff --git a/proprietary/src/main/resources/static/js/audit/dashboard.js b/proprietary/src/main/resources/static/js/audit/dashboard.js
index f5cf6324c..a3f10f3c0 100644
--- a/proprietary/src/main/resources/static/js/audit/dashboard.js
+++ b/proprietary/src/main/resources/static/js/audit/dashboard.js
@@ -80,6 +80,31 @@ document.addEventListener('keydown', function(e) {
});
// Initialize page
+// Theme change listener to redraw charts when theme changes
+function setupThemeChangeListener() {
+ // Watch for theme changes (usually by a class on body or html element)
+ const observer = new MutationObserver(function(mutations) {
+ mutations.forEach(function(mutation) {
+ if (mutation.attributeName === 'data-bs-theme' || mutation.attributeName === 'class') {
+ // Redraw charts with new theme colors if they exist
+ if (typeChart && userChart && timeChart) {
+ debugLog('Theme changed, redrawing charts');
+ // If we have stats data cached, use it
+ if (window.cachedStatsData) {
+ renderCharts(window.cachedStatsData);
+ }
+ }
+ }
+ });
+ });
+
+ // Observe the document element for theme changes
+ observer.observe(document.documentElement, { attributes: true });
+
+ // Also observe body for class changes
+ observer.observe(document.body, { attributes: true });
+}
+
document.addEventListener('DOMContentLoaded', function() {
debugLog('Page initialized');
@@ -106,7 +131,7 @@ document.addEventListener('DOMContentLoaded', function() {
// Show a loading message immediately
if (auditTableBody) {
auditTableBody.innerHTML =
- '
Loading audit data... |
';
+ ' ' + window.i18n.loading + ' |
';
} else {
debugLog('ERROR: auditTableBody element not found!');
}
@@ -117,6 +142,9 @@ document.addEventListener('DOMContentLoaded', function() {
// Load statistics for dashboard
loadStats(7);
+ // Setup theme change listener
+ setupThemeChangeListener();
+
// Set up event listeners
pageSizeSelect.addEventListener('change', function() {
pageSize = parseInt(this.value);
@@ -350,7 +378,7 @@ function loadAuditData(targetPage, realPageSize) {
.catch(error => {
debugLog('Error loading data', error.message);
if (auditTableBody) {
- auditTableBody.innerHTML = `Error loading data: ${error.message} |
`;
+ auditTableBody.innerHTML = `${window.i18n.errorLoading} ${error.message} |
`;
}
hideLoading('table-loading');
@@ -375,6 +403,8 @@ function loadStats(days) {
.then(response => response.json())
.then(data => {
document.getElementById('total-events').textContent = data.totalEvents;
+ // Cache stats data for theme changes
+ window.cachedStatsData = data;
renderCharts(data);
hideLoading('type-chart-loading');
hideLoading('user-chart-loading');
@@ -412,7 +442,7 @@ function renderTable(events) {
if (!events || events.length === 0) {
debugLog('No events to render');
- auditTableBody.innerHTML = 'No audit events found matching the current filters |
';
+ auditTableBody.innerHTML = '' + window.i18n.noEventsFound + ' |
';
return;
}
@@ -452,7 +482,7 @@ function renderTable(events) {
debugLog('Table rendering complete');
} catch (e) {
debugLog('Error in renderTable', e.message);
- auditTableBody.innerHTML = 'Error rendering table: ' + e.message + ' |
';
+ auditTableBody.innerHTML = '' + window.i18n.errorRendering + ' ' + e.message + ' |
';
}
}
@@ -521,6 +551,9 @@ function goToPage(page) {
// Render charts
function renderCharts(data) {
+ // Get theme colors
+ const colors = getThemeColors();
+
// Prepare data for charts
const typeLabels = Object.keys(data.eventsByType);
const typeValues = Object.values(data.eventsByType);
@@ -532,6 +565,10 @@ function renderCharts(data) {
const timeLabels = Object.keys(data.eventsByDay).sort();
const timeValues = timeLabels.map(day => data.eventsByDay[day] || 0);
+ // Chart.js global defaults for dark mode compatibility
+ Chart.defaults.color = colors.text;
+ Chart.defaults.borderColor = colors.grid;
+
// Type chart
if (typeChart) {
typeChart.destroy();
@@ -543,19 +580,84 @@ function renderCharts(data) {
data: {
labels: typeLabels,
datasets: [{
- label: 'Events by Type',
+ label: window.i18n.eventsByType,
data: typeValues,
- backgroundColor: getChartColors(typeLabels.length),
- borderColor: getChartColors(typeLabels.length, 1), // Full opacity for borders
+ backgroundColor: colors.chartColors.slice(0, typeLabels.length),
+ borderColor: colors.chartColors.slice(0, typeLabels.length),
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
+ plugins: {
+ legend: {
+ labels: {
+ color: colors.text,
+ font: {
+ weight: colors.isDarkMode ? 'bold' : 'normal',
+ size: 14
+ }
+ }
+ },
+ tooltip: {
+ titleFont: {
+ weight: 'bold',
+ size: 14
+ },
+ bodyFont: {
+ size: 13
+ },
+ backgroundColor: colors.isDarkMode ? 'rgba(0, 0, 0, 0.8)' : 'rgba(255, 255, 255, 0.8)',
+ titleColor: colors.isDarkMode ? '#ffffff' : '#000000',
+ bodyColor: colors.isDarkMode ? '#ffffff' : '#000000',
+ borderColor: colors.grid,
+ borderWidth: 1
+ }
+ },
scales: {
y: {
- beginAtZero: true
+ beginAtZero: true,
+ ticks: {
+ color: colors.text,
+ font: {
+ weight: colors.isDarkMode ? 'bold' : 'normal',
+ size: 12
+ }
+ },
+ grid: {
+ color: colors.grid
+ },
+ title: {
+ display: true,
+ text: 'Count',
+ color: colors.text,
+ font: {
+ weight: colors.isDarkMode ? 'bold' : 'normal',
+ size: 14
+ }
+ }
+ },
+ x: {
+ ticks: {
+ color: colors.text,
+ font: {
+ weight: colors.isDarkMode ? 'bold' : 'normal',
+ size: 12
+ }
+ },
+ grid: {
+ color: colors.grid
+ },
+ title: {
+ display: true,
+ text: 'Event Type',
+ color: colors.text,
+ font: {
+ weight: colors.isDarkMode ? 'bold' : 'normal',
+ size: 14
+ }
+ }
}
}
}
@@ -572,15 +674,58 @@ function renderCharts(data) {
data: {
labels: userLabels,
datasets: [{
- label: 'Events by User',
+ label: window.i18n.eventsByUser,
data: userValues,
- backgroundColor: getChartColors(userLabels.length),
- borderWidth: 1
+ backgroundColor: colors.chartColors.slice(0, userLabels.length),
+ borderWidth: 1,
+ borderColor: colors.isDarkMode ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.2)'
}]
},
options: {
responsive: true,
- maintainAspectRatio: false
+ maintainAspectRatio: false,
+ plugins: {
+ legend: {
+ position: 'right',
+ labels: {
+ color: colors.text,
+ font: {
+ size: colors.isDarkMode ? 14 : 12,
+ weight: colors.isDarkMode ? 'bold' : 'normal'
+ },
+ padding: 15,
+ // Add a box around each label for better contrast in dark mode
+ generateLabels: function(chart) {
+ const original = Chart.overrides.pie.plugins.legend.labels.generateLabels;
+ const labels = original.call(this, chart);
+
+ if (colors.isDarkMode) {
+ labels.forEach(label => {
+ label.fillStyle = 'rgba(0, 0, 0, 0.7)'; // Dark background for text
+ label.strokeStyle = label.strokeStyle; // Keep original color for border
+ label.lineWidth = 2; // Thicker border
+ });
+ }
+
+ return labels;
+ }
+ }
+ },
+ tooltip: {
+ titleFont: {
+ weight: 'bold',
+ size: 14
+ },
+ bodyFont: {
+ size: 13
+ },
+ backgroundColor: colors.isDarkMode ? 'rgba(0, 0, 0, 0.8)' : 'rgba(255, 255, 255, 0.8)',
+ titleColor: colors.isDarkMode ? '#ffffff' : '#000000',
+ bodyColor: colors.isDarkMode ? '#ffffff' : '#000000',
+ borderColor: colors.grid,
+ borderWidth: 1
+ }
+ }
}
});
@@ -595,10 +740,10 @@ function renderCharts(data) {
data: {
labels: timeLabels,
datasets: [{
- label: 'Events Over Time',
+ label: window.i18n.eventsOverTime,
data: timeValues,
- backgroundColor: 'rgba(75, 192, 192, 0.2)',
- borderColor: 'rgba(75, 192, 192, 1)',
+ backgroundColor: colors.chartColors[0] + '40', // 40 = 25% opacity
+ borderColor: colors.chartColors[0],
tension: 0.1,
fill: true
}]
@@ -606,9 +751,74 @@ function renderCharts(data) {
options: {
responsive: true,
maintainAspectRatio: false,
+ plugins: {
+ legend: {
+ labels: {
+ color: colors.text,
+ font: {
+ weight: colors.isDarkMode ? 'bold' : 'normal',
+ size: 14
+ }
+ }
+ },
+ tooltip: {
+ titleFont: {
+ weight: 'bold',
+ size: 14
+ },
+ bodyFont: {
+ size: 13
+ },
+ backgroundColor: colors.isDarkMode ? 'rgba(0, 0, 0, 0.8)' : 'rgba(255, 255, 255, 0.8)',
+ titleColor: colors.isDarkMode ? '#ffffff' : '#000000',
+ bodyColor: colors.isDarkMode ? '#ffffff' : '#000000',
+ borderColor: colors.grid,
+ borderWidth: 1
+ }
+ },
scales: {
y: {
- beginAtZero: true
+ beginAtZero: true,
+ ticks: {
+ color: colors.text,
+ font: {
+ weight: colors.isDarkMode ? 'bold' : 'normal',
+ size: 12
+ }
+ },
+ grid: {
+ color: colors.grid
+ },
+ title: {
+ display: true,
+ text: 'Number of Events',
+ color: colors.text,
+ font: {
+ weight: colors.isDarkMode ? 'bold' : 'normal',
+ size: 14
+ }
+ }
+ },
+ x: {
+ ticks: {
+ color: colors.text,
+ font: {
+ weight: colors.isDarkMode ? 'bold' : 'normal',
+ size: 12
+ }
+ },
+ grid: {
+ color: colors.grid
+ },
+ title: {
+ display: true,
+ text: 'Date',
+ color: colors.text,
+ font: {
+ weight: colors.isDarkMode ? 'bold' : 'normal',
+ size: 14
+ }
+ }
}
}
}
@@ -684,8 +894,63 @@ function loadEventTypes() {
});
}
+// Get theme colors for charts
+function getThemeColors() {
+ const isDarkMode = document.documentElement.getAttribute('data-bs-theme') === 'dark';
+
+ // In dark mode, use higher contrast colors for text
+ const textColor = isDarkMode ?
+ 'rgb(255, 255, 255)' : // White for dark mode for maximum contrast
+ getComputedStyle(document.documentElement).getPropertyValue('--md-sys-color-on-surface').trim();
+
+ // Use a more visible grid color in dark mode
+ const gridColor = isDarkMode ?
+ 'rgba(255, 255, 255, 0.2)' : // Semi-transparent white for dark mode
+ getComputedStyle(document.documentElement).getPropertyValue('--md-sys-color-outline-variant').trim();
+
+ return {
+ text: textColor,
+ grid: gridColor,
+ backgroundColor: getComputedStyle(document.documentElement).getPropertyValue('--md-sys-color-surface-container').trim(),
+ chartColors: [
+ getComputedStyle(document.documentElement).getPropertyValue('--md-sys-color-primary').trim(),
+ getComputedStyle(document.documentElement).getPropertyValue('--md-sys-color-secondary').trim(),
+ getComputedStyle(document.documentElement).getPropertyValue('--md-sys-color-tertiary').trim(),
+ getComputedStyle(document.documentElement).getPropertyValue('--md-nav-section-color-other').trim(),
+ getComputedStyle(document.documentElement).getPropertyValue('--md-nav-section-color-convert').trim(),
+ getComputedStyle(document.documentElement).getPropertyValue('--md-nav-section-color-sign').trim(),
+ getComputedStyle(document.documentElement).getPropertyValue('--md-nav-section-color-security').trim(),
+ getComputedStyle(document.documentElement).getPropertyValue('--md-nav-section-color-convertto').trim(),
+ ],
+ isDarkMode: isDarkMode
+ };
+}
+
// Function to generate a palette of colors for charts
function getChartColors(count, opacity = 0.6) {
+ try {
+ // Use theme colors first
+ const themeColors = getThemeColors();
+ if (themeColors && themeColors.chartColors && themeColors.chartColors.length > 0) {
+ const result = [];
+ for (let i = 0; i < count; i++) {
+ // Get the raw color and add opacity
+ let color = themeColors.chartColors[i % themeColors.chartColors.length];
+ // If it's rgb() format, convert to rgba()
+ if (color.startsWith('rgb(')) {
+ color = color.replace('rgb(', '').replace(')', '');
+ result.push(`rgba(${color}, ${opacity})`);
+ } else {
+ // Just use the color directly
+ result.push(color);
+ }
+ }
+ return result;
+ }
+ } catch (e) {
+ console.warn('Error using theme colors, falling back to default colors', e);
+ }
+
// Base colors - a larger palette than the default
const colors = [
[54, 162, 235], // blue
diff --git a/proprietary/src/main/resources/templates/AUDIT_HELP.md b/proprietary/src/main/resources/templates/AUDIT_HELP.md
new file mode 100644
index 000000000..be931076c
--- /dev/null
+++ b/proprietary/src/main/resources/templates/AUDIT_HELP.md
@@ -0,0 +1,42 @@
+# Audit System Help
+
+## About the Audit System
+The Stirling PDF audit system records user actions and system events for security monitoring, compliance, and troubleshooting purposes.
+
+## Audit Levels
+
+| Level | Name | Description | Use Case |
+|-------|------|-------------|----------|
+| 0 | OFF | Minimal auditing, only critical security events | Development environments |
+| 1 | BASIC | Authentication events, security events, and errors | Production environments with minimal storage |
+| 2 | STANDARD | All HTTP requests and operations (default) | Normal production use |
+| 3 | VERBOSE | Detailed information including headers, parameters, and results | Troubleshooting and detailed analysis |
+
+## Configuration
+Audit settings are configured in the `settings.yml` file under the `premium.proFeatures.audit` section:
+
+```yaml
+premium:
+ proFeatures:
+ audit:
+ enabled: true # Enable/disable audit logging
+ level: 2 # Audit level (0=OFF, 1=BASIC, 2=STANDARD, 3=VERBOSE)
+ retentionDays: 90 # Number of days to retain audit logs
+```
+
+## Common Event Types
+
+### BASIC Events:
+- USER_LOGIN - User login
+- USER_LOGOUT - User logout
+- USER_FAILED_LOGIN - Failed login attempt
+- USER_PROFILE_UPDATE - User or profile operations
+
+### STANDARD Events:
+- HTTP_REQUEST - GET requests for viewing
+- PDF_PROCESS - PDF processing operations
+- FILE_OPERATION - File-related operations
+- SETTINGS_CHANGED - System or admin settings operations
+
+### VERBOSE Events:
+- Detailed versions of STANDARD events with parameters and results
\ No newline at end of file
diff --git a/proprietary/src/main/resources/templates/audit/dashboard.html b/proprietary/src/main/resources/templates/audit/dashboard.html
index 5eea355c6..71c29a1f7 100644
--- a/proprietary/src/main/resources/templates/audit/dashboard.html
+++ b/proprietary/src/main/resources/templates/audit/dashboard.html
@@ -12,30 +12,30 @@
-
+
-
Audit Dashboard
+
Audit Dashboard
-
Status
+
Status
- Enabled
- Disabled
+ Enabled
+ Disabled
-
Current Level
+
Current Level
STANDARD
@@ -43,13 +43,13 @@
-
Retention Period
-
90 days
+
Retention Period
+
90 days
-
Total Events
+
Total Events
-
@@ -60,16 +60,15 @@
-
-
+
-
-
+
-
-
+
-
-
@@ -80,18 +79,18 @@
- Loading...
+ Loading...
@@ -102,13 +101,13 @@
- Loading...
+ Loading...
@@ -121,13 +120,13 @@
- Loading...
+ Loading...
@@ -142,7 +141,7 @@
@@ -150,36 +149,36 @@
-
+
-
-
+
+
@@ -188,17 +187,17 @@
- Loading...
+ Loading...
- ID |
- Time |
- User |
- Type |
- Details |
+ ID |
+ Time |
+ User |
+ Type |
+ Details |
@@ -210,15 +209,15 @@
@@ -453,830 +361,25 @@
+
+
+
-
-
-
-
+
diff --git a/stirling-pdf/src/main/resources/messages_en_GB.properties b/stirling-pdf/src/main/resources/messages_en_GB.properties
index c337551dd..940431990 100644
--- a/stirling-pdf/src/main/resources/messages_en_GB.properties
+++ b/stirling-pdf/src/main/resources/messages_en_GB.properties
@@ -1636,6 +1636,83 @@ validateSignature.cert.keyUsage=Key Usage
validateSignature.cert.selfSigned=Self-Signed
validateSignature.cert.bits=bits
+# Audit Dashboard
+audit.dashboard.title=Audit Dashboard
+audit.dashboard.systemStatus=Audit System Status
+audit.dashboard.status=Status
+audit.dashboard.enabled=Enabled
+audit.dashboard.disabled=Disabled
+audit.dashboard.currentLevel=Current Level
+audit.dashboard.retentionPeriod=Retention Period
+audit.dashboard.days=days
+audit.dashboard.totalEvents=Total Events
+
+# Audit Dashboard Tabs
+audit.dashboard.tab.dashboard=Dashboard
+audit.dashboard.tab.events=Audit Events
+audit.dashboard.tab.export=Export
+# Dashboard Charts
+audit.dashboard.eventsByType=Events by Type
+audit.dashboard.eventsByUser=Events by User
+audit.dashboard.eventsOverTime=Events Over Time
+audit.dashboard.period.7days=7 Days
+audit.dashboard.period.30days=30 Days
+audit.dashboard.period.90days=90 Days
+
+# Events Tab
+audit.dashboard.auditEvents=Audit Events
+audit.dashboard.filter.eventType=Event Type
+audit.dashboard.filter.allEventTypes=All event types
+audit.dashboard.filter.user=User
+audit.dashboard.filter.userPlaceholder=Filter by user
+audit.dashboard.filter.startDate=Start Date
+audit.dashboard.filter.endDate=End Date
+audit.dashboard.filter.apply=Apply Filters
+audit.dashboard.filter.reset=Reset Filters
+
+# Table Headers
+audit.dashboard.table.id=ID
+audit.dashboard.table.time=Time
+audit.dashboard.table.user=User
+audit.dashboard.table.type=Type
+audit.dashboard.table.details=Details
+
+# Pagination
+audit.dashboard.pagination.show=Show
+audit.dashboard.pagination.entries=entries
+audit.dashboard.pagination.pageInfo1=Page
+audit.dashboard.pagination.pageInfo2=of
+audit.dashboard.pagination.totalRecords=Total records:
+
+# Modal
+audit.dashboard.modal.eventDetails=Event Details
+audit.dashboard.modal.id=ID
+audit.dashboard.modal.user=User
+audit.dashboard.modal.type=Type
+audit.dashboard.modal.time=Time
+audit.dashboard.modal.data=Data
+
+# Export Tab
+audit.dashboard.export.title=Export Audit Data
+audit.dashboard.export.format=Export Format
+audit.dashboard.export.csv=CSV (Comma Separated Values)
+audit.dashboard.export.json=JSON (JavaScript Object Notation)
+audit.dashboard.export.button=Export Data
+audit.dashboard.export.infoTitle=Export Information
+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.
+audit.dashboard.export.infoDesc2=Exported data will include:
+audit.dashboard.export.infoItem1=Event ID
+audit.dashboard.export.infoItem2=User
+audit.dashboard.export.infoItem3=Event Type
+audit.dashboard.export.infoItem4=Timestamp
+audit.dashboard.export.infoItem5=Event Data
+
+# JavaScript i18n keys
+audit.dashboard.js.noEventsFound=No audit events found matching the current filters
+audit.dashboard.js.errorLoading=Error loading data:
+audit.dashboard.js.errorRendering=Error rendering table:
+audit.dashboard.js.loadingPage=Loading page
+
####################
# Cookie banner #
####################