CreaRack-SL

Servicios Centralizados Frontend

Servicios Centralizados Frontend

IMPORTANTE: Antes de crear un nuevo servicio/utility, consulta este documento. Los servicios centralizados evitan duplicación de código y facilitan el mantenimiento.

Última actualización: 02-04-2026 Versión: v1.0.8


Ubicación de Servicios

static/js/
├── editor/                 # Utilidades de editor (compartidas globalmente)
│   └── modal_helper.js   # ✅ Modal open/close/toggle centralizado
├── services/               # Servicios principales (API, logging, notificaciones)
│   ├── ApiService.js      # ✅ HTTP requests con CSRF
│   ├── EChartsService.js  # ✅ Factory para gráficas Apache ECharts
│   ├── ChartGuideService.js # ✅ Modal centralizado de guía de interpretación de gráficas
│   ├── ScopedInsightTab.js # ✅ Reusable CNS (AI Insights) tab — scoped by targetIds (Observatory, Wireless, UPS)
│   ├── InsightIndicatorService.js # ✅ Sidebar pulsing dot indicators for active AI insights
│   ├── QRLabelService.js # ✅ Centralized QR label generation + print (dashboard + editor)
│   ├── NavModuleService.js # ✅ Module nav same-tab/new-tab preference + first-use popover
│   ├── ToastService.js    # ✅ Notificaciones toast
│   └── Logger.js          # ✅ Sistema de logging con niveles
├── utils/                  # Utilidades auxiliares (validación, helpers)
│   ├── TabHelper.js       # ✅ "New Tab" standalone mode
│   ├── IpValidator.js     # ✅ Validación IPs RFC 1918
│   ├── DeviceFilterService.js # ✅ Device list filtering (search, status, group, rack)
│   ├── InsightDetailModal.js  # ✅ AI Insight detail modal + conversation history + revise (shared Observatory + Editor)
│   ├── InsightUtils.js        # ✅ Shared constants (RISK_COLORS, RISK_PRIORITY) + helpers (escHtml, timeAgo)
│   ├── InsightActions.js      # ✅ Shared insight action handlers (handleExplain, handleRevise, buildInsightActions)
│   ├── SidebarResize.js      # ✅ Draggable sidebar resize (Terminal, Observatory)
│   └── GuideService.js       # ✅ App Guide slideshow: open/close/reset, auto-detect slide from URL, keyboard nav
├── state_core/             # Gestión de estado global
│   ├── StateManager.js    # ✅ Undo/Redo global
│   └── PersistenceManager.js # ✅ LocalStorage wrapper
└── constants/              # Constantes globales
    ├── Timing.js          # ✅ Timeouts, debounce
    └── Typography.js      # ✅ Fuentes autorizadas

1. ApiService.js

Ubicación: static/js/services/ApiService.js Propósito: Wrapper centralizado para HTTP requests con manejo automático de CSRF tokens.

Cuándo Usar

  • ✅ SIEMPRE para llamadas a la API backend
  • ✅ Cuando necesites GET, POST, PUT, DELETE a endpoints Django

Cuándo NO Usar

  • ❌ Para fetch() a APIs externas (usar fetch nativo)
  • ❌ Para WebSocket connections

API

import { ApiService } from '../services/ApiService.js';

// GET request
const data = await ApiService.get('/api/endpoint');

// POST request
const result = await ApiService.post('/api/endpoint', { key: 'value' });

// PUT request
await ApiService.put('/api/endpoint/123', { updated: true });

// DELETE request
await ApiService.delete('/api/endpoint/123');

Características

  • ✅ CSRF token automático (lectura desde cookie)
  • ✅ Headers JSON automáticos
  • ✅ Error handling con throw
  • ✅ Singleton pattern (window.ApiService)

Ejemplo de Uso Incorrecto

// ❌ NO HACER ESTO
fetch('/api/devices', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data)
});

// ✅ HACER ESTO
await ApiService.post('/api/devices', data);

2. TabHelper.js

Ubicación: static/js/utils/TabHelper.js Propósito: Utilidad centralizada para abrir páginas en “New Tab” con modo standalone.

Cuándo Usar

  • ✅ Para implementar botones “New Tab” en cualquier página
  • ✅ Para ocultar sidebar/elementos en modo standalone
  • ✅ Para detectar si una página está en standalone mode

Cuándo NO Usar

  • ❌ Para navegación normal entre páginas (usar <a> o window.location)
  • ❌ Para abrir modales

API

import {
    openInNewTab,
    applyStandaloneMode,
    isStandaloneMode,
    openTerminalInNewTab,
    openObservatoryInNewTab,
    openObservatoryOverviewInNewTab
} from '../utils/TabHelper.js';

