CreaRack-SL

Descripción

Antes (s114), la página Terminal listaba solo racks.Device colocados en un rack y con Network Management (management_config válido). Los dispositivos detectados por Auto-Provision y derivados a otras páginas (wireless, UPS, Digital Signage, observatory) no aparecían en el Terminal aunque también se gestionan por SSH.

Ahora el Terminal une dos fuentes, deduplicado por IP:

  1. racks.Device con Network Management → sesión SSH directa (credenciales guardadas en management_config).
  2. network.DeviceProfile (registro que Auto-Provision crea por cada (org, IP) detectada) con supports_ssh o supports_snmp, cuya IP no esté ya cubierta por un Device.

Sin modelo nuevo en la lista — reaprovecha el DeviceProfile existente.

Implementación

  • terminal/services.py::terminal_hosts(org): devuelve {"hosts": [...], "racks": [...]}. Cada host se normaliza a un dict con uid (device:{id} / profile:{id}), source, category, name, ip, status, derived_to y, para Device, device_id/rack_id/group_ids_str.
  • category (filtro de tipo): para Device = rack. Para profile la decide DeviceProfile.assigned_page (Ficha Central F4 — el campo scope del MonitoringTarget fue eliminado en la migración monitoring/0025): assigned_page wireless/ups/signage → esa categoría; si no está asignada pero está enlazado a un rack → rack; resto → other. Importante: la categoría se calcula EN VIVO en cada carga (no se persiste) — re-ejecutar Auto-Provision no cambia la categorización, es código.
  • terminal/views.py::index: usa el servicio en lugar del query directo a Device.
  • templates/terminal/index.html: el bucle itera hosts y ramifica por source. Las filas device mantienen checkbox + TerminalApp.openSession(device_id). Las filas profile usan TerminalApp.quickConnectHost(...).
  • Filtro primario por tipo: <select id="device-type-filter"> (All / Rack / Wireless / UPS / Digital Signage / Other) sobre los secundarios (Status / Groups / Racks). DeviceFilterService filtra por data-category.

Herencia de credenciales SSH (PR #81 · migración network/0047)

Antes, las credenciales SSH que el usuario daba en Auto-Provision se usaban para descubrir y se descartaban (_persist_profile no las recibía). Ahora:

  • DeviceProfile gana ssh_username + ssh_password_encrypted (Fernet vía CredentialManager) + props has_stored_ssh / get_ssh_password(). Mismo nivel de cifrado que el management_config de los racks (clave dedicada CREDENTIAL_ENCRYPTION_KEY, Plan Hardening E).
  • provision_stages.py::_persist_profile recibe ssh_username/ssh_password (desde service.py::provision_device) y los guarda cifrados SOLO si el SSH de discovery funcionó (ssh_data truthy). Un re-discovery sin creds no borra las guardadas (las claves SSH se inyectan en defaults solo cuando hay nuevas).
  • Endpoint GET /auto-provision/profiles/{id}/credentials (network/api/profiles.py, gateado require_perm(network, edit) + org) devuelve {has_credentials, username, password, port} con la contraseña descifrada — patrón gemelo del /device/{id}/credentials existente.
  • Terminal: terminal_hosts expone has_stored_ssh + profile_id. quickConnectHost(ip, name, profileId, hasStored) (en static/js/pages/terminal.js): si hay creds guardadas las trae del endpoint y pre-rellena usuario/contraseña en el modal manual; si no, las pide (Regla 13, no inventa nada).

Nota operativa: para que un dispositivo ya existente herede sus claves hay que volver a pasarle Auto-Provision con credenciales — las antiguas nunca se guardaron.

Tests

tests/api/test_terminal_hosts.py (13): unión, dedup por IP, categorías (incl. wifi por device_type), exclusión de profiles sin SSH/SNMP, aislamiento por organización, round-trip de cifrado, endpoint de credenciales (admin 200 / viewer 403 / sin-creds), has_stored_ssh, y un test de vista que parchea terminal.views.render (patrón de test_monitoring, porque el job Backend de CI no compila assets Vite).

Footgun

La plantilla del Terminal usa {% vite_asset 'src/pages/terminal.ts' %}. Un test que renderice la página real falla en el job Backend de CI con DjangoViteAssetNotFoundError (CI no compila Vite ahí). Patrón correcto: parchear render y verificar el contexto, no el HTML.