viernes, 3 de enero de 2025

Nginx (II) Autenticación LDAP

 1. Introducción

Se puede utilizar el módulo adicional ngx_http_auth_ldap_module, pero se necesita una versión de nginx precompilada con este módulo adicional. Para Ubuntu se instala:

sudo apt install nginx-extras

2. Configurar LDAP

Segun chatgpt esto sirve:

http {
    ldap_server my_ldap_server {
        url ldap://ldap.example.com:389/dc=example,dc=com?uid?sub?(objectClass=person);
        binddn "cn=admin,dc=example,dc=com";
        binddn_passwd "admin_password";
        group_attribute member;
        group_attribute_is_dn on;
        require valid_user;
    }

    server {
        listen 80;
        server_name example.com;

        location /protected {
            auth_ldap "Restricted Area";
            auth_ldap_servers my_ldap_server;

            proxy_pass http://backend_server;
        }
    }
}

Y aclara:

Explicación de la configuración:

  • ldap_server my_ldap_server:

    • Define un servidor LDAP con el nombre my_ldap_server.
    • url: Especifica la URL del servidor LDAP. En este caso, ldap://ldap.example.com:389.
      • dc=example,dc=com: Indica la base de búsqueda en el árbol LDAP.
      • ?uid: Especifica el atributo de búsqueda del usuario (generalmente uid o cn).
      • ?sub: Define el alcance de la búsqueda (sub para buscar recursivamente en subárboles).
      • (objectClass=person): Filtro para encontrar objetos LDAP que representan usuarios.
  • binddn y binddn_passwd:

    • Credenciales usadas por Nginx para consultar el servidor LDAP.
  • auth_ldap y auth_ldap_servers:

    • Habilita la autenticación LDAP en el bloque de ubicación.

3. Proteger rutas específicas

En este ejemplo, solo las solicitudes al directorio /protected requieren autenticación LDAP. Si la autenticación es exitosa, el usuario puede acceder; de lo contrario, se muestra un desafío de autenticación.


Pruebas y solución de problemas

  1. Revisar los logs:

    • Logs de acceso: /var/log/nginx/access.log
    • Logs de error: /var/log/nginx/error.log
  2. Asegurar conectividad con el servidor LDAP:

    • Probar la conexión desde el servidor donde está Nginx:

ldapsearch -H ldap://ldap.example.com -D "cn=admin,dc=example,dc=com" -w admin_password -b "dc=example,dc=com"

  1. Depurar errores comunes:

    • Credenciales incorrectas: Revisar binddn y binddn_passwd.
    • URL LDAP incorrecta: Verificar que el servidor LDAP y la base DN sean correctos.

Conclusión

Usar LDAP con Nginx es posible con el módulo adecuado, como ngx_http_auth_ldap_module o la solución comercial de Nginx Plus. Este enfoque permite autenticar usuarios centralmente mediante un servidor LDAP, lo cual es útil en entornos empresariales.



 

Nginx (I) Introducción

1. Introducción

Esto es un resumen de Saquib Khan

Nginx es:

  • Non threaded (en contra de los servidores web tradicionales que son multi-threaded)
  • Dirigido por eventos
  • Permite manejar más conexiones simultáneas más de 10.000 peticiones concurrentes
  • Es apropiado para sitios web de alto tráfico.
  • Puede actuar como balaceador de cargas
  • Cacheador de request HTTP, reduciendo el tiempo de carga y mejorando la respuesta
  • Reverse proxy, pwermitiendo el balanceo de carga delos servidores.
  • API gateway, manejando y enrutadno las request por API.
  • Servidor de ficheros estáticos (imágenes, vídeos etc)
  • Manejo SSL (certificados, conexiones segurasa los usuarios)

2. Forward Proxy y Reverse Proxy

Hemos dicho que Nginx es reverse proxy. Veamos la diferencia respecto a un Forward proxy

Forward Proxy se caracteriza por:
  • El proxy está entre el cliente y el servidor. El cliente envia la petición al proxy y este al servidor.
  • El servidor se desvincula del cliente, intereactuando solo con el proxy. Esto permite la privacidad y superar las restricciones geográficas. (VPN es un ejemplo)
Reverse Proxy se caracteriza por:
  • Se situa entre el cliente y múltiples servidores, decidiendo que server va a recibor el request. 
  • El cliene desconoce que servidor le contesta.
  • Los criterios de selección de servidores pueden ser por la ruta (por ejemplo /admin se  entruta al servidor 1 y /settings al servidor 2). Es decir se predefinen reglas para la redirección de servidores.

3. Instalación de nginx

Si lo instalamos en docker

# 1. Instalar Ubuntu en un contenedor docker usando el puerto 8080 de la máquina local y el puerto 80 del contenedor
docker run -it -p 8080:80 ubuntu

# 2. Instalar nginx
apt-get update
apt-get install nginx

# 3. Verificar la instalación
nginx -v

Si lo instalamos en la máquina local

# 1. Instalar nginx en Ubuntu
sudo apt update
sudo apt install nginx

# 2. Arrancar nginx
sudo systemctl start nginx

# 3. Verificar 
sudo systemctl status nginx


4. Entender el fichero de configuración nginx.conf

Este fichero se encuenta en /etc/nginx/nginx.conf  

Veamos el contenido de un fichero ejemplo:

