forward-netbox 2.2.4__tar.gz → 2.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (291) hide show
  1. forward_netbox-2.2.4/README.md → forward_netbox-2.3.0/PKG-INFO +59 -7
  2. forward_netbox-2.2.4/PKG-INFO → forward_netbox-2.3.0/README.md +25 -31
  3. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/__init__.py +2 -2
  4. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/forms.py +57 -2
  5. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/jobs.py +51 -6
  6. forward_netbox-2.3.0/forward_netbox/management/commands/forward_metrics.py +105 -0
  7. forward_netbox-2.3.0/forward_netbox/management/commands/forward_stuck_job_alert.py +76 -0
  8. forward_netbox-2.3.0/forward_netbox/migrations/0031_encrypt_source_password.py +41 -0
  9. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/models.py +18 -4
  10. forward_netbox-2.3.0/forward_netbox/queries/forward_device_feature_tags.nqe +26 -0
  11. forward_netbox-2.3.0/forward_netbox/tests/test_crypto.py +67 -0
  12. forward_netbox-2.3.0/forward_netbox/tests/test_dependency_preview_summary.py +45 -0
  13. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_device_scope_reconciliation_audit_command.py +21 -1
  14. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_device_scope_tagging.py +9 -9
  15. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_forms.py +31 -0
  16. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_forward_api.py +44 -5
  17. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_interface_naming.py +3 -2
  18. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_issue_rendering.py +0 -1
  19. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_jobs.py +91 -0
  20. forward_netbox-2.3.0/forward_netbox/tests/test_migrations.py +55 -0
  21. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_models.py +88 -4
  22. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_query_registry.py +28 -7
  23. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_scope_module_ui.py +1 -1
  24. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_sensitive_content.py +23 -0
  25. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_sync.py +62 -39
  26. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_sync_facade.py +4 -3
  27. forward_netbox-2.3.0/forward_netbox/utilities/crypto.py +65 -0
  28. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/density_learning.py +0 -109
  29. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/forward_api_impl.py +47 -2
  30. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/interface_naming.py +1 -1
  31. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/model_validation.py +17 -2
  32. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/query_fetch_execution.py +17 -1
  33. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/query_registry.py +7 -0
  34. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/scope_reconciliation.py +23 -3
  35. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sensitive_content.py +35 -0
  36. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/single_branch_executor.py +1 -1
  37. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_facade.py +0 -6
  38. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_state.py +0 -2
  39. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/views.py +17 -8
  40. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/pyproject.toml +18 -3
  41. forward_netbox-2.2.4/forward_netbox/queries/forward_device_feature_tags.nqe +0 -20
  42. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/LICENSE +0 -0
  43. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/api/__init__.py +0 -0
  44. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/api/serializers.py +0 -0
  45. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/api/urls.py +0 -0
  46. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/api/views.py +0 -0
  47. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/choices.py +0 -0
  48. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/exceptions.py +0 -0
  49. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/filtersets.py +0 -0
  50. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/__init__.py +0 -0
  51. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/commands/__init__.py +0 -0
  52. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/commands/forward_apic_cimc_readiness_audit.py +0 -0
  53. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/commands/forward_apply_identity_audit.py +0 -0
  54. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/commands/forward_blocker_audit.py +0 -0
  55. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/commands/forward_collection_gap_alert.py +0 -0
  56. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/commands/forward_device_scope_reconciliation_audit.py +0 -0
  57. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/commands/forward_link_vsys_parents.py +0 -0
  58. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/commands/forward_module_readiness.py +0 -0
  59. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/commands/forward_primary_ip_audit.py +0 -0
  60. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/commands/forward_pushdown_profile.py +0 -0
  61. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/commands/forward_query_diff_coverage_audit.py +0 -0
  62. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/commands/forward_scope_ipam_audit.py +0 -0
  63. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/commands/forward_seed_ui_harness.py +0 -0
  64. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/commands/forward_tag_delete_eligible.py +0 -0
  65. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/management/commands/forward_validation_org_query_audit.py +0 -0
  66. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0001_initial.py +0 -0
  67. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0002_add_coalesce_fields.py +0 -0
  68. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0003_forwardingestionissue_context_fields.py +0 -0
  69. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0004_forwardingestion_baseline_fields.py +0 -0
  70. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0005_validation_and_reporting.py +0 -0
  71. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0006_alter_forwardnqemap_netbox_model.py +0 -0
  72. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0007_forwardingestion_persisted_change_counters.py +0 -0
  73. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0008_alter_forwardnqemap_netbox_model.py +0 -0
  74. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0009_alter_forwardnqemap_netbox_model.py +0 -0
  75. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0010_forwardvalidationrun_override_applied_and_more.py +0 -0
  76. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0011_forwardingestion_change_request_id.py +0 -0
  77. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0012_forwardnqemap_query_reference.py +0 -0
  78. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0013_execution_run_step.py +0 -0
  79. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0014_execution_step_fetch_scope.py +0 -0
  80. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0015_execution_step_apply_engine.py +0 -0
  81. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0016_execution_step_query_metrics.py +0 -0
  82. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0017_execution_step_row_counters.py +0 -0
  83. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0018_execution_run_reconciliation_events.py +0 -0
  84. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0019_forwardexecutionstep_query_parameters.py +0 -0
  85. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0020_execution_step_operation.py +0 -0
  86. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0021_add_fhrpgroup_nqe_map_choice.py +0 -0
  87. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0022_alter_forwardnqemap_netbox_model.py +0 -0
  88. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0023_split_ip_address_maps.py +0 -0
  89. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0024_forwarddeviceanalysis.py +0 -0
  90. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0025_device_analysis_netboxmodel.py +0 -0
  91. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0026_forwarddeviceanalysis_collection_result.py +0 -0
  92. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0027_alter_forwardexecutionrun_backend.py +0 -0
  93. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0028_remove_forwardexecutionstep_run_and_more.py +0 -0
  94. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0029_forward_parent_device_cf.py +0 -0
  95. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/0030_forwarddeviceanalysis_cve_ids.py +0 -0
  96. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/migrations/__init__.py +0 -0
  97. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/navigation.py +0 -0
  98. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/__init__.py +0 -0
  99. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_aci_apic_cimc_inventory.nqe +0 -0
  100. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_aci_apic_nodes.nqe +0 -0
  101. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_aci_app_profiles.nqe +0 -0
  102. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_aci_bridge_domains.nqe +0 -0
  103. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_aci_command_inventory.nqe +0 -0
  104. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_aci_contracts.nqe +0 -0
  105. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_aci_endpoint_groups.nqe +0 -0
  106. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_aci_fabrics.nqe +0 -0
  107. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_aci_filters.nqe +0 -0
  108. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_aci_l3outs.nqe +0 -0
  109. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_aci_nodes.nqe +0 -0
  110. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_aci_pods.nqe +0 -0
  111. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_aci_static_port_bindings.nqe +0 -0
  112. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_aci_tenants.nqe +0 -0
  113. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_aci_vrfs.nqe +0 -0
  114. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_bgp_address_families.nqe +0 -0
  115. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_bgp_peer_address_families.nqe +0 -0
  116. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_bgp_peers.nqe +0 -0
  117. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_device_analysis.nqe +0 -0
  118. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_device_feature_tags_with_rules.nqe +0 -0
  119. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_device_models.nqe +0 -0
  120. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_device_models_with_netbox_aliases.nqe +0 -0
  121. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_device_types.nqe +0 -0
  122. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_device_vendors.nqe +0 -0
  123. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_devices.nqe +0 -0
  124. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_devices_with_netbox_aliases.nqe +0 -0
  125. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_hsrp_groups.nqe +0 -0
  126. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_inferred_interface_cables.nqe +0 -0
  127. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_interfaces.nqe +0 -0
  128. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_inventory_items.nqe +0 -0
  129. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_ip_addresses_ipv4.nqe +0 -0
  130. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_ip_addresses_ipv6.nqe +0 -0
  131. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_ip_addresses_unassignable_diagnostics.nqe +0 -0
  132. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_locations.nqe +0 -0
  133. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_mac_addresses.nqe +0 -0
  134. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_modules.nqe +0 -0
  135. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_ospf_areas.nqe +0 -0
  136. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_ospf_instances.nqe +0 -0
  137. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_ospf_interfaces.nqe +0 -0
  138. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_peering_sessions.nqe +0 -0
  139. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_platforms.nqe +0 -0
  140. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_prefixes_ipv4.nqe +0 -0
  141. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_prefixes_ipv6.nqe +0 -0
  142. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_routing_import_diagnostics.nqe +0 -0
  143. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_virtual_chassis.nqe +0 -0
  144. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_vlans.nqe +0 -0
  145. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/forward_vrfs.nqe +0 -0
  146. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/queries/netbox_utilities.nqe +0 -0
  147. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/signals.py +0 -0
  148. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/static/forward_netbox/img/fn-logo.svg +0 -0
  149. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tables.py +0 -0
  150. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/template_content.py +0 -0
  151. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/forwarddriftpolicy.html +0 -0
  152. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/forwardingestion.html +0 -0
  153. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/forwardnqemap.html +0 -0
  154. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/forwardsource.html +0 -0
  155. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/forwardsync.html +0 -0
  156. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/forwardsync_dependency_preview.html +0 -0
  157. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/forwardsync_drift_report.html +0 -0
  158. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/forwardsync_health.html +0 -0
  159. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/forwardsync_module_readiness.html +0 -0
  160. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/forwardsync_scope_reconciliation.html +0 -0
  161. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/forwardvalidationrun.html +0 -0
  162. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/inc/brand_bar.html +0 -0
  163. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/inc/device_analysis_panel.html +0 -0
  164. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/inc/diff.html +0 -0
  165. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/inc/execution_insights.html +0 -0
  166. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/inc/logs_pending.html +0 -0
  167. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/inc/merge_form.html +0 -0
  168. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/inc/validation_force_allow_form.html +0 -0
  169. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/partials/change_explainability.html +0 -0
  170. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/partials/ingestion_all.html +0 -0
  171. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/partials/ingestion_merge_button.html +0 -0
  172. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/partials/ingestion_progress.html +0 -0
  173. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/partials/ingestion_statistics.html +0 -0
  174. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/partials/ingestion_status.html +0 -0
  175. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/partials/job_logs.html +0 -0
  176. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/partials/object_tabs.html +0 -0
  177. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/partials/stage_switcher_buttons.html +0 -0
  178. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templates/forward_netbox/partials/validation_force_allow_button.html +0 -0
  179. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templatetags/__init__.py +0 -0
  180. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/templatetags/forward_netbox_helpers.py +0 -0
  181. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/__init__.py +0 -0
  182. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/fixtures/aci_command_inventory_expected.json +0 -0
  183. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/fixtures/aci_discovery_expected.json +0 -0
  184. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/scenarios.py +0 -0
  185. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_api_usage.py +0 -0
  186. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_api_views.py +0 -0
  187. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_apic_cimc_readiness_audit_command.py +0 -0
  188. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_apply_engine.py +0 -0
  189. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_apply_identity_audit.py +0 -0
  190. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_branching.py +0 -0
  191. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_brand_bar.py +0 -0
  192. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_bulk_adapter_parity.py +0 -0
  193. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_bulk_merge.py +0 -0
  194. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_bulk_merge_scale.py +0 -0
  195. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_device_analysis_query.py +0 -0
  196. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_execution_insights_delete_label.py +0 -0
  197. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_health.py +0 -0
  198. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_ingestion_merge.py +0 -0
  199. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_ip_address_dedup_query.py +0 -0
  200. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_ipaddress_dup_global.py +0 -0
  201. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_job_compat.py +0 -0
  202. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_job_liveness.py +0 -0
  203. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_log_export.py +0 -0
  204. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_logging.py +0 -0
  205. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_module_readiness.py +0 -0
  206. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_module_readiness_rollup.py +0 -0
  207. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_plugin_integrations.py +0 -0
  208. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_primary_ip.py +0 -0
  209. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_primary_ip_audit.py +0 -0
  210. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_primary_ip_integration.py +0 -0
  211. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_query_binding.py +0 -0
  212. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_query_diff_coverage_audit_command.py +0 -0
  213. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_release_readiness_audit.py +0 -0
  214. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_runtime_dependency_check.py +0 -0
  215. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_scope_ipam_audit.py +0 -0
  216. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_scope_matched_tags.py +0 -0
  217. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_stability_hardening.py +0 -0
  218. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_sync_aci.py +0 -0
  219. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_sync_orchestration.py +0 -0
  220. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_sync_runner_contracts.py +0 -0
  221. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_sync_state.py +0 -0
  222. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_synthetic_scenarios.py +0 -0
  223. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_validation_org_query_audit_command.py +0 -0
  224. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/tests/test_vsys_parent.py +0 -0
  225. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/urls.py +0 -0
  226. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/__init__.py +0 -0
  227. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/api_usage.py +0 -0
  228. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/apply_engine.py +0 -0
  229. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/apply_engine_bulk.py +0 -0
  230. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/apply_engine_decision.py +0 -0
  231. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/apply_identity_audit.py +0 -0
  232. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/branch_budget.py +0 -0
  233. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/branching.py +0 -0
  234. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/bulk_merge.py +0 -0
  235. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/change_explainability.py +0 -0
  236. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/device_analysis.py +0 -0
  237. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/direct_changes.py +0 -0
  238. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/drift_report.py +0 -0
  239. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/execution_ledger.py +0 -0
  240. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/execution_telemetry.py +0 -0
  241. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/fast_bootstrap_executor.py +0 -0
  242. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/fetch_artifacts.py +0 -0
  243. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/forward_api.py +0 -0
  244. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/health.py +0 -0
  245. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/health_apply_fetch.py +0 -0
  246. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/health_checks.py +0 -0
  247. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/health_summary_blocks.py +0 -0
  248. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/ingestion_issues.py +0 -0
  249. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/ingestion_merge.py +0 -0
  250. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/ingestion_presentation.py +0 -0
  251. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/job_compat.py +0 -0
  252. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/job_liveness.py +0 -0
  253. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/json_safe.py +0 -0
  254. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/logging.py +0 -0
  255. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/merge.py +0 -0
  256. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/model_contracts.py +0 -0
  257. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/module_readiness.py +0 -0
  258. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/multi_branch_lifecycle.py +0 -0
  259. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/plugin_integrations/__init__.py +0 -0
  260. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/plugin_integrations/registry.py +0 -0
  261. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/primary_ip.py +0 -0
  262. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/primary_ip_audit.py +0 -0
  263. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/query_binding.py +0 -0
  264. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/query_binding_resolution.py +0 -0
  265. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/query_diagnostics.py +0 -0
  266. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/query_fetch.py +0 -0
  267. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/runtime_guidance.py +0 -0
  268. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/scope_ipam_audit.py +0 -0
  269. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/snapshot_freshness.py +0 -0
  270. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/support_bundle_archive.py +0 -0
  271. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync.py +0 -0
  272. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_aci.py +0 -0
  273. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_cable.py +0 -0
  274. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_contracts.py +0 -0
  275. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_core_models.py +0 -0
  276. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_device.py +0 -0
  277. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_events.py +0 -0
  278. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_execution.py +0 -0
  279. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_interface.py +0 -0
  280. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_inventory_module.py +0 -0
  281. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_ipam.py +0 -0
  282. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_orchestration.py +0 -0
  283. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_primitives.py +0 -0
  284. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_reporting.py +0 -0
  285. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_routing.py +0 -0
  286. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_routing_impl.py +0 -0
  287. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_runner_adapters.py +0 -0
  288. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/sync_runner_contracts.py +0 -0
  289. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/validation.py +0 -0
  290. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/validation_org_query.py +0 -0
  291. {forward_netbox-2.2.4 → forward_netbox-2.3.0}/forward_netbox/utilities/vsys_parent.py +0 -0
