ui: place offline SVG button directly beside "SVG herunterladen"

NetBox renders the native download link inside each per-face
elevation card, not through a plugin buttons() extension point, so
there's no server-side hook to inject a matching link next to it. The
right_page() extension now emits only a script (no separate sidebar
panel) that finds every a[href*="render=svg"] link, reads its face=
param, and inserts a matching "SVG offline" button right after it. The
button's href stays in sync with the rack-view select the same way
the previous panel's links did.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-17 16:13:40 +02:00
co-authored by Claude Sonnet 5
parent 3cff66ad33
commit ca1e38a1da
4 changed files with 45 additions and 29 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
from netbox.plugins import PluginConfig, get_plugin_config
__version__ = "0.12.1"
__version__ = "0.12.2"
class NetBoxUtilitiesConfig(PluginConfig):
+4
View File
@@ -156,6 +156,10 @@ class RackUtilitiesContent(PluginTemplateExtension):
models = ["dcim.rack"]
def right_page(self):
# Injects a script (no visible panel here) which places an "SVG
# offline" button right next to each native "SVG herunterladen"
# link, since NetBox renders those inside the per-face elevation
# cards rather than through a plugin buttons() extension point.
if not offline_svg_export_enabled():
return ""
rack = self.context["object"]
@@ -1,37 +1,49 @@
<div class="card">
<h2 class="card-header">SVG offline exportieren</h2>
<div class="card-body d-flex flex-column gap-2">
<a href="{% url 'plugins:netbox_utilities:rack_elevation_offline_svg' pk=rack.pk %}?face=front"
class="btn btn-outline-secondary netbox-utilities-offline-svg-link" data-face="front"
title="Vorderseite als linkfreie SVG-Datei herunterladen">
<i class="mdi mdi-download" aria-hidden="true"></i>
Vorderseite
</a>
<a href="{% url 'plugins:netbox_utilities:rack_elevation_offline_svg' pk=rack.pk %}?face=rear"
class="btn btn-outline-secondary netbox-utilities-offline-svg-link" data-face="rear"
title="Rückseite als linkfreie SVG-Datei herunterladen">
<i class="mdi mdi-download" aria-hidden="true"></i>
Rückseite
</a>
</div>
</div>
<script>
(function () {
var links = document.querySelectorAll(".netbox-utilities-offline-svg-link");
var offlineBaseUrl = "{% url 'plugins:netbox_utilities:rack_elevation_offline_svg' pk=rack.pk %}";
var viewSelect = document.querySelector("select.rack-view");
if (!links.length || !viewSelect) {
return;
function currentView() {
return viewSelect ? viewSelect.value : "images-and-labels";
}
function syncLinks() {
links.forEach(function (link) {
var url = new URL(link.href, window.location.origin);
url.searchParams.set("view", viewSelect.value);
link.href = url.toString();
function buildHref(face) {
var url = new URL(offlineBaseUrl, window.location.origin);
url.searchParams.set("face", face);
url.searchParams.set("view", currentView());
return url.toString();
}
function insertOfflineButtons() {
document.querySelectorAll('a[href*="render=svg"]').forEach(function (nativeLink) {
if (nativeLink.dataset.netboxUtilitiesOfflineInserted) {
return;
}
nativeLink.dataset.netboxUtilitiesOfflineInserted = "true";
var nativeUrl = new URL(nativeLink.href, window.location.origin);
var face = nativeUrl.searchParams.get("face") || "front";
var offlineLink = document.createElement("a");
offlineLink.className = nativeLink.className + " netbox-utilities-offline-svg-link";
offlineLink.title = "Wie \"SVG herunterladen\", aber ohne interne Links – funktioniert offline";
offlineLink.href = buildHref(face);
offlineLink.innerHTML = '<i class="mdi mdi-download" aria-hidden="true"></i> SVG offline';
nativeLink.insertAdjacentElement("afterend", offlineLink);
});
}
viewSelect.addEventListener("change", syncLinks);
syncLinks();
insertOfflineButtons();
if (viewSelect) {
viewSelect.addEventListener("change", function () {
document.querySelectorAll(".netbox-utilities-offline-svg-link").forEach(function (link) {
var url = new URL(link.href, window.location.origin);
url.searchParams.set("view", currentView());
link.href = url.toString();
});
});
}
})();
</script>
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "netbox-utilities"
version = "0.12.1"
version = "0.12.2"
description = "Navigation, tenant utilities, connection VLANs, partial-width racks, and bulk operations for NetBox 4.6/4.7"
readme = "README.md"
requires-python = ">=3.12"