diff --git a/netbox_device_view/static/netbox_device_view/css/device_view_svg.css b/netbox_device_view/static/netbox_device_view/css/device_view_svg.css index 6b0cc26..7ba1003 100644 --- a/netbox_device_view/static/netbox_device_view/css/device_view_svg.css +++ b/netbox_device_view/static/netbox_device_view/css/device_view_svg.css @@ -76,6 +76,8 @@ .dv-port.bg-success .dv-port-rect { fill: #198754; } .dv-port.bg-secondary .dv-port-rect { fill: #6c757d; } .dv-port.bg-danger .dv-port-rect { fill: #dc3545; } +/* Partial connection: cable attached but far-end has no device */ +.dv-port.bg-warning .dv-port-rect { fill: #ffc107; } /* Cable colour override — applied inline via JS (style attribute on .dv-port) */ /* No rule needed; the JS sets fill on .dv-port-rect directly */ @@ -85,6 +87,15 @@ fill: url(#dv-nocolor-pattern); } +/* Partial connection dot overlay — small warning circle in the top-right corner. + Always rendered on top of the port rect, visible even in cable colour mode. */ +.dv-partial-dot { + fill: #ffc107; + stroke: #333; + stroke-width: 0.5; + pointer-events: none; +} + /* ── Structural elements ───────────────────────────────────────────────────── */ .dv-spacer { diff --git a/netbox_device_view/templates/netbox_device_view/deviceview.html b/netbox_device_view/templates/netbox_device_view/deviceview.html index 0c69ae7..48febf8 100644 --- a/netbox_device_view/templates/netbox_device_view/deviceview.html +++ b/netbox_device_view/templates/netbox_device_view/deviceview.html @@ -118,6 +118,7 @@ name: "{{ int.name|escapejs }}", enabled: {% if int.enabled or int.is_port %}true{% else %}false{% endif %}, connected: {% if int.connected_endpoints|length > 0 or int.is_port and int.link_peers|length > 0 %}true{% else %}false{% endif %}, + partiallyConnected: {% if int.link_peers|length > 0 and int.connected_endpoints|length == 0 %}true{% else %}false{% endif %}, cableColor: "{% if cable_colors == 'on' and int.cable.color != '' %}{{ int.cable.color }}{% endif %}", cableNoColor: {% if cable_colors == "on" and int.cable.color == "" or cable_colors == "on" and int.cable == None %}true{% else %}false{% endif %}, connectedEndpoints: [{% for ce in int.connected_endpoints %}"{{ ce.device }} | {{ ce.name }}"{% if not forloop.last %},{% endif %}{% endfor %}], @@ -139,12 +140,25 @@ if (rect) rect.setAttribute("fill", "#" + p.cableColor); } else if (p.connected) { g.classList.add("bg-success"); + } else if (p.partiallyConnected) { + g.classList.add("bg-warning"); } else if (p.enabled) { g.classList.add("bg-secondary"); } else { g.classList.add("bg-danger"); } + // Always show a small warning dot for partially connected ports, + // even when cable color mode overrides the fill colour. + if (p.partiallyConnected && rect) { + var dot = document.createElementNS("http://www.w3.org/2000/svg", "circle"); + dot.setAttribute("class", "dv-partial-dot"); + dot.setAttribute("cx", parseFloat(rect.getAttribute("x")) + parseFloat(rect.getAttribute("width")) - 3); + dot.setAttribute("cy", parseFloat(rect.getAttribute("y")) + 3); + dot.setAttribute("r", "2.5"); + g.appendChild(dot); + } + g.setAttribute("role", "link"); g.style.cursor = "pointer"; g.addEventListener("click", function () { window.location.href = p.url; }); diff --git a/netbox_device_view/templates/netbox_device_view/ports.html b/netbox_device_view/templates/netbox_device_view/ports.html index d8ad42f..4cc06a0 100644 --- a/netbox_device_view/templates/netbox_device_view/ports.html +++ b/netbox_device_view/templates/netbox_device_view/ports.html @@ -115,6 +115,7 @@

Deviceview {{ object.name }}

name: "{{ int.name|escapejs }}", enabled: {% if int.enabled or int.is_port %}true{% else %}false{% endif %}, connected: {% if int.connected_endpoints|length > 0 or int.is_port and int.link_peers|length > 0 %}true{% else %}false{% endif %}, + partiallyConnected: {% if int.link_peers|length > 0 and int.connected_endpoints|length == 0 %}true{% else %}false{% endif %}, cableColor: "{% if cable_colors == 'on' and int.cable.color != '' %}{{ int.cable.color }}{% endif %}", cableNoColor: {% if cable_colors == "on" and int.cable.color == "" or cable_colors == "on" and int.cable == None %}true{% else %}false{% endif %}, connectedEndpoints: [{% for ce in int.connected_endpoints %}"{{ ce.device }} | {{ ce.name }}"{% if not forloop.last %},{% endif %}{% endfor %}], @@ -135,11 +136,25 @@

Deviceview {{ object.name }}

if (rect) rect.setAttribute("fill", "#" + p.cableColor); } else if (p.connected) { g.classList.add("bg-success"); + } else if (p.partiallyConnected) { + g.classList.add("bg-warning"); } else if (p.enabled) { g.classList.add("bg-secondary"); } else { g.classList.add("bg-danger"); } + + // Always show a small warning dot for partially connected ports, + // even when cable color mode overrides the fill colour. + if (p.partiallyConnected && rect) { + var dot = document.createElementNS("http://www.w3.org/2000/svg", "circle"); + dot.setAttribute("class", "dv-partial-dot"); + dot.setAttribute("cx", parseFloat(rect.getAttribute("x")) + parseFloat(rect.getAttribute("width")) - 3); + dot.setAttribute("cy", parseFloat(rect.getAttribute("y")) + 3); + dot.setAttribute("r", "2.5"); + g.appendChild(dot); + } + g.setAttribute("role", "link"); g.style.cursor = "pointer"; g.addEventListener("click", function () { window.location.href = p.url; });