diff --git a/netbox/dcim/svg.py b/netbox/dcim/svg.py index 5601bc591..b7f1576ee 100644 --- a/netbox/dcim/svg.py +++ b/netbox/dcim/svg.py @@ -398,6 +398,39 @@ class CableTraceSVG: return group + def _draw_wirelesslink(self, url, labels): + """ + Draw a line with labels representing a WirelessLink. + + :param url: Hyperlink URL + :param labels: Iterable of text labels + """ + group = Group(class_='connector') + + # Draw the wireless link + start = (OFFSET + self.center, self.cursor) + height = PADDING * 2 + LINE_HEIGHT * len(labels) + PADDING * 2 + end = (start[0], start[1] + height) + line = Line(start=start, end=end, class_='wireless-link') + group.add(line) + + self.cursor += PADDING * 2 + + # Add link + link = Hyperlink(href=f'{self.base_url}{url}', target='_blank') + + # Add text label(s) + for i, label in enumerate(labels): + self.cursor += LINE_HEIGHT + text_coords = (self.center + PADDING * 2, self.cursor - LINE_HEIGHT / 2) + text = Text(label, insert=text_coords, class_='bold' if not i else []) + link.add(text) + + group.add(link) + self.cursor += PADDING * 2 + + return group + def _draw_attachment(self): """ Return an SVG group containing a line element and "Attachment" label. @@ -418,6 +451,9 @@ class CableTraceSVG: """ Return an SVG document representing a cable trace. """ + from dcim.models import Cable + from wireless.models import WirelessLink + traced_path = self.origin.trace() # Prep elements list @@ -452,24 +488,39 @@ class CableTraceSVG: ) terminations.append(termination) - # Connector (either a Cable or attachment to a ProviderNetwork) + # Connector (a Cable or WirelessLink) if connector is not None: # Cable - cable_labels = [ - f'Cable {connector}', - connector.get_status_display() - ] - if connector.type: - cable_labels.append(connector.get_type_display()) - if connector.length and connector.length_unit: - cable_labels.append(f'{connector.length} {connector.get_length_unit_display()}') - cable = self._draw_cable( - color=connector.color or '000000', - url=connector.get_absolute_url(), - labels=cable_labels - ) - connectors.append(cable) + if type(connector) is Cable: + connector_labels = [ + f'Cable {connector}', + connector.get_status_display() + ] + if connector.type: + connector_labels.append(connector.get_type_display()) + if connector.length and connector.length_unit: + connector_labels.append(f'{connector.length} {connector.get_length_unit_display()}') + cable = self._draw_cable( + color=connector.color or '000000', + url=connector.get_absolute_url(), + labels=connector_labels + ) + connectors.append(cable) + + # WirelessLink + elif type(connector) is WirelessLink: + connector_labels = [ + f'Wireless link {connector}', + connector.get_status_display() + ] + if connector.ssid: + connector_labels.append(connector.ssid) + wirelesslink = self._draw_wirelesslink( + url=connector.get_absolute_url(), + labels=connector_labels + ) + connectors.append(wirelesslink) # Far end termination termination = self._draw_box( diff --git a/netbox/project-static/dist/cable_trace.css b/netbox/project-static/dist/cable_trace.css index 633ccd572..50622f128 100644 --- a/netbox/project-static/dist/cable_trace.css +++ b/netbox/project-static/dist/cable_trace.css @@ -1 +1 @@ -:root{--nbx-trace-color: #000;--nbx-trace-node-bg: #e9ecef;--nbx-trace-termination-bg: #f8f9fa;--nbx-trace-cable-shadow: #343a40;--nbx-trace-attachment: #ced4da}:root[data-netbox-color-mode=dark]{--nbx-trace-color: #fff;--nbx-trace-node-bg: #212529;--nbx-trace-termination-bg: #343a40;--nbx-trace-cable-shadow: #e9ecef;--nbx-trace-attachment: #6c757d}*{font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:.875rem}text{text-anchor:middle;dominant-baseline:middle}text:not([fill]){fill:var(--nbx-trace-color)}text.bold{font-weight:700}svg rect{fill:var(--nbx-trace-node-bg);stroke:#606060;stroke-width:1}svg rect .termination{fill:var(--nbx-trace-termination-bg)}svg .connector text{text-anchor:start}svg line{stroke-width:5px}svg line.cable-shadow{stroke:var(--nbx-trace-cable-shadow);stroke-width:7px}svg line.attachment{stroke:var(--nbx-trace-attachment);stroke-dasharray:5px,5px} +:root{--nbx-trace-color: #000;--nbx-trace-node-bg: #e9ecef;--nbx-trace-termination-bg: #f8f9fa;--nbx-trace-cable-shadow: #343a40;--nbx-trace-attachment: #ced4da}:root[data-netbox-color-mode=dark]{--nbx-trace-color: #fff;--nbx-trace-node-bg: #212529;--nbx-trace-termination-bg: #343a40;--nbx-trace-cable-shadow: #e9ecef;--nbx-trace-attachment: #6c757d}*{font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:.875rem}text{text-anchor:middle;dominant-baseline:middle}text:not([fill]){fill:var(--nbx-trace-color)}text.bold{font-weight:700}svg rect{fill:var(--nbx-trace-node-bg);stroke:#606060;stroke-width:1}svg rect .termination{fill:var(--nbx-trace-termination-bg)}svg .connector text{text-anchor:start}svg line{stroke-width:5px}svg line.cable-shadow{stroke:var(--nbx-trace-cable-shadow);stroke-width:7px}svg line.wireless-link{stroke:var(--nbx-trace-attachment);stroke-dasharray:4px 12px;stroke-linecap:round}svg line.attachment{stroke:var(--nbx-trace-attachment);stroke-dasharray:5px} diff --git a/netbox/project-static/styles/cable-trace.scss b/netbox/project-static/styles/cable-trace.scss index 85deafe96..51d94d38a 100644 --- a/netbox/project-static/styles/cable-trace.scss +++ b/netbox/project-static/styles/cable-trace.scss @@ -59,8 +59,13 @@ svg { stroke: var(--nbx-trace-cable-shadow); stroke-width: 7px; } + line.wireless-link { + stroke: var(--nbx-trace-attachment); + stroke-dasharray: 4px 12px; + stroke-linecap: round; + } line.attachment { stroke: var(--nbx-trace-attachment); - stroke-dasharray: 5px, 5px; + stroke-dasharray: 5px; } }