worker_processes auto;

events {
    worker_connections 1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    keepalive_timeout  65;

    server {
        listen       80;
        server_name  localhost;

        location / {
            root   /usr/share/nginx/html;
            index  index.html index.htm;
        }
    }
}

Veamos las claves:
   
worker_processes auto;
Un worker process se encarga del manejo de las peticiones. Con la opción auto dejamos enmanos de nginx que cree el número de workers en base a los cores de la CPU

events { worker_connections 1024; } 
events indica la configuración del manejo de conexiones. worker_connections 1024; indica que cada worker puede manejar hasta 1024 conexiones simultáneas.

http {...}
Define la configuracion del manejo de las request HTTP

include mime.types;
Le indica a nginx la inclusión del fichero mime.types que mapea la extensión de un fichero a los tipos MIME. Por ejemplo .html como text/html, .jpg como image/jpeg )

default_type application/octet-stream; 
Si no se puede obtener el tipo mime de un fichero, se utilizará por omisión application/octet-stream que significa "descargar este fichero"

sendfile on;
Para utilizar la llamada del sistema"sendfile" que permite el copiado de ficheros entre servidores o redes sin utilizar passar por los buffers de la memoria del usuario. Ver CocCoc Techblog 


keepalive_timeout 65;
Determina el tiempo que una conexión estará abierta en segundos antes que nginx la cierre. Por defecto son 65 segundos


5. La parte mas importante de nginx.conf: server

server 
Para configurar un servidor virtual, por ejemplo un servidor que maneja la "request"para un dominio específico i una dirección IP
 
Repitamos esta parte:

server {
    listen       80;
    server_name  localhost;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }
}


listen 80; 
Le indica a nginx que escuche las peticiones del puerto 80. Sabemos que el puerto 80 es el puerto por omisión para HTTP

server_name localhost;  
indica el nombre de dominio que este bloque de configuración del servidor va a responder. localhost se usa para test local, pero en producción se debe reemplazar por el dominio (por ejemplo example.com

location / { ... }  
Indica como responder a diferentes URIs de la "request".  "/" indica que este bloque manejará las las "request" de la URL root . En este caso http:/localhost/ pues el server_name del punto anterior es localhost  

root  /usr/share/nginx/html;  
La directiva root indica la carpeta donde nginx buscará los ficheros a servir. En este caso /usr/share/nginx/html que es la carpeta donde nginx guarda los ficheros HTML por omisión.

index index.html index.htm; 
Cuando la request apunta a un directorio sin indicar un fichero de dicho directorio, entonces nginx buscará un fichero llamado index.html o index.htm dentro de dicho directorio para servirlo

6. Configuración básica de prueba de nginx


Supongamos que creamos este fichero para probar nginx ( /etc/nginx/nginx.conf). Ojo hacer una copia de seguridad del fichero anterior.

events {
}

http {
    server {
        listen 80;
        server_name _;  # This means it will respond to any server name
        location / {
            return 200 "Hello from Nginx Conf File";
        }
    }
}


Con esto, cualquier petición a cualquier servidor va a responder "Hello from Nginx Conf File"
pero hay que hacer un

nginx -s reload 

Si creamos un directorio llamado /www/data y dentro de el creamos el fichero index.html

mkdir -p /www/data
echo "<h1>Welcome to Nginx</h1>" > /www/data/index.html

Podemos modificar la parte server del fichero nginx.conf con el posterior nginx -s reload para que muestre el contenido del fichero /www/data/index.html que es "<h1>Welcome to Nginx</h1>" . La "request" que debemos realizar es http://localhost:8080 que es la que apunta a "/"

server {
    listen 80;
    server_name localhost;
    root /www/data;

    location / {
        try_files $uri $uri/ =404;
    }
}

Observar que $uri es la ruta del recurso por ejemplo http://example.com/path/to/resource, $uri sería /path/to/resource.

7. Los bloques Server, Location y Upstream

Como se vió, el bloque server define laconfiguracion oara un dominio particular o dirección IP.

El bloque location define como responder a las URIs de "requests" específicas, por ejemplo:

location /images/ {
    root /var/www/images;
}
 
que indica que para un servidor específico cuando accedemos a /images nos servirá los fichero de la carpeta /var/www/images

El bloque upstream define un grupo de servidors backend (segundo plano) para el balanceo de cargas, por ejemplo:

upstream backend {
    server backend1.example.com;
    server backend2.example.com;
}


8. Reverse Proxy

Veamos unaconfiguración como reverse proxy

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://backend_server;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

proxy_pass http://backend_server; 
Para pasar la "request" a http://backend_server o sea backend_server va a ser el servidor encargado de responder a la "request"

proxy_set_header Host $host;  
Asegura que el "header" del host original  es pasado al servidor backend_server. La variable $host representa el valor del encabezado Host original de la "request" del cliente. Si elencabezado Hos noesta presente en la "request" toma el nombre sel servidor definido en server_name (en este caso example,.com) o dirección IP del servidor

proxy_set_header X-Real-IP $remote_addr; 
Envía la dirección IP real del cliente al servidor de destino.

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;  
Agrega la dirección IP del cliente a la lista de encabezados X-Forwarded-For ,que es usado por el backend para identificar la IP original del cliente

9. Balanceo de carga

Veamos este ejemplo:

upstream backend {
    server backend1.example.com;
    server backend2.example.com;
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://backend;
    }
}