// Función genérica
openInNewTab({
    baseUrl: '/my-page/',
    params: { item_id: 123, name: 'Item' },
    onClose: () => closeInternalTab()
});

// Wrappers específicos
openTerminalInNewTab(deviceId, deviceName, () => closeTab(tabId));
openObservatoryInNewTab(deviceId, deviceName, () => closeTab(tabId));
openObservatoryOverviewInNewTab();

// Aplicar standalone mode en init de página
applyStandaloneMode({
    sidebar: '.my-sidebar',
    layout: '.my-layout',
    hideElements: ['.overview-tab', '.menu']
});

// Detectar standalone mode
if (isStandaloneMode()) {
    console.log('Running in standalone mode');
}

Características

  • ✅ URL construction automática con parámetro standalone=1
  • ✅ Callback opcional al abrir (ej: cerrar tab interna)
  • ✅ Wrappers específicos para Terminal y Observatory
  • ✅ Standalone mode detection y aplicación

Flujo de Uso Completo

1. En la página de origen (ej: Terminal):

// En el botón "New Tab"
button.onclick = () => {
    openTerminalInNewTab(deviceId, deviceName, () => {
        // Cerrar la tab interna después de abrir la nueva
        this.app.closeTab(tabId);
    });
};

2. En la página de destino (ej: Terminal):

// En DOMContentLoaded o init()
applyStandaloneMode({
    sidebar: '.terminal-sidebar',
    layout: '.terminal-layout'
});

Páginas que Usan TabHelper

  • ✅ Terminal (/terminal/)
  • ✅ Observatory Devices (/monitoring/?device_id=X)
  • ✅ Observatory Manual Targets (/monitoring/?target_id=X)
  • ✅ Observatory Overview (/monitoring/?overview=1)

Agregar a Nueva Página

Paso 1: Import en archivo principal

import { openInNewTab, applyStandaloneMode } from '../utils/TabHelper.js';

Paso 2: Añadir botón “New Tab”

<button onclick="MyApp.openInNewTab()">New Tab</button>

Paso 3: Implementar función

MyApp.openInNewTab() {
    openInNewTab({
        baseUrl: '/my-page/',
        params: { id: this.currentId },
        onClose: () => this.closeInternalView()
    });
}

Paso 4: Aplicar standalone mode en init

document.addEventListener('DOMContentLoaded', () => {
    applyStandaloneMode({
        sidebar: '.my-sidebar',
        layout: '.my-layout'
    });
});

3. EChartsService.js

Ubicación: static/js/services/EChartsService.js Propósito: Factory centralizado para crear, configurar y gestionar gráficas Apache ECharts.

Cuándo Usar

  • ✅ Para crear gráficas en Observatory
  • ✅ Para aplicar estilos consistentes (13 estilos predefinidos)
  • ✅ Para gestionar animación, zoom, export

Cuándo NO Usar

  • ❌ Para gráficas simples de Canvas/SVG custom
  • ❌ Para otras librerías de gráficas

API

import { EChartsService } from '../services/EChartsService.js';

// Crear gráfica
const chart = EChartsService.createChart('my-chart-id', {
    xAxis: { type: 'time' },
    yAxis: { type: 'value' },
    series: [{ type: 'line', data: [...] }]
});

// Actualizar datos
EChartsService.updateChart('my-chart-id', newData);

// Reset zoom
EChartsService.resetZoom('my-chart-id');

// Export PNG
EChartsService.exportChart('my-chart-id', 'filename');

// Aplicar estilo
EChartsService.applyChartStyle('my-chart-id', 'smooth');

// Destruir
EChartsService.destroy('my-chart-id');

Características

  • ✅ 13 estilos predefinidos (smooth, sharp, area, scatter, etc.)
  • ✅ Gestión automática de resize
  • ✅ Registry interna para evitar memory leaks
  • ✅ Export a PNG con watermark

4. ToastService.js

Ubicación: static/js/services/ToastService.js Propósito: Sistema de notificaciones toast consistente.

Cuándo Usar

  • ✅ Para feedback de acciones (success, error, info)
  • ✅ Para notificaciones temporales

Cuándo NO Usar

  • ❌ Para confirmaciones (usar confirm())
  • ❌ Para modales complejos

API

import { ToastService } from '../services/ToastService.js';

ToastService.show('Operation successful', 'success', 3000);
ToastService.show('Error occurred', 'error', 5000);
ToastService.show('Loading...', 'info');

Nota: Disponible globalmente como window.showAppToast().


5. StateManager.js

Ubicación: static/js/state_core/StateManager.js Propósito: Undo/Redo global para editores (Racks, Blueprints).

