Rack Editor - Documentación Técnica
Versión: 1.0 Fecha: 03-04-2026 Estado: Produccion - Konva.js Canvas + Auto-Save + Half-Width Devices
Resumen
Editor visual de racks de datacenter integrado en CreaRack Pro. Permite diseñar la disposición física de equipos dentro de armarios rack estándar de 19” mediante un canvas interactivo basado en Konva.js.
Funcionalidades principales:
- Canvas 2D interactivo con zoom, pan y drag-drop de dispositivos
- Librería de stencils con importación Visio (.vssx/.vdx/.vss)
- Dispositivos full-width y half-width (medio rack) con detección de colisiones 2D
- Panel de propiedades: nombre, puertos (Ethernet + SFP), management config, notas
- Auto-save con debounce 500ms — los cambios se persisten automáticamente
- Undo/Redo via StateManager (Ctrl+Z / Ctrl+Y)
- Templates: guardar configuraciones de rack reutilizables (Masters)
- Copy from…: copiar el montaje de cualquier rack o Master en otro, modo Replace (borra destino) o Add on top (solo donde no colisiona) — v1.79.0
- SSH integrado: terminal xterm.js + navegador SFTP desde el editor
- Config Backups: snapshots de running-config con diff viewer
- Auto-Provision: panel flotante de dispositivos descubiertos pendientes de colocación
- QR Labels: generación e impresión de etiquetas QR por dispositivo o rack completo
- Export: CSV, JSON, QR labels
- Papelera con soft-delete y restauración
- Grupos de racks con asignación y colores
Arquitectura
Estructura de Archivos
Frontend (static/js/editor/ — 20 archivos, ~7.500 LOC):
| Archivo | LOC | Responsabilidad |
|---|---|---|
main.js | ~363 | Bootstrap: carga datos del DOM, inicializa Konva, orquesta módulos |
state.js | ~55 | Estado global compartido (STATE), constantes de grid, helpers de posición |
konva_setup.js | ~189 | Inicialización Stage/Layer, zoom, pan, renderizado del frame del rack |
devices.js | ~432 | Creación, posicionamiento, drag-drop, snap y colisión de dispositivos |
history_adapter.js | ~172 | Adaptador Undo/Redo via StateManager con snapshots inmutables |
editor_api.js | ~86 | Auto-save con debounce 500ms, serialización hacia backend |
ui_library.js | ~272 | Panel de librería de stencils: búsqueda, carpetas, drag hacia canvas |
PropertiesPanelRenderer.js | ~563 | Renderizado HTML del panel de propiedades del dispositivo seleccionado |
PropertiesPanelEvents.js | ~925 | Eventos del panel: cambios de nombre, puertos, acciones (SSH, QR, etc.) |
auto_provision_panel.js | ~239 | Panel flotante de dispositivos pendientes de Auto-Provision |
ssh_integration.js | ~411 | Terminal SSH embebido, gestión de sesiones, integración SFTP |
SshConfigModal.js | ~254 | Modal de configuración SSH: IP, puerto, credenciales, vendor autodetect |
import_export.js | ~506 | Importación Visio (.vssx/.vdx/.vss), gestión de categorías |
backups.js | ~623 | Snapshots de configuración de red, diff viewer, restore |
sftp_ui.js | ~340 | Navegador SFTP (árbol, upload, download) via Local Agent |
ConnectionModal.js | — | Modal de conexiones inter-dispositivo (cables, puertos) |
DeviceInfoModal.js | — | Modal de información detallada del dispositivo |
PortConfigDiff.js | — | Visor de diferencias de configuración de puertos |
modal_helper.js | — | Utilidades compartidas para modales |
Backend (racks/ — Django app, ~3.300 LOC):
racks/
├── models.py # 6 modelos: BoxCategory, RackGroup, Stencil, Rack, Device, ConfigBackup
├── views.py # Vistas HTMX: index (lista racks), rack_editor (canvas)
├── urls.py # 3 rutas: /, /editor/{id}/, /rack/{id} (legacy)
├── schemas.py # Pydantic schemas para API (Django Ninja)
├── api/
│ ├── __init__.py # Router principal /api/racks/
│ ├── racks.py # CRUD racks + dispositivos + apply_template + copy_from (~410 LOC)
│ ├── library.py # Stencils + Visio import (~745 LOC)
│ ├── export.py # CSV/JSON/PDF export (~1.023 LOC)
│ ├── labels.py # QR labels API (~111 LOC)
│ └── trash.py # Papelera racks (~96 LOC)
├── services/
│ ├── racks.py # RackService: list, create, update, bulk_update (~398 LOC)
│ ├── copy_from.py # copy_devices_from(): copia el montaje de otro rack/Master, modos replace/add (~94 LOC, v1.79.0)
│ └── library.py # LibraryService: stencils, Visio import (~133 LOC)
└── migrations/ # 12+ migraciones
Templates:
| Template | Ruta | Propósito |
|---|---|---|
editor.html | templates/racks/editor.html | Página principal con canvas, paneles laterales, toolbars |
index.html | templates/racks/index.html | Lista de racks del dashboard |
Estado Global (state.js)
Objeto STATE
El módulo state.js exporta un singleton mutable que actúa como fuente de verdad para todo el editor:
export const STATE = {
rackData: null, // Datos del rack cargados del DOM (#rack-data)
devices: [], // Array de objetos dispositivo con nodos Konva
selectedNode: null, // Nodo Konva actualmente seleccionado
isLocked: false, // Bloqueo de interacción del canvas
currentDragItem: null, // Item arrastrándose desde la librería
stage: null, // Instancia Konva.Stage
layer: null, // Instancia Konva.Layer principal
rackGroup: null, // Konva.Group del frame del rack
pos: { x: 50, y: 50 },// Posición dinámica del rack en el canvas
CONFIG: {
U_HEIGHT: 35, // Pixeles por unidad de rack (U)
RACK_WIDTH: 400, // Ancho fijo del rack en pixeles
RACK_HEIGHT: 0 // Calculado en init: height_u * U_HEIGHT
}
};
Sistema de Slots (Half-Width)
Soporta dispositivos de medio ancho (195px) que comparten una misma U:
export const SLOT = { FULL: 'full', LEFT: 'left', RIGHT: 'right' };
export const HALF_WIDTH = 195; // (RACK_WIDTH - 10) / 2
| Función | Descripción |
|---|---|
getSlotX(slot) | Retorna 0 para LEFT/FULL, HALF_WIDTH para RIGHT |
getRackHeight() | Retorna CONFIG.RACK_HEIGHT |
calculateDeviceY(u_position, u_height) | Convierte posición U a coordenada Y: RACK_HEIGHT - (u_position - 1) * U_HEIGHT |
Estructura de un Dispositivo
Cada entrada en STATE.devices[]:
{
type: "switch", // Tipo de dispositivo
originalName: "Cisco 2960", // Nombre del stencil original
u_height: 1, // Altura en unidades U
u_position: 15, // Posición U (desde abajo, base 1)
notes: "", // Notas del usuario
image: "/media/uploads/...", // URL de la imagen del stencil
db_id: 42, // ID en BD (null si nuevo, no guardado aún)
half_width: false, // true = dispositivo de medio ancho
slot: "full", // "full" | "left" | "right"
ports: { // Mapa de puertos (dos formatos coexisten)
"eth0": { live: "up", label: "WAN", desired: "trunk" }, // Enriquecido
"sfp1": "10G" // Legacy
},
meta: { // Metadatos heredados del stencil
ports: {...}, notes: "...", half_width: false
},
konvaNode: Konva.Group // Referencia al nodo visual en canvas
}
Canvas Konva.js (konva_setup.js)
Inicialización
STATE.stage = new Konva.Stage({
container: containerId,
width: containerWidth,
height: containerHeight,
draggable: true // Pan habilitado
});
STATE.layer = new Konva.Layer();
STATE.stage.add(STATE.layer);
Jerarquía Visual
Konva.Stage (draggable, zoom con wheel)
└── Konva.Layer
├── STATE.rackGroup (Konva.Group)
│ ├── Rect — fondo del rack/cabinet
│ ├── Lines — separadores horizontales por U
│ └── Text — números de U: 1, 2, 3...
├── DeviceGroup_1 (Konva.Group, draggable)
│ ├── Rect — fondo del dispositivo
│ ├── Image — stencil
│ └── Text — nombre
└── DeviceGroup_N ...
Interacción
| Evento | Comportamiento |
|---|---|
wheel | Zoom con factor 1.1x, recalcula posición de cámara |
mousedown | Cursor grabbing (pan del canvas) |
mouseup | Cursor default |
| Stage drag | Pan libre del canvas completo |
fitRackToView(): Centra el rack en el viewport, respeta el ancho de paneles laterales (librería izquierda + propiedades derecha). Actualiza STATE.pos y reposiciona dispositivos.
Gestión de Dispositivos (devices.js)
Creación
addDeviceToRack(deviceData):
- Crea
Konva.Groupcon atributos del dispositivo - Posición absoluta:
x = STATE.pos.x + getSlotX(slot),y = STATE.pos.y + calculateDeviceY() - Hijos del grupo:
Rect(fondo) +Image(stencil) +Text(nombre) draggable: !STATE.isLocked- Registra en
STATE.devices[]y añade aSTATE.layer
Snap (Magnetismo)
Cada dispositivo tiene un dragBoundFunc:
- Distancia snap: 60px a los raíles del rack
- Full-width: Snap al raíl izquierdo
- Half-width: Snap a LEFT o RIGHT según proximidad al centro del rack
- Vertical: Snap a incrementos de
U_HEIGHT(35px)
Detección de Colisiones
| Función | Algoritmo |
|---|---|
checkCollisionU(targetU, uHeight, ignoreNode) | 1D: solapamiento vertical entre rangos de U |
checkCollision(targetU, uHeight, slot, ignoreNode) | 2D: vertical + horizontal. FULL bloquea cualquier posición. LEFT y RIGHT coexisten si no se solapan verticalmente |
Flujo Drag-End
dragend →
1. Validar que el dispositivo está dentro del rack
2. Calcular u_position desde Y pixel
3. checkCollision(targetU, uHeight, slot)
4. Si colisión → revertir a posición anterior
5. Si válido → actualizar u_position y slot
6. Disparar auto-save
Eliminación
removeDeviceFromRack(device): destruye nodo Konva, elimina de STATE.devices, snapshot en historial, auto-save.
Undo/Redo (history_adapter.js)
Adaptador entre el editor y el servicio global StateManager:
Edición → saveState() → createSnapshot() → STATE_MANAGER.push(snapshot)
Ctrl+Z → STATE_MANAGER.undo() → restoreState(snapshot)
↓
Limpia STATE.devices
Re-crea nodos Konva via addDeviceToRack()
Restaura selección
Dispara auto-save
Cada snapshot captura: array de dispositivos (clonado profundo de ports) + U position del seleccionado.
Persistencia y API (editor_api.js)
Auto-Save
Debounce de 500ms. Serializa STATE.devices[] y envía:
ApiService.put(`/api/racks/${rackId}/devices`, { devices });
Conversión Coordenadas - U Position
uPosition = (RACK_HEIGHT - (yRelative + deviceHeight)) / U_HEIGHT + 1
// yRelative = konvaNode.y() - STATE.pos.y
// deviceHeight = u_height * U_HEIGHT
Panel de Propiedades
Renderizado (PropertiesPanelRenderer.js)
Genera HTML dinámico para el dispositivo seleccionado:
- Cabecera: Nombre editable + tipo
- Imagen: Preview del stencil
- Posición: U position (read-only) + altura en U
- Management Config: IP, vendor, hostname
- Puertos Ethernet: Grid de hasta 48 puertos — formato enriquecido (
live/desired/label) o legacy (string) - Puertos SFP: Grid de hasta 8 puertos (layout 2 columnas)
- Notas: Textarea editable
- Acciones: Delete, Clone, Save to Library, QR, SSH, Backups
Eventos (PropertiesPanelEvents.js)
| Evento | Acción |
|---|---|
| Input nombre | Actualiza texto del nodo Konva + auto-save |
| Input puerto | Actualiza device.ports[key] + auto-save |
| Blur campo | Snapshot Undo via request-save-history |
| Delete | removeDeviceFromRack() + confirmación |
| Clone | Duplica en siguiente U disponible |
| Save to Library | POST /api/stencils/create_from_device |
| QR | QRLabelService.showDevicePreview() |
| SSH | SshConfigModal → openSSHSession() |
| Backups | Modal de config snapshots |
Librería de Stencils (ui_library.js)
Estructura de un Stencil
{
id: 123,
name: "Cisco Catalyst 2960",
image_path: "/media/uploads/stencils/cisco_2960.png",
default_u_height: 1,
manufacturer: "Cisco",
category: "Switches",
extra_data: '{"half_width": false, "ports": {...}}'
}
Drag desde Librería al Canvas
- Stencil tiene atributos
data-id,data-name,data-type,data-height,data-image dragstart→ metadata enSTATE.currentDragItemdropsobre canvas → calcula posición U desde coordenadas del mouseaddDeviceToRack()con datos del stencilSTATE.currentDragItem = null
Backup y restauración de la librería de stencils
El panel Library del Rack Editor incluye botones para hacer backup y restaurar toda la librería de stencils de la organización.
Para hacer backup de los stencils:
- Abre el Rack Editor (cualquier rack)
- En el panel izquierdo Library, pulsa el botón Backup
- Se descarga un archivo ZIP con todos los stencils, categorías e imágenes de tu organización
Para restaurar stencils desde un backup:
- En el panel Library, pulsa el botón Restore
- Selecciona el archivo ZIP de backup
- El sistema analiza el contenido y muestra un resumen de lo que se importará
- Confirma para restaurar los stencils
Endpoints de la API:
GET /api/racks/library/backup— Descarga el backup ZIP completo de la libreríaPOST /api/racks/library/restore/analyze— Analiza un archivo de backup antes de restaurarPOST /api/racks/library/restore/confirm— Confirma y ejecuta la restauración
El backup incluye stencils personalizados, categorías y las imágenes asociadas. Los stencils globales (Default Pack) no se incluyen porque están disponibles para todas las organizaciones.
Importación Visio (Pipeline 2 pasos)
- Analyze:
POST /api/racks/visio/analyze(FormData con .vssx/.vdx/.vss) →session_id+ lista de masters - Confirm:
POST /api/racks/visio/confirm({ session_id, masters, category_name }) → crea stencils - Cleanup:
DELETE /api/racks/cleanup/session/{id}— archivos temporales
Auto-Provision Panel (auto_provision_panel.js)
Flujo de integración con el módulo Auto-Provision:
Auto-Provision wizard
↓ Escribe en localStorage
localStorage['auto_provision_pending'] = {
version: 2,
racks: { "rack_42": [{ name, type, stencil_id, management_config }] }
}
↓ Editor detecta al cargar
auto_provision_panel.js lee localStorage, filtra por rack actual
↓ Renderiza panel flotante
Lista de dispositivos pendientes para ESTE rack
↓ Usuario selecciona y arrastra
addDeviceToRack() con management_config auto-set
↓ Dispositivo consumido
Se elimina de localStorage, panel se actualiza
Soporta formato v1 (legacy, array plano) y v2 (objeto con contexto de rack).
SSH Integrado
Terminal (ssh_integration.js)
1. Click "SSH" en panel de propiedades
2. SshConfigModal muestra formulario con datos de management_config
3. Confirmación → openSSHSession(deviceId, displayName)
4. Modal con terminal xterm.js embebido
5. WebSocket: ws://localhost:5050/ws/terminal (via Local Agent)
6. SFTP sidebar lazy-loaded si se solicita
Configuración (SshConfigModal.js)
Campos: IP, puerto SSH (default 22), vendor (autodetect SNMP), username, password, enable password.
Device reachability check via Local Agent:
POST http://localhost:5050/check?host={ip}&port={port}
→ { reachable: true/false }
SFTP (sftp_ui.js)
Browser → Django API → Local Agent (localhost:5050) → asyncssh → Dispositivo
| Endpoint Agent | Descripción |
|---|---|
POST /sftp/list | Listar directorio |
POST /sftp/download | Descargar archivo |
POST /sftp/upload | Subir archivo (multipart) |
POST /sftp/mkdir | Crear directorio |
POST /sftp/delete | Eliminar archivo/directorio |
UI: Árbol de archivos con expansión dinámica, breadcrumbs, upload/download.
Config Backups (backups.js)
Snapshots de running-config para dispositivos de red gestionados:
POST /api/device/{deviceId}/backup— Ejecuta SSH show running-config- Backend selecciona comando según vendor (Cisco/Arista/Juniper)
- Almacena en
ConfigBackupcon hash SHA256 para detección de cambios
| Operación | Descripción |
|---|---|
| Create | SSH → captura config → almacena con hash |
| List | Historial con timestamps y tipo (auto/manual/pre_change) |
| Diff | Compara dos snapshots lado a lado |
| Restore | Aplica configuración de backup anterior |
Modelos Django (racks/models.py)
BoxCategory
Categorización de dispositivos. unique_together: (name, organization).
| Campo | Tipo | Descripción |
|---|---|---|
name | CharField(100) | Nombre de la categoría |
organization | FK(Organization) | Nullable — null = global |
RackGroup
Agrupación lógica de racks. unique_together: (name, organization).
| Campo | Tipo | Descripción |
|---|---|---|
name | CharField(100) | Nombre del grupo |
color | CharField(20) | Color hex (default #6c757d) |
organization | FK(Organization) | Propietario |
created_at | DateTimeField | Auto |
Stencil
Templates de dispositivos (globales del sistema o por organización).
| Campo | Tipo | Descripción |
|---|---|---|
name | CharField(100) | Nombre del stencil |
image_path | CharField(200) | Ruta de la imagen |
default_u_height | IntegerField | Altura por defecto en U |
manufacturer | CharField(100) | Fabricante |
category | CharField(100) | Carpeta/categoría |
extra_data | TextField (JSON) | Metadata: half_width, ports, etc. |
organization | FK(Organization) | Nullable — null = stencil global del sistema |
Property: image_url — resuelve path a URL servible.
Rack
Armario rack de datacenter.
| Campo | Tipo | Descripción |
|---|---|---|
name | CharField(100) | Nombre del rack |
location | CharField(200) | Ubicación física |
notes | TextField | Notas libres |
height_u | IntegerField | Unidades totales (default 42) |
power_consumption | FloatField | Consumo en kWh |
status | CharField | active / planned / deprecated / maintenance |
organization | FK(Organization) | Propietario (CASCADE) |
parent_rack | FK(self) | Rack padre (nullable, SET_NULL) |
groups | M2M(RackGroup) | Grupos asignados |
created_at | DateTimeField | Auto |
updated_at | DateTimeField | Auto |
deleted_at | DateTimeField | Soft delete (nullable, indexed) |
is_template | BooleanField | Template reutilizable (indexed) |
Metodos: soft_delete(), restore(). Property: is_deleted.
Device
Dispositivo dentro de un rack.
| Campo | Tipo | Descripción |
|---|---|---|
rack | FK(Rack) | Rack contenedor (CASCADE) |
name | CharField(100) | Nombre del dispositivo |
notes | TextField | Notas libres |
u_position | IntegerField | Posición U (base 1, desde abajo) |
u_height | IntegerField | Altura en U (default 1) |
model_data | TextField (JSON) | half_width, slot, ports, image, type |
status | CharField | online / offline / warning / unknown |
last_status_check | DateTimeField | Nullable |
management_config | TextField (JSON) | IP, vendor, credenciales (Fernet AES encrypted) |
groups | M2M(RackGroup) | Grupos asignados |
Properties: has_network_management, management_ip, management_vendor.
Metodo: _decrypt_if_needed(value) — Fernet decryption para credenciales.
ConfigBackup
Snapshots de configuración de dispositivos de red.
| Campo | Tipo | Descripción |
|---|---|---|
device | FK(Device) | Dispositivo (CASCADE) |
rack | FK(Rack) | Rack (CASCADE) |
config_text | TextField | Running-config completo |
config_hash | CharField(64) | SHA256 para detección de cambios |
backup_type | CharField | auto / manual / pre_change |
created_at | DateTimeField | Auto (indexed) |
created_by | FK(User) | Nullable (SET_NULL) |
vendor | CharField(50) | Fabricante del dispositivo |
device_version | CharField(100) | Versión del firmware |
is_active | BooleanField | Activo/archivado |
notes | TextField | Notas |
Indexes: (device, -created_at), (rack, -created_at). Ordering: -created_at.
Relaciones
Organization (core)
├──→ Rack (FK) ←──→ RackGroup (M2M)
│ ├──→ Device (FK) ←──→ RackGroup (M2M)
│ │ └──→ ConfigBackup (FK)
│ └──→ ConfigBackup (FK)
├──→ RackGroup (FK)
├──→ Stencil (FK, nullable = global si null)
└──→ BoxCategory (FK, nullable)
API Endpoints (42+)
Racks CRUD (/api/racks/)
| Metodo | URL | Descripción |
|---|---|---|
| POST | /api/racks/ | Crear rack |
| GET | /api/racks/{id} | Obtener rack con dispositivos |
| PUT | /api/racks/{id} | Actualizar metadata |
| DELETE | /api/racks/{id} | Soft-delete |
| POST | /api/racks/{id}/rename | Renombrar |
| POST | /api/racks/{id}/clone | Clonar con dispositivos |
| PUT | /api/racks/{id}/devices | Auto-save: sincronizar dispositivos del canvas |
| POST | /api/racks/{id}/create_template | Guardar como template |
| POST | /api/racks/{id}/apply_template | Aplicar template (legacy — la UI del editor usa copy_from) |
| POST | /api/racks/{id}/copy_from | Copy from… (v1.79.0): copiar el montaje de otro rack o Master — origen = cualquier rack de la organización (source_id), modo replace (borra destino) o add (solo donde no colisiona, regla de ocupación de racks/utils/placement.py). Notas y management_config no viajan. Servicio: racks/services/copy_from.py::copy_devices_from() |
| GET | /api/racks/list | Lista ligera |
| GET | /api/racks/templates | Lista de templates |
| PUT | /api/racks/bulk-update | Actualización masiva |
Grupos (/api/racks/groups/)
| Metodo | URL | Descripción |
|---|---|---|
| GET | /api/racks/groups | Listar grupos |
| POST | /api/racks/groups | Crear grupo |
| PUT | /api/racks/groups/assign | Asignar racks a grupo |
| DELETE | /api/racks/groups/{id} | Eliminar grupo |
Stencils (/api/racks/stencils/)
| Metodo | URL | Descripción |
|---|---|---|
| GET | /api/racks/stencils | Listar stencils (sistema + organización) |
| PUT | /api/racks/stencils/{id} | Editar stencil |
| DELETE | /api/racks/stencils/{id} | Eliminar stencil |
| POST | /api/racks/stencils/custom | Crear custom |
| PUT | /api/racks/stencils/{id}/move | Mover de categoría |
| POST | /api/racks/stencils/create_from_image | Crear desde imagen upload |
| POST | /api/racks/stencils/create_from_device | Crear desde dispositivo en canvas |
Visio Import (/api/racks/visio/)
| Metodo | URL | Descripción |
|---|---|---|
| POST | /api/racks/visio/analyze | Analizar archivo Visio |
| POST | /api/racks/visio/confirm | Confirmar importación |
| DELETE | /api/racks/cleanup/session/{id} | Limpiar sesión temporal |
Library Backup/Restore (/api/racks/library/)
| Metodo | URL | Descripción |
|---|---|---|
| POST | /api/racks/library/import | Importar librería JSON |
| GET | /api/racks/library/backup | Exportar backup completo |
| POST | /api/racks/library/restore/analyze | Analizar backup |
| POST | /api/racks/library/restore/confirm | Confirmar restauración |
| DELETE | /api/racks/library/category/delete | Eliminar categoría |
| PUT | /api/racks/library/category/rename | Renombrar categoría |
Export (/api/racks/export/)
| Metodo | URL | Descripción |
|---|---|---|
| GET | /api/racks/export/csv | Exportar todos los racks a CSV |
| GET | /api/racks/{id}/export/csv | Exportar rack específico a CSV |
Labels (/api/racks/labels/)
| Metodo | URL | Descripción |
|---|---|---|
| GET | /api/racks/labels/{target} | Generar QR labels (target = “all” o rack_id) |
| GET | /api/racks/labels/rack_info/{target} | Info del rack para QR |
Papelera (/api/racks/trash/)
| Metodo | URL | Descripción |
|---|---|---|
| GET | /api/racks/trash/racks | Listar racks en papelera |
| POST | /api/racks/trash/racks/{id}/restore | Restaurar rack |
| DELETE | /api/racks/trash/racks/{id} | Eliminar permanentemente |
| DELETE | /api/racks/trash/racks | Vaciar papelera |
| GET | /api/racks/trash/count | Contar racks en papelera |
Diagramas de Flujo
Carga del Editor
GET /editor/{rack_id}/
↓ Django view → template editor.html
↓ main.js bootstrap
├── Lee #rack-data del DOM → STATE.rackData
├── Calcula CONFIG.RACK_HEIGHT = height_u * U_HEIGHT
├── konva_setup.js → Stage, Layer, rackGroup
├── fitRackToView() → Centra rack en viewport
├── devices.js → addDeviceToRack() por cada device
├── ui_library.js → Carga stencils en panel izquierdo
├── auto_provision_panel.js → Check localStorage pending
└── history_adapter.js → Snapshot inicial
Auto-Save
Usuario edita (drag, rename, port change...)
↓ Módulo correspondiente
├── Actualiza STATE.devices[i]
├── triggerAutoSave() → debounce 500ms → saveRackState()
│ ↓
│ PUT /api/racks/{id}/devices
└── dispatchEvent('request-save-history')
↓
history_adapter.saveState() → createSnapshot() → STATE_MANAGER.push()
Última actualización: 21-08-2026 Mantenido por: Equipo CreaRack
Véase también
- [[crearack-tech—agents—dev-rack-editor]] — agente técnico del Rack Editor
- [[concept—racks—editor]] — concepto del Rack Editor
- [[entity—racks—model—rack]] — modelo Rack
- [[entity—racks—model—device]] — modelo Device (dispositivo)
- [[entity—racks—model—stencil]] — modelo Stencil
- [[crearack—conceptos—rack-editor-conceptos]] — conceptos base del Rack Editor para usuarios
- [[crearack—racks—crear-rack]] — cómo crear un rack en la app
- [[crearack-tech—guides—interconnection-guide]] — guía de interconexionado (cableado y puertos entre dispositivos)