refactor form for blocklist download
This commit is contained in:
@@ -9,6 +9,11 @@ import html
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from zoneinfo import ZoneInfo
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
|
# imports for the __init_subclass__ method, do not remove pls
|
||||||
|
from firewall import fwtype
|
||||||
|
from firewall.iptables import Iptables
|
||||||
|
from firewall.raw import Raw
|
||||||
|
|
||||||
|
|
||||||
def _escape(value) -> str:
|
def _escape(value) -> str:
|
||||||
"""Escape HTML special characters to prevent XSS attacks."""
|
"""Escape HTML special characters to prevent XSS attacks."""
|
||||||
@@ -653,11 +658,13 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str:
|
|||||||
</svg>
|
</svg>
|
||||||
<span class="github-logo-text">BlessedRebuS/Krawl</span>
|
<span class="github-logo-text">BlessedRebuS/Krawl</span>
|
||||||
</a>
|
</a>
|
||||||
<div class="download-section">
|
<form class="download-section" action="{dashboard_path}/api/get_banlist" method="GET" >
|
||||||
<a href="{dashboard_path}/api/download/malicious_ips.txt" class="download-btn" download>
|
<select class="download-btn" name="fwtype" id="fwtype">
|
||||||
Export Malicious IPs
|
<option value="raw">raw</option>
|
||||||
</a>
|
<option value="iptables">iptables</option>
|
||||||
</div>
|
</select>
|
||||||
|
<input type="submit" class="download-btn" value="Export IPs Banlist">
|
||||||
|
</form>
|
||||||
<h1>Krawl Dashboard</h1>
|
<h1>Krawl Dashboard</h1>
|
||||||
|
|
||||||
<div class="stats-grid">
|
<div class="stats-grid">
|
||||||
@@ -1106,7 +1113,7 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str:
|
|||||||
if (stats.category_history && stats.category_history.length > 0) {{
|
if (stats.category_history && stats.category_history.length > 0) {{
|
||||||
html += '<div class="timeline-section">';
|
html += '<div class="timeline-section">';
|
||||||
html += '<div class="timeline-container">';
|
html += '<div class="timeline-container">';
|
||||||
|
|
||||||
// Timeline column
|
// Timeline column
|
||||||
html += '<div class="timeline-column">';
|
html += '<div class="timeline-column">';
|
||||||
html += '<div class="timeline-header">Behavior Timeline</div>';
|
html += '<div class="timeline-header">Behavior Timeline</div>';
|
||||||
@@ -1117,18 +1124,18 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str:
|
|||||||
const timestamp = formatTimestamp(change.timestamp);
|
const timestamp = formatTimestamp(change.timestamp);
|
||||||
const oldClass = change.old_category ? 'category-' + change.old_category.toLowerCase().replace('_', '-') : '';
|
const oldClass = change.old_category ? 'category-' + change.old_category.toLowerCase().replace('_', '-') : '';
|
||||||
const newClass = 'category-' + categoryClass;
|
const newClass = 'category-' + categoryClass;
|
||||||
|
|
||||||
html += '<div class="timeline-item">';
|
html += '<div class="timeline-item">';
|
||||||
html += `<div class="timeline-marker ${{categoryClass}}"></div>`;
|
html += `<div class="timeline-marker ${{categoryClass}}"></div>`;
|
||||||
html += '<div class="timeline-content">';
|
html += '<div class="timeline-content">';
|
||||||
|
|
||||||
if (change.old_category) {{
|
if (change.old_category) {{
|
||||||
html += `<span class="category-badge ${{oldClass}}">${{change.old_category}}</span>`;
|
html += `<span class="category-badge ${{oldClass}}">${{change.old_category}}</span>`;
|
||||||
html += '<span style="color: #8b949e; margin: 0 4px;">→</span>';
|
html += '<span style="color: #8b949e; margin: 0 4px;">→</span>';
|
||||||
}} else {{
|
}} else {{
|
||||||
html += '<span style="color: #8b949e;">Initial:</span>';
|
html += '<span style="color: #8b949e;">Initial:</span>';
|
||||||
}}
|
}}
|
||||||
|
|
||||||
html += `<span class="category-badge ${{newClass}}">${{change.new_category}}</span>`;
|
html += `<span class="category-badge ${{newClass}}">${{change.new_category}}</span>`;
|
||||||
html += `<div class="timeline-time">${{timestamp}}</div>`;
|
html += `<div class="timeline-time">${{timestamp}}</div>`;
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
@@ -1137,14 +1144,14 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str:
|
|||||||
|
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
|
|
||||||
// Reputation column
|
// Reputation column
|
||||||
html += '<div class="timeline-column">';
|
html += '<div class="timeline-column">';
|
||||||
|
|
||||||
if (stats.list_on && Object.keys(stats.list_on).length > 0) {{
|
if (stats.list_on && Object.keys(stats.list_on).length > 0) {{
|
||||||
html += '<div class="timeline-header">Listed On</div>';
|
html += '<div class="timeline-header">Listed On</div>';
|
||||||
const sortedSources = Object.entries(stats.list_on).sort((a, b) => a[0].localeCompare(b[0]));
|
const sortedSources = Object.entries(stats.list_on).sort((a, b) => a[0].localeCompare(b[0]));
|
||||||
|
|
||||||
sortedSources.forEach(([source, url]) => {{
|
sortedSources.forEach(([source, url]) => {{
|
||||||
if (url && url !== 'N/A') {{
|
if (url && url !== 'N/A') {{
|
||||||
html += `<a href="${{url}}" target="_blank" rel="noopener noreferrer" class="reputation-badge" title="${{source}}">${{source}}</a>`;
|
html += `<a href="${{url}}" target="_blank" rel="noopener noreferrer" class="reputation-badge" title="${{source}}">${{source}}</a>`;
|
||||||
@@ -1156,7 +1163,7 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str:
|
|||||||
html += '<div class="timeline-header">Reputation</div>';
|
html += '<div class="timeline-header">Reputation</div>';
|
||||||
html += '<span class="reputation-clean" title="Not found on public blacklists">✓ Clean</span>';
|
html += '<span class="reputation-clean" title="Not found on public blacklists">✓ Clean</span>';
|
||||||
}}
|
}}
|
||||||
|
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
@@ -1360,23 +1367,23 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str:
|
|||||||
document.querySelectorAll('.tab-content').forEach(tab => {{
|
document.querySelectorAll('.tab-content').forEach(tab => {{
|
||||||
tab.classList.remove('active');
|
tab.classList.remove('active');
|
||||||
}});
|
}});
|
||||||
|
|
||||||
// Remove active class from all buttons
|
// Remove active class from all buttons
|
||||||
document.querySelectorAll('.tab-button').forEach(btn => {{
|
document.querySelectorAll('.tab-button').forEach(btn => {{
|
||||||
btn.classList.remove('active');
|
btn.classList.remove('active');
|
||||||
}});
|
}});
|
||||||
|
|
||||||
// Show selected tab
|
// Show selected tab
|
||||||
const selectedTab = document.getElementById(tabName);
|
const selectedTab = document.getElementById(tabName);
|
||||||
const selectedButton = document.querySelector(`.tab-button[href="#${{tabName}}"]`);
|
const selectedButton = document.querySelector(`.tab-button[href="#${{tabName}}"]`);
|
||||||
|
|
||||||
if (selectedTab) {{
|
if (selectedTab) {{
|
||||||
selectedTab.classList.add('active');
|
selectedTab.classList.add('active');
|
||||||
}}
|
}}
|
||||||
if (selectedButton) {{
|
if (selectedButton) {{
|
||||||
selectedButton.classList.add('active');
|
selectedButton.classList.add('active');
|
||||||
}}
|
}}
|
||||||
|
|
||||||
// Load data for this tab
|
// Load data for this tab
|
||||||
if (tabName === 'ip-stats') {{
|
if (tabName === 'ip-stats') {{
|
||||||
loadIpStatistics(1);
|
loadIpStatistics(1);
|
||||||
@@ -1418,7 +1425,7 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str:
|
|||||||
if (e.target.classList.contains('sortable') && e.target.closest('#ip-stats-tbody')) {{
|
if (e.target.classList.contains('sortable') && e.target.closest('#ip-stats-tbody')) {{
|
||||||
return; // Don't sort when inside tbody
|
return; // Don't sort when inside tbody
|
||||||
}}
|
}}
|
||||||
|
|
||||||
const sortHeader = e.target.closest('th.sortable');
|
const sortHeader = e.target.closest('th.sortable');
|
||||||
if (!sortHeader) return;
|
if (!sortHeader) return;
|
||||||
|
|
||||||
@@ -1426,7 +1433,7 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str:
|
|||||||
if (!table || !table.classList.contains('ip-stats-table')) return;
|
if (!table || !table.classList.contains('ip-stats-table')) return;
|
||||||
|
|
||||||
const sortField = sortHeader.getAttribute('data-sort');
|
const sortField = sortHeader.getAttribute('data-sort');
|
||||||
|
|
||||||
// Toggle sort order if clicking the same field
|
// Toggle sort order if clicking the same field
|
||||||
if (currentSortBy === sortField) {{
|
if (currentSortBy === sortField) {{
|
||||||
currentSortOrder = currentSortOrder === 'desc' ? 'asc' : 'desc';
|
currentSortOrder = currentSortOrder === 'desc' ? 'asc' : 'desc';
|
||||||
@@ -1457,9 +1464,9 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str:
|
|||||||
console.error('IP stats tbody not found');
|
console.error('IP stats tbody not found');
|
||||||
return;
|
return;
|
||||||
}}
|
}}
|
||||||
|
|
||||||
tbody.innerHTML = '<tr><td colspan="6" style="text-align: center;">Loading...</td></tr>';
|
tbody.innerHTML = '<tr><td colspan="6" style="text-align: center;">Loading...</td></tr>';
|
||||||
|
|
||||||
try {{
|
try {{
|
||||||
console.log('Fetching attackers from page:', page, 'sort:', currentSortBy, currentSortOrder);
|
console.log('Fetching attackers from page:', page, 'sort:', currentSortBy, currentSortOrder);
|
||||||
const response = await fetch(DASHBOARD_PATH + '/api/attackers?page=' + page + '&page_size=' + PAGE_SIZE + '&sort_by=' + currentSortBy + '&sort_order=' + currentSortOrder, {{
|
const response = await fetch(DASHBOARD_PATH + '/api/attackers?page=' + page + '&page_size=' + PAGE_SIZE + '&sort_by=' + currentSortBy + '&sort_order=' + currentSortOrder, {{
|
||||||
@@ -1469,14 +1476,14 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str:
|
|||||||
'Pragma': 'no-cache'
|
'Pragma': 'no-cache'
|
||||||
}}
|
}}
|
||||||
}});
|
}});
|
||||||
|
|
||||||
console.log('Response status:', response.status);
|
console.log('Response status:', response.status);
|
||||||
|
|
||||||
if (!response.ok) throw new Error(`HTTP ${{response.status}}`);
|
if (!response.ok) throw new Error(`HTTP ${{response.status}}`);
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
console.log('Received data:', data);
|
console.log('Received data:', data);
|
||||||
|
|
||||||
if (!data.attackers || data.attackers.length === 0) {{
|
if (!data.attackers || data.attackers.length === 0) {{
|
||||||
tbody.innerHTML = '<tr><td colspan="6" style="text-align: center;">No attackers on this page.</td></tr>';
|
tbody.innerHTML = '<tr><td colspan="6" style="text-align: center;">No attackers on this page.</td></tr>';
|
||||||
currentPage = page;
|
currentPage = page;
|
||||||
@@ -1484,7 +1491,7 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str:
|
|||||||
updatePaginationControls();
|
updatePaginationControls();
|
||||||
return;
|
return;
|
||||||
}}
|
}}
|
||||||
|
|
||||||
// Update pagination info
|
// Update pagination info
|
||||||
currentPage = data.pagination.page;
|
currentPage = data.pagination.page;
|
||||||
totalPages = data.pagination.total_pages;
|
totalPages = data.pagination.total_pages;
|
||||||
@@ -1492,7 +1499,7 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str:
|
|||||||
document.getElementById('total-pages').textContent = totalPages;
|
document.getElementById('total-pages').textContent = totalPages;
|
||||||
document.getElementById('total-attackers').textContent = data.pagination.total_attackers;
|
document.getElementById('total-attackers').textContent = data.pagination.total_attackers;
|
||||||
updatePaginationControls();
|
updatePaginationControls();
|
||||||
|
|
||||||
let html = '';
|
let html = '';
|
||||||
data.attackers.forEach((attacker, index) => {{
|
data.attackers.forEach((attacker, index) => {{
|
||||||
const rank = (currentPage - 1) * PAGE_SIZE + index + 1;
|
const rank = (currentPage - 1) * PAGE_SIZE + index + 1;
|
||||||
@@ -1512,10 +1519,10 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str:
|
|||||||
</td>
|
</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
}});
|
}});
|
||||||
|
|
||||||
tbody.innerHTML = html;
|
tbody.innerHTML = html;
|
||||||
console.log('Populated', data.attackers.length, 'attacker records');
|
console.log('Populated', data.attackers.length, 'attacker records');
|
||||||
|
|
||||||
// Re-attach click listeners for expandable rows
|
// Re-attach click listeners for expandable rows
|
||||||
attachAttackerClickListeners();
|
attachAttackerClickListeners();
|
||||||
}} catch (err) {{
|
}} catch (err) {{
|
||||||
@@ -1527,7 +1534,7 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str:
|
|||||||
function updatePaginationControls() {{
|
function updatePaginationControls() {{
|
||||||
const prevBtn = document.getElementById('prev-page-btn');
|
const prevBtn = document.getElementById('prev-page-btn');
|
||||||
const nextBtn = document.getElementById('next-page-btn');
|
const nextBtn = document.getElementById('next-page-btn');
|
||||||
|
|
||||||
if (prevBtn) prevBtn.disabled = currentPage <= 1;
|
if (prevBtn) prevBtn.disabled = currentPage <= 1;
|
||||||
if (nextBtn) nextBtn.disabled = currentPage >= totalPages;
|
if (nextBtn) nextBtn.disabled = currentPage >= totalPages;
|
||||||
}}
|
}}
|
||||||
@@ -1799,7 +1806,7 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str:
|
|||||||
async function loadOverviewTable(tableId) {{
|
async function loadOverviewTable(tableId) {{
|
||||||
const config = tableConfig[tableId];
|
const config = tableConfig[tableId];
|
||||||
if (!config) return;
|
if (!config) return;
|
||||||
|
|
||||||
const state = overviewState[tableId];
|
const state = overviewState[tableId];
|
||||||
const tbody = document.getElementById(tableId + '-tbody');
|
const tbody = document.getElementById(tableId + '-tbody');
|
||||||
if (!tbody) return;
|
if (!tbody) return;
|
||||||
@@ -1833,7 +1840,7 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str:
|
|||||||
let html = '';
|
let html = '';
|
||||||
items.forEach((item, index) => {{
|
items.forEach((item, index) => {{
|
||||||
const rank = (state.currentPage - 1) * 5 + index + 1;
|
const rank = (state.currentPage - 1) * 5 + index + 1;
|
||||||
|
|
||||||
if (tableId === 'honeypot') {{
|
if (tableId === 'honeypot') {{
|
||||||
html += `<tr class="ip-row" data-ip="${{item.ip}}"><td class="rank">${{rank}}</td><td class="ip-clickable">${{item.ip}}</td><td>${{item.paths.join(', ')}}</td><td>${{item.count}}</td></tr>`;
|
html += `<tr class="ip-row" data-ip="${{item.ip}}"><td class="rank">${{rank}}</td><td class="ip-clickable">${{item.ip}}</td><td>${{item.paths.join(', ')}}</td><td>${{item.count}}</td></tr>`;
|
||||||
html += `<tr class="ip-stats-row" id="stats-row-honeypot-${{item.ip.replace(/\\./g, '-')}}" style="display: none;">
|
html += `<tr class="ip-stats-row" id="stats-row-honeypot-${{item.ip.replace(/\\./g, '-')}}" style="display: none;">
|
||||||
@@ -1979,12 +1986,12 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str:
|
|||||||
async function showIpDetail(ip) {{
|
async function showIpDetail(ip) {{
|
||||||
const modal = document.getElementById('ip-detail-modal');
|
const modal = document.getElementById('ip-detail-modal');
|
||||||
const bodyDiv = document.getElementById('ip-detail-body');
|
const bodyDiv = document.getElementById('ip-detail-body');
|
||||||
|
|
||||||
if (!modal || !bodyDiv) return;
|
if (!modal || !bodyDiv) return;
|
||||||
|
|
||||||
bodyDiv.innerHTML = '<div class="loading" style="text-align: center;">Loading IP details...</div>';
|
bodyDiv.innerHTML = '<div class="loading" style="text-align: center;">Loading IP details...</div>';
|
||||||
modal.classList.add('show');
|
modal.classList.add('show');
|
||||||
|
|
||||||
try {{
|
try {{
|
||||||
const response = await fetch(`${{DASHBOARD_PATH}}/api/ip-stats/${{ip}}`, {{
|
const response = await fetch(`${{DASHBOARD_PATH}}/api/ip-stats/${{ip}}`, {{
|
||||||
cache: 'no-store',
|
cache: 'no-store',
|
||||||
@@ -1993,9 +2000,9 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str:
|
|||||||
'Pragma': 'no-cache'
|
'Pragma': 'no-cache'
|
||||||
}}
|
}}
|
||||||
}});
|
}});
|
||||||
|
|
||||||
if (!response.ok) throw new Error(`HTTP ${{response.status}}`);
|
if (!response.ok) throw new Error(`HTTP ${{response.status}}`);
|
||||||
|
|
||||||
const stats = await response.json();
|
const stats = await response.json();
|
||||||
bodyDiv.innerHTML = '<h2>' + stats.ip + ' - Detailed Statistics</h2>' + formatIpStats(stats);
|
bodyDiv.innerHTML = '<h2>' + stats.ip + ' - Detailed Statistics</h2>' + formatIpStats(stats);
|
||||||
}} catch (err) {{
|
}} catch (err) {{
|
||||||
@@ -2422,7 +2429,7 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str:
|
|||||||
// Initialize map when Attacks tab is opened
|
// Initialize map when Attacks tab is opened
|
||||||
const originalSwitchTab = window.switchTab;
|
const originalSwitchTab = window.switchTab;
|
||||||
let attackTypesChartLoaded = false;
|
let attackTypesChartLoaded = false;
|
||||||
|
|
||||||
window.switchTab = function(tabName) {{
|
window.switchTab = function(tabName) {{
|
||||||
originalSwitchTab(tabName);
|
originalSwitchTab(tabName);
|
||||||
if (tabName === 'ip-stats') {{
|
if (tabName === 'ip-stats') {{
|
||||||
@@ -2455,7 +2462,7 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str:
|
|||||||
}});
|
}});
|
||||||
|
|
||||||
if (!response.ok) throw new Error('Failed to fetch attack types');
|
if (!response.ok) throw new Error('Failed to fetch attack types');
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
const attacks = data.attacks || [];
|
const attacks = data.attacks || [];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user