diff --git a/README.md b/README.md
index ec7eeb3..b74299b 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,6 @@
# netbox-interface-sync
-[Русская версия](./README_ru.md)
## Overview
-This plugin allows you to compare and synchronize interfaces between devices and device types in NetBox. It can be useful for finding and correcting inconsistencies between interfaces.
+This plugin allows you to compare and synchronize interfaces between devices and device types in NetBox 4. It can be useful for finding and correcting inconsistencies between interfaces.
Tested with NetBox version 4.0
## Installation
If your NetBox installation uses virtualenv, activate it like this:
diff --git a/README_ru.md b/README_ru.md
deleted file mode 100644
index 0b48464..0000000
--- a/README_ru.md
+++ /dev/null
@@ -1,44 +0,0 @@
-# netbox-interface-sync
-[English version](./README.md)
-## Обзор
-Плагин для NetBox, позволяющий сравнивать и синхронизировать интерфейсы между устройствами (devices) и типами устройств (device types). Полезен для поиска и исправления несоответствий между интерфейсами. Работа проверена с NetBox версий 4.0
-## Установка
-Если NetBox использует virtualenv, то активируйте его, например, так:
-```
-source /opt/netbox/venv/bin/activate
-```
-Установите плагин из репозитория PyPI:
-```
-pip install netbox-interface-sync
-```
-или клонируйте этот репозиторий, затем перейдите в папку с ним и установите плагин:
-```
-pip install .
-```
-Включите плагин в файле `configuration.py` (обычно он находится в `/opt/netbox/netbox/netbox/`), добавьте его имя в список `PLUGINS`:
-```
-PLUGINS = [
- 'netbox_interface_sync'
-]
-```
-Перезапустите NetBox:
-```
-sudo systemctl restart netbox
-```
-## Использование
-Для того чтобы сравнить интерфейсы, откройте страницу нужного устройства и найдите кнопку "Interface sync" справа сверху:
-
-Отметьте требуемые действия напротив интерфейсов флажками и нажмите "Apply".
-
-### Настройки плагина
-Если вы хотите переопределить значения по умолчанию, настройте переменную `PLUGINS_CONFIG` в вашем файле `configuration.py`:
-```
-PLUGINS_CONFIG = {
- 'netbox_interface_sync': {
- 'exclude_virtual_interfaces': True
- }
-}
-```
-| Настройка | Значение по умолчанию | Описание |
-| --- | --- | --- |
-| exclude_virtual_interfaces | `True` | Не учитывать виртуальные интерфейсы (VLAN, LAG) при сравнении
diff --git a/docs/images/1_device_page.png b/docs/images/1_device_page.png
index cca75ba..dd1d67f 100644
Binary files a/docs/images/1_device_page.png and b/docs/images/1_device_page.png differ
diff --git a/docs/images/2_interface_comparison.png b/docs/images/2_interface_comparison.png
index d69170b..b86629a 100644
Binary files a/docs/images/2_interface_comparison.png and b/docs/images/2_interface_comparison.png differ
diff --git a/netbox_interface_sync/__init__.py b/netbox_interface_sync/__init__.py
index f7ceee8..95a688d 100644
--- a/netbox_interface_sync/__init__.py
+++ b/netbox_interface_sync/__init__.py
@@ -3,25 +3,13 @@ from netbox.plugins import PluginConfig
class Config(PluginConfig):
name = 'netbox_interface_sync'
- verbose_name = 'NetBox 4 Interface Synchronization'
- description = 'Compare and synchronize components (interfaces, ports, outlets, etc.) between NetBox 4 device types ' \
- 'and devices'
- version = '0.4.0'
- author = 'based on work by Victor Golovanenko'
- author_email = 'drygdryg2014@yandex.ru'
+ verbose_name = 'NetBox interface synchronization'
+ description = 'Syncing interfaces with the interfaces from device type for NetBox 4'
+ version = '0.4.1'
+ author = 'Keith Knowles'
+ author_email = 'mkknowles@outlook.com'
default_settings = {
- # Ignore case and spaces in names when matching components between device type and device
- 'name_comparison': {
- 'case-insensitive': True,
- 'space-insensitive': True
- },
- # Exclude virtual interfaces (bridge, link aggregation group (LAG), "virtual") from comparison
- 'exclude_virtual_interfaces': True,
- # Add a panel with information about the number of interfaces to the device page
- 'include_interfaces_panel': False,
- # Consider component descriptions when comparing. If this option is set to True, then take into account
- # component descriptions when comparing components and synchronizing their attributes, otherwise - ignore
- 'sync_descriptions': True
+ 'exclude_virtual_interfaces': True
}
diff --git a/netbox_interface_sync/comparison.py b/netbox_interface_sync/comparison.py
deleted file mode 100644
index d643f9d..0000000
--- a/netbox_interface_sync/comparison.py
+++ /dev/null
@@ -1,156 +0,0 @@
-from typing import Optional
-
-import attr
-from attrs import fields
-from django.conf import settings
-
-from netbox.models import PrimaryModel
-
-config = settings.PLUGINS_CONFIG["netbox_interface_sync"]
-SYNC_DESCRIPTIONS: bool = config["sync_descriptions"]
-
-
-@attr.s(frozen=True, auto_attribs=True)
-class BaseComparison:
- """Common fields of a device component"""
- # Do not compare IDs
- id: int = attr.ib(eq=False, metadata={'printable': False, 'netbox_exportable': False})
- # Compare names case-insensitively and spaces-insensitively
- name: str = attr.ib(metadata={'printable': False})
- label: str = attr.ib()
- # Compare descriptions if it is set by the configuration
- description: str = attr.ib(eq=SYNC_DESCRIPTIONS, metadata={'synced': SYNC_DESCRIPTIONS})
- # Do not compare `is_template` properties
- is_template: bool = attr.ib(
- default=False, kw_only=True, eq=False,
- metadata={'printable': False, 'netbox_exportable': False}
- )
-
- @property
- def fields_display(self) -> str:
- """Generate human-readable list of printable fields to display in the comparison table"""
- fields_to_display = []
- for field in fields(self.__class__):
- if not field.metadata.get('printable', True):
- continue
- field_value = getattr(self, field.name)
- if not field_value:
- continue
- field_caption = field.metadata.get('displayed_caption') or field.name.replace('_', ' ').capitalize()
- if isinstance(field_value, BaseComparison):
- field_value = f'{field_value.name} (ID: {field_value.id})'
- fields_to_display.append(f'{field_caption}: {field_value}')
- return '\n'.join(fields_to_display)
-
- def get_fields_for_netbox_component(self, sync=False):
- """
- Returns a dict of fields and values for creating or updating a NetBox component object
- :param sync: if True, returns fields for syncing an existing component, otherwise - for creating a new one.
- """
-
- def field_filter(field: attr.Attribute, _):
- result = field.metadata.get('netbox_exportable', True)
- if sync:
- result &= field.metadata.get('synced', True)
- return result
-
- return attr.asdict(self, recurse=True, filter=field_filter)
-
-
-@attr.s(frozen=True, auto_attribs=True)
-class BaseTypedComparison(BaseComparison):
- """Common fields of a device typed component"""
- type: str = attr.ib(metadata={'printable': False})
- type_display: str = attr.ib(eq=False, metadata={'displayed_caption': 'Type', 'netbox_exportable': False})
-
-
-@attr.s(frozen=True, auto_attribs=True)
-class ConsolePortComparison(BaseTypedComparison):
- """A unified way to represent the consoleport and consoleport template"""
- pass
-
-
-@attr.s(frozen=True, auto_attribs=True)
-class ConsoleServerPortComparison(BaseTypedComparison):
- """A unified way to represent the consoleserverport and consoleserverport template"""
- pass
-
-
-@attr.s(frozen=True, auto_attribs=True)
-class PowerPortComparison(BaseTypedComparison):
- """A unified way to represent the power port and power port template"""
- maximum_draw: str = attr.ib()
- allocated_draw: str = attr.ib()
-
-
-@attr.s(frozen=True, auto_attribs=True)
-class PowerOutletComparison(BaseTypedComparison):
- """A unified way to represent the power outlet and power outlet template"""
- power_port: PowerPortComparison = attr.ib()
- feed_leg: str = attr.ib()
-
-
-@attr.s(frozen=True, auto_attribs=True)
-class InterfaceComparison(BaseTypedComparison):
- """A unified way to represent the interface and interface template"""
- mgmt_only: bool = attr.ib()
-
-
-@attr.s(frozen=True, auto_attribs=True)
-class FrontPortComparison(BaseTypedComparison):
- """A unified way to represent the front port and front port template"""
- color: str = attr.ib()
- # rear_port_id: int
- rear_port_position: int = attr.ib(metadata={'displayed_caption': 'Position'})
-
-
-@attr.s(frozen=True, auto_attribs=True)
-class RearPortComparison(BaseTypedComparison):
- """A unified way to represent the rear port and rear port template"""
- color: str = attr.ib()
- positions: int = attr.ib()
-
-
-@attr.s(frozen=True, auto_attribs=True)
-class DeviceBayComparison(BaseComparison):
- """A unified way to represent the device bay and device bay template"""
- pass
-
-
-def from_netbox_object(netbox_object: PrimaryModel) -> Optional[BaseComparison]:
- """Makes a comparison object from the NetBox object"""
- type_map = {
- "DeviceBay": DeviceBayComparison,
- "Interface": InterfaceComparison,
- "FrontPort": FrontPortComparison,
- "RearPort": RearPortComparison,
- "ConsolePort": ConsolePortComparison,
- "ConsoleServerPort": ConsoleServerPortComparison,
- "PowerPort": PowerPortComparison,
- "PowerOutlet": PowerOutletComparison
- }
-
- obj_name = netbox_object._meta.object_name
- if obj_name.endswith("Template"):
- is_template = True
- obj_name = obj_name[:-8] # TODO: use `removesuffix` introduced in Python 3.9
- else:
- is_template = False
-
- comparison = type_map.get(obj_name)
- if not comparison:
- return
-
- values = {}
- for field in fields(comparison):
- if field.name == "is_template":
- continue
- if field.name == "type_display":
- values[field.name] = netbox_object.get_type_display()
- else:
- field_value = getattr(netbox_object, field.name)
- if isinstance(field_value, PrimaryModel):
- field_value = from_netbox_object(field_value)
- values[field.name] = field_value
-
- return comparison(**values, is_template=is_template)
diff --git a/netbox_interface_sync/forms.py b/netbox_interface_sync/forms.py
new file mode 100644
index 0000000..0799ebe
--- /dev/null
+++ b/netbox_interface_sync/forms.py
@@ -0,0 +1,6 @@
+from django import forms
+
+
+class InterfaceComparisonForm(forms.Form):
+ add_to_device = forms.BooleanField(required=False)
+ remove_from_device = forms.BooleanField(required=False)
diff --git a/netbox_interface_sync/template_content.py b/netbox_interface_sync/template_content.py
index 71a4cc9..750ee09 100644
--- a/netbox_interface_sync/template_content.py
+++ b/netbox_interface_sync/template_content.py
@@ -6,9 +6,9 @@ class DeviceViewExtension(PluginTemplateExtension):
model = "dcim.device"
def buttons(self):
- """Implements a compare button at the top of the page"""
+ """Implements a compare interfaces button at the top of the page"""
obj = self.context['object']
- return self.render("netbox_interface_sync/compare_components_button.html", extra_context={
+ return self.render("netbox_interface_sync/compare_interfaces_button.html", extra_context={
"device": obj
})
diff --git a/netbox_interface_sync/templates/netbox_interface_sync/compare_components_button.html b/netbox_interface_sync/templates/netbox_interface_sync/compare_components_button.html
deleted file mode 100644
index 3682f71..0000000
--- a/netbox_interface_sync/templates/netbox_interface_sync/compare_components_button.html
+++ /dev/null
@@ -1,65 +0,0 @@
-{% if perms.dcim.change_device %}
-
+{% if templates_count == interfaces_count %}
+ The device and device type have the same number of interfaces.
+{% else %}
+ The device and device type have a different number of interfaces.
+ Device: {{ interfaces_count }}
+ Device type: {{ templates_count }}
+{% endif %}
+
+{% if templates_count == interfaces_count %}
+ The device and device type have the same number of interfaces.
+{% else %}
+ The device and device type have a different number of interfaces.
+ Device: {{ interfaces_count }}
+ Device type: {{ templates_count }}
+{% endif %}
+
+{% if templates_count == interfaces_count %}
+ The device and device type have the same number of interfaces.
+{% else %}
+ The device and device type have a different number of interfaces.
+ Device: {{ interfaces_count }}
+ Device type: {{ templates_count }}
+{% endif %}
+