Commit Graph
5 Commits
Author SHA1 Message Date
MrBlakeandClaude Sonnet 5 67d999e234 v0.2.1: rework PNG export to draw directly on canvas, like netbox-topology-views
Investigated how MrBlake/mrb-netbox-topology-views does its PNG export:
it renders its topology natively onto a canvas (vis-network draws
there directly) and just calls canvas.toDataURL() - no SVG round-trip
at all. Applied the same principle here.

Previous approach loaded the standalone SVG export via <img src="blob:...">
and rasterized that onto a canvas - fragile, because the whole export
failed if that SVG document wasn't strictly valid XML (as our leaked
{# #} comment bug demonstrated) or if a single device type image
reference inside it couldn't be resolved.

New approach: a dedicated export/data/ endpoint returns the elevation's
plain pixel geometry as JSON (positions, sizes, colors, labels, image
URLs - the same numbers the SVG is built from), and export.js draws
that directly onto a <canvas> with native 2D primitives (fillRect,
strokeRect, fillText/strokeText, drawImage). Device type images are
fetched individually as same-origin blobs before drawing; one image
failing to load only drops that picture; the rest of the export still
succeeds. Added a combined front+rear PNG option.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 12:53:42 +02:00
MrBlakeandClaude Sonnet 5 3420d84803 v0.2.0: fix leaked multi-line {# #} comments, add German translation
- Fix template comments rendering as literal text: Django's {# #} tag
  regex (tag_re in django/template/base.py) has no re.DOTALL, so a
  comment spanning multiple lines is never recognized as a tag at all
  and leaks into the rendered output verbatim - exactly what showed up
  under "Elevation". Collapsed all three offending comments to single
  lines. This was also almost certainly the real cause of the "PNG
  could not be generated" report: two of the leaked comments sat
  inside the shared SVG partial, corrupting the exported SVG enough
  that the browser's image decoder rejected it (my own onerror handler
  then showed a misleading CORS-sounding message - the actual bug was
  malformed markup, not cross-origin images).
- Add a German translation (locale/de/), built from every _() and
  {% trans %}/{% blocktrans %} string in the plugin (269 entries,
  cross-checked against the source for completeness). English needs no
  catalog of its own since it's the literal source text; both were
  already listed in NetBox's own language selector.
- Add CHANGELOG.md and bump the version to 0.2.0, covering this and
  every unreleased change so far; will keep bumping per change from
  here on.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 12:45:01 +02:00
MrBlakeandClaude Sonnet 5 b289bc5aeb Mirror device cabling 1:1 (per-type tabs) and copy cables on rack import
- Split the device detail page's single "Components" table into the
  same seven tabs a real dcim.Device has, in the same order: Console
  Ports, Console Server Ports, Power Ports, Power Outlets, Interfaces,
  Front Ports, Rear Ports. Each tab is its own ObjectChildrenView
  filtered by component_type (one ConceptComponent model backs all of
  them, so this only needed the view/URL layer, not a schema change),
  hidden when empty, exactly like core NetBox. Replace the ad hoc "Add
  component" button with the same "Add Components" dropdown real
  device pages use, one entry per type, each landing back on its own
  tab.
- When copying a real rack into a concept (or merging two real racks
  into one), automatically sync every copied device's components and
  recreate, as ConceptCables, every real cable whose both ends land on
  one of those devices - reading the same CableTermination data dcim
  itself uses, so the copy comes out cabled internally the same as the
  source. Cables reaching outside the copied rack(s), or multi-
  termination trunk cables, are skipped and reported as a count in the
  success message - the other end isn't part of the concept, so it
  can't be represented. copy_rack_to_concept() and
  merge_racks_to_concept() now return an extra cable_summary value;
  updated all their callers (views and API).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 10:34:26 +02:00
MrBlakeandClaude Sonnet 5 66a9174859 Fix ModuleNotFoundError on plugin load: use relative resource paths
PluginConfig._load_resource() prepends self.__module__ (already
'netbox_rack_concept') to the configured path, so 'menu' and
'template_extensions' must be given relative to the plugin package
('navigation.menu', 'template_content.template_extensions'), not
prefixed with 'netbox_rack_concept.' again. The prefixed form made
Django try to import 'netbox_rack_concept.netbox_rack_concept...',
which doesn't exist, crashing manage.py on every command.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 09:12:28 +02:00
MrBlakeandClaude Sonnet 5 17cf94ac0d Add netbox_rack_concept plugin: rack blueprint planning
Introduces a NetBox 4.6 plugin for planning rack layouts (RackConcept,
ConceptDevice) without creating real dcim.Rack/dcim.Device objects, so
blueprints never appear in rack/device lists, utilization reports, or
IPAM.

Features:
- Copy paths: real rack -> concept, concept -> concept (clone), and
  concept -> one or more real racks (deploy, with name patterning and
  dry-run support)
- Manual concept creation via a dedicated "Rack Concepts" nav menu
- Tenant and tenant group assignment on concepts and planned devices
- Partial-width device placement (1/1-1/4) with slot-based collision
  detection, matching netbox_utilities' partial-width feature
- Custom drag-and-drop elevation (netbox-reorder-rack operates on real
  Device querysets, which concepts don't have) with server-side
  validation of every move
- Full REST API, filtering, CSV import, bulk edit, search integration
- Optional, config-driven interop with netbox_utilities (partial-width
  field mapping) and netbox-reorder-rack, degrading gracefully when
  either plugin is absent

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 09:00:26 +02:00