feat: versión 1.6.1 mejoras de UI y corrección de bugs

Mejoras de UX/UI:
- Mensajes de progreso persistentes durante la validación
- Estadísticas reales de 'IPs Disponibles' actualizadas post-validación
- Badge de estado muestra 'No disponible' en rojo para conflictos de ping
- Mejor distribución del formulario (checkboxes agrupados)
Correcciones:
- Corregido bug que ignoraba el límite de IPs seleccionado
- Corregido ocultamiento prematuro de indicadores de carga
- Corregido desaparición rápida de mensajes de éxito
Docs:
- Actualizado manifest a v1.6.1
- Actualizado CHANGELOG.md y README.md
This commit is contained in:
DANYDHSV 2025-12-08 14:39:03 -06:00
parent ecee38cf96
commit 647bdfc6ae
6 changed files with 20648 additions and 1004 deletions

View File

@ -7,6 +7,20 @@ y este proyecto adhiere a [Semantic Versioning](https://semver.org/lang/es/).
---
## [1.6.1] - 2025-12-08
### 🚀 Mejoras de UX/UI
- Reorganización de elementos del formulario: Checkboxes agrupados y dropdown de límite al final.
- **Mensajes de progreso persistentes**: Los mensajes de estado ("Validando por ping...", "Consultando...") ahora permanecen visibles durante todo el proceso.
- **Badge de IP**: Ahora muestra "No disponible" en rojo cuando una IP está en uso por ping o validación de sitios (antes mostraba "Cliente" en verde).
- **Estadísticas reales**: Los contadores de "IPs Disponibles" y "IPs en Uso" ahora reflejan exactamente lo que se muestra en la tabla después de la validación progresiva.
### 🐛 Bug Fixes
- **Límite de IPs**: Corregido bug donde la validación progresiva ignoraba el límite seleccionado (5, 10, 20) y validaba todas.
- **Indicadores de carga**: Corregido problema donde el spinner y mensajes desaparecían prematuramente antes de terminar la validación progresiva.
---
## [1.6.0] - 2025-12-06
### ✨ Añadido

View File

@ -1,6 +1,6 @@
# SIIP - Buscador de IP's Disponibles UISP
[![Version](https://img.shields.io/badge/version-1.5.1-blue.svg)](manifest.json)
[![Version](https://img.shields.io/badge/version-1.6.1-blue.svg)](manifest.json)
[![UCRM](https://img.shields.io/badge/UCRM-Compatible-green.svg)](https://uisp.com/)
[![UNMS](https://img.shields.io/badge/UNMS-Compatible-green.svg)](https://uisp.com/)

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@
"displayName": "SIIP - Buscador de IP's Disponibles UISP",
"description": "Este plugin permite buscar IP's disponibles en UISP (UNMS) y asignarlas a los clientes en UCRM. Evitando así la asignación de IP's duplicadas y mejorando la gestión de direcciones IP en la red.",
"url": "https://siip.mx",
"version": "1.6.0",
"version": "1.6.1",
"ucrmVersionCompliancy": {
"min": "1.0.0",
"max": null

View File

@ -717,6 +717,18 @@ $pingEnabled = !empty($config['enablePingVerification']) && ($config['enablePing
color: #4facfe;
}
.alert-info {
background: rgba(79, 209, 197, 0.1);
border: 1px solid rgba(79, 209, 197, 0.3);
color: #4fd1c5;
}
.alert-warning {
background: rgba(255, 193, 7, 0.1);
border: 1px solid rgba(255, 193, 7, 0.3);
color: #ffc107;
}
.ip-type-badge {
padding: 6px 12px;
border-radius: 6px;
@ -1270,7 +1282,7 @@ $pingEnabled = !empty($config['enablePingVerification']) && ($config['enablePing
</div>
<div class="input-group" style="flex: 0 0 auto; min-width: auto;">
<label class="checkbox-label">
<input type="checkbox" id="hideAdmin" name="hide_admin" style="width: auto; padding: 0; margin: 0;">
<input type="checkbox" id="hideAdmin" name="hide_admin" style="width: auto; padding: 0; margin: 0;" checked>
<span style="color: var(--text-secondary); font-size: 0.9rem;">👁️ Ocultar IP's de Administración</span>
</label>
</div>
@ -1856,12 +1868,6 @@ $pingEnabled = !empty($config['enablePingVerification']) && ($config['enablePing
const data = await response.json();
console.log('Datos recibidos:', data);
loading.classList.remove('active');
<?php if ($pingEnabled): ?>
loadingPingWarning.style.display = 'none';
<?php endif; ?>
searchBtn.disabled = false;
if (data.success) {
const clientIps = displayResults(data);
@ -1869,13 +1875,15 @@ $pingEnabled = !empty($config['enablePingVerification']) && ($config['enablePing
<?php if ($pingEnabled): ?>
if (shouldVerifyPing) {
// Si ping está marcado, validar con site search y luego con ping
// NO ocultar loading, se encargará runProgressiveValidation
runProgressiveValidation(clientIps, pingLimit, shouldVerifyPing);
} else {
// Si ping NO está marcado, solo validar con site search
// Si ping NO está marcado, validar solo con site search
// NO ocultar loading, se encargará runProgressiveValidation
runProgressiveValidation(clientIps, 0, false);
}
<?php else: ?>
// Si ping no está habilitado en config, validar con site search
// Si ping no está habilitado globalmente, solo site search
runProgressiveValidation(clientIps, 0, false);
<?php endif; ?>
} else {
@ -1944,15 +1952,26 @@ $pingEnabled = !empty($config['enablePingVerification']) && ($config['enablePing
// Calcular índice visual
const index = ipTableBody.children.length + 1;
// Determinar el badge de tipo de IP
let ipTypeBadge = '';
if (ipTypeLabel === 'Administración') {
ipTypeBadge = '<span class="ip-type-badge ip-type-admin">Administración</span>';
} else {
// Si la IP está en uso (status contiene "En uso" o statusClass es "used"), mostrar "No disponible" en rojo
if (statusClass === 'used' || statusText.includes('En uso')) {
ipTypeBadge = '<span class="ip-type-badge ip-type-admin">No disponible</span>';
} else {
ipTypeBadge = '<span class="ip-type-badge ip-type-client">Cliente</span>';
}
}
row.innerHTML = `
<td>${index}</td>
<td>
<div class="ip-cell-mobile">
<span class="ip-address">${ip}</span>
<span class="ip-type-badge ip-type-${ipTypeLabel === 'Administración' ? 'admin' : 'client'}">
${ipTypeLabel}
</span>
${ipTypeBadge}
</div>
</td>
<td id="status-${ip.replace(/\./g, '-')}">
@ -2068,6 +2087,32 @@ $pingEnabled = !empty($config['enablePingVerification']) && ($config['enablePing
console.log(`Iniciando validación progresiva de ${clientIps.length} IPs`);
console.log(`Ping habilitado: ${shouldVerifyPing}, Límite: ${pingLimit}`);
// Determinar cuántas IPs validar según el límite
let ipsToValidate = [];
if (pingLimit > 0 && shouldVerifyPing) {
// Si hay límite y ping está habilitado, solo validar las primeras N
ipsToValidate = clientIps.slice(0, pingLimit);
} else {
// Si no hay límite o ping no está habilitado, validar todas
ipsToValidate = [...clientIps];
}
console.log(`Validando ${ipsToValidate.length} IPs de ${clientIps.length} disponibles`);
// 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...';
} else {
progressMessage = 'Validando IP\'s con búsqueda de sitios. 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);
// Mostrar botón de cancelar
verificationCancelled = false;
if (cancelBtn) {
@ -2077,7 +2122,7 @@ $pingEnabled = !empty($config['enablePingVerification']) && ($config['enablePing
let validatedIps = [];
// Procesar cada IP secuencialmente
for (const ip of clientIps) {
for (const ip of ipsToValidate) {
// Verificar si el usuario canceló
if (verificationCancelled) {
console.log('Validación cancelada por el usuario');
@ -2135,7 +2180,37 @@ $pingEnabled = !empty($config['enablePingVerification']) && ($config['enablePing
cancelBtn.style.display = 'none';
}
console.log(`Validación completada. ${validatedIps.length} IPs disponibles de ${clientIps.length} totales`);
// Actualizar estadísticas con los números reales
// Contar IPs disponibles (status-available) y en uso (status-used o status-conflict)
const allRows = ipTableBody.querySelectorAll('tr');
let availableIpsCount = 0;
let usedIpsCount = 0;
allRows.forEach(row => {
const statusBadge = row.querySelector('.status-badge');
if (statusBadge) {
if (statusBadge.classList.contains('status-available')) {
availableIpsCount++;
} else if (statusBadge.classList.contains('status-used') ||
statusBadge.classList.contains('status-conflict')) {
usedIpsCount++;
}
}
});
// Actualizar contadores en la UI
availableCount.textContent = availableIpsCount;
usedCount.textContent = usedIpsCount;
// Ocultar indicadores de carga y reactivar botón
loading.classList.remove('active');
<?php if ($pingEnabled): ?>
if (loadingPingWarning) loadingPingWarning.style.display = 'none';
<?php endif; ?>
if (searchBtn) searchBtn.disabled = false;
console.log(`Validación completada. ${availableIpsCount} IPs disponibles, ${usedIpsCount} IPs en uso`);
showError(`Validación completada. ${availableIpsCount} IPs disponibles encontradas.`, 'success', false);
}
/**
@ -2146,6 +2221,19 @@ $pingEnabled = !empty($config['enablePingVerification']) && ($config['enablePing
if (statusCell) {
statusCell.innerHTML = `<span class="status-badge status-${statusClass}">${statusText}</span>`;
}
// Si el estado es "En uso", también actualizar el badge de tipo de IP a "No disponible"
if (statusClass === 'used' || statusText.includes('En uso')) {
const row = document.getElementById(`row-${ip.replace(/\./g, '-')}`);
if (row) {
const ipTypeBadge = row.querySelector('.ip-type-badge');
if (ipTypeBadge && !ipTypeBadge.classList.contains('ip-type-admin')) {
// Cambiar de "Cliente" a "No disponible" con color rojo
ipTypeBadge.className = 'ip-type-badge ip-type-admin';
ipTypeBadge.textContent = 'No disponible';
}
}
}
}
async function runProgressiveVerification(clientIps, limit) {
@ -2193,6 +2281,27 @@ $pingEnabled = !empty($config['enablePingVerification']) && ($config['enablePing
cancelBtn.style.display = 'none';
}
// Actualizar estadísticas con los números reales
const allRows = ipTableBody.querySelectorAll('tr');
let availableIpsCount = 0;
let usedIpsCount = 0;
allRows.forEach(row => {
const statusBadge = row.querySelector('.status-badge');
if (statusBadge) {
if (statusBadge.classList.contains('status-available')) {
availableIpsCount++;
} else if (statusBadge.classList.contains('status-used') ||
statusBadge.classList.contains('status-conflict')) {
usedIpsCount++;
}
}
});
// 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.
}
@ -2268,11 +2377,35 @@ $pingEnabled = !empty($config['enablePingVerification']) && ($config['enablePing
if (cell) {
cell.innerHTML = `<span class="status-badge status-${type}">${text}</span>`;
}
// Si el tipo es "conflict" (En uso por ping), actualizar el badge de tipo de IP
if (type === 'conflict') {
const row = document.getElementById(`row-${ip.replace(/\./g, '-')}`);
if (row) {
const ipTypeBadge = row.querySelector('.ip-type-badge');
if (ipTypeBadge && !ipTypeBadge.classList.contains('ip-type-admin')) {
// Cambiar de "Cliente" a "No disponible" con color rojo
ipTypeBadge.className = 'ip-type-badge ip-type-admin';
ipTypeBadge.textContent = 'No disponible';
}
}
}
}
function showError(message, type = 'error') {
const alertClass = type === 'success' ? 'alert-success' : 'alert-error';
const icon = type === 'success' ? '✓' : '⚠';
function showError(message, type = 'error', autohide = true) {
let alertClass = 'alert-error';
let icon = '⚠';
if (type === 'success') {
alertClass = 'alert-success';
icon = '✓';
} else if (type === 'info') {
alertClass = 'alert-info';
icon = '⏳';
} else if (type === 'warning') {
alertClass = 'alert-warning';
icon = '⚠';
}
errorContainer.innerHTML = `
<div class="alert ${alertClass}">
@ -2281,8 +2414,8 @@ $pingEnabled = !empty($config['enablePingVerification']) && ($config['enablePing
</div>
`;
// Auto-ocultar mensajes de éxito después de 5 segundos
if (type === 'success') {
// Auto-ocultar solo si autohide es true
if (autohide && type === 'success') {
setTimeout(() => {
errorContainer.innerHTML = '';
}, 5000);

View File

@ -1,983 +0,0 @@
<?php
// --- SERVIR IMÁGENES DESDE LA URL ---
if (isset($_GET['img'])) {
$imageName = basename($_GET['img']);
$imagePath = __DIR__ . '/img/' . $imageName;
if (file_exists($imagePath)) {
$mimeType = mime_content_type($imagePath);
header('Content-Type: ' . $mimeType);
readfile($imagePath);
exit;
} else {
http_response_code(404);
echo 'Imagen no encontrada';
exit;
}
}
// --- SERVIR CONTRASEÑA GENERADA POR AJAX ---
if (isset($_GET['generate_password'])) {
echo generate_strong_password();
exit;
}
// --- ENDPOINT PARA ACTUALIZAR CONTRASEÑA DEL CLIENTE ---
if (isset($_POST['update_password']) && isset($_POST['clientId']) && isset($_POST['password'])) {
// log
$logger = \Ubnt\UcrmPluginSdk\Service\PluginLogManager::create();
$logger->appendLog('POST update_password recibido: ' . var_export($_POST, true));
// lógica PATCH
$result = updateClientPasswordAttribute($_POST['clientId'], $_POST['password']);
$logger->appendLog('Resultado updateClientPasswordAttribute: ' . var_export($result, true));
header('Content-Type: application/json');
echo json_encode($result);
exit;
}
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
chdir(__DIR__);
require_once __DIR__ . '/vendor/autoload.php';
// --- ENDPOINT PARA ACTUALIZAR CONTRASEÑA DEL CLIENTE ---
if (isset($_POST['update_password']) && isset($_POST['clientId']) && isset($_POST['password'])) {
$logger = \Ubnt\UcrmPluginSdk\Service\PluginLogManager::create();
$logger->appendLog('POST update_password recibido: ' . var_export($_POST, true));
$clientId = $_POST['clientId'];
$password = $_POST['password'];
$result = updateClientPasswordAttribute($clientId, $password);
$logger->appendLog('Resultado updateClientPasswordAttribute: ' . var_export($result, true));
header('Content-Type: application/json');
echo json_encode($result);
exit;
}
// Get UCRM log manager.
$log = \Ubnt\UcrmPluginSdk\Service\PluginLogManager::create();
// $log->appendLog(
// sprintf(
// 'Executed from public URL: %s',
// file_get_contents('php://input')
// )
// );
// Variable para almacenar la respuesta de la API, mensajes de error y estado
$respuestaAPI = '';
$esError = false;
$clientFullName = '';
$IpAddressClientId = '';
$clientIdentifierType = 'ID'; // Puede ser 'ID', 'IP', 'MAC'
//variables para mostrar info del cliente
$clientFullName = '';
$IpAddressClientId = '';
// Ensure global variables are initialized at the top of the script
$clientFullName = $clientFullName ?? '';
$IpAddressClientId = $IpAddressClientId ?? '';
// Verificar si se ha enviado el formulario
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
/**
* Actualiza el atributo personalizado 'passwordAntenaCliente' de un cliente en UCRM.
*
* @param int|string $clientId ID del cliente
* @param string $newPassword Nueva contraseña a setear
* @return array [success => bool, message => string]
*/
function updateClientPasswordAttribute($clientId, $newPassword) {
$configManager = \Ubnt\UcrmPluginSdk\Service\PluginConfigManager::create();
$config = $configManager->loadConfig();
$baseUri = $config['ipserver'];
$crmAuthToken = $config['apitoken']; // O $config['ucrmApiToken'] si así lo tienes
$clientUcrm = new \GuzzleHttp\Client([
'base_uri' => 'https://' . $baseUri . '/crm/api/v1.0/',
'verify' => false
]);
$logger = \Ubnt\UcrmPluginSdk\Service\PluginLogManager::create();
try {
// 1. Obtener el cliente actual
$response = $clientUcrm->get('clients/' . $clientId, [
'headers' => [
'X-Auth-Token' => $crmAuthToken,
'Content-Type' => 'application/json'
]
]);
if ($response->getStatusCode() !== 200) {
return ['success' => false, 'message' => 'No se pudo obtener el cliente.'];
}
$clientData = json_decode($response->getBody(), true);
// 2. Buscar el atributo personalizado 'passwordAntenaCliente'
$found = false;
$attributes = [];
if (isset($clientData['attributes']) && is_array($clientData['attributes'])) {
foreach ($clientData['attributes'] as $attr) {
if ($attr['key'] === 'passwordAntenaCliente') {
$attr['value'] = $newPassword;
$found = true;
}
$attributes[] = $attr;
}
}
if (!$found) {
return ['success' => false, 'message' => 'No se encontró el atributo personalizado passwordAntenaCliente.'];
}
// 3. Actualizar el cliente completo usando PATCH
// Solo enviar el array attributes con customAttributeId y value
$logger->appendLog('PATCH endpoint: clients/' . $clientId);
$patchBody = [
'attributes' => [
[
'customAttributeId' => 17,
'value' => $newPassword
]
]
];
$logger->appendLog('PATCH body: ' . json_encode($patchBody));
try {
$patchResponse = $clientUcrm->patch('clients/' . $clientId, [
'headers' => [
'X-Auth-Token' => $crmAuthToken,
'Content-Type' => 'application/json'
],
'json' => $patchBody
]);
$logger->appendLog('PATCH status: ' . $patchResponse->getStatusCode());
$logger->appendLog('PATCH response: ' . $patchResponse->getBody());
if ($patchResponse->getStatusCode() === 200) {
return ['success' => true, 'message' => 'Contraseña actualizada correctamente.'];
} else {
return ['success' => false, 'message' => 'Error al actualizar el cliente.'];
}
} catch (Exception $e) {
$logger->appendLog('PATCH exception: ' . $e->getMessage());
return ['success' => false, 'message' => 'Excepción: ' . $e->getMessage()];
}
} catch (Exception $e) {
$logger->appendLog('PATCH attributes exception: ' . $e->getMessage());
return ['success' => false, 'message' => 'Excepción: ' . $e->getMessage()];
}
}
$entrada = trim($_POST['identificador']);
// Validar si es una dirección IP válida
if (filter_var($entrada, FILTER_VALIDATE_IP)) {
// Es una dirección IP válida
$respuestaAPI = getVaultCredentials($entrada);
}
// Validar si es una dirección MAC válida
elseif (preg_match('/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/', $entrada)) {
// Es una dirección MAC válida
$respuestaAPI = getVaultCredentials($entrada);
}
// Validar si es un ID numérico menor a 10,000
elseif (ctype_digit($entrada) && intval($entrada) < 10000) {
// Es un ID válido
$respuestaAPI = getVaultCredentials($entrada);
} else {
// Entrada no válida
$mensajeError = "La entrada no es válida. Por favor, ingresa una dirección IP, una dirección MAC o un ID menor a 10,000.";
}
// Verificar si la respuesta de la API indica un error
if (strpos($respuestaAPI, "Error:") === 0) {
$esError = true;
}
}
function setNewPassword($clientId, $newPassword)
{
// Aquí iría la lógica para actualizar la contraseña del cliente en UCRM
// Usando la API de UCRM y el cliente Guzzle
// Retornar true si se actualizó correctamente, false en caso contrario
global $clientFullName, $IpAddressClientId;
$logger = \Ubnt\UcrmPluginSdk\Service\PluginLogManager::create();
$configManager = \Ubnt\UcrmPluginSdk\Service\PluginConfigManager::create();
$config = $configManager->loadConfig();
$baseUri = $config['ipserver'];
$crmAuthToken = $config['apitoken'];
$unmsAuthToken = $config['unmsApiToken'];
$ucrmBaseUri = 'https://' . $baseUri . '/crm/api/v1.0/';
$clientUcrm = new Client([
'base_uri' => $ucrmBaseUri,
'verify' => false // Deshabilitar la verificación del certificado SSL
]);
try {
$responseClient = $clientUcrm->patch('clients/' . $clientId, [
'headers' => [
'X-Auth-Token' => $crmAuthToken,
'Content-Type' => 'application/json'
],
'json' => [
'attributes' => [
[
'customAttributeId' => 17, // ID del atributo personalizado 'passwordAntenaCliente'
'value' => $newPassword
]
]
]
]);
if ($responseClient->getStatusCode() === 200) {
$logger->appendLog('Contraseña actualizada correctamente para el cliente: ' . $clientFullName . ' (ID: ' . $IpAddressClientId . ')');
return true;
} else {
$logger->appendLog('Error al actualizar la contraseña. Código de estado HTTP: ' . $responseClient->getStatusCode());
return false;
}
} catch (RequestException $e) {
$logger->appendLog('Excepción al actualizar la contraseña: ' . $e->getMessage());
return false;
}
}
function getVaultCredentials($dataToSearch): string
{
global $clientFullName, $IpAddressClientId, $clientIdentifierType;
$logger = \Ubnt\UcrmPluginSdk\Service\PluginLogManager::create();
$configManager = \Ubnt\UcrmPluginSdk\Service\PluginConfigManager::create();
$config = $configManager->loadConfig();
$baseUri = $config['ipserver'];
$crmAuthToken = $config['apitoken'];
$unmsAuthToken = $config['unmsApiToken'];
$unmsBaseUri = 'https://' . $baseUri . '/nms/api/v2.1/';
$ucrmBaseUri = 'https://' . $baseUri . '/crm/api/v1.0/';
//$authToken = '7adc9198-50b1-41d0-9bfa-d4946902ed89';
// Crear una instancia del cliente Guzzle
$clientUnms = new Client([
'base_uri' => $unmsBaseUri,
'verify' => false // Deshabilitar la verificación del certificado SSL
]);
$clientUcrm = new Client([
'base_uri' => $ucrmBaseUri,
'verify' => false // Deshabilitar la verificación del certificado SSL
]);
//validar si dataToSearch es una dirección IP o una dirección MAC
if (filter_var($dataToSearch, FILTER_VALIDATE_IP)) {
$clientIdentifierType = 'IP';
// La variable es una dirección IP válida
$logger->appendLog('Consulta por dirección IP: ' . $dataToSearch);
$IpAddressClientId = filter_var($dataToSearch, FILTER_VALIDATE_IP);
try {
$responseSitesByIP = $clientUnms->request('GET', 'sites/search?query=' . $dataToSearch . '&count=1&page=1', [
'headers' => [
'X-Auth-Token' => $unmsAuthToken
]
]); //Sites por IP
if ($responseSitesByIP->getStatusCode() === 200) {
$datasSitesByIP = json_decode($responseSitesByIP->getBody(), true);
$jsonDevicesBySite = json_encode($datasSitesByIP, JSON_PRETTY_PRINT);
//print_r($jsonDevicesBySite . PHP_EOL); //Devices por ID del sitio
if (isset($datasSitesByIP[0])) {
$siteId = $datasSitesByIP[0]['id'];
// print_r('ID DEL SITIO: ' . $siteId . PHP_EOL); // ID del sitio
} else {
$logger->appendLog('No se encontró ningún sitio para la IP proporcionada: ' . $dataToSearch);
return 'Error: No se encontró ningún sitio para la IP proporcionada: ' . $dataToSearch; // Return early if no site is found
}
} else {
//echo "Error en la solicitud. Código de estado HTTP: " . $responseSitesByIP->getStatusCode() . PHP_EOL;
$logger->appendLog('Error en la solicitud. Código de estado HTTP: ' . $responseSitesByIP->getStatusCode());
return 'Error: Falla en la solicitud. Código de estado HTTP: ' . $responseSitesByIP->getStatusCode(); // Return early if the request fails
}
$devicesBySiteId = $clientUnms->request('GET', 'devices?siteId=' . $siteId, [
'headers' => [
'X-Auth-Token' => $unmsAuthToken
]
]); //Devices por ID del sitio
if ($devicesBySiteId->getStatusCode() === 200) {
$dataDevicesBySite = json_decode($devicesBySiteId->getBody(), true);
$jsonDevicesBySite = json_encode($dataDevicesBySite, JSON_PRETTY_PRINT);
//print_r($jsonDevicesBySite . PHP_EOL); //Devices por ID del sitio
$deviceID = null;
foreach ($dataDevicesBySite as $device) {
if (isset($device['ipAddress'])) {
$ipAddress = explode('/', $device['ipAddress'])[0]; // Obtener solo la IP sin la máscara
if ($ipAddress === $IpAddressClientId) {
$deviceID = $device['identification']['id']; // ID del dispositivo
break; // Salir del ciclo si se encuentra la IP
}
} else {
$logger->appendLog('No se encontró la IP del dispositivo en la respuesta de la API.');
return 'Error: No se encontró la IP del dispositivo en la respuesta de la API.'; // Return early if the IP is not found
}
}
if ($deviceID == null) {
//echo "No se encontró el dispositivo con la IP proporcionada." . PHP_EOL;
$logger->appendLog('No se encontró el dispositivo con la IP proporcionada: ' . $IpAddressClientId);
return 'Error: No se encontró el dispositivo con la IP proporcionada: ' . $IpAddressClientId; // Return early if no device is found
}
} else {
// echo "Error en la solicitud. Código de estado HTTP: " . $devicesBySiteId->getStatusCode() . PHP_EOL;
$logger->appendLog('Error en la solicitud. Código de estado HTTP: ' . $devicesBySiteId->getStatusCode());
return 'Error: Falla en la solicitud. Código de estado HTTP: ' . $devicesBySiteId->getStatusCode(); // Return early if the request fails
}
$responsePasswordVault = $clientUnms->request('GET', 'vault/' . $deviceID . '/credentials', [
'headers' => [
'X-Auth-Token' => $unmsAuthToken
]
]);
if ($responsePasswordVault->getStatusCode() === 200) {
$dataPasswordVault = json_decode($responsePasswordVault->getBody(), true);
// $jsonPasswordVault = json_encode($dataPasswordVault, JSON_PRETTY_PRINT);
//print_r($jsonPasswordVault . PHP_EOL); //Cred
if (isset($dataPasswordVault['credentials'][0]['password'])) {
//asignar el campo password a la variable dataPasswordVault
$dataPasswordVault = $dataPasswordVault['credentials'][0]['password'];
} else {
// echo "No se encontró la contraseña en la respuesta de la API." . PHP_EOL;
$logger->appendLog('No se encontró una contraseña en la bóveda para la antena de este cliente, es altamente probable que conserve una contraseña conocida.');
return "Error: No se encontró una contraseña en la bóveda para la antena de este cliente, es altamente probable que conserve una contraseña conocida."; // Return early if the password is not found
}
return $dataPasswordVault;
} else {
// echo "Error en la solicitud. Código de estado HTTP: " . $responsePasswordVault->getStatusCode() . PHP_EOL;
$logger->appendLog('Error en la solicitud. Código de estado HTTP: ' . $responsePasswordVault->getStatusCode());
return 'Error: Falla en la solicitud. Código de estado HTTP: ' . $responsePasswordVault->getStatusCode(); // Return early if the request fails
}
} catch (RequestException $requestException) {
// Manejar errores de la solicitud, si hay error 404 entonces responder que no se encontró niguna IP asociada a alguna antena o dispositivo de red
if ($requestException->hasResponse()) {
$response = $requestException->getResponse();
$statusCode = $response->getStatusCode();
$reason = $response->getReasonPhrase();
// echo "Error: {$statusCode} - {$reason}" . PHP_EOL;
// echo $response->getBody();
if ($statusCode == 404) {
// echo "No se encontró el cliente con la dirección IP proporcionada." . PHP_EOL;
$logger->appendLog('No se encontró el cliente con la dirección IP proporcionada: ' . $IpAddressClientId);
return 'Error: No se encontró el cliente con la dirección IP proporcionada: ' . $IpAddressClientId; // Return early if the client is not found
}
return 'Error: ' . $reason; // Return early if the request fails
} else {
// echo "Error: " . $requestException->getMessage() . PHP_EOL;
$logger->appendLog('Error: ' . $requestException->getMessage());
return 'Error: ' . $requestException->getMessage(); // Return early if the request fails
}
}
} else if (preg_match('/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/', $dataToSearch)) {
$clientIdentifierType = 'MAC';
// La variable es una dirección MAC válida
$logger->appendLog('Consulta por dirección MAC: ' . $dataToSearch);
try {
// //para mandarla al endpoint de dispositivos por MAC se necesita convertir la cadena de la direccion mac en un formato como el siguiente ejemplo: para la dirección mac 60:22:32:c8:b2:c3 quedaría como 60%3A22%3A32%3Ac8%3Ab2%3Ac3
// $dataToSearch = str_replace(':', '%3A', $dataToSearch);
// $logger->appendLog('Consulta por dirección MAC: ' . $dataToSearch );
$responseDeviceByMAC = $clientUnms->request('GET', 'devices/mac/' . $dataToSearch, [
'headers' => [
'X-Auth-Token' => $unmsAuthToken
]
]);
if ($responseDeviceByMAC->getStatusCode() === 200) {
$dataDeviceByMAC = json_decode($responseDeviceByMAC->getBody(), true);
$jsonDeviceByMac = json_encode($dataDeviceByMAC, JSON_PRETTY_PRINT);
// print_r($jsonDeviceByMac . PHP_EOL); //Devices por ID del sitio
//$logger->appendLog($jsonDeviceByMac.PHP_EOL);
//Ejemplo de $responseDeviceByMac en json: {"id":"84c8a581-154c-41db-81d5-a1b1c9ede411","type":"airMax","series":"AC"}
//obtener el id del dispositivo y si no existe el id del dispositivo entonces devolver un mensaje de error
if (isset($dataDeviceByMAC['id'])) {
$deviceId = $dataDeviceByMAC['id'];
// print_r('ID DEL DISPOSITIVO: ' . $deviceId . PHP_EOL); // ID del dispositivo
} else {
// echo "No se encontró el dispositivo con la dirección MAC proporcionada." . PHP_EOL;
$logger->appendLog('No se encontró el dispositivo con la dirección MAC proporcionada: ' . $dataToSearch);
return 'Error: No se encontró el dispositivo con la dirección MAC proporcionada: ' . $dataToSearch; // Return early if no device is found
}
}
$responsePasswordVault = $clientUnms->request('GET', 'vault/' . $deviceId . '/credentials', [
'headers' => [
'X-Auth-Token' => $unmsAuthToken
]
]);
if ($responsePasswordVault->getStatusCode() === 200) {
$dataPasswordVault = json_decode($responsePasswordVault->getBody(), true);
// $jsonPasswordVault = json_encode($dataPasswordVault, JSON_PRETTY_PRINT);
//print_r($jsonPasswordVault . PHP_EOL); //Credenciales del dispositivo
if (isset($dataPasswordVault['credentials'][0]['password'])) {
//asignar el campo password a la variable dataPasswordVault
$dataPasswordVault = $dataPasswordVault['credentials'][0]['password'];
} else {
// echo "No se encontró la contraseña en la respuesta de la API." . PHP_EOL;
$logger->appendLog('No se encontró una contraseña en la bóveda para la antena de este cliente, es altamente probable que conserve una contraseña conocida.');
return "Error: No se encontró una contraseña en la bóveda para la antena de este cliente, es altamente probable que conserve una contraseña conocida."; // Return early if the password is not found
}
return $dataPasswordVault;
} else {
// echo "Error en la solicitud. Código de estado HTTP: " . $responsePasswordVault->getStatusCode() . PHP_EOL;
$logger->appendLog('Error en la solicitud. Código de estado HTTP: ' . $responsePasswordVault->getStatusCode());
return 'Error: Falla en la solicitud. Código de estado HTTP: ' . $responsePasswordVault->getStatusCode(); // Return early if the request fails
}
} catch (RequestException $requestException) {
// Manejar errores de la solicitud, si hay error 404 entonces responder que no se encontró niguna MAC asociada a alguna antena o dispositivo de red
if ($requestException->hasResponse()) {
$response = $requestException->getResponse();
$statusCode = $response->getStatusCode();
$reason = $response->getReasonPhrase();
// echo "Error: {$statusCode} - {$reason}" . PHP_EOL;
// echo $response->getBody();
if ($statusCode == 404) {
// echo "No se encontró el cliente con la dirección MAC proporcionada." . PHP_EOL;
$logger->appendLog('No se encontró ninguna antena de cliente o dispositivo en la red con la dirección MAC proporcionada: ' . $dataToSearch);
return 'Error: No se encontró ninguna antena de cliente o dispositivo en la red con la dirección MAC proporcionada: ' . $dataToSearch; // Return early if the client is not found
}
return 'Error: ' . $reason; // Return early if the request fails
} else {
// echo "Error: " . $requestException->getMessage() . PHP_EOL;
$logger->appendLog('Error: ' . $requestException->getMessage());
return 'Error: ' . $requestException->getMessage(); // Return early if the request fails
}
}
} else {
$clientIdentifierType = 'ID';
// La variable no es una dirección IP válida, se asume que es un ID
$IpAddressClientId = filter_var($dataToSearch, FILTER_SANITIZE_NUMBER_INT);
//print ('Consulta por ID: ' . $dataToSearch . PHP_EOL);
// $logger->appendLog('Consulta por ID' . $dataToSearch);
// $logger->appendLog('endpoint que se envía: '.$ucrmBaseUri.'clients/services?clientId=' . $IpAddressClientId);
// $logger->appendLog('apiToken que se usa: '.$crmAuthToken);
try {
//Obtener id del sitio por medio del servicio
$responseServices = $clientUcrm->get('clients/services?clientId=' . $IpAddressClientId, [
'headers' => [
'X-Auth-Token' => $crmAuthToken,
'Content-Type: application/json'
]
]);
//Obtener el nombre del cliente por medio del ID
$responseClientName = $clientUcrm->get('clients/' . $IpAddressClientId, [
'headers' => [
'X-Auth-Token' => $crmAuthToken,
'Content-Type: application/json'
]
]);
//ejemplo de responseClientName en json: {"id":3201,"userIdent":null,"previousIsp":null,"isLead":false,"clientType":1,"companyName":null,"companyRegistrationNumber":null,"companyTaxId":null,"companyWebsite":null,"street1":"CALLE CHIAPAS #31, ZONA CENTRO","street2":null,"city":"Dolores Hidalgo Cuna de la Independencia Nacional","countryId":173,"stateId":null,"zipCode":"37800","fullAddress":"CALLE CHIAPAS #31, ZONA CENTRO, Dolores Hidalgo Cuna de la Independencia Nacional, 37800","invoiceStreet1":null,"invoiceStreet2":null,"invoiceCity":null,"invoiceStateId":null,"invoiceCountryId":null,"invoiceZipCode":null,"invoiceAddressSameAsContact":true,"note":"Soy un teibolero :) programador en SIIP INTERNET","sendInvoiceByPost":null,"invoiceMaturityDays":17,"stopServiceDue":false,"stopServiceDueDays":null,"organizationId":1,"tax1Id":null,"tax2Id":null,"tax3Id":null,"registrationDate":"2024-04-19T00:00:00-0600","leadConvertedAt":null,"companyContactFirstName":null,"companyContactLastName":null,"isActive":false,"firstName":"DANIEL HUMBERTO","lastName":"SOTO VILLEGAS","username":"dhsv.141089@gmail.com","contacts":[{"id":4188,"clientId":3201,"email":"dhsv.141089@gmail.com","phone":"5214181878106","name":null,"isBilling":false,"isContact":false,"types":[{"id":4,"name":"WhatsApp"}]},{"id":5242,"clientId":3201,"email":null,"phone":"5214181148783","name":"WA Bussiness","isBilling":false,"isContact":false,"types":[]}],"attributes":[{"id":21180,"clientId":3201,"customAttributeId":35,"name":"Site","key":"site","value":"0LOCS","clientZoneVisible":true},{"id":18147,"clientId":3201,"customAttributeId":1,"name":"ip","key":"ip","value":"172.16.13.64","clientZoneVisible":false},{"id":19859,"clientId":3201,"customAttributeId":29,"name":"Stripe Customer ID","key":"stripeCustomerId","value":"cus_RKiSN955qKhmch","clientZoneVisible":true},{"id":19860,"clientId":3201,"customAttributeId":30,"name":"Clabe Interbancaria","key":"clabeInterbancaria","value":"124180650741646979","clientZoneVisible":true},{"id":21181,"clientId":3201,"customAttributeId":36,"name":"Antena/Sectorial","key":"antenaSectorial","value":"Sectorial-4b 172.16.13.16/24","clientZoneVisible":true},{"id":27184,"clientId":3201,"customAttributeId":21,"name":"Chat de CallBell","key":"chatDeCallbell","value":"https://dash.callbell.eu/chat/90611886ac204c56a75240f951c53874","clientZoneVisible":false},{"id":28687,"clientId":3201,"customAttributeId":22,"name":"uuid","key":"uuid","value":"2038d2d99ae543f3b56f1ae54f4cc82b","clientZoneVisible":false},{"id":29853,"clientId":3201,"customAttributeId":37,"name":"Password Antena Cliente","key":"passwordAntenaCliente","value":"gYAIEK:Be}SK*01z5+/V","clientZoneVisible":false}],"accountBalance":-300,"accountCredit":0,"accountOutstanding":300,"currencyCode":"MXN","organizationName":"SIIP","bankAccounts":[{"id":2,"accountNumber":"124180650741646979"}],"tags":[{"id":1,"name":"NS EXENTO","colorBackground":"#00a0df","colorText":"#fff"}],"invitationEmailSentDate":null,"avatarColor":"#4caf50","addressGpsLat":21.1564209,"addressGpsLon":-100.9384185,"isArchived":false,"generateProformaInvoices":null,"usesProforma":false,"hasOverdueInvoice":true,"hasOutage":false,"hasSuspendedService":false,"hasServiceWithoutDevices":false,"referral":null,"hasPaymentSubscription":false,"hasAutopayCreditCard":false}
$clientName = json_decode($responseClientName->getBody(), true);
$clientFullName = $clientName['firstName'] . ' ' . $clientName['lastName'];
$logger->appendLog('CLIENTE: ' . $clientFullName . ' (ID: ' . $IpAddressClientId . ')');
} catch (RequestException $requestException) {
// Manejar errores de la solicitud
if ($requestException->hasResponse()) {
$response = $requestException->getResponse();
$statusCode = $response->getStatusCode();
$reason = $response->getReasonPhrase();
//si el statusCode es 404 significa que no se encontró el cliente
if ($statusCode == 404) {
// echo "No se encontró el cliente con el ID proporcionado." . PHP_EOL;
$logger->appendLog('No se encontró el cliente con el ID proporcionado: ' . $IpAddressClientId);
return 'Error: No se encontró el cliente con el ID proporcionado: ' . $IpAddressClientId; // Return early if the client is not found
}
return 'Error: ' . $reason; // Return early if the request fails
} else {
// echo "Error: " . $requestException->getMessage() . PHP_EOL;
$logger->appendLog('Error: ' . $requestException->getMessage());
return 'Error: ' . $requestException->getMessage(); // Return early if the request fails
}
}
if ($responseServices->getStatusCode() === 200) {
$dataServices = json_decode($responseServices->getBody(), true);
// $jsonServices = json_encode($dataServices, JSON_PRETTY_PRINT);
// print_r($jsonServices . PHP_EOL);
if (isset($dataServices[0])) {
$unmsSiteID = $dataServices[0]['unmsClientSiteId']; // Example: 9c6798f3-0254-4e5b-bc3b-9da82fe16e46
} else {
// echo "No se encontraron servicios para el cliente proporcionado." . PHP_EOL;
$logger->appendLog('No se encontraron servicios para el cliente proporcionado: ' . $IpAddressClientId);
return "Error: No se encontraron servicios para el cliente proporcionado: " . $IpAddressClientId; // Return early if no services are found
}
} else {
// echo "Error en la solicitud. Código de estado HTTP: " . $responseServices->getStatusCode() . PHP_EOL;
$logger->appendLog('Error en la solicitud. Código de estado HTTP: ' . $responseServices->getStatusCode());
return "Error: En la solicitud. Código de estado HTTP: " . $responseServices->getStatusCode();
}
// $logger->appendLog('ID del sitio obtenido: ' . $unmsSiteID);
// $logger->appendLog('endpoint que se envía: '.$unmsBaseUri.'devices?siteId=' . $unmsSiteID);
// $logger->appendLog('apiToken que se usa: '.$crmAuthToken);
$responseDevicesBySite = $clientUnms->request('GET', 'devices?siteId=' . $unmsSiteID, [
'headers' => [
'X-Auth-Token' => $unmsAuthToken
]
]);
if ($responseDevicesBySite->getStatusCode() === 200) {
$dataDevicesBySite = json_decode($responseDevicesBySite->getBody(), true);
$jsonDevicesBySite = json_encode($dataDevicesBySite, JSON_PRETTY_PRINT);
//print_r($jsonDevicesBySite . PHP_EOL); //Devices por ID del sitio
//id del device al que está conectado el cliente
if (isset($dataDevicesBySite[0])) {
$idClientDevice = $dataDevicesBySite[0]['identification']['id'];
$deviceID = $dataDevicesBySite[0]['attributes']['apDevice']['id'];
} else {
// echo "No se encontraron dispositivos para el sitio proporcionado." . PHP_EOL;
$logger->appendLog('No se encontraron dispositivos para el sitio proporcionado: ' . $unmsSiteID);
return "Error: No se encontraron dispositivos para el sitio proporcionado."; // Return early if no devices are found
}
} else {
// echo "Error en la solicitud. Código de estado HTTP: " . $responseDevicesBySite->getStatusCode() . PHP_EOL;
$logger->appendLog('Error en la solicitud. Código de estado HTTP: ' . $responseDevicesBySite->getStatusCode());
return "Error: Falla en la solicitud. Código de estado HTTP: " . $responseDevicesBySite->getStatusCode();
}
// $logger->appendLog('ID del device al que está conectado el cliente: ' . $deviceID);
// $logger->appendLog('endpoint que se envía: '.$unmsBaseUri.'devices/' . $deviceID);
// $logger->appendLog('apiToken que se usa: '.$unmsAuthToken);
$responseDevicesBySite = $clientUnms->request('GET', 'devices/' . $deviceID, [
'headers' => [
'X-Auth-Token' => $unmsAuthToken
]
]);
if ($responseDevicesBySite->getStatusCode() === 200) {
$dataDevices = json_decode($responseDevicesBySite->getBody(), true);
$jsonDevices = json_encode($dataDevices, JSON_PRETTY_PRINT);
//print_r($jsonDevices . PHP_EOL);
// $logger->appendLog('ID del device al que está conectado el cliente: ' . $idDevice);
// $logger->appendLog('endpoint que se envía: '.$unmsBaseUri.'vault/' . $idDevice . '/credentials');
// $logger->appendLog('apiToken que se usa: '.$unmsAuthToken);
//print_r('ID del device al que está conectado el cliente: ' . $idDevice . PHP_EOL);
$responsePasswordVault = $clientUnms->request('GET', 'vault/' . $idClientDevice . '/credentials', [
'headers' => [
'X-Auth-Token' => $unmsAuthToken
]
]);
if ($responsePasswordVault->getStatusCode() === 200) {
$dataPasswordVault = json_decode($responsePasswordVault->getBody(), true);
$jsonPasswordVault = json_encode($dataPasswordVault, JSON_PRETTY_PRINT);
// $logger->appendLog($jsonPasswordVault); //Credenciales del dispositivo
//ejemplo de $responsePasswordVault en json: {"credentials":[{"username":"ubnt","readOnly":false,"password":"gYAIEK:Be}SK*01z5+/V","createdAt":"2025-03-27T23:29:54.948Z"}],"isPassphraseMissing":false}
//verificar si existe la contraseña en la respuesta de la API
if (isset($dataPasswordVault['credentials'][0]['password'])) {
//asignar el campo password a la variable dataPasswordVault
$dataPasswordVault = $dataPasswordVault['credentials'][0]['password'];
} else {
// echo "No se encontró la contraseña en la respuesta de la API." . PHP_EOL;
$logger->appendLog('Id device: ' . $idClientDevice);
$logger->appendLog('No se encontró una contraseña en la bóveda para la antena de este cliente, es altamente probable que conserve una contraseña conocida.');
return "Error: No se encontró una contraseña en la bóveda para la antena de este cliente, es altamente probable que conserve una contraseña conocida."; // Return early if the password is not found
}
return $dataPasswordVault;
} else {
// echo "Error en la solicitud. Código de estado HTTP: " . $responseDevicesBySite->getStatusCode() . PHP_EOL;
$logger->appendLog('Error en la solicitud. Código de estado HTTP: ' . $responsePasswordVault->getStatusCode());
return 'Error: Falla en la solicitud. Código de estado HTTP: ' . $responsePasswordVault->getStatusCode(); // Return early if the request fails
}
} else {
// echo "Error en la solicitud. Código de estado HTTP: " . $responseDevicesBySite->getStatusCode() . PHP_EOL;
$logger->appendLog('Error en la solicitud. Código de estado HTTP: ' . $responseDevicesBySite->getStatusCode());
return 'Error: Falla en la solicitud. Código de estado HTTP: ' . $responseDevicesBySite->getStatusCode(); // Return early if the request fails
}
}
}
global $clientFullName, $IpAddressClientId; // Variables globales para usar en la función getVaultCredentials
/**
* Genera una contraseña aleatoria y fuerte (CSPRNG).
*
* - Garantiza al menos 1 minúscula, 1 mayúscula, 1 dígito y 1 símbolo.
* - Usa random_int() / random_bytes() (CSPRNG).
* - Evita sesgos al seleccionar y mezcla la contraseña con Fisher-Yates.
*
* @param int $length Longitud deseada (mínimo 8). Default 20.
* @param bool $avoid_ambiguous Si true evita caracteres ambiguos como l, I, 0, O.
* @return string Contraseña generada.
* @throws InvalidArgumentException Si $length < 8.
*/
function generate_strong_password(int $length = 20, bool $avoid_ambiguous = true): string
{
if ($length < 8) {
throw new InvalidArgumentException('La longitud mínima recomendada es 8 caracteres.');
}
// Conjuntos de caracteres
$lower = 'abcdefghijklmnopqrstuvwxyz';
$upper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$digits = '0123456789';
// Conjunto de símbolos seguros; puedes ampliarlo si lo deseas
$symbols = '!@#$%&*()-_=+[]{};:,.<>?';
if ($avoid_ambiguous) {
// Eliminar caracteres ambiguos
$amb = ['l', 'I', '1', '0', 'O'];
$lower = str_replace($amb, '', $lower);
$upper = str_replace($amb, '', $upper);
$digits = str_replace($amb, '', $digits);
// los símbolos raramente confunden, los mantenemos
}
// Asegurar al menos uno de cada tipo
$required = [];
$required[] = $lower[random_int(0, strlen($lower) - 1)];
$required[] = $upper[random_int(0, strlen($upper) - 1)];
$required[] = $digits[random_int(0, strlen($digits) - 1)];
$required[] = $symbols[random_int(0, strlen($symbols) - 1)];
// Relleno con una mezcla de todos los conjuntos
$all = $lower . $upper . $digits . $symbols;
$remaining = $length - count($required);
$pwChars = $required;
for ($i = 0; $i < $remaining; $i++) {
$pwChars[] = $all[random_int(0, strlen($all) - 1)];
}
// Mezclar usando Fisher-Yates con random_int (sin sesgo)
$n = count($pwChars);
for ($i = $n - 1; $i > 0; $i--) {
$j = random_int(0, $i);
$tmp = $pwChars[$i];
$pwChars[$i] = $pwChars[$j];
$pwChars[$j] = $tmp;
}
return implode('', $pwChars);
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Buscador de Contraseñas de la bóveda para dispositivos de la red UISP</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
background-color: #f4f4f9;
color: #333;
}
h1 {
color: #2955CD;
text-align: center;
margin-bottom: 30px;
}
form {
max-width: 500px;
margin: 0 auto;
padding: 20px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
label {
display: block;
margin-bottom: 10px;
font-weight: bold;
color: #2955CD;
text-align: center;
}
input[type="text"] {
width: calc(100% - 20px);
/* Margen de 10px en cada lado */
padding: 10px;
margin-bottom: 20px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 16px;
box-sizing: border-box;
/* Asegura que el ancho incluya padding y borde */
}
button {
display: block;
/* Centra el botón */
width: auto;
/* Ancho automático */
margin: 0 auto;
/* Centrado horizontal */
padding: 10px 20px;
/* Tamaño acorde al texto */
background-color: #52BFFF;
color: white;
font-size: 16px;
font-weight: bold;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s ease;
}
button:hover {
background-color: #327BAC;
}
.resultado {
max-width: 500px;
margin: 20px auto;
padding: 20px;
background-color: #e6ffed;
border: 1px solid #39cc64;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.resultado h3 {
text-align: center;
/* Centrar el texto del título */
margin-bottom: 15px;
color: #219e44;
/* Color del texto en la caja de resultado */
}
.resultado h3.error {
color: red;
/* Color rojo para errores */
}
.caja-contrasena {
display: flex;
flex-direction: column;
align-items: center;
}
.caja-contrasena input {
width: calc(100% - 22px);
/* Ajuste para el padding y el borde */
padding: 10px;
margin-bottom: 15px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 16px;
color: #53535A;
/* Color del texto en la caja de resultado */
box-sizing: border-box;
/* Asegura que el ancho incluya padding y borde */
}
.caja-contrasena button {
padding: 10px 20px;
background-color: #39cc64;
color: white;
font-size: 14px;
font-weight: bold;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.caja-contrasena button:hover {
background-color: #2ab653;
}
.error {
color: red;
font-weight: bold;
text-align: center;
margin-top: 10px;
}
</style>
</head>
<body>
<h1>Buscador de contraseñas de dispositivos almacenados en la bóveda de UISP</h1>
<!-- Formulario -->
<form method="POST" action="">
<label for="identificador">Ingrese el ID del cliente, dirección IP o dirección MAC de la antena del cliente,
sectorial o AP:</label>
<input type="text" id="identificador" name="identificador" required
placeholder="Ejemplo: 3201 o 172.16.13.64 o 00:1A:2B:3C:4D:5E">
<button type="submit">
<img src="public.php?img=search-icon.png" alt="Obtener" style="width:16px; height:16px; vertical-align:middle; margin-right:5px;">Buscar contraseña
</button>
</form>
<!-- Resultado -->
<?php if ($respuestaAPI) { ?>
<script>
window.clientFullName = '<?php echo addslashes(!empty($clientFullName) ? $clientFullName : ''); ?>';
window.IpAddressClientId = '<?php echo addslashes(!empty($IpAddressClientId) ? $IpAddressClientId : ''); ?>';
window.clientIdentifierType = '<?php echo addslashes($clientIdentifierType); ?>';
</script>
<div class="resultado">
<h3 class="<?php echo $esError ? 'error' : ''; ?>">
<?php
$errorText = $esError ? htmlspecialchars(substr($respuestaAPI, 6)) : 'Contraseña obtenida';
echo $errorText;
?>
</h3>
<?php
// Mostrar info de cliente si la contraseña fue encontrada
if (!$esError) {
$displayName = !empty($clientFullName) ? htmlspecialchars($clientFullName) : 'Cliente';
$displayId = !empty($IpAddressClientId) ? htmlspecialchars($IpAddressClientId) : '';
?>
<div style="text-align:center; color:#53535A; font-size:15px; margin-bottom:10px;">
<b><?php echo $displayName; ?></b> <?php if($displayId) { echo "(" . $clientIdentifierType . ": <b>" . $displayId . "</b>)"; } ?>
</div>
<?php }
if ($esError && strpos($respuestaAPI, 'No se encontró una contraseña') !== false) { ?>
<div id="generar-contenedor" style="text-align:center; margin-top:20px;">
<div style="margin-bottom:10px; color: #2c1ee9ff; font-size:16px;">¿Desea intentar generar una contraseña para la antena de este cliente?</div>
<button id="btn-generar" type="button" style="background: #04bb75ff; color:white; font-weight:bold; border:none; border-radius:4px; padding:10px 20px; font-size:15px; cursor:pointer;">
<img src="public.php?img=generate-password-icon.png" alt="Generar" style="width:16px; height:16px; vertical-align:middle; margin-right:5px;">Generar una contraseña
</button>
</div>
<script>
document.getElementById('btn-generar').onclick = function() {
// Generar la contraseña y actualizar el atributo en el cliente
fetch('public.php?generate_password=1')
.then(response => response.text())
.then(password => {
document.getElementById('generar-contenedor').innerHTML = '';
let resultadoDiv = document.querySelector('.resultado');
resultadoDiv.querySelector('h3').textContent = 'Nueva contraseña generada';
// Mostrar info de cliente
let displayId = window.IpAddressClientId || '';
let displayName = window.clientFullName || 'Cliente';
let infoDiv = document.createElement('div');
infoDiv.style = 'text-align:center; color:#53535A; font-size:15px; margin-bottom:10px;';
infoDiv.innerHTML = `<b>${displayName}</b> ${displayId ? `(${window.clientIdentifierType || 'ID'}: <b>${displayId}</b>)` : ''}`;
resultadoDiv.appendChild(infoDiv);
let caja = document.createElement('div');
caja.className = 'caja-contrasena';
caja.innerHTML = `<input type="text" id="contrasena" value="${password}" readonly>
<button onclick="copiarAlPortapapeles()">
<img src='public.php?img=clipboard-icon.png' alt='Copiar' style='width:16px; height:16px; vertical-align:middle; margin-right:5px;'>Copiar Contraseña
</button>`;
resultadoDiv.appendChild(caja);
// Llamar al endpoint para actualizar la contraseña en el cliente
if (displayId) {
fetch('public.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `update_password=1&clientId=${encodeURIComponent(clientId)}&password=${encodeURIComponent(password)}`
})
.then(res => res.json())
.then(data => {
alert(data.success ? 'Contraseña actualizada' : 'Error: ' + data.message);
});
}
});
};
</script>
<?php } else if (!$esError) { ?>
<div class="caja-contrasena">
<input type="text" id="contrasena" value="<?php echo htmlspecialchars($respuestaAPI); ?>" readonly>
<button onclick="copiarAlPortapapeles()">
<img src="public.php?img=clipboard-icon.png" alt="Copiar" style="width:16px; height:16px; vertical-align:middle; margin-right:5px;">Copiar Contraseña
</button>
</div>
<?php } ?>
</div>
<?php } ?>
<script>
// Función para copiar la contraseña al portapapeles
function copiarAlPortapapeles() {
const contrasenaInput = document.getElementById('contrasena');
contrasenaInput.select();
contrasenaInput.setSelectionRange(0, 99999); // Para dispositivos móviles
document.execCommand('copy');
alert('Contraseña copiada al portapapeles: ' + contrasenaInput.value);
}
</script>
</body>
</html><q></q>