first commit
This commit is contained in:
294
static/css/style.css
Normal file
294
static/css/style.css
Normal file
@@ -0,0 +1,294 @@
|
||||
/* IP WHOIS Analyzer Pro - Custom Styles */
|
||||
|
||||
:root[data-bs-theme="dark"] {
|
||||
--accent: #00d4ff;
|
||||
--accent-rgb: 0, 212, 255;
|
||||
--accent-hover: #00b8e6;
|
||||
}
|
||||
|
||||
:root[data-bs-theme="light"] {
|
||||
--accent: #0d6efd;
|
||||
--accent-rgb: 13, 110, 253;
|
||||
--accent-hover: #0b5ed7;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--bs-body-bg);
|
||||
min-height: 100vh;
|
||||
padding-bottom: 3rem;
|
||||
}
|
||||
|
||||
/* Header Gradient */
|
||||
.header-gradient {
|
||||
background: linear-gradient(135deg, var(--accent) 0%, #667eea 100%);
|
||||
padding: 3rem 2rem;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 2rem;
|
||||
box-shadow: 0 10px 30px rgba(var(--accent-rgb), 0.2);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header-gradient::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
right: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
|
||||
animation: pulse 15s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { transform: scale(1); opacity: 0.5; }
|
||||
50% { transform: scale(1.1); opacity: 0.8; }
|
||||
}
|
||||
|
||||
.header-gradient h1,
|
||||
.header-gradient p {
|
||||
color: white;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Cards */
|
||||
.card {
|
||||
border: 1px solid var(--bs-border-color);
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
transition: transform 0.2s ease, box-shadow 0.3s ease;
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: linear-gradient(135deg, rgba(var(--accent-rgb), 0.1) 0%, rgba(102,126,234,0.1) 100%);
|
||||
border-left: 4px solid var(--accent);
|
||||
}
|
||||
|
||||
.endpoint-card {
|
||||
border-left: 4px solid var(--accent);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
/* Table */
|
||||
.table-hover tbody tr:hover {
|
||||
background-color: rgba(var(--accent-rgb), 0.1);
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.ip-cell {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-weight: 600;
|
||||
color: var(--accent);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
/* Badges */
|
||||
.badge-custom {
|
||||
background: rgba(var(--accent-rgb), 0.2);
|
||||
color: var(--accent);
|
||||
padding: 0.4rem 0.8rem;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.method-badge {
|
||||
font-weight: bold;
|
||||
padding: 0.4rem 0.8rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.method-post {
|
||||
background: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.method-get {
|
||||
background: #17a2b8;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Filter chips */
|
||||
.filter-chip {
|
||||
display: inline-block;
|
||||
background: var(--bs-secondary-bg);
|
||||
color: var(--bs-body-color);
|
||||
border: 1px solid var(--bs-border-color);
|
||||
padding: 0.4rem 1rem;
|
||||
border-radius: 20px;
|
||||
margin: 0.2rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.filter-chip:hover {
|
||||
transform: scale(1.05);
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 2px 8px rgba(var(--accent-rgb), 0.3);
|
||||
}
|
||||
|
||||
.filter-chip.active {
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
border-color: var(--accent);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Export section */
|
||||
.export-section {
|
||||
background: var(--bs-secondary-bg);
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
margin-top: 1rem;
|
||||
border: 1px solid var(--bs-border-color);
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
code {
|
||||
background: var(--bs-secondary-bg);
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.9em;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
pre {
|
||||
position: relative;
|
||||
background: var(--bs-tertiary-bg);
|
||||
border: 1px solid var(--bs-border-color);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
z-index: 10;
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Theme toggle */
|
||||
.theme-toggle {
|
||||
position: fixed;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
z-index: 1050;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
/* Spinner */
|
||||
.spinner-border {
|
||||
border-color: rgba(var(--accent-rgb), 0.2);
|
||||
border-top-color: var(--accent);
|
||||
}
|
||||
|
||||
/* List group */
|
||||
.list-group-item {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.list-group-item:hover {
|
||||
background: rgba(var(--accent-rgb), 0.1);
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
/* Form controls */
|
||||
textarea.form-control,
|
||||
input.form-control {
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
textarea.form-control:focus,
|
||||
input.form-control:focus {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 0.25rem rgba(var(--accent-rgb), 0.25);
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Stats cards */
|
||||
.stat-card h3 {
|
||||
font-weight: 700;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.stat-card h6 {
|
||||
text-transform: uppercase;
|
||||
font-size: 0.85rem;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
/* Scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--bs-tertiary-bg);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--accent);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--accent-hover);
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.alert {
|
||||
border-left: 4px solid;
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
border-left-color: var(--accent);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.header-gradient h1 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
.header-gradient p {
|
||||
font-size: 1rem;
|
||||
}
|
||||
.filter-chip {
|
||||
font-size: 0.85rem;
|
||||
padding: 0.3rem 0.8rem;
|
||||
}
|
||||
}
|
||||
129
static/js/api.js
Normal file
129
static/js/api.js
Normal 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
421
static/js/main.js
Normal 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 = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
};
|
||||
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' });
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user