From 33490d40c49244f80e622b97a7bc205ed74073d3 Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 13 Aug 2026 09:58:34 +0200 Subject: [PATCH] fix: preserve native reorder rack devices --- README.md | 4 +++ netbox_utilities/__init__.py | 2 +- .../netbox_utilities/reorder-rack-width.js | 34 +++++++++++++++---- netbox_utilities/tests/test_reorder_rack.py | 3 ++ pyproject.toml | 2 +- 5 files changed, 37 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index a0be430..952032d 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,10 @@ Reorder ersetzt seine nativen Gerätekacheln erst, nachdem ein vollständiger und zur JavaScript-Version passender Datensatz geprüft wurde. Bei einer gemischten Installation aus altem Python-Code und neuen statischen Dateien bleibt deshalb die native Ansicht erhalten, statt ein leeres Rack zu zeigen. +Ab Version `0.9.4` werden vorhandene Reorder-Kacheln außerdem direkt auf das +12-Spalten-Raster erweitert und nicht mehr vorsorglich neu erzeugt. Nur ein +zweites, von NetBox' Ein-Gerät-pro-HE-Darstellung unterschlagenes Gerät wird +aus den Plugin-Daten ergänzt. Das verhindert browserabhängige leere Racks. Bereits vorhandene Geräte, die dieselbe HE und Rackseite belegen, aber noch keine Plugin-Platzierungszeile besitzen, werden in Rack-SVG, Reorder und der Topology-Rack-Ansicht ohne Datenbankänderung gleichmäßig nebeneinander diff --git a/netbox_utilities/__init__.py b/netbox_utilities/__init__.py index 7dbe5de..f2676f9 100644 --- a/netbox_utilities/__init__.py +++ b/netbox_utilities/__init__.py @@ -1,6 +1,6 @@ from netbox.plugins import PluginConfig, get_plugin_config -__version__ = "0.9.3" +__version__ = "0.9.4" class NetBoxUtilitiesConfig(PluginConfig): diff --git a/netbox_utilities/static/netbox_utilities/reorder-rack-width.js b/netbox_utilities/static/netbox_utilities/reorder-rack-width.js index 1f5f33b..c8ee0cf 100644 --- a/netbox_utilities/static/netbox_utilities/reorder-rack-width.js +++ b/netbox_utilities/static/netbox_utilities/reorder-rack-width.js @@ -48,6 +48,8 @@ console.warn('NetBox Utilities received no replacement devices and kept the populated native layout.'); return; } + const devicesById = new Map(data.devices.map(device => [String(device.id), device])); + const nativeItemsByFace = {}; const setAttribute = (element, name, value) => element.setAttribute(name, String(value)); const originalFetch = window.fetch.bind(window); @@ -73,12 +75,26 @@ Object.entries(gridElements).forEach(([face, grid]) => { setAttribute(grid, 'gs-column', columns); + const nativeItemsById = new Map( + Array.from(grid.querySelectorAll('.grid-stack-item')).map(item => [item.getAttribute('gs-id'), item]), + ); grid.querySelectorAll('.grid-stack-item').forEach(item => { - // The native Reorder view is based on Rack.get_rack_units(), which can - // expose only one device per rack unit. Rebuild the mounted grids from - // the complete server payload; keep native non-racked devices intact. + // Keep every native widget whenever possible. Rack.get_rack_units() can + // expose only one device per unit, so missing shared widgets are added + // from the complete payload below. if (face !== 'other') { - item.remove(); + const device = devicesById.get(item.getAttribute('gs-id')); + const belongsOnFace = device && (device.face === face || device.full_depth); + if (!belongsOnFace) { + item.remove(); + return; + } + setAttribute(item, 'gs-w', device.grid_width); + setAttribute(item, 'gs-h', device.grid_height); + setAttribute(item, 'gs-x', device.grid_x); + setAttribute(item, 'gs-y', device.grid_y); + item.dataset.rackWidth = String(device.width); + item.dataset.horizontalPosition = String(device.horizontal_position); return; } setAttribute(item, 'gs-w', columns); @@ -86,6 +102,7 @@ item.dataset.rackWidth = '1'; item.dataset.horizontalPosition = '1'; }); + nativeItemsByFace[face] = nativeItemsById; }); const addWidget = (device, gridFace, itemFace, rearSide = false) => { @@ -125,9 +142,14 @@ data.devices.forEach(device => { const face = device.face === 'rear' ? 'rear' : 'front'; - addWidget(device, face, face); + if (!nativeItemsByFace[face].has(String(device.id))) { + addWidget(device, face, face); + } if (device.full_depth) { - addWidget(device, face === 'front' ? 'rear' : 'front', 'back', true); + const oppositeFace = face === 'front' ? 'rear' : 'front'; + if (!nativeItemsByFace[oppositeFace].has(String(device.id))) { + addWidget(device, oppositeFace, 'back', true); + } } }); diff --git a/netbox_utilities/tests/test_reorder_rack.py b/netbox_utilities/tests/test_reorder_rack.py index 26f1810..d2a2400 100644 --- a/netbox_utilities/tests/test_reorder_rack.py +++ b/netbox_utilities/tests/test_reorder_rack.py @@ -291,6 +291,9 @@ class ReorderRackFrontendTest(SimpleTestCase): self.assertIn("data.schema_version !== 3", script) self.assertIn("data.complete !== true", script) self.assertIn("nativeMountedItems.length", script) + self.assertIn("const nativeItemsByFace = {}", script) + self.assertIn("const device = devicesById.get", script) + self.assertIn("if (!nativeItemsByFace[face].has", script) self.assertNotIn("partialDeviceIds", script) self.assertNotIn("JSON.stringify", script) diff --git a/pyproject.toml b/pyproject.toml index bfd8ea4..d522be9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "netbox-utilities" -version = "0.9.3" +version = "0.9.4" description = "Navigation, tenant utilities, partial-width rack devices, bulk uploads, and rack reordering for NetBox 4.6" readme = "README.md" requires-python = ">=3.12"