upstream backend { ... }
Define la lista se servidores backend

server backend1.example.com; 
Añade dicho servidor a la lista de backend servers
 
proxy_pass http://backend; 
Redirige la "request" a uno de los servidores de la lista de servidores backends

10. Securizando con SSL/TLS

Este protocolo encripta la transmisión entre cliente y servidor. Veamos como se configura:

server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate /etc/nginx/ssl/example.com.crt;
    ssl_certificate_key /etc/nginx/ssl/example.com.key;

    location / {
        root /var/www/html;
        index index.html;
    }
}

server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

Vemos que aparecen dos bloques server, uno para ssl (https)  y otro http

listen 443 ssl;
Indica que utilice escuche el puerto 443 (por defecto el de https) con SSL habilitado


ssl_certificate   /etc/nginx/ssl/example.com.crt;;
indica la ruta del certificado del servidor a utilizar por nginx

ssl_certificate_key /etc/nginx/ssl/example.com.key;
Indica la ruta donde está la clave del certificado de servidor 

return 301 https://$host$request_uri;
return sirve para redirigir. 
301 és el código de estado HTTP que indica Redirección permanente, indicando al navegador del cliente que el recurso solicitado se ha movido ae manera permanente a una nueva dirección.
https://$host$request_uri redirige al cliente a la misma dirección pero utilizando el protocolo https. 
$host representa elbombre del host (en este caso example.com)
$request_uri incluye la ruta completa y los parámetros de la solicitud original, asegurando que la redirección sea precisa


11. Cacheando

Nginx puede cachear las respuestas del servidor backend para acelerar los tiempos de respuesta para posteriores "requests"

Por ejemplo:

proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=1g inactive=60m;


proxy_cache_path
Establece la carpeta y configuración para el caché, en este caso /var/cache/nginx 

levels=1:2 
Define la estructura del directorio para la caché. La estructura tendrá:
  • 1 nivel con un subdirectorio de 1 carácter
  • 2 niveles adicionales con subdirectorios de 2 caracteres cada uno
Con ello se distribuyen los archivos en múltiples directorios, evitando tener muchos archivos en una sola carpeta


keys_zone=my_cache:10m
Crea una zona de memoria compartida llamada my_cache con 10 MB de memoria. Esta zona compartida se utiliza para almacenar metadatos de los objetos de caché como claves y tiempos de expiración

max_size=1g
Limita el tamaño máximo a 1 GB. Cuando se alcanze este límite , se eliminan los objetos menos utilizados (uando el algoritmo LRU, "Least Reacently Used"

inactive=60m
Elimina los elementos del caché si no se ha accedido a ellos durante 60 minutos

12. Monitoreando y logging

Nginx hace una bitácora (log) de todas las peticiones y errores, permitiendo el control de errores (troubleshooting) y monitoreo

En este ejemplo:

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                  '$status $body_bytes_sent "$http_referer" '
                  '"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/access.log main;


log_format main ...
Define un formato a medida de bitácora llamado main
Siendo:
$remote_addr la dirección IP del cliente que realizó el "request"
$remote_user el nombre del usuario autenticado si se utilizó la autenticación HTTP. Sinó se ha autenticado queda vacío
[$time_local] La hora local en que se realizó la petición en formato [día/mes/año:hora:minuto:segundo zona_horaria]
"$request" La solicitud HTTP completa, incluyendo el método (GET, POST...), el URI, y la versionb del protocolo, por ejemplo: GET /index.html HTTP/1.1
$status el código del estado devuelto, por ejemplo 200, 404, 500
$body_bytes_sent el número de bytes enviados al cliente, excluyendo los encabezados de respuesta.
"$http_referer" el encabezado Referer enviado por el cliente, que indica la página que ha visitado el cliente previamente para acceder al sitio actual.Si no hay un referer, queda vacío.
"$http_user_agent" el encabezado User-Agent, que identifica al cliente (por ejemplo, un navegador o una herramienta de línea de comandos).
"$http_x_forwarded_for" el encabezado X-Forwarded-For, que contiene las direcciones IP de los clientes originales en caso de que Nginx actúe como un proxy inverso. Si no existe este encabezado, queda vacío.


access_log /var/log/nginx/access.log main;
Almacena la bitácora de accesos en el fichero especificado (/var/log/nginx/access.log main) usando el formato de log definido como main

Veamos este ejemplo, donde un cliente con la dirección 192.168.1.1 accedeal recurso /index.html. Entonces se tendría una entrada como esta en el fichero de registro 

192.168.1.1 - - [03/Jan/2025:12:34:56 +0000] "GET /index.html HTTP/1.1" 200 1024 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" "-"

Siendo:

192.168.1.1: Dirección IP del cliente.
-: Sin usuario autenticado.
[03/Jan/2025:12:34:56 +0000]: Fecha y hora de la solicitud.
"GET /index.html HTTP/1.1": Solicitud HTTP realizada.
200: Código de estado HTTP.
1024: Bytes enviados al cliente (excluyendo encabezados).
"-": Sin Referer.
"Mozilla/5.0 (Windows NT 10.0; Win64; x64)": User-Agent del cliente.
"-": Sin encabezado X-Forwarded-For


Los errores típicos que se reciben son:
400 Bad Request, 401 Unauthorized, 403 Forbidden, y 404 Not Found: Errores del cliente.
500 Internal Server Error, 502 Bad Gateway, y 503 Service Unavailable: Errores del servidor.

13. Códigos de estado típicos 

En general los códigos de error típicos que se pueden obtener son:

1xx - Respuestas informativas

Indican que la solicitud fue recibida y el servidor está procesándola.

    100 Continue: El cliente puede continuar con su solicitud.
    101 Switching Protocols: El servidor acepta cambiar a un protocolo diferente solicitado por el cliente.
    103 Early Hints: El servidor envía encabezados preliminares antes de la respuesta final.

2xx - Éxito. La solicitud se procesó correctamente.

    200 OK: La solicitud fue exitosa y el servidor devolvió el contenido solicitado.
    201 Created: La solicitud fue exitosa y se creó un nuevo recurso.
    202 Accepted: La solicitud fue recibida, pero su procesamiento no ha concluido aún.
    204 No Content: La solicitud fue exitosa, pero no hay contenido que devolver.
    206 Partial Content: Se devuelve parte del contenido solicitado, usado en solicitudes con rangos.

3xx - Redirecciones. Indican que el cliente debe tomar medidas adicionales para completar la solicitud.

    301 Moved Permanently: El recurso solicitado se ha movido de manera permanente a una nueva URL.
    302 Found: El recurso se encuentra temporalmente en una ubicación diferente.
    303 See Other: La respuesta está disponible en una URL diferente (usado después de una solicitud POST para redirigir a una página de confirmación).
    304 Not Modified: Indica que el recurso no ha cambiado desde la última vez que fue solicitado (usado con caché).
    307 Temporary Redirect: La redirección es temporal y debe usar el mismo método HTTP.
    308 Permanent Redirect: Similar a 301, pero obliga a usar el mismo método HTTP.

4xx - Errores del cliente. Indican que la solicitud del cliente contiene un error.

    400 Bad Request: La solicitud no es válida debido a un error de sintaxis.
    401 Unauthorized: El cliente no está autenticado (requiere credenciales).
    403 Forbidden: El cliente no tiene permisos para acceder al recurso.
    404 Not Found: El recurso solicitado no existe en el servidor.
    405 Method Not Allowed: El método HTTP usado no está permitido para el recurso.
    408 Request Timeout: El servidor agotó el tiempo de espera para la solicitud.
    409 Conflict: Hay un conflicto con el estado actual del recurso (por ejemplo, datos duplicados).
    410 Gone: El recurso ya no está disponible y no se sabe su ubicación.
    413 Payload Too Large: El cuerpo de la solicitud supera el límite permitido por el servidor.
    414 URI Too Long: El URI de la solicitud es demasiado largo para ser procesado.
    429 Too Many Requests: El cliente ha enviado demasiadas solicitudes en un tiempo determinado (límite superado).

5xx - Errores del servidor. Indican que el servidor falló al procesar la solicitud.

    500 Internal Server Error: Error genérico cuando el servidor encuentra una condición inesperada.
    501 Not Implemented: El servidor no soporta la funcionalidad requerida.
    502 Bad Gateway: El servidor recibió una respuesta inválida de un servidor upstream.
    503 Service Unavailable: El servidor no está disponible temporalmente, generalmente por mantenimiento o sobrecarga.
    504 Gateway Timeout: El servidor upstream no respondió a tiempo.
    505 HTTP Version Not Supported: El servidor no soporta la versión del protocolo HTTP usada en la solicitud.




domingo, 29 de diciembre de 2024

Python (XXI) . Creación de un miniframework (III) Definición de las acciones tipo "table"(II)

 1. Definición de las acciones tipo "table"

Veamos el fichero "actions_04" de la carpeta "static/conf" a modo de ejemplo

#########################################
# actions_04.yml
#########################################
# 4.1 Taula municipio
41: 
    type: table
    conn: postgresDemo
    dbTable: POBLACIONS
    help: Taula de municipis 

    
    ox: > 
      Codis: [id, CPRO, CMUN, DC];
      Descripcions: [description];
    
    readOnly: [id]
    hidden: [CODAUTO]  
    
    #events: 
    #  campo1:  
    #    hx_trigger: change   #Podemos añadir mas eventos separado por coma
    #    hx_include: '#campo2'
    #    hx_target: '#campo3' 
    #    hx_edu_target: ["#campo3","#campo4","#grid_interessats"]
    #    module: basicutils/xmalbaopc.py
    #    function: albaExpSelecExpsAndInteressats(expCodiOrigen=expCodiOrigen, expCodiDestinacio=expCodiDestinacio)
     
  
    #other: []
    #session: 
      #save: [campo6, campo7]
      #read: [campo8, campo9]
        

Veamos  los parámetros:

  • La clave del menú "41"
  • type : En este caso es tipo "table"
  • conn: Nombre de la conexión. Las conexiones se guardan en un fichero a parte que indicaremos mas adelante en esta entrada.
  • dbTable: el nombre de la tabla a utilizar
  • readonly: Es la lista de los campos que no se pueden modificar
  • hidden: es una lista de campos de la tabla que se van a ocultar.
  • El resto de campos se indica su definición en la entrada anterior y  estánomentados pues no se utiliza ninguno en este ejemplo, pero no impide que se puedan utilizar y son:  help, params, ox, events (y sus componentes), other session

2. Definición de la conexión:

Para definir a conexión tenemos este fichero "db.yml" que se deberia guardar en "static/conf"

postgresDemo: 
    dbType: 'postgresql+psycopg2'
    dbUser: myUser
    dbPwd: myPassword
    dbHost: localhost
    dbPort: 5432
    dbName: ayto
    dbSchema: TERRIRORI

Cabe destacar el nombre de la conexión y sobre todo el tipo de la conexión. Los demás detalles son importantes sobre todo s se utilian esquemas.


3. Pantallas que se dan:

Hay 3 tabs, para el primer tab el de "taula" solo nos permite buscar un nimero de registro mas o menos grande, y podemos mofivcar los campos directamente en la tabla, Y no podemos hacer nada mas quwe volver

El siguiente tab "Registre" nos permte modificar, dar altas copiando o de un registro en blanco, y borrar.
Hay que observar que la claves que son ajenas se muestran como un "combo" o "select". De momento cuando se tengan un gran número de opciones no se ha pensado en un componente que sea mas versátil, pero se prevé utilizarlo en proximas vesiones. En este caso es el código de provincia


El tercer "tab" es el de búsqueda, donde se permiten seleccionar los campos y establecer el orden de los registros. Aquí se buscan registros cuyo campo descripción termine con "Cull" y seguramene encontrará pocos municipios. Se ordenarán por description e id.


















sábado, 28 de diciembre de 2024

Python (XX) . Creación de un miniframework (II) Definición de las acciones tipo "info", "python" y "bash" (I)

 1. Acciones tipo "info"

Estas acciones son las más simples pues son solo informativas y no hacen nada. 

Veamos un ejemplo que representa la acción del menú "112". Puede tener por nombre "actions_112.yml" y se guardará en la carpeta "static/conf" del proyecto

# 1. Firmar Actes per Alcaldia"
112: 
  type: info
  help: > 
    Alcaldia firmarà després que Secretaria.
    PROCEDIMENT:
    1. Executar Xolido (XolidoSign)
    2. Seleccione els fitxers PDF de la carpeta a firmar (Z:\TEMP\ACTES_PLE_2022\Secretaria)
    3. En firmar-Opciones-Configuración seleccionar:
      3.1 Seleccionar el certificat.
      3.2 Formato de la firma: Perfil -XL (3ªOpció)
      3.3 Selec el tipus de firma:
      3.4 Preferencia de la firma: Marcar totes les opcions 
      3.5 Política de firma: Marcar: 
        3.5.1: Aprobación.
        3.5.2: OID: 2.16.724.1.3.1.1.2.1.9
        3.5.3: URI: https://sede.060.gob.es/politica_de_firma_anexo_1.pdf
        3.5.4: Algoritmo de resumen: SHA-1
        3.5.5: Valor de resumen: G7roucf600+f03r/o0bAOQ6WAs0=
      3.6 Opciones avanzadas: SHA-512 (resto no marcar)
      3.7 Servidores de sello de tiempo: ACCV
      3.8 Información de PDF: 
        3.8.1: Introduir motivo: Alcaldia RD 2568/1986 (ROF)
        3.8.2: Introduir ubicación: Tavernes de Valldigna
      3.9 Indicar una carpeta eixida diferent a la entrada (Z:\TEMP\ACTES_PLE_2022\Alcaldia)
      3.10 Modo de salida : Modo identificado 
    4. Firmar amb Xolido 

      

veamos los parámetros:

  • La clave del menú "112"
  • type : En este caso es tipo "info"
  • help: es el texto informativo de ayuda que te indica lo que hay que hacer

Y sale esta pantalla


2. Acciones tipo "python"

Estas acciones ejecutan un programa en python dentro de este mismo proyecto, por eso nos vale el entorno virtual que tenemos.

Veamos un ejemplo que representa la acción del menú "112". Puede tener por nombre "actions_112.yml" y se guardará en la carpeta "static/conf" del proyecto

211: 
    type: python 
    module: basicutils/xmalbaopc.py 
    function: albaCopyInteressats(expCodiOrigen=expCodiOrigen, expCodiDestinacio=expCodiDestinacio, grid_interessats=grid_interessats)
    help: Per a copiar interessats des de l'expedient origen al de destinació  

    #OJO: el pattern hem de canviar ?->\? si no trenca la http despres en el programa hemde  desfer el canvi \?->?       
    params: [
      [expCodiOrigen,string,10,"Codi expedient Sedipualba Origen:","",[]],
      [expCodiDestinacio,string,10,"Codi expedient Sedipualba Destinació:","",[]],
      [descExpOrigen,string,100,"Descripció expedient Origen:","",[]],
      [descExpDestinacio,string,100,"Descripció expedient Destinació:","",[]],
      [grid_interessats,grid,100,"Interessats:","",[]],
    ]
    ox: > 
      Origen i destinació dels interessats [expCodiOrigen, descExpOrigen;expCodiDestinacio, descExpDestinacio];
      Detall dels interessats [grid_interessats];
   
    events:  
      expCodiOrigen:   
        hx_trigger: change # other evens are possible separated by commas
        hx_include: "#expCodiDestinacio"
        hx_target: "#descExpOrigen"
        hx_edu_target: ["#descExpOrigen","#descExpDestinacio","#grid_interessats"] # More field targets
        module: basicutils/xmalbaopc.py
        function: albaExpSelecExpsAndInteressats(expCodiOrigen=expCodiOrigen, expCodiDestinacio=expCodiDestinacio)
       
      expCodiDestinacio:   
        hx_trigger: change
        hx_include: "#expCodiOrigen"
        hx_target: "#descExpDestinacio"
        hx_edu_target: ["#descExpOrigen","#descExpDestinacio","#grid_interessats"]
        module: basicutils/xmalbaopc.py
        function: albaExpSelecExpsAndInteressats(expCodiOrigen=expCodiOrigen, expCodiDestinacio=expCodiDestinacio)
         
     
    other: [
      #[fase,1],
    ]
    session:  
      save: [expCodiOrigen, expCodiDestinacio]
      #read: [expCodiOrigen, expCodiDestinacio]
    
    

Veamos  los parámetros:

  • La clave del menú "211"
  • type : En este caso es tipo "python"
  • module: el nombre del módulo a ejecutar 
  • function: nombre de la función del módulo con los parámetros ajecutar
  • help: es el texto informativo de ayuda que te indica lo que hay que hacer
  • params: es una lista de campos que apareceran en la pantalla que se pueden editar. Para cada campo tenemos otra lista donde las posiciones indican:
  1. "id" y "name" del campo en html
  2. tipo de campo (str: string, int: entero ..., grid: una tabla)
  3. Longiutd en caracteres (por ejemplo 60)
  4. Etiqueta del campo en el formulario 
  5. Valor por omisión
  6. Conjunto de valores que puede tener en caso de ser un elemto tipo "select" o combo
  • ox: Disposición de los campos según el modelo de pantalla de OpenXava. (Agradezco a Javier Paniza de Gestion400 ). En principio con:
    nombre panel [campo1, campo2;campo3] inidcamos con corchetes que tenemos un panel de nombre "nombre panel" qyue tiene 3 campos, los cos primeros en una misma línea (separado por coma ",") y el tercero en una línea nueva (separado por punto y coma ",")
    nombre tab1 {campo4}, nombre tab2{campo5;campo6}  indicamos que tenemos dos tabs o pestañas, el primero con un campo y el segundo con dos campos, cada uno en una lìnea (separacion por punto y coma ";")
  • events: Definimos los eventos, para ello tenemos que decirle el campo que ha provocdeo el evento, en este caso tenemos 2 campos que lanzan eventos ( expCodiOrigen y  expCodiDestinacio) y hay que pasarle los atributos de htmx (hx-trigger para el evento a lanzar, hx-include para los parámetros a pasar al request a parte del campo que lanza el evento, hx-target que es el elemento que recibe el resultado la request)
    También tenemos que indicarle los elementos que se veran afectados sus valores al recibir la request hx_edu_target.
    También hay que indicar el módulo "module" y la función "function"del módulo junto con sus parámetros que se ejecutará en el request
  • other: Es una lista de campos con valor fijo que normalemente se meteran ocultamente en el form y así poderlos pasar al request. Tendra de estructyra n[nombre_campo, valor], para cada campo
  • session: es para guardar y leer datos datos en la sesión. Normalmente se utiliza cuando una acción recoge valores que se urilizan en una opción. Por ejmplo si qeremos copiar un campo de un registro a otro, se ejecutaría una acción para recoger el campo y guardarlo en la sesión y luego se ejecutaría otra acción que recogería el valor del campo de la sesión y lo copiaría al nuevo registro.


Veamos la pantalla que nos genera

En este caso al darle valor al campo de "codi expedient Sedipualba Origen" rellena el campo de descripción de expediente de origen y llena la abla con los interesados del primer expedeiente, Cuando se de el expediente de estino se llenara el campo de descripción y en la tabla solo apareceran los interesados del expediene origen que no estan en el expediente de destino y con el botón aceotar de traspasan los interesados al expdiente de destino

3. Acciones tipo "bash". Se desaconseja su uso

En este caso todo es igual que las acciones tipo python excepto que  podemos llamamar a módulos de otro proyecto y por tanto tenenemos que cargar su entorno virtual.

Esta opción "batch" la desaconsejo, pues complica las ejecución, La única ventaja que tiene es que podemos separar el proyecto en módulos independientes, per hay que cargar el entorno virtual de cada proyecto de llamada

Veamos el fichero parte del fichero  "actions_01.yml" de la carpeta "static/script":

# 3. Generar l'index en PDF i part del llibre ENI XML
113  : 
  type: bash
  program: llibreries
  folder: ../02.llibreries
  venv: venv02
  module: eni/xmexpeni.py
  function: expedientENI

  help: >
    Han d'estar firmades les actes per part d'Alcaldia i Secretaria.
    Es generaran els documents ENI i s'ompli la taula d'índexs en el DOCX

  #OJO: el pattern hem de canviar ?->\? si no trenca la http
  #     despres en el programa hem de  desfer el canvi \?->?       
  params: [
    [anyPle,int,4,"Any:","$$YEAR-1$$",[]],
    [expCodi,string,10,"Codi expedient Sedipualba:","1814787N",[]],
    [prefPle,string,10,"Prefix ple:","INDEX_ACTES_PLE",[]],
    [docsFolderPath, folder, 60, "Carpeta de Documents:", '/mnt/NASDADES_DOCS/TEMP/ACTES_PLE_2022/Definitiu/', [] ],
    [windowsDocsFolderPath, folder, 60, "Carpeta de Documents Windows:", 'Z:\\TEMP\\ACTES_PLE_2022\\Definitiu\\', [] ],
    [pattern, string, 60, "Pattern de les actes:", '^Ple.*\.pdf$', [] ],
    [sufixFirmaSecretari, str, 60, "Sufix firma Secretari:", '_firmado_por_CESAR_HERRERO_POMBO_-_DNI_35299409K.pdf', [] ],
  ]
  ox: > 
      Paràmetres Generals [anyPle, expCodi];
      Localització Documents [docsFolderPath;windowsDocsFolderPath];
      Altres [ prefPle; pattern; sufixFirmaSecretari]
    
  other: [
    [fase,1],
    [templatesPath,templates/ENI/],
    [organo,L01462384],
    [tDocu,TD02],
    [plantillaXML,ENIExpTemplate.xml],
    [docxIndex,indexTemplate.docx],
  ]
    


  • "type" es ahora "bash" en vez de "python".
  • "type" es ahora "bash" en vez de "python".
  • "program" es la shell a ejecutar añadiéndole la terminación ".sh" y estará en la carpeta "static/scripts". En este caso utilizamos el shell "llibreries.sh" que se mostrará más adelante.
  • "venv": nombre de la carpeta donde está el entorno virtual a ejecutar
  • "folder": La carpeta general del proyecto
  • "module": Es la carpeta donde está el módulo python
  • "function": Es la función a ejecutar
Los otros campos coinciden con el de la acción tipo "bash", por tanto se puede consultar en el apartado anterior estos parámetros: help, params, ox, events (y sus componentes), other y session

En ese caso la pantalla resultante no difiere para nada de la de una tipo "python"

Veamos la complejidad que aporta el shell script "llibreries.sh" de la carpeta "static/scrips"

#!/bin/bash

#--------------------------
# 0. Exemple de crida
# ./static/scripts/llibreries.sh \
#  "folder=../02.llibreries" "venv=venv02" \
#  "module=eni.xmexpeni" "function=expedientENI" \
#  "docsFolderPath=docs/actes2022/" \
#  "templatesPath=templates/ENI/" \
#  "filter=*ord*aprov*sio*_signed.pdf" \
#  "anyPle=2022" \
#  "expCodi=1814787N" \
#  "organo=L01462384" \
#  "tDocu=TD02" \
#  "prefPle=INDEX_ACTES_PLE" \
#  "plantillaXML=ENIExpTemplate.xml" \
#  "docxIndex=indexTemplate.docx"
#
#
# O també podriem executar en background
# Run the Python script in the background
#  nohup python myscript.py > /dev/null 2>&1 &
#
#---------------------------

remove_quotes() {
    local arg="$1"
	# Remove leading double quote if present
    arg="${arg#\"}"
	# Remove leading double quote if present
    arg="${arg%\"}"
    echo "$arg"
}

#------------------------------------
# 1. Recogemos los parametros
#------------------------------------

# Inicialize variables
folder_value=""
venv_value=""
module_value=""
other_params=""

# Función para mostrar uso del script
usage() {
  echo "Uso: $0 'folder=RUTA'  'venv=carpeta entorno virtual' 'module=carpeta.modulo.py'  ... otros_parametros=VALOR ..."
  echo "Ejemplo:"
  echo "  $0 folder=/mi/carpeta env=producción venv=venv02 module=eni.xmexpeni config=ejemplo version=1.0"
  exit 1
}

# Verificar si se proporcionaron argumentos
if [ $# -lt 3 ]; then
  usage
fi

# Procesar cada argumento
for argIni in "$@"; do
  # Remover comillas
  arg=$(remove_quotes "$argIni")
  
  # Verificar si el argumento contiene un '='
  if [[ "$arg" == *=* ]]; then
    # Dividir el argumento en clave y valor
    key="${arg%%=*}"
    value="${arg#*=}"
	#echo "$key    AAAA   $value  AAAAA  $arg"
	
    case "$key" in
      folder)
        folder_value="$value"
        ;;
      venv)
        venv_value="$value"
        ;;
	  module) 
        module_value="$arg"
		;;
      *)
        # Concatenar otros parámetros
        other_params+=" \"$arg\""
        ;;
    esac
	#echo "$key    BBBBB   $value"
  else
    echo "Argumento inválido: $arg"
    usage
  fi
done

#echo "FOLDER=  $folder_value"
#echo "VENV= $venv_value"
#echo "MODULE= $module_value"

# Verificar que 'folder' y 'env' hayan sido proporcionados
if [[ -z "$folder_value" || -z "$venv_value" || -z "$module_value" ]]; then
  echo "Error: Se requieren al menos un argumento 'folder' 'venv'y 'module'."
  usage
fi


#--------------------------------------
# 2. Nos cambiamos a la carpeta del modulo
#-------------------------------------
pwd
cd "$folder_value"
pwd

#--------------------------------------
# 3. Activamos el entorno virtual
#-------------------------------------
source "$venv_value"/bin/activate


# Crear la cadena concatenada
final_string1="python xmexec.py \"${module_value}\" ${other_params}"
echo "--------------------------------------"
echo "FINAL STRING=$final_string1"
echo "--------------------------------------"
python xmexec.py \"${module_value}\" ${other_params}







Python (XIX) . Creación de un miniframework (I) Definición de los menús

1. Configuración de los menús

 Para definir los menús utilizaremos un fichero yaml, donde cada menú es un objeto o diccionario cuya clave es el número de menu.

Si la clave tiene una sola cifra se entiende que es un menú principal (que no tiene padre), per ejemplo 1,2,3...

Si acabamos con los números podemos utilizar letras.

Es conveniente situarlos en orden, prinmero el menú y despúes sus hijos.

Supongamos que tenemos 3 menús principales (1,2,3) , y el número 1 tiene 2 hijos (11 y 12) y que el hijo (11) tiene 3 hijos más (111,112, y 113). Tendremos este orden

1,11,111,112,113,12,2,

Si al menú 2 le damos dos hijos (21, 22) y al 3 le damos 3 hijos (31,32,33) quedaría:

1,11,111,112,113,12,2,21,22,3,31,32,33 

Las propedades que podemos dar a cada menú son:

description: La descripción que aprarecerá

tooltip: mensaje que aparece al situar el rató sobre el menú,

icon: icono de font awesome

En principio, obligatoriament se necesita la clave que indica la posición del menú por ejemplo "111" y la "description". Los demás parámetros son opcionales, pero recomendables.

Veamos un ejemplo del fichero menus.yaml. Este fichero se guardará en la carpeta "static/conf" del proyecto

############################################################:
# menus.yml
############################################################:
1    : {description: 'Secretaria', tooltip: 'Secretaria guai!' , icon: 'fa-solid fa-skull-crossbones'}
11   : { description: "Llibre d'actes de plens"   }
111  : { description: "1. LOCAL: Firmar Actes per l'Alcaldia" }
112  : { description: "2. LOCAL: Firmar Actes per la Secretaria" }
113  : { description: "3. Generar l'index en PDF i part del llibre ENI XML" }
114  : { description: "4. LOCAL: firmar l'índex en PDF per Secretaria"    }
115  : { description: "5. Afegir l'índex al PDF per Secretaria"  }
116  : { description: "6. LOCAL: Firmar l'index del llibre ENI XML per Secretaria"  }
2    : { description: 'Sedipualba'  }
21   : { description: 'A. Expedients'}
211  : { description: 'A1. Copiar interessats entre expedients'}
212  : { description: 'A2. Copiar les resolucions d''un any a un expedient'}
3    : { description: 'Personal'                    }
31   : { description: 'Control Presència'}
32   : { description: 'Menu 32'     }
321  : { description: 'Menu 321' , icon: 'fa-solid fa-skull-crossbones'}
322  : { description: 'Menu 322'   }
323  : { description: 'Menu 323'   }
4    : { description: 'Informàtica' ,  tooltip: 'Informàtica guai del Paraguai!' , icon: 'fa-solid fa-skull-crossbones'}
41   : { description: 'Taula Municipio'   }

Esto nos dará una imagen como esta

2. La clase menu.py


En la clase menú, a parte de tener la clave ("id"), "description", "tooltip" e "icon", tenemos la propieada "action", que es la acción del menú y se definirá en fichero a parte.

A partir del fichero "menu.yml" podemos obtener quien es el padre de nuestro menú (basta con quitarle la última letra de nuestro menú) que es la propiedad "parentId" y la lista de hijos "childrenIds" que corresponden a todos aquellos menús que tienen una letra más de longitud en su "id" y que sus claves comientan con la clave del padre. Por ejemplo "3" es padre de "31" y los hijos de "31" són "311" y "312"

De esta clase hemos destacado sus atributos que hemos visto antes. Cabe destacar el constuctor "__init__" ,los "setters" y las funciones encargadas de leer las propiedades de los menús que están guardadas en:

  • menus_???.yml: Nomalmente los emnús de una aplicación se pueden ver bien en un solo fichero "menus.yml", pero podemos partirlo en varios ficheros como "menus_01.yml", "menus_011.yml". La condicion es que comience por "menus "y a continuación  un guión bajo "_" y varios dígitos y terminar en ".yml"
  • menu_icons.yml: Para cada tipo de acción asigna un icono por omisión.
  • actions_???.yml: Aquí se definen las acciones. Si que es conveniente separarlo en varios ficheros pues las acciones tienen un montón de parámetros, por tanto con el fin de evitar errores se opta por tener un fichero por cada acción y como mucho 3 acciones, pero no es conveniente.
Veamos el fichero menu_icons.yml que para cada tipo de acción (tabién se guardará en la carpeta "static/conf" del proyecto

############################################################:
# menu_icons.yml
############################################################:

default: 'fa-solid fa-folder'
table: 'fa-solid fa-table-cells'
python: 'fa-brands fa-python'
report: 'fa-solid fa-rectangle-list'
action: 'fa-solid fa-gears'
bash: 'fa-solid fa-bolt'
none: 'fa-solid fa-hand-middle-finger'

Los tipos de acciones son:
default: por omisión
table: para hace un CRUD de una tabla
python: un programa python
report: Un listado
action: Una accion general (???)
bash: un porgrama bash
none: Nada (????)