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' } ] }; 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 = `
${selectedOption.dataset.description}
`; 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 = 'Database ready (Last update: ' + formatDate(data.last_update) + ')'; } else if (data.needs_update) { statusDiv.className = 'alert alert-warning mb-0'; statusDiv.innerHTML = 'Database needs update '; } else { statusDiv.className = 'alert alert-info mb-0'; statusDiv.innerHTML = 'Downloading database...'; } } }) .catch(error => { console.error('Error:', error); }); } function updateDatabase() { const statusDiv = document.getElementById('dbStatus'); statusDiv.className = 'alert alert-info mb-0'; statusDiv.innerHTML = '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 = 'Database updated successfully'; setTimeout(checkDatabaseStatus, 2000); } else { statusDiv.className = 'alert alert-danger mb-0'; statusDiv.innerHTML = '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 = ` Lightning fast! Config loaded from Redis cache in <100ms Generated: ${new Date(generatedAt).toLocaleString()} `; 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 = ' From Cache'; showCacheBadge(true, generatedAt); } else { cacheIndicator.innerHTML = ' Fresh'; } 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 = ' From Cache'; showCacheBadge(true, result.generated_at); } else { cacheIndicator.innerHTML = ' Fresh'; } 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); }