git commit -m "feat: sistema de configuración avanzada de IPs administrativas por segmento (v1.5.0)
- Agregar editor visual de configuración avanzada con protección por contraseña
- Implementar rangos de IPs administrativas personalizados por segmento
- Crear AdminRangeHelper.php para procesamiento de JSON por segmento
- Agregar botón ⚙️ de acceso rápido al editor
- Implementar modal de autenticación con validación de contraseña
- Agregar fallback inteligente a rangos globales para segmentos no configurados
Configuración avanzada:
- 3 campos nuevos en manifest.json (checkbox, JSON textarea, password)
- Editor visual completo con formularios dinámicos
- Agregar/eliminar segmentos y rangos en tiempo real
- Tabla de segmentos configurados con acciones
- Validación de cambios sin guardar
- Mensaje de éxito al guardar configuración
Backend:
- AdminRangeHelper.php: getSegmentLimits(), isAdminIpCustom(), validateConfigJson()
- IpSearchService.php: soporte para rangos personalizados con fallback
- Handler POST para guardar configuración JSON
Frontend:
- ~250 líneas de JavaScript para gestión del editor
- CSS responsive con soporte para temas claro/oscuro
- Interfaz amigable para usuarios no técnicos
Lógica de fallback:
- Checkbox desactivado → rangos globales para todos
- Checkbox activado + segmento en JSON → rangos del JSON
- Checkbox activado + segmento NO en JSON → fallback a rangos globales
Archivos creados:
- src/AdminRangeHelper.php
Archivos modificados:
- manifest.json: 3 campos nuevos, versión 1.5.0
- src/IpSearchService.php: lógica de fallback
- public.php: editor completo, modal, JavaScript
- CHANGELOG.md: entrada v1.5.0
- README.md: documentación de nuevos campos"
This commit is contained in:
parent
57f9646b71
commit
1b821410c1
32
CHANGELOG.md
32
CHANGELOG.md
@ -7,6 +7,38 @@ y este proyecto adhiere a [Semantic Versioning](https://semver.org/lang/es/).
|
||||
|
||||
---
|
||||
|
||||
## [1.5.0] - 2025-11-27
|
||||
|
||||
### ✨ Añadido
|
||||
- **Configuración Avanzada de IPs Administrativas por Segmento**: Sistema completo de gestión de rangos administrativos personalizados por segmento.
|
||||
- Editor visual protegido por contraseña
|
||||
- Configuración JSON por segmento
|
||||
- Múltiples rangos iniciales y finales por segmento
|
||||
- Interfaz amigable para gestionar rangos
|
||||
- **Botón de Configuración Avanzada** (⚙️): Acceso rápido al editor desde la interfaz principal.
|
||||
- **Modal de Autenticación**: Protección por contraseña para acceso al editor.
|
||||
- **Fallback Inteligente**: Segmentos no configurados en JSON usan automáticamente rangos globales.
|
||||
|
||||
### 🔧 Mejorado
|
||||
- **AdminRangeHelper.php**: Nueva clase helper para procesar configuración JSON por segmento.
|
||||
- **IpSearchService.php**: Lógica mejorada con soporte para rangos personalizados y fallback.
|
||||
- **Validación de Cambios**: Advertencia al salir del editor con cambios sin guardar.
|
||||
|
||||
### 🎨 Interfaz
|
||||
- Editor visual completo con formularios dinámicos
|
||||
- Tabla de segmentos configurados
|
||||
- Agregar/eliminar rangos en tiempo real
|
||||
- Diseño responsive con soporte para temas claro/oscuro
|
||||
|
||||
## [1.4.0] - 2025-11-27
|
||||
|
||||
### ✨ Añadido
|
||||
- **Rangos de IPs Administrativas Configurables**: Ahora puedes configurar qué rangos de IPs se consideran administrativas mediante 4 campos numéricos en la configuración del plugin (inicio/fin de rango inicial y final), en lugar de tener valores fijos (1-30 y 254).
|
||||
|
||||
### 🔧 Mejorado
|
||||
- **Flexibilidad**: Permite adaptar la clasificación de IPs según las necesidades específicas de cada red.
|
||||
- **Backend y Frontend**: Toda la lógica de clasificación ahora usa configuración dinámica.
|
||||
|
||||
## [1.3.3] - 2025-11-27
|
||||
|
||||
### ✨ Añadido
|
||||
|
||||
11
README.md
11
README.md
@ -1,6 +1,6 @@
|
||||
# SIIP - Buscador de IP's Disponibles UISP
|
||||
|
||||
[](manifest.json)
|
||||
[](manifest.json)
|
||||
[](https://uisp.com/)
|
||||
[](https://uisp.com/)
|
||||
|
||||
@ -93,6 +93,13 @@ Después de instalar el plugin, configura los siguientes parámetros:
|
||||
|-----------|-------------|-----|
|
||||
| **Token de la API UNMS** | Token de UNMS (34 caracteres) | Para búsqueda de IPs en dispositivos de red |
|
||||
| **Habilitar verificación por Ping** | Activa modo de verificación por ping | Permite verificar disponibilidad real de IPs |
|
||||
| **Inicio primer rango admin** | Primera IP del rango administrativo inicial (default: 1) | Define el inicio del primer rango de IPs administrativas |
|
||||
| **Fin primer rango admin** | Última IP del rango administrativo inicial (default: 30) | Define el fin del primer rango de IPs administrativas |
|
||||
| **Inicio rango admin final** | Primera IP del rango administrativo final (default: 254) | Define el inicio del rango final de IPs administrativas |
|
||||
| **Fin rango admin final** | Última IP del rango administrativo final (default: 254) | Define el fin del rango final de IPs administrativas |
|
||||
| **Usar rangos personalizados por segmento** | Habilita configuración avanzada por segmento | Permite definir rangos específicos para cada segmento de red |
|
||||
| **Configuración JSON de rangos** | JSON con rangos por segmento | Configuración detallada de rangos administrativos personalizados |
|
||||
| **Contraseña de administrador** | Contraseña para editor avanzado | Protege el acceso al editor de configuración avanzada |
|
||||
| **Debug Mode** | Modo de depuración | Habilita logs más detallados |
|
||||
| **Enable debug logs** | Logs verbosos | Información adicional en logs |
|
||||
|
||||
@ -600,5 +607,5 @@ Este plugin es propiedad de **SIIP Internet**. Todos los derechos reservados.
|
||||
|
||||
---
|
||||
|
||||
**Versión**: 1.3.3
|
||||
**Versión**: 1.5.0
|
||||
**Última actualización**: 27 de noviembre de 2025
|
||||
|
||||
@ -1 +1 @@
|
||||
{"ipserver":"sistema.siip.mx","apitoken":"393eb3d0-9b46-4a47-b9b4-473e4e24a89c","unmsApiToken":"393eb3d0-9b46-4a47-b9b4-473e4e24a89c","debugMode":true,"logging_level":true,"enablePingVerification":true}
|
||||
{"ipserver":"sistema.siip.mx","apitoken":"393eb3d0-9b46-4a47-b9b4-473e4e24a89c","unmsApiToken":"393eb3d0-9b46-4a47-b9b4-473e4e24a89c","debugMode":true,"logging_level":true,"enablePingVerification":true,"adminRangeStart":"2","adminRangeEnd":"50","adminRangeFinalStart":"254","adminRangeFinalEnd":"255"}
|
||||
165
data/plugin.log
165
data/plugin.log
@ -3581,3 +3581,168 @@ Verificando lote de 1 IPs
|
||||
Iniciando verificación por ping de 1 IPs (lotes de 1)
|
||||
Procesando lote 1/1 (1 IPs)
|
||||
Lote completado: 0/1 IPs responden (1.10s)
|
||||
=== NUEVA PETICIÓN ===
|
||||
Método: GET
|
||||
POST data: []
|
||||
GET data: []
|
||||
Content-Type:
|
||||
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:145.0) Gecko/20100101 Firefox/145.0
|
||||
Acceso a la interfaz pública de búsqueda de IPs
|
||||
=== NUEVA PETICIÓN ===
|
||||
Método: POST
|
||||
POST data: {"action":"search","segment":"13","verify_ping":"false"}
|
||||
GET data: []
|
||||
Content-Type: multipart/form-data; boundary=----geckoformboundaryd9c9bd779b06b9bab23d3ccee85be660
|
||||
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:145.0) Gecko/20100101 Firefox/145.0
|
||||
>>> Entrando al handler de búsqueda AJAX
|
||||
Configuración cargada: {"ipserver":"sistema.siip.mx","hasUnmsToken":true,"hasApiToken":true}
|
||||
Buscando IPs en segmento: 13 (Búsqueda inicial rápida)
|
||||
URL de API: https://sistema.siip.mx/nms/api/v2.1/devices/ips?suspended=false&management=true&includeObsolete=true
|
||||
Iniciando conexión a API: https://sistema.siip.mx/nms/api/v2.1/devices/ips?suspended=false&management=true&includeObsolete=true
|
||||
Respuesta HTTP: 200
|
||||
Longitud de respuesta: 48923 bytes
|
||||
IPs obtenidas exitosamente: 3106 direcciones
|
||||
Búsqueda de IPs en segmento 172.16.13.x - Disponibles: 153, En uso: 101
|
||||
Resultado de búsqueda: {"success":true,"ipsDisponibles":153,"ipsEnUso":101}
|
||||
<<< Finalizando handler de búsqueda AJAX
|
||||
=== NUEVA PETICIÓN ===
|
||||
Método: POST
|
||||
POST data: {"action":"verify_batch","ips":"[\"172.16.13.31\"]"}
|
||||
GET data: []
|
||||
Content-Type: multipart/form-data; boundary=----geckoformboundary6eaecbebd5f8b555bb94571735b05c99
|
||||
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:145.0) Gecko/20100101 Firefox/145.0
|
||||
>>> Entrando al handler de verificación por lotes (verify_batch)
|
||||
Verificando lote de 1 IPs
|
||||
Iniciando verificación por ping de 1 IPs (lotes de 1)
|
||||
Procesando lote 1/1 (1 IPs)
|
||||
Lote completado: 0/1 IPs responden (1.10s)
|
||||
=== NUEVA PETICIÓN ===
|
||||
Método: POST
|
||||
POST data: {"action":"verify_batch","ips":"[\"172.16.13.33\"]"}
|
||||
GET data: []
|
||||
Content-Type: multipart/form-data; boundary=----geckoformboundaryf35d820ad41ba3e6d10728762ee63d9
|
||||
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:145.0) Gecko/20100101 Firefox/145.0
|
||||
>>> Entrando al handler de verificación por lotes (verify_batch)
|
||||
Verificando lote de 1 IPs
|
||||
Iniciando verificación por ping de 1 IPs (lotes de 1)
|
||||
Procesando lote 1/1 (1 IPs)
|
||||
Lote completado: 0/1 IPs responden (1.10s)
|
||||
=== NUEVA PETICIÓN ===
|
||||
Método: POST
|
||||
POST data: {"action":"verify_batch","ips":"[\"172.16.13.34\"]"}
|
||||
GET data: []
|
||||
Content-Type: multipart/form-data; boundary=----geckoformboundaryee78e7f8a9ba81f13ea3e610be045399
|
||||
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:145.0) Gecko/20100101 Firefox/145.0
|
||||
>>> Entrando al handler de verificación por lotes (verify_batch)
|
||||
Verificando lote de 1 IPs
|
||||
Iniciando verificación por ping de 1 IPs (lotes de 1)
|
||||
Procesando lote 1/1 (1 IPs)
|
||||
Lote completado: 0/1 IPs responden (1.10s)
|
||||
=== NUEVA PETICIÓN ===
|
||||
Método: POST
|
||||
POST data: {"action":"verify_batch","ips":"[\"172.16.13.35\"]"}
|
||||
GET data: []
|
||||
Content-Type: multipart/form-data; boundary=----geckoformboundary8e80bfbb0ef83ea63233d967489cc33c
|
||||
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:145.0) Gecko/20100101 Firefox/145.0
|
||||
>>> Entrando al handler de verificación por lotes (verify_batch)
|
||||
Verificando lote de 1 IPs
|
||||
Iniciando verificación por ping de 1 IPs (lotes de 1)
|
||||
Procesando lote 1/1 (1 IPs)
|
||||
Lote completado: 0/1 IPs responden (1.10s)
|
||||
=== NUEVA PETICIÓN ===
|
||||
Método: POST
|
||||
POST data: {"action":"verify_batch","ips":"[\"172.16.13.36\"]"}
|
||||
GET data: []
|
||||
Content-Type: multipart/form-data; boundary=----geckoformboundary59280d458af805620af084daa0eaea4
|
||||
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:145.0) Gecko/20100101 Firefox/145.0
|
||||
>>> Entrando al handler de verificación por lotes (verify_batch)
|
||||
Verificando lote de 1 IPs
|
||||
Iniciando verificación por ping de 1 IPs (lotes de 1)
|
||||
Procesando lote 1/1 (1 IPs)
|
||||
Lote completado: 0/1 IPs responden (1.10s)
|
||||
=== NUEVA PETICIÓN ===
|
||||
Método: GET
|
||||
POST data: []
|
||||
GET data: []
|
||||
Content-Type:
|
||||
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:145.0) Gecko/20100101 Firefox/145.0
|
||||
Acceso a la interfaz pública de búsqueda de IPs
|
||||
=== NUEVA PETICIÓN ===
|
||||
Método: GET
|
||||
POST data: []
|
||||
GET data: []
|
||||
Content-Type:
|
||||
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:145.0) Gecko/20100101 Firefox/145.0
|
||||
Acceso a la interfaz pública de búsqueda de IPs
|
||||
=== NUEVA PETICIÓN ===
|
||||
Método: POST
|
||||
POST data: {"action":"search","segment":"100","verify_ping":"false"}
|
||||
GET data: []
|
||||
Content-Type: multipart/form-data; boundary=----geckoformboundary5ddd3ee57a052d31763847c17daef50
|
||||
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:145.0) Gecko/20100101 Firefox/145.0
|
||||
>>> Entrando al handler de búsqueda AJAX
|
||||
Configuración cargada: {"ipserver":"sistema.siip.mx","hasUnmsToken":true,"hasApiToken":true}
|
||||
Buscando IPs en segmento: 100 (Búsqueda inicial rápida)
|
||||
URL de API: https://sistema.siip.mx/nms/api/v2.1/devices/ips?suspended=false&management=true&includeObsolete=true
|
||||
Iniciando conexión a API: https://sistema.siip.mx/nms/api/v2.1/devices/ips?suspended=false&management=true&includeObsolete=true
|
||||
Respuesta HTTP: 200
|
||||
Longitud de respuesta: 48923 bytes
|
||||
IPs obtenidas exitosamente: 3106 direcciones
|
||||
Búsqueda de IPs en segmento 172.16.100.x - Disponibles: 123, En uso: 131
|
||||
Resultado de búsqueda: {"success":true,"ipsDisponibles":123,"ipsEnUso":131}
|
||||
<<< Finalizando handler de búsqueda AJAX
|
||||
=== NUEVA PETICIÓN ===
|
||||
Método: POST
|
||||
POST data: {"action":"verify_batch","ips":"[\"172.16.100.55\"]"}
|
||||
GET data: []
|
||||
Content-Type: multipart/form-data; boundary=----geckoformboundary8344b24563663e659e7e20943555f60
|
||||
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:145.0) Gecko/20100101 Firefox/145.0
|
||||
>>> Entrando al handler de verificación por lotes (verify_batch)
|
||||
Verificando lote de 1 IPs
|
||||
Iniciando verificación por ping de 1 IPs (lotes de 1)
|
||||
Procesando lote 1/1 (1 IPs)
|
||||
Lote completado: 0/1 IPs responden (1.10s)
|
||||
=== NUEVA PETICIÓN ===
|
||||
Método: POST
|
||||
POST data: {"action":"verify_batch","ips":"[\"172.16.100.57\"]"}
|
||||
GET data: []
|
||||
Content-Type: multipart/form-data; boundary=----geckoformboundary4d513f2e427d730558bc8fe000c66683
|
||||
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:145.0) Gecko/20100101 Firefox/145.0
|
||||
>>> Entrando al handler de verificación por lotes (verify_batch)
|
||||
Verificando lote de 1 IPs
|
||||
Iniciando verificación por ping de 1 IPs (lotes de 1)
|
||||
Procesando lote 1/1 (1 IPs)
|
||||
Lote completado: 0/1 IPs responden (1.10s)
|
||||
=== NUEVA PETICIÓN ===
|
||||
Método: POST
|
||||
POST data: {"action":"verify_batch","ips":"[\"172.16.100.58\"]"}
|
||||
GET data: []
|
||||
Content-Type: multipart/form-data; boundary=----geckoformboundaryfeb931394f33d483c38cb8d49c83bab1
|
||||
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:145.0) Gecko/20100101 Firefox/145.0
|
||||
>>> Entrando al handler de verificación por lotes (verify_batch)
|
||||
Verificando lote de 1 IPs
|
||||
Iniciando verificación por ping de 1 IPs (lotes de 1)
|
||||
Procesando lote 1/1 (1 IPs)
|
||||
Lote completado: 0/1 IPs responden (1.12s)
|
||||
=== NUEVA PETICIÓN ===
|
||||
Método: POST
|
||||
POST data: {"action":"verify_batch","ips":"[\"172.16.100.59\"]"}
|
||||
GET data: []
|
||||
Content-Type: multipart/form-data; boundary=----geckoformboundarye70755c6fad06c208f6ad0d8651cb811
|
||||
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:145.0) Gecko/20100101 Firefox/145.0
|
||||
>>> Entrando al handler de verificación por lotes (verify_batch)
|
||||
Verificando lote de 1 IPs
|
||||
Iniciando verificación por ping de 1 IPs (lotes de 1)
|
||||
Procesando lote 1/1 (1 IPs)
|
||||
Lote completado: 0/1 IPs responden (1.12s)
|
||||
=== NUEVA PETICIÓN ===
|
||||
Método: POST
|
||||
POST data: {"action":"verify_batch","ips":"[\"172.16.100.69\"]"}
|
||||
GET data: []
|
||||
Content-Type: multipart/form-data; boundary=----geckoformboundaryc8832b02f5f8eb4a670c6cc46e34ce90
|
||||
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:145.0) Gecko/20100101 Firefox/145.0
|
||||
>>> Entrando al handler de verificación por lotes (verify_batch)
|
||||
Verificando lote de 1 IPs
|
||||
Iniciando verificación por ping de 1 IPs (lotes de 1)
|
||||
Procesando lote 1/1 (1 IPs)
|
||||
Lote completado: 0/1 IPs responden (1.10s)
|
||||
|
||||
@ -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.3.3",
|
||||
"version": "1.5.0",
|
||||
"ucrmVersionCompliancy": {
|
||||
"min": "1.0.0",
|
||||
"max": null
|
||||
@ -58,6 +58,60 @@
|
||||
"description": "Permite verificar la disponibilidad real de las IPs mediante ping. Cuando está habilitado, el usuario puede elegir si desea verificar las IPs con ping antes de mostrarlas como disponibles. Esto ayuda a detectar dispositivos no registrados en UISP.",
|
||||
"required": 0,
|
||||
"type": "checkbox"
|
||||
},
|
||||
{
|
||||
"key": "adminRangeStart",
|
||||
"label": "Inicio primer rango admin",
|
||||
"description": "Primera IP del rango administrativo inicial (ej: 1). Las IPs en este rango se marcarán como administrativas.",
|
||||
"required": 0,
|
||||
"type": "number",
|
||||
"default": 1
|
||||
},
|
||||
{
|
||||
"key": "adminRangeEnd",
|
||||
"label": "Fin primer rango admin",
|
||||
"description": "Última IP del rango administrativo inicial (ej: 30). Las IPs desde 'Inicio primer rango' hasta este valor se marcarán como administrativas.",
|
||||
"required": 0,
|
||||
"type": "number",
|
||||
"default": 30
|
||||
},
|
||||
{
|
||||
"key": "adminRangeFinalStart",
|
||||
"label": "Inicio rango admin final",
|
||||
"description": "Primera IP del rango administrativo final (ej: 254). Útil para reservar las últimas IPs del segmento.",
|
||||
"required": 0,
|
||||
"type": "number",
|
||||
"default": 254
|
||||
},
|
||||
{
|
||||
"key": "adminRangeFinalEnd",
|
||||
"label": "Fin rango admin final",
|
||||
"description": "Última IP del rango administrativo final (ej: 254). Las IPs desde 'Inicio rango final' hasta este valor se marcarán como administrativas.",
|
||||
"required": 0,
|
||||
"type": "number",
|
||||
"default": 254
|
||||
},
|
||||
{
|
||||
"key": "useCustomAdminRanges",
|
||||
"label": "Usar rangos de IPs administrativas personalizados por segmento",
|
||||
"description": "Habilita configuración personalizada por segmento en lugar de rangos globales. Permite definir rangos específicos para cada segmento de red.",
|
||||
"required": 0,
|
||||
"type": "checkbox"
|
||||
},
|
||||
{
|
||||
"key": "customAdminRangesJson",
|
||||
"label": "Configuración JSON de rangos administrativos",
|
||||
"description": "JSON con rangos administrativos por segmento. Formato: {\"segmentos\": [{\"segmento\": \"18\", \"administrativas_iniciales\": [{\"inicio\": 1, \"hasta\": 50}], \"administrativas_finales\": [{\"inicio\": 253, \"hasta\": 255}]}]}",
|
||||
"required": 1,
|
||||
"type": "textarea",
|
||||
"default": "{\"segmentos\":[]}"
|
||||
},
|
||||
{
|
||||
"key": "adminPassword",
|
||||
"label": "Contraseña de administrador para configuración avanzada",
|
||||
"description": "Contraseña requerida para acceder al editor visual de configuración avanzada de rangos administrativos.",
|
||||
"required": 1,
|
||||
"type": "password"
|
||||
}
|
||||
],
|
||||
"menu": [
|
||||
|
||||
585
public.php
585
public.php
@ -30,12 +30,43 @@ require_once __DIR__ . '/vendor/autoload.php';
|
||||
require_once __DIR__ . '/src/IpSearchService.php';
|
||||
require_once __DIR__ . '/src/PingService.php';
|
||||
require_once __DIR__ . '/src/ApiHandlers.php';
|
||||
require_once __DIR__ . '/src/AdminRangeHelper.php';
|
||||
|
||||
use Ubnt\UcrmPluginSdk\Service\PluginLogManager;
|
||||
use Ubnt\UcrmPluginSdk\Service\PluginConfigManager;
|
||||
use Ubnt\UcrmPluginSdk\Service\UcrmApi;
|
||||
use SiipAvailableIps\IpSearchService;
|
||||
use SiipAvailableIps\PingService;
|
||||
|
||||
// Manejo de errores para el handler de configuración avanzada
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 1);
|
||||
|
||||
// Handler para guardar configuración avanzada
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['save_advanced_config'])) {
|
||||
try {
|
||||
$jsonData = $_POST['save_advanced_config'];
|
||||
|
||||
// Validar JSON
|
||||
$validation = \SiipAvailableIps\AdminRangeHelper::validateConfigJson($jsonData);
|
||||
if (!$validation['valid']) {
|
||||
die('Error: ' . $validation['error']);
|
||||
}
|
||||
|
||||
// Guardar en config.json
|
||||
$configManager = PluginConfigManager::create();
|
||||
$config = $configManager->loadConfig();
|
||||
$config['customAdminRangesJson'] = $jsonData;
|
||||
$configManager->saveConfig($config);
|
||||
|
||||
// Redirigir con mensaje de éxito
|
||||
header('Location: ?success=1');
|
||||
exit;
|
||||
} catch (Exception $e) {
|
||||
die('Error al guardar configuración: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Get UCRM log manager
|
||||
$log = PluginLogManager::create();
|
||||
|
||||
@ -843,18 +874,313 @@ $pingEnabled = !empty($config['enablePingVerification']) && ($config['enablePing
|
||||
right: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Botón de configuración avanzada */
|
||||
.config-toggle {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
background: var(--card-bg);
|
||||
border: 2px solid var(--card-border);
|
||||
color: var(--text-primary);
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: var(--shadow-lg);
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.config-toggle:hover {
|
||||
transform: scale(1.1) rotate(90deg);
|
||||
box-shadow: var(--shadow-xl);
|
||||
}
|
||||
|
||||
/* Ajustar posición del botón de tema */
|
||||
.theme-toggle {
|
||||
top: 80px; /* Debajo del botón de configuración */
|
||||
}
|
||||
|
||||
/* Modal de contraseña */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
z-index: 2000;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: var(--card-bg);
|
||||
border: 2px solid var(--card-border);
|
||||
border-radius: 16px;
|
||||
padding: 40px;
|
||||
max-width: 400px;
|
||||
width: 90%;
|
||||
box-shadow: var(--shadow-xl);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.modal-content h3 {
|
||||
margin-bottom: 10px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.modal-content p {
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.modal-input {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 2px solid var(--card-border);
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: var(--text-primary);
|
||||
font-size: 1rem;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Editor Visual */
|
||||
.advanced-editor {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--dark-bg);
|
||||
z-index: 1500;
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.editor-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30px;
|
||||
padding: 20px;
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--card-border);
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.editor-header h2 {
|
||||
margin: 0;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.editor-content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.add-segment-form {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.range-input-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.range-input-group input {
|
||||
width: 100px;
|
||||
padding: 8px;
|
||||
border: 2px solid var(--card-border);
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.range-input-group span {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.btn-small {
|
||||
padding: 6px 12px;
|
||||
font-size: 0.85rem;
|
||||
background: rgba(102, 126, 234, 0.2);
|
||||
border: 1px solid rgba(102, 126, 234, 0.3);
|
||||
border-radius: 8px;
|
||||
color: #667eea;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-small:hover {
|
||||
background: rgba(102, 126, 234, 0.3);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.editor-actions {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
justify-content: center;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.config-toggle {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
font-size: 1.3rem;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
}
|
||||
|
||||
.theme-toggle {
|
||||
top: 70px;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
padding: 30px 20px;
|
||||
}
|
||||
|
||||
.editor-header {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.range-input-group {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body data-theme="dark">
|
||||
<!-- Botón de configuración avanzada -->
|
||||
<button id="advancedConfigBtn" class="config-toggle" title="Configuración avanzada">
|
||||
<span>⚙️</span>
|
||||
</button>
|
||||
|
||||
<!-- Botón de cambio de tema -->
|
||||
<button id="themeToggle" class="theme-toggle" title="Cambiar tema">
|
||||
<span id="themeIcon">🌙</span>
|
||||
</button>
|
||||
|
||||
<!-- Modal de contraseña -->
|
||||
<div id="passwordModal" class="modal" style="display: none;">
|
||||
<div class="modal-content">
|
||||
<h3>🔒 Acceso Restringido</h3>
|
||||
<p>Ingresa la contraseña de administrador para acceder a la configuración avanzada</p>
|
||||
<input type="password" id="adminPasswordInput" class="modal-input" placeholder="Contraseña" />
|
||||
<div class="modal-actions">
|
||||
<button class="btn btn-primary" onclick="validatePassword()">Acceder</button>
|
||||
<button class="btn" onclick="closePasswordModal()">Cancelar</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Editor Visual de Configuración Avanzada -->
|
||||
<div id="advancedEditor" class="advanced-editor" style="display: none;">
|
||||
<div class="editor-header">
|
||||
<h2>⚙️ Editor de Rangos Administrativos por Segmento</h2>
|
||||
<button class="btn" onclick="returnToMain()">← Volver</button>
|
||||
</div>
|
||||
|
||||
<div class="editor-content">
|
||||
<!-- Formulario para agregar segmento -->
|
||||
<div class="card">
|
||||
<h3>Agregar Nuevo Segmento</h3>
|
||||
<div class="add-segment-form">
|
||||
<div class="form-row">
|
||||
<div class="input-group">
|
||||
<label>Segmento</label>
|
||||
<input type="number" id="newSegment" placeholder="ej: 18" min="0" max="255" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4>Rangos Iniciales (IPs al inicio del segmento)</h4>
|
||||
<div id="initialRangesContainer">
|
||||
<div class="range-input-group">
|
||||
<input type="number" class="range-start" placeholder="Desde" min="1" max="254" />
|
||||
<span>hasta</span>
|
||||
<input type="number" class="range-end" placeholder="Hasta" min="1" max="254" />
|
||||
<button class="btn-small" onclick="addInitialRange()">+ Agregar Rango</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4>Rangos Finales (IPs al final del segmento)</h4>
|
||||
<div id="finalRangesContainer">
|
||||
<div class="range-input-group">
|
||||
<input type="number" class="range-start" placeholder="Desde" min="1" max="254" />
|
||||
<span>hasta</span>
|
||||
<input type="number" class="range-end" placeholder="Hasta" min="1" max="254" />
|
||||
<button class="btn-small" onclick="addFinalRange()">+ Agregar Rango</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary" onclick="addSegmentToTable()">✅ Agregar Segmento</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tabla de segmentos configurados -->
|
||||
<div class="card">
|
||||
<h3>Segmentos Configurados</h3>
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Segmento</th>
|
||||
<th>Rangos Iniciales</th>
|
||||
<th>Rangos Finales</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="segmentsTableBody">
|
||||
<!-- Filas dinámicas -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor-actions">
|
||||
<button class="btn btn-primary" onclick="saveConfiguration()">💾 Guardar Configuración</button>
|
||||
<button class="btn" onclick="returnToMain()">Cancelar</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<div class="logo">🌐</div>
|
||||
<h1>Buscador de IPs Disponibles</h1>
|
||||
<p class="subtitle">Sistema de gestión de direcciones IP para UISP</p>
|
||||
<?php if (isset($_GET['success']) && $_GET['success'] == '1'): ?>
|
||||
<div style="background: rgba(79, 209, 197, 0.2); border: 1px solid rgba(79, 209, 197, 0.4); color: #4fd1c5; padding: 15px; border-radius: 12px; margin-top: 20px; text-align: center;">
|
||||
✅ Configuración avanzada guardada exitosamente
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
@ -957,6 +1283,14 @@ $pingEnabled = !empty($config['enablePingVerification']) && ($config['enablePing
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Configuración de rangos de IPs administrativas
|
||||
const adminConfig = {
|
||||
rangeStart: <?php echo isset($config['adminRangeStart']) ? (int)$config['adminRangeStart'] : 1; ?>,
|
||||
rangeEnd: <?php echo isset($config['adminRangeEnd']) ? (int)$config['adminRangeEnd'] : 30; ?>,
|
||||
finalStart: <?php echo isset($config['adminRangeFinalStart']) ? (int)$config['adminRangeFinalStart'] : 254; ?>,
|
||||
finalEnd: <?php echo isset($config['adminRangeFinalEnd']) ? (int)$config['adminRangeFinalEnd'] : 254; ?>
|
||||
};
|
||||
|
||||
const searchForm = document.getElementById('searchForm');
|
||||
const searchBtn = document.getElementById('searchBtn');
|
||||
const loading = document.getElementById('loading');
|
||||
@ -969,6 +1303,249 @@ $pingEnabled = !empty($config['enablePingVerification']) && ($config['enablePing
|
||||
const cancelBtn = document.getElementById('cancelBtn');
|
||||
let verificationCancelled = false;
|
||||
|
||||
// ========== ADVANCED CONFIGURATION EDITOR ==========
|
||||
|
||||
// Variables del editor
|
||||
let editorData = { segmentos: [] };
|
||||
let hasUnsavedChanges = false;
|
||||
const adminPassword = '<?php echo addslashes($config['adminPassword'] ?? ''); ?>';
|
||||
const customRangesJson = '<?php echo addslashes($config['customAdminRangesJson'] ?? '{"segmentos":[]}'); ?>';
|
||||
|
||||
// Event listener para botón de configuración avanzada
|
||||
document.getElementById('advancedConfigBtn').addEventListener('click', openAdvancedConfig);
|
||||
|
||||
function openAdvancedConfig() {
|
||||
document.getElementById('passwordModal').style.display = 'flex';
|
||||
document.getElementById('adminPasswordInput').value = '';
|
||||
document.getElementById('adminPasswordInput').focus();
|
||||
}
|
||||
|
||||
function closePasswordModal() {
|
||||
document.getElementById('passwordModal').style.display = 'none';
|
||||
}
|
||||
|
||||
function validatePassword() {
|
||||
const inputPassword = document.getElementById('adminPasswordInput').value;
|
||||
|
||||
if (inputPassword === adminPassword) {
|
||||
closePasswordModal();
|
||||
showEditor();
|
||||
} else {
|
||||
alert('❌ Contraseña incorrecta');
|
||||
document.getElementById('adminPasswordInput').value = '';
|
||||
document.getElementById('adminPasswordInput').focus();
|
||||
}
|
||||
}
|
||||
|
||||
// Permitir Enter en el campo de contraseña
|
||||
document.getElementById('adminPasswordInput').addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
validatePassword();
|
||||
}
|
||||
});
|
||||
|
||||
function showEditor() {
|
||||
document.querySelector('.container').style.display = 'none';
|
||||
document.getElementById('advancedEditor').style.display = 'block';
|
||||
loadEditorData();
|
||||
hasUnsavedChanges = false;
|
||||
}
|
||||
|
||||
function returnToMain() {
|
||||
if (hasUnsavedChanges) {
|
||||
if (!confirm('⚠️ Tienes cambios sin guardar. ¿Deseas salir de todos modos?')) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('advancedEditor').style.display = 'none';
|
||||
document.querySelector('.container').style.display = 'block';
|
||||
hasUnsavedChanges = false;
|
||||
}
|
||||
|
||||
function loadEditorData() {
|
||||
try {
|
||||
editorData = JSON.parse(customRangesJson);
|
||||
if (!editorData.segmentos) {
|
||||
editorData = { segmentos: [] };
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error parsing JSON:', e);
|
||||
editorData = { segmentos: [] };
|
||||
}
|
||||
renderSegmentsTable();
|
||||
}
|
||||
|
||||
function renderSegmentsTable() {
|
||||
const tbody = document.getElementById('segmentsTableBody');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if (editorData.segmentos.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="4" style="text-align: center; color: var(--text-secondary);">No hay segmentos configurados</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
editorData.segmentos.forEach((seg, index) => {
|
||||
const row = document.createElement('tr');
|
||||
|
||||
// Formatear rangos iniciales
|
||||
const iniciales = seg.administrativas_iniciales.map(r => `${r.inicio}-${r.hasta}`).join(', ');
|
||||
|
||||
// Formatear rangos finales
|
||||
const finales = seg.administrativas_finales.map(r => `${r.inicio}-${r.hasta}`).join(', ');
|
||||
|
||||
row.innerHTML = `
|
||||
<td><strong>172.16.${seg.segmento}.x</strong></td>
|
||||
<td>${iniciales || '-'}</td>
|
||||
<td>${finales || '-'}</td>
|
||||
<td>
|
||||
<button class="btn-small" onclick="deleteSegment(${index})" style="background: rgba(245, 87, 108, 0.2); color: #ff6b6b; border-color: rgba(245, 87, 108, 0.3);">
|
||||
🗑️ Eliminar
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
function addInitialRange() {
|
||||
const container = document.getElementById('initialRangesContainer');
|
||||
const newRange = document.createElement('div');
|
||||
newRange.className = 'range-input-group';
|
||||
newRange.innerHTML = `
|
||||
<input type="number" class="range-start" placeholder="Desde" min="1" max="254" />
|
||||
<span>hasta</span>
|
||||
<input type="number" class="range-end" placeholder="Hasta" min="1" max="254" />
|
||||
<button class="btn-small" onclick="this.parentElement.remove()" style="background: rgba(245, 87, 108, 0.2); color: #ff6b6b;">
|
||||
✖ Quitar
|
||||
</button>
|
||||
`;
|
||||
container.appendChild(newRange);
|
||||
}
|
||||
|
||||
function addFinalRange() {
|
||||
const container = document.getElementById('finalRangesContainer');
|
||||
const newRange = document.createElement('div');
|
||||
newRange.className = 'range-input-group';
|
||||
newRange.innerHTML = `
|
||||
<input type="number" class="range-start" placeholder="Desde" min="1" max="254" />
|
||||
<span>hasta</span>
|
||||
<input type="number" class="range-end" placeholder="Hasta" min="1" max="254" />
|
||||
<button class="btn-small" onclick="this.parentElement.remove()" style="background: rgba(245, 87, 108, 0.2); color: #ff6b6b;">
|
||||
✖ Quitar
|
||||
</button>
|
||||
`;
|
||||
container.appendChild(newRange);
|
||||
}
|
||||
|
||||
function addSegmentToTable() {
|
||||
const segmentInput = document.getElementById('newSegment');
|
||||
const segment = segmentInput.value;
|
||||
|
||||
if (!segment || segment < 0 || segment > 255) {
|
||||
alert('❌ Por favor ingresa un segmento válido (0-255)');
|
||||
return;
|
||||
}
|
||||
|
||||
// Verificar si el segmento ya existe
|
||||
if (editorData.segmentos.some(s => s.segmento == segment)) {
|
||||
alert('⚠️ Este segmento ya está configurado');
|
||||
return;
|
||||
}
|
||||
|
||||
// Obtener rangos iniciales
|
||||
const initialRanges = [];
|
||||
const initialContainer = document.getElementById('initialRangesContainer');
|
||||
const initialGroups = initialContainer.querySelectorAll('.range-input-group');
|
||||
|
||||
initialGroups.forEach(group => {
|
||||
const start = parseInt(group.querySelector('.range-start').value);
|
||||
const end = parseInt(group.querySelector('.range-end').value);
|
||||
|
||||
if (start && end && start <= end) {
|
||||
initialRanges.push({ inicio: start, hasta: end });
|
||||
}
|
||||
});
|
||||
|
||||
// Obtener rangos finales
|
||||
const finalRanges = [];
|
||||
const finalContainer = document.getElementById('finalRangesContainer');
|
||||
const finalGroups = finalContainer.querySelectorAll('.range-input-group');
|
||||
|
||||
finalGroups.forEach(group => {
|
||||
const start = parseInt(group.querySelector('.range-start').value);
|
||||
const end = parseInt(group.querySelector('.range-end').value);
|
||||
|
||||
if (start && end && start <= end) {
|
||||
finalRanges.push({ inicio: start, hasta: end });
|
||||
}
|
||||
});
|
||||
|
||||
// Agregar segmento
|
||||
editorData.segmentos.push({
|
||||
segmento: segment,
|
||||
administrativas_iniciales: initialRanges,
|
||||
administrativas_finales: finalRanges
|
||||
});
|
||||
|
||||
// Limpiar formulario
|
||||
segmentInput.value = '';
|
||||
initialContainer.innerHTML = `
|
||||
<div class="range-input-group">
|
||||
<input type="number" class="range-start" placeholder="Desde" min="1" max="254" />
|
||||
<span>hasta</span>
|
||||
<input type="number" class="range-end" placeholder="Hasta" min="1" max="254" />
|
||||
<button class="btn-small" onclick="addInitialRange()">+ Agregar Rango</button>
|
||||
</div>
|
||||
`;
|
||||
finalContainer.innerHTML = `
|
||||
<div class="range-input-group">
|
||||
<input type="number" class="range-start" placeholder="Desde" min="1" max="254" />
|
||||
<span>hasta</span>
|
||||
<input type="number" class="range-end" placeholder="Hasta" min="1" max="254" />
|
||||
<button class="btn-small" onclick="addFinalRange()">+ Agregar Rango</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
hasUnsavedChanges = true;
|
||||
renderSegmentsTable();
|
||||
alert('✅ Segmento agregado correctamente');
|
||||
}
|
||||
|
||||
function deleteSegment(index) {
|
||||
if (confirm('¿Estás seguro de eliminar este segmento?')) {
|
||||
editorData.segmentos.splice(index, 1);
|
||||
hasUnsavedChanges = true;
|
||||
renderSegmentsTable();
|
||||
}
|
||||
}
|
||||
|
||||
function saveConfiguration() {
|
||||
if (!confirm('¿Deseas guardar la configuración? Esto actualizará el archivo de configuración del plugin.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const jsonData = JSON.stringify(editorData);
|
||||
|
||||
// Crear formulario para enviar datos
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = '';
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'save_advanced_config';
|
||||
input.value = jsonData;
|
||||
|
||||
form.appendChild(input);
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
|
||||
// ========== FIN ADVANCED CONFIGURATION EDITOR ==========
|
||||
|
||||
|
||||
// Theme toggle functionality
|
||||
const themeToggle = document.getElementById('themeToggle');
|
||||
const themeIcon = document.getElementById('themeIcon');
|
||||
@ -1202,12 +1779,14 @@ $pingEnabled = !empty($config['enablePingVerification']) && ($config['enablePing
|
||||
|
||||
// Función para clasificar el tipo de IP
|
||||
function getIpType(ip) {
|
||||
const parts = ip.split('.');
|
||||
const lastOctet = parseInt(parts[3]);
|
||||
const lastOctet = parseInt(ip.split('.')[3]);
|
||||
|
||||
if ((lastOctet >= 1 && lastOctet <= 30) || lastOctet === 254) {
|
||||
// Verificar si está en alguno de los rangos administrativos configurados
|
||||
if ((lastOctet >= adminConfig.rangeStart && lastOctet <= adminConfig.rangeEnd) ||
|
||||
(lastOctet >= adminConfig.finalStart && lastOctet <= adminConfig.finalEnd)) {
|
||||
return 'Administración';
|
||||
}
|
||||
|
||||
return 'Válida para cliente';
|
||||
}
|
||||
|
||||
|
||||
130
src/AdminRangeHelper.php
Normal file
130
src/AdminRangeHelper.php
Normal file
@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
namespace SiipAvailableIps;
|
||||
|
||||
/**
|
||||
* Helper class para manejar rangos de IPs administrativas personalizados por segmento
|
||||
*/
|
||||
class AdminRangeHelper
|
||||
{
|
||||
/**
|
||||
* Obtiene los límites de IPs administrativas para un segmento específico
|
||||
*
|
||||
* @param string|int $segmentNumber Número del segmento (ej: 18 para 172.16.18.x)
|
||||
* @param string $jsonData JSON con configuración de rangos
|
||||
* @return array|null Array con límites o null si el segmento no existe
|
||||
*/
|
||||
public static function getSegmentLimits($segmentNumber, $jsonData)
|
||||
{
|
||||
if (empty($jsonData)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = json_decode($jsonData, true);
|
||||
|
||||
if (!isset($data['segmentos']) || !is_array($data['segmentos'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($data['segmentos'] as $segmento) {
|
||||
if ($segmento['segmento'] == $segmentNumber) {
|
||||
// Última IP del bloque inicial
|
||||
$ultimoInicial = 0;
|
||||
if (isset($segmento['administrativas_iniciales']) && is_array($segmento['administrativas_iniciales'])) {
|
||||
foreach ($segmento['administrativas_iniciales'] as $rango) {
|
||||
if (isset($rango['hasta']) && $rango['hasta'] > $ultimoInicial) {
|
||||
$ultimoInicial = $rango['hasta'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Primera IP del bloque final
|
||||
$primerFinal = 999;
|
||||
if (isset($segmento['administrativas_finales']) && is_array($segmento['administrativas_finales'])) {
|
||||
foreach ($segmento['administrativas_finales'] as $rango) {
|
||||
if (isset($rango['inicio']) && $rango['inicio'] < $primerFinal) {
|
||||
$primerFinal = $rango['inicio'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'ultimo_inicial_reservado' => $ultimoInicial,
|
||||
'primer_final_reservado' => $primerFinal,
|
||||
'rangos_iniciales' => $segmento['administrativas_iniciales'] ?? [],
|
||||
'rangos_finales' => $segmento['administrativas_finales'] ?? []
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica si una IP es administrativa según configuración JSON personalizada
|
||||
*
|
||||
* @param string $ip Dirección IP completa (ej: 172.16.18.45)
|
||||
* @param string $jsonData JSON con configuración de rangos
|
||||
* @return bool True si es IP administrativa
|
||||
*/
|
||||
public static function isAdminIpCustom($ip, $jsonData)
|
||||
{
|
||||
$parts = explode('.', $ip);
|
||||
if (count($parts) !== 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$segment = $parts[2];
|
||||
$lastOctet = (int)$parts[3];
|
||||
|
||||
$limits = self::getSegmentLimits($segment, $jsonData);
|
||||
if (!$limits) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verificar rangos iniciales
|
||||
if (isset($limits['rangos_iniciales']) && is_array($limits['rangos_iniciales'])) {
|
||||
foreach ($limits['rangos_iniciales'] as $rango) {
|
||||
if ($lastOctet >= $rango['inicio'] && $lastOctet <= $rango['hasta']) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verificar rangos finales
|
||||
if (isset($limits['rangos_finales']) && is_array($limits['rangos_finales'])) {
|
||||
foreach ($limits['rangos_finales'] as $rango) {
|
||||
if ($lastOctet >= $rango['inicio'] && $lastOctet <= $rango['hasta']) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Valida el formato del JSON de configuración
|
||||
*
|
||||
* @param string $jsonData JSON a validar
|
||||
* @return array Array con 'valid' (bool) y 'error' (string|null)
|
||||
*/
|
||||
public static function validateConfigJson($jsonData)
|
||||
{
|
||||
if (empty($jsonData)) {
|
||||
return ['valid' => false, 'error' => 'JSON vacío'];
|
||||
}
|
||||
|
||||
$data = json_decode($jsonData, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
return ['valid' => false, 'error' => 'JSON inválido: ' . json_last_error_msg()];
|
||||
}
|
||||
|
||||
if (!isset($data['segmentos']) || !is_array($data['segmentos'])) {
|
||||
return ['valid' => false, 'error' => 'Falta el array "segmentos"'];
|
||||
}
|
||||
|
||||
return ['valid' => true, 'error' => null];
|
||||
}
|
||||
}
|
||||
@ -102,9 +102,9 @@ function handleIpRequest($data, $log) {
|
||||
|
||||
// Formatear respuesta para API
|
||||
if ($resultado['success']) {
|
||||
// Filtrar IPs administrativas - Solo devolver IPs aptas para clientes (31-253)
|
||||
$clientAvailableIps = array_filter($resultado['data'], function($ip) {
|
||||
return !\SiipAvailableIps\IpSearchService::isAdminIp($ip);
|
||||
// Filtrar IPs administrativas - Solo devolver IPs aptas para clientes
|
||||
$clientAvailableIps = array_filter($resultado['data'], function($ip) use ($config) {
|
||||
return !\SiipAvailableIps\IpSearchService::isAdminIp($ip, $config);
|
||||
});
|
||||
$clientAvailableIps = array_values($clientAvailableIps); // Reindexar array
|
||||
|
||||
@ -138,8 +138,8 @@ function handleIpRequest($data, $log) {
|
||||
|
||||
// Filtrar IPs que responden de las IPs de cliente
|
||||
if (isset($resultado['ping_responding'])) {
|
||||
$clientPingResponding = array_filter($resultado['ping_responding'], function($ip) {
|
||||
return !\SiipAvailableIps\IpSearchService::isAdminIp($ip);
|
||||
$clientPingResponding = array_filter($resultado['ping_responding'], function($ip) use ($config) {
|
||||
return !\SiipAvailableIps\IpSearchService::isAdminIp($ip, $config);
|
||||
});
|
||||
$response['ping_responding'] = array_values($clientPingResponding);
|
||||
}
|
||||
@ -210,7 +210,7 @@ function handleIpCheck($data, $log) {
|
||||
if ($resultado['success']) {
|
||||
$isAvailable = in_array($ipToCheck, $resultado['data']);
|
||||
$isUsed = in_array($ipToCheck, $resultado['used']);
|
||||
$ipType = \SiipAvailableIps\IpSearchService::getIpType($ipToCheck);
|
||||
$ipType = \SiipAvailableIps\IpSearchService::getIpType($ipToCheck, $config);
|
||||
|
||||
// Determinar status con información de tipo de IP
|
||||
$status = 'unknown';
|
||||
|
||||
@ -8,13 +8,6 @@ class IpSearchService
|
||||
private $apiToken;
|
||||
private $logger;
|
||||
|
||||
// Rangos de IPs administrativas (no recomendadas para clientes)
|
||||
const ADMIN_IP_RANGES = [
|
||||
'start' => 1,
|
||||
'end' => 30,
|
||||
'broadcast' => 254
|
||||
];
|
||||
|
||||
public function __construct($apiUrl, $apiToken, $logger = null)
|
||||
{
|
||||
$this->apiUrl = $apiUrl;
|
||||
@ -26,10 +19,28 @@ class IpSearchService
|
||||
* Determina si una IP es administrativa (no recomendada para clientes)
|
||||
*
|
||||
* @param string $ip Dirección IP completa (ej: 172.16.13.5)
|
||||
* @param array $config Configuración del plugin con rangos admin
|
||||
* @return bool True si es IP administrativa
|
||||
*/
|
||||
public static function isAdminIp($ip)
|
||||
public static function isAdminIp($ip, $config = [])
|
||||
{
|
||||
// Si está habilitada la configuración personalizada por segmento
|
||||
if (!empty($config['useCustomAdminRanges']) && !empty($config['customAdminRangesJson'])) {
|
||||
// Intentar obtener límites del segmento desde JSON
|
||||
$parts = explode('.', $ip);
|
||||
if (count($parts) === 4) {
|
||||
$segment = $parts[2];
|
||||
$limits = AdminRangeHelper::getSegmentLimits($segment, $config['customAdminRangesJson']);
|
||||
|
||||
// Si el segmento está configurado en JSON, usar esos rangos
|
||||
if ($limits !== null) {
|
||||
return AdminRangeHelper::isAdminIpCustom($ip, $config['customAdminRangesJson']);
|
||||
}
|
||||
// Si el segmento NO está en JSON, usar rangos globales como fallback
|
||||
}
|
||||
}
|
||||
|
||||
// Usar configuración estándar (rangos globales)
|
||||
$parts = explode('.', $ip);
|
||||
if (count($parts) !== 4) {
|
||||
return false;
|
||||
@ -37,19 +48,26 @@ class IpSearchService
|
||||
|
||||
$lastOctet = (int)$parts[3];
|
||||
|
||||
return ($lastOctet >= self::ADMIN_IP_RANGES['start'] && $lastOctet <= self::ADMIN_IP_RANGES['end'])
|
||||
|| $lastOctet === self::ADMIN_IP_RANGES['broadcast'];
|
||||
// Obtener rangos de configuración o usar valores por defecto
|
||||
$rangeStart = isset($config['adminRangeStart']) ? (int)$config['adminRangeStart'] : 1;
|
||||
$rangeEnd = isset($config['adminRangeEnd']) ? (int)$config['adminRangeEnd'] : 30;
|
||||
$finalStart = isset($config['adminRangeFinalStart']) ? (int)$config['adminRangeFinalStart'] : 254;
|
||||
$finalEnd = isset($config['adminRangeFinalEnd']) ? (int)$config['adminRangeFinalEnd'] : 254;
|
||||
|
||||
return ($lastOctet >= $rangeStart && $lastOctet <= $rangeEnd)
|
||||
|| ($lastOctet >= $finalStart && $lastOctet <= $finalEnd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene el tipo de IP (administrativa o cliente)
|
||||
*
|
||||
* @param string $ip Dirección IP completa
|
||||
* @param array $config Configuración del plugin con rangos admin
|
||||
* @return array ['type' => 'admin'|'client', 'label' => string, 'recommended' => bool]
|
||||
*/
|
||||
public static function getIpType($ip)
|
||||
public static function getIpType($ip, $config = [])
|
||||
{
|
||||
if (self::isAdminIp($ip)) {
|
||||
if (self::isAdminIp($ip, $config)) {
|
||||
return [
|
||||
'type' => 'admin',
|
||||
'label' => 'Administración',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user