Cuándo Usar

  • ✅ Para implementar Ctrl+Z / Ctrl+Y en editores
  • ✅ Para historial de cambios

Cuándo NO Usar

  • ❌ Para estado temporal de UI (usar variables locales)
  • ❌ Para formularios simples

API

import { StateManager } from '../state_core/StateManager.js';

const stateManager = new StateManager(maxHistorySize);

// Guardar snapshot
stateManager.saveState(currentState);

// Undo
const previousState = stateManager.undo();

// Redo
const nextState = stateManager.redo();

6. PersistenceManager.js

Ubicación: static/js/state_core/PersistenceManager.js Propósito: Wrapper seguro para localStorage con namespace y validación.

Cuándo Usar

  • ✅ Para guardar preferencias de usuario
  • ✅ Para persistir estado entre sesiones
  • ✅ Para cache de configuración

Cuándo NO Usar

  • ❌ Para datos sensibles (usar backend)
  • ❌ Para datos grandes (>5MB)

API

import { PersistenceManager } from '../state_core/PersistenceManager.js';

// Save
PersistenceManager.save('myKey', { data: 'value' });

// Load
const data = PersistenceManager.load('myKey', defaultValue);

// Remove
PersistenceManager.remove('myKey');

// Clear all
PersistenceManager.clear();

7. IpValidator.js

Ubicación: static/js/utils/IpValidator.js Propósito: Validación de direcciones IP según RFC 1918 (redes privadas).

Cuándo Usar

  • ✅ Para validar IPs en formularios
  • ✅ Para detectar IPs privadas vs públicas

API

import { isPrivateIP } from '../utils/IpValidator.js';

if (isPrivateIP('192.168.1.1')) {
    console.log('IP privada');
}

8. Logger.js

Ubicación: static/js/services/Logger.js Propósito: Sistema de logging con niveles (debug, info, warn, error).

Cuándo Usar

  • ✅ Para logging consistente en desarrollo
  • ✅ Para debugging con niveles configurables

API

import { Logger } from '../services/Logger.js';

const logger = new Logger('MyModule');

logger.debug('Debug message');
logger.info('Info message');
logger.warn('Warning message');
logger.error('Error message');

9. DeviceFilterService.js

Ubicación: static/js/utils/DeviceFilterService.js Propósito: Filtrado centralizado de listas de dispositivos (search, status, group, rack). Compartido entre Terminal y Observatory.

Cuándo Usar

  • ✅ Para filtrar device lists en sidebars
  • ✅ Cuando una página tenga una lista de .device-item con data attributes

Cuándo NO Usar

  • ❌ Para filtrado de racks/blueprints (usan otro patrón)

API — Page-level filtering (fixed IDs)

import { DeviceFilterService } from '../utils/DeviceFilterService.js';

// Filter items in a list container
DeviceFilterService.filterDevices('#device-list');

// Clear search input and re-filter
DeviceFilterService.clearSearch(() => myFilterFn());

// Toggle search clear button visibility
DeviceFilterService.toggleClearBtn();

API — Scoped filtering (for modals / embedded panels)

Uses data-filter attributes instead of fixed IDs — avoids DOM conflicts when the page already has #device-search etc.

// Filter .filter-item elements within a scoped container
DeviceFilterService.filterScoped(overlayElement, '#my-list');

// Clear scoped search and re-filter
DeviceFilterService.clearScoped(overlayElement, () => myFilterFn());

// Toggle scoped clear button visibility
DeviceFilterService.toggleClearScoped(overlayElement);

Scoped HTML contract:

<input  data-filter="search">          <!-- Search input -->
<button data-filter="clear">           <!-- Clear button -->
<select data-filter-attr="risk">       <!-- Matches item data-risk -->
<select data-filter-attr="status">     <!-- Matches item data-status -->
<!-- Items: .filter-item with data-name, data-ip, + any data-* matching selects -->

Consumers

  • terminal/DeviceManager.js — Terminal Hub sidebar (page-level)
  • pages/observatory.js — Network Observatory sidebar (page-level)
  • pages/wireless.js — Wireless Monitor sidebar (page-level)
  • pages/observatory/ObservatoryCNS.js — Acknowledge History modal (scoped)

Expected HTML IDs (page-level mode)

  • #device-search — Search input
  • #device-search-clear — Clear button
  • #device-status-filter — Status dropdown
  • #device-group-filter — Group dropdown
  • #device-rack-filter — Rack dropdown

Expected Data Attributes on .device-item (page-level)

  • data-name — Device name (searchable)
  • data-ip — IP address (searchable)
  • data-status — Status (online/offline/unknown)
  • data-group-ids — Comma-separated group IDs
  • data-rack-id — Rack ID