@@ -1,3 +1,36 @@
1
+ Metadata-Version: 2.4
2
+ Name: forward-netbox
3
+ Version: 2.3.0
4
+ Summary: NetBox plugin to sync Forward data into NetBox via built-in NQE queries
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Keywords: netbox,forward,plugin,sync,nqe
8
+ Author: Craig Johnson
9
+ Author-email: craigjohnson@forwardnetworks.com
10
+ Requires-Python: >=3.10,<4.0
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Framework :: Django
13
+ Classifier: Intended Audience :: Information Technology
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: System :: Networking
24
+ Requires-Dist: httpx (>0.26,<0.29)
25
+ Requires-Dist: netboxlabs-netbox-branching (>=1.1.0)
26
+ Requires-Dist: pyzipper (>=0.3.6,<0.4.0)
27
+ Project-URL: Changelog, https://github.com/forwardnetworks/forward-netbox/blob/main/CHANGELOG.md
28
+ Project-URL: Documentation, https://github.com/forwardnetworks/forward-netbox/tree/main/docs
29
+ Project-URL: Homepage, https://github.com/forwardnetworks/forward-netbox
30
+ Project-URL: Issue Tracker, https://github.com/forwardnetworks/forward-netbox/issues
31
+ Project-URL: Repository, https://github.com/forwardnetworks/forward-netbox
32
+ Description-Content-Type: text/markdown
33
+
1
34
  # Forward Field Integration
