CreaRack-SL

El modal de restauración enseña el mismo árbol, con badges "ya existe" y selector de modo (v1.76.0)

Valor para el usuario

Hasta ahora, el modal de restauración solo enseñaba una lista plana de racks con checkboxes, y el resto del contenido de la maleta se escondía detrás de un desplegable “What’s inside this backup”. Desde v1.76.0 el modal abre con el MISMO árbol que el diálogo de creación (v1.74.0): las secciones del dashboard (Maps, Rows, Rooms y “No Map”, con sus racks y conteos) y los dominios completos (Monitoring, Wireless, UPS, DSM, ITSM, Terminal, Cables, Catálogo) — todo a la vista, nada plegado. Además, cada elemento que ya existe en la organización destino (por su identidad natural: nombre de rack o plano, IP de una ficha o target, nombre de script o playlist, stencil) lleva una etiqueta “already exists”, para que el usuario vea qué va a chocar ANTES de decidir. Y junto al árbol aparece el selector de modo: Add as a copy (el histórico: todo se recrea, lo que coincide se duplica) o Update existing (el motor de v1.75.0: lo que coincide se sobrescribe, lo nuevo se añade, nada se borra), con un texto de confirmación distinto y veraz según el modo elegido. Si el backup subido es parcial, el resumen lo avisa.

Tercera de las cuatro piezas de la ventana del task #236 (grill del 21-08). Queda la última: un informe final claro tras aplicar el restore.

Cómo usarla

  1. Configuration → File Operations → restaurar → seleccionar el ZIP.
  2. El cliente llama a POST /api/racks/restore/full/preview; la respuesta cambia de forma — pierde la lista plana racks y gana tree (mismo esquema que build_backup_inventory de la creación) y partial (bool).
  3. El frontend reutiliza buildBackupTree() (compartida con el diálogo de creación) para pintar el árbol en el contenedor restore-tree, con las badges de “ya existe” por rack, sección y dominio.
  4. El usuario desmarca lo que no quiere traer y elige el modo con un radio button (Add as a copy / Update existing); el texto de confirmación cambia según el modo.
  5. Al confirmar, si algo quedó desmarcado, el cliente añade selection (JSON del árbol, con los IDs de la maleta) a la petición; si todo sigue marcado, no manda selección y viaja la maleta entera. El mode siempre viaja.
  6. POST /api/racks/restore/full recibe selection + mode; el backend normaliza selection y lo pasa como tree_selection, que prevalece sobre el rack_ids CSV legacy si ambos llegasen.

Implementación

  • racks/api/export/restore_preview.py (nuevo, 173 LOC): build_restore_tree(org, data) construye el árbol desde la maleta con la misma regla de sección que el dashboard (el primer plano, en el orden de la maleta, que coloca el rack; sin colocación → “No Map”) y calcula las banderas exists/existing comparando por identidad natural contra la org destino: Rack.name, (Blueprint.name, Blueprint.mode), DeviceProfile.ip_address (por página Wireless/UPS/DSM), MonitoringTarget.ip_address, Script.name, Playlist.name, (Stencil.name, Stencil.image_path). filter_backup_by_tree(data, selection) poda la maleta (en sitio) a la selección de árbol — reutiliza filter_backup_selection() del restore selectivo ([[feature—racks—restore-selectivo-v171]]) para los racks y la nueva prune_domains() para los dominios.
  • racks/api/export/backup_selection.py: prune_domains(data, domains) se extrae de prune_backup_data() (antes un bloque inline) para que la poda de dominios sea la MISMA función que usan tanto la creación selectiva ([[feature—backup-restore—creacion-selectiva-v1-74-0]]) como la restauración por árbol — sin duplicar la lógica de qué colecciones caen por dominio, fichas por página, huérfanos cruzados y ventanas de mantenimiento.
  • racks/api/export/restore.py: apply_full_restore_from_zip() gana el parámetro tree_selection=None; si no es None prevalece sobre rack_ids (que queda como compat). restore_preview() devuelve ahora partial (del manifest) y tree (de build_restore_tree) en vez de la lista racks. restore_full() gana selection: str = Form("") (JSON), normalizado con normalize_selection() (de backup_selection.py) antes de pasarlo a la tarea Huey.
  • racks/tasks.py: run_full_restore() gana tree_selection=None y lo propaga a apply_full_restore_from_zip; selective en el resultado del job pasa a ser rack_ids is not None or tree_selection is not None.
  • Frontend (static/js/alpine-components.js, static/js/base.js, templates/base.html): buildBackupTree() gana un parámetro containerId (antes fijo a backup-tree) para poder pintar en restore-tree además del modal de creación; nuevas badges existsBadge/existingBadge. El modal de restauración sustituye la lista de checkboxes de rack y el desplegable “What’s inside” por el árbol compartido más el selector de modo (radio add/update) con confirm() veraz por modo — el texto anterior ya se había corregido para no prometer un “overwrite” con safety backup inexistente (incidente del 16-07-2026, ver [[feature—racks—restore-selectivo-v171]]). Cache-busting: base.js?v=5, alpine-components.js?v=4.
  • i18n: 11 strings EN+ES nuevos vía scripts/i18n/add_restore_tree_strings.py (patrón polib ya usado en las piezas anteriores de esta ventana).
  • Tests: tests/api/test_restore_tree.py (5 casos nuevos) + 1 adaptado en tests/api/test_restore_selective.py. Área restore en Docker: 36 tests + mypy en verde.

Commits relacionados

  • ff6c90e8 (2026-08-21) — feat(backups): modal de restauracion con el arbol completo, badges ‘ya existe’ y selector de modo - v1.76.0 (task #236 ventana) (#407).

Véase también

  • [[entity—racks—service—apply-full-restore-from-zip]]
  • [[feature—racks—restore-selectivo-v171]]
  • [[feature—backup-restore—modo-actualizar-v1-75-0]]
  • [[feature—backup-restore—creacion-selectiva-v1-74-0]]
  • [[feature—backup-restore—extended-domains-v1-70-0]]
  • [[feature—backup-restore—manifest-verificacion-v1-69-0]]
  • [[feature—racks—backup-restore-async-v156]]
  • [[crearack-tech—admin—backup-restore]]