Expected Data Attributes on .filter-item (scoped mode)

  • data-name — Primary searchable text
  • data-ip — Secondary searchable text
  • Any data-* matching select[data-filter-attr] values

10. ModalHelper (modal_helper.js)

Ubicación: static/js/editor/modal_helper.js CSS: static/css/components.css (clases .modal-overlay, .modal-content, .modal-content-md, .modal-content-lg) Propósito: Sistema centralizado de apertura/cierre de modales con drag & drop universal. Todos los modales de la aplicación deben usar este helper.

Cuándo Usar

  • ✅ SIEMPRE para abrir/cerrar modales (.modal-overlay)
  • ✅ Para configurar close handlers (botón X, Cancel, Escape)
  • ✅ Para cerrar todos los modales de golpe (closeAllModals)

Cuándo NO Usar

  • ❌ Para togglear visibilidad de elementos internos (paneles, progress bars, formularios dentro de modales)
  • ❌ Para overlays custom que no usan .modal-overlay (ej: guide-overlay, wizard-overlay)

API

import { openModal, closeModal, toggleModal, setupModalCloseHandlers, initModal, closeAllModals } from '../editor/modal_helper.js';

// Abrir modal (display: flex)
openModal('my-modal-id');

// Cerrar modal (display: none)
closeModal('my-modal-id');

// Toggle
toggleModal('my-modal-id');

// Setup completo con close handlers (botón X, Cancel, backdrop)
setupModalCloseHandlers('my-modal-id', {
    closeOnBackdrop: false,  // default: false (click fuera NO cierra)
    onClose: () => console.log('Modal cerrado')
});

// Init = setup + retorna objeto de control
const modal = initModal('my-modal-id');
modal.open();
modal.close();

// Cerrar todos los modales abiertos
closeAllModals();

Acceso Global

Las funciones principales están expuestas en window para scripts que no son ES modules:

window.openModal('my-modal-id');
window.closeModal('my-modal-id');
window.toggleModal('my-modal-id');

Configuración: closeOnBackdrop

Por defecto: false — hacer click fuera del modal NO lo cierra. Los modales solo se cierran con:

  • Botón de cierre (.close-modal, .modal-close, .modal-close-btn)
  • Botón Cancel en footer (.btn-secondary, [data-action="cancel"])
  • Llamada explícita a closeModal()

Consumers

MóduloImport
dashboard.jsopenModal, closeModal
auth.jsGlobals (window.openModal/closeModal)
base.jsGlobals (window.openModal/closeModal)
network_status.jsGlobals (window.openModal/closeModal)
script_manager.jsopenModal, closeModal
editor/backups.jsopenModal, closeModal
editor/import_export.jsopenModal, closeModal, closeAllModals
editor/ui_panels.jsopenModal, setupModalCloseHandlers
editor/DeviceInfoModal.jssetupModalCloseHandlers + window.openModal
editor/SshConfigModal.jssetupModalCloseHandlers
editor/PortConfigDiff.jsopenModal, closeModal
editor/ssh_integration.jsGlobals (window.openModal/closeModal)
terminal/AgentLifecycle.jsopenModal as _open, closeModal as _close
terminal/ManualConnection.jsopenModal as _open, closeModal as _close
terminal/DeviceManager.jsopenModal, closeModal
network/auto_provision.jsopenModal as _openModal, closeModal as _closeModal
observatory/ObservatoryAlerts.jsopenModal, closeModal
blueprints/MapInteraction.jsopenModal, closeModal

Estructura HTML de un Modal

<div id="my-modal" class="modal-overlay">
    <div class="modal-content modal-content-md">
        <div class="modal-header">
            <h2>Title</h2>
            <button class="modal-close-btn" title="Close">×</button>
        </div>
        <div class="modal-body">
            <!-- Content -->
        </div>
        <div class="modal-footer">
            <button class="btn btn-neutral close-modal" title="Cancel">Cancel</button>
            <button class="btn btn-action" title="Save changes">Save</button>
        </div>
    </div>
</div>

Drag & Drop

Todos los modales son arrastrables desde su header. El sistema detecta automáticamente el drag handle con 3 niveles de fallback:

  1. Clases conocidas: .modal-header, .modal-header-simple, .port-diff-modal-header, .guide-header
  2. Heading directo: Primer <h2>, <h3> o <h4> hijo directo del .modal-content
  3. Div wrapper: Primer <div> hijo que contenga un heading (ej: .terminal-header, .flex-between-center)