2
35
 
3
36
  **Forward Field Integration** (`forward_netbox`) is a NetBox plugin that syncs Forward inventory into NetBox through direct Forward API connectivity and NQE while preserving the branch-backed sync, diff, and merge workflow.
@@ -8,7 +41,9 @@ Forward 26.6 is the baseline for async NQE.
8
41
 
9
42
  | Plugin Release | NetBox Version | Status |
10
43
  | --- | --- | --- |
11
- | `v2.2.4` | `4.6.3` required; needs netbox-branching `1.1.0+` | Current release; Hotfix: device-analysis NQE (bare foreach) errored refresh + CVE list; surface job errors into job.data |
44
+ | `v2.3.0` | `4.6.4` required; needs netbox-branching `1.1.0+` | Current release; GA/enterprise hardening: encrypted Forward credential at rest, PyPI Trusted Publishing + SBOM, Prometheus metrics + stuck-job alert, populated-DB upgrade test, dead-code removal (multi_branch/density-learning), reliability fixes (jittered/Retry-After backoff, SaaS rate clamp, PK-anchored device prune), and supported-product framing. Drop-in from 2.2.5 — stored credentials auto-encrypt on save; rotating SECRET_KEY requires re-entering them. |
45
+ | `v2.2.5` | `4.6.4` required; needs netbox-branching `1.1.0+` | Superseded by `v2.3.0`; Feature: operator-selectable **Sync Device Tags** — pick which Forward device tags (e.g. `Mgmt_*`) become NetBox device tags (replaces the hardcoded feature-tag set); Fix dependency-preview AttributeError + vsys job pile-up guard (hung pending); test/require NetBox 4.6.4 |
46
+ | `v2.2.4` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.2.5`; Hotfix: device-analysis NQE (bare foreach) errored refresh + CVE list; surface job errors into job.data |
12
47
  | `v2.2.3` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.2.4`; Field-feedback fixes: delete-count labeling, vsys/vdom auto-link, skip empty VRFs, per-device CVE list, churn pinpoint, query-ID status clarify |
13
48
  | `v2.2.2` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.2.3`; Fix 504 gateway timeouts on large syncs: stop recomputing change-explainability on every poll during a long merge + back off poll to 15s |
14
49
  | `v2.2.1` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.2.2`; Add read-only forward_apply_identity_audit diagnostic to pinpoint 1-created/1-deleted idempotency churn |
@@ -16,7 +51,6 @@ Forward 26.6 is the baseline for async NQE.
16
51
  | `v2.1.5` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.2.0`; Fix Prune orphans erroring on empty sites that still hold a VLAN/VM/prefix (delete only truly-empty sites) |
17
52
  | `v2.1.4` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.1.5`; Tag delete-eligible global IPAM (prefixes/VLANs/VRFs) for manual review |
18
53
  | `v2.1.3` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.1.4`; Prune empty orphan sites (zero devices + zero racks) alongside out-of-scope devices |
19
- | `v2.1.3` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.1.3`; Prune empty orphan sites (zero devices + zero racks) alongside out-of-scope devices |
20
54
  | `v2.1.2` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.1.3`; Feature + docs: (1) new out-of-scope orphan health signal — the sync health summary now shows how many NetBox devices match none of the included Forward tags (removable via Scope Reconciliation -> Prune orphans), mirroring the backfilled signal, via a self-healing `forward-out-of-scope` device tag and a `?tag=forward-out-of-scope` filter; (2) docs: the "no covering prefix" diagnostic now names /32 and /128 host addresses (loopbacks, anycast, some VIPs), and the Operations Guide documents backfilled (in-scope, kept) vs out-of-scope (removable) devices. Drop-in from `2.1.1`. |
21
55
  | `v2.1.1` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.1.2`; Bugfix + diagnostics: (1) the IPv4/IPv6 IP queries global dedup now pins the chosen interface to the chosen device (mirroring the VRF and MAC dedup blocks), so a deduped global address can no longer be attributed to an interface on a different device — the source of spurious "target interface was not imported" skips; (2) new read-only `forward_primary_ip_audit` command buckets Mgmt_ primary-IP resolution per device (resolvable / device-not-in-netbox / interface-not-matched / interface-present-no-IP) to pinpoint why a device does not get a primary IP. Drop-in from `2.1.0`. |
