Servicio: Modal de configuración de dispositivo (frontend)
Descripción
Módulo JavaScript reutilizable (DeviceConfigModal) que proporciona un modal overlay compartido para editar configuración de DeviceProfile (dispositivos descubiertos automáticamente) desde cualquier página de monitorización.
Usado por Observatory, Wireless, UPS y Digital Signage.
Ubicación: static/js/services/DeviceConfigModal.js (236 LOC)
API pública
DeviceConfigModal.show(profileId, profileName, onSaved)
Abre el modal para un dispositivo.
Parámetros:
profileId(int): ID del perfil (recibido del endpoint).profileName(string): Nombre a mostrar en el título (“Configure — {name}”).onSaved(function | null): Callback ejecutado tras guardar (ej. recargar lista de dispositivos).
Efecto:
- Crea el DOM si no existe.
- Carga configuración actual con
GET /api/network/auto-provision/profiles/{profileId}/config. - Renderiza formulario con valores actuales.
- Muestra modal (flex overlay).
DeviceConfigModal.hide()
Cierra el modal sin guardar.
Efecto:
- Oculta overlay con
display: none.
Estructura del modal
┌─────────────────────────────────────────┐
│ Configure — [Device Name] [✕] │
├─────────────────────────────────────────┤
│ ⚠ [Error si falla GET/PUT] │
│ │
│ SNMP │
│ Version: [v2c ▼ | v3] │
│ [Community field] (si v2c) │
│ [Username, Auth, Priv fields] (si v3) │
│ │
│ SSH │
│ [Username] [Password] │
│ [Hint: A password is stored...] │
│ Credentials are encrypted (Fernet/AES) │
│ │
│ Groups │
│ ☐ Group 1 ☐ Group 2 ☐ Group 3 ... │
│ │
├─────────────────────────────────────────┤
│ [Cancel] [Save] │
└─────────────────────────────────────────┘
Campos del formulario
SNMP
Version selector (v2c | v3):
- Toggle dinámico: muestra/oculta campos según versión.
- Controla campos
.dc-snmp-v2cy.dc-snmp-v3.
Si v2c:
input.dc-snmp-community(placeholder: “public”)
Si v3:
input.dc-v3-usernameselect.dc-v3-auth-proto(— | SHA | MD5)input.dc-v3-auth-key(password, placeholder vacío, hint si key almacenada)select.dc-v3-priv-proto(— | AES | DES)input.dc-v3-priv-key(password, placeholder vacío, hint si key almacenada)
Hint secretos: Solo visible si has_snmp_v3_keys es true → “Keys are stored. Leave blank to keep them unchanged.”
SSH
Campos:
input.dc-ssh-usernameinput.dc-ssh-password(password type, placeholder vacío)
Hint: Solo visible si has_stored_ssh es true → “A password is stored. Leave blank to keep it unchanged.”
Nota: “Credentials are encrypted (Fernet/AES) before storage.” siempre visible.
Groups
Checkboxes: Cargados dinámicamente desde GET /api/racks/groups.
- Cada checkbox:
input.dc-group[value="{id}"]. - Selected si
id in group_idsdel perfil.
Fallback: Si falla carga de grupos, muestra “Could not load groups” o “No groups defined”.
Ciclo de vida
Inicialización (_ensureDOM())
- Detecta si overlay ya existe (
this._overlay). - Si no: crea elemento
<div id="device-config-modal">con estructura HTML inline. - Aplica CSS variables (tema claro/oscuro automático vía
:root). - Llama
_bindEvents().
Vinculación de eventos (_bindEvents())
.modal-close-btn→hide()..dc-cancel→hide().- Click en overlay (fuera modal) →
hide()(backdrop dismiss). .dc-snmp-versionselect →_toggleSnmpVersion()..dc-savebutton →_save().
Carga de configuración (_loadConfig())
- Fetch
GET /api/network/auto-provision/profiles/{profile_id}/config. - Pobla campos con valores:
snmp_version,snmp_community,snmp_v3_username, etc.ssh_username(password siempre vacío).- Hints de secretos según flags
has_snmp_v3_keys,has_stored_ssh.
- Llama
_loadGroups(cfg.group_ids). - Llama
_toggleSnmpVersion()para alineación inicial. - Si error: muestra en
.dc-errory retorna.
Carga de grupos (_loadGroups(selectedIds))
- Fetch
GET /api/racks/groups. - Renderiza checkboxes dinámicos (
.dc-group). - Marca como “checked” los grupos ya asignados (
selectedIds). - Si error o lista vacía: muestra fallback.
Guardado (_save())
- Recolecta valores del formulario:
const payload = { snmp_version: version, // "v2c" | "v3" snmp_community: (si v2c), snmp_v3_username: (si v3), snmp_v3_auth_protocol: (si v3), snmp_v3_auth_key: (solo si editado), snmp_v3_priv_protocol: (si v3), snmp_v3_priv_key: (solo si editado), ssh_username: (trimmed), ssh_password: (solo si no vacío), group_ids: [ids seleccionados] }; - Semántica de secretos: Solo se envían si el usuario escribió algo nuevo (campos no vacíos).
- PUT
PUT /api/network/auto-provision/profiles/{this._profileId}/configcon payload. - Desabilita botón “Save” durante request.
- Si éxito (200):
- Toast: “Configuration saved” (success).
- Llama callback
onSaved()si existe. - Cierra modal.
- Si error:
- Muestra mensaje en
.dc-error. - Habilita botón.
- Muestra mensaje en
Integración con páginas
Observatory
Archivo: static/js/pages/observatory.js
Método: Observatory.openDeviceConfig(profileId, name)
- Valida existencia de
window.DeviceConfigModal. - Llama
show(profileId, name, () => this.loadTargets()). - Callback recarga lista de targets/dispositivos.
Decodificación de ID:
const PROFILE_ID_OFFSET = 1_000_000_000;
const endpoint = deviceId >= PROFILE_ID_OFFSET
? `/api/monitoring/profiles/${deviceId - PROFILE_ID_OFFSET}/enable-monitoring`
: `/api/monitoring/devices/${deviceId}/enable-monitoring`;
ObservatoryTabs
Archivo: static/js/pages/observatory/ObservatoryTabs.js
Botón HTML (en pestaña de dispositivo):
${device.source === 'profile' ? `
<button class="btn btn-xs btn-action"
data-action="Observatory.openDeviceConfig"
data-args='[${device.id - 1000000000}, "${device.name.replace(/"/g, '\\"')}"]'
title="Edit credentials and groups">Config</button>
` : ''}
Nota: Decoding inline en data-args porque es una acción declarativa.
Wireless, UPS, Digital Signage
Patrón similar: Cada página añade botón y listener:
const cfgBtn = container.querySelector('.btn-device-config');
if (cfgBtn) {
cfgBtn.addEventListener('click', () => {
if (window.DeviceConfigModal) {
window.DeviceConfigModal.show(
parseInt(cfgBtn.dataset.profileId),
cfgBtn.dataset.profileName || '',
null // sin callback
);
}
});
}
Dependencias del servicio
Frontend:
window.ApiService— Para GET/PUT a endpoints.window.showAppToast()— Notificación de éxito (opcional).
Backend (consumidos por modal):
GET /api/network/auto-provision/profiles/{profile_id}/configPUT /api/network/auto-provision/profiles/{profile_id}/configGET /api/racks/groups
Seguridad en frontend
- XSS prevention: Usa
.textContentpara mostrar datos, escape HTML en_esc(). - No almacena secretos: Modal no guarda credenciales en localStorage ni sessionStorage.
- Placeholder safety: Campos de password siempre vacíos (placeholder: “leave blank to keep”).
- Validación de entrada: Backend valida; frontend solo recolecta.
Styling
CSS inlining: Usa variables CSS :root para tema:
--card-bg— Fondo de inputs.--border-color— Bordes.--text-primary— Texto principal.--text-secondary— Texto secundario (hints).
Clases Bootstrap reutilizadas:
.modal-overlay,.modal-content,.modal-header,.modal-body,.modal-footer..btn,.btn-neutral,.btn-action,.btn-sm.
Casos de uso
- Post-descubrimiento: Dispositivo descubierto pero sin credenciales SNMP/SSH → Opera abre Config, añade community/usuario/pass.
- Cambio de community: Operador cambia SNMP community en switch → Actualiza en Observatory sin ir a Auto-Provision.
- Asignación rápida: Nuevo UPS sin grupo → Clica Config, asigna a grupo de PDU, guarda.
- Auditoría: Todo cambio queda registrado en log de backend (
log_action).
Notas técnicas
- Modal como singleton:
this._overlayes persistente durante la sesión (se reutiliza). DOM se crea una sola vez. - Callback flexible:
onSavedpuede ser null (ej. UPS, DS) o función (ej. Observatory →loadTargets()). - Exclude_unset en backend: Solo campos presentes en payload se actualizan; permite actualizaciones parciales.
Véase también
- [[feature—monitoring—device-config-modal]]
- [[entity—network—endpoint—profile-config]]
- [[entity—network—model—device-profile]]
- [[entity—monitoring—view—observatory]]
- [[concept—ui—modal-patterns]]