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)
* Valida cada IP antes de mostrarla en la tabla
* Inserta una fila en la tabla manteniendo el orden por último octeto de IP
* Las filas flotantes y admin van primero (antes de las de cliente)
*/
async function runProgressiveValidation(clientIps, pingLimit, shouldVerifyPing) {
console.log(`Iniciando validación progresiva de ${clientIps.length} IPs`);
console.log(`Ping habilitado: ${shouldVerifyPing}, Límite: ${pingLimit}`);
function insertRowSorted(ip, statusText, statusClass) {
const ipTypeLabel = getIpType(ip);
const hideAdminCheckbox = document.getElementById('hideAdmin');
const isHidden = hideAdminCheckbox && hideAdminCheckbox.checked && ipTypeLabel === 'Administración';
// Determinar IPs a validar (inicialmente todas, filtramos dinámicamente)
let ipsToProcess = [...clientIps];
const row = document.createElement('tr');
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`);
// Mostrar mensaje de progreso según cantidad de IPs
let progressMessage = '';
if (shouldVerifyPing && (pingLimit === 0 || pingLimit >= 20)) {
progressMessage = 'Validando todas las IP\'s por ping, esto puede llevar varios minutos. Espere por favor...';
} else if (shouldVerifyPing) {
progressMessage = 'Validando IP\'s por ping. Espere por favor...';
// Determinar el badge de tipo de IP
let ipTypeBadge = '';
if (statusClass === 'floating') {
ipTypeBadge = '<span class="ip-type-badge ip-type-floating">IP Flotante</span>';
} else if (ipTypeLabel === 'Administración') {
ipTypeBadge = '<span class="ip-type-badge ip-type-admin">Administración</span>';
} else if (statusClass === 'used' || statusClass === 'conflict' || statusText.includes('En uso')) {
ipTypeBadge = '<span class="ip-type-badge ip-type-admin">No disponible</span>';
} 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> ';
showError(spinnerHtml + progressMessage, 'info', false);
@ -2964,33 +3035,29 @@ if ($systemUserId) {
cancelBtn.style.display = 'inline-flex';
}
// Estado compartido entre workers (thread-safe en JS por event loop)
let nextIndex = 0;
let foundAvailableCount = 0;
let processedCount = 0;
const totalIps = ipsToProcess.length;
// Procesar cada IP secuencialmente
for (const ip of ipsToProcess) {
// Verificar si debemos detenernos por límite (Smart Limit)
if (pingLimit > 0 && shouldVerifyPing && foundAvailableCount >= pingLimit) {
console.log(`Se alcanzó el límite de ${pingLimit} IPs disponibles. Deteniendo búsqueda.`);
break;
}
// Función de un worker individual
async function worker(workerId) {
while (nextIndex < totalIps) {
// Verificar cancelación
if (verificationCancelled) break;
// Verificar si el usuario canceló
if (verificationCancelled) {
console.log('Validación cancelada por el usuario');
showError('Validación cancelada. Mostrando resultados parciales.', 'warning');
break;
}
// Verificar límite (con ping habilitado)
if (pingLimit > 0 && shouldVerifyPing && foundAvailableCount >= pingLimit) 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 tableContainer = document.querySelector('.table-container');
if (tableContainer) {
tableContainer.scrollTop = tableContainer.scrollHeight;
}
const ip = ipsToProcess[currentIndex];
try {
// Llamar a validación de IP con búsqueda de sitios
// Validar IP con búsqueda de sitios
const formData = new FormData();
formData.append('action', 'validate');
formData.append('ip', ip);
@ -3001,46 +3068,59 @@ if ($systemUserId) {
});
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) {
// IP disponible según site search
if (shouldVerifyPing) {
// Si ping está habilitado, renderizar y verificar con ping
renderRow(ip, '⏳ Verificando ping...', 'verifying');
// Insertar en tabla con estado "verificando ping"
insertRowSorted(ip, '⏳ Verificando ping...', 'verifying');
// Verificar ping y esperar resultado
// Verificar ping
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)) {
foundAvailableCount++;
}
// Si resonding (conflicto), NO incrementamos contador y seguimos buscando
} else {
// Si ping NO está habilitado, renderizar como disponible
renderRow(ip, '✅ Disponible', 'available');
foundAvailableCount++; // Contamos como encontrada
// Sin ping: insertar directamente como disponible
insertRowSorted(ip, '✅ Disponible', 'available');
foundAvailableCount++;
}
} else {
// IP en uso según site search - NO RENDERIZAR
// Solo agregar log en consola
console.log(`IP ${ip} filtrada (en uso en UISP)`);
// IP en uso — no renderizar
console.log(`[W${workerId}] IP ${ip} filtrada (en uso)`);
}
} catch (error) {
console.error(`Error validando IP ${ip}:`, error);
// En caso de error, renderizar con error
renderRow(ip, '❌ Error validación', 'error');
processedCount++;
console.error(`[W${workerId}] Error validando IP ${ip}:`, 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
if (cancelBtn) {
cancelBtn.style.display = 'none';
}
// Actualizar estadísticas con los números reales
// Contar IPs disponibles (status-available) y en uso (status-used, status-conflict, o status-floating)
// Actualizar estadísticas finales
const allRows = ipTableBody.querySelectorAll('tr');
let availableIpsCount = 0;
let usedIpsCount = 0;
@ -3058,7 +3138,6 @@ if ($systemUserId) {
}
});
// Actualizar contadores en la UI
availableCount.textContent = availableIpsCount;
usedCount.textContent = usedIpsCount;
@ -3069,7 +3148,7 @@ if ($systemUserId) {
<?php endif; ?>
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);
}
@ -3097,18 +3176,16 @@ if ($systemUserId) {
}
async function runProgressiveVerification(clientIps, limit) {
const WORKER_COUNT = 5;
let ipsToVerify = [];
// 1. Filtrar IPs a verificar según el límite
if (limit > 0) {
// Si hay límite, solo tomamos las primeras N
ipsToVerify = clientIps.slice(0, limit);
} else {
// Si límite es 0 (Todas), verificamos todas
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
verificationCancelled = false;
@ -3116,32 +3193,42 @@ if ($systemUserId) {
cancelBtn.style.display = 'inline-flex';
}
// 2. Procesar una por una (secuencialmente)
for (const ip of ipsToVerify) {
// Verificar si el usuario canceló
if (verificationCancelled) {
console.log('Verificación cancelada por el usuario');
showError('Verificación cancelada. Mostrando resultados parciales.', 'warning');
break;
}
// Estado compartido
let nextIndex = 0;
const totalIps = ipsToVerify.length;
// A. Renderizar fila con estado "Verificando"
renderRow(ip, '⏳ Verificando...', 'verifying');
// Función de un worker individual
async function worker(workerId) {
while (nextIndex < totalIps) {
if (verificationCancelled) break;
// Scroll al final de la tabla para seguir el progreso
const tableContainer = document.querySelector('.table-container');
tableContainer.scrollTop = tableContainer.scrollHeight;
const currentIndex = nextIndex++;
if (currentIndex >= totalIps) break;
// 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]);
}
}
// 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
if (cancelBtn) {
cancelBtn.style.display = 'none';
}
// Actualizar estadísticas con los números reales
// Actualizar estadísticas
const allRows = ipTableBody.querySelectorAll('tr');
let availableIpsCount = 0;
let usedIpsCount = 0;
@ -3159,11 +3246,8 @@ if ($systemUserId) {
}
});
// Actualizar contadores en la UI
availableCount.textContent = availableIpsCount;
usedCount.textContent = usedIpsCount;
// NOTA: Las IPs que exceden el límite NO se renderizan, cumpliendo el requerimiento.
}
// Event listener para botón de cancelar