Descripción
Refactorización del menú desplegable “CONFIGURATION” (anteriormente “Config”) en el header del dashboard, reorganizando 7 botones sueltos en 6 secciones acordeón auto-colapsables con gestión CSP-safe mediante Alpine.js.
Motivación
Antes de esta refactorización, el header del dashboard contenía múltiples botones sueltos (Agent, Wide View, Shine, Guide, Help, Logout, Users Management) distribuidos en varios grupos visuales, lo que reducía legibilidad y ocupaba mucho espacio. La tarea #182 (UX del header) buscaba consolidar estas acciones en una única ubicación jerárquica.
Cambios principales
1. Visibilidad e inclusión
- El menú CONFIGURATION ahora es visible para todos los usuarios (antes era admin-only)
- Acciones sensibles mantienen restricción de rol admin (LOCAL AGENT, FILE OPERATIONS, SETTINGS, Users Management)
LogoutyHelpaccesibles para usuarios no-admin (garantiza acceso a salida segura y ayuda)
2. Estructura de secciones
CONFIGURATION (menú desplegable)
├── LOCAL AGENT [admin]
│ ├── Agent
│ ├── Download Agent [nuevo]
│ └── Uninstall Local Agent
├── NAVIGATION [todos]
│ ├── Wide View
│ ├── Shine
│ └── Open modules in new tab (checkbox)
├── USERS [todos, Users Management admin]
│ ├── Users Management [admin]
│ └── Logout ({{ user.username }})
├── HELP [todos]
│ ├── Guide
│ └── Help
├── FILE OPERATIONS [admin]
│ ├── Export to CSV
│ ├── System Logs
│ ├── Project Report
│ ├── Full System Backup
│ └── Full System Restore [peligro]
└── SETTINGS [admin]
├── Company Data
├── Third-Party Licenses
├── Stored Credentials
└── Re-run Welcome Wizard
3. Comportamiento del acordeón
- Una única sección abierta a la vez (auto-colapsable)
- Toda acción dentro del menú cierra el dropdown tras ejecutar
- Cabecero de sección es clickeable y muestra estado activo
- Transiciones CSS fluidas (200ms)
Implementación
CSS (.dropdown-section, .dropdown-section-body)
- Cabecero de sección:
background: rgba(59, 130, 246, 0.05), uppercase, 0.7rem, font-weight 800 - Estado activo: borde izquierdo azul (3px), fondo más intenso, color más claro
- Cuerpo de sección: ítems indentados (padding-left: 28px) bajo su cabecero
- Transiciones: 0.2s en background y color
Archivo: static/css/components.css (+45 líneas)
Componente Alpine (refactorización completa)
Cambios en static/js/alpine-components.js:
Alpine.data('configDropdown', () => ({
open: false, // estado del dropdown
section: null, // sección abierta ('agent', 'nav', 'users', 'help', 'files', 'settings', null)
// Control básico
toggle() { this.open = !this.open; if (!this.open) this.section = null; }
close() { this.open = false; this.section = null; }
// Toggles por sección (CSP-safe: sin args)
toggleAgent() { this.section = this.section === 'agent' ? null : 'agent'; }
toggleNav() { this.section = this.section === 'nav' ? null : 'nav'; }
toggleUsers() { this.section = this.section === 'users' ? null : 'users'; }
toggleHelpSec() { this.section = this.section === 'help' ? null : 'help'; }
toggleFiles() { this.section = this.section === 'files' ? null : 'files'; }
toggleSettings() { this.section = this.section === 'settings' ? null : 'settings'; }
// Getters de visibilidad (:x-show)
get secAgent() { return this.section === 'agent'; }
get secNav() { return this.section === 'nav'; }
get secUsers() { return this.section === 'users'; }
get secHelp() { return this.section === 'help'; }
get secFiles() { return this.section === 'files'; }
get secSettings() { return this.section === 'settings'; }
// Getters de clase (:class - aplicar 'active' a cabecero)
get clsAgent() { return this.section === 'agent' ? 'dropdown-section active' : 'dropdown-section'; }
get clsNav() { return this.section === 'nav' ? 'dropdown-section active' : 'dropdown-section'; }
// ... (6 en total)
// Acciones desplegadas desde header a menú
openAgentFleet() { this.close(); openAgentFleetModal(); }
toggleWidth() { this.close(); toggleWidthMode(); }
cycleShineNow() { this.close(); cycleShine(); }
openGuide() { this.close(); openGuideModal(); }
toggleHelp() { this.close(); toggleHelpPanel(); }
// Acciones existentes refactorizadas (all call this.close() antes)
exportCsv() { this.close(); ... }
// ... etc
}))
Archivo: static/js/alpine-components.js (+63 líneas, 39 líneas de métodos/getters)
Principios CSP:
- Sin expresiones en template: todas las decisiones via getters (
:class,:x-show) - Métodos sin argumentos → safe en build Alpine CSP
- Patrón de “acciones envolventes” (close + acción real)
Template (reestructuración)
Archivo: templates/base.html (~+110 líneas)
Cambios principales:
- Botón “Config” → “CONFIGURATION” (uppercase, letter-spacing 0.03em)
- Estructura de secciones:
<button :class="cls...">+<div class="dropdown-section-body" x-show="sec...">con items anidados - Permisos templated:
{% if user.role == 'admin' %}rodea secciones sensibles - IDs preservados:
id="agent-fleet-btn",id="width-toggle-btn",id="shine-toggle-btn",id="logout-btn",id="settings-users-item",id="settings-company-item"para mantener compatibilidad con listeners existentes - Nuevo elemento:
Download Agentlink siAGENT_DOWNLOAD_URLestá configurada (env/settings)
Compatibilidad
- IDs preservados: Todos los botones con listeners externos mantienen su
idoriginal → sin ruptura de JS listeners - Context variables: Espera
AGENT_DOWNLOAD_URLen contexto template (puede ser None) - I18n: Strings nuevos (Configuration, Download Agent, Users, Help, File Operations, Settings, Local Agent, Navigation, Users) sin traducción ES aún (quedan EN canónico hasta ciclo i18n F4)
Notas técnicas
- El dropdown mantiene la ref a
restoreInput(input file hidden para restauración de backup) - Click-outside (Alpine:
@click.outside="close") cierra automáticamente el menú - Transiciones CSS animadas (enter/leave, 100ms enter, 75ms leave)
- Accordion logic completamente client-side, sin API calls
Véase también
- [[concept—general—interfaz-usuario]]
- [[concept—general—accesibilidad-permisos]]
- [[concept—frontend—alpine-js]]
- [[concept—security—csp]]
- [[entity—static—component—base-template]]