netbox-map 0.1.0__py3-none-any.whl

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 (46) hide show
  1. netbox_map/__init__.py +23 -0
  2. netbox_map/api/__init__.py +0 -0
  3. netbox_map/api/serializers.py +115 -0
  4. netbox_map/api/urls.py +10 -0
  5. netbox_map/api/views.py +30 -0
  6. netbox_map/choices.py +44 -0
  7. netbox_map/filtersets.py +90 -0
  8. netbox_map/forms.py +449 -0
  9. netbox_map/migrations/0001_initial.py +133 -0
  10. netbox_map/migrations/0002_rename_netbox_map_assigne_idx_netbox_map__assigne_eb5b30_idx.py +18 -0
  11. netbox_map/migrations/0003_floorplantile_latitude_longitude_locationcoordinates.py +76 -0
  12. netbox_map/migrations/0004_mapmarker.py +125 -0
  13. netbox_map/migrations/0005_rename_netbox_map__assigne_mapmarker_idx_netbox_map__assigne_6fd3ec_idx.py +18 -0
  14. netbox_map/migrations/0006_mapsettings.py +28 -0
  15. netbox_map/migrations/0007_alter_mapsettings_id.py +18 -0
  16. netbox_map/migrations/__init__.py +0 -0
  17. netbox_map/models.py +464 -0
  18. netbox_map/navigation.py +67 -0
  19. netbox_map/search.py +29 -0
  20. netbox_map/signals.py +150 -0
  21. netbox_map/static/netbox_map/css/floorplan.css +706 -0
  22. netbox_map/static/netbox_map/css/leaflet-1.9.4.min.css +661 -0
  23. netbox_map/static/netbox_map/css/site_map.css +434 -0
  24. netbox_map/static/netbox_map/js/floorplan_editor.js +644 -0
  25. netbox_map/static/netbox_map/js/floorplan_viewer.js +1713 -0
  26. netbox_map/static/netbox_map/js/jspdf.umd.min.js +398 -0
  27. netbox_map/static/netbox_map/js/leaflet-1.9.4.min.js +6 -0
  28. netbox_map/static/netbox_map/js/site_map.js +1354 -0
  29. netbox_map/tables.py +135 -0
  30. netbox_map/template_content.py +41 -0
  31. netbox_map/templates/netbox_map/device_map_locations.html +62 -0
  32. netbox_map/templates/netbox_map/floorplan.html +74 -0
  33. netbox_map/templates/netbox_map/floorplan_visualization.html +560 -0
  34. netbox_map/templates/netbox_map/floorplantile.html +65 -0
  35. netbox_map/templates/netbox_map/inc/device_floorplan_panel.html +25 -0
  36. netbox_map/templates/netbox_map/inc/site_floorplan_panel.html +28 -0
  37. netbox_map/templates/netbox_map/mapmarker.html +82 -0
  38. netbox_map/templates/netbox_map/settings.html +135 -0
  39. netbox_map/templates/netbox_map/site_map.html +121 -0
  40. netbox_map/urls.py +43 -0
  41. netbox_map/views.py +586 -0
  42. netbox_map-0.1.0.dist-info/METADATA +418 -0
  43. netbox_map-0.1.0.dist-info/RECORD +46 -0
  44. netbox_map-0.1.0.dist-info/WHEEL +5 -0
  45. netbox_map-0.1.0.dist-info/licenses/LICENSE +190 -0
  46. netbox_map-0.1.0.dist-info/top_level.txt +1 -0
