first commit
This commit is contained in:
170
netbox_librenms_plugin/views/sync/locations.py
Normal file
170
netbox_librenms_plugin/views/sync/locations.py
Normal file
@@ -0,0 +1,170 @@
|
||||
from collections import namedtuple
|
||||
|
||||
from dcim.models import Site
|
||||
from django.contrib import messages
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.shortcuts import redirect
|
||||
from django_tables2 import SingleTableView
|
||||
|
||||
from netbox_librenms_plugin.filtersets import SiteLocationFilterSet
|
||||
from netbox_librenms_plugin.tables.locations import SiteLocationSyncTable
|
||||
from netbox_librenms_plugin.views.mixins import LibreNMSAPIMixin, LibreNMSPermissionMixin
|
||||
|
||||
|
||||
class SyncSiteLocationView(LibreNMSPermissionMixin, LibreNMSAPIMixin, SingleTableView):
|
||||
"""Synchronize NetBox Sites with LibreNMS locations."""
|
||||
|
||||
table_class = SiteLocationSyncTable
|
||||
template_name = "netbox_librenms_plugin/site_location_sync.html"
|
||||
filterset = SiteLocationFilterSet
|
||||
|
||||
COORDINATE_TOLERANCE = 0.0001
|
||||
SyncData = namedtuple("SyncData", ["netbox_site", "librenms_location", "is_synced"])
|
||||
|
||||
def get_table(self, *args, **kwargs):
|
||||
"""Return the configured sync table."""
|
||||
table = super().get_table(*args, **kwargs)
|
||||
table.configure(self.request)
|
||||
return table
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""Return context with filter form for site-location sync."""
|
||||
context = super().get_context_data(**kwargs)
|
||||
queryset = self.get_queryset()
|
||||
context["filter_form"] = self.filterset(self.request.GET, queryset=queryset).form
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
"""Return sync data pairing NetBox sites with LibreNMS locations."""
|
||||
netbox_sites = Site.objects.all()
|
||||
success, librenms_locations = self.get_librenms_locations()
|
||||
if not success or not isinstance(librenms_locations, list):
|
||||
return []
|
||||
|
||||
sync_data = [self.create_sync_data(site, librenms_locations) for site in netbox_sites]
|
||||
|
||||
if self.request.GET and self.filterset:
|
||||
return self.filterset(self.request.GET, queryset=sync_data).qs
|
||||
|
||||
return sync_data
|
||||
|
||||
def get_librenms_locations(self):
|
||||
"""Fetch all locations from LibreNMS."""
|
||||
return self.librenms_api.get_locations()
|
||||
|
||||
def create_sync_data(self, site, librenms_locations):
|
||||
"""Create a SyncData tuple pairing a site with its LibreNMS location."""
|
||||
matched_location = self.match_site_with_location(site, librenms_locations)
|
||||
if matched_location:
|
||||
is_synced = self.check_coordinates_match(
|
||||
site.latitude,
|
||||
site.longitude,
|
||||
matched_location.get("lat"),
|
||||
matched_location.get("lng"),
|
||||
)
|
||||
return self.SyncData(site, matched_location, is_synced)
|
||||
return self.SyncData(site, None, False)
|
||||
|
||||
def match_site_with_location(self, site, librenms_locations):
|
||||
"""Return the LibreNMS location matching the given site, or None."""
|
||||
for location in librenms_locations:
|
||||
if location["location"].lower() == site.name.lower() or location["location"].lower() == site.slug.lower():
|
||||
return location
|
||||
return None
|
||||
|
||||
def check_coordinates_match(self, site_lat, site_lng, librenms_lat, librenms_lng):
|
||||
"""Return True if site and LibreNMS coordinates match within tolerance."""
|
||||
if None in (site_lat, site_lng, librenms_lat, librenms_lng):
|
||||
return False
|
||||
lat_match = abs(float(site_lat) - float(librenms_lat)) < self.COORDINATE_TOLERANCE
|
||||
lng_match = abs(float(site_lng) - float(librenms_lng)) < self.COORDINATE_TOLERANCE
|
||||
return lat_match and lng_match
|
||||
|
||||
def post(self, request):
|
||||
"""Handle create or update of a LibreNMS location from a NetBox site."""
|
||||
# Check write permission before modifying LibreNMS locations
|
||||
if error := self.require_write_permission():
|
||||
return error
|
||||
|
||||
action = request.POST.get("action")
|
||||
pk = request.POST.get("pk")
|
||||
if not pk:
|
||||
messages.error(request, "No site ID provided.")
|
||||
return redirect("plugins:netbox_librenms_plugin:site_location_sync")
|
||||
|
||||
site = self.get_site_by_pk(pk)
|
||||
if not site:
|
||||
messages.error(request, "Site not found.")
|
||||
return redirect("plugins:netbox_librenms_plugin:site_location_sync")
|
||||
|
||||
if action == "update":
|
||||
return self.update_librenms_location(request, site)
|
||||
if action == "create":
|
||||
return self.create_librenms_location(request, site)
|
||||
|
||||
messages.error(request, f"Unknown action '{action}'.")
|
||||
return redirect("plugins:netbox_librenms_plugin:site_location_sync")
|
||||
|
||||
def get_site_by_pk(self, pk):
|
||||
"""Return the Site for the given pk, or None if not found."""
|
||||
try:
|
||||
return Site.objects.get(pk=pk)
|
||||
except ObjectDoesNotExist:
|
||||
return None
|
||||
|
||||
def create_librenms_location(self, request, site):
|
||||
"""Create a new location in LibreNMS from the given site."""
|
||||
if site.latitude is None or site.longitude is None:
|
||||
messages.warning(
|
||||
request,
|
||||
f"Latitude and/or longitude is missing. Cannot create location '{site.name}' in LibreNMS.",
|
||||
)
|
||||
return redirect("plugins:netbox_librenms_plugin:site_location_sync")
|
||||
|
||||
location_data = self.build_location_data(site)
|
||||
success, message = self.librenms_api.add_location(location_data)
|
||||
if success:
|
||||
messages.success(request, f"Location '{site.name}' created in LibreNMS successfully.")
|
||||
else:
|
||||
messages.error(
|
||||
request,
|
||||
f"Failed to create location '{site.name}' in LibreNMS: {message}",
|
||||
)
|
||||
return redirect("plugins:netbox_librenms_plugin:site_location_sync")
|
||||
|
||||
def update_librenms_location(self, request, site):
|
||||
"""Update an existing LibreNMS location with the site coordinates."""
|
||||
if site.latitude is None or site.longitude is None:
|
||||
messages.warning(
|
||||
request,
|
||||
f"Latitude and/or longitude is missing. Cannot update location '{site.name}' in LibreNMS.",
|
||||
)
|
||||
return redirect("plugins:netbox_librenms_plugin:site_location_sync")
|
||||
|
||||
success, librenms_locations = self.get_librenms_locations()
|
||||
if not success:
|
||||
messages.error(request, "Failed to retrieve LibreNMS locations.")
|
||||
return redirect("plugins:netbox_librenms_plugin:site_location_sync")
|
||||
|
||||
matched_location = self.match_site_with_location(site, librenms_locations)
|
||||
if not matched_location:
|
||||
messages.error(request, f"Could not find matching location for site '{site.name}'")
|
||||
return redirect("plugins:netbox_librenms_plugin:site_location_sync")
|
||||
|
||||
location_data = self.build_location_data(site, include_name=False)
|
||||
success, message = self.librenms_api.update_location(matched_location["location"], location_data)
|
||||
if success:
|
||||
messages.success(request, f"Location '{site.name}' updated in LibreNMS successfully.")
|
||||
else:
|
||||
messages.error(
|
||||
request,
|
||||
f"Failed to update location '{site.name}' in LibreNMS: {message}",
|
||||
)
|
||||
return redirect("plugins:netbox_librenms_plugin:site_location_sync")
|
||||
|
||||
def build_location_data(self, site, include_name=True):
|
||||
"""Build a location data dict from the given site."""
|
||||
data = {"lat": str(site.latitude), "lng": str(site.longitude)}
|
||||
if include_name:
|
||||
data["location"] = site.name
|
||||
return data
|
||||
Reference in New Issue
Block a user