netbox-cable-patcher 0.4.1__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 (26) hide show
  1. netbox_cable_patcher-0.4.1/LICENSE +1 -0
  2. netbox_cable_patcher-0.4.1/PKG-INFO +85 -0
  3. netbox_cable_patcher-0.4.1/README.md +57 -0
  4. netbox_cable_patcher-0.4.1/netbox_cable_patcher/__init__.py +20 -0
  5. netbox_cable_patcher-0.4.1/netbox_cable_patcher/api/__init__.py +1 -0
  6. netbox_cable_patcher-0.4.1/netbox_cable_patcher/api/serializers.py +239 -0
  7. netbox_cable_patcher-0.4.1/netbox_cable_patcher/api/urls.py +12 -0
  8. netbox_cable_patcher-0.4.1/netbox_cable_patcher/api/views.py +500 -0
  9. netbox_cable_patcher-0.4.1/netbox_cable_patcher/filtersets.py +1 -0
  10. netbox_cable_patcher-0.4.1/netbox_cable_patcher/forms.py +1 -0
  11. netbox_cable_patcher-0.4.1/netbox_cable_patcher/models.py +2 -0
  12. netbox_cable_patcher-0.4.1/netbox_cable_patcher/navigation.py +9 -0
  13. netbox_cable_patcher-0.4.1/netbox_cable_patcher/static/netbox_cable_patcher/css/patcher.css +802 -0
  14. netbox_cable_patcher-0.4.1/netbox_cable_patcher/static/netbox_cable_patcher/js/patcher.js +2093 -0
  15. netbox_cable_patcher-0.4.1/netbox_cable_patcher/tables.py +1 -0
  16. netbox_cable_patcher-0.4.1/netbox_cable_patcher/templates/netbox_cable_patcher/patcher.html +402 -0
  17. netbox_cable_patcher-0.4.1/netbox_cable_patcher/urls.py +7 -0
  18. netbox_cable_patcher-0.4.1/netbox_cable_patcher/views.py +22 -0
  19. netbox_cable_patcher-0.4.1/netbox_cable_patcher.egg-info/PKG-INFO +85 -0
  20. netbox_cable_patcher-0.4.1/netbox_cable_patcher.egg-info/SOURCES.txt +24 -0
  21. netbox_cable_patcher-0.4.1/netbox_cable_patcher.egg-info/dependency_links.txt +1 -0
  22. netbox_cable_patcher-0.4.1/netbox_cable_patcher.egg-info/requires.txt +8 -0
  23. netbox_cable_patcher-0.4.1/netbox_cable_patcher.egg-info/top_level.txt +1 -0
  24. netbox_cable_patcher-0.4.1/pyproject.toml +53 -0
  25. netbox_cable_patcher-0.4.1/setup.cfg +4 -0
  26. netbox_cable_patcher-0.4.1/tests/test_netbox_cable_patcher.py +504 -0
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,85 @@
1
+ Metadata-Version: 2.4
2
+ Name: netbox-cable-patcher
3
+ Version: 0.4.1
4
+ Summary: NetBox plugin providing a visual patch bay interface for managing cable connections.
5
+ Author: NetBox Cable Patcher Contributors
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Documentation, https://github.com/netbox-community/netbox-cable-patcher/blob/main/README.md
8
+ Project-URL: Source, https://github.com/netbox-community/netbox-cable-patcher
9
+ Project-URL: Tracker, https://github.com/netbox-community/netbox-cable-patcher/issues
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Natural Language :: English
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Requires-Python: >=3.10.0
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Provides-Extra: test
21
+ Requires-Dist: black==24.3.0; extra == "test"
22
+ Requires-Dist: check-manifest==0.49; extra == "test"
23
+ Requires-Dist: flake8; extra == "test"
24
+ Requires-Dist: flake8-pyproject; extra == "test"
25
+ Requires-Dist: pre-commit==3.7.0; extra == "test"
26
+ Requires-Dist: pytest==8.1.1; extra == "test"
27
+ Dynamic: license-file
28
+
29
+ # NetBox Cable Patcher
30
+
31
+ NetBox plugin for netbox_cable_patcher.
32
+
33
+
34
+ * Free software: Apache-2.0
35
+ * Documentation: https://.github.io/netbox-cable-patcher/
36
+
37
+
38
+ ## Features
39
+
40
+ The features the plugin provides should be listed here.
41
+
42
+ ## Compatibility
43
+
44
+ | NetBox Version | Plugin Version |
45
+ |----------------|----------------|
46
+ | 4.0 | 0.1.0 |
47
+
48
+ ## Installing
49
+
50
+ For adding to a NetBox Docker setup see
51
+ [the general instructions for using netbox-docker with plugins](https://github.com/netbox-community/netbox-docker/wiki/Using-Netbox-Plugins).
52
+
53
+ While this is still in development and not yet on pypi you can install with pip:
54
+
55
+ ```bash
56
+ pip install git+https://github.com//netbox-cable-patcher
57
+ ```
58
+
59
+ or by adding to your `local_requirements.txt` or `plugin_requirements.txt` (netbox-docker):
60
+
61
+ ```bash
62
+ git+https://github.com//netbox-cable-patcher
63
+ ```
64
+
65
+ Enable the plugin in `/opt/netbox/netbox/netbox/configuration.py`,
66
+ or if you use netbox-docker, your `/configuration/plugins.py` file :
67
+
68
+ ```python
69
+ PLUGINS = [
70
+ 'netbox-cable-patcher'
71
+ ]
72
+
73
+ PLUGINS_CONFIG = {
74
+ "netbox-cable-patcher": {},
75
+ }
76
+ ```
77
+
78
+ ## Credits
79
+
80
+ Based on the NetBox plugin tutorial:
81
+
82
+ - [demo repository](https://github.com/netbox-community/netbox-plugin-demo)
83
+ - [tutorial](https://github.com/netbox-community/netbox-plugin-tutorial)
84
+
85
+ This package was created with [Cookiecutter](https://github.com/audreyr/cookiecutter) and the [`netbox-community/cookiecutter-netbox-plugin`](https://github.com/netbox-community/cookiecutter-netbox-plugin) project template.
@@ -0,0 +1,57 @@
1
+ # NetBox Cable Patcher
2
+
3
+ NetBox plugin for netbox_cable_patcher.
4
+
5
+
6
+ * Free software: Apache-2.0
7
+ * Documentation: https://.github.io/netbox-cable-patcher/
8
+
9
+
10
+ ## Features
11
+
12
+ The features the plugin provides should be listed here.
13
+
14
+ ## Compatibility
15
+
16
+ | NetBox Version | Plugin Version |
17
+ |----------------|----------------|
18
+ | 4.0 | 0.1.0 |
19
+
20
+ ## Installing
21
+
22
+ For adding to a NetBox Docker setup see
23
+ [the general instructions for using netbox-docker with plugins](https://github.com/netbox-community/netbox-docker/wiki/Using-Netbox-Plugins).
24
+
25
+ While this is still in development and not yet on pypi you can install with pip:
26
+
27
+ ```bash
28
+ pip install git+https://github.com//netbox-cable-patcher
29
+ ```
30
+
31
+ or by adding to your `local_requirements.txt` or `plugin_requirements.txt` (netbox-docker):
32
+
33
+ ```bash
34
+ git+https://github.com//netbox-cable-patcher
35
+ ```
36
+
37
+ Enable the plugin in `/opt/netbox/netbox/netbox/configuration.py`,
38
+ or if you use netbox-docker, your `/configuration/plugins.py` file :
39
+
40
+ ```python
41
+ PLUGINS = [
42
+ 'netbox-cable-patcher'
43
+ ]
44
+
45
+ PLUGINS_CONFIG = {
46
+ "netbox-cable-patcher": {},
47
+ }
48
+ ```
49
+
50
+ ## Credits
51
+
52
+ Based on the NetBox plugin tutorial:
53
+
54
+ - [demo repository](https://github.com/netbox-community/netbox-plugin-demo)
55
+ - [tutorial](https://github.com/netbox-community/netbox-plugin-tutorial)
56
+
57
+ This package was created with [Cookiecutter](https://github.com/audreyr/cookiecutter) and the [`netbox-community/cookiecutter-netbox-plugin`](https://github.com/netbox-community/cookiecutter-netbox-plugin) project template.
@@ -0,0 +1,20 @@
1
+ """Top-level package for NetBox Cable Patcher."""
2
+
3
+ __author__ = """John Doe"""
4
+ __email__ = ""
5
+ __version__ = "0.4.1"
6
+
7
+
8
+ from netbox.plugins import PluginConfig
9
+
10
+
11
+ class NetBoxCablePatcherConfig(PluginConfig):
12
+ name = "netbox_cable_patcher"
13
+ verbose_name = "Cable Patcher"
14
+ description = "Visual patch bay interface for NetBox with interactive cable connections."
15
+ version = __version__
16
+ base_url = "cable-patcher"
17
+ min_version = "4.0.0"
18
+
19
+
20
+ config = NetBoxCablePatcherConfig
@@ -0,0 +1 @@
1
+ # API module for NetBox Cable Patcher
@@ -0,0 +1,239 @@
1
+ from rest_framework import serializers
2
+ from dcim.models import (
3
+ Site, Location, Rack, Device, Interface,
4
+ FrontPort, RearPort, PowerPort, PowerOutlet,
5
+ ConsolePort, ConsoleServerPort, Cable,
6
+ ModuleBay, Module, ModuleType
7
+ )
8
+
9
+
10
+ class SiteSerializer(serializers.ModelSerializer):
11
+ class Meta:
12
+ model = Site
13
+ fields = ['id', 'name', 'slug']
14
+
15
+
16
+ class LocationSerializer(serializers.ModelSerializer):
17
+ class Meta:
18
+ model = Location
19
+ fields = ['id', 'name', 'slug', 'site']
20
+
21
+
22
+ class RackSerializer(serializers.ModelSerializer):
23
+ class Meta:
24
+ model = Rack
25
+ fields = ['id', 'name', 'site', 'location']
26
+
27
+
28
+ class PortSerializer(serializers.Serializer):
29
+ """Generic serializer for any port type."""
30
+ id = serializers.IntegerField()
31
+ name = serializers.CharField()
32
+ type = serializers.CharField(source='get_type_display', required=False, allow_null=True)
33
+ cable_id = serializers.SerializerMethodField()
34
+ connected_endpoint = serializers.SerializerMethodField()
35
+ port_type = serializers.SerializerMethodField()
36
+ module_bay_id = serializers.SerializerMethodField()
37
+
38
+ def get_cable_id(self, obj):
39
+ if hasattr(obj, 'cable') and obj.cable:
40
+ return obj.cable.id
41
+ return None
42
+
43
+ def get_connected_endpoint(self, obj):
44
+ if hasattr(obj, 'connected_endpoints') and obj.connected_endpoints:
45
+ endpoints = obj.connected_endpoints
46
+ if endpoints:
47
+ endpoint = endpoints[0]
48
+ return {
49
+ 'id': endpoint.id,
50
+ 'name': endpoint.name if hasattr(endpoint, 'name') else str(endpoint),
51
+ 'device_id': endpoint.device.id if hasattr(endpoint, 'device') else None,
52
+ 'device_name': endpoint.device.name if hasattr(endpoint, 'device') else None,
53
+ 'type': endpoint.__class__.__name__
54
+ }
55
+ return None
56
+
57
+ def get_port_type(self, obj):
58
+ return obj.__class__.__name__
59
+
60
+ untagged_vlan_vid = serializers.SerializerMethodField()
61
+
62
+ def get_untagged_vlan_vid(self, obj):
63
+ if hasattr(obj, 'untagged_vlan') and obj.untagged_vlan:
64
+ return obj.untagged_vlan.vid
65
+ return None
66
+
67
+ def get_module_bay_id(self, obj):
68
+ if hasattr(obj, 'module') and obj.module:
69
+ return obj.module.module_bay_id
70
+ return None
71
+
72
+
73
+ class ModuleBaySerializer(serializers.Serializer):
74
+ """Serializer for module bay data within a device."""
75
+ id = serializers.IntegerField()
76
+ name = serializers.CharField()
77
+ position = serializers.CharField(allow_null=True, required=False)
78
+ label = serializers.CharField(allow_blank=True, required=False)
79
+ installed_module = serializers.SerializerMethodField()
80
+
81
+ def get_installed_module(self, obj):
82
+ if not hasattr(obj, 'installed_module') or not obj.installed_module:
83
+ return None
84
+ mod = obj.installed_module
85
+ return {
86
+ 'id': mod.id,
87
+ 'module_type': {
88
+ 'id': mod.module_type_id,
89
+ 'model': mod.module_type.model,
90
+ 'manufacturer': mod.module_type.manufacturer.name,
91
+ },
92
+ 'status': mod.status,
93
+ }
94
+
95
+
96
+ class ModuleTypeSearchSerializer(serializers.ModelSerializer):
97
+ """Serializer for module type search results."""
98
+ manufacturer_name = serializers.CharField(source='manufacturer.name', read_only=True)
99
+
100
+ class Meta:
101
+ model = ModuleType
102
+ fields = ['id', 'model', 'manufacturer_name', 'description', 'part_number']
103
+
104
+
105
+ class ModuleInstallSerializer(serializers.Serializer):
106
+ """Serializer for validating module installation requests."""
107
+ module_type_id = serializers.IntegerField()
108
+ serial = serializers.CharField(required=False, allow_blank=True, default='')
109
+ asset_tag = serializers.CharField(required=False, allow_blank=True, allow_null=True, default=None)
110
+
111
+
112
+ class DeviceWithPortsSerializer(serializers.ModelSerializer):
113
+ interfaces = serializers.SerializerMethodField()
114
+ power_ports = serializers.SerializerMethodField()
115
+ power_outlets = serializers.SerializerMethodField()
116
+ front_ports = serializers.SerializerMethodField()
117
+ rear_ports = serializers.SerializerMethodField()
118
+ console_ports = serializers.SerializerMethodField()
119
+ console_server_ports = serializers.SerializerMethodField()
120
+ module_bays = serializers.SerializerMethodField()
121
+
122
+ class Meta:
123
+ model = Device
124
+ fields = [
125
+ 'id', 'name', 'device_type', 'rack', 'position',
126
+ 'interfaces', 'power_ports', 'power_outlets',
127
+ 'front_ports', 'rear_ports', 'console_ports', 'console_server_ports',
128
+ 'module_bays'
129
+ ]
130
+
131
+ def get_interfaces(self, obj):
132
+ # Exclude non-physical interface types (virtual, lag, bridge)
133
+ qs = obj.interfaces.exclude(type__in=['virtual', 'lag', 'bridge'])
134
+ return PortSerializer(qs, many=True).data
135
+
136
+ def get_power_ports(self, obj):
137
+ return PortSerializer(obj.powerports.all(), many=True).data
138
+
139
+ def get_power_outlets(self, obj):
140
+ return PortSerializer(obj.poweroutlets.all(), many=True).data
141
+
142
+ def get_front_ports(self, obj):
143
+ return PortSerializer(obj.frontports.all(), many=True).data
144
+
145
+ def get_rear_ports(self, obj):
146
+ return PortSerializer(obj.rearports.all(), many=True).data
147
+
148
+ def get_console_ports(self, obj):
149
+ return PortSerializer(obj.consoleports.all(), many=True).data
150
+
151
+ def get_console_server_ports(self, obj):
152
+ return PortSerializer(obj.consoleserverports.all(), many=True).data
153
+
154
+ def get_module_bays(self, obj):
155
+ return ModuleBaySerializer(obj.modulebays.all(), many=True).data
156
+
157
+
158
+ class CableSerializer(serializers.ModelSerializer):
159
+ a_terminations = serializers.SerializerMethodField()
160
+ b_terminations = serializers.SerializerMethodField()
161
+ status_display = serializers.CharField(source='get_status_display', read_only=True)
162
+ type_display = serializers.CharField(source='get_type_display', read_only=True)
163
+
164
+ class Meta:
165
+ model = Cable
166
+ fields = [
167
+ 'id', 'type', 'type_display', 'status', 'status_display',
168
+ 'color', 'label', 'length', 'length_unit',
169
+ 'a_terminations', 'b_terminations'
170
+ ]
171
+
172
+ def get_a_terminations(self, obj):
173
+ return self._serialize_terminations(obj.a_terminations)
174
+
175
+ def get_b_terminations(self, obj):
176
+ return self._serialize_terminations(obj.b_terminations)
177
+
178
+ def _serialize_terminations(self, terminations):
179
+ result = []
180
+ for term in terminations:
181
+ data = {
182
+ 'id': term.id,
183
+ 'name': term.name if hasattr(term, 'name') else str(term),
184
+ 'type': term.__class__.__name__,
185
+ }
186
+ if hasattr(term, 'device'):
187
+ data['device_id'] = term.device.id
188
+ data['device_name'] = term.device.name
189
+ result.append(data)
190
+ return result
191
+
192
+
193
+ # Defines which port types may be connected to which other port types.
194
+ # This mirrors NetBox's own cable termination compatibility rules.
195
+ COMPATIBLE_TERMINATION_TYPES = {
196
+ 'Interface': {'Interface', 'FrontPort', 'RearPort'},
197
+ 'FrontPort': {'Interface', 'FrontPort', 'RearPort', 'ConsolePort', 'ConsoleServerPort'},
198
+ 'RearPort': {'Interface', 'FrontPort', 'RearPort', 'ConsolePort', 'ConsoleServerPort'},
199
+ 'ConsolePort': {'ConsoleServerPort', 'FrontPort', 'RearPort'},
200
+ 'ConsoleServerPort': {'ConsolePort', 'FrontPort', 'RearPort'},
201
+ 'PowerPort': {'PowerOutlet'},
202
+ 'PowerOutlet': {'PowerPort'},
203
+ }
204
+
205
+
206
+ class CableCreateSerializer(serializers.Serializer):
207
+ """Serializer for creating cables via the patcher interface."""
208
+ a_termination_type = serializers.CharField()
209
+ a_termination_id = serializers.IntegerField()
210
+ b_termination_type = serializers.CharField()
211
+ b_termination_id = serializers.IntegerField()
212
+ type = serializers.CharField(required=False, allow_blank=True)
213
+ status = serializers.CharField(default='connected')
214
+ color = serializers.CharField(required=False, allow_blank=True)
215
+ label = serializers.CharField(required=False, allow_blank=True)
216
+
217
+ def validate(self, data):
218
+ a_type = data.get('a_termination_type')
219
+ b_type = data.get('b_termination_type')
220
+
221
+ valid_types = set(COMPATIBLE_TERMINATION_TYPES.keys())
222
+
223
+ if a_type not in valid_types:
224
+ raise serializers.ValidationError(
225
+ {'a_termination_type': f'Invalid termination type: {a_type}'}
226
+ )
227
+ if b_type not in valid_types:
228
+ raise serializers.ValidationError(
229
+ {'b_termination_type': f'Invalid termination type: {b_type}'}
230
+ )
231
+
232
+ allowed_b_types = COMPATIBLE_TERMINATION_TYPES.get(a_type, set())
233
+ if b_type not in allowed_b_types:
234
+ raise serializers.ValidationError(
235
+ f'Cannot connect a {a_type} to a {b_type}. '
236
+ f'A {a_type} may only connect to: {", ".join(sorted(allowed_b_types))}.'
237
+ )
238
+
239
+ return data
@@ -0,0 +1,12 @@
1
+ from django.urls import path, include
2
+ from rest_framework.routers import DefaultRouter
3
+ from . import views
4
+
5
+ router = DefaultRouter()
6
+ router.register('locations', views.LocationsViewSet, basename='locations')
7
+ router.register('devices', views.DevicesViewSet, basename='devices')
8
+ router.register('cables', views.CablesViewSet, basename='cables')
9
+ router.register('module-types', views.ModuleTypesViewSet, basename='module-types')
10
+ router.register('module-bays', views.ModuleBaysViewSet, basename='module-bays')
11
+
12
+ urlpatterns = router.urls