Implementación:

  • MutationObserver detecta automáticamente cuando cualquier modal se hace visible (por style.display, classList.add('active'), HTMX, etc.)
  • Usa transform: translate(x, y) (GPU-accelerated, zero reflows)
  • Al cerrar, la posición se resetea al centro
  • No interfiere con botones/inputs/selects dentro del header
  • Cursor grab / grabbing como feedback visual

No requiere configuración: Funciona automáticamente en todos los modales existentes y futuros.

Backlight Glow (CSS)

Los modales tienen un efecto de iluminación trasera blanca (backlight) definido en components.css:

  • 3 capas de box-shadow blanca con blur/spread progresivos
  • Simula luz indirecta posterior sobre fondo oscuro
  • Las rack cards del Dashboard también tienen un glow sutil (dashboard.css)

Ejemplo de Uso Incorrecto

// ❌ NO HACER ESTO
const modal = document.getElementById('my-modal');
modal.style.display = 'flex';
// ...
modal.style.display = 'none';

// ✅ HACER ESTO
import { openModal, closeModal } from '../editor/modal_helper.js';
openModal('my-modal');
closeModal('my-modal');

11. Semantic Button System (components.css)

Ubicación: static/css/components.css (sección SEMANTIC BUTTON ALIASES) Propósito: Sistema centralizado de clases de botón con colores semánticos. Evita duplicación de estilos de botón en CSS de páginas individuales.

Cuándo Usar

  • ✅ SIEMPRE que crees un botón en HTML o JS dinámico
  • ✅ Para estados toggle de botones (activo/inactivo)

Cuándo NO Usar

  • ❌ NUNCA crear estilos de botón custom en archivos CSS de página
  • ❌ NUNCA duplicar colores/bordes de estas clases bajo otro nombre

Clases Disponibles