22
56
  | `v2.1.0` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.1.1`; Feature: `forward_scope_ipam_audit` management command — a read-only audit listing network-global IPAM (prefixes, VLANs, VRFs) that NetBox holds but the sync's latest Forward fetch no longer reports, as manual-review candidates. Device-tag scope prune is device-derived and never removes global IPAM; this surfaces stale global objects without deleting anything (identity matching reuses the apply engine so verdicts match the sync). Drop-in from `2.0.8`. |
@@ -32,7 +66,6 @@ Forward 26.6 is the baseline for async NQE.
32
66
  | `v1.7.2` | `4.6.3` required (4.5.x dropped); needs netbox-branching `1.1.0+` | Superseded by `v2.0.0`; Collection-gap diagnostics: per-reason backfill breakdown + staleness, growth/trend escalation, per-device collection result, ACI delete safety valve, opt-in auto-tag |
33
67
  | `v1.7.1` | `4.5.9` and `4.6.2` validated; shared branch for `4.5.x` and `4.6.x` with capability-gated 4.6 features | Superseded by `v1.7.2`; ACI BD/L3Out graduation + FHRP churn fix (replaces yanked 1.7.0 and 1.6.2) |
34
68
  | `v1.7.0` | `4.5.9` and `4.6.2` validated; shared branch for `4.5.x` and `4.6.x` with capability-gated 4.6 features | Superseded by `v1.7.1`; ACI bridge domain and L3Out NQE maps; query publish hardening |
35
- | `v1.7.0` | `4.5.9` and `4.6.2` validated; shared branch for `4.5.x` and `4.6.x` with capability-gated 4.6 features | Superseded by `v1.7.0`; ACI bridge domain and L3Out NQE maps; query publish hardening |
36
69
  | `v1.6.2` | `4.5.9` and `4.6.2` validated; shared branch for `4.5.x` and `4.6.x` with capability-gated 4.6 features | Superseded by `v1.7.0`; completes the 1.6.1 line (1.6.1 was yanked — its PyPI build predated these): device tag scope now covers VLANs/VRFs and prefixes derive from connected interface subnets; the FHRP group churn (delete+recreate every sync) is fixed by identity-bucket sharding; device analysis is a first-class model with a fleet list view, REST API, and an Open in Forward deep-link. |
37
70
  | `v1.6.1` | `4.5.9` and `4.6.2` validated; shared branch for `4.5.x` and `4.6.x` with capability-gated 4.6 features | Superseded by `v1.6.2`; matures the 1.6.0 features and tooling — Device Analysis is now a NetBox model with a fleet-wide list view, REST API, and per-device-FK panel scoping (with up-interface blast-radius and opt-in post-sync refresh); adds a schedulable collection-gap alert command, run-history drill-down links, and hardened release tooling (one-command release script, generated CHANGELOG, conventional-commit hook). |
38
71
  | `v1.6.0` | `4.5.9` and `4.6.2` validated; shared branch for `4.5.x` and `4.6.x` with capability-gated 4.6 features | Superseded by `v1.6.1`; ships the blue-sky tranche — release automation (`invoke release`), an Operations Guide, a collection-gap health signal, a sync run-history panel, a read-only device analysis panel (GA reachability / connectivity-degree blast radius / CVE exposure), and a bidirectional per-model drift report. |
@@ -184,9 +217,21 @@ Forward 26.6 is the baseline for async NQE.
184
217
  | `v0.1.1` | Added pagination, shared built-in NQE helpers, and release/doc cleanup. |
185
218
  | `v0.1.0` | Initial unsupported NetBox plugin release with built-in Forward sync workflow and seeded NQE maps. |
186
219
 
187
- ## Support Disclaimer
220
+ ## Support
221
+
222
+ Forward Field Integration is an officially maintained Forward Networks integration
223
+ for NetBox. Supported NetBox and `netbox-branching` versions are listed in the
224
+ Release Compatibility table above; support targets the latest released version.
188
225
 
189
- This repository is provided for use at your own risk. It is an unsupported release and is not an officially supported Forward product. There is no warranty, support commitment, or compatibility guarantee beyond the version table above.
226
+ - **Bugs / feature requests:** open a GitHub issue using the provided templates.
227
+ - **Security vulnerabilities:** report privately per [SECURITY.md](SECURITY.md) —
228
+ do not open a public issue.
229
+ - **Upgrades:** follow the [Upgrade and Rollback guide](docs/01_User_Guide/upgrade.md);
230
+ back up the NetBox database before upgrading.
231
+
232
+ Deploy on a supported NetBox version and review the deployment security notes in
233
+ `SECURITY.md` (credential-at-rest and the sync trust boundary) before production
234
+ use.
190
235
 
191
236
  ## Features
192
237
 
@@ -226,10 +271,16 @@ This repository is provided for use at your own risk. It is an unsupported relea
226
271
 
227
272
  1. Install the plugin into the same Python environment as NetBox:
228
273
 
229
- Install the wheel or source archive from GitHub Releases:
274
+ Install the latest release from PyPI:
275
+
276
+ ```bash
277
+ pip install forward-netbox
278
+ ```
279
+
280
+ Or install a specific wheel or source archive from GitHub Releases:
230
281
 
231
282
  ```bash
232
- pip install /path/to/forward_netbox-0.9.4.1-py3-none-any.whl
283
+ pip install /path/to/forward_netbox-2.2.5-py3-none-any.whl
233
284
  ```
234
285
 
235
286
  2. Enable both plugins in the NetBox configuration:
@@ -314,3 +365,4 @@ Normal UI/API sync jobs default to native multi-branch execution, with a default
314
365
  - [Model Mapping Matrix](docs/02_Reference/model-mapping-matrix.md)
315
366
  - [Shipped NQE Query Files](https://github.com/forwardnetworks/forward-netbox/tree/main/forward_netbox/queries)
316
367
  - [License](LICENSE)
368
+
@@ -1,26 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: forward-netbox
3
- Version: 2.2.4
4
- Summary: NetBox plugin to sync Forward data into NetBox via built-in NQE queries
5
- License: MIT
6
- License-File: LICENSE
7
- Keywords: netbox,forward,plugin,sync,nqe
8
- Author: Craig Johnson
9
- Author-email: craigjohnson@forwardnetworks.com
10
- Requires-Python: >=3.10,<4.0
11
- Classifier: License :: OSI Approved :: MIT License
12
- Classifier: Operating System :: OS Independent
13
- Classifier: Programming Language :: Python :: 3
14
- Classifier: Programming Language :: Python :: 3.10
15
- Classifier: Programming Language :: Python :: 3.11
16
- Classifier: Programming Language :: Python :: 3.12
17
- Classifier: Programming Language :: Python :: 3.13
18
- Classifier: Programming Language :: Python :: 3.14
19
- Requires-Dist: httpx (>0.26,<0.29)
20
- Requires-Dist: netboxlabs-netbox-branching (>=1.1.0)
21
- Requires-Dist: pyzipper (>=0.3.6,<0.4.0)
22
- Description-Content-Type: text/markdown
23
-
24
1
  # Forward Field Integration
25
2
 
26
3
  **Forward Field Integration** (`forward_netbox`) is a NetBox plugin that syncs Forward inventory into NetBox through direct Forward API connectivity and NQE while preserving the branch-backed sync, diff, and merge workflow.
@@ -31,7 +8,9 @@ Forward 26.6 is the baseline for async NQE.
31
8
 
32
9
  | Plugin Release | NetBox Version | Status |
33
10
  | --- | --- | --- |
34
- | `v2.2.4` | `4.6.3` required; needs netbox-branching `1.1.0+` | Current release; Hotfix: device-analysis NQE (bare foreach) errored refresh + CVE list; surface job errors into job.data |
11
+ | `v2.3.0` | `4.6.4` required; needs netbox-branching `1.1.0+` | Current release; GA/enterprise hardening: encrypted Forward credential at rest, PyPI Trusted Publishing + SBOM, Prometheus metrics + stuck-job alert, populated-DB upgrade test, dead-code removal (multi_branch/density-learning), reliability fixes (jittered/Retry-After backoff, SaaS rate clamp, PK-anchored device prune), and supported-product framing. Drop-in from 2.2.5 — stored credentials auto-encrypt on save; rotating SECRET_KEY requires re-entering them. |
12
+ | `v2.2.5` | `4.6.4` required; needs netbox-branching `1.1.0+` | Superseded by `v2.3.0`; Feature: operator-selectable **Sync Device Tags** — pick which Forward device tags (e.g. `Mgmt_*`) become NetBox device tags (replaces the hardcoded feature-tag set); Fix dependency-preview AttributeError + vsys job pile-up guard (hung pending); test/require NetBox 4.6.4 |
13
+ | `v2.2.4` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.2.5`; Hotfix: device-analysis NQE (bare foreach) errored refresh + CVE list; surface job errors into job.data |
35
14
  | `v2.2.3` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.2.4`; Field-feedback fixes: delete-count labeling, vsys/vdom auto-link, skip empty VRFs, per-device CVE list, churn pinpoint, query-ID status clarify |
36
15
  | `v2.2.2` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.2.3`; Fix 504 gateway timeouts on large syncs: stop recomputing change-explainability on every poll during a long merge + back off poll to 15s |
