Files
geoip_block_generator/static/js/app.js
Mateusz Gruszczyński bf02af0192 haproxy map
2026-02-23 13:10:16 +01:00

381 lines
13 KiB
JavaScript

const BASE_URL = window.location.origin;
const APP_VARIANTS = {
'raw-cidr': [
{
value: 'txt',
text: 'Plain Text (.txt)',
description: 'Simple list of CIDR blocks, one per line'
},
{
value: 'csv',
text: 'CSV Format (.csv)',
description: 'Structured CSV with country codes and networks'
}
],
nginx: [
{
value: 'deny',
text: 'Deny Directives',
description: 'Simple and fast. Works everywhere. Recommended for large lists.',
recommended: true
},
{
value: 'geo',
text: 'Geo Module',
description: 'Fast with native CIDR support. Requires http_geo_module compiled in nginx.'
},
{
value: 'map',
text: 'Map Module (regex)',
description: 'Slow with 10k+ rules. Uses regex patterns. Not recommended for production.',
warning: true
}
],
apache: [
{
value: '24',
text: 'Apache 2.4 (Require)',
description: 'Modern Apache 2.4+ syntax using Require directives'
},
{
value: '22',
text: 'Apache 2.2 (Allow/Deny)',
description: 'Legacy Apache 2.2 syntax with Allow/Deny directives'
}
],
haproxy: [
{
value: 'acl',
text: 'ACL Rules',
description: 'Native HAProxy ACL rules for frontend/backend blocking'
},
{
value: 'lua',
text: 'Lua Script',
description: 'Lua-based blocking script for advanced HAProxy setups'
},
{
value: 'map',
text: 'Map File',
description: 'HAProxy map format (for use with -m ip / map files)'
}
]
};
document.addEventListener('DOMContentLoaded', function() {
updateVariants();
checkDatabaseStatus();
});
function updateVariants() {
const appType = document.getElementById('appType').value;
const variantSelect = document.getElementById('appVariant');
const variantSection = document.getElementById('variantSection');
const variants = APP_VARIANTS[appType] || [];
variantSelect.innerHTML = '';
variantSection.style.display = 'block';
variants.forEach(variant => {
const option = document.createElement('option');
option.value = variant.value;
option.textContent = variant.text;
option.dataset.description = variant.description || '';
option.dataset.warning = variant.warning || false;
option.dataset.recommended = variant.recommended || false;
variantSelect.appendChild(option);
});
updateVariantDescription();
}
function updateVariantDescription() {
const variantSelect = document.getElementById('appVariant');
const descriptionDiv = document.getElementById('variantDescription');
if (!descriptionDiv) return;
const selectedOption = variantSelect.options[variantSelect.selectedIndex];
if (selectedOption && selectedOption.dataset.description) {
const isWarning = selectedOption.dataset.warning === 'true';
const isRecommended = selectedOption.dataset.recommended === 'true';
let alertClass = 'alert-info';
let borderClass = 'border-info';
let icon = 'fa-info-circle';
if (isRecommended) {
alertClass = 'alert-success';
borderClass = 'border-success';
icon = 'fa-check-circle';
} else if (isWarning) {
alertClass = 'alert-warning';
borderClass = 'border-warning';
icon = 'fa-exclamation-triangle';
}
descriptionDiv.innerHTML = `
<div class="alert ${alertClass} border-start border-4 ${borderClass} mb-0 py-2">
<small>
<i class="fas ${icon} me-2"></i>
${selectedOption.dataset.description}
</small>
</div>
`;
descriptionDiv.style.display = 'block';
} else {
descriptionDiv.style.display = 'none';
}
}
function checkDatabaseStatus() {
fetch(BASE_URL + '/api/database/status')
.then(response => response.json())
.then(data => {
const statusDiv = document.getElementById('dbStatus');
if (data.success) {
if (data.exists && !data.needs_update) {
statusDiv.className = 'alert alert-success mb-0';
statusDiv.innerHTML = '<i class="fas fa-check-circle me-2"></i>Database ready (Last update: ' + formatDate(data.last_update) + ')';
} else if (data.needs_update) {
statusDiv.className = 'alert alert-warning mb-0';
statusDiv.innerHTML = '<i class="fas fa-exclamation-triangle me-2"></i>Database needs update <button class="btn btn-sm btn-warning ms-2" onclick="updateDatabase()">Update Now</button>';
} else {
statusDiv.className = 'alert alert-info mb-0';
statusDiv.innerHTML = '<i class="fas fa-download me-2"></i>Downloading database...';
}
}
})
.catch(error => {
console.error('Error:', error);
});
}
function updateDatabase() {
const statusDiv = document.getElementById('dbStatus');
statusDiv.className = 'alert alert-info mb-0';
statusDiv.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Updating database...';
fetch(BASE_URL + '/api/database/update', { method: 'POST' })
.then(response => response.json())
.then(data => {
if (data.success) {
statusDiv.className = 'alert alert-success mb-0';
statusDiv.innerHTML = '<i class="fas fa-check-circle me-2"></i>Database updated successfully';
setTimeout(checkDatabaseStatus, 2000);
} else {
statusDiv.className = 'alert alert-danger mb-0';
statusDiv.innerHTML = '<i class="fas fa-times-circle me-2"></i>Update failed: ' + data.error;
}
});
}
function selectAll() {
const checkboxes = document.querySelectorAll('input[name="countries"]');
checkboxes.forEach(cb => cb.checked = true);
}
function deselectAll() {
const checkboxes = document.querySelectorAll('input[name="countries"]');
checkboxes.forEach(cb => cb.checked = false);
}
function formatDate(dateString) {
if (!dateString) return 'Never';
try {
const date = new Date(dateString);
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
} catch (e) {
return dateString;
}
}
function copyToClipboard() {
const content = document.getElementById('previewContent').textContent;
navigator.clipboard.writeText(content).then(() => {
showResult('Copied to clipboard!', 'success');
}).catch(err => {
const textarea = document.createElement('textarea');
textarea.value = content;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand('copy');
showResult('Copied to clipboard!', 'success');
} catch (e) {
showResult('Failed to copy to clipboard', 'danger');
}
document.body.removeChild(textarea);
});
}
function getFormData() {
const countries = Array.from(document.querySelectorAll('input[name="countries"]:checked'))
.map(input => input.value);
if (countries.length === 0) {
return null;
}
const useCacheCheckbox = document.getElementById('useCache');
return {
countries: countries,
app_type: document.getElementById('appType').value,
app_variant: document.getElementById('appVariant').value,
aggregate: document.getElementById('aggregate').checked,
use_cache: useCacheCheckbox ? useCacheCheckbox.checked : true
};
}
function showCacheBadge(fromCache, generatedAt) {
if (fromCache) {
const badge = document.createElement('div');
badge.className = 'alert alert-success alert-dismissible fade show mt-3';
badge.innerHTML = `
<i class="fas fa-bolt me-2"></i>
<strong>Lightning fast!</strong> Config loaded from Redis cache in &lt;100ms
<small class="d-block mt-1">Generated: ${new Date(generatedAt).toLocaleString()}</small>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
const container = document.querySelector('.container > .row > .col-lg-10');
container.insertBefore(badge, container.firstChild);
setTimeout(() => {
badge.classList.remove('show');
setTimeout(() => badge.remove(), 150);
}, 5000);
}
}
async function previewConfiguration() {
const formData = getFormData();
if (!formData) {
showResult('Please select at least one country to continue', 'warning');
return;
}
showProgress();
try {
const endpoint = formData.app_type === 'raw-cidr'
? '/api/generate/raw'
: '/api/generate/preview';
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData)
});
hideProgress();
if (!response.ok) {
const errorData = await response.json().catch(() => ({ error: 'Unknown error' }));
showResult('Error: ' + (errorData.error || 'Request failed'), 'danger');
return;
}
if (formData.app_type === 'raw-cidr') {
const text = await response.text();
const fromCache = response.headers.get('X-From-Cache') === 'true';
const generatedAt = response.headers.get('X-Generated-At');
const contentDisposition = response.headers.get('Content-Disposition');
let filename = 'blocklist.txt';
if (contentDisposition) {
const matches = /filename="?(.+)"?/.exec(contentDisposition);
if (matches) filename = matches[1];
}
document.getElementById('previewContent').textContent = text;
const cacheIndicator = document.getElementById('cacheIndicator');
if (fromCache) {
cacheIndicator.innerHTML = '<span class="badge bg-success ms-2"><i class="fas fa-bolt"></i> From Cache</span>';
showCacheBadge(true, generatedAt);
} else {
cacheIndicator.innerHTML = '<span class="badge bg-info ms-2"><i class="fas fa-sync"></i> Fresh</span>';
}
window.lastGeneratedConfig = text;
window.lastGeneratedFilename = filename;
const modal = new bootstrap.Modal(document.getElementById('previewModal'));
modal.show();
} else {
const result = await response.json();
if (result.success) {
document.getElementById('previewContent').textContent = result.config;
const cacheIndicator = document.getElementById('cacheIndicator');
if (result.from_cache) {
cacheIndicator.innerHTML = '<span class="badge bg-success ms-2"><i class="fas fa-bolt"></i> From Cache</span>';
showCacheBadge(true, result.generated_at);
} else {
cacheIndicator.innerHTML = '<span class="badge bg-info ms-2"><i class="fas fa-sync"></i> Fresh</span>';
}
if (result.stats) {
const statsText = `${result.stats.countries} countries, ${result.stats.total_networks.toLocaleString()} networks`;
document.getElementById('previewStats').textContent = statsText;
}
window.lastGeneratedConfig = result.config;
window.currentStats = result.stats;
const modal = new bootstrap.Modal(document.getElementById('previewModal'));
modal.show();
} else {
showResult(result.error || 'An error occurred while generating the preview', 'danger');
}
}
} catch (error) {
hideProgress();
showResult('Network error: ' + error.message, 'danger');
console.error('Preview error:', error);
}
}
async function downloadFromPreview() {
const formData = getFormData();
if (!formData) {
showResult('Please select at least one country', 'warning');
return;
}
const modal = bootstrap.Modal.getInstance(document.getElementById('previewModal'));
if (modal) {
modal.hide();
}
await downloadConfiguration(formData);
}