ClaseColorIntención
btn-actionAzul (#82B1FF)Save, Create, Confirm, Apply
btn-dangerRojo (—danger)Delete, Remove, Disconnect
btn-warningNaranja (—warning)Edit, Modify, Reset, Caution
btn-successVerde (—success)Login, Connect, Accept
btn-exportAmarillo (#e3b341)Export, Print, Download
btn-infoCyan (#58a6ff)Info, Status, Expert Charts
btn-neutralBlancoNavigate, View, Close, Cancel

Tamaños

ClaseAltoUso típico
(base)~38pxModales, formularios
btn-sm28pxToolbars, paneles
btn-xs22pxBadges interactivos

Uso en HTML

<button class="btn btn-sm btn-action" title="Save changes">Save</button>

Uso en JS Dinámico

// Crear botón
const btn = document.createElement('button');
btn.className = 'btn btn-sm btn-action';
btn.textContent = 'Save';
btn.title = 'Save changes';

// Toggle estado
btn.classList.toggle('btn-action', isActive);
btn.classList.toggle('btn-warning', !isActive);

Regla de Oro

Si necesitas propiedades adicionales (width, margin, text-transform), añádelas en el CSS de la página pero SIN redefinir colores, bordes ni fondos. Estos vienen exclusivamente de components.css.

Auditoría (05-03-2026)

Se eliminaron ~158 líneas de CSS duplicado en 12 archivos. Clases eliminadas: .btn-obs, .btn-obs-blue, .btn-obs-orange, .btn-obs-red, .btn-edit, .btn-delete, .chart-guide-btn, .chart-newtab-btn.


12. InsightUtils.js

Ubicación: static/js/utils/InsightUtils.js Propósito: Constantes y helpers compartidos por todas las vistas de AI Insights (CNS).

Exports

ExportTipoDescripción
RISK_COLORSObject{ HIGH: '#ef4444', MEDIUM: '#f59e0b', LOW: '#3b82f6' }
RISK_PRIORITYObject{ HIGH: 3, MEDIUM: 2, LOW: 1 } (para ordenar por severidad)
escHtml(str)FunctionEscapa HTML (<, >, &, ") para prevenir XSS
timeAgo(date)FunctionFormato relativo (“5m ago”, “2h ago”, “3d ago”)

Consumidores

  • InsightDetailModal.js, InsightActions.js, ObservatoryCNS.js, ObservatoryOverview.js, PropertiesPanelEvents.js

Cuándo Usar

  • ✅ Al renderizar badges de riesgo, colores de insight, timestamps relativos
  • ✅ Al escapar contenido de insight en HTML dinámico

13. InsightDetailModal.js

Ubicación: static/js/utils/InsightDetailModal.js Propósito: Modal unificado para mostrar detalle de AI Insights. Usado por Observatory y Rack Editor.

Exports

ExportTipoDescripción
showInsightDetailModal(insight, actions)FunctionAbre modal con detalle completo del insight
showInsightListModal(insights, actions)FunctionLista de insights → click abre detalle
appendConversation(overlay, entry)FunctionAñade entrada Q&A al historial de conversaciones del modal

Parámetro actions

{
    onApply: (insightId) => { ... },           // Ejecutar fix SSH
    onAcknowledge: (insightId, overlay) => { ... }, // Acknowledgar
    onExplain: (insightId, overlay) => { ... },     // Preguntar a la IA
    onRevise: (insightId, overlay) => { ... },      // Revisar diagnóstico
}

Features del Modal (v4)

  • Grid de metadatos: target, risk level, confidence, status, provider, OSI layer, dates
  • Summary + Root Cause: Con badge “REVISED” violeta si fue revisado, y valores originales en cursiva
  • Commands + Rollback: Con syntax highlighting
  • Acknowledge: Input de notas + botón, o info de acknowledge previo
  • Explain: Input + respuesta IA, auto-append al historial de conversaciones
  • Conversation History: Historial completo Q&A cargado desde API, scroll interno
  • Revise Diagnosis: Botón visible solo cuando hay conversaciones, envía contexto a IA para re-evaluar

Consumidores

  • observatory.js (Observatory → viewInsight), PropertiesPanelEvents.js (Rack Editor)

14. InsightActions.js

Ubicación: static/js/utils/InsightActions.js Propósito: Handlers centralizados de acciones sobre AI Insights. Evita duplicación entre Observatory, CNS History y Rack Editor.

Exports

ExportTipoDescripción
handleExplain(insightId, overlay, options)async FunctionPOST explain, muestra respuesta, persiste conversación, auto-append
handleRevise(insightId, overlay, onRefresh)async FunctionPOST revise, muestra resultado, callback refresh si revisado
showAcknowledgePrompt(insightId)FunctionMini-modal para acknowledge con notas. Retorna Promise<string|null>
buildInsightActions(options)FunctionFactory que retorna { onApply, onAcknowledge, onExplain, onRevise }

handleExplain options

{
    inputRole: 'explain-input',       // data-role del input
    btnAction: 'explain',             // data-action del botón
    resultRole: 'explain-result',     // data-role del contenedor resultado
    answerRole: 'explain-answer',     // data-role del texto respuesta
    providerRole: 'explain-provider', // data-role del provider
}

buildInsightActions options

{
    onRefresh: () => loadCNSData(), // Callback tras apply/acknowledge/revise exitoso
}

Consumidores

  • observatory.js (buildInsightActions), PropertiesPanelEvents.js (buildInsightActions), ObservatoryCNS.js (handleExplain para History)

15. Group Service (Backend Centralizado)

Ubicación: monitoring/services/group_service.py Propósito: Servicio compartido para grupos de dispositivos — centraliza queries de membresía, agregación de métricas VictoriaMetrics, y asignación de perfiles a grupos.

Cuándo Usar

  • ✅ Para añadir grupos a un nuevo device type (ej: switches, servidores)
  • ✅ Para consultar métricas agregadas de un grupo de targets
  • ✅ Para gestionar membresía de perfiles en grupos

Cuándo NO Usar

  • ❌ Para lógica de summary específica de un tipo (SSIDs en wireless, battery en UPS) — eso va en el endpoint específico
  • ❌ Para operaciones CRUD de grupos (crear/eliminar) — eso usa /api/network/device-groups directamente

API (Python)

from monitoring.services.group_service import (
    get_group_profiles,
    get_group_target_ids,
    query_group_metric,
    assign_profiles_to_group,
)

# Obtener perfiles de un grupo filtrados por tipo
profiles = get_group_profiles(group_id, org, ['ups'])

# Extraer target IDs para queries VictoriaMetrics
target_ids = get_group_target_ids(profiles)

# Consultar métrica agregada (sum o avg según tipo)
result = query_group_metric(target_ids, tenant_id, 'battery_charge', hours=4)

# Asignar perfiles a un grupo (add selected, remove unselected)
count = assign_profiles_to_group(org, group_id, profile_ids, ['ups'])

Consumidores

  • monitoring/api/ups.py — group-summary, group-metric, group-assign
  • monitoring/api/wireless.py — group-metric, group-assign

Frontend counterparts

  • UpsGroupDetail.js — Vista de detalle de grupo UPS (summary, charts, members)
  • WirelessGroupDetail.js — Vista de detalle de grupo wireless (summary, SSIDs, members)

16. ChartGuideService.js

Ubicación: static/js/services/ChartGuideService.js Propósito: Modal centralizado de guía de interpretación de gráficas. Recibe título, registro de métricas y datos de guía; renderiza un modal con secciones por métrica (descripción, tabla de umbrales, tips).

Cuándo Usar

  • ✅ Para añadir una guía de interpretación a cualquier página con gráficas (Observatory, Wireless, UPS, etc.)
  • ✅ Cuando necesites explicar umbrales y rangos de métricas al usuario

Cuándo NO Usar

  • ❌ Para modales genéricos sin estructura de guía de métricas — usar modal_helper.js

API

import { showChartGuide } from '../services/ChartGuideService.js';

// registry: { key: { label, color } }
// guideData: { key: { description, thresholds: [{level,range,color,detail}], tips: [string] } }
showChartGuide('My Guide Title', registry, guideData);

Datos de guía por página

  • Wireless: static/js/pages/wireless/ChartGuideData.js — 9 métricas WiFi
  • Observatory: static/js/pages/observatory/ObservatoryChartGuideData.js — heartbeat, bandwidth, errors

Consumidores

  • WirelessChartGrid.js → botón “Chart Guide” en toolbar de AP
  • observatory.js → botón “Chart Guide” en header de device tab

CSS

Reutiliza clases existentes: .chart-guide-overlay, .chart-guide-modal, .chart-guide-header, .chart-guide-body, .chart-guide-section, .expert-threshold-table, .expert-threshold-dot, .expert-tips


17. ScopedInsightTab.js

Ubicación: static/js/services/ScopedInsightTab.js Propósito: Componente reutilizable de pestaña CNS (AI Insights) con scope por targetIds. Renderiza tabla de insights, stats bar, filtros, paginación, export CSV y auto-refresh — todo scoped a un conjunto de dispositivos.

Cuándo Usar

  • Para añadir una pestaña CNS a cualquier página de dispositivos (Observatory, Wireless, UPS, futuros tipos)

Cuándo NO Usar

  • Para mostrar un solo insight (usar InsightDetailModal.js)
  • Para indicadores en sidebar (usar InsightIndicatorService.js)

API

import { ScopedInsightTab } from '../services/ScopedInsightTab.js';

const cnsTab = new ScopedInsightTab({
    containerId: 'my-cns-container',   // DOM element ID where tab renders
    targetIds: [1, 2, 3],             // MonitoringTarget IDs to scope
    actionNamespace: 'wireless',       // Namespace for button data-action (avoids DOM conflicts)
});

// Render the full CNS tab (stats + filters + table + pagination)
cnsTab.render();

// Refresh data (called on tab activation or WS insight_update)
cnsTab.refresh();

// Cleanup when tab is destroyed
cnsTab.destroy();

Parameters

ParameterTypeDescription
containerIdstringID of the container element (created by caller or auto-created)
targetIdsnumber[]Array of MonitoringTarget IDs — insights are filtered to these targets only
actionNamespacestringPrefix for data-action attributes (e.g., wireless-cns-details) to avoid conflicts when multiple CNS tabs exist

Consumers

  • ObservatoryCNS.js — Observatory (all targets, namespace observatory)
  • wireless.js — Wireless Monitor (AP targets, namespace wireless)
  • ups.js — UPS Monitor (UPS targets, namespace ups)

Backend dependency

  • GET /api/sentinel/insights/stats/?target_ids=1,2,3 — scoped stats
  • GET /api/sentinel/insights/?target_ids=1,2,3 — scoped insight list

18. InsightIndicatorService.js

Ubicación: static/js/services/InsightIndicatorService.js Propósito: Muestra puntos pulsantes de riesgo (HIGH=rojo, MEDIUM=naranja, LOW=azul) junto a los nombres de dispositivos en sidebars. Soporta actualizaciones en tiempo real via WebSocket.

Cuándo Usar

  • Para mostrar indicadores de insights activos en listas de dispositivos de cualquier página

Cuándo NO Usar

  • Para mostrar detalles de insights (usar InsightDetailModal.js)
  • Para la tabla completa de insights (usar ScopedInsightTab.js)

API

import { InsightIndicatorService } from '../services/InsightIndicatorService.js';

const indicators = new InsightIndicatorService({
    containerSelector: '.device-list',   // CSS selector of the sidebar list
    targetAttr: 'data-target-id',        // Attribute on .device-item with target ID
});

// Load initial indicators for given target IDs
await indicators.load([1, 2, 3]);

// Update a single device indicator (e.g., on WS insight_update)
indicators.update(targetId, { risk_level: 'HIGH', count: 2 });

// Clear all indicators
indicators.clear();

CSS

  • .insight-indicator — pulsing dot (defined in components.css)
  • Color classes: .risk-high, .risk-medium, .risk-low

Consumers

  • observatory.js — Observatory sidebar (rack + manual devices)
  • wireless.js — Wireless sidebar (AP devices)
  • ups.js — UPS sidebar (UPS devices)

19. ObservatoryITSM.js (Centralized ITSM Dashboard)

Ubicación: static/js/pages/observatory/ObservatoryITSM.js Propósito: Dashboard ITSM completo que se puede instanciar en cualquier contexto. Genera su propio HTML skeleton y channel modal, los inyecta en el DOM, y renderiza todas las secciones ITSM (SLA, Notifications, Escalation, Maintenance, Analytics, Known Issues, Runbooks, Patterns, Incident Groups).

Cuándo Usar

  • Para incrustar el dashboard ITSM en cualquier página. Actualmente se accede desde Observatory y desde los botones “ITSM Settings” en las pestanas CNS de Wireless y UPS.

Cuándo NO Usar

  • Para funcionalidad CNS (insights, tabla, filtros) — usar ScopedInsightTab.js

API

import { ObservatoryITSM } from '../pages/observatory/ObservatoryITSM.js';

const itsm = new ObservatoryITSM({
    containerId: 'itsm-container',     // DOM element ID for the dashboard
    namespace: 'observatory',          // Namespace for data-action attributes
    targetIds: null,                   // Optional: scope analytics/groups/patterns by target IDs
});

// Initialize: generates HTML skeleton, appends channel modal to body, loads data
itsm.init();

// Refresh data (called on tab activation)
itsm.refresh();

Parameters

ParameterTypeDescription
containerIdstringID of the container where ITSM dashboard renders
namespacestringPrefix for data-action attributes to avoid DOM conflicts
targetIdsnumber[] or nullWhen set, Analytics/Incident Groups/Recurring Patterns are scoped to these targets. SLA, Channels, Escalation, Maintenance, Known Issues, Runbooks remain org-wide

Scoping behavior

SectionScoped by targetIdsReason
AnalyticsYesMTTA/MTTR/compliance filtered to relevant devices
Incident GroupsYesOnly groups containing scoped targets
Recurring PatternsYesOnly patterns for scoped targets
SLA PoliciesNoOrg-wide configuration
Notification ChannelsNoOrg-wide configuration
Escalation PoliciesNoOrg-wide configuration
Maintenance WindowsNoOrg-wide configuration
Known IssuesNoOrg-wide knowledge base
RunbooksNoOrg-wide procedures

Consumers

  • observatory.js — ITSM tab (full org-wide view)
  • Wireless/UPS CNS tabs — “ITSM Settings” button navigates to Observatory ITSM tab

Checklist: ¿Necesito Crear un Nuevo Servicio?

Antes de crear un nuevo servicio, verifica:

  • ¿Ya existe un servicio similar en services/ o utils/?
  • ¿He consultado este documento?
  • ¿He buscado en el código existente? (grep -r "keyword" static/js/)
  • ¿Este servicio será usado en más de 2 lugares?
  • ¿Es específico de un módulo o reutilizable?

Si es específico de un módulo: Crear en subcarpeta del módulo (ej: static/js/terminal/) Si es reutilizable: Añadir a services/ o utils/ y DOCUMENTAR AQUÍ.


Añadir un Nuevo Servicio Centralizado

Paso 1: Crear archivo en services/ o utils/

// static/js/services/MyNewService.js
export class MyNewService {
    static doSomething() { ... }
}

Paso 2: Añadir a index.js correspondiente

// static/js/services/index.js
export { MyNewService } from './MyNewService.js';

Paso 3: DOCUMENTAR EN ESTE ARCHIVO (CENTRALIZED_SERVICES.md)

  • Añadir sección con API, cuándo usar, cuándo NO usar, ejemplos

Paso 4: Actualizar Documentation/INDEX.md

  • Registrar nuevo servicio en sección Frontend

Paso 5: Commit con mensaje claro

git commit -m "feat: add MyNewService centralizado (propósito del servicio)"

Referencias

  • Design Guidelines: Documentation/frontend/DESIGN_GUIDELINES.md
  • Performance Guidelines: Documentation/frontend/PERFORMANCE_GUIDELINES.md
  • Agent Rules: Documentation/guides/AGENT_RULES.md
  • Project Context: Documentation/architecture/PROJECT_CONTEXT.md

Mantenido por: Equipo CreaRack Revisión recomendada: Al añadir nuevos servicios o módulos JS

Véase también

  • [[crearack-tech—frontend—design-guidelines]] — guidelines de diseño UI
  • [[crearack-tech—frontend—design-system]] — design system de componentes
  • [[crearack-tech—frontend—performance-guidelines]] — guidelines de performance frontend
  • [[crearack-tech—agents—dev-core]] — agente técnico del módulo core
  • [[ia-tech—roles—dev-frontend]] — agente IA frontend