fix: sort topology devices without rendering rack elevations
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
|
||||
@@ -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 += `<text x="${headingX + headingBounds.width / 2}" y="${headingY + 15}" text-anchor="middle" fill="${palette.heading}" font-family="sans-serif" font-size="13" font-weight="600">${xmlEscape(heading.textContent)}</text>`;
|
||||
drawioCells += `<mxCell id="group-${drawioId++}" value="${xmlEscape(heading.textContent)}" style="text;html=1;strokeColor=none;fillColor=none;align=center;fontStyle=1;fontColor=${palette.heading};" vertex="1" parent="1"><mxGeometry x="${headingX}" y="${headingY}" width="${headingBounds.width}" height="22" as="geometry"/></mxCell>`;
|
||||
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 += `<rect x="${x}" y="${y}" width="${bounds.width}" height="${bounds.height}" fill="none" stroke="${palette.frame}" stroke-width="3"/>`;
|
||||
drawioCells += `<mxCell id="lane-${drawioId++}" value="${xmlEscape(lane.dataset.face === 'rear' ? 'Rückseite' : 'Vorderseite')}" style="rounded=0;html=1;fillColor=none;strokeColor=${palette.frame};strokeWidth=3;verticalAlign=top;fontSize=9;fontColor=${palette.muted};" vertex="1" parent="1"><mxGeometry x="${x}" y="${y}" width="${bounds.width}" height="${bounds.height}" as="geometry"/></mxCell>`;
|
||||
});
|
||||
});
|
||||
root.querySelectorAll('.rack-topology-node').forEach(node => {
|
||||
const bounds = node.getBoundingClientRect();
|
||||
|
||||
@@ -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"],
|
||||
|
||||
Reference in New Issue
Block a user