first commit

This commit is contained in:
Mateusz Gruszczyński
2026-02-05 12:11:00 +01:00
commit a547894adc
13 changed files with 2335 additions and 0 deletions

129
static/js/api.js Normal file
View File

@@ -0,0 +1,129 @@
// Copy code to clipboard
function copyCode(btn) {
const pre = btn.closest('pre');
const code = pre.querySelector('code').textContent;
navigator.clipboard.writeText(code).then(() => {
const originalHTML = btn.innerHTML;
btn.innerHTML = '<i class="fas fa-check"></i> Copied!';
btn.classList.add('btn-success');
btn.classList.remove('btn-secondary');
setTimeout(() => {
btn.innerHTML = originalHTML;
btn.classList.remove('btn-success');
btn.classList.add('btn-secondary');
}, 2000);
}).catch(err => {
console.error('Failed to copy:', err);
alert('Failed to copy to clipboard');
});
}
// Theme toggle
function toggleTheme() {
const html = document.documentElement;
const currentTheme = html.getAttribute('data-bs-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
html.setAttribute('data-bs-theme', newTheme);
localStorage.setItem('theme', newTheme);
const icon = document.getElementById('themeIcon');
if (icon) {
icon.className = newTheme === 'dark' ? 'fas fa-moon' : 'fas fa-sun';
}
}
// Load saved theme on page load
window.addEventListener('DOMContentLoaded', () => {
const savedTheme = localStorage.getItem('theme') || 'dark';
document.documentElement.setAttribute('data-bs-theme', savedTheme);
const icon = document.getElementById('themeIcon');
if (icon) {
icon.className = savedTheme === 'dark' ? 'fas fa-moon' : 'fas fa-sun';
}
// Initialize syntax highlighting
if (typeof hljs !== 'undefined') {
hljs.highlightAll();
}
});
// Smooth scroll for navigation links
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({
behavior: 'smooth',
block: 'start',
inline: 'nearest'
});
// Highlight target card briefly
target.classList.add('border-primary');
setTimeout(() => {
target.classList.remove('border-primary');
}, 2000);
}
});
});
});
// Active navigation highlighting on scroll
window.addEventListener('scroll', () => {
const sections = document.querySelectorAll('.endpoint-card');
const navLinks = document.querySelectorAll('.list-group-item');
let current = '';
sections.forEach(section => {
const sectionTop = section.offsetTop;
const sectionHeight = section.clientHeight;
// Check if section is in viewport (with offset for navbar)
if (window.pageYOffset >= sectionTop - 120) {
current = section.getAttribute('id');
}
});
// Update active link
navLinks.forEach(link => {
link.classList.remove('active');
if (link.getAttribute('href') === '#' + current) {
link.classList.add('active');
}
});
});
// Test API endpoint directly from documentation (optional utility)
async function testEndpoint(endpoint, method = 'POST', body = {}) {
try {
const response = await fetch(endpoint, {
method: method,
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(body)
});
const data = await response.text();
console.log('API Response:', data);
return data;
} catch (error) {
console.error('API Error:', error);
}
}
// Quick copy all code snippets (Ctrl+Shift+C)
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.shiftKey && e.key === 'C') {
const allCode = Array.from(document.querySelectorAll('pre code'))
.map(code => code.textContent)
.join('\n\n' + '='.repeat(50) + '\n\n');
navigator.clipboard.writeText(allCode).then(() => {
alert('All code snippets copied to clipboard!');
});
}
});

421
static/js/main.js Normal file
View File