netbox_map/__init__.py ADDED
@@ -0,0 +1,23 @@
1
+ from netbox.plugins import PluginConfig
2
+
3
+
4
+ class MapConfig(PluginConfig):
5
+ name = 'netbox_map'
6
+ verbose_name = 'NetBox Map'
7
+ author = 'Christian Rose'
8
+ description = 'Interactive floor plan visualization for NetBox sites'
9
+ version = '0.1.0'
10
+ base_url = 'map'
11
+ min_version = '4.5.0'
12
+ default_settings = {
13
+ 'default_grid_width': 20,
14
+ 'default_grid_height': 20,
15
+ 'default_tile_size': 60,
16
+ }
17
+
18
+ def ready(self):
19
+ super().ready()
20
+ from . import signals # noqa: F401
21
+
22
+
23
+ config = MapConfig
File without changes
@@ -0,0 +1,115 @@
1
+ from django.contrib.contenttypes.models import ContentType
2
+ from rest_framework import serializers
3
+
4
+ from dcim.api.serializers_.sites import SiteSerializer
5
+ from dcim.api.serializers import LocationSerializer
6
+ from netbox.api.fields import ContentTypeField
7
+ from netbox.api.serializers import NetBoxModelSerializer
8
+ from utilities.api import get_serializer_for_model
9
+ from ..models import FloorPlan, FloorPlanTile, LocationCoordinates, MapMarker
10
+
11
+
12
+ class FloorPlanSerializer(NetBoxModelSerializer):
13
+ site = SiteSerializer(nested=True)
14
+ location = LocationSerializer(
15
+ nested=True,
16
+ required=False,
17
+ allow_null=True,
18
+ default=None
19
+ )
20
+
21
+ class Meta:
22
+ model = FloorPlan
23
+ fields = [
24
+ 'id', 'url', 'display_url', 'display', 'site', 'location',
25
+ 'name', 'grid_width', 'grid_height', 'tile_size',
26
+ 'background_image', 'description', 'comments',
27
+ 'tags', 'custom_fields', 'created', 'last_updated',
28
+ ]
29
+ brief_fields = ('id', 'url', 'display', 'name', 'site')
30
+
31
+
32
+ class FloorPlanTileSerializer(NetBoxModelSerializer):
33
+ floorplan = FloorPlanSerializer(nested=True)
34
+ assigned_object_type = ContentTypeField(
35
+ queryset=ContentType.objects.filter(
36
+ app_label='dcim',
37
+ model__in=['device', 'rack', 'powerpanel', 'powerfeed'],
38
+ ),
39
+ required=False,
40
+ allow_null=True,
41
+ )
42
+ assigned_object = serializers.SerializerMethodField(read_only=True)
43
+ utilization = serializers.SerializerMethodField(read_only=True)
44
+
45
+ class Meta:
46
+ model = FloorPlanTile
47
+ fields = [
48
+ 'id', 'url', 'display_url', 'display', 'floorplan',
49
+ 'x_position', 'y_position', 'width', 'height',
50
+ 'assigned_object_type', 'assigned_object_id', 'assigned_object',
51
+ 'label', 'tile_type', 'status', 'orientation',
52
+ 'fov_direction', 'fov_angle', 'fov_distance',
53
+ 'latitude', 'longitude',
54
+ 'utilization',
55
+ 'tags', 'custom_fields', 'created', 'last_updated',
56
+ ]
57
+ brief_fields = (
58
+ 'id', 'url', 'display', 'x_position', 'y_position',
59
+ 'label', 'tile_type',
60
+ )
61
+
62
+ def get_assigned_object(self, obj):
63
+ if obj.assigned_object is not None:
64
+ serializer = get_serializer_for_model(obj.assigned_object)
65
+ return serializer(obj.assigned_object, nested=True, context=self.context).data
66
+ return None
67
+
68
+ def get_utilization(self, obj):
69
+ if obj.assigned_object_type and obj.assigned_object_type.model == 'rack' and obj.assigned_object:
70
+ return round(obj.assigned_object.get_utilization(), 1)
71
+ return None
72
+
73
+
74
+ class LocationCoordinatesSerializer(NetBoxModelSerializer):
75
+ location = LocationSerializer(nested=True)
76
+
77
+ class Meta:
78
+ model = LocationCoordinates
79
+ fields = [
80
+ 'id', 'url', 'display_url', 'display', 'location',
81
+ 'latitude', 'longitude',
82
+ 'tags', 'custom_fields', 'created', 'last_updated',
83
+ ]
84
+ brief_fields = ('id', 'url', 'display', 'location', 'latitude', 'longitude')
85
+
86
+
87
+ class MapMarkerSerializer(NetBoxModelSerializer):
88
+ site = SiteSerializer(nested=True, required=False, allow_null=True, default=None)
89
+ assigned_object_type = ContentTypeField(
90
+ queryset=ContentType.objects.filter(
91
+ app_label='dcim',
92
+ model__in=['device', 'rack', 'powerpanel', 'powerfeed'],
93
+ ),
94
+ required=False,
95
+ allow_null=True,
96
+ )
97
+ assigned_object = serializers.SerializerMethodField(read_only=True)
98
+
99
+ class Meta:
100
+ model = MapMarker
101
+ fields = [
102
+ 'id', 'url', 'display_url', 'display',
103
+ 'latitude', 'longitude', 'label', 'marker_type', 'status',
104
+ 'site', 'fov_direction', 'fov_angle', 'fov_distance',
105
+ 'assigned_object_type', 'assigned_object_id', 'assigned_object',
106
+ 'description',
107
+ 'tags', 'custom_fields', 'created', 'last_updated',
108
+ ]
109
+ brief_fields = ('id', 'url', 'display', 'label', 'marker_type', 'latitude', 'longitude')
110
+
111
+ def get_assigned_object(self, obj):
112
+ if obj.assigned_object is not None:
113
+ serializer = get_serializer_for_model(obj.assigned_object)
114
+ return serializer(obj.assigned_object, nested=True, context=self.context).data
115
+ return None
netbox_map/api/urls.py ADDED
@@ -0,0 +1,10 @@
1
+ from netbox.api.routers import NetBoxRouter
2
+ from . import views
3
+
4
+ router = NetBoxRouter()
5
+ router.register('floorplans', views.FloorPlanViewSet)
6
+ router.register('floorplan-tiles', views.FloorPlanTileViewSet)
7
+ router.register('location-coordinates', views.LocationCoordinatesViewSet)
8
+ router.register('map-markers', views.MapMarkerViewSet)
9
+
10
+ urlpatterns = router.urls
@@ -0,0 +1,30 @@
1
+ from netbox.api.viewsets import NetBoxModelViewSet
2
+ from .. import filtersets
3
+ from ..models import FloorPlan, FloorPlanTile, LocationCoordinates, MapMarker
4
+ from .serializers import (
5
+ FloorPlanSerializer, FloorPlanTileSerializer, LocationCoordinatesSerializer,
6
+ MapMarkerSerializer,
7
+ )
8
+
9
+
10
+ class FloorPlanViewSet(NetBoxModelViewSet):
11
+ queryset = FloorPlan.objects.all()
12
+ serializer_class = FloorPlanSerializer
13
+ filterset_class = filtersets.FloorPlanFilterSet
14
+
15
+
16
+ class FloorPlanTileViewSet(NetBoxModelViewSet):
17
+ queryset = FloorPlanTile.objects.select_related('floorplan', 'assigned_object_type')
18
+ serializer_class = FloorPlanTileSerializer
19
+ filterset_class = filtersets.FloorPlanTileFilterSet
20
+
21
+
22
+ class LocationCoordinatesViewSet(NetBoxModelViewSet):
23
+ queryset = LocationCoordinates.objects.select_related('location__site')
24
+ serializer_class = LocationCoordinatesSerializer
25
+
26
+
27
+ class MapMarkerViewSet(NetBoxModelViewSet):
28
+ queryset = MapMarker.objects.select_related('site', 'assigned_object_type')
29
+ serializer_class = MapMarkerSerializer
30
+ filterset_class = filtersets.MapMarkerFilterSet
netbox_map/choices.py ADDED
@@ -0,0 +1,44 @@
1
+ from netbox.choices import ChoiceSet
2
+ from django.utils.translation import gettext_lazy as _
3
+
4
+
5
+ class FloorPlanTileTypeChoices(ChoiceSet):
6
+ TYPE_RACK = 'rack'
7
+ TYPE_AISLE = 'aisle'
8
+ TYPE_WALL = 'wall'
9
+ TYPE_COLUMN = 'column'
10
+ TYPE_DOOR = 'door'
11
+ TYPE_COOLING = 'cooling'
12
+ TYPE_POWER = 'power'
13
+ TYPE_EMPTY = 'empty'
14
+ TYPE_RESERVED = 'reserved'
15
+ TYPE_AP = 'ap'
16
+ TYPE_CAMERA = 'camera'
17
+ TYPE_PRINTER = 'printer'
18
+
19
+ CHOICES = [
20
+ (TYPE_RACK, _('Rack'), 'blue'),
21
+ (TYPE_AISLE, _('Aisle'), 'gray'),
22
+ (TYPE_WALL, _('Wall'), 'dark'),
23
+ (TYPE_COLUMN, _('Column'), 'dark'),
24
+ (TYPE_DOOR, _('Door'), 'teal'),
25
+ (TYPE_COOLING, _('Cooling'), 'cyan'),
26
+ (TYPE_POWER, _('Power'), 'yellow'),
27
+ (TYPE_EMPTY, _('Empty'), 'white'),
28
+ (TYPE_RESERVED, _('Reserved'), 'orange'),
29
+ (TYPE_AP, _('Access Point'), 'purple'),
30
+ (TYPE_CAMERA, _('Camera'), 'red'),
31
+ (TYPE_PRINTER, _('Printer'), 'orange'),
32
+ ]
33
+
34
+
35
+ class FloorPlanTileStatusChoices(ChoiceSet):
36
+ STATUS_ACTIVE = 'active'
37
+ STATUS_PLANNED = 'planned'
38
+ STATUS_DECOMMISSIONED = 'decommissioned'
39
+
40
+ CHOICES = [
41
+ (STATUS_ACTIVE, _('Active'), 'green'),
42
+ (STATUS_PLANNED, _('Planned'), 'cyan'),
43
+ (STATUS_DECOMMISSIONED, _('Decommissioned'), 'red'),
44
+ ]
@@ -0,0 +1,90 @@
1
+ import django_filters
2
+ from django.contrib.contenttypes.models import ContentType
3
+ from django.utils.translation import gettext_lazy as _
4
+
5
+ from dcim.models import Site, Location
6
+ from netbox.filtersets import NetBoxModelFilterSet
7
+ from .models import FloorPlan, FloorPlanTile, MapMarker
8
+ from .choices import FloorPlanTileTypeChoices, FloorPlanTileStatusChoices
9
+
10
+
11
+ class FloorPlanFilterSet(NetBoxModelFilterSet):
12
+ site_id = django_filters.ModelMultipleChoiceFilter(
13
+ queryset=Site.objects.all(),
14
+ label=_('Site (ID)'),
15
+ )
16
+ site = django_filters.ModelMultipleChoiceFilter(
17
+ field_name='site__slug',
18
+ queryset=Site.objects.all(),
19
+ to_field_name='slug',
20
+ label=_('Site (slug)'),
21
+ )
22
+ location_id = django_filters.ModelMultipleChoiceFilter(
23
+ queryset=Location.objects.all(),
24
+ label=_('Location (ID)'),
25
+ )
26
+
27
+ class Meta:
28
+ model = FloorPlan
29
+ fields = ['id', 'name']
30
+
31
+ def search(self, queryset, name, value):
32
+ return queryset.filter(name__icontains=value)
33
+
34
+
35
+ class FloorPlanTileFilterSet(NetBoxModelFilterSet):
36
+ floorplan_id = django_filters.ModelMultipleChoiceFilter(
37
+ queryset=FloorPlan.objects.all(),
38
+ label=_('Floor Plan (ID)'),
39
+ )
40
+ assigned_object_type = django_filters.ModelChoiceFilter(
41
+ queryset=ContentType.objects.filter(
42
+ app_label='dcim',
43
+ model__in=['device', 'rack', 'powerpanel', 'powerfeed'],
44
+ ),
45
+ label=_('Object Type'),
46
+ )
47
+ tile_type = django_filters.MultipleChoiceFilter(
48
+ choices=FloorPlanTileTypeChoices,
49
+ label=_('Tile Type'),
50
+ )
51
+ status = django_filters.MultipleChoiceFilter(
52
+ choices=FloorPlanTileStatusChoices,
53
+ label=_('Status'),
54
+ )
55
+
56
+ class Meta:
57
+ model = FloorPlanTile
58
+ fields = ['id', 'floorplan_id', 'assigned_object_type', 'tile_type', 'status',
59
+ 'x_position', 'y_position']
60
+
61
+ def search(self, queryset, name, value):
62
+ return queryset.filter(label__icontains=value)
63
+
64
+
65
+ class MapMarkerFilterSet(NetBoxModelFilterSet):
66
+ site_id = django_filters.ModelMultipleChoiceFilter(
67
+ queryset=Site.objects.all(),
68
+ label=_('Site (ID)'),
69
+ )
70
+ site = django_filters.ModelMultipleChoiceFilter(
71
+ field_name='site__slug',
72
+ queryset=Site.objects.all(),
73
+ to_field_name='slug',
74
+ label=_('Site (slug)'),
75
+ )
76
+ marker_type = django_filters.MultipleChoiceFilter(
77
+ choices=FloorPlanTileTypeChoices,
78
+ label=_('Marker Type'),
79
+ )
80
+ status = django_filters.MultipleChoiceFilter(
81
+ choices=FloorPlanTileStatusChoices,
82
+ label=_('Status'),
83
+ )
84
+
85
+ class Meta:
86
+ model = MapMarker
87
+ fields = ['id', 'site_id', 'marker_type', 'status']
88
+
89
+ def search(self, queryset, name, value):
90
+ return queryset.filter(label__icontains=value)