From bb0693bef7f606138a641183af1cf8001e2f4b33 Mon Sep 17 00:00:00 2001 From: Louis Date: Tue, 28 Jul 2026 14:32:34 +0200 Subject: [PATCH] fix: sort topology devices without rendering rack elevations --- README.md | 2 +- .../css/rack_elevation.css | 43 +------------------ netbox_topology_views/static_dev/css/app.scss | 17 ++------ .../netbox_topology_views/rack_elevation.html | 33 ++------------ netbox_topology_views/views.py | 18 +------- 5 files changed, 11 insertions(+), 102 deletions(-) diff --git a/README.md b/README.md index 01855a4..beaaeea 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Rückseite und Verkabelung. - Vorder- und Rückseite eines Racks getrennt betrachten - Rackübergreifende und externe Kabelverbindungen erkennen - Geräte in der Kabeltopologie frei verschieben -- Geräte in der Kabeltopologie zunächst entsprechend ihrer Rackposition und Rackseite anordnen +- Geräte in der Kabeltopologie innerhalb jedes Racks nach ihrer Rackposition sortieren - Manuelle Positionen pro Rack-Auswahl im Browser speichern - Verbindungen eines Geräts beim Hover weiß hervorheben - Alle verbundenen Ports und Gegenstellen eines Geräts anzeigen diff --git a/netbox_topology_views/static/netbox_topology_views/css/rack_elevation.css b/netbox_topology_views/static/netbox_topology_views/css/rack_elevation.css index 75768ab..160cf20 100644 --- a/netbox_topology_views/static/netbox_topology_views/css/rack_elevation.css +++ b/netbox_topology_views/static/netbox_topology_views/css/rack_elevation.css @@ -63,42 +63,10 @@ html[data-netbox-color-mode="dark"] .rack-device { display: flex; flex-direction: column; gap: .75rem; - width: 30rem; + width: 15rem; } -.rack-topology-column.is-external { width: 15rem; } .rack-topology-column h4 { text-align: center; font-size: .85rem; } -.rack-topology-rack-body { - display: grid; - grid-template-columns: 1fr 1fr; - gap: .5rem; - height: calc(var(--rack-units) * 22px); - min-height: 280px; - margin-top: 1rem; -} -.rack-topology-lane { - position: relative; - border: 3px solid #718096; - background: repeating-linear-gradient(to top, rgba(56, 73, 89, .14) 0, rgba(56, 73, 89, .14) 1px, transparent 1px, transparent calc(100% / var(--rack-units))); -} -.rack-topology-lane-label { - position: absolute; - top: -1.4rem; - left: 0; - right: 0; - text-align: center; - font-size: .65rem; - text-transform: uppercase; -} -.rack-topology-external-body { display: flex; flex-direction: column; gap: .75rem; } -.rack-topology-lane .rack-topology-node { - position: absolute; - left: 3px; - right: 3px; - min-height: 18px; - padding: 2px 6px; - overflow: hidden; - box-sizing: border-box; -} +.rack-topology-device-list { display: flex; flex-direction: column; gap: .75rem; } .rack-topology-node { display: block; padding: .55rem .75rem; @@ -163,10 +131,3 @@ html[data-netbox-color-mode="dark"] .rack-device { border-top: 1px solid var(--tblr-border-color, #ced4da); } .rack-topology-tooltip small { opacity: .65; } -html[data-bs-theme="dark"] .rack-topology-lane, -body[data-bs-theme="dark"] .rack-topology-lane, -html[data-netbox-color-mode="dark"] .rack-topology-lane { - border-color: #3b4654; - background-color: #212b36; - background-image: repeating-linear-gradient(to top, rgba(200, 215, 230, .12) 0, rgba(200, 215, 230, .12) 1px, transparent 1px, transparent calc(100% / var(--rack-units))); -} diff --git a/netbox_topology_views/static_dev/css/app.scss b/netbox_topology_views/static_dev/css/app.scss index ccee7d2..b0f12f2 100644 --- a/netbox_topology_views/static_dev/css/app.scss +++ b/netbox_topology_views/static_dev/css/app.scss @@ -54,7 +54,7 @@ html[data-netbox-color-mode=dark] #visgraph { .rack-topology { position: relative; min-height: 18rem; overflow-x: auto; padding: 1rem; background: var(--tblr-bg-surface-secondary, rgba(127,127,127,.05)); } .rack-topology-lines { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; z-index: 1; } .rack-topology-columns { position: relative; z-index: 2; display: flex; align-items: flex-start; justify-content: space-around; gap: 5rem; min-width: max-content; pointer-events: none; } -.rack-topology-column { display: flex; flex-direction: column; gap: .75rem; width: 30rem; } +.rack-topology-column { display: flex; flex-direction: column; gap: .75rem; width: 15rem; } .rack-topology-column h4 { text-align: center; font-size: .85rem; } .rack-topology-node { display: block; padding: .55rem .75rem; border: 2px solid var(--node-color); border-radius: .35rem; background: var(--tblr-bg-surface, #fff); color: inherit; text-decoration: none; box-shadow: 0 2px 6px rgba(0,0,0,.12); cursor: grab; touch-action: none; user-select: none; pointer-events: auto; } .rack-topology-node.is-dragging { cursor: grabbing; z-index: 5; } @@ -76,21 +76,10 @@ html[data-netbox-color-mode=dark] #visgraph { background-color: #fff; } -.rack-topology-rack-body { - display: grid; - grid-template-columns: 1fr 1fr; - gap: .5rem; - height: calc(var(--rack-units) * 22px); - min-height: 280px; - margin-top: 1rem; -} - -.rack-topology-lane { position: relative; border: 3px solid #718096; } -.rack-topology-lane .rack-topology-node { position: absolute; left: 3px; right: 3px; min-height: 18px; box-sizing: border-box; } -.rack-topology-external-body { display: flex; flex-direction: column; gap: .75rem; } +.rack-topology-device-list { display: flex; flex-direction: column; gap: .75rem; } html[data-bs-theme="dark"], body[data-bs-theme="dark"], html[data-netbox-color-mode="dark"] { - .rack-frame, .rack-topology-lane { border-color: #3b4654; background-color: #212b36; } + .rack-frame { border-color: #3b4654; background-color: #212b36; } .rack-device { background: #162e45; color: #fff; } } diff --git a/netbox_topology_views/templates/netbox_topology_views/rack_elevation.html b/netbox_topology_views/templates/netbox_topology_views/rack_elevation.html index eb11fdf..18f253b 100644 --- a/netbox_topology_views/templates/netbox_topology_views/rack_elevation.html +++ b/netbox_topology_views/templates/netbox_topology_views/rack_elevation.html @@ -161,25 +161,12 @@ groups.forEach(group => { const column = document.createElement('section'); - column.className = `rack-topology-column ${group.rackUnits ? 'is-rack' : 'is-external'}`; + column.className = 'rack-topology-column'; const heading = document.createElement('h4'); heading.textContent = group.name; column.appendChild(heading); const nodeContainer = document.createElement('div'); - nodeContainer.className = group.rackUnits ? 'rack-topology-rack-body' : 'rack-topology-external-body'; - if (group.rackUnits) { - nodeContainer.style.setProperty('--rack-units', group.rackUnits); - ['front', 'rear'].forEach(face => { - const lane = document.createElement('div'); - lane.className = `rack-topology-lane is-${face}`; - lane.dataset.face = face; - const label = document.createElement('span'); - label.className = 'rack-topology-lane-label'; - label.textContent = face === 'front' ? 'Vorderseite' : 'Rückseite'; - lane.appendChild(label); - nodeContainer.appendChild(lane); - }); - } + nodeContainer.className = 'rack-topology-device-list'; column.appendChild(nodeContainer); group.nodes.sort((a, b) => (b.position || 0) - (a.position || 0)).forEach(node => { const item = document.createElement(node.url ? 'a' : 'div'); @@ -193,16 +180,9 @@ const portElement = document.createElement('small'); portElement.textContent = node.subtitle || node.name; item.append(nameElement, portElement); - if (group.rackUnits) { - item.style.bottom = `${((node.position || 1) - 1) / group.rackUnits * 100}%`; - item.style.height = `${Math.max(node.height || 1, 1) / group.rackUnits * 100}%`; - } const saved = storedPositions[node.node_id]; if (saved) item.style.transform = `translate(${saved.x}px, ${saved.y}px)`; - const target = group.rackUnits - ? nodeContainer.querySelector(`.rack-topology-lane.is-${node.face === 'rear' ? 'rear' : 'front'}`) - : nodeContainer; - target.appendChild(item); + nodeContainer.appendChild(item); }); columns.appendChild(column); }); @@ -457,13 +437,6 @@ const headingY = headingBounds.top - rootBounds.top + root.scrollTop; content += `${xmlEscape(heading.textContent)}`; drawioCells += ``; - column.querySelectorAll('.rack-topology-lane').forEach(lane => { - const bounds = lane.getBoundingClientRect(); - const x = bounds.left - rootBounds.left + root.scrollLeft; - const y = bounds.top - rootBounds.top + root.scrollTop; - content += ``; - drawioCells += ``; - }); }); root.querySelectorAll('.rack-topology-node').forEach(node => { const bounds = node.getBoundingClientRect(); diff --git a/netbox_topology_views/views.py b/netbox_topology_views/views.py index 383446d..34c5048 100644 --- a/netbox_topology_views/views.py +++ b/netbox_topology_views/views.py @@ -992,21 +992,7 @@ class RackElevationView(PermissionRequiredMixin, View): }) cable_rows = [] - topology_nodes = { - f"device-{device.pk}": { - "node_id": f"device-{device.pk}", "device_id": device.pk, - "device_name": device.name or str(device.device_type), - "name": "", "subtitle": str(device.device_type), - "rack_id": device.rack_id, "rack_name": device.rack.name, - "url": device.get_absolute_url(), - "color": str(device.role.color or "1685fc"), - "position": float(device.position or 1), - "height": max(device.device_type.u_height or 1, 1), - "face": device.elevation_face, - "rack_u_height": device.rack.u_height, - } - for rack in rack_list for device in rack.elevation_devices - } + topology_nodes = {} topology_edges = [] if device_ids: cable_ids = CableTermination.objects.filter( @@ -1022,7 +1008,7 @@ class RackElevationView(PermissionRequiredMixin, View): cable_rows.append({"cable": cable, "a": a, "b": b}) for endpoint in (a, b): if endpoint: - topology_nodes.setdefault(endpoint["node_id"], endpoint) + topology_nodes[endpoint["node_id"]] = endpoint if a and b: topology_edges.append({ "from": a["node_id"], "to": b["node_id"],