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=sanitized_ip,
|
||||
total_requests=0,
|
||||
first_seen=datetime.utcnow(),
|
||||
last_seen=datetime.utcnow(),
|
||||
first_seen=datetime.now(),
|
||||
last_seen=datetime.now(),
|
||||
)
|
||||
session.add(ip_stats)
|
||||
|
||||
|
||||
@@ -152,7 +152,6 @@ async def ban_override(request: Request, body: BanOverrideRequest):
|
||||
|
||||
db = get_db()
|
||||
action_map = {"ban": True, "unban": False, "reset": None}
|
||||
override_value = action_map.get(body.action)
|
||||
if body.action not in action_map:
|
||||
return JSONResponse(
|
||||
content={"error": "Invalid action. Use: ban, unban, reset"},
|
||||
@@ -162,7 +161,7 @@ async def ban_override(request: Request, body: BanOverrideRequest):
|
||||
if body.action == "ban":
|
||||
success = db.force_ban_ip(body.ip)
|
||||
else:
|
||||
success = db.set_ban_override(body.ip, override_value)
|
||||
success = db.set_ban_override(body.ip, action_map[body.action])
|
||||
|
||||
if success:
|
||||
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)
|
||||
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);
|
||||
}
|
||||
const data = getAlpineData('[x-data="dashboardApp()"]');
|
||||
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)
|
||||
window.krawlModal = {
|
||||
_create(icon, iconClass, message, buttons) {
|
||||
@@ -314,13 +323,14 @@ window.krawlModal = {
|
||||
// Global ban action for IP insight page (auth-gated)
|
||||
window.ipBanAction = async function(ip, action) {
|
||||
// Check if authenticated
|
||||
const container = document.querySelector('[x-data="dashboardApp()"]');
|
||||
const data = container && (Alpine.$data ? Alpine.$data(container) : (container._x_dataStack && container._x_dataStack[0]));
|
||||
const data = getAlpineData('[x-data="dashboardApp()"]');
|
||||
if (!data || !data.authenticated) {
|
||||
if (data && typeof data.promptAuth === 'function') data.promptAuth();
|
||||
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;
|
||||
try {
|
||||
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(() => ({}));
|
||||
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');
|
||||
if (overrides) {
|
||||
htmx.ajax('GET', `${window.__DASHBOARD_PATH__}/htmx/ban/overrides?page=1`, {
|
||||
@@ -340,7 +350,7 @@ window.ipBanAction = async function(ip, action) {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
krawlModal.error(result.error || `Failed to ${action} IP ${ip}`);
|
||||
krawlModal.error(escapeHtml(result.error || `Failed to ${action} IP ${ip}`));
|
||||
}
|
||||
} catch {
|
||||
krawlModal.error('Request failed');
|
||||
@@ -355,8 +365,7 @@ function updateBanActionVisibility(authenticated) {
|
||||
}
|
||||
// Update visibility after HTMX swaps in new content
|
||||
document.addEventListener('htmx:afterSwap', () => {
|
||||
const container = document.querySelector('[x-data="dashboardApp()"]');
|
||||
const data = container && (Alpine.$data ? Alpine.$data(container) : (container._x_dataStack && container._x_dataStack[0]));
|
||||
const data = getAlpineData('[x-data="dashboardApp()"]');
|
||||
if (data) updateBanActionVisibility(data.authenticated);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user