mirror of
https://github.com/drygdryg/netbox-plugin-interface-sync
synced 2024-11-25 18:10:52 +03:00
Ready for netboxcommunity
This plugin has been re-developed for Netbox 4. The sync button has been redesigned as well as the colors of the sync table.
This commit is contained in:
parent
51b75bc6d5
commit
b48cfaca4b
@ -3,25 +3,13 @@ from netbox.plugins import PluginConfig
|
|||||||
|
|
||||||
class Config(PluginConfig):
|
class Config(PluginConfig):
|
||||||
name = 'netbox_interface_sync'
|
name = 'netbox_interface_sync'
|
||||||
verbose_name = 'NetBox 4 Interface Synchronization'
|
verbose_name = 'NetBox interface synchronization'
|
||||||
description = 'Compare and synchronize components (interfaces, ports, outlets, etc.) between NetBox 4 device types ' \
|
description = 'Syncing interfaces with the interfaces from device type for NetBox 4'
|
||||||
'and devices'
|
|
||||||
version = '0.4.0'
|
version = '0.4.0'
|
||||||
author = 'based on work by Victor Golovanenko'
|
author = 'Keith Knowles'
|
||||||
author_email = 'drygdryg2014@yandex.ru'
|
author_email = 'mkknowles@outlook.com'
|
||||||
default_settings = {
|
default_settings = {
|
||||||
# Ignore case and spaces in names when matching components between device type and device
|
'exclude_virtual_interfaces': True
|
||||||
'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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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)
|
|
6
netbox_interface_sync/forms.py
Normal file
6
netbox_interface_sync/forms.py
Normal file
@ -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)
|
@ -6,9 +6,9 @@ class DeviceViewExtension(PluginTemplateExtension):
|
|||||||
model = "dcim.device"
|
model = "dcim.device"
|
||||||
|
|
||||||
def buttons(self):
|
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']
|
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
|
"device": obj
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,65 +0,0 @@
|
|||||||
{% if perms.dcim.change_device %}
|
|
||||||
<div class="dropdown">
|
|
||||||
<button id="device-type-sync" type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
|
||||||
Device type sync
|
|
||||||
</button>
|
|
||||||
<ul class="dropdown-menu" aria-labeled-by="device-type-sync">
|
|
||||||
{% if perms.dcim.add_consoleport %}
|
|
||||||
<li>
|
|
||||||
<a class="dropdown-item" href="{% url 'plugins:netbox_interface_sync:consoleport_comparison' device_id=device.id %}">
|
|
||||||
Console Ports
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.dcim.add_consoleserverport %}
|
|
||||||
<li>
|
|
||||||
<a class="dropdown-item" href="{% url 'plugins:netbox_interface_sync:consoleserverport_comparison' device_id=device.id %}">
|
|
||||||
Console Server Ports
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.dcim.add_powerport %}
|
|
||||||
<li>
|
|
||||||
<a class="dropdown-item" href="{% url 'plugins:netbox_interface_sync:powerport_comparison' device_id=device.id %}">
|
|
||||||
Power Ports
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.dcim.add_poweroutlet %}
|
|
||||||
<li>
|
|
||||||
<a class="dropdown-item" href="{% url 'plugins:netbox_interface_sync:poweroutlet_comparison' device_id=device.id %}">
|
|
||||||
Power Outlets
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.dcim.add_interface %}
|
|
||||||
<li>
|
|
||||||
<a class="dropdown-item" href="{% url 'plugins:netbox_interface_sync:interface_comparison' device_id=device.id %}">
|
|
||||||
Interfaces
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{# {% if perms.dcim.add_frontport %}#}
|
|
||||||
{# <li>#}
|
|
||||||
{# <a class="dropdown-item" href="{% url 'plugins:netbox_interface_sync:frontport_comparison' device_id=device.id %}">#}
|
|
||||||
{# Front Ports#}
|
|
||||||
{# </a>#}
|
|
||||||
{# </li>#}
|
|
||||||
{# {% endif %}#}
|
|
||||||
{% if perms.dcim.add_rearport %}
|
|
||||||
<li>
|
|
||||||
<a class="dropdown-item" href="{% url 'plugins:netbox_interface_sync:rearport_comparison' device_id=device.id %}">
|
|
||||||
Rear Ports
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.dcim.add_devicebay %}
|
|
||||||
<li>
|
|
||||||
<a class="dropdown-item" href="{% url 'plugins:netbox_interface_sync:devicebay_comparison' device_id=device.id %}">
|
|
||||||
Device Bays
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
@ -0,0 +1,3 @@
|
|||||||
|
<a href="{% url 'plugins:netbox_interface_sync:interface_comparison' device_id=device.id %}" class="btn btn-purple"><i class="mdi mdi-sync" aria-hidden="true"></i>
|
||||||
|
Interface Sync
|
||||||
|
</a>
|
@ -0,0 +1,3 @@
|
|||||||
|
<a href="{% url 'plugins:netbox_interface_sync:interface_comparison' device_id=device.id %}" class="btn btn-purple">
|
||||||
|
Interface Sync
|
||||||
|
</a>
|
@ -1,158 +0,0 @@
|
|||||||
{% extends 'base/layout.html' %}
|
|
||||||
|
|
||||||
{% block title %}{{ device }} - {{ component_type_name|capfirst }} comparison{% endblock %}
|
|
||||||
{% block header %}
|
|
||||||
<nav class="breadcrumb-container px-3" aria-label="breadcrumb">
|
|
||||||
<ol class="breadcrumb">
|
|
||||||
<li class="breadcrumb-item"><a href="{% url 'dcim:device_list' %}">Devices</a></li>
|
|
||||||
<li class="breadcrumb-item"><a href="{% url 'dcim:device_list' %}?site={{ device.site.slug }}">{{ device.site }}</a></li>
|
|
||||||
<li class="breadcrumb-item"><a href="{% url 'dcim:device' pk=device.id %}">{{ device }}</a></li>
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
{{ block.super }}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<style>
|
|
||||||
.caption-red {
|
|
||||||
caption-side: top;
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.caption-green {
|
|
||||||
caption-side: top;
|
|
||||||
color: green;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script>
|
|
||||||
function toggle(event) {
|
|
||||||
event = event || window.event;
|
|
||||||
const src = event.target || event.srcElement || event;
|
|
||||||
const checkboxes = document.getElementsByName(src.id);
|
|
||||||
for(const checkbox of checkboxes) checkbox.checked = src.checked;
|
|
||||||
}
|
|
||||||
|
|
||||||
function uncheck(event) {
|
|
||||||
event = event || window.event;
|
|
||||||
const src = event.target || event.srcElement || event;
|
|
||||||
if (src.checked === false) {
|
|
||||||
document.getElementById(src.name).checked = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class="table-responsive-xl">
|
|
||||||
<table class="table table-hover table-bordered">
|
|
||||||
{% if templates_count == components_count %}
|
|
||||||
<caption class="caption-green">
|
|
||||||
The device and device type have the same number of {{ component_type_name }}.
|
|
||||||
</caption>
|
|
||||||
{% else %}
|
|
||||||
<caption class="caption-red">
|
|
||||||
The device and device type have different number of {{ component_type_name }}.<br>
|
|
||||||
Device: {{ components_count }}<br>
|
|
||||||
Device type: {{ templates_count }}
|
|
||||||
</caption>
|
|
||||||
{% endif %}
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col" colspan="2">Device type</th>
|
|
||||||
<th scope="col">Actions</th>
|
|
||||||
<th scope="col" colspan="2">Device</th>
|
|
||||||
<th scope="col" colspan="2">Actions</th>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="col">Name</th>
|
|
||||||
<th scope="col">Attributes</th>
|
|
||||||
<th scope="col">
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" id="add" onclick="toggle(this)">
|
|
||||||
Add to the device
|
|
||||||
</label>
|
|
||||||
</th>
|
|
||||||
<th scope="col">Name</th>
|
|
||||||
<th scope="col">Attributes</th>
|
|
||||||
<th scope="col">
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" id="remove" onclick="toggle(this)">
|
|
||||||
Remove
|
|
||||||
</label>
|
|
||||||
</th>
|
|
||||||
<th scope="col">
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" id="sync" onclick="toggle(this)">
|
|
||||||
Sync attributes
|
|
||||||
</label>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for component_template, component in comparison_items %}
|
|
||||||
<tr>
|
|
||||||
{% if component_template %}
|
|
||||||
<th scope="row" {% if not component %}class="table-danger"{% endif %}>
|
|
||||||
{% if component and component_template.name != component.name %}
|
|
||||||
<span style="background-color: #eab2b2">{{ component_template.name }}</span>
|
|
||||||
{% else %}
|
|
||||||
{{ component_template.name }}
|
|
||||||
{% endif %}
|
|
||||||
</th>
|
|
||||||
<td style="white-space:pre" {% if not component %}class="table-danger"{% endif %}>{{ component_template.fields_display }}</td>
|
|
||||||
<td {% if not component %}class="table-danger"{% endif %}>
|
|
||||||
{% if not component %}
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" name="add" value="{{ component_template.id }}" onclick="uncheck(this)">
|
|
||||||
Add to device
|
|
||||||
</label>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
{% else %}
|
|
||||||
<th scope="row"> </th>
|
|
||||||
<td> </td>
|
|
||||||
<td> </td>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if component %}
|
|
||||||
<th scope="row" {% if not component_template %}class="table-success"{% endif %}>
|
|
||||||
{% if component_template and component_template.name != component.name %}
|
|
||||||
<span style="background-color: #cde8c2">{{ component.name }}</span>
|
|
||||||
{% else %}
|
|
||||||
{{ component.name }}
|
|
||||||
{% endif %}
|
|
||||||
</th>
|
|
||||||
<td style="white-space:pre" {% if not component_template %}class="table-success"{% endif %}>{{ component.fields_display }}</td>
|
|
||||||
<td {% if not component_template %}class="table-success"{% endif %}>
|
|
||||||
{% if not component_template %}
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" name="remove" value="{{ component.id }}" onclick="uncheck(this)">
|
|
||||||
Remove
|
|
||||||
</label>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td {% if not component_template %}class="table-success"{% endif %}>
|
|
||||||
{% if component_template and component_template != component %}
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" name="sync" value="{{ component.id }}" onclick="uncheck(this)">
|
|
||||||
Sync attributes
|
|
||||||
</label>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
{% else %}
|
|
||||||
<td> </td>
|
|
||||||
<td> </td>
|
|
||||||
<td> </td>
|
|
||||||
<td> </td>
|
|
||||||
{% endif %}
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<input type="submit" value="Apply" class="btn btn-primary" style="float: right;">
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
@ -0,0 +1,161 @@
|
|||||||
|
{% extends 'base/layout.html' %}
|
||||||
|
|
||||||
|
{% block title %}{{ device }} - Interface comparison{% endblock %}
|
||||||
|
{% block header %}
|
||||||
|
<nav class="breadcrumb-container px-3" aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="{% url 'dcim:device_list' %}">Devices</a></li>
|
||||||
|
<li class="breadcrumb-item"><a href="{% url 'dcim:device_list' %}?site={{ device.site.slug }}">{{ device.site }}</a></li>
|
||||||
|
<li class="breadcrumb-item"><a href="{% url 'dcim:device' pk=device.id %}">{{ device }}</a></li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
{{ block.super }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<style>
|
||||||
|
.checkbox-group {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
function toggle(event) {
|
||||||
|
event = event || window.event;
|
||||||
|
var src = event.target || event.srcElement || event;
|
||||||
|
checkboxes = document.getElementsByName(src.id);
|
||||||
|
for(var checkbox of checkboxes) checkbox.checked = src.checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
function uncheck(event) {
|
||||||
|
event = event || window.event;
|
||||||
|
var src = event.target || event.srcElement || event;
|
||||||
|
if (src.checked == false) {
|
||||||
|
document.getElementById(src.name).checked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{% 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.<br>
|
||||||
|
Device: {{ interfaces_count }}<br>
|
||||||
|
Device type: {{ templates_count }}
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form method="post">
|
||||||
|
<!-- Interface templates -->
|
||||||
|
{% csrf_token %}
|
||||||
|
<table class="table" style="width: 50%; float: left;">
|
||||||
|
<tr>
|
||||||
|
<th colspan="2">Device Type</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>
|
||||||
|
<label class="checkbox-group">
|
||||||
|
<input type="checkbox" id="add_to_device" onclick="toggle(this)">
|
||||||
|
Add To The Device
|
||||||
|
</label>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
{% for template, interface in comparison_items %}
|
||||||
|
{% if template %}
|
||||||
|
<tr {% if not interface %}class="success" data-mark-connected="true"{% endif %}>
|
||||||
|
<td>
|
||||||
|
{% if interface and template.name != interface.name %}
|
||||||
|
<span style="background-color: #cde8c2">{{ template.name }}</span>
|
||||||
|
{% else %}
|
||||||
|
{{ template.name }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{ template.type_display }}</td>
|
||||||
|
<td>
|
||||||
|
{% if not interface %}
|
||||||
|
<label class="checkbox-group">
|
||||||
|
<input type="checkbox" name="add_to_device" value="{{ template.id }}" onclick="uncheck(this)">
|
||||||
|
Add To Device
|
||||||
|
</label>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<td> </td>
|
||||||
|
<td> </td>
|
||||||
|
<td> </td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table class="table" style="width: 50%; float: right;">
|
||||||
|
<!-- Interfaces -->
|
||||||
|
<tr>
|
||||||
|
<th colspan="2">Device</th>
|
||||||
|
<th colspan="2">Actions</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>
|
||||||
|
<label class="checkbox-group">
|
||||||
|
<input type="checkbox" id="remove_from_device" onclick="toggle(this)">
|
||||||
|
Remove
|
||||||
|
</label>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<label class="checkbox-group">
|
||||||
|
<input type="checkbox" id="fix_name" onclick="toggle(this)">
|
||||||
|
Fix The Name
|
||||||
|
</label>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
{% for template, interface in comparison_items %}
|
||||||
|
{% if interface %}
|
||||||
|
<tr {% if not template %}class="danger" data-enabled="disabled"{% endif %}>
|
||||||
|
<td>
|
||||||
|
{% if template and template.name != interface.name %}
|
||||||
|
<span style="background-color: #eab2b2">{{ interface.name }}</span>
|
||||||
|
{% else %}
|
||||||
|
{{ interface.name }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{ interface.type_display }}</td>
|
||||||
|
<td>
|
||||||
|
{% if not template %}
|
||||||
|
<label class="checkbox-group">
|
||||||
|
<input type="checkbox" name="remove_from_device" value="{{ interface.id }}" onclick="uncheck(this)">
|
||||||
|
Remove
|
||||||
|
</label>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if template and template.name != interface.name %}
|
||||||
|
<label class="checkbox-group">
|
||||||
|
<input type="checkbox" name="fix_name" value="{{ interface.id }}" onclick="uncheck(this)">
|
||||||
|
Fix Name
|
||||||
|
</label>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<td> </td>
|
||||||
|
<td> </td>
|
||||||
|
<td> </td>
|
||||||
|
<td> </td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
<div class="text-right">
|
||||||
|
<input type="submit" value="Apply" class="btn btn-primary">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -0,0 +1,161 @@
|
|||||||
|
{% extends 'base/layout.html' %}
|
||||||
|
|
||||||
|
{% block title %}{{ device }} - Interface comparison{% endblock %}
|
||||||
|
{% block header %}
|
||||||
|
<nav class="breadcrumb-container px-3" aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="{% url 'dcim:device_list' %}">Devices</a></li>
|
||||||
|
<li class="breadcrumb-item"><a href="{% url 'dcim:device_list' %}?site={{ device.site.slug }}">{{ device.site }}</a></li>
|
||||||
|
<li class="breadcrumb-item"><a href="{% url 'dcim:device' pk=device.id %}">{{ device }}</a></li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
{{ block.super }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<style>
|
||||||
|
.checkbox-group {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
function toggle(event) {
|
||||||
|
event = event || window.event;
|
||||||
|
var src = event.target || event.srcElement || event;
|
||||||
|
checkboxes = document.getElementsByName(src.id);
|
||||||
|
for(var checkbox of checkboxes) checkbox.checked = src.checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
function uncheck(event) {
|
||||||
|
event = event || window.event;
|
||||||
|
var src = event.target || event.srcElement || event;
|
||||||
|
if (src.checked == false) {
|
||||||
|
document.getElementById(src.name).checked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{% 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.<br>
|
||||||
|
Device: {{ interfaces_count }}<br>
|
||||||
|
Device type: {{ templates_count }}
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form method="post">
|
||||||
|
<!-- Interface templates -->
|
||||||
|
{% csrf_token %}
|
||||||
|
<table class="table" style="width: 50%; float: left;">
|
||||||
|
<tr>
|
||||||
|
<th colspan="2">Device Type</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>
|
||||||
|
<label class="checkbox-group">
|
||||||
|
<input type="checkbox" id="add_to_device" onclick="toggle(this)">
|
||||||
|
Add To The Device
|
||||||
|
</label>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
{% for template, interface in comparison_items %}
|
||||||
|
{% if template %}
|
||||||
|
<tr {% if not interface %}class="success" data-enabled="enabled"{% endif %}>
|
||||||
|
<td>
|
||||||
|
{% if interface and template.name != interface.name %}
|
||||||
|
<span style="background-color: #cde8c2">{{ template.name }}</span>
|
||||||
|
{% else %}
|
||||||
|
{{ template.name }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{ template.type_display }}</td>
|
||||||
|
<td>
|
||||||
|
{% if not interface %}
|
||||||
|
<label class="checkbox-group">
|
||||||
|
<input type="checkbox" name="add_to_device" value="{{ template.id }}" onclick="uncheck(this)">
|
||||||
|
Add To Device
|
||||||
|
</label>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<td> </td>
|
||||||
|
<td> </td>
|
||||||
|
<td> </td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table class="table" style="width: 50%; float: right;">
|
||||||
|
<!-- Interfaces -->
|
||||||
|
<tr>
|
||||||
|
<th colspan="2">Device</th>
|
||||||
|
<th colspan="2">Actions</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>
|
||||||
|
<label class="checkbox-group">
|
||||||
|
<input type="checkbox" id="remove_from_device" onclick="toggle(this)">
|
||||||
|
Remove
|
||||||
|
</label>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<label class="checkbox-group">
|
||||||
|
<input type="checkbox" id="fix_name" onclick="toggle(this)">
|
||||||
|
Fix The Name
|
||||||
|
</label>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
{% for template, interface in comparison_items %}
|
||||||
|
{% if interface %}
|
||||||
|
<tr {% if not template %}class="danger"{% endif %}>
|
||||||
|
<td>
|
||||||
|
{% if template and template.name != interface.name %}
|
||||||
|
<span style="background-color: #cde8c2">{{ interface.name }}</span>
|
||||||
|
{% else %}
|
||||||
|
{{ interface.name }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{ interface.type_display }}</td>
|
||||||
|
<td>
|
||||||
|
{% if not template %}
|
||||||
|
<label class="checkbox-group">
|
||||||
|
<input type="checkbox" name="remove_from_device" value="{{ interface.id }}" onclick="uncheck(this)">
|
||||||
|
Remove
|
||||||
|
</label>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if template and template.name != interface.name %}
|
||||||
|
<label class="checkbox-group">
|
||||||
|
<input type="checkbox" name="fix_name" value="{{ interface.id }}" onclick="uncheck(this)">
|
||||||
|
Fix Name
|
||||||
|
</label>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<td> </td>
|
||||||
|
<td> </td>
|
||||||
|
<td> </td>
|
||||||
|
<td> </td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
<div class="text-right">
|
||||||
|
<input type="submit" value="Apply" class="btn btn-primary">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -0,0 +1,161 @@
|
|||||||
|
{% extends 'base/layout.html' %}
|
||||||
|
|
||||||
|
{% block title %}{{ device }} - Interface comparison{% endblock %}
|
||||||
|
{% block header %}
|
||||||
|
<nav class="breadcrumb-container px-3" aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="{% url 'dcim:device_list' %}">Devices</a></li>
|
||||||
|
<li class="breadcrumb-item"><a href="{% url 'dcim:device_list' %}?site={{ device.site.slug }}">{{ device.site }}</a></li>
|
||||||
|
<li class="breadcrumb-item"><a href="{% url 'dcim:device' pk=device.id %}">{{ device }}</a></li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
{{ block.super }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<style>
|
||||||
|
.checkbox-group {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
function toggle(event) {
|
||||||
|
event = event || window.event;
|
||||||
|
var src = event.target || event.srcElement || event;
|
||||||
|
checkboxes = document.getElementsByName(src.id);
|
||||||
|
for(var checkbox of checkboxes) checkbox.checked = src.checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
function uncheck(event) {
|
||||||
|
event = event || window.event;
|
||||||
|
var src = event.target || event.srcElement || event;
|
||||||
|
if (src.checked == false) {
|
||||||
|
document.getElementById(src.name).checked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{% 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.<br>
|
||||||
|
Device: {{ interfaces_count }}<br>
|
||||||
|
Device type: {{ templates_count }}
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form method="post">
|
||||||
|
<!-- Interface templates -->
|
||||||
|
{% csrf_token %}
|
||||||
|
<table class="table" style="width: 50%; float: left;">
|
||||||
|
<tr>
|
||||||
|
<th colspan="2">Device Type</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>
|
||||||
|
<label class="checkbox-group">
|
||||||
|
<input type="checkbox" id="add_to_device" onclick="toggle(this)">
|
||||||
|
Add To The Device
|
||||||
|
</label>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
{% for template, interface in comparison_items %}
|
||||||
|
{% if template %}
|
||||||
|
<tr {% if not interface %}class="danger"{% endif %}>
|
||||||
|
<td>
|
||||||
|
{% if interface and template.name != interface.name %}
|
||||||
|
<span style="background-color: #eab2b2">{{ template.name }}</span>
|
||||||
|
{% else %}
|
||||||
|
{{ template.name }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{ template.type_display }}</td>
|
||||||
|
<td>
|
||||||
|
{% if not interface %}
|
||||||
|
<label class="checkbox-group">
|
||||||
|
<input type="checkbox" name="add_to_device" value="{{ template.id }}" onclick="uncheck(this)">
|
||||||
|
Add To Device
|
||||||
|
</label>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<td> </td>
|
||||||
|
<td> </td>
|
||||||
|
<td> </td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table class="table" style="width: 50%; float: right;">
|
||||||
|
<!-- Interfaces -->
|
||||||
|
<tr>
|
||||||
|
<th colspan="2">Device</th>
|
||||||
|
<th colspan="2">Actions</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>
|
||||||
|
<label class="checkbox-group">
|
||||||
|
<input type="checkbox" id="remove_from_device" onclick="toggle(this)">
|
||||||
|
Remove
|
||||||
|
</label>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<label class="checkbox-group">
|
||||||
|
<input type="checkbox" id="fix_name" onclick="toggle(this)">
|
||||||
|
Fix The Name
|
||||||
|
</label>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
{% for template, interface in comparison_items %}
|
||||||
|
{% if interface %}
|
||||||
|
<tr {% if not template %}class="success"{% endif %}>
|
||||||
|
<td>
|
||||||
|
{% if template and template.name != interface.name %}
|
||||||
|
<span style="background-color: #cde8c2">{{ interface.name }}</span>
|
||||||
|
{% else %}
|
||||||
|
{{ interface.name }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{ interface.type_display }}</td>
|
||||||
|
<td>
|
||||||
|
{% if not template %}
|
||||||
|
<label class="checkbox-group">
|
||||||
|
<input type="checkbox" name="remove_from_device" value="{{ interface.id }}" onclick="uncheck(this)">
|
||||||
|
Remove
|
||||||
|
</label>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if template and template.name != interface.name %}
|
||||||
|
<label class="checkbox-group">
|
||||||
|
<input type="checkbox" name="fix_name" value="{{ interface.id }}" onclick="uncheck(this)">
|
||||||
|
Fix Name
|
||||||
|
</label>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<td> </td>
|
||||||
|
<td> </td>
|
||||||
|
<td> </td>
|
||||||
|
<td> </td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
<div class="text-right">
|
||||||
|
<input type="submit" value="Apply" class="btn btn-primary">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -1,12 +1,10 @@
|
|||||||
{% if config.include_interfaces_panel %}
|
<div class="card">
|
||||||
<div class="card">
|
<h5 class="card-header">Number of interfaces</h5>
|
||||||
<h5 class="card-header">Number of interfaces</h5>
|
<div class="card-body">
|
||||||
<div class="card-body">
|
Total interfaces: {{ interfaces|length }}<br>
|
||||||
Total interfaces: {{ interfaces|length }}<br>
|
{% if config.exclude_virtual_interfaces %}
|
||||||
{% if config.exclude_virtual_interfaces %}
|
Non-virtual interfaces: {{ real_interfaces|length }}<br>
|
||||||
Non-virtual interfaces: {{ real_interfaces|length }}<br>
|
{% endif %}
|
||||||
{% endif %}
|
Interfaces in the assigned device type: {{ interface_templates|length }}
|
||||||
Interfaces in the assigned device type: {{ interface_templates|length }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
</div>
|
@ -6,44 +6,5 @@ from . import views
|
|||||||
# Define a list of URL patterns to be imported by NetBox. Each pattern maps a URL to
|
# Define a list of URL patterns to be imported by NetBox. Each pattern maps a URL to
|
||||||
# a specific view so that it can be accessed by users.
|
# a specific view so that it can be accessed by users.
|
||||||
urlpatterns = (
|
urlpatterns = (
|
||||||
path(
|
path("interface-comparison/<int:device_id>/", views.InterfaceComparisonView.as_view(), name="interface_comparison"),
|
||||||
"consoleport-comparison/<int:device_id>/",
|
|
||||||
views.ConsolePortComparisonView.as_view(),
|
|
||||||
name="consoleport_comparison",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"consoleserverport-comparison/<int:device_id>/",
|
|
||||||
views.ConsoleServerPortComparisonView.as_view(),
|
|
||||||
name="consoleserverport_comparison",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"interface-comparison/<int:device_id>/",
|
|
||||||
views.InterfaceComparisonView.as_view(),
|
|
||||||
name="interface_comparison",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"powerport-comparison/<int:device_id>/",
|
|
||||||
views.PowerPortComparisonView.as_view(),
|
|
||||||
name="powerport_comparison",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"poweroutlet-comparison/<int:device_id>/",
|
|
||||||
views.PowerOutletComparisonView.as_view(),
|
|
||||||
name="poweroutlet_comparison",
|
|
||||||
),
|
|
||||||
# path(
|
|
||||||
# "frontport-comparison/<int:device_id>/",
|
|
||||||
# views.FrontPortComparisonView.as_view(),
|
|
||||||
# name="frontport_comparison",
|
|
||||||
# ),
|
|
||||||
path(
|
|
||||||
"rearport-comparison/<int:device_id>/",
|
|
||||||
views.RearPortComparisonView.as_view(),
|
|
||||||
name="rearport_comparison",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"devicebay-comparison/<int:device_id>/",
|
|
||||||
views.DeviceBayComparisonView.as_view(),
|
|
||||||
name="devicebay_comparison",
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
import re
|
import re
|
||||||
from typing import Iterable, List
|
from typing import Iterable
|
||||||
from django.conf import settings
|
from dataclasses import dataclass
|
||||||
|
|
||||||
config = settings.PLUGINS_CONFIG['netbox_interface_sync']
|
|
||||||
|
|
||||||
|
|
||||||
def split(s):
|
def split(s):
|
||||||
for x, y in re.findall(r"(\d*)(\D*)", s):
|
for x, y in re.findall(r'(\d*)(\D*)', s):
|
||||||
yield "", int(x or "0")
|
yield '', int(x or '0')
|
||||||
yield y, 0
|
yield y, 0
|
||||||
|
|
||||||
|
|
||||||
@ -19,21 +17,19 @@ def human_sorted(iterable: Iterable):
|
|||||||
return sorted(iterable, key=natural_keys)
|
return sorted(iterable, key=natural_keys)
|
||||||
|
|
||||||
|
|
||||||
def make_integer_list(lst: List[str]):
|
@dataclass(frozen=True)
|
||||||
return [int(i) for i in lst if i.isdigit()]
|
class UnifiedInterface:
|
||||||
|
"""A unified way to represent the interface and interface template"""
|
||||||
|
id: int
|
||||||
|
name: str
|
||||||
|
type: str
|
||||||
|
type_display: str
|
||||||
|
is_template: bool = False
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
# Ignore some fields when comparing; ignore interface name case and whitespaces
|
||||||
|
return (self.name.lower().replace(' ', '') == other.name.lower().replace(' ', '')) and (self.type == other.type)
|
||||||
|
|
||||||
def get_permissions_for_model(model, actions: Iterable[str]) -> List[str]:
|
def __hash__(self):
|
||||||
"""
|
# Ignore some fields when hashing; ignore interface name case and whitespaces
|
||||||
Resolve a list of permissions for a given model (or instance).
|
return hash((self.name.lower().replace(' ', ''), self.type))
|
||||||
|
|
||||||
:param model: A model or instance
|
|
||||||
:param actions: List of actions: view, add, change, or delete
|
|
||||||
"""
|
|
||||||
permissions = []
|
|
||||||
for action in actions:
|
|
||||||
if action not in ("view", "add", "change", "delete"):
|
|
||||||
raise ValueError(f"Unsupported action: {action}")
|
|
||||||
permissions.append(f'{model._meta.app_label}.{action}_{model._meta.model_name}')
|
|
||||||
|
|
||||||
return permissions
|
|
||||||
|
@ -1,461 +1,114 @@
|
|||||||
from collections import namedtuple
|
from django.shortcuts import get_object_or_404, render, redirect
|
||||||
from typing import Type, Tuple
|
|
||||||
|
|
||||||
from django.db.models import QuerySet
|
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
from dcim.models import (Device, Interface, InterfaceTemplate, PowerPort, PowerPortTemplate, ConsolePort,
|
from dcim.models import Device, Interface, InterfaceTemplate
|
||||||
ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, DeviceBay,
|
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
|
||||||
DeviceBayTemplate, FrontPort, FrontPortTemplate, PowerOutlet, PowerOutletTemplate, RearPort,
|
|
||||||
RearPortTemplate)
|
|
||||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
|
||||||
from netbox.models import PrimaryModel
|
from .utils import UnifiedInterface, natural_keys
|
||||||
from dcim.constants import VIRTUAL_IFACE_TYPES
|
from .forms import InterfaceComparisonForm
|
||||||
|
|
||||||
from . import comparison
|
|
||||||
from .utils import get_permissions_for_model, make_integer_list, human_sorted
|
|
||||||
|
|
||||||
config = settings.PLUGINS_CONFIG['netbox_interface_sync']
|
config = settings.PLUGINS_CONFIG['netbox_interface_sync']
|
||||||
ComparisonTableRow = namedtuple('ComparisonTableRow', ('component_template', 'component'))
|
|
||||||
|
|
||||||
|
|
||||||
class GenericComparisonView(PermissionRequiredMixin, View):
|
class InterfaceComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View):
|
||||||
"""
|
"""Comparison of interfaces between a device and a device type and beautiful visualization"""
|
||||||
Generic object comparison view
|
permission_required = ("dcim.view_interface", "dcim.add_interface", "dcim.change_interface", "dcim.delete_interface")
|
||||||
|
|
||||||
obj_model: Model of the object involved in the comparison (for example, Interface)
|
|
||||||
obj_template_model: Model of the object template involved in the comparison (for example, InterfaceTemplate)
|
|
||||||
"""
|
|
||||||
obj_model: Type[PrimaryModel] = None
|
|
||||||
obj_template_model: Type[PrimaryModel] = None
|
|
||||||
|
|
||||||
def get_permission_required(self):
|
|
||||||
# User must have permission to view the device whose components are being compared
|
|
||||||
permissions = ["dcim.view_device"]
|
|
||||||
|
|
||||||
# Resolve permissions related to the object and the object template
|
|
||||||
permissions.extend(get_permissions_for_model(self.obj_model, ("view", "add", "change", "delete")))
|
|
||||||
permissions.extend(get_permissions_for_model(self.obj_template_model, ("view",)))
|
|
||||||
|
|
||||||
return permissions
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def filter_comparison_components(component_templates: QuerySet, components: QuerySet) -> Tuple[QuerySet, QuerySet]:
|
|
||||||
"""Override this in the inherited View to implement special comparison objects filtering logic"""
|
|
||||||
return component_templates, components
|
|
||||||
|
|
||||||
def _fetch_comparison_objects(self, device_id: int):
|
|
||||||
self.device = get_object_or_404(Device, id=device_id)
|
|
||||||
component_templates = self.obj_template_model.objects.filter(device_type_id=self.device.device_type.id)
|
|
||||||
components = self.obj_model.objects.filter(device_id=device_id)
|
|
||||||
self.component_templates, self.components = self.filter_comparison_components(component_templates, components)
|
|
||||||
self.comparison_component_templates = [comparison.from_netbox_object(obj) for obj in self.component_templates]
|
|
||||||
self.comparison_components = [comparison.from_netbox_object(obj) for obj in self.components]
|
|
||||||
|
|
||||||
name_comparison_config = config['name_comparison']
|
|
||||||
|
|
||||||
def name_key(obj_name: str) -> str:
|
|
||||||
name = obj_name
|
|
||||||
if name_comparison_config.get('case-insensitive'):
|
|
||||||
name = name.lower()
|
|
||||||
if name_comparison_config.get('space-insensitive'):
|
|
||||||
name = name.replace(' ', '')
|
|
||||||
return name
|
|
||||||
|
|
||||||
component_templates_dict = {name_key(obj.name): obj for obj in self.comparison_component_templates}
|
|
||||||
components_dict = {name_key(obj.name): obj for obj in self.comparison_components}
|
|
||||||
|
|
||||||
self.comparison_table = tuple(
|
|
||||||
ComparisonTableRow(
|
|
||||||
component_template=component_templates_dict.get(component_name),
|
|
||||||
component=components_dict.get(component_name)
|
|
||||||
)
|
|
||||||
for component_name in human_sorted(set().union(component_templates_dict.keys(), components_dict.keys()))
|
|
||||||
)
|
|
||||||
|
|
||||||
def get(self, request, device_id):
|
def get(self, request, device_id):
|
||||||
self._fetch_comparison_objects(device_id)
|
|
||||||
|
|
||||||
return render(request, "netbox_interface_sync/components_comparison.html", {
|
|
||||||
"component_type_name": self.obj_model._meta.verbose_name_plural,
|
|
||||||
"comparison_items": self.comparison_table,
|
|
||||||
"templates_count": len(self.comparison_component_templates),
|
|
||||||
"components_count": len(self.comparison_components),
|
|
||||||
"device": self.device,
|
|
||||||
})
|
|
||||||
|
|
||||||
def post(self, request, device_id):
|
|
||||||
components_to_add = make_integer_list(request.POST.getlist("add"))
|
|
||||||
components_to_delete = make_integer_list(request.POST.getlist("remove"))
|
|
||||||
components_to_sync = make_integer_list(request.POST.getlist("sync"))
|
|
||||||
if not any((components_to_add, components_to_delete, components_to_sync)):
|
|
||||||
messages.warning(request, "No actions selected")
|
|
||||||
return redirect(request.path)
|
|
||||||
|
|
||||||
self._fetch_comparison_objects(device_id)
|
|
||||||
|
|
||||||
component_ids_to_delete = []
|
|
||||||
components_to_bulk_create = []
|
|
||||||
synced_count = 0
|
|
||||||
for template, component in self.comparison_table:
|
|
||||||
if template and (template.id in components_to_add):
|
|
||||||
# Add component to the device from the template
|
|
||||||
components_to_bulk_create.append(
|
|
||||||
self.obj_model(device=self.device, **template.get_fields_for_netbox_component())
|
|
||||||
)
|
|
||||||
elif component and (component.id in components_to_delete):
|
|
||||||
# Delete component from the device
|
|
||||||
component_ids_to_delete.append(component.id)
|
|
||||||
elif (template and component) and (component.id in components_to_sync):
|
|
||||||
# Update component attributes from the template
|
|
||||||
synced_count += self.components.filter(id=component.id).update(
|
|
||||||
**template.get_fields_for_netbox_component(sync=True)
|
|
||||||
)
|
|
||||||
|
|
||||||
deleted_count = self.obj_model.objects.filter(id__in=component_ids_to_delete).delete()[0]
|
|
||||||
created_count = len(self.obj_model.objects.bulk_create(components_to_bulk_create))
|
|
||||||
|
|
||||||
# Generating result message
|
|
||||||
component_type_name = self.obj_model._meta.verbose_name_plural
|
|
||||||
message = []
|
|
||||||
if synced_count > 0:
|
|
||||||
message.append(f"synced {synced_count} {component_type_name}")
|
|
||||||
if created_count > 0:
|
|
||||||
message.append(f"created {created_count} {component_type_name}")
|
|
||||||
if deleted_count > 0:
|
|
||||||
message.append(f"deleted {deleted_count} {component_type_name}")
|
|
||||||
messages.success(request, "; ".join(message).capitalize())
|
|
||||||
|
|
||||||
return redirect(request.path)
|
|
||||||
|
|
||||||
|
|
||||||
class ConsolePortComparisonView(GenericComparisonView):
|
|
||||||
"""Comparison of console ports between a device and a device type and beautiful visualization"""
|
|
||||||
obj_model = ConsolePort
|
|
||||||
obj_template_model = ConsolePortTemplate
|
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortComparisonView(GenericComparisonView):
|
|
||||||
"""Comparison of console server ports between a device and a device type and beautiful visualization"""
|
|
||||||
obj_model = ConsoleServerPort
|
|
||||||
obj_template_model = ConsoleServerPortTemplate
|
|
||||||
|
|
||||||
|
|
||||||
class InterfaceComparisonView(GenericComparisonView):
|
|
||||||
"""Comparison of interfaces between a device and a device type and beautiful visualization"""
|
|
||||||
obj_model = Interface
|
|
||||||
obj_template_model = InterfaceTemplate
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def filter_comparison_components(component_templates: QuerySet, components: QuerySet) -> Tuple[QuerySet, QuerySet]:
|
|
||||||
if config["exclude_virtual_interfaces"]:
|
|
||||||
components = components.exclude(type__in=VIRTUAL_IFACE_TYPES)
|
|
||||||
component_templates = component_templates.exclude(type__in=VIRTUAL_IFACE_TYPES)
|
|
||||||
return component_templates, components
|
|
||||||
|
|
||||||
|
|
||||||
class PowerPortComparisonView(GenericComparisonView):
|
|
||||||
"""Comparison of power ports between a device and a device type and beautiful visualization"""
|
|
||||||
obj_model = PowerPort
|
|
||||||
obj_template_model = PowerPortTemplate
|
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletComparisonView(GenericComparisonView):
|
|
||||||
"""Comparison of power outlets between a device and a device type and beautiful visualization"""
|
|
||||||
obj_model = PowerOutlet
|
|
||||||
obj_template_model = PowerOutletTemplate
|
|
||||||
|
|
||||||
def post(self, request, device_id):
|
|
||||||
device = get_object_or_404(Device.objects.filter(id=device_id))
|
device = get_object_or_404(Device.objects.filter(id=device_id))
|
||||||
|
interfaces = device.vc_interfaces()
|
||||||
|
if config["exclude_virtual_interfaces"]:
|
||||||
|
interfaces = list(filter(lambda i: not i.is_virtual, interfaces))
|
||||||
|
interface_templates = InterfaceTemplate.objects.filter(device_type=device.device_type)
|
||||||
|
|
||||||
poweroutlets = device.poweroutlets.all()
|
unified_interfaces = [UnifiedInterface(i.id, i.name, i.type, i.get_type_display()) for i in interfaces]
|
||||||
poweroutlets_templates = PowerOutletTemplate.objects.filter(device_type=device.device_type)
|
unified_interface_templates = [
|
||||||
|
UnifiedInterface(i.id, i.name, i.type, i.get_type_display(), is_template=True) for i in interface_templates]
|
||||||
|
|
||||||
# Generating result message
|
# List of interfaces and interface templates presented in the unified format
|
||||||
message = []
|
overall_interfaces = list(set(unified_interface_templates + unified_interfaces))
|
||||||
created = 0
|
overall_interfaces.sort(key=lambda o: natural_keys(o.name))
|
||||||
updated = 0
|
|
||||||
fixed = 0
|
|
||||||
|
|
||||||
remove_from_device = filter(
|
comparison_templates = []
|
||||||
lambda i: i in poweroutlets.values_list("id", flat=True),
|
comparison_interfaces = []
|
||||||
map(int, filter(lambda x: x.isdigit(), request.POST.getlist("remove")))
|
for i in overall_interfaces:
|
||||||
|
try:
|
||||||
|
comparison_templates.append(unified_interface_templates[unified_interface_templates.index(i)])
|
||||||
|
except ValueError:
|
||||||
|
comparison_templates.append(None)
|
||||||
|
|
||||||
|
try:
|
||||||
|
comparison_interfaces.append(unified_interfaces[unified_interfaces.index(i)])
|
||||||
|
except ValueError:
|
||||||
|
comparison_interfaces.append(None)
|
||||||
|
|
||||||
|
comparison_items = list(zip(comparison_templates, comparison_interfaces))
|
||||||
|
return render(
|
||||||
|
request, "netbox_interface_sync/interface_comparison.html",
|
||||||
|
{
|
||||||
|
"comparison_items": comparison_items,
|
||||||
|
"templates_count": len(interface_templates),
|
||||||
|
"interfaces_count": len(interfaces),
|
||||||
|
"device": device
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Remove selected power outlets from the device and count them
|
def post(self, request, device_id):
|
||||||
deleted = PowerOutlet.objects.filter(id__in=remove_from_device).delete()[0]
|
form = InterfaceComparisonForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
device = get_object_or_404(Device.objects.filter(id=device_id))
|
||||||
|
interfaces = device.vc_interfaces()
|
||||||
|
if config["exclude_virtual_interfaces"]:
|
||||||
|
interfaces = interfaces.exclude(type__in=["virtual", "lag"])
|
||||||
|
interface_templates = InterfaceTemplate.objects.filter(device_type=device.device_type)
|
||||||
|
|
||||||
# Get device power ports to check dependency between power outlets
|
# Manually validating interfaces and interface templates lists
|
||||||
device_pp = PowerPort.objects.filter(device_id=device.id)
|
|
||||||
|
|
||||||
matching = {}
|
|
||||||
mismatch = False
|
|
||||||
for i in poweroutlets_templates:
|
|
||||||
found = False
|
|
||||||
if i.power_port_id is not None:
|
|
||||||
ppt = PowerPortTemplate.objects.get(id=i.power_port_id)
|
|
||||||
for pp in device_pp:
|
|
||||||
if pp.name == ppt.name:
|
|
||||||
# Save matching to add the correct power port later
|
|
||||||
matching[i.id] = pp.id
|
|
||||||
found = True
|
|
||||||
|
|
||||||
# If at least one power port is not found in device there is a dependency
|
|
||||||
# Better not to sync at all
|
|
||||||
if not found:
|
|
||||||
mismatch = True
|
|
||||||
break
|
|
||||||
|
|
||||||
if not mismatch:
|
|
||||||
add_to_device = filter(
|
add_to_device = filter(
|
||||||
lambda i: i in poweroutlets_templates.values_list("id", flat=True),
|
lambda i: i in interface_templates.values_list("id", flat=True),
|
||||||
map(int, filter(lambda x: x.isdigit(), request.POST.getlist("add")))
|
map(int, filter(lambda x: x.isdigit(), request.POST.getlist("add_to_device")))
|
||||||
|
)
|
||||||
|
remove_from_device = filter(
|
||||||
|
lambda i: i in interfaces.values_list("id", flat=True),
|
||||||
|
map(int, filter(lambda x: x.isdigit(), request.POST.getlist("remove_from_device")))
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add selected component to the device and count them
|
# Remove selected interfaces from the device and count them
|
||||||
add_to_device_component = PowerOutletTemplate.objects.filter(id__in=add_to_device)
|
interfaces_deleted = Interface.objects.filter(id__in=remove_from_device).delete()[0]
|
||||||
|
|
||||||
bulk_create = []
|
# Add selected interfaces to the device and count them
|
||||||
updated = 0
|
add_to_device_interfaces = InterfaceTemplate.objects.filter(id__in=add_to_device)
|
||||||
keys_to_avoid = ["id"]
|
interfaces_created = len(Interface.objects.bulk_create([
|
||||||
|
Interface(device=device, name=i.name, type=i.type) for i in add_to_device_interfaces
|
||||||
|
]))
|
||||||
|
|
||||||
if not config["compare_description"]:
|
# Getting and validating a list of interfaces to rename
|
||||||
keys_to_avoid.append("description")
|
fix_name_interfaces = filter(lambda i: str(i.id) in request.POST.getlist("fix_name"), interfaces)
|
||||||
|
# Casting interface templates into UnifiedInterface objects for proper comparison with interfaces for renaming
|
||||||
for i in add_to_device_component.values():
|
unified_interface_templates = [
|
||||||
to_create = False
|
UnifiedInterface(i.id, i.name, i.type, i.get_type_display()) for i in interface_templates]
|
||||||
|
|
||||||
|
# Rename selected interfaces
|
||||||
|
interfaces_fixed = 0
|
||||||
|
for interface in fix_name_interfaces:
|
||||||
|
unified_interface = UnifiedInterface(interface.id, interface.name, interface.type, interface.get_type_display())
|
||||||
try:
|
try:
|
||||||
# If power outlets already exists, update and do not recreate
|
# Try to extract an interface template with the corresponding name
|
||||||
po = device.poweroutlets.get(name=i["name"])
|
corresponding_template = unified_interface_templates[unified_interface_templates.index(unified_interface)]
|
||||||
except PowerOutlet.DoesNotExist:
|
interface.name = corresponding_template.name
|
||||||
po = PowerOutlet()
|
interface.save()
|
||||||
po.device = device
|
interfaces_fixed += 1
|
||||||
to_create = True
|
|
||||||
|
|
||||||
# Copy all fields from template
|
|
||||||
for k in i.keys():
|
|
||||||
if k not in keys_to_avoid:
|
|
||||||
setattr(po, k, i[k])
|
|
||||||
po.power_port_id = matching.get(i["id"], None)
|
|
||||||
|
|
||||||
if to_create:
|
|
||||||
bulk_create.append(po)
|
|
||||||
else:
|
|
||||||
po.save()
|
|
||||||
updated += 1
|
|
||||||
|
|
||||||
created = len(PowerOutlet.objects.bulk_create(bulk_create))
|
|
||||||
|
|
||||||
# Getting and validating a list of components to rename
|
|
||||||
fix_name_components = filter(lambda i: str(i.id) in request.POST.getlist("fix_name"), poweroutlets)
|
|
||||||
|
|
||||||
# Casting component templates into Unified objects for proper comparison with component for renaming
|
|
||||||
unified_component_templates = [
|
|
||||||
PowerOutletComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(),
|
|
||||||
power_port_name=PowerPortTemplate.objects.get(id=i.power_port_id).name
|
|
||||||
if i.power_port_id is not None else "",
|
|
||||||
feed_leg=i.feed_leg, is_template=True)
|
|
||||||
for i in poweroutlets_templates]
|
|
||||||
|
|
||||||
# Rename selected power outlets
|
|
||||||
fixed = 0
|
|
||||||
for component in fix_name_components:
|
|
||||||
unified_poweroutlet = PowerOutletComparison(
|
|
||||||
component.id, component.name, component.label, component.description, component.type,
|
|
||||||
component.get_type_display(),
|
|
||||||
power_port_name=PowerPort.objects.get(id=component.power_port_id).name
|
|
||||||
if component.power_port_id is not None else "",
|
|
||||||
feed_leg=component.feed_leg
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
# Try to extract a component template with the corresponding name
|
|
||||||
corresponding_template = unified_component_templates[
|
|
||||||
unified_component_templates.index(unified_poweroutlet)
|
|
||||||
]
|
|
||||||
component.name = corresponding_template.name
|
|
||||||
component.save()
|
|
||||||
fixed += 1
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
else:
|
|
||||||
messages.error(request, "Dependency detected, sync power ports first!")
|
|
||||||
|
|
||||||
if created > 0:
|
# Generating result message
|
||||||
message.append(f"created {created} power outlets")
|
message = []
|
||||||
if updated > 0:
|
if interfaces_created > 0:
|
||||||
message.append(f"updated {updated} power outlets")
|
message.append(f"created {interfaces_created} interfaces")
|
||||||
if deleted > 0:
|
if interfaces_deleted > 0:
|
||||||
message.append(f"deleted {deleted} power outlets")
|
message.append(f"deleted {interfaces_deleted} interfaces")
|
||||||
if fixed > 0:
|
if interfaces_fixed > 0:
|
||||||
message.append(f"fixed {fixed} power outlets")
|
message.append(f"fixed {interfaces_fixed} interfaces")
|
||||||
|
messages.success(request, "; ".join(message).capitalize())
|
||||||
|
|
||||||
messages.info(request, "; ".join(message).capitalize())
|
return redirect(request.path)
|
||||||
|
|
||||||
return redirect(request.path)
|
|
||||||
|
|
||||||
|
|
||||||
class RearPortComparisonView(GenericComparisonView):
|
|
||||||
"""Comparison of rear ports between a device and a device type and beautiful visualization"""
|
|
||||||
obj_model = RearPort
|
|
||||||
obj_template_model = RearPortTemplate
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceBayComparisonView(GenericComparisonView):
|
|
||||||
"""Comparison of device bays between a device and a device type and beautiful visualization"""
|
|
||||||
obj_model = DeviceBay
|
|
||||||
obj_template_model = DeviceBayTemplate
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class FrontPortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View):
|
|
||||||
# """Comparison of front ports between a device and a device type and beautiful visualization"""
|
|
||||||
# permission_required = get_permissions_for_object("dcim", "frontport")
|
|
||||||
#
|
|
||||||
# def get(self, request, device_id):
|
|
||||||
#
|
|
||||||
# device = get_object_or_404(Device.objects.filter(id=device_id))
|
|
||||||
#
|
|
||||||
# frontports = device.frontports.all()
|
|
||||||
# frontports_templates = FrontPortTemplate.objects.filter(device_type=device.device_type)
|
|
||||||
#
|
|
||||||
# unified_frontports = [
|
|
||||||
# FrontPortComparison(
|
|
||||||
# i.id, i.name, i.label, i.description, i.type, i.get_type_display(), i.color, i.rear_port_position)
|
|
||||||
# for i in frontports]
|
|
||||||
# unified_frontports_templates = [
|
|
||||||
# FrontPortComparison(
|
|
||||||
# i.id, i.name, i.label, i.description, i.type, i.get_type_display(), i.color,
|
|
||||||
# i.rear_port_position, is_template=True)
|
|
||||||
# for i in frontports_templates]
|
|
||||||
#
|
|
||||||
# return get_components(request, device, frontports, unified_frontports, unified_frontports_templates)
|
|
||||||
#
|
|
||||||
# def post(self, request, device_id):
|
|
||||||
# form = ComponentComparisonForm(request.POST)
|
|
||||||
# if form.is_valid():
|
|
||||||
# device = get_object_or_404(Device.objects.filter(id=device_id))
|
|
||||||
#
|
|
||||||
# frontports = device.frontports.all()
|
|
||||||
# frontports_templates = FrontPortTemplate.objects.filter(device_type=device.device_type)
|
|
||||||
#
|
|
||||||
# # Generating result message
|
|
||||||
# message = []
|
|
||||||
# created = 0
|
|
||||||
# updated = 0
|
|
||||||
# fixed = 0
|
|
||||||
#
|
|
||||||
# remove_from_device = filter(
|
|
||||||
# lambda i: i in frontports.values_list("id", flat=True),
|
|
||||||
# map(int, filter(lambda x: x.isdigit(), request.POST.getlist("remove_from_device")))
|
|
||||||
# )
|
|
||||||
#
|
|
||||||
# # Remove selected front ports from the device and count them
|
|
||||||
# deleted = FrontPort.objects.filter(id__in=remove_from_device).delete()[0]
|
|
||||||
#
|
|
||||||
# # Get device rear ports to check dependency between front ports
|
|
||||||
# device_rp = RearPort.objects.filter(device_id=device.id)
|
|
||||||
#
|
|
||||||
# matching = {}
|
|
||||||
# mismatch = False
|
|
||||||
# for i in frontports_templates:
|
|
||||||
# found = False
|
|
||||||
# if i.rear_port_id is not None:
|
|
||||||
# rpt = RearPortTemplate.objects.get(id=i.rear_port_id)
|
|
||||||
# for rp in device_rp:
|
|
||||||
# if rp.name == rpt.name:
|
|
||||||
# # Save matching to add the correct rear port later
|
|
||||||
# matching[i.id] = rp.id
|
|
||||||
# found = True
|
|
||||||
#
|
|
||||||
# # If at least one rear port is not found in device there is a dependency
|
|
||||||
# # Better not to sync at all
|
|
||||||
# if not found:
|
|
||||||
# mismatch = True
|
|
||||||
# break
|
|
||||||
#
|
|
||||||
# if not mismatch:
|
|
||||||
# add_to_device = filter(
|
|
||||||
# lambda i: i in frontports_templates.values_list("id", flat=True),
|
|
||||||
# map(int, filter(lambda x: x.isdigit(), request.POST.getlist("add_to_device")))
|
|
||||||
# )
|
|
||||||
#
|
|
||||||
# # Add selected component to the device and count them
|
|
||||||
# add_to_device_component = FrontPortTemplate.objects.filter(id__in=add_to_device)
|
|
||||||
#
|
|
||||||
# bulk_create = []
|
|
||||||
# updated = 0
|
|
||||||
# keys_to_avoid = ["id"]
|
|
||||||
#
|
|
||||||
# if not config["compare_description"]:
|
|
||||||
# keys_to_avoid.append("description")
|
|
||||||
#
|
|
||||||
# for i in add_to_device_component.values():
|
|
||||||
# to_create = False
|
|
||||||
#
|
|
||||||
# try:
|
|
||||||
# # If front port already exists, update and do not recreate
|
|
||||||
# fp = device.frontports.get(name=i["name"])
|
|
||||||
# except FrontPort.DoesNotExist:
|
|
||||||
# fp = FrontPort()
|
|
||||||
# fp.device = device
|
|
||||||
# to_create = True
|
|
||||||
#
|
|
||||||
# # Copy all fields from template
|
|
||||||
# for k in i.keys():
|
|
||||||
# if k not in keys_to_avoid:
|
|
||||||
# setattr(fp, k, i[k])
|
|
||||||
# fp.rear_port_id = matching.get(i["id"], None)
|
|
||||||
#
|
|
||||||
# if to_create:
|
|
||||||
# bulk_create.append(fp)
|
|
||||||
# else:
|
|
||||||
# fp.save()
|
|
||||||
# updated += 1
|
|
||||||
#
|
|
||||||
# created = len(FrontPort.objects.bulk_create(bulk_create))
|
|
||||||
#
|
|
||||||
# # Getting and validating a list of components to rename
|
|
||||||
# fix_name_components = filter(lambda i: str(i.id) in request.POST.getlist("fix_name"), frontports)
|
|
||||||
#
|
|
||||||
# # Casting component templates into Unified objects for proper comparison with component for renaming
|
|
||||||
# unified_frontports_templates = [
|
|
||||||
# FrontPortComparison(
|
|
||||||
# i.id, i.name, i.label, i.description, i.type, i.get_type_display(),
|
|
||||||
# i.color, i.rear_port_position, is_template=True)
|
|
||||||
# for i in frontports_templates]
|
|
||||||
# # Rename selected front ports
|
|
||||||
# fixed = 0
|
|
||||||
# for component in fix_name_components:
|
|
||||||
# unified_frontport = FrontPortComparison(
|
|
||||||
# component.id, component.name, component.label, component.description, component.type,
|
|
||||||
# component.get_type_display(), component.color, component.rear_port_position
|
|
||||||
# )
|
|
||||||
#
|
|
||||||
# try:
|
|
||||||
# # Try to extract a component template with the corresponding name
|
|
||||||
# corresponding_template = unified_frontports_templates[
|
|
||||||
# unified_frontports_templates.index(unified_frontport)
|
|
||||||
# ]
|
|
||||||
# component.name = corresponding_template.name
|
|
||||||
# component.save()
|
|
||||||
# fixed += 1
|
|
||||||
# except ValueError:
|
|
||||||
# pass
|
|
||||||
# else:
|
|
||||||
# messages.error(request, "Dependency detected, sync rear ports first!")
|
|
||||||
#
|
|
||||||
# if created > 0:
|
|
||||||
# message.append(f"created {created} front ports")
|
|
||||||
# if updated > 0:
|
|
||||||
# message.append(f"updated {updated} front ports")
|
|
||||||
# if deleted > 0:
|
|
||||||
# message.append(f"deleted {deleted} front ports")
|
|
||||||
# if fixed > 0:
|
|
||||||
# message.append(f"fixed {fixed} front ports")
|
|
||||||
#
|
|
||||||
# messages.info(request, "; ".join(message).capitalize())
|
|
||||||
#
|
|
||||||
# return redirect(request.path)
|
|
||||||
|
Loading…
Reference in New Issue
Block a user