import logging import re from html import unescape from django.apps import apps from django.core.exceptions import ObjectDoesNotExist from django.db import OperationalError, ProgrammingError from django.templatetags.static import static from django.utils.html import escape, json_script from netbox.plugins import get_plugin_config from . import __version__ from .rack_width import effective_width_positions logger = logging.getLogger(__name__) TOPOLOGY_RACK_ELEVATION_VIEW = "plugins:netbox_topology_views:rack_elevation" RACK_DEVICE_TAG_RE = re.compile( r"]*\bclass=[\"'][^\"']*\brack-device\b[^\"']*[\"'][^>]*>", re.IGNORECASE, ) HREF_RE = re.compile(r"\bhref=[\"'](?P[^\"']+)[\"']", re.IGNORECASE) STYLE_RE = re.compile(r"\bstyle=(?P[\"'])(?P' payload = json_script(data, "netbox-utilities-topology-rack-width-data") script_url = escape(f"{static('netbox_utilities/topology-rack-width.js')}?v={__version__}") return f'{stylesheet}{payload}' def _apply_topology_widths_to_device_tags(html, data): descriptors = {device["url"]: device for device in data["devices"]} def replace_tag(match): tag = match.group(0) href_match = HREF_RE.search(tag) if href_match is None: return tag descriptor = descriptors.get(unescape(href_match.group("url"))) if descriptor is None: return tag geometry = ( "right:auto!important;" f"left:calc({descriptor['left_percent']}% + 3px)!important;" f"width:calc({descriptor['width_percent']}% - 6px)!important" ) style_match = STYLE_RE.search(tag) if style_match is not None: quote = style_match.group("quote") existing = style_match.group("style").rstrip().rstrip(";") replacement = f"style={quote}{existing};{geometry}{quote}" return f"{tag[: style_match.start()]}{replacement}{tag[style_match.end() :]}" return f'{tag[:-1]} style="{geometry}">' return RACK_DEVICE_TAG_RE.sub(replace_tag, html) def _inject_topology_widths(response): if ( getattr(response, "streaming", False) or getattr(response, "status_code", None) != 200 or "text/html" not in response.get("Content-Type", "") ): return response html = response.content.decode(response.charset or "utf-8") if "" not in html.lower() or "netbox-utilities-topology-rack-width-data" in html: return response visible_urls = _visible_topology_device_urls(html) data = _topology_data_for_visible_devices(visible_urls) html = _apply_topology_widths_to_device_tags(html, data) insertion = _topology_width_head(data) head_end = html.lower().index("") html = f"{html[:head_end]}{insertion}{html[head_end:]}" content = html.encode(response.charset or "utf-8") response.content = content if response.has_header("Content-Length"): response["Content-Length"] = str(len(content)) return response def apply_topology_rack_widths(request, response): """Apply widths after the optional view has rendered its authorized devices.""" if not topology_rack_width_enabled(request): return response try: return _inject_topology_widths(response) except Exception: logger.warning( "Could not inject partial rack widths into NetBox Topology Views path %s", request.get_full_path(), exc_info=True, ) return response