fix: target the correct classes for images-only/labels-only export
Decompiled NetBox core's own bundled JS (netbox.js) to find the real toggle logic: its "images-only"/"labels-only" control only ever hides image.device-image and text.device-image-label - it never touches the plain text.label, relying on an opaque device image to visually cover it. A device without a photo therefore still shows its name live, even in "images-only". apply_rack_view_mode() previously removed .label for images-only and .device-image/.device-image-label for labels-only, which not only targeted the wrong element for images-only but also left device- image-label (the name overlaid on the image) around in labels-only. For a static, non-interactive export there's no image left to cover stray text, so per explicit request this deliberately does not mirror NetBox's live fallback: images-only now always removes every label (.label and .device-image-label) for a pure image view, and labels-only removes the image layer (.device-image and .device-image-label) to reproduce NetBox's own box-with-text view. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
from netbox.plugins import PluginConfig, get_plugin_config
|
from netbox.plugins import PluginConfig, get_plugin_config
|
||||||
|
|
||||||
__version__ = "0.12.0"
|
__version__ = "0.12.1"
|
||||||
|
|
||||||
|
|
||||||
class NetBoxUtilitiesConfig(PluginConfig):
|
class NetBoxUtilitiesConfig(PluginConfig):
|
||||||
|
|||||||
@@ -11,24 +11,30 @@ _IMAGE_TAGS = (f"{{{SVG_NS}}}image", "image")
|
|||||||
_HREF_ATTRS = ("href", f"{{{XLINK_NS}}}href")
|
_HREF_ATTRS = ("href", f"{{{XLINK_NS}}}href")
|
||||||
|
|
||||||
RACK_VIEW_MODES = {"images-and-labels", "images-only", "labels-only"}
|
RACK_VIEW_MODES = {"images-and-labels", "images-only", "labels-only"}
|
||||||
_LABEL_CLASSES = {"label"}
|
_PLAIN_LABEL_CLASS = "label"
|
||||||
_IMAGE_CLASSES = {"device-image", "device-image-label"}
|
_IMAGE_CLASS = "device-image"
|
||||||
|
_IMAGE_LABEL_CLASS = "device-image-label"
|
||||||
|
|
||||||
|
|
||||||
def apply_rack_view_mode(svg_source, mode):
|
def apply_rack_view_mode(svg_source, mode):
|
||||||
"""Mirror the rack elevation page's "Bilder"/"Beschriftungen" toggle server-side.
|
"""Filter the rack elevation SVG down to images-only or labels-only content.
|
||||||
|
|
||||||
That toggle only hides elements client-side via CSS after the browser
|
NetBox's own "Bilder"/"Beschriftungen" toggle never actually removes the
|
||||||
already has the full SVG, so a freshly server-rendered export needs its
|
plain .label text: it just leaves an opaque .device-image sitting on top
|
||||||
own equivalent to honor the mode the user had selected.
|
of it, so a device without a photo still shows its name in "images-only"
|
||||||
|
live. A downloaded, static export has no such fallback text to hide
|
||||||
|
behind an image, so here "images-only" removes every label outright
|
||||||
|
(.label and .device-image-label) to always produce a pure image view,
|
||||||
|
and "labels-only" removes the image layer to reproduce the same
|
||||||
|
box-with-text view NetBox itself already shows for that mode.
|
||||||
"""
|
"""
|
||||||
if mode not in RACK_VIEW_MODES or mode == "images-and-labels":
|
if mode not in RACK_VIEW_MODES or mode == "images-and-labels":
|
||||||
return svg_source
|
return svg_source
|
||||||
root = ET.fromstring(svg_source)
|
root = ET.fromstring(svg_source)
|
||||||
if mode == "images-only":
|
if mode == "images-only":
|
||||||
_remove_elements_with_class(root, _LABEL_CLASSES)
|
_remove_elements_with_class(root, {_PLAIN_LABEL_CLASS, _IMAGE_LABEL_CLASS})
|
||||||
elif mode == "labels-only":
|
elif mode == "labels-only":
|
||||||
_remove_elements_with_class(root, _IMAGE_CLASSES)
|
_remove_elements_with_class(root, {_IMAGE_CLASS, _IMAGE_LABEL_CLASS})
|
||||||
return ET.tostring(root, encoding="unicode")
|
return ET.tostring(root, encoding="unicode")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -105,14 +105,14 @@ class ApplyRackViewModeTest(SimpleTestCase):
|
|||||||
def test_images_and_labels_leaves_svg_untouched(self):
|
def test_images_and_labels_leaves_svg_untouched(self):
|
||||||
self.assertEqual(apply_rack_view_mode(self.SVG, "images-and-labels"), self.SVG)
|
self.assertEqual(apply_rack_view_mode(self.SVG, "images-and-labels"), self.SVG)
|
||||||
|
|
||||||
def test_images_only_removes_label_text(self):
|
def test_images_only_keeps_only_the_image(self):
|
||||||
result = apply_rack_view_mode(self.SVG, "images-only")
|
result = apply_rack_view_mode(self.SVG, "images-only")
|
||||||
|
|
||||||
self.assertNotIn('class="label"', result)
|
self.assertNotIn('class="label"', result)
|
||||||
|
self.assertNotIn("device-image-label", result)
|
||||||
self.assertIn('class="device-image"', result)
|
self.assertIn('class="device-image"', result)
|
||||||
self.assertIn('class="device-image-label"', result)
|
|
||||||
|
|
||||||
def test_labels_only_removes_device_images(self):
|
def test_labels_only_keeps_only_the_plain_label(self):
|
||||||
result = apply_rack_view_mode(self.SVG, "labels-only")
|
result = apply_rack_view_mode(self.SVG, "labels-only")
|
||||||
|
|
||||||
self.assertIn('class="label"', result)
|
self.assertIn('class="label"', result)
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "netbox-utilities"
|
name = "netbox-utilities"
|
||||||
version = "0.12.0"
|
version = "0.12.1"
|
||||||
description = "Navigation, tenant utilities, connection VLANs, partial-width racks, and bulk operations for NetBox 4.6/4.7"
|
description = "Navigation, tenant utilities, connection VLANs, partial-width racks, and bulk operations for NetBox 4.6/4.7"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
|
|||||||
Reference in New Issue
Block a user