{% load i18n %} {% load static %} {% load rack_design %} {# ------------------------------------------------------------------------- #} {# Shared per-rack block: one rack rendered with BOTH its Front and Rear #} {# faces side by side, full-depth opposite-face hatch and the hover-card #} {# data-* attributes. Used by BOTH the interactive editor #} {# (design_editor.html, editable=True) and the read-only projected elevation #} {# (design_elevation.html, editable=False) so the two can never drift apart. #} {# #} {# Context: #} {# b the per-rack bundle from views._project_rack_bundle: #} {# {rack, front, rear, non_racked, widgets, rack_meta, hidden?} #} {# editable True -> editor affordances (drag grids, per-tile × remove, #} {# embedded widget JSON payload for editor.js). #} {# False -> static, read-only (locked tiles, no remove buttons, #} {# no JSON payload). View-only Front/Rear toggles + the #} {# hover card still work. #} {# #} {# Widget indices (editor only): the page builds `widgets` in the order #} {# (*front, *rear, *non_racked); each tile is stamped with its GLOBAL index #} {# via gs-id/data-widget-index so editor.js can pair JSON to DOM. #} {# ------------------------------------------------------------------------- #} {% with rack=b.rack front=b.front rear=b.rear non_racked=b.non_racked %} {% with front_len=front|length rear_len=rear|length %} {% with rid=rack.pk|stringformat:"s" %}
{# ---- Per-rack header: name + site/location + face toggles ---- #}
{{ rack.name }} {% if rack.location %}· {{ rack.location }} {% elif rack.site %}· {{ rack.site }}{% endif %}
{# Independent on/off toggles per rack: each shows/hides its OWN face. #} {# Both start active; the JS refuses to hide the last visible face. #}
{# Per-rack power planning input (docs/pdu-distribution-spec.md §6.3): the #} {# greenfield planned-power flow (define planned feeds + power_limitation/ #} {# pdu_location override, models.DesignRackPower) -- GATED to racks with #} {# NO real PowerFeeds yet. A provisioned rack's PDUs bind straight to its #} {# real feeds via the per-PDU bind-to-feed dialog instead. Editor only. #} {% if editable and not b.has_real_feeds %} {% endif %}
{# ---- Per-rack power bar (docs/power-projection-spec.md): projected draw #} {# vs capacity, always visible in normal mode. ok/warn/critical color. #} {% if b.power %}
{{ b.power.draw_w|floatformat:0 }} / {{ b.power.capacity_w|floatformat:0 }} W · {{ b.power.util_pct|floatformat:0 }}%{% if b.power.unconnected_count %} ⚠ {{ b.power.unconnected_count }}{% endif %}
{% endif %} {# ---- Per-bank power distribution (docs/pdu-distribution-spec.md): present #} {# only in distribution_mode="script". power_heatmap.js reads it to color #} {# PDU banks and tint tiles by feed/bank when the heatmap is on. #} {% if b.power.distribution %}{% with did="rd-distribution-"|add:rid %}{{ b.power.distribution|json_script:did }}{% endwith %}{% endif %} {# ---- Rack faces: Front + Rear side by side (independent toggles). ---- #}
{# ---- Front face ---- #}

{% trans "Front" %}

    {% for u in rack.units %}
  • {% if u|rack_whole_unit %}{{ u|floatformat:"0" }}{% endif %}
  • {% endfor %}
{# Positioning anchor for the displacement stripe bar (spec §3, user #} {# ruling 2026-07-09): the bar hangs OUTSIDE the grid's right edge, #} {# absolutely positioned against this wrapper. Created by editor.js #} {# only; the read-only elevation never renders one. #}
{% for slot in front %} {# A DISPLACED vacating slot (spec §3, ruling 2026-07-09) renders #} {# as the OUTSIDE stripe bar (after the grid, below), never a full #} {# tile composited under the occupant. Editable mode keeps the #} {# tile: editor.js collapses it and owns the bar (displaceOne). #} {% if editable or not slot.displaced %}
{% if editable and not slot.opposite_face %}{% endif %} {% if slot.display_label != slot.label %}{{ slot.label }}{{ slot.display_label }}{% else %}{{ slot.label }}{% endif %}
{% endif %} {% endfor %}
{# Read-only displaced treatment (spec §3, ruling 2026-07-09): the #} {# outside stripe bar per displaced slot -- same geometry/CSS as the #} {# editor's makeStripeBar. Editable mode: editor.js owns the bars. #} {% if not editable %}{% for slot in front %}{% if slot.displaced %}
{% endif %}{% endfor %}{% endif %}
{# /.nbx-rd-grid-wrap #}
{# ---- Rear face ---- #}

{% trans "Rear" %}

    {% for u in rack.units %}
  • {% if u|rack_whole_unit %}{{ u|floatformat:"0" }}{% endif %}
  • {% endfor %}
{# Stripe-bar anchor, same as the front face above. #}
{% for slot in rear %} {% if editable or not slot.displaced %}
{% if editable and not slot.opposite_face %}{% endif %} {% if slot.display_label != slot.label %}{{ slot.label }}{{ slot.display_label }}{% else %}{{ slot.label }}{% endif %}
{% endif %} {% endfor %}
{# Read-only displaced stripe bars, same as the front face above. #} {% if not editable %}{% for slot in rear %}{% if slot.displaced %}
{% endif %}{% endfor %}{% endif %}
{# /.nbx-rd-grid-wrap #}
{# /.nbx-rd-faces #} {# ---- Per-rack non-racked tray ---- #} {# Editor: always shown as a drop target. Read-only: shown only when there #} {# are non-racked placements (no empty drop affordance in view mode). #} {% if editable or non_racked %}

{% trans "Non-racked tray" %}

{% for slot in non_racked %} {# The tray is a LIST (spec §9.2/§9.4), not a grid with meaningful #} {# rows: every item gets its own sequential row (index * 2, since #} {# every tray tile is a fixed gs-h="2") so items never overlap and #} {# the container grows to fit -- server-render order first, session #} {# drops append after (see editor.js's tray "dropped" handler). #}
{% if editable %}{% endif %} {% if slot.display_label != slot.label %}{{ slot.label }}{{ slot.display_label }}{% else %}{{ slot.label }}{% endif %}
{% endfor %}
{% endif %} {# Per-rack editor payload (editor only): one widget per projected slot, #} {# keyed by the rack pk so editor.js can hydrate each block independently. #} {% if editable %}{% with eid="rd-editor-data-"|add:rid %}{{ b.widgets|json_script:eid }}{% endwith %}{% endif %} {# Saved per-(design, rack) power planning override (docs/pdu-distribution- #} {# spec.md): lets the rack-power button (editor.js) pre-fill without an #} {# extra fetch. null when nothing has been saved for this rack yet. #} {% if editable %}{% with rpid="rd-rackpower-"|add:rid %}{{ b.rack_power|json_script:rpid }}{% endwith %}{% endif %}
{# /.nbx-rd-rack-block #} {% endwith %}{% endwith %}{% endwith %}