starting full refactor with FastAPI routes + HTMX and AlpineJS on client side
This commit is contained in:
125
src/templates/static/js/dashboard.js
Normal file
125
src/templates/static/js/dashboard.js
Normal file
@@ -0,0 +1,125 @@
|
||||
// Alpine.js Dashboard Application
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('dashboardApp', () => ({
|
||||
// State
|
||||
tab: 'overview',
|
||||
dashboardPath: window.__DASHBOARD_PATH__ || '',
|
||||
|
||||
// Banlist dropdown
|
||||
banlistOpen: false,
|
||||
|
||||
// Raw request modal
|
||||
rawModal: { show: false, content: '', logId: null },
|
||||
|
||||
// Map state
|
||||
mapInitialized: false,
|
||||
|
||||
// Chart state
|
||||
chartLoaded: false,
|
||||
|
||||
init() {
|
||||
// Handle hash-based tab routing
|
||||
const hash = window.location.hash.slice(1);
|
||||
if (hash === 'ip-stats' || hash === 'attacks') {
|
||||
this.switchToAttacks();
|
||||
}
|
||||
|
||||
window.addEventListener('hashchange', () => {
|
||||
const h = window.location.hash.slice(1);
|
||||
if (h === 'ip-stats' || h === 'attacks') {
|
||||
this.switchToAttacks();
|
||||
} else {
|
||||
this.switchToOverview();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
switchToAttacks() {
|
||||
this.tab = 'attacks';
|
||||
window.location.hash = '#ip-stats';
|
||||
|
||||
// Delay initialization to ensure the container is visible and
|
||||
// the browser has reflowed after x-show removes display:none.
|
||||
// Leaflet and Chart.js need visible containers with real dimensions.
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
if (!this.mapInitialized && typeof initializeAttackerMap === 'function') {
|
||||
initializeAttackerMap();
|
||||
this.mapInitialized = true;
|
||||
}
|
||||
if (!this.chartLoaded && typeof loadAttackTypesChart === 'function') {
|
||||
loadAttackTypesChart();
|
||||
this.chartLoaded = true;
|
||||
}
|
||||
}, 200);
|
||||
});
|
||||
},
|
||||
|
||||
switchToOverview() {
|
||||
this.tab = 'overview';
|
||||
window.location.hash = '#overview';
|
||||
},
|
||||
|
||||
async viewRawRequest(logId) {
|
||||
try {
|
||||
const resp = await fetch(
|
||||
`${this.dashboardPath}/api/raw-request/${logId}`,
|
||||
{ cache: 'no-store' }
|
||||
);
|
||||
if (resp.status === 404) {
|
||||
alert('Raw request not available');
|
||||
return;
|
||||
}
|
||||
const data = await resp.json();
|
||||
this.rawModal.content = data.raw_request || 'No content available';
|
||||
this.rawModal.logId = logId;
|
||||
this.rawModal.show = true;
|
||||
} catch (err) {
|
||||
alert('Failed to load raw request');
|
||||
}
|
||||
},
|
||||
|
||||
closeRawModal() {
|
||||
this.rawModal.show = false;
|
||||
this.rawModal.content = '';
|
||||
this.rawModal.logId = null;
|
||||
},
|
||||
|
||||
downloadRawRequest() {
|
||||
if (!this.rawModal.content) return;
|
||||
const blob = new Blob([this.rawModal.content], { type: 'text/plain' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `raw-request-${this.rawModal.logId || Date.now()}.txt`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
},
|
||||
|
||||
toggleIpDetail(event) {
|
||||
const row = event.target.closest('tr');
|
||||
if (!row) return;
|
||||
const detailRow = row.nextElementSibling;
|
||||
if (detailRow && detailRow.classList.contains('ip-stats-row')) {
|
||||
detailRow.style.display =
|
||||
detailRow.style.display === 'table-row' ? 'none' : 'table-row';
|
||||
}
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
// Utility function for formatting timestamps (used by map popups)
|
||||
function formatTimestamp(isoTimestamp) {
|
||||
if (!isoTimestamp) return 'N/A';
|
||||
try {
|
||||
const date = new Date(isoTimestamp);
|
||||
return date.toLocaleString('en-US', {
|
||||
year: 'numeric', month: '2-digit', day: '2-digit',
|
||||
hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false
|
||||
});
|
||||
} catch {
|
||||
return isoTimestamp;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user