{% extends 'generic/object.html' %} {% load static %} {% load i18n %} {% load rack_design %} {# Interactive single-rack layout editor (Stage 2, slice 2a: MOVE + REMOVE). #} {# Forks design_elevation.html but initialises GridStack WITHOUT staticGrid so #} {# slots can be dragged vertically (and between faces / into the non-racked #} {# tray). editor.js hydrates from #rd-editor-data, tracks each widget's original #} {# (u_position, face), and POSTs a diff to data-save-url. NO model changes. #} {# #} {# Widget indices: views.py builds `widgets` (and widgets_json) in the order #} {# (*front, *rear, *non_racked). Each grid-stack-item below is stamped with the #} {# matching GLOBAL index via `gs-id`/data-widget-index so editor.js can pair the #} {# JSON payload to its DOM tile: front uses forloop.counter0; rear adds the #} {# front length; the tray adds front+rear lengths. #} {% block head %} {{ block.super }} {% endblock head %} {% block content %}

{% blocktrans with rack=rack.name design=object.title %}Edit layout: {{ rack }} ({{ design }}){% endblocktrans %}
{# Independent on/off toggles: each shows/hides its OWN face, so BOTH #} {# faces can be visible side by side. Both start active (visible); #} {# editor.js refuses to hide the last visible face. #}

{# The editor root: carries the save URL + rack meta the JS needs. #}
{% with front_len=front|length rear_len=rear|length %} {# Legend — also acts as a per-state filter (see legend_filter.js). #}
{# ---- Left rail: a vertical stack of compact NetBox cards. The catalog #} {# card sits here; below it a Device-role and a Tenant selector card #} {# supply the role/tenant applied to NEW adds at drop time. ---- #}
{# ---- Catalog palette (drag a device type onto a face to plan an add) ---- #}

{% trans "Device-type catalog" %}

{# NetBox API-backed searchable select; the editor JS reads its #} {# value (#id_manufacturer) to filter the CATALOG search only. #}
{{ palette_form.manufacturer }}
{# Only this list scrolls; the controls above stay pinned. #}
{# ---- Device role applied to NEW adds (captured at drop time) ---- #}

{% trans "Device role" %}

{# API-backed searchable select; value (#id_device_role) is #} {# applied to NEW adds at drop time. #} {{ palette_form.device_role }}
{% trans "Applied to devices you drag in next." %}
{# ---- Tenant applied to NEW adds (captured at drop time) ---- #}

{% trans "Tenant" %}

{# API-backed searchable select; value (#id_tenant) is applied #} {# to NEW adds at drop time. #} {{ palette_form.tenant }}
{% trans "Applied to devices you drag in next." %}
{# ---- Quick access: its OWN full-height column (not in the left rail) ---- #} {# Rack-tall card; favorites list scrolls internally. As a separate column #} {# it never shrinks the catalog. Independent of the catalog search + #} {# manufacturer filter; drag a row onto a face to plan an add, click its #} {# filled star to unpin. ---- #}

{% trans "Quick access" %}

{# This list scrolls if many favorites; populated by editor.js. #}
{# ---- Rack faces: Front + Rear side by side (independent toggles). #} {# Grouped in one flex wrapper so they always sit beside each #} {# other and wrap together as a unit on narrow viewports, rather #} {# than one face wrapping under the left-rail columns. ---- #}
{# ---- Front face ---- #}

{% trans "Front" %}

    {% for u in rack.units %}
  • {% if u|rack_whole_unit %}{{ u|floatformat:"0" }}{% endif %}
  • {% endfor %}
{% for slot in front %}
{% if not slot.opposite_face %}{% endif %} {{ slot.label }}
{% endfor %}
{{ rack.name }} {% if rack.tenant %}· {{ rack.tenant }}{% endif %}
{# ---- Rear face ---- #}

{% trans "Rear" %}

    {% for u in rack.units %}
  • {% if u|rack_whole_unit %}{{ u|floatformat:"0" }}{% endif %}
  • {% endfor %}
{% for slot in rear %}
{% if not slot.opposite_face %}{% endif %} {{ slot.label }}
{% endfor %}
{{ rack.name }} {% if rack.tenant %}· {{ rack.tenant }}{% endif %}
{# /.nbx-rd-faces #}
{# ---- Non-racked tray (drop zone) ---- #}

{% trans "Non-racked tray" %}

{% trans "Drag a device here to move it off the rack, or use the × on a tile to flag it for removal." %}

{% for slot in non_racked %}
{{ slot.label }}
{% endfor %}
{% endwith %} {# Editor payload for the JS: one widget per projected slot (see views._slot_to_widget). #} {# Use Django's json_script so the JSON is safely escaped for a {% endblock javascript %}