@@ -0,0 +1,421 @@
let allResults = [];
let filteredResults = [];
let selectedFilters = {
countries: new Set(),
asns: new Set(),
owners: new Set()
};
// Theme Management
function toggleTheme() {
const html = document.documentElement;
const currentTheme = html.getAttribute('data-bs-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
html.setAttribute('data-bs-theme', newTheme);
localStorage.setItem('theme', newTheme);
const icon = document.getElementById('themeIcon');
if (icon) {
icon.className = newTheme === 'dark' ? 'fas fa-moon' : 'fas fa-sun';
}
}
// Load saved theme
window.addEventListener('DOMContentLoaded', () => {
const savedTheme = localStorage.getItem('theme') || 'dark';
document.documentElement.setAttribute('data-bs-theme', savedTheme);
const icon = document.getElementById('themeIcon');
if (icon) {
icon.className = savedTheme === 'dark' ? 'fas fa-moon' : 'fas fa-sun';
}
});
// Main Analysis Function
async function analyzeIPs() {
const input = document.getElementById('ipInput').value;
const loading = document.getElementById('loading');
const results = document.getElementById('results');
const error = document.getElementById('error');
if (!input.trim()) {
showError('⚠️ Please paste a list of IP addresses');
return;
}
error.classList.add('d-none');
results.classList.add('d-none');
loading.classList.remove('d-none');
try {
const response = await fetch('/api/analyze', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ ips: input })
});
if (!response.ok) {
throw new Error('Analysis failed - check connection');
}
const data = await response.json();
allResults = data.results;
filteredResults = [...allResults];
displayResults(data);
} catch (e) {
showError('❌ ' + e.message);
} finally {
loading.classList.add('d-none');
}
}
// Display Results
function displayResults(data) {
displayStats(data.stats);
displayFilters(data.stats);
displayTable(filteredResults);
const resultsDiv = document.getElementById('results');
resultsDiv.classList.remove('d-none');
resultsDiv.scrollIntoView({ behavior: 'smooth' });
}
// Display Statistics
function displayStats(stats) {
const statsDiv = document.getElementById('stats');
statsDiv.innerHTML = `
<div class="col-md-4 mb-3">
<div class="card stat-card">
<div class="card-body">
<h6><i class="fas fa-globe text-primary"></i> Countries</h6>
<h3 class="mb-0">${Object.keys(stats.countries).length}</h3>
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card stat-card">
<div class="card-body">
<h6><i class="fas fa-server text-success"></i> ASNs</h6>
<h3 class="mb-0">${Object.keys(stats.asns).length}</h3>
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card stat-card">
<div class="card-body">
<h6><i class="fas fa-map-marker-alt text-info"></i> IP Addresses</h6>
<h3 class="mb-0">${allResults.length}</h3>
</div>
</div>
</div>
`;
}
// Display Filters
function displayFilters(stats) {
// Countries (top 15)
const countries = Object.entries(stats.countries)
.sort((a, b) => b[1] - a[1])
.slice(0, 15);
document.getElementById('countryFilters').innerHTML = countries.map(([country, count]) =>
`<span class="filter-chip" data-type="countries" data-value="${country}"
onclick="toggleFilter('countries', '${country}')">
${country} (${count})
</span>`
).join('');
// ASNs (top 15)
const asns = Object.entries(stats.asns)
.sort((a, b) => b[1] - a[1])
.slice(0, 15);
document.getElementById('asnFilters').innerHTML = asns.map(([asn, count]) =>
`<span class="filter-chip" data-type="asns" data-value="${asn}"
onclick="toggleFilter('asns', '${escapeHtml(asn)}')">
${asn} (${count})
</span>`
).join('');
// Owners (top 15)
const owners = Object.entries(stats.owners)
.sort((a, b) => b[1] - a[1])
.slice(0, 15);
document.getElementById('ownerFilters').innerHTML = owners.map(([owner, count]) => {
const shortOwner = owner.length > 30 ? owner.substring(0, 30) + '...' : owner;
return `<span class="filter-chip" data-type="owners" data-value="${owner}"
onclick="toggleFilter('owners', '${escapeHtml(owner)}')" title="${owner}">
${shortOwner} (${count})
</span>`;
}).join('');
}
// Toggle Filter
function toggleFilter(type, value) {
if (selectedFilters[type].has(value)) {
selectedFilters[type].delete(value);
} else {
selectedFilters[type].add(value);
}
document.querySelectorAll(`[data-type="${type}"][data-value="${value}"]`).forEach(chip => {
chip.classList.toggle('active');
});
applyFilters();
}
// Clear All Filters
function clearFilters() {
selectedFilters = { countries: new Set(), asns: new Set(), owners: new Set() };
document.querySelectorAll('.filter-chip').forEach(chip => chip.classList.remove('active'));
applyFilters();
}
// Apply Filters
function applyFilters() {
const hasFilters = selectedFilters.countries.size > 0 ||
selectedFilters.asns.size > 0 ||
selectedFilters.owners.size > 0;
if (!hasFilters) {
filteredResults = [...allResults];
} else {
filteredResults = allResults.filter(item => {
return (selectedFilters.countries.size === 0 || selectedFilters.countries.has(item.country)) &&
(selectedFilters.asns.size === 0 || selectedFilters.asns.has(item.asn)) &&
(selectedFilters.owners.size === 0 || selectedFilters.owners.has(item.owner));
});
}
displayTable(filteredResults);
updateFilterCount();
}
// Update Filter Count
function updateFilterCount() {
const total = selectedFilters.countries.size + selectedFilters.asns.size + selectedFilters.owners.size;
const countEl = document.getElementById('filterCount');
if (countEl) {
countEl.textContent = total > 0
? `Active filters: ${total} | Results: ${filteredResults.length}/${allResults.length}`
: '';
}
}
// Display Table
function displayTable(results) {
const tbody = document.getElementById('tableBody');
if (!tbody) return;
tbody.innerHTML = results.map((item, idx) => `
<tr>
<td><input type="checkbox" class="form-check-input ip-checkbox" checked></td>
<td class="ip-cell">${item.ip}</td>
<td><span class="badge-custom">${item.asn}</span></td>
<td><small>${escapeHtml(item.owner)}</small></td>
<td>${item.country}</td>
<td><code>${item.network}</code></td>
</tr>
`).join('');
document.getElementById('tableCount').textContent = results.length;
}
// Toggle All Checkboxes
function toggleAll() {
const checked = document.getElementById('selectAll').checked;
document.querySelectorAll('.ip-checkbox').forEach(cb => cb.checked = checked);
}
// Get Selected IPs
function getSelectedIPs() {
const checkboxes = document.querySelectorAll('#tableBody input[type="checkbox"]:checked');
return Array.from(checkboxes).map(cb =>
cb.closest('tr').querySelector('.ip-cell').textContent
);
}
// Show Export Output
function showExport(title, code) {
document.getElementById('exportTitle').textContent = title;
document.getElementById('exportCode').textContent = code;
const output = document.getElementById('exportOutput');
output.classList.remove('d-none');
output.scrollIntoView({ behavior: 'smooth' });
}
// Export Functions
async function exportIPSet() {
const ips = getSelectedIPs();
if (ips.length === 0) {
showError('Please select at least one IP address');
return;
}
const response = await fetch('/api/export/ipset', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ ips: ips, timeout: 86400 })
});
const code = await response.text();
showExport('IPSet Rules', code);
}
async function exportIPTables() {
const ips = getSelectedIPs();
if (ips.length === 0) return;
const response = await fetch('/api/export/iptables', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ ips: ips })
});
const code = await response.text();
showExport('iptables Rules', code);
}
async function exportNginx() {
const ips = getSelectedIPs();
if (ips.length === 0) return;
const response = await fetch('/api/export/nginx', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ ips: ips })
});
const code = await response.text();
showExport('Nginx Configuration', code);
}
async function exportApache() {
const ips = getSelectedIPs();
if (ips.length === 0) return;
const response = await fetch('/api/export/apache', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ ips: ips })
});
const code = await response.text();
showExport('Apache Configuration', code);
}
async function exportFirewalld() {
const ips = getSelectedIPs();
if (ips.length === 0) return;
const response = await fetch('/api/export/firewalld', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ ips: ips })
});
const code = await response.text();
showExport('Firewalld Rules', code);
}
async function exportMikrotik() {
const ips = getSelectedIPs();
if (ips.length === 0) return;
const response = await fetch('/api/export/mikrotik', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ ips: ips })
});
const code = await response.text();
showExport('MikroTik Configuration', code);
}
async function exportCIDR() {
const response = await fetch('/api/export/cidr', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ results: filteredResults })
});
const code = await response.text();
showExport('CIDR Network List', code);
}
async function exportCSV() {
const response = await fetch('/api/export/csv', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ results: filteredResults })
});
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `ip-analysis-${new Date().toISOString().split('T')[0]}.csv`;
a.click();
}
// Copy to Clipboard
function copyToClipboard() {
const code = document.getElementById('exportCode').textContent;
navigator.clipboard.writeText(code).then(() => {
const btn = event.target.closest('button');
const originalHTML = btn.innerHTML;
btn.innerHTML = '<i class="fas fa-check"></i> Copied!';
btn.classList.add('btn-success');
btn.classList.remove('btn-secondary');
setTimeout(() => {
btn.innerHTML = originalHTML;
btn.classList.remove('btn-success');
btn.classList.add('btn-secondary');
}, 2000);
}).catch(err => {
showError('Failed to copy: ' + err);
});
}
// Utility Functions
function showError(message) {
const error = document.getElementById('error');
if (error) {
error.textContent = message;
error.classList.remove('d-none');
setTimeout(() => error.classList.add('d-none'), 5000);
}
}
function escapeHtml(text) {
const map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
return String(text).replace(/[&<>"']/g, m => map[m]);
}
// Keyboard Shortcuts
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 'Enter' && document.getElementById('ipInput')) {
analyzeIPs();
}
});
// Smooth scroll for anchor links
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
});
});
});