{% extends 'generic/object.html' %}
{% load helpers %}
{% load i18n %}
{% block title %}{{ object }} - {% trans "Drift Report" %}{% endblock %}
{% block extra_controls %}
{% if perms.forward_netbox.run_forwardsync %}
{% endif %}
{% trans "Sync" %}
{% endblock extra_controls %}
{% block content %}
{% if drift_stale %}
{% if drift_stale_newer_sync %}
{% trans "This drift is computed from a dependency preview, but a newer sync has run since โ the numbers below are stale." %}
{% else %}
{% trans "This drift is computed from a dependency preview that is over a day old โ the numbers below may be stale." %}
{% endif %}
{% trans "Preview:" %} {{ preview_at|placeholder }} ยท {% trans "Last sync:" %} {{ last_sync_at|placeholder }}.
{% trans "Re-run Preview Dependencies for current drift." %}
{% endif %}
{% if not report.comparison_available %}
{% trans "This dependency preview measures estimated apply workload, not object-level drift. In-sync status is not measured because fetched rows are change candidates, not confirmed mismatches." %}
{% endif %}
{% if latest_sync_evidence %}
{% if latest_sync_evidence.status == "confirmation_required" %}
{% trans "The latest completed sync applied changes, so convergence is not confirmed yet." %}
{% trans "Run this sync again against the same snapshot; a completed run with zero changes and zero failures confirms convergence." %}
{% elif latest_sync_evidence.status == "converged" %}
{% trans "Convergence confirmed: the latest completed sync applied zero changes with zero failures against the preview snapshot." %}
{% elif latest_sync_evidence.status == "failed" %}
{% trans "The latest sync recorded failures and cannot confirm convergence. Resolve the failures, then rerun the same snapshot." %}
{% elif latest_sync_evidence.status == "unsatisfiable_rows" %}
{% blocktrans with n=latest_sync_evidence.skipped %}The latest sync merged and promoted its baseline, but {{ n }} row(s) were refused by NetBox validation rules and skipped.{% endblocktrans %}
{% trans "Re-running cannot change a validation rejection, because the rule belongs to NetBox rather than the incoming data. The rows are recorded as ingestion issues; drift is measured, and convergence stays unconfirmed until they are resolved in NetBox." %}
{% elif latest_sync_evidence.status == "incomplete" %}
{% trans "The latest ingestion is not completed, merged, and baseline-ready, so its counters cannot confirm convergence." %}
{% elif latest_sync_evidence.status == "ownership_incomplete" %}
{% trans "The latest ingestion has not completed durable ownership reconciliation. Convergence remains blocked until the required post-merge ownership domains finish without conflicts." %}
{% elif latest_sync_evidence.status == "snapshot_mismatch" %}
{% trans "The latest completed sync had zero changes, but it used a different snapshot than this preview. Rerun the sync and preview against one snapshot before accepting convergence." %}
{% elif latest_sync_evidence.status == "snapshot_unknown" %}
{% trans "The latest completed sync had zero changes, but the snapshot match is unavailable. Rerun Preview Dependencies and the sync against one explicit snapshot." %}
{% endif %}
{% endif %}
{% if report.looks_like_full_create %}
{% blocktrans with n=report.full_create_model_count total=report.model_count drift=report.total_drift %}Every model with data ({{ n }} of {{ total }}) shows all Forward rows as pending with no removals. That is the signature of a preview taken against an empty or unmerged NetBox โ it is "everything Forward has," not {{ drift }} real mismatches.{% endblocktrans %}
{% trans "If this is a first sync it is expected; otherwise re-run Preview Dependencies after a completed, merged sync." %}
{% endif %}
| {% trans "In sync" %} |
{% if not report.comparison_available %}
{% trans "Not measured" %}
{% elif report.in_sync %}
{% trans "Yes" %}
{% else %}
{% trans "No" %}
{% endif %}
|
| {% trans "Drifted models" %} |
{% if report.comparison_available %}{{ report.drifted_model_count }} / {{ report.model_count }}{% else %}{% trans "Not measured" %}{% endif %} |
| {% trans "Total drift" %} |
{% if report.comparison_available %}{{ report.total_drift }}{% else %}{% trans "Not measured" %}{% endif %} |
| {% trans "Estimated apply work" %} |
{{ report.total_apply_work }} |
| {% trans "From preview" %} |
{{ report.generated_at|placeholder }} |
{% if latest_sync_evidence %}
| {% trans "Convergence" %} |
{% if latest_sync_evidence.convergence_confirmed %}
{% trans "Confirmed" %}
{% elif latest_sync_evidence.status == "failed" %}
{% trans "Failed" %}
{% else %}
{% trans "Not confirmed" %}
{% endif %}
|
| {% trans "Sync snapshot" %} |
{{ latest_sync_evidence.snapshot_id|placeholder }} |
| {% trans "Ownership" %} |
{% if latest_sync_evidence.ownership.complete %}
{% trans "Current" %}
{% else %}
{% trans "Incomplete" %}
{% endif %}
|
| {% trans "Same as preview" %} |
{% if not latest_sync_evidence.snapshot_comparison_available %}
{% trans "Unknown" %}
{% elif latest_sync_evidence.same_snapshot %}
{% trans "Yes" %}
{% else %}
{% trans "No" %}
{% endif %}
|
| {% trans "Applied" %} | {{ latest_sync_evidence.applied }} |
| {% trans "Created" %} | {{ latest_sync_evidence.created }} |
| {% trans "Updated" %} | {{ latest_sync_evidence.updated }} |
| {% trans "Deleted" %} | {{ latest_sync_evidence.deleted }} |
| {% trans "Failed" %} | {{ latest_sync_evidence.failed }} |
| {% trans "Skipped (rule rejections)" %} | {{ latest_sync_evidence.skipped }} |
| {% trans "Completed" %} | {{ latest_sync_evidence.completed_at|placeholder }} |
{% endif %}
| {% trans "Model" %} |
{% trans "Forward rows" %} |
{% trans "Upsert/change candidates" %} |
{% trans "Pending removals" %} |
{% trans "Estimated apply work" %} |
{% trans "Drift" %} |
{% trans "In sync" %} |
{% for row in report.models %}
{{ row.model }} |
{{ row.forward_rows|placeholder }} |
{{ row.pending_changes }} |
{{ row.pending_removes }} |
{{ row.estimated_apply_work }} |
{% if row.comparison_available %}{% if row.drift %}{{ row.drift }}{% else %}0{% endif %}{% else %}{% trans "Not measured" %}{% endif %} |
{% if not row.comparison_available %}{% trans "Not measured" %}{% elif row.in_sync %}{% trans "Yes" %}{% else %}{% trans "No" %}{% endif %} |
{% empty %}
| {% trans "No model results in the preview." %} |
{% endfor %}
{% endblock content %}