CreaRack-SL

Reorganización de menú CONFIGURATION con acordeón (tarea #182)

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)
  • Logout y Help accesibles 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:

  1. Botón “Config” → “CONFIGURATION” (uppercase, letter-spacing 0.03em)
  2. Estructura de secciones: <button :class="cls..."> + <div class="dropdown-section-body" x-show="sec..."> con items anidados
  3. Permisos templated: {% if user.role == 'admin' %} rodea secciones sensibles
  4. 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
  5. Nuevo elemento: Download Agent link si AGENT_DOWNLOAD_URL está configurada (env/settings)

Compatibilidad

  • IDs preservados: Todos los botones con listeners externos mantienen su id original → sin ruptura de JS listeners
  • Context variables: Espera AGENT_DOWNLOAD_URL en 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]]