{% extends 'generic/object.html' %} {% load helpers %} {% load i18n %} {% block title %}{{ object }} - {% trans "Scope Reconciliation" %}{% endblock %} {% block extra_controls %}  {% trans "Sync" %} {% endblock extra_controls %} {% block content %}
{% if report_error %} {% trans "Failed" %} {{ report_error }} {% elif report_pending %} {% trans "Not run" %} {% trans "This report queries Forward for every device and endpoint, so it runs in the background rather than on page load." %} {% else %} {% trans "Report ready" %} {# Naming the producer as well as the time: every completed sync #} {# now refreshes this, so "generated" three days ago means the #} {# post-sync job has been failing, not that nobody pressed a #} {# button. Without the source those two look identical. #} {% trans "Generated" %} {{ report_generated_at }}{% if report_source %} {% blocktrans %}by {{ report_source }}{% endblocktrans %}{% endif %} {% endif %}
{% csrf_token %}
{% trans "Device Scope Reconciliation" %}
{% if payload.forward_in_scope_endpoints %} {% endif %} {% if payload.unmanaged.untagged_total %} {% endif %}
{% trans "Include tags" %} {{ payload.include_tags|join:", "|placeholder }}
{% trans "NetBox devices" %} {{ payload.netbox_device_count }}
{% trans "In scope (collected)" %} {{ payload.forward_in_scope_completed }}
{% trans "In scope (SNMP endpoints)" %} {{ payload.forward_in_scope_endpoints }}
{% trans "Tagged but backfilled" %} {{ payload.forward_tagged_backfilled }} {% if payload.netbox_present_backfilled %}  {% blocktrans with count=payload.netbox_present_backfilled %}view {{ count }} tagged{% endblocktrans %}  {% trans "why?" %} {% endif %}
{% trans "Out of scope (orphans)" %} {% if payload.netbox_out_of_scope %} {# Straight to the card carrying the cause badges and the full list. #} {{ payload.netbox_out_of_scope }} {% else %} 0 {% endif %} {% include 'forward_netbox/inc/count_trend.html' with trend=out_of_scope_trend %}
{% trans "Empty orphan sites" %} {% if payload.netbox_empty_orphan_site_count %} {{ payload.netbox_empty_orphan_site_count }} {% else %} 0 {% endif %}
{% trans "In scope, missing from NetBox" %} {{ payload.forward_missing_in_netbox }}
{% trans "Carry no include tag" %} {{ payload.unmanaged.untagged_total }} {% if payload.unmanaged.owned_untagged %} {% blocktrans with count=payload.unmanaged.owned_untagged %}{{ count }} created by this sync{% endblocktrans %} {# The cause of this number, and the cleanup for it, are in a #} {# card further down the page - which is the half a customer #} {# reading only the summary never reached. #} {% trans "why? / clean up" %} {% include 'forward_netbox/inc/count_trend.html' with trend=uncovered_trend %} {% endif %} {% if payload.unmanaged.unclaimed %} {% blocktrans with count=payload.unmanaged.unclaimed %}{{ count }} not claimed by this sync{% endblocktrans %} {% trans "why?" %} {% endif %}
{% trans "Out-of-Scope Sample" %}
{% if payload.out_of_scope_sample %} {% if payload.out_of_scope_absence.available %} {% comment %} Which kind of absence, because all three look identical here and only one of them means the device really left. {% endcomment %}

{% if payload.out_of_scope_absence.absent_from_snapshot %} {% blocktrans count counter=payload.out_of_scope_absence.absent_from_snapshot %}{{ counter }} gone from Forward{% plural %}{{ counter }} gone from Forward{% endblocktrans %} {% endif %} {% if payload.out_of_scope_absence.present_untagged %} {% blocktrans count counter=payload.out_of_scope_absence.present_untagged %}{{ counter }} in Forward but untagged{% plural %}{{ counter }} in Forward but untagged{% endblocktrans %} {% endif %} {% if payload.out_of_scope_absence.vendor_excluded %} {% blocktrans count counter=payload.out_of_scope_absence.vendor_excluded %}{{ counter }} custom-command source{% plural %}{{ counter }} custom-command source{% endblocktrans %} {% endif %}

{% if payload.out_of_scope_absence.present_untagged %}

{% trans "Devices Forward still has but no longer reports under these tags. A Forward-side tag edit and a narrowed query look identical here — confirm in Forward before pruning." %}

{% endif %} {% if payload.out_of_scope_absence.absent_detail %}

{% blocktrans count counter=payload.out_of_scope_absence.absent_still_tagged %}Of those gone from the snapshot, Forward's device configuration still lists {{ counter }} under an include tag:{% plural %}Of those gone from the snapshot, Forward's device configuration still lists {{ counter }} under an include tag:{% endblocktrans %} {% for row in payload.out_of_scope_absence.absent_detail %} {{ row.count }} — {{ row.label }} {% endfor %} {% trans "Forward's UI shows these as tagged; the sync reads the snapshot, which does not carry them. A disabled device stays here until collection is re-enabled or it is pruned." %}

{% endif %} {% endif %} {% comment %} How long the absence has lasted, which is a different question from what kind of absence it is, and the one that decides whether the prune may act yet. {% endcomment %} {% if payload.out_of_scope_quarantine.held %}

{% blocktrans count counter=payload.out_of_scope_quarantine.held %}{{ counter }} in quarantine{% plural %}{{ counter }} in quarantine{% endblocktrans %} {% if payload.out_of_scope_quarantine.prune_eligible %} {% blocktrans count counter=payload.out_of_scope_quarantine.prune_eligible %}{{ counter }} prune-eligible{% plural %}{{ counter }} prune-eligible{% endblocktrans %} {% endif %}

{% blocktrans with runs=payload.out_of_scope_quarantine.required_runs hours=payload.out_of_scope_quarantine.required_hours %}A device disabled in Forward disappears exactly as a removed one does, so an absence must persist for {{ runs }} consecutive syncs and {{ hours }} hours before the prune will delete it. Devices that reappear leave quarantine automatically.{% endblocktrans %}

{% endif %}
    {% for name in payload.out_of_scope_sample %}
  • {{ name }}
  • {% endfor %}
{% if payload.netbox_out_of_scope > payload.out_of_scope_sample|length %}

{% blocktrans with shown=payload.out_of_scope_sample|length total=payload.netbox_out_of_scope %}Showing {{ shown }} of {{ total }}.{% endblocktrans %}

{% endif %} {% if payload.netbox_out_of_scope %} {# The red button below deletes every one of these; the sample is 25. #}

 {% blocktrans with count=payload.netbox_out_of_scope %}List all {{ count }}{% endblocktrans %}

{% endif %} {% else %}

{% trans "No out-of-scope devices." %}

{% endif %}
{% trans "Post-Upgrade Catalog Reconciliation" %}
{% if upgrade_reconciliation.dlm.available %} {% endif %}
{% trans "Mode" %} {% trans "Read only" %}
{% trans "Platforms with manufacturer" %} {{ upgrade_reconciliation.platforms.with_manufacturer }} / {{ upgrade_reconciliation.platforms.total }}
{% trans "Platforms without manufacturer" %} {{ upgrade_reconciliation.platforms.without_manufacturer }}
{% trans "Stale endpoint DeviceTypes" %} {% if upgrade_reconciliation.stale_endpoint_device_types.candidate_count %} {{ upgrade_reconciliation.stale_endpoint_device_types.candidate_count }} {% else %} 0 {% endif %}
{% trans "DLM status" %} {{ upgrade_reconciliation.dlm.status }}
{% trans "Software versions" %} {{ upgrade_reconciliation.dlm.software_versions.total }}
{% trans "Versions without devices" %} {{ upgrade_reconciliation.dlm.software_versions.without_devices }}
{% trans "Protected versions without devices" %} {{ upgrade_reconciliation.dlm.software_versions.protected_without_devices }}
{% trans "Unreferenced version candidates" %} {% if upgrade_reconciliation.dlm.software_versions.unreferenced_without_devices %} {{ upgrade_reconciliation.dlm.software_versions.unreferenced_without_devices }} {% else %} 0 {% endif %}
{% trans "CVEs" %} {{ upgrade_reconciliation.dlm.cves.total }}
{% trans "CVEs with vulnerabilities" %} {{ upgrade_reconciliation.dlm.cves.with_vulnerabilities }}
{% trans "CVEs with affected software" %} {{ upgrade_reconciliation.dlm.cves.with_affected_software }}
{% trans "Unlinked CVEs" %} {{ upgrade_reconciliation.dlm.cves.unlinked }}
{% trans "Review Candidates" %}
{% if upgrade_reconciliation.stale_endpoint_device_types.sample %}
{% trans "Stale endpoint DeviceTypes" %}
    {% for item in upgrade_reconciliation.stale_endpoint_device_types.sample %}
  • {{ item.manufacturer__name }} / {{ item.model }}
  • {% endfor %}
{% endif %} {% if upgrade_reconciliation.dlm.software_versions.unreferenced_sample %}
{% trans "Unreferenced software versions" %}
    {% for item in upgrade_reconciliation.dlm.software_versions.unreferenced_sample %}
  • {{ item.platform__name }} / {{ item.version }}
  • {% endfor %}
{% endif %} {% if upgrade_reconciliation.dlm.software_versions.protected_sample %}
{% trans "Protected software versions without devices" %}
    {% for item in upgrade_reconciliation.dlm.software_versions.protected_sample %}
  • {{ item.platform__name }} / {{ item.version }} ({{ item.protected_by }})
  • {% endfor %}
{% endif %} {% if not upgrade_reconciliation.stale_endpoint_device_types.sample and not upgrade_reconciliation.dlm.software_versions.unreferenced_sample and not upgrade_reconciliation.dlm.software_versions.protected_sample %}

{% trans "No post-upgrade review candidates." %}

{% endif %}
{% trans "Backfill Reason Breakdown" %}
{% if payload.backfilled_reason_breakdown %}

{% trans "Why these in-scope devices were not freshly collected by Forward. This is a Forward collection-health signal, not a sync error — fix collection in Forward (rotate credentials for AUTHENTICATION_FAILED, check reachability for CONNECTION_TIMEOUT, finish onboarding for INCOMPLETE_SETUP)." %}

{% for reason, count in payload.backfilled_reason_breakdown.items %} {% endfor %}
{{ reason }} {{ count }}
{% else %}

{% trans "No backfilled devices in the latest snapshot." %}

{% endif %}
{% trans "Tagged-but-Backfilled Sample" %}
{% if payload.present_backfilled_detail_sample %}

{% trans "In NetBox but not freshly collected in the latest snapshot (serving last-known-good data). Days = how stale the served data is." %}

{% for item in payload.present_backfilled_detail_sample %} {% endfor %}
{% trans "Device" %}{% trans "Reason" %}{% trans "Stale" %}
{{ item.name }} {{ item.reason }} {% if item.stale_days is not None %}{% blocktrans count days=item.stale_days %}{{ days }} day{% plural %}{{ days }} days{% endblocktrans %}{% else %}—{% endif %}
{% if payload.netbox_present_backfilled > payload.present_backfilled_detail_sample|length %}

{% blocktrans with shown=payload.present_backfilled_detail_sample|length total=payload.netbox_present_backfilled %}Showing {{ shown }} of {{ total }}.{% endblocktrans %}

{% endif %} {% if payload.netbox_present_backfilled %}

 {% blocktrans with count=payload.netbox_present_backfilled %}List all {{ count }}{% endblocktrans %}

{% endif %} {% else %}

{% trans "No backfilled devices in NetBox." %}

{% endif %}
{% if site_relabel_pairs.available and site_relabel_pairs.pair_count %}
{% trans "Site-Relabel Duplicates" %}

{% trans "When Forward relabels a device's site, older versions of this plugin created a second device instead of moving the first. The older copy is stranded at its stale site, still holding the device's primary IP; the newer copy is the one Forward currently reports. This repairs existing pairs only - a current sync no longer creates them." %}

{% if perms.dcim.delete_device %}
{% csrf_token %}
{% endif %} {% if site_relabel_pairs.held_count %}

{% blocktrans count counter=site_relabel_pairs.held_count %}{{ counter }} more duplicated name is held - it cannot be proven safe to merge automatically.{% plural %}{{ counter }} more duplicated names are held - they cannot be proven safe to merge automatically.{% endblocktrans %}

{% endif %}
{% endif %} {% if payload.unmanaged.untagged_total %}
{% trans "Uncovered, Created by This Sync" %}
{% if payload.unmanaged.owned_untagged_sample %}

{% trans "This sync created these devices, and the current Forward result no longer covers them. A device disabled in Forward looks exactly like this, so they are kept, not pruned — which is why this count grows. Use the tag to list all of them, not just this sample." %}

{% if payload.unmanaged.owned_absence.available %}

{% trans "Why:" %} {% if payload.unmanaged.owned_absence.absent_from_snapshot %} {% blocktrans count counter=payload.unmanaged.owned_absence.absent_from_snapshot %}{{ counter }} gone from Forward{% plural %}{{ counter }} gone from Forward{% endblocktrans %} {% endif %} {% if payload.unmanaged.owned_absence.present_untagged %} {% blocktrans count counter=payload.unmanaged.owned_absence.present_untagged %}{{ counter }} in Forward but untagged{% plural %}{{ counter }} in Forward but untagged{% endblocktrans %} {% endif %} {% if payload.unmanaged.owned_absence.vendor_excluded %} {% blocktrans count counter=payload.unmanaged.owned_absence.vendor_excluded %}{{ counter }} custom-command source{% plural %}{{ counter }} custom-command source{% endblocktrans %} {% endif %}

{% if payload.unmanaged.owned_absence.endpoint_detail %}

{% trans "Of those Forward still has, these are SNMP endpoints the endpoint scope excludes:" %} {% for row in payload.unmanaged.owned_absence.endpoint_detail %} {{ row.count }} — {{ row.label }} {% endfor %} {% trans "A console server in that list means its endpoint tags match none of the include tags; a generic endpoint or CIMC controller is excluded by design." %}

{% endif %} {% if payload.unmanaged.owned_absence.absent_detail %} {% comment %} "Tagged in Forward but not in NetBox" is what a customer reads off Forward's Device Tags page, which shows the configuration; the sync reads the snapshot. Name the configuration fact so the gap is not filed as a sync bug. {% endcomment %}

{% blocktrans count counter=payload.unmanaged.owned_absence.absent_still_tagged %}Of those gone from Forward, its device configuration still lists {{ counter }} under an include tag:{% plural %}Of those gone from Forward, its device configuration still lists {{ counter }} under an include tag:{% endblocktrans %} {% for row in payload.unmanaged.owned_absence.absent_detail %} {{ row.count }} — {{ row.label }} {% endfor %} {% trans "Forward's UI shows these as tagged; the snapshot the sync reads does not carry them, so they cannot be tagged or updated in NetBox. Re-enable collection in Forward to cover a disabled device again." %}

{% endif %} {% elif payload.unmanaged.owned_absence %}

{% trans "The Forward census that classifies the cause did not run for this report." %}

{% endif %}

 {% blocktrans with count=payload.unmanaged.owned_untagged %}List all {{ count }}{% endblocktrans %}  {% trans "Filter devices by tag" %}

{# The tag list above is not this sync's count: it is one tag shared #} {# by every sync, so a device another sync uncovered - or one this #} {# sync has since covered again - still carries it and still shows #} {# up there. A customer bulk-deleted from exactly that list reading #} {# "546 uncovered" against a sync whose own card said 107. #}

{% trans "The tag list is shared by every sync and kept from earlier runs; a device another sync uncovered, or one this sync has since covered again, still carries it. List all above is this sync's current count." %}

{% comment %} Only the devices Forward no longer reports at all are eligible, so the button counts `absent_from_snapshot` rather than the card's own total. Offering the larger number would promise a deletion the gates refuse, which is how an operator learns to distrust the panel. {% endcomment %} {% if perms.dcim.delete_device %}
{% csrf_token %} {% if payload.unmanaged.owned_quarantine.held %}
{% endif %} {# Counts what the button would actually delete, not what is of #} {# the right kind. It used to read "Delete 105" while the #} {# quarantine held all 105, so pressing it deleted nothing. #} {% if payload.unmanaged.owned_quarantine.held %} {% blocktrans count counter=payload.unmanaged.owned_quarantine.held %}{{ counter }} more held by the quarantine{% plural %}{{ counter }} more held by the quarantine{% endblocktrans %} {% endif %}
{% endif %} {# The orphan card has carried this sentence since the quarantine #} {# shipped; the uncovered card never did, so an operator watching a #} {# count that would not move had nothing on the page explaining why. #} {% if payload.unmanaged.owned_quarantine.required_runs is not None %}

{% blocktrans with runs=payload.unmanaged.owned_quarantine.required_runs hours=payload.unmanaged.owned_quarantine.required_hours %}A device disabled in Forward disappears exactly as a removed one does, so an absence must persist for {{ runs }} consecutive syncs and {{ hours }} hours before the prune will delete it. Devices that reappear leave quarantine automatically.{% endblocktrans %}

{% endif %}
    {% for name in payload.unmanaged.owned_untagged_sample %}
  • {{ name }}
  • {% endfor %}
{% if payload.unmanaged.owned_untagged > payload.unmanaged.owned_untagged_sample|length %}

{% blocktrans with shown=payload.unmanaged.owned_untagged_sample|length total=payload.unmanaged.owned_untagged %}Showing {{ shown }} of {{ total }}.{% endblocktrans %}

{% endif %} {% else %}

{% trans "Every device this sync created is covered by the current Forward result." %}

{% endif %}
{% trans "Uncovered, Not Claimed by This Sync" %}
{% if payload.unmanaged.unclaimed_sample %}

{% trans "Devices this sync never created: another source, an operator, or an old SNMP endpoint import. Listed so the count above can be accounted for — this sync makes no claim about them, tags none of them, and never prunes them." %}

 {% blocktrans with count=payload.unmanaged.unclaimed %}List all {{ count }}{% endblocktrans %}

    {% for name in payload.unmanaged.unclaimed_sample %}
  • {{ name }}
  • {% endfor %}
{% if payload.unmanaged.unclaimed > payload.unmanaged.unclaimed_sample|length %}

{% blocktrans with shown=payload.unmanaged.unclaimed_sample|length total=payload.unmanaged.unclaimed %}Showing {{ shown }} of {{ total }}.{% endblocktrans %}

{% endif %} {% else %}

{% trans "Every uncovered device was created by this sync." %}

{% endif %}
{% endif %} {% endblock content %}