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]]