{# SPDX-License-Identifier: Apache-2.0 #} {# Copyright (C) 2025 Marcin Zieba #} {% extends 'base/layout.html' %} {% block title %}Import Preview{% endblock %} {% block head %} {% endblock %} {% block content %}
{# Header #}
Import Preview — Step 2 of 3
{{ filename }}
{# Summary bar #}
{% with c=result.counts %} {{ c.racks_created|default:0 }} rack{{ c.racks_created|default:0|pluralize }} to create {{ c.devices_created|default:0 }} device{{ c.devices_created|default:0|pluralize }} to create {% if c.racks_updated or c.devices_updated %} {% with racks_updated=c.racks_updated|default:0 devices_updated=c.devices_updated|default:0 %} {{ racks_updated|add:devices_updated }} to update {% endwith %} {% endif %} {% if c.skipped %} {{ c.skipped }} skipped {% endif %} {% if c.ignored %} {{ c.ignored }} ignored {% endif %} {% if c.errors %} {{ c.errors }} error{{ c.errors|pluralize }} {% endif %} {% endwith %}
{# Action buttons #}
Back {% if view_mode == 'racks' %} Row view {% else %} Rack view {% endif %} {% if profile_id %} {# Auto-match existing devices #}
{% csrf_token %}
{% endif %} {% if not result.has_errors %}
{% csrf_token %}
{% else %} {% endif %}
{% if result.counts.errors %}
{{ result.counts.errors }} row{{ result.counts.errors|pluralize }} need attention. Use the buttons in the last column to add mappings, link existing devices, or ignore rows. The preview refreshes automatically after each fix.
{% endif %} {% if unused_columns %} {# ── Unused Source Columns Panel ─────────────────────────────────── #}
Unused source columns {{ unused_columns|length }}

These columns have data in your file but are not mapped to any NetBox field. Click a badge to quickly create a mapping. Columns with no data are hidden.

{% for col in unused_columns %} {% endfor %}
{% endif %}
{% if view_mode == 'racks' %} {# Rack view — error rows not shown in any rack card (device_type, unnamed rack, etc.) #} {% if non_card_error_rows %}
Errors not shown in rack cards — resolve before import
{% for row in non_card_error_rows %} {% endfor %}
RowSource IDNameTypeDetail
{{ row.row_number }} {{ row.source_id|default:"—" }} {{ row.name|default:"—" }} {{ row.object_type }} {{ row.detail }} {% if row.object_type == 'device_type' and profile_id %} {% elif row.object_type == 'manufacturer' and profile_id %} {% endif %}
{% endif %} {# Rack view — cards #}
{% for rack_name, group in result.rack_groups.items %}
{{ rack_name }} {% if group.rack_row %} {% if group.rack_row.action == 'create' %}Create {% elif group.rack_row.action == 'update' %}Update {% elif group.rack_row.action == 'skip' %}Existing {% elif group.rack_row.action == 'error' %}Error {% endif %} {% else %} No rack row {% endif %} {{ group.devices|length }} device{{ group.devices|length|pluralize }}
{% if group.devices %}
    {% for dev in group.devices %}
  • {% if dev.action == 'create' %}+ {% elif dev.action == 'update' %}~ {% elif dev.action == 'skip' %} {% elif dev.action == 'error' %}! {% elif dev.action == 'ignore' %} {% endif %} {% if dev.extra_data.u_position %}U{{ dev.extra_data.u_position }}{% endif %} {{ dev.name }} {% if dev.source_id %}#{{ dev.source_id }}{% endif %} {% if dev.action != 'ignore' %} {% if dev.extra_data.source_make and dev.extra_data.source_model %} {{ dev.extra_data.source_make|truncatechars:12 }} / {{ dev.extra_data.source_model|truncatechars:16 }} {% else %} {{ dev.detail|truncatechars:40 }} {% endif %} {% endif %}
  • {% endfor %}
{% else %}

No devices

{% endif %}
{% empty %}

No racks found in preview.

{% endfor %}
{% else %} {# Per-row table (default) #}
Row-by-row preview ({{ result.rows|length }} row{{ result.rows|length|pluralize }})
{% for row in result.rows %} {% if row.action == 'update' and row.extra_data.field_diff and row.extra_data.netbox_device_id %} {% endif %} {% empty %} {% endfor %}
# Source ID Name Type Action Detail
{{ row.row_number }} {{ row.source_id|default:"—" }} {% if row.netbox_url %}{{ row.name|default:"—" }} {% else %}{{ row.name|default:"—" }}{% endif %} {# Show source make/model for device rows #} {% if row.object_type == 'device' and row.extra_data.source_make %}
{{ row.extra_data.source_make }} / {{ row.extra_data.source_model }}
{% if row.extra_data.is_explicit_mapping %}
{{ row.extra_data.mfg_slug }} / {{ row.extra_data.dt_slug }} {% if row.extra_data.dt_exists %} type exists {% else %} type missing {% endif %}
{% endif %} {% endif %}
{% if row.object_type == 'rack' %} Rack {% elif row.object_type == 'device' %} Device {% elif row.object_type == 'manufacturer' %} Manufacturer {% elif row.object_type == 'device_type' %} Device type {% else %} {{ row.object_type|default:"?" }} {% endif %} {% if row.action == 'create' %} Create {% elif row.action == 'update' %} Update {% elif row.action == 'skip' %} Skip {% elif row.action == 'error' %} Error {% elif row.action == 'ignore' %} Ignored {% endif %} {{ row.detail }} {% if row.action == 'error' and row.extra_data.conflict_row_number %} Jump to row {{ row.extra_data.conflict_row_number }} {% endif %} {% if row.action == 'update' and row.extra_data.field_diff %} {% with diff=row.extra_data.field_diff %}
{% endwith %} {% endif %}
{% if row.object_type == 'manufacturer' and row.action == 'create' and profile_id %} {# Manufacturer: create now OR map to existing #}
{% csrf_token %}
{% elif row.object_type == 'device_type' and row.action in 'create,error' and profile_id %} {# Device type: map to existing OR create now (both open same modal) #} {% elif row.action == 'error' and row.object_type == 'rack' and profile_id %} {# Rack error — offer ignore #}
{% csrf_token %}
{% elif row.object_type == 'rack' and profile_id and row.extra_data.source_class %} {# Rack row — allow reconfiguring the class mapping (e.g. to set rack type) #} {% elif row.action == 'error' and row.object_type == 'device' and profile_id %} {# Device error — show class mapping button if we have source_class #} {% if row.extra_data.source_class %} {% endif %} {# Link to existing device #} {# Ignore button #}
{% csrf_token %}
{% elif row.action == 'create' and row.object_type == 'device' and row.source_id and profile_id %} {# Device will be created — allow linking to existing device instead #} {# Ignore button #}
{% csrf_token %}
{% elif row.action == 'ignore' and row.object_type == 'device' and row.source_id and profile_id %} {% if row.extra_data.ignore_kind == 'individual' %} {# Individually ignored — offer unignore #}
{% csrf_token %}
{% else %} {# Class-level ignore — direct user to edit the mapping #} Class rule {% endif %} {% endif %} {# Split button: show on device rows where name contains ' - ' #} {% if row.object_type == 'device' and row.source_id and profile_id and ' - ' in row.name %} {% endif %} {# Map DT button: show on all device rows with source make/model (create, update, error) #} {% if row.object_type == 'device' and row.extra_data.source_make and profile_id %} {% endif %} {# Unlink from existing device #} {% if row.object_type == 'device' and row.source_id in device_match_source_ids and profile_id %}
{% csrf_token %}
{% endif %} {# Conflict badge: shown when two source columns disagree on the same target field #} {% if row.extra_data.conflicts %} {% endif %} {# Per-row sync button: show on create rows for devices and racks only; disabled when conflicts present #} {% if row.action == 'create' and row.object_type == 'device' or row.action == 'create' and row.object_type == 'rack' %} {% endif %}
{% for field_name, vals in row.extra_data.field_diff.items %} {% endfor %}
Field NetBox File
{{ field_name }} {{ vals.netbox|default:"—" }} {{ vals.file|default:"—" }} {% if field_name in syncable_fields %} {% else %} (manual) {% endif %}
No rows found in file.
{% endif %}
{# Improved split-name modal #} {# ── Conflict Resolution Modal ─────────────────────────────────────────── #} {{ existing_resolutions|json_script:"ndi-existing-resolutions" }} {{ device_match_info|json_script:"ndi-device-match-info" }} {{ conflicts_by_row|json_script:"ndi-conflicts-by-row" }} {{ extra_columns_by_row|json_script:"ndi-extra-columns-by-row" }} {{ split_field_values_by_source_id|json_script:"ndi-split-field-values" }} {# Device Type Mapping Modal #} {# Class Mapping Modal #} {# Device Match Modal #} {# Manufacturer Mapping Modal #} {% if unused_columns %} {# Quick Map Column Modal — panel is inline above; this modal is shared #} {% endif %} {# ── Quick Map Column Modal ──────────────────────────────────────────────── #} {# ===== Per-row sync confirmation modal ===== #} {% endblock %}