Problema

En entornos de homelab y virtualización es frecuente personalizar la interfaz web de herramientas como Proxmox, OpenNebula o cualquier aplicación construida sobre ExtJS. El objetivo es mejorar la estética o adaptar colores a un esquema propio. Sin embargo, al aplicar un tema CSS se pueden presentar dos síntomas recurrentes:

  1. Confusión entre fila seleccionada y fila bajo el cursor – los estilos :hover y .x-grid-item-selected comparten la misma regla de color, de modo que el usuario no distingue qué elemento está realmente activo.
  2. Rotura del layout después de una actualización del paquete – el instalador del tema guarda una copia del archivo CSS original y la restaura en cada upgrade. Si la actualización incluye un nuevo stylesheet, el script sobrescribe el nuevo archivo con la copia antigua, dejando la UI “pegada” a una versión obsoleta del tema base.

Estos problemas no son exclusivos de Proxmox; cualquier aplicación que dependa de ExtJS para medir geometría en tiempo de renderizado sufre cuando el tema altera propiedades que afectan al cálculo de tamaños (peso de fuente, letter-spacing, padding, bordes). El resultado son botones con texto recortado, columnas de tabla desalineadas o sombras que aparecen fuera de lugar.

Causa

1. Regla CSS idéntica para hover y selected

En muchos temas predeterminados la hoja de estilos contiene una línea como:

.x-grid-item-over,.x-grid-item-selected{background-color:#595959}

Al usar el mismo selector y color para ambos estados, el navegador no tiene forma de diferenciar visualmente la fila que el usuario ha seleccionado de la que simplemente está bajo el puntero.

2. Instaladores que hacen snapshots estáticos

Los scripts de instalación de temas suelen:

  • Copiar el stylesheet original a un archivo de respaldo (*.bak).
  • Reemplazar el original con el tema personalizado.
  • En cada actualización del paquete, restaurar el backup antes de aplicar el nuevo archivo del paquete.

Si la actualización entrega un stylesheet modificado (por ejemplo, corrige el bug anterior), el script restaura la versión antigua del backup, impidiendo que el nuevo código llegue al sistema. El tema queda “fijado” a una versión anterior del upstream.

3. Cambios de geometría en CSS

ExtJS calcula anchos y alturas de componentes una sola vez, durante el render. Si el tema modifica:

  • font-weight a un valor más grueso,
  • letter-spacing,
  • padding o margin de celdas de grid,

el cálculo original queda desfasado y los textos se recortan o los elementos se superponen. El problema es sutil porque la UI sigue funcionando, pero la experiencia de uso se degrada.

4. Falta de verificación de integridad

Al restaurar el archivo original sin validar su hash, el instalador no detecta si el archivo ya había sido modificado por el paquete. Esto permite que versiones antiguas de estilos persistan indefinidamente.

Solución

Enfoque general

  1. Separar estilos de interacción (hover) de estilos de estado (selected).
    Define colores diferentes o, al menos, modifica la regla para que el selector :hover tenga prioridad visual.

  2. Implementar un instalador “no‑snapshot”.
    En lugar de guardar una copia del stylesheet, verifica si el archivo actual coincide con el que entrega el paquete (comparando hashes). Si es idéntico, guarda ese hash como base y superpone tu tema encima. En upgrades, vuelve a comparar; si el hash cambió, actualiza la base antes de aplicar la capa de tema.

  3. Limitar el tema a propiedades de “pintado”.
    Evita tocar cualquier regla que altere el modelo de caja: font-weight, letter-spacing, padding, border. En su lugar, usa color, background, box-shadow y opacity. Si necesitas un efecto visual que implique bordes, usa sombras internas (inset box-shadow) para que el tamaño del elemento no cambie.

  4. Validar integridad con MD5 o SHA256.
    Al desinstalar, genera el hash del archivo original del paquete y compáralo con el que está en el sistema. Sólo si coinciden, reemplaza el archivo; de lo contrario, avisa al administrador de un posible desalineamiento.

  5. Automatizar pruebas de layout.
    Usa herramientas como puppeteer o selenium para capturar dimensiones de componentes críticos antes y después de aplicar el tema. Si alguna medida varía, revierte los cambios que afecten la geometría.

Pasos concretos

  1. Crear una hoja de tema mínima que solo sobrescriba colores y sombras.

  2. Añadir una regla específica para la fila seleccionada:

    .x-grid-item-selected{background-color:#2a9d8f !important}
    .x-grid-item-over{background-color:#595959}
    
  3. Escribir un script de instalación que:

    • Detecte la ruta del stylesheet (/usr/share/pve-manager/css/pvemanager.css en Proxmox).
    • Calcule su hash y lo guarde en /var/lib/theme-manager/base.hash.
    • Copie el tema personalizado a la misma ubicación.
    • En upgrades, vuelva a calcular el hash; si difiere, actualice la base antes de volver a aplicar el tema.
  4. Desinstalación segura: comparar el hash guardado con el del archivo actual; si coinciden, restaurar el archivo original del paquete usando dpkg -L proxmox-ve para obtener la ruta del archivo de referencia.

Cuándo aplicar esta solución

  • Síntomas de selección indistinguible en cualquier tabla o grid de la UI basada en ExtJS.
  • Desalineación visual tras una actualización del software que incluye cambios en el CSS base.
  • Problemas de clipping de texto o iconos en botones y encabezados de columnas.
  • Entornos donde se gestionan varios hosts y se desea mantener un tema consistente sin bloquear actualizaciones.

No aplicar si el problema se origina en un bug de JavaScript propio de la aplicación y no en el CSS; en ese caso la solución implica parchear el código fuente o esperar una corrección upstream.

Código

#!/usr/bin/env bash
# theme-manager.sh – instala/uninstala un tema CSS sin sobrescribir upstream

THEME_DIR="/usr/share/pve-manager/css"
BASE_FILE="${THEME_DIR}/pvemanager.css"
CUSTOM_FILE="/opt/custom-theme/pvemanager.css"
HASH_FILE="/var/lib/theme-manager/base.hash"

install_theme() {
    if [[ ! -f "$HASH_FILE" ]]; then
        # Primer despliegue: guardar hash del archivo original
        md5sum "$BASE_FILE" | awk '{print $1}' > "$HASH_FILE"
    fi

    # Copiar tema personalizado
    cp "$CUSTOM_FILE" "$BASE_FILE"
    echo "Tema aplicado."
}

update_base() {
    # Detectar cambio en el upstream
    NEW_HASH=$(md5sum "$BASE_FILE" | awk '{print $1}')
    OLD_HASH=$(cat "$HASH_FILE")

    if [[ "$NEW_HASH" != "$OLD_HASH" ]]; then
        echo "Upstream CSS cambió, actualizando base..."
        echo "$NEW_HASH" > "$HASH_FILE"
    fi
}

uninstall_theme() {
    if [[ -f "$HASH_FILE" ]]; then
        # Restaurar archivo original usando dpkg (asume paquete proxmox-ve)
        dpkg -L proxmox-ve | grep "$BASE_FILE" | xargs -I{} cp {} "$BASE_FILE"
        rm -f "$HASH_FILE"
        echo "Tema removido, archivo original restaurado."
    else
        echo "Hash no encontrado, no se pudo restaurar."
    fi
}

case "$1" in
    install) install_theme ;;
    update) update_base ;;
    uninstall) uninstall_theme ;;
    *) echo "Uso: $0 {install|update|uninstall}" ;;
esac

Verificación

  1. Comprobar colores de fila: abre la UI, pasa el cursor sobre una fila y selecciona otra. Los colores deben diferir claramente.
  2. Validar dimensiones: abre la consola del navegador y ejecuta document.querySelectorAll('.x-grid-item')[0].getBoundingClientRect() antes y después de aplicar el tema; los valores width y height deben ser idénticos.
  3. Revisar hash: tras una actualización del paquete, ejecuta theme-manager.sh update y verifica que el archivo base.hash contiene el nuevo hash.
  4. Desinstalar y comparar: corre theme-manager.sh uninstall y confirma que el archivo CSS coincide con el que entrega el paquete (md5sum /usr/share/pve-manager/css/pvemanager.css).

Notas adicionales

  • Backup de la hoja original siempre es útil, pero guárdalo fuera del árbol de paquetes (por ejemplo en /var/backups/pve-css.bak).
  • ExtJS y fuentes: si necesitas cambiar la tipografía, hazlo mediante @font-face y mantén el mismo peso (font-weight: normal) para evitar que el cálculo de ancho varíe.
  • Compatibilidad con versiones futuras: revisa el changelog del paquete después de cada upgrade; si el upstream corrige el bug de hover/selected, puedes eliminar la regla personalizada.
  • Uso de herramientas de lint CSS: herramientas como stylelint pueden detectar reglas que alteren el box model y prevenir errores antes de aplicar el tema.