mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-21 11:37:21 -06:00
Add support for cable fan-outs
This commit is contained in:
parent
ecba9699ed
commit
8d92ec2007
@ -16,8 +16,8 @@ __all__ = (
|
|||||||
OFFSET = 0.5
|
OFFSET = 0.5
|
||||||
PADDING = 10
|
PADDING = 10
|
||||||
LINE_HEIGHT = 20
|
LINE_HEIGHT = 20
|
||||||
|
FANOUT_HEIGHT = 25
|
||||||
TERMINATION_WIDTH = 80
|
TERMINATION_WIDTH = 100
|
||||||
|
|
||||||
|
|
||||||
class Node(Hyperlink):
|
class Node(Hyperlink):
|
||||||
@ -66,6 +66,47 @@ class Node(Hyperlink):
|
|||||||
return self.box['x'] + self.box['width'] / 2, self.box['y'] + self.box['height']
|
return self.box['x'] + self.box['width'] / 2, self.box['y'] + self.box['height']
|
||||||
|
|
||||||
|
|
||||||
|
class Connector(Group):
|
||||||
|
"""
|
||||||
|
Return an SVG group containing a line element and text labels representing a Cable.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
color: Cable (line) color
|
||||||
|
url: Hyperlink URL
|
||||||
|
labels: Iterable of text labels
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, start, url, color, labels=[], **extra):
|
||||||
|
super().__init__(class_='connector', **extra)
|
||||||
|
|
||||||
|
self.start = start
|
||||||
|
self.height = PADDING * 2 + LINE_HEIGHT * len(labels) + PADDING * 2
|
||||||
|
self.end = (start[0], start[1] + self.height)
|
||||||
|
self.color = color or '000000'
|
||||||
|
|
||||||
|
# Draw a "shadow" line to give the cable a border
|
||||||
|
cable_shadow = Line(start=self.start, end=self.end, class_='cable-shadow')
|
||||||
|
self.add(cable_shadow)
|
||||||
|
|
||||||
|
# Draw the cable
|
||||||
|
cable = Line(start=self.start, end=self.end, style=f'stroke: #{self.color}')
|
||||||
|
self.add(cable)
|
||||||
|
|
||||||
|
# Add link
|
||||||
|
link = Hyperlink(href=url, target='_blank')
|
||||||
|
|
||||||
|
# Add text label(s)
|
||||||
|
cursor = start[1]
|
||||||
|
cursor += PADDING * 2
|
||||||
|
for i, label in enumerate(labels):
|
||||||
|
cursor += LINE_HEIGHT
|
||||||
|
text_coords = (start[0] + PADDING * 2, cursor - LINE_HEIGHT / 2)
|
||||||
|
text = Text(label, insert=text_coords, class_='bold' if not i else [])
|
||||||
|
link.add(text)
|
||||||
|
|
||||||
|
self.add(link)
|
||||||
|
|
||||||
|
|
||||||
class CableTraceSVG:
|
class CableTraceSVG:
|
||||||
"""
|
"""
|
||||||
Generate a graphical representation of a CablePath in SVG format.
|
Generate a graphical representation of a CablePath in SVG format.
|
||||||
@ -136,13 +177,15 @@ class CableTraceSVG:
|
|||||||
node = Node(
|
node = Node(
|
||||||
position=(0, self.cursor),
|
position=(0, self.cursor),
|
||||||
width=self.width,
|
width=self.width,
|
||||||
url=obj.get_absolute_url(),
|
url=f'{self.base_url}{obj.get_absolute_url()}',
|
||||||
color=self._get_color(obj),
|
color=self._get_color(obj),
|
||||||
labels=self._get_labels(obj)
|
labels=self._get_labels(obj)
|
||||||
)
|
)
|
||||||
self.parent_objects.append(node)
|
self.parent_objects.append(node)
|
||||||
self.cursor += node.box['height']
|
self.cursor += node.box['height']
|
||||||
|
|
||||||
|
return node
|
||||||
|
|
||||||
def draw_terminations(self, terminations):
|
def draw_terminations(self, terminations):
|
||||||
"""
|
"""
|
||||||
Draw a row of terminating objects (e.g. interfaces) belonging to the same parent object, all of which
|
Draw a row of terminating objects (e.g. interfaces) belonging to the same parent object, all of which
|
||||||
@ -156,7 +199,7 @@ class CableTraceSVG:
|
|||||||
node = Node(
|
node = Node(
|
||||||
position=(x + i * TERMINATION_WIDTH, self.cursor),
|
position=(x + i * TERMINATION_WIDTH, self.cursor),
|
||||||
width=TERMINATION_WIDTH,
|
width=TERMINATION_WIDTH,
|
||||||
url=term.get_absolute_url(),
|
url=f'{self.base_url}{term.get_absolute_url()}',
|
||||||
color=self._get_color(term),
|
color=self._get_color(term),
|
||||||
labels=self._get_labels(term),
|
labels=self._get_labels(term),
|
||||||
radius=5
|
radius=5
|
||||||
@ -169,53 +212,51 @@ class CableTraceSVG:
|
|||||||
|
|
||||||
return nodes
|
return nodes
|
||||||
|
|
||||||
def draw_cable(self, color, url, labels):
|
def draw_fanin(self, node, connector):
|
||||||
"""
|
self.connectors.extend((
|
||||||
Return an SVG group containing a line element and text labels representing a Cable.
|
Line(start=node.bottom_center, end=connector.start, class_='cable-shadow'),
|
||||||
|
Line(start=node.bottom_center, end=connector.start, style=f'stroke: #{connector.color}'),
|
||||||
|
))
|
||||||
|
|
||||||
:param color: Cable (line) color
|
def draw_fanout(self, node, connector):
|
||||||
:param url: Hyperlink URL
|
self.connectors.extend((
|
||||||
:param labels: Iterable of text labels
|
Line(start=connector.end, end=node.top_center, class_='cable-shadow'),
|
||||||
"""
|
Line(start=connector.end, end=node.top_center, style=f'stroke: #{connector.color}')
|
||||||
group = Group(class_='connector')
|
))
|
||||||
|
|
||||||
# Draw a "shadow" line to give the cable a border
|
def draw_cable(self, cable):
|
||||||
start = (OFFSET + self.center, self.cursor)
|
labels = [
|
||||||
height = PADDING * 2 + LINE_HEIGHT * len(labels) + PADDING * 2
|
f'Cable {cable}',
|
||||||
end = (start[0], start[1] + height)
|
cable.get_status_display()
|
||||||
cable_shadow = Line(start=start, end=end, class_='cable-shadow')
|
]
|
||||||
group.add(cable_shadow)
|
if cable.type:
|
||||||
|
labels.append(cable.get_type_display())
|
||||||
|
if cable.length and cable.length_unit:
|
||||||
|
labels.append(f'{cable.length} {cable.get_length_unit_display()}')
|
||||||
|
connector = Connector(
|
||||||
|
start=(self.center + OFFSET, self.cursor),
|
||||||
|
color=cable.color or '000000',
|
||||||
|
url=f'{self.base_url}{cable.get_absolute_url()}',
|
||||||
|
labels=labels
|
||||||
|
)
|
||||||
|
|
||||||
# Draw the cable
|
self.cursor += connector.height
|
||||||
cable = Line(start=start, end=end, style=f'stroke: #{color}')
|
|
||||||
group.add(cable)
|
|
||||||
|
|
||||||
self.cursor += PADDING * 2
|
return connector
|
||||||
|
|
||||||
# Add link
|
def draw_wirelesslink(self, wirelesslink):
|
||||||
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_wirelesslink(self, url, labels):
|
|
||||||
"""
|
"""
|
||||||
Draw a line with labels representing a WirelessLink.
|
Draw a line with labels representing a WirelessLink.
|
||||||
|
|
||||||
:param url: Hyperlink URL
|
|
||||||
:param labels: Iterable of text labels
|
|
||||||
"""
|
"""
|
||||||
group = Group(class_='connector')
|
group = Group(class_='connector')
|
||||||
|
|
||||||
|
labels = [
|
||||||
|
f'Wireless link {wirelesslink}',
|
||||||
|
wirelesslink.get_status_display()
|
||||||
|
]
|
||||||
|
if wirelesslink.ssid:
|
||||||
|
labels.append(wirelesslink.ssid)
|
||||||
|
|
||||||
# Draw the wireless link
|
# Draw the wireless link
|
||||||
start = (OFFSET + self.center, self.cursor)
|
start = (OFFSET + self.center, self.cursor)
|
||||||
height = PADDING * 2 + LINE_HEIGHT * len(labels) + PADDING * 2
|
height = PADDING * 2 + LINE_HEIGHT * len(labels) + PADDING * 2
|
||||||
@ -226,7 +267,7 @@ class CableTraceSVG:
|
|||||||
self.cursor += PADDING * 2
|
self.cursor += PADDING * 2
|
||||||
|
|
||||||
# Add link
|
# Add link
|
||||||
link = Hyperlink(href=f'{self.base_url}{url}', target='_blank')
|
link = Hyperlink(href=f'{self.base_url}{wirelesslink.get_absolute_url()}', target='_blank')
|
||||||
|
|
||||||
# Add text label(s)
|
# Add text label(s)
|
||||||
for i, label in enumerate(labels):
|
for i, label in enumerate(labels):
|
||||||
@ -267,52 +308,47 @@ class CableTraceSVG:
|
|||||||
|
|
||||||
# Iterate through each (terms, cable, terms) segment in the path
|
# Iterate through each (terms, cable, terms) segment in the path
|
||||||
for i, segment in enumerate(traced_path):
|
for i, segment in enumerate(traced_path):
|
||||||
near_ends, connector, far_ends = segment
|
near_ends, link, far_ends = segment
|
||||||
|
|
||||||
# Near end parent
|
# Near end parent
|
||||||
if i == 0:
|
if i == 0:
|
||||||
# If this is the first segment, draw the originating termination's parent object
|
# If this is the first segment, draw the originating termination's parent object
|
||||||
self.draw_parent_object(near_ends[0].parent_object)
|
self.draw_parent_object(near_ends[0].parent_object)
|
||||||
|
|
||||||
# Near end termination
|
# Near end termination(s)
|
||||||
self.draw_terminations(near_ends)
|
terminations = self.draw_terminations(near_ends)
|
||||||
|
|
||||||
# Connector (a Cable or WirelessLink)
|
# Connector (a Cable or WirelessLink)
|
||||||
connector = connector[0] # Remove Cable from list
|
link = link[0] # Remove Cable from list
|
||||||
if connector is not None:
|
if link is not None:
|
||||||
|
|
||||||
# Cable
|
# Cable
|
||||||
if type(connector) is Cable:
|
if type(link) is Cable:
|
||||||
connector_labels = [
|
|
||||||
f'Cable {connector}',
|
# Account for fan-ins height
|
||||||
connector.get_status_display()
|
if len(near_ends) > 1:
|
||||||
]
|
self.cursor += FANOUT_HEIGHT
|
||||||
if connector.type:
|
|
||||||
connector_labels.append(connector.get_type_display())
|
cable = self.draw_cable(link)
|
||||||
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
|
|
||||||
)
|
|
||||||
self.connectors.append(cable)
|
self.connectors.append(cable)
|
||||||
|
|
||||||
|
# Draw fan-ins
|
||||||
|
if len(near_ends) > 1:
|
||||||
|
for term in terminations:
|
||||||
|
self.draw_fanin(term, cable)
|
||||||
|
|
||||||
# WirelessLink
|
# WirelessLink
|
||||||
elif type(connector) is WirelessLink:
|
elif type(link) is WirelessLink:
|
||||||
connector_labels = [
|
wirelesslink = self.draw_wirelesslink(link)
|
||||||
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
|
|
||||||
)
|
|
||||||
self.connectors.append(wirelesslink)
|
self.connectors.append(wirelesslink)
|
||||||
|
|
||||||
# Far end termination
|
# Far end termination(s)
|
||||||
|
if len(far_ends) > 1:
|
||||||
|
self.cursor += FANOUT_HEIGHT
|
||||||
|
terminations = self.draw_terminations(far_ends)
|
||||||
|
for term in terminations:
|
||||||
|
self.draw_fanout(term, cable)
|
||||||
|
else:
|
||||||
self.draw_terminations(far_ends)
|
self.draw_terminations(far_ends)
|
||||||
|
|
||||||
# Far end parent
|
# Far end parent
|
||||||
|
Loading…
Reference in New Issue
Block a user