37
16
  | `v2.2.1` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.2.2`; Add read-only forward_apply_identity_audit diagnostic to pinpoint 1-created/1-deleted idempotency churn |
@@ -39,7 +18,6 @@ Forward 26.6 is the baseline for async NQE.
39
18
  | `v2.1.5` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.2.0`; Fix Prune orphans erroring on empty sites that still hold a VLAN/VM/prefix (delete only truly-empty sites) |
40
19
  | `v2.1.4` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.1.5`; Tag delete-eligible global IPAM (prefixes/VLANs/VRFs) for manual review |
41
20
  | `v2.1.3` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.1.4`; Prune empty orphan sites (zero devices + zero racks) alongside out-of-scope devices |
42
- | `v2.1.3` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.1.3`; Prune empty orphan sites (zero devices + zero racks) alongside out-of-scope devices |
43
21
  | `v2.1.2` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.1.3`; Feature + docs: (1) new out-of-scope orphan health signal — the sync health summary now shows how many NetBox devices match none of the included Forward tags (removable via Scope Reconciliation -> Prune orphans), mirroring the backfilled signal, via a self-healing `forward-out-of-scope` device tag and a `?tag=forward-out-of-scope` filter; (2) docs: the "no covering prefix" diagnostic now names /32 and /128 host addresses (loopbacks, anycast, some VIPs), and the Operations Guide documents backfilled (in-scope, kept) vs out-of-scope (removable) devices. Drop-in from `2.1.1`. |
44
22
  | `v2.1.1` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.1.2`; Bugfix + diagnostics: (1) the IPv4/IPv6 IP queries global dedup now pins the chosen interface to the chosen device (mirroring the VRF and MAC dedup blocks), so a deduped global address can no longer be attributed to an interface on a different device — the source of spurious "target interface was not imported" skips; (2) new read-only `forward_primary_ip_audit` command buckets Mgmt_ primary-IP resolution per device (resolvable / device-not-in-netbox / interface-not-matched / interface-present-no-IP) to pinpoint why a device does not get a primary IP. Drop-in from `2.1.0`. |
45
23
  | `v2.1.0` | `4.6.3` required; needs netbox-branching `1.1.0+` | Superseded by `v2.1.1`; Feature: `forward_scope_ipam_audit` management command — a read-only audit listing network-global IPAM (prefixes, VLANs, VRFs) that NetBox holds but the sync's latest Forward fetch no longer reports, as manual-review candidates. Device-tag scope prune is device-derived and never removes global IPAM; this surfaces stale global objects without deleting anything (identity matching reuses the apply engine so verdicts match the sync). Drop-in from `2.0.8`. |
@@ -55,7 +33,6 @@ Forward 26.6 is the baseline for async NQE.
55
33
  | `v1.7.2` | `4.6.3` required (4.5.x dropped); needs netbox-branching `1.1.0+` | Superseded by `v2.0.0`; Collection-gap diagnostics: per-reason backfill breakdown + staleness, growth/trend escalation, per-device collection result, ACI delete safety valve, opt-in auto-tag |
56
34
  | `v1.7.1` | `4.5.9` and `4.6.2` validated; shared branch for `4.5.x` and `4.6.x` with capability-gated 4.6 features | Superseded by `v1.7.2`; ACI BD/L3Out graduation + FHRP churn fix (replaces yanked 1.7.0 and 1.6.2) |
57
35
  | `v1.7.0` | `4.5.9` and `4.6.2` validated; shared branch for `4.5.x` and `4.6.x` with capability-gated 4.6 features | Superseded by `v1.7.1`; ACI bridge domain and L3Out NQE maps; query publish hardening |
58
- | `v1.7.0` | `4.5.9` and `4.6.2` validated; shared branch for `4.5.x` and `4.6.x` with capability-gated 4.6 features | Superseded by `v1.7.0`; ACI bridge domain and L3Out NQE maps; query publish hardening |
59
36
  | `v1.6.2` | `4.5.9` and `4.6.2` validated; shared branch for `4.5.x` and `4.6.x` with capability-gated 4.6 features | Superseded by `v1.7.0`; completes the 1.6.1 line (1.6.1 was yanked — its PyPI build predated these): device tag scope now covers VLANs/VRFs and prefixes derive from connected interface subnets; the FHRP group churn (delete+recreate every sync) is fixed by identity-bucket sharding; device analysis is a first-class model with a fleet list view, REST API, and an Open in Forward deep-link. |
60
37
  | `v1.6.1` | `4.5.9` and `4.6.2` validated; shared branch for `4.5.x` and `4.6.x` with capability-gated 4.6 features | Superseded by `v1.6.2`; matures the 1.6.0 features and tooling — Device Analysis is now a NetBox model with a fleet-wide list view, REST API, and per-device-FK panel scoping (with up-interface blast-radius and opt-in post-sync refresh); adds a schedulable collection-gap alert command, run-history drill-down links, and hardened release tooling (one-command release script, generated CHANGELOG, conventional-commit hook). |
61
38
  | `v1.6.0` | `4.5.9` and `4.6.2` validated; shared branch for `4.5.x` and `4.6.x` with capability-gated 4.6 features | Superseded by `v1.6.1`; ships the blue-sky tranche — release automation (`invoke release`), an Operations Guide, a collection-gap health signal, a sync run-history panel, a read-only device analysis panel (GA reachability / connectivity-degree blast radius / CVE exposure), and a bidirectional per-model drift report. |
@@ -207,9 +184,21 @@ Forward 26.6 is the baseline for async NQE.
207
184
  | `v0.1.1` | Added pagination, shared built-in NQE helpers, and release/doc cleanup. |
208
185
  | `v0.1.0` | Initial unsupported NetBox plugin release with built-in Forward sync workflow and seeded NQE maps. |
209
186
 
210
- ## Support Disclaimer
187
+ ## Support
188
+
189
+ Forward Field Integration is an officially maintained Forward Networks integration
190
+ for NetBox. Supported NetBox and `netbox-branching` versions are listed in the
191
+ Release Compatibility table above; support targets the latest released version.
211
192
 
212
- This repository is provided for use at your own risk. It is an unsupported release and is not an officially supported Forward product. There is no warranty, support commitment, or compatibility guarantee beyond the version table above.
193
+ - **Bugs / feature requests:** open a GitHub issue using the provided templates.
194
+ - **Security vulnerabilities:** report privately per [SECURITY.md](SECURITY.md) —
195
+ do not open a public issue.
196
+ - **Upgrades:** follow the [Upgrade and Rollback guide](docs/01_User_Guide/upgrade.md);
197
+ back up the NetBox database before upgrading.
198
+
199
+ Deploy on a supported NetBox version and review the deployment security notes in
200
+ `SECURITY.md` (credential-at-rest and the sync trust boundary) before production
201
+ use.
213
202
 
214
203
  ## Features
215
204
 
@@ -249,10 +238,16 @@ This repository is provided for use at your own risk. It is an unsupported relea
249
238
 
250
239
  1. Install the plugin into the same Python environment as NetBox:
251
240
 
252
- Install the wheel or source archive from GitHub Releases:
241
+ Install the latest release from PyPI:
242
+
243
+ ```bash
244
+ pip install forward-netbox
245
+ ```
246
+
247
+ Or install a specific wheel or source archive from GitHub Releases:
253
248
 
254
249
  ```bash
255
- pip install /path/to/forward_netbox-0.9.4.1-py3-none-any.whl
250
+ pip install /path/to/forward_netbox-2.2.5-py3-none-any.whl
256
251
  ```
257
252
 
258
253
  2. Enable both plugins in the NetBox configuration:
@@ -337,4 +332,3 @@ Normal UI/API sync jobs default to native multi-branch execution, with a default
337
332
  - [Model Mapping Matrix](docs/02_Reference/model-mapping-matrix.md)
338
333
  - [Shipped NQE Query Files](https://github.com/forwardnetworks/forward-netbox/tree/main/forward_netbox/queries)
339
334
  - [License](LICENSE)
340
-
@@ -8,9 +8,9 @@ class NetboxForwardConfig(PluginConfig):
8
8
  name = "forward_netbox"
9
9
  verbose_name = "Forward Field Integration"
10
10
  description = "Sync Forward data into NetBox using built-in NQE queries."
11
- version = "2.2.4"
11
+ version = "2.3.0"
12
12
  base_url = "forward"
13
- min_version = "4.6.3"
13
+ min_version = "4.6.4"
14
14
 
15
15
  def ready(self):
16
16
  super().ready()
@@ -436,6 +436,19 @@ class ForwardSourceForm(NetBoxModelForm):
436
436
  "filtered in NetBox."
437
437
  ),
438
438
  )
439
+ self.fields["sync_device_tags"] = FlexibleMultipleChoiceField(
440
+ required=False,
441
+ choices=(),
442
+ widget=APISelectMultiple(
443
+ api_url="/api/plugins/forward/source/available-tags/"
444
+ ),
445
+ label="Sync Device Tags",
446
+ help_text=(
447
+ "Forward device tags (e.g. Mgmt_*) to sync onto NetBox devices as "
448
+ "device tags. Select one or more; leave empty to sync none. "
449
+ "Independent of the include/exclude scope filters above."
450
+ ),
451
+ )
439
452
  _configure_api_select(
440
453
  self.fields["network_id"].widget,
441
454
  {
@@ -448,6 +461,7 @@ class ForwardSourceForm(NetBoxModelForm):
448
461
  )
449
462
  self.fields["device_tag_include_tags"].widget.attrs["multiple"] = "multiple"
450
463
  self.fields["device_tag_exclude_tags"].widget.attrs["multiple"] = "multiple"
464
+ self.fields["sync_device_tags"].widget.attrs["multiple"] = "multiple"
451
465
  _configure_api_select(
452
466
  self.fields["device_tag_include_tags"].widget,
453
467
  {
@@ -470,6 +484,17 @@ class ForwardSourceForm(NetBoxModelForm):
470
484
  "network_id": "$network_id",
471
485
  },
472
486
  )
487
+ _configure_api_select(
488
+ self.fields["sync_device_tags"].widget,
489
+ {
490
+ "type": "$type",
491
+ "url": "$url",
492
+ "username": "$username",
493
+ "password": "$password",
494
+ "verify": "$verify",
495
+ "network_id": "$network_id",
496
+ },
497
+ )
473
498
  if self.instance.pk:
474
499
  self.fields["network_id"].widget.add_query_param(
475
500
  "source_id", self.instance.pk
@@ -480,6 +505,9 @@ class ForwardSourceForm(NetBoxModelForm):
480
505
  self.fields["device_tag_exclude_tags"].widget.add_query_param(
481
506
  "source_id", self.instance.pk
482
507
  )
508
+ self.fields["sync_device_tags"].widget.add_query_param(
509
+ "source_id", self.instance.pk
510
+ )
483
511
 
484
512
  parameters = self.instance.parameters or {}
485
513
  existing_username = parameters.get("username")
@@ -549,9 +577,11 @@ class ForwardSourceForm(NetBoxModelForm):
549
577
  self.fields["network_id"].choices = _selected_choice(existing_network_id)
550
578
  include_bound = []
551
579
  exclude_bound = []
580
+ sync_tags_bound = []
552
581
  if self.is_bound:
553
582
  include_bound = self._bound_tag_values("device_tag_include_tags")
554
583
  exclude_bound = self._bound_tag_values("device_tag_exclude_tags")
584
+ sync_tags_bound = self._bound_tag_values("sync_device_tags")
555
585
  include_initial = (
556
586
  include_bound
557
587
  if self.is_bound
@@ -568,14 +598,22 @@ class ForwardSourceForm(NetBoxModelForm):
568
598
  if exclude_initial is None and parameters.get("device_tag_exclude"):
569
599
  exclude_initial = [parameters.get("device_tag_exclude")]
570
600
  exclude_initial = self._normalize_tag_values(exclude_initial)
601
+ sync_tags_initial = (
602
+ sync_tags_bound if self.is_bound else parameters.get("sync_device_tags")
603
+ )
604
+ sync_tags_initial = self._normalize_tag_values(sync_tags_initial)
571
605
  self.fields["device_tag_include_tags"].initial = include_initial
572
606
  self.fields["device_tag_exclude_tags"].initial = exclude_initial
607
+ self.fields["sync_device_tags"].initial = sync_tags_initial
573
608
  self.fields["device_tag_include_tags"].choices = [
574
609
  (tag, tag) for tag in include_initial
575
610
  ]
576
611
  self.fields["device_tag_exclude_tags"].choices = [
577
612
  (tag, tag) for tag in exclude_initial
578
613
  ]
614
+ self.fields["sync_device_tags"].choices = [
615
+ (tag, tag) for tag in sync_tags_initial
616
+ ]
579
617
  self.fields["device_tag_include_match"].initial = (
580
618
  parameters.get("device_tag_include_match") or "any"
581
619
  )
@@ -619,6 +657,7 @@ class ForwardSourceForm(NetBoxModelForm):
619
657
  "device_tag_filter_mode",
620
658
  "device_tag_prune_out_of_scope",
621
659
  "apply_device_scope_tags",
660
+ "sync_device_tags",
622
661
  name="Parameters",
623
662
  )
624
663
  )
@@ -649,6 +688,7 @@ class ForwardSourceForm(NetBoxModelForm):
649
688
  "device_tag_filter_mode",
650
689
  "device_tag_prune_out_of_scope",
651
690
  "apply_device_scope_tags",
691
+ "sync_device_tags",
652
692
  name="Parameters",
653
693
  )
654
694
  )
@@ -690,6 +730,11 @@ class ForwardSourceForm(NetBoxModelForm):
690
730
  exclude_tags = self._normalize_tag_values(
691
731
  self._bound_tag_values("device_tag_exclude_tags")
692
732
  )
733
+ sync_device_tags = self._normalize_tag_values(cleaned.get("sync_device_tags"))
734
+ if not sync_device_tags:
735
+ sync_device_tags = self._normalize_tag_values(
736
+ self._bound_tag_values("sync_device_tags")
737
+ )
693
738
  candidate_parameters = {
694
739
  "username": username,
695
740
  "password": password,
@@ -794,6 +839,7 @@ class ForwardSourceForm(NetBoxModelForm):
794
839
  cleaned.get("device_tag_prune_out_of_scope")
795
840
  ),
796
841
  "apply_device_scope_tags": bool(cleaned.get("apply_device_scope_tags")),
842
+ "sync_device_tags": sync_device_tags,
797
843
  }
798
844
  self.instance.type = source_type
799
845
  self.instance.url = (
@@ -834,6 +880,9 @@ class ForwardSourceForm(NetBoxModelForm):
834
880
  self.fields["device_tag_exclude_tags"].choices = [
835
881
  (tag, tag) for tag in exclude_tags
836
882
  ]
883
+ self.fields["sync_device_tags"].choices = [
884
+ (tag, tag) for tag in sync_device_tags
885
+ ]
837
886
  return cleaned
838
887
 
839
888
  def save(self, *args, **kwargs):
@@ -861,6 +910,13 @@ class ForwardSourceForm(NetBoxModelForm):
861
910
  exclude_tags = self._normalize_tag_values(
862
911
  self._bound_tag_values("device_tag_exclude_tags")
863
912
  )
913
+ sync_device_tags = self._normalize_tag_values(
914
+ self.cleaned_data.get("sync_device_tags")
915
+ )
916
+ if not sync_device_tags:
917
+ sync_device_tags = self._normalize_tag_values(
918
+ self._bound_tag_values("sync_device_tags")
919
+ )
864
920
  self.instance.parameters = {
865
921
  "username": self.cleaned_data.get("username")
866
922
  or existing_parameters.get("username")
@@ -976,6 +1032,7 @@ class ForwardSourceForm(NetBoxModelForm):
976
1032
  "apply_device_scope_tags": bool(
977
1033
  self.cleaned_data.get("apply_device_scope_tags")
978
1034
  ),
1035
+ "sync_device_tags": sync_device_tags,
979
1036
  }
980
1037
  self.instance.status = ForwardSourceStatusChoices.NEW
981
1038
  return super().save(*args, **kwargs)
@@ -1205,7 +1262,6 @@ class ForwardSyncForm(NetBoxModelForm):
1205
1262
  parameters = {
1206
1263
  "execution_backend": ForwardExecutionBackendChoices.SINGLE_BRANCH,
1207
1264
  "auto_merge": cleaned.get("auto_merge", False),
1208
- "multi_branch": True,
1209
1265
  "snapshot_id": snapshot_id,
1210
1266
  "enable_bulk_orm": bool(cleaned.get("enable_bulk_orm", False)),
1211
1267
  "bulk_orm_models": list(
@@ -1230,7 +1286,6 @@ class ForwardSyncForm(NetBoxModelForm):
1230
1286
  parameters = {
1231
1287
  "execution_backend": ForwardExecutionBackendChoices.SINGLE_BRANCH,
1232
1288
  "auto_merge": self.cleaned_data.get("auto_merge", False),
1233
- "multi_branch": True,
1234
1289
  "snapshot_id": self.cleaned_data.get("snapshot_id")
1235
1290
  or LATEST_PROCESSED_SNAPSHOT,
1236
1291
  "enable_bulk_orm": bool(self.cleaned_data.get("enable_bulk_orm", False)),
@@ -53,8 +53,23 @@ def _resolve_request_user(*, sync, job=None):
53
53
  return job.user
54
54
  if getattr(sync, "user", None) is not None:
55
55
  return sync.user
56
+ # No invoking or owning user. Inventory-wide writes and their ObjectChange
57
+ # attribution would otherwise SILENTLY run as an arbitrary superuser, leaving
58
+ # an unexplainable audit trail. Keep the run working (a user FK is required
59
+ # downstream) but surface the fallback loudly so an operator assigns a proper
60
+ # sync owner.
56
61
  User = get_user_model()
57
- return User.objects.filter(is_active=True, is_superuser=True).order_by("pk").first()
62
+ fallback = (
63
+ User.objects.filter(is_active=True, is_superuser=True).order_by("pk").first()
64
+ )
65
+ logger.warning(
66
+ "Forward sync %s has no invoking or owning user; attributing changes to "
67
+ "fallback superuser '%s'. Assign an owner to the sync so inventory writes "
68
+ "are attributed correctly.",
69
+ getattr(sync, "pk", "?"),
70
+ getattr(fallback, "username", None) or "<none>",
71
+ )
72
+ return fallback
58
73
 
59
74
 
60
75
  def _normalize_job_log_level(level):
@@ -121,6 +136,24 @@ def record_timeout_issue(ingestion, phase, message):
121
136
  )
122
137
 
123
138
 
139
+ def _sync_has_active_job(sync, name):
140
+ """True if a pending/running job with ``name`` already exists for ``sync``.
141
+
142
+ Post-sync overlays are enqueued after EVERY sync; a slow/large overlay (e.g.
143
+ the vsys parent-link's full-network fetch) can still be running when the next
144
+ sync fires, so without this guard duplicate jobs pile up in PENDING behind it
145
+ and look 'hung'. Skipping the enqueue when one is already active keeps at most
146
+ one overlay of each kind queued per sync.
147
+ """
148
+ return sync.jobs.filter(
149
+ name=name,
150
+ status__in=[
151
+ JobStatusChoices.STATUS_PENDING,
152
+ JobStatusChoices.STATUS_RUNNING,
153
+ ],
154
+ ).exists()
155
+
156
+
124
157
  def _maybe_enqueue_device_analysis_refresh(sync):
125
158
  """Opt-in: after a successful sync, refresh the device-analysis overlay.
126
159
 
@@ -132,11 +165,14 @@ def _maybe_enqueue_device_analysis_refresh(sync):
132
165
  try:
133
166
  from django.utils.module_loading import import_string
134
167
 
168
+ name = f"{sync.name} - refresh device analysis (auto)"
169
+ if _sync_has_active_job(sync, name):
170
+ return
135
171
  Job.enqueue(
136
172
  import_string("forward_netbox.jobs.refresh_forward_device_analysis"),
137
173
  instance=sync,
138
174
  user=sync.user,
139
- name=f"{sync.name} - refresh device analysis (auto)",
175
+ name=name,
140
176
  )
141
177
  except Exception as exc: # pragma: no cover - defensive
142
178
  logger.warning("Auto device-analysis refresh enqueue failed: %s", exc)
@@ -156,11 +192,14 @@ def _maybe_enqueue_backfilled_tag_refresh(sync):
156
192
  try:
157
193
  from django.utils.module_loading import import_string
158
194
 
195
+ name = f"{sync.name} - tag backfilled devices (auto)"
196
+ if _sync_has_active_job(sync, name):
197
+ return
159
198
  Job.enqueue(
160
199
  import_string("forward_netbox.jobs.tag_forward_backfilled_devices"),
161
200
  instance=sync,
162
201
  user=sync.user,
163
- name=f"{sync.name} - tag backfilled devices (auto)",
202
+ name=name,
164
203
  )
165
204
  except Exception as exc: # pragma: no cover - defensive
166
205
  logger.warning("Auto backfilled-tag refresh enqueue failed: %s", exc)
@@ -180,11 +219,14 @@ def _maybe_enqueue_vsys_parent_link(sync):
180
219
  try:
181
220
  from django.utils.module_loading import import_string
182
221
 
222
+ name = f"{sync.name} - link vsys/vdom parents (auto)"
223
+ if _sync_has_active_job(sync, name):
224
+ return
183
225
  Job.enqueue(
184
226
  import_string("forward_netbox.jobs.link_forward_vsys_parents"),
185
227
  instance=sync,
186
228
  user=sync.user,
187
- name=f"{sync.name} - link vsys/vdom parents (auto)",
229
+ name=name,
188
230
  )
189
231
  except Exception as exc: # pragma: no cover - defensive
190
232
  logger.warning("Auto vsys parent-link enqueue failed: %s", exc)
@@ -202,11 +244,14 @@ def _maybe_enqueue_auto_prune(sync):
202
244
  try:
203
245
  from django.utils.module_loading import import_string
204
246
 
247
+ name = f"{sync.name} - prune orphans (auto)"
248
+ if _sync_has_active_job(sync, name):
249
+ return
205
250
  Job.enqueue(
206
251
  import_string("forward_netbox.jobs.prune_forward_orphans"),
207
252
  instance=sync,
208
253
  user=sync.user,
209
- name=f"{sync.name} - prune orphans (auto)",
254
+ name=name,
210
255
  )
211
256
  except Exception as exc: # pragma: no cover - defensive
212
257
  logger.warning("Auto prune-orphans enqueue failed: %s", exc)
@@ -523,7 +568,7 @@ def create_forward_module_bays(job, *args, **kwargs):
523
568
  def forward_dependency_preview(job, *args, **kwargs):
524
569
  """Background dependency dry-run preview for a sync.
525
570
 
526
- The dry-run builds a full multi-branch plan against live Forward data, which
571
+ The dry-run builds a full single-branch plan against live Forward data, which
527
572
  far exceeds an HTTP gateway timeout on large fabrics. Run it as a job and
528
573
  cache the JSON payload on ``job.data`` so the preview page can render it
529
574
  later without a Forward round-trip.
@@ -0,0 +1,105 @@
1
+ import logging
2
+
3
+ from core.choices import JobStatusChoices
4
+ from core.models import Job
5
+ from django.contrib.contenttypes.models import ContentType
6
+ from django.core.management.base import BaseCommand
7
+ from django.utils import timezone
8
+
9
+ from forward_netbox.models import ForwardIngestion
10
+ from forward_netbox.models import ForwardSource
11
+ from forward_netbox.models import ForwardSync
12
+ from forward_netbox.utilities.job_liveness import job_has_live_execution
13
+
14
+ logger = logging.getLogger("forward_netbox.metrics")
15
+
16
+ ACTIVE_JOB_STATUSES = (
17
+ JobStatusChoices.STATUS_PENDING,
18
+ JobStatusChoices.STATUS_RUNNING,
19
+ )
20
+
21
+
22
+ def _metric(lines, name, help_text, value, mtype="gauge", labels=""):
23
+ lines.append(f"# HELP {name} {help_text}")
24
+ lines.append(f"# TYPE {name} {mtype}")
25
+ label_block = f"{{{labels}}}" if labels else ""
26
+ lines.append(f"{name}{label_block} {value}")
27
+
28
+
29
+ class Command(BaseCommand):
30
+ help = (
31
+ "Emit forward_netbox metrics in Prometheus text-exposition format on "
32
+ "stdout, for a node_exporter textfile collector or a scrape sidecar. "
33
+ "Read-only; safe to run on a schedule."
34
+ )
35
+
36
+ def handle(self, *args, **options):
37
+ lines = []
38
+
39
+ _metric(
40
+ lines,
41
+ "forward_sources_total",
42
+ "Configured Forward sources.",
43
+ ForwardSource.objects.count(),
44
+ )
45
+ _metric(
46
+ lines,
47
+ "forward_syncs_total",
48
+ "Configured Forward syncs.",
49
+ ForwardSync.objects.count(),
50
+ )
51
+ _metric(
52
+ lines,
53
+ "forward_ingestions_total",
54
+ "Recorded Forward ingestion runs.",
55
+ ForwardIngestion.objects.count(),
56
+ )
57
+
58
+ forward_type_ids = list(
59
+ ContentType.objects.filter(app_label="forward_netbox").values_list(
60
+ "pk", flat=True
61
+ )
62
+ )
63
+ jobs = Job.objects.filter(object_type_id__in=forward_type_ids)
64
+
65
+ status_counts = {}
66
+ # ChoiceSet entries may be (value, label) or (value, label, color); take
67
+ # the value.
68
+ for choice in JobStatusChoices.CHOICES:
69
+ status = choice[0]
70
+ status_counts[status] = jobs.filter(status=status).count()
71
+ lines.append("# HELP forward_jobs Forward background jobs by status.")
72
+ lines.append("# TYPE forward_jobs gauge")
73
+ for status, count in status_counts.items():
74
+ lines.append(f'forward_jobs{{status="{status}"}} {count}')
75
+
76
+ active = jobs.filter(status__in=ACTIVE_JOB_STATUSES)
77
+ stuck = sum(1 for job in active if not job_has_live_execution(job))
78
+ _metric(
79
+ lines,
80
+ "forward_stuck_jobs",
81
+ "Active jobs with no live worker execution (wedged).",
82
+ stuck,
83
+ )
84
+
85
+ last_completed = (
86
+ jobs.filter(status=JobStatusChoices.STATUS_COMPLETED)
87
+ .exclude(completed__isnull=True)
88
+ .order_by("-completed")
89
+ .first()
90
+ )
91
+ if last_completed and last_completed.completed:
92
+ _metric(
93
+ lines,
94
+ "forward_last_completed_job_timestamp_seconds",
95
+ "Unix time of the most recent completed Forward job.",
96
+ int(last_completed.completed.timestamp()),
97
+ )
98
+ _metric(
99
+ lines,
100
+ "forward_last_completed_job_age_seconds",
101
+ "Seconds since the most recent completed Forward job.",
102
+ int((timezone.now() - last_completed.completed).total_seconds()),
103
+ )
104
+
105
+ self.stdout.write("\n".join(lines) + "\n")