primera implementación asíncrona con workers

This commit is contained in:
DANYDHSV 2026-03-26 15:50:43 -06:00
parent c10f2a44fe
commit a8dd6a8f38
2 changed files with 1728 additions and 97 deletions

File diff suppressed because it is too large Load Diff

View File

@ -2932,29 +2932,100 @@ if ($systemUserId) {
/** /**
* Validación progresiva con búsqueda de sitios (event.ip_validate) * Inserta una fila en la tabla manteniendo el orden por último octeto de IP
* Valida cada IP antes de mostrarla en la tabla * Las filas flotantes y admin van primero (antes de las de cliente)
*/ */
async function runProgressiveValidation(clientIps, pingLimit, shouldVerifyPing) { function insertRowSorted(ip, statusText, statusClass) {
console.log(`Iniciando validación progresiva de ${clientIps.length} IPs`); const ipTypeLabel = getIpType(ip);
console.log(`Ping habilitado: ${shouldVerifyPing}, Límite: ${pingLimit}`); const hideAdminCheckbox = document.getElementById('hideAdmin');
const isHidden = hideAdminCheckbox && hideAdminCheckbox.checked && ipTypeLabel === 'Administración';
// Determinar IPs a validar (inicialmente todas, filtramos dinámicamente) const row = document.createElement('tr');
let ipsToProcess = [...clientIps]; row.id = `row-${ip.replace(/\./g, '-')}`;
if (isHidden) row.classList.add('hidden-row');
if (ipTypeLabel === 'Administración') row.classList.add('admin-row');
if (statusClass === 'floating') row.classList.add('floating-row');
row.classList.add('client-result-row');
console.log(`Validando hasta encontrar ${pingLimit > 0 ? pingLimit : 'todas'} IPs disponibles entre ${clientIps.length} candidatos`); // Determinar el badge de tipo de IP
let ipTypeBadge = '';
// Mostrar mensaje de progreso según cantidad de IPs if (statusClass === 'floating') {
let progressMessage = ''; ipTypeBadge = '<span class="ip-type-badge ip-type-floating">IP Flotante</span>';
if (shouldVerifyPing && (pingLimit === 0 || pingLimit >= 20)) { } else if (ipTypeLabel === 'Administración') {
progressMessage = 'Validando todas las IP\'s por ping, esto puede llevar varios minutos. Espere por favor...'; ipTypeBadge = '<span class="ip-type-badge ip-type-admin">Administración</span>';
} else if (shouldVerifyPing) { } else if (statusClass === 'used' || statusClass === 'conflict' || statusText.includes('En uso')) {
progressMessage = 'Validando IP\'s por ping. Espere por favor...'; ipTypeBadge = '<span class="ip-type-badge ip-type-admin">No disponible</span>';
} else { } else {
progressMessage = 'Validando IP\'s con búsqueda de sitios. Espere por favor...'; ipTypeBadge = '<span class="ip-type-badge ip-type-client">Cliente</span>';
}
row.innerHTML = `
<td class="row-number">0</td>
<td>
<div class="ip-cell-mobile">
<span class="ip-address">${ip}</span>
${ipTypeBadge}
</div>
</td>
<td id="status-${ip.replace(/\./g, '-')}">
<span class="status-badge status-${statusClass}">${statusText}</span>
</td>
<td>
<button class="btn-copy" onclick="copyToClipboard('${ip}', this)">
📋 Copiar
</button>
</td>
`;
// Encontrar posición correcta entre las filas de resultado de cliente
const newOctet = parseInt(ip.split('.')[3]);
const existingRows = ipTableBody.querySelectorAll('tr.client-result-row');
let insertBefore = null;
for (const existingRow of existingRows) {
const existingIp = existingRow.querySelector('.ip-address');
if (existingIp) {
const existingOctet = parseInt(existingIp.textContent.split('.')[3]);
if (existingOctet > newOctet) {
insertBefore = existingRow;
break;
}
}
}
if (insertBefore) {
ipTableBody.insertBefore(row, insertBefore);
} else {
ipTableBody.appendChild(row);
}
// Renumerar filas visibles
renumberVisibleRows();
return row;
}
/**
* Validación paralela con pool de workers
* Usa N workers concurrentes para validar IPs simultáneamente
*/
async function runProgressiveValidation(clientIps, pingLimit, shouldVerifyPing) {
const WORKER_COUNT = 5;
console.log(`Iniciando validación PARALELA de ${clientIps.length} IPs con ${WORKER_COUNT} workers`);
console.log(`Ping habilitado: ${shouldVerifyPing}, Límite: ${pingLimit}`);
let ipsToProcess = [...clientIps];
// Mostrar mensaje de progreso
let progressMessage = '';
if (shouldVerifyPing && (pingLimit === 0 || pingLimit >= 20)) {
progressMessage = 'Validando todas las IP\'s (modo paralelo). Espere por favor...';
} else if (shouldVerifyPing) {
progressMessage = 'Validando IP\'s por ping (modo paralelo). Espere por favor...';
} else {
progressMessage = 'Validando IP\'s con búsqueda de sitios (modo paralelo). Espere por favor...';
} }
// Usar autohide = false explícitamente y agregar spinner HTML al mensaje
const spinnerHtml = '<span class="loading-spinner-small"></span> '; const spinnerHtml = '<span class="loading-spinner-small"></span> ';
showError(spinnerHtml + progressMessage, 'info', false); showError(spinnerHtml + progressMessage, 'info', false);
@ -2964,33 +3035,29 @@ if ($systemUserId) {
cancelBtn.style.display = 'inline-flex'; cancelBtn.style.display = 'inline-flex';
} }
// Estado compartido entre workers (thread-safe en JS por event loop)
let nextIndex = 0;
let foundAvailableCount = 0; let foundAvailableCount = 0;
let processedCount = 0;
const totalIps = ipsToProcess.length;
// Procesar cada IP secuencialmente // Función de un worker individual
for (const ip of ipsToProcess) { async function worker(workerId) {
// Verificar si debemos detenernos por límite (Smart Limit) while (nextIndex < totalIps) {
if (pingLimit > 0 && shouldVerifyPing && foundAvailableCount >= pingLimit) { // Verificar cancelación
console.log(`Se alcanzó el límite de ${pingLimit} IPs disponibles. Deteniendo búsqueda.`); if (verificationCancelled) break;
break;
}
// Verificar si el usuario canceló // Verificar límite (con ping habilitado)
if (verificationCancelled) { if (pingLimit > 0 && shouldVerifyPing && foundAvailableCount >= pingLimit) break;
console.log('Validación cancelada por el usuario');
showError('Validación cancelada. Mostrando resultados parciales.', 'warning');
break;
}
// NO renderizar aún, primero validar // Tomar siguiente IP del pool compartido
const currentIndex = nextIndex++;
if (currentIndex >= totalIps) break;
// Scroll al final de la tabla const ip = ipsToProcess[currentIndex];
const tableContainer = document.querySelector('.table-container');
if (tableContainer) {
tableContainer.scrollTop = tableContainer.scrollHeight;
}
try { try {
// Llamar a validación de IP con búsqueda de sitios // Validar IP con búsqueda de sitios
const formData = new FormData(); const formData = new FormData();
formData.append('action', 'validate'); formData.append('action', 'validate');
formData.append('ip', ip); formData.append('ip', ip);
@ -3001,46 +3068,59 @@ if ($systemUserId) {
}); });
const data = await response.json(); const data = await response.json();
processedCount++;
// Actualizar progreso cada 5 IPs validadas
if (processedCount % 5 === 0) {
showError(
spinnerHtml + `Validando IP's... ${processedCount}/${totalIps} procesadas, ${foundAvailableCount} disponibles`,
'info', false
);
}
if (data.success && !data.in_use) { if (data.success && !data.in_use) {
// IP disponible según site search // IP disponible según site search
if (shouldVerifyPing) { if (shouldVerifyPing) {
// Si ping está habilitado, renderizar y verificar con ping // Insertar en tabla con estado "verificando ping"
renderRow(ip, '⏳ Verificando ping...', 'verifying'); insertRowSorted(ip, '⏳ Verificando ping...', 'verifying');
// Verificar ping y esperar resultado // Verificar ping
const verifyResult = await verifyBatch([ip]); const verifyResult = await verifyBatch([ip]);
// Si el resultado confirma que NO responde (not_responding), entonces es válida
if (verifyResult && verifyResult.not_responding && verifyResult.not_responding.includes(ip)) { if (verifyResult && verifyResult.not_responding && verifyResult.not_responding.includes(ip)) {
foundAvailableCount++; foundAvailableCount++;
} }
// Si resonding (conflicto), NO incrementamos contador y seguimos buscando
} else { } else {
// Si ping NO está habilitado, renderizar como disponible // Sin ping: insertar directamente como disponible
renderRow(ip, '✅ Disponible', 'available'); insertRowSorted(ip, '✅ Disponible', 'available');
foundAvailableCount++; // Contamos como encontrada foundAvailableCount++;
} }
} else { } else {
// IP en uso según site search - NO RENDERIZAR // IP en uso — no renderizar
// Solo agregar log en consola console.log(`[W${workerId}] IP ${ip} filtrada (en uso)`);
console.log(`IP ${ip} filtrada (en uso en UISP)`);
} }
} catch (error) { } catch (error) {
console.error(`Error validando IP ${ip}:`, error); processedCount++;
// En caso de error, renderizar con error console.error(`[W${workerId}] Error validando IP ${ip}:`, error);
renderRow(ip, '❌ Error validación', 'error'); insertRowSorted(ip, '❌ Error validación', 'error');
} }
} }
}
// Lanzar N workers en paralelo
const workerPromises = [];
for (let i = 0; i < Math.min(WORKER_COUNT, totalIps); i++) {
workerPromises.push(worker(i + 1));
}
// Esperar a que todos terminen
await Promise.all(workerPromises);
// Ocultar botón de cancelar // Ocultar botón de cancelar
if (cancelBtn) { if (cancelBtn) {
cancelBtn.style.display = 'none'; cancelBtn.style.display = 'none';
} }
// Actualizar estadísticas con los números reales // Actualizar estadísticas finales
// Contar IPs disponibles (status-available) y en uso (status-used, status-conflict, o status-floating)
const allRows = ipTableBody.querySelectorAll('tr'); const allRows = ipTableBody.querySelectorAll('tr');
let availableIpsCount = 0; let availableIpsCount = 0;
let usedIpsCount = 0; let usedIpsCount = 0;
@ -3058,7 +3138,6 @@ if ($systemUserId) {
} }
}); });
// Actualizar contadores en la UI
availableCount.textContent = availableIpsCount; availableCount.textContent = availableIpsCount;
usedCount.textContent = usedIpsCount; usedCount.textContent = usedIpsCount;
@ -3069,7 +3148,7 @@ if ($systemUserId) {
<?php endif; ?> <?php endif; ?>
if (searchBtn) searchBtn.disabled = false; if (searchBtn) searchBtn.disabled = false;
console.log(`Validación completada. ${availableIpsCount} IPs disponibles, ${usedIpsCount} IPs en uso`); console.log(`Validación paralela completada. ${availableIpsCount} IPs disponibles, ${usedIpsCount} IPs en uso`);
showError(`Validación completada. ${availableIpsCount} IPs disponibles encontradas.`, 'success', false); showError(`Validación completada. ${availableIpsCount} IPs disponibles encontradas.`, 'success', false);
} }
@ -3097,18 +3176,16 @@ if ($systemUserId) {
} }
async function runProgressiveVerification(clientIps, limit) { async function runProgressiveVerification(clientIps, limit) {
const WORKER_COUNT = 5;
let ipsToVerify = []; let ipsToVerify = [];
// 1. Filtrar IPs a verificar según el límite
if (limit > 0) { if (limit > 0) {
// Si hay límite, solo tomamos las primeras N
ipsToVerify = clientIps.slice(0, limit); ipsToVerify = clientIps.slice(0, limit);
} else { } else {
// Si límite es 0 (Todas), verificamos todas
ipsToVerify = [...clientIps]; ipsToVerify = [...clientIps];
} }
console.log(`Iniciando verificación progresiva de ${ipsToVerify.length} IPs`); console.log(`Iniciando verificación PARALELA de ${ipsToVerify.length} IPs con ${WORKER_COUNT} workers`);
// Mostrar botón de cancelar // Mostrar botón de cancelar
verificationCancelled = false; verificationCancelled = false;
@ -3116,32 +3193,42 @@ if ($systemUserId) {
cancelBtn.style.display = 'inline-flex'; cancelBtn.style.display = 'inline-flex';
} }
// 2. Procesar una por una (secuencialmente) // Estado compartido
for (const ip of ipsToVerify) { let nextIndex = 0;
// Verificar si el usuario canceló const totalIps = ipsToVerify.length;
if (verificationCancelled) {
console.log('Verificación cancelada por el usuario');
showError('Verificación cancelada. Mostrando resultados parciales.', 'warning');
break;
}
// A. Renderizar fila con estado "Verificando" // Función de un worker individual
renderRow(ip, '⏳ Verificando...', 'verifying'); async function worker(workerId) {
while (nextIndex < totalIps) {
if (verificationCancelled) break;
// Scroll al final de la tabla para seguir el progreso const currentIndex = nextIndex++;
const tableContainer = document.querySelector('.table-container'); if (currentIndex >= totalIps) break;
tableContainer.scrollTop = tableContainer.scrollHeight;
// B. Verificar (lote de 1) const ip = ipsToVerify[currentIndex];
// Insertar fila con estado "Verificando" en posición ordenada
insertRowSorted(ip, '⏳ Verificando...', 'verifying');
// Verificar con ping
await verifyBatch([ip]); await verifyBatch([ip]);
} }
}
// Lanzar N workers en paralelo
const workerPromises = [];
for (let i = 0; i < Math.min(WORKER_COUNT, totalIps); i++) {
workerPromises.push(worker(i + 1));
}
await Promise.all(workerPromises);
// Ocultar botón de cancelar al finalizar // Ocultar botón de cancelar al finalizar
if (cancelBtn) { if (cancelBtn) {
cancelBtn.style.display = 'none'; cancelBtn.style.display = 'none';
} }
// Actualizar estadísticas con los números reales // Actualizar estadísticas
const allRows = ipTableBody.querySelectorAll('tr'); const allRows = ipTableBody.querySelectorAll('tr');
let availableIpsCount = 0; let availableIpsCount = 0;
let usedIpsCount = 0; let usedIpsCount = 0;
@ -3159,11 +3246,8 @@ if ($systemUserId) {
} }
}); });
// Actualizar contadores en la UI
availableCount.textContent = availableIpsCount; availableCount.textContent = availableIpsCount;
usedCount.textContent = usedIpsCount; usedCount.textContent = usedIpsCount;
// NOTA: Las IPs que exceden el límite NO se renderizan, cumpliendo el requerimiento.
} }
// Event listener para botón de cancelar // Event listener para botón de cancelar