feat: add IP insight feature with detailed view and actions
- Updated various tables to include "Actions" column with inspect buttons for IP insights. - Created a new IP insight template for displaying detailed information about an IP address. - Implemented JavaScript functions to handle opening the IP insight view and loading data via HTMX. - Enhanced map markers to include inspect buttons for quick access to IP insights. - Added styles for the new IP insight page and buttons to maintain UI consistency.
This commit is contained in:
@@ -17,19 +17,26 @@ document.addEventListener('alpine:init', () => {
|
||||
// Chart state
|
||||
chartLoaded: false,
|
||||
|
||||
// IP Insight state
|
||||
insightIp: null,
|
||||
|
||||
init() {
|
||||
// Handle hash-based tab routing
|
||||
const hash = window.location.hash.slice(1);
|
||||
if (hash === 'ip-stats' || hash === 'attacks') {
|
||||
this.switchToAttacks();
|
||||
}
|
||||
// ip-insight tab is only accessible via lens buttons, not direct hash navigation
|
||||
|
||||
window.addEventListener('hashchange', () => {
|
||||
const h = window.location.hash.slice(1);
|
||||
if (h === 'ip-stats' || h === 'attacks') {
|
||||
this.switchToAttacks();
|
||||
} else {
|
||||
this.switchToOverview();
|
||||
} else if (h !== 'ip-insight') {
|
||||
// Don't switch away from ip-insight via hash if already there
|
||||
if (this.tab !== 'ip-insight') {
|
||||
this.switchToOverview();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -60,6 +67,31 @@ document.addEventListener('alpine:init', () => {
|
||||
window.location.hash = '#overview';
|
||||
},
|
||||
|
||||
switchToIpInsight() {
|
||||
// Only allow switching if an IP is selected
|
||||
if (!this.insightIp) return;
|
||||
this.tab = 'ip-insight';
|
||||
window.location.hash = '#ip-insight';
|
||||
},
|
||||
|
||||
openIpInsight(ip) {
|
||||
// Set the IP and load the insight content
|
||||
this.insightIp = ip;
|
||||
this.tab = 'ip-insight';
|
||||
window.location.hash = '#ip-insight';
|
||||
|
||||
// Load IP insight content via HTMX
|
||||
this.$nextTick(() => {
|
||||
const container = document.getElementById('ip-insight-htmx-container');
|
||||
if (container && typeof htmx !== 'undefined') {
|
||||
htmx.ajax('GET', `${this.dashboardPath}/htmx/ip-insight/${encodeURIComponent(ip)}`, {
|
||||
target: '#ip-insight-htmx-container',
|
||||
swap: 'innerHTML'
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async viewRawRequest(logId) {
|
||||
try {
|
||||
const resp = await fetch(
|
||||
@@ -110,6 +142,19 @@ document.addEventListener('alpine:init', () => {
|
||||
}));
|
||||
});
|
||||
|
||||
// Global function for opening IP Insight (used by map popups)
|
||||
window.openIpInsight = function(ip) {
|
||||
// Find the Alpine component and call openIpInsight
|
||||
const container = document.querySelector('[x-data="dashboardApp()"]');
|
||||
if (container) {
|
||||
// Try Alpine 3.x API first, then fall back to older API
|
||||
const data = Alpine.$data ? Alpine.$data(container) : (container._x_dataStack && container._x_dataStack[0]);
|
||||
if (data && typeof data.openIpInsight === 'function') {
|
||||
data.openIpInsight(ip);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Utility function for formatting timestamps (used by map popups)
|
||||
function formatTimestamp(isoTimestamp) {
|
||||
if (!isoTimestamp) return 'N/A';
|
||||
|
||||
@@ -340,6 +340,15 @@ function buildMapMarkers(ips) {
|
||||
`;
|
||||
}
|
||||
|
||||
// Add inspect button
|
||||
popupContent += `
|
||||
<div style="margin-top: 12px; border-top: 1px solid #30363d; padding-top: 12px; text-align: center;">
|
||||
<button onclick="window.openIpInsight('${ip.ip}')" class="inspect-btn" style="display: inline-flex; align-items: center; gap: 4px; padding: 6px 12px; background: #21262d; color: #58a6ff; border: 1px solid #30363d; border-radius: 4px; font-size: 12px; font-weight: 500; cursor: pointer; transition: all 0.2s;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 16 16" fill="currentColor"><path d="M10.68 11.74a6 6 0 0 1-7.922-8.982 6 6 0 0 1 8.982 7.922l3.04 3.04a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215ZM11.5 7a4.499 4.499 0 1 0-8.997 0A4.499 4.499 0 0 0 11.5 7Z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
popupContent += '</div>';
|
||||
marker.setPopupContent(popupContent);
|
||||
} catch (err) {
|
||||
@@ -363,6 +372,11 @@ function buildMapMarkers(ips) {
|
||||
<div style="margin-top: 12px; border-top: 1px solid #30363d; padding-top: 12px; text-align: center; color: #f85149; font-size: 11px;">
|
||||
Failed to load chart: ${err.message}
|
||||
</div>
|
||||
<div style="margin-top: 12px; text-align: center;">
|
||||
<button onclick="window.openIpInsight('${ip.ip}')" class="inspect-btn" style="display: inline-flex; align-items: center; gap: 4px; padding: 6px 12px; background: #21262d; color: #58a6ff; border: 1px solid #30363d; border-radius: 4px; font-size: 12px; font-weight: 500; cursor: pointer; transition: all 0.2s;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 16 16" fill="currentColor"><path d="M10.68 11.74a6 6 0 0 1-7.922-8.982 6 6 0 0 1 8.982 7.922l3.04 3.04a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215ZM11.5 7a4.499 4.499 0 1 0-8.997 0A4.499 4.499 0 0 0 11.5 7Z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
marker.setPopupContent(errorPopup);
|
||||
|
||||
Reference in New Issue
Block a user