feat: update IP stats timestamps to use local time and improve dashboard JavaScript for safer data handling
This commit is contained in:
@@ -2269,8 +2269,8 @@ class DatabaseManager:
|
|||||||
ip_stats = IpStats(
|
ip_stats = IpStats(
|
||||||
ip=sanitized_ip,
|
ip=sanitized_ip,
|
||||||
total_requests=0,
|
total_requests=0,
|
||||||
first_seen=datetime.utcnow(),
|
first_seen=datetime.now(),
|
||||||
last_seen=datetime.utcnow(),
|
last_seen=datetime.now(),
|
||||||
)
|
)
|
||||||
session.add(ip_stats)
|
session.add(ip_stats)
|
||||||
|
|
||||||
|
|||||||
@@ -152,7 +152,6 @@ async def ban_override(request: Request, body: BanOverrideRequest):
|
|||||||
|
|
||||||
db = get_db()
|
db = get_db()
|
||||||
action_map = {"ban": True, "unban": False, "reset": None}
|
action_map = {"ban": True, "unban": False, "reset": None}
|
||||||
override_value = action_map.get(body.action)
|
|
||||||
if body.action not in action_map:
|
if body.action not in action_map:
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
content={"error": "Invalid action. Use: ban, unban, reset"},
|
content={"error": "Invalid action. Use: ban, unban, reset"},
|
||||||
@@ -162,7 +161,7 @@ async def ban_override(request: Request, body: BanOverrideRequest):
|
|||||||
if body.action == "ban":
|
if body.action == "ban":
|
||||||
success = db.force_ban_ip(body.ip)
|
success = db.force_ban_ip(body.ip)
|
||||||
else:
|
else:
|
||||||
success = db.set_ban_override(body.ip, override_value)
|
success = db.set_ban_override(body.ip, action_map[body.action])
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
get_app_logger().info(f"Ban override: {body.action} on IP {body.ip}")
|
get_app_logger().info(f"Ban override: {body.action} on IP {body.ip}")
|
||||||
|
|||||||
@@ -252,19 +252,28 @@ document.addEventListener('alpine:init', () => {
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Helper to access Alpine.js component data
|
||||||
|
function getAlpineData(selector) {
|
||||||
|
const container = document.querySelector(selector);
|
||||||
|
if (!container) return null;
|
||||||
|
return Alpine.$data ? Alpine.$data(container) : (container._x_dataStack && container._x_dataStack[0]);
|
||||||
|
}
|
||||||
|
|
||||||
// Global function for opening IP Insight (used by map popups)
|
// Global function for opening IP Insight (used by map popups)
|
||||||
window.openIpInsight = function(ip) {
|
window.openIpInsight = function(ip) {
|
||||||
// Find the Alpine component and call openIpInsight
|
const data = getAlpineData('[x-data="dashboardApp()"]');
|
||||||
const container = document.querySelector('[x-data="dashboardApp()"]');
|
if (data && typeof data.openIpInsight === 'function') {
|
||||||
if (container) {
|
data.openIpInsight(ip);
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Escape HTML to prevent XSS when inserting into innerHTML
|
||||||
|
function escapeHtml(str) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.textContent = str;
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
// Custom modal system (replaces native confirm/alert)
|
// Custom modal system (replaces native confirm/alert)
|
||||||
window.krawlModal = {
|
window.krawlModal = {
|
||||||
_create(icon, iconClass, message, buttons) {
|
_create(icon, iconClass, message, buttons) {
|
||||||
@@ -314,13 +323,14 @@ window.krawlModal = {
|
|||||||
// Global ban action for IP insight page (auth-gated)
|
// Global ban action for IP insight page (auth-gated)
|
||||||
window.ipBanAction = async function(ip, action) {
|
window.ipBanAction = async function(ip, action) {
|
||||||
// Check if authenticated
|
// Check if authenticated
|
||||||
const container = document.querySelector('[x-data="dashboardApp()"]');
|
const data = getAlpineData('[x-data="dashboardApp()"]');
|
||||||
const data = container && (Alpine.$data ? Alpine.$data(container) : (container._x_dataStack && container._x_dataStack[0]));
|
|
||||||
if (!data || !data.authenticated) {
|
if (!data || !data.authenticated) {
|
||||||
if (data && typeof data.promptAuth === 'function') data.promptAuth();
|
if (data && typeof data.promptAuth === 'function') data.promptAuth();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const confirmed = await krawlModal.confirm(`Are you sure you want to ${action} IP <strong>${ip}</strong>?`);
|
const safeIp = escapeHtml(ip);
|
||||||
|
const safeAction = escapeHtml(action);
|
||||||
|
const confirmed = await krawlModal.confirm(`Are you sure you want to ${safeAction} IP <strong>${safeIp}</strong>?`);
|
||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(`${window.__DASHBOARD_PATH__}/api/ban-override`, {
|
const resp = await fetch(`${window.__DASHBOARD_PATH__}/api/ban-override`, {
|
||||||
@@ -331,7 +341,7 @@ window.ipBanAction = async function(ip, action) {
|
|||||||
});
|
});
|
||||||
const result = await resp.json().catch(() => ({}));
|
const result = await resp.json().catch(() => ({}));
|
||||||
if (resp.ok) {
|
if (resp.ok) {
|
||||||
krawlModal.success(result.message || `${action} successful for ${ip}`);
|
krawlModal.success(escapeHtml(result.message || `${action} successful for ${ip}`));
|
||||||
const overrides = document.getElementById('overrides-container');
|
const overrides = document.getElementById('overrides-container');
|
||||||
if (overrides) {
|
if (overrides) {
|
||||||
htmx.ajax('GET', `${window.__DASHBOARD_PATH__}/htmx/ban/overrides?page=1`, {
|
htmx.ajax('GET', `${window.__DASHBOARD_PATH__}/htmx/ban/overrides?page=1`, {
|
||||||
@@ -340,7 +350,7 @@ window.ipBanAction = async function(ip, action) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
krawlModal.error(result.error || `Failed to ${action} IP ${ip}`);
|
krawlModal.error(escapeHtml(result.error || `Failed to ${action} IP ${ip}`));
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
krawlModal.error('Request failed');
|
krawlModal.error('Request failed');
|
||||||
@@ -355,8 +365,7 @@ function updateBanActionVisibility(authenticated) {
|
|||||||
}
|
}
|
||||||
// Update visibility after HTMX swaps in new content
|
// Update visibility after HTMX swaps in new content
|
||||||
document.addEventListener('htmx:afterSwap', () => {
|
document.addEventListener('htmx:afterSwap', () => {
|
||||||
const container = document.querySelector('[x-data="dashboardApp()"]');
|
const data = getAlpineData('[x-data="dashboardApp()"]');
|
||||||
const data = container && (Alpine.$data ? Alpine.$data(container) : (container._x_dataStack && container._x_dataStack[0]));
|
|
||||||
if (data) updateBanActionVisibility(data.authenticated);
|
if (data) updateBanActionVisibility(data.authenticated);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user