mirror of
https://github.com/drygdryg/netbox-plugin-interface-sync
synced 2024-11-25 18:10:52 +03:00
Styling rendered table with bootstrap 4. Removed the double for loop. Added str method to comparison classes.
This commit is contained in:
parent
729218f577
commit
861eff8a61
@ -20,6 +20,9 @@ class ParentComparison:
|
||||
def __hash__(self):
|
||||
return hash(self.name.lower().replace(" ", ""))
|
||||
|
||||
def __str__(self):
|
||||
return f"Label: {self.label}\nDescription: {self.description}"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ParentTypedComparison(ParentComparison):
|
||||
@ -41,6 +44,9 @@ class ParentTypedComparison(ParentComparison):
|
||||
# Ignore some fields when hashing; ignore interface name case and whitespaces
|
||||
return hash((self.name.lower().replace(" ", ""), self.type))
|
||||
|
||||
def __str__(self):
|
||||
return f"{super().__str__()}\nType: {self.type_display}"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class InterfaceComparison(ParentTypedComparison):
|
||||
@ -63,6 +69,9 @@ class InterfaceComparison(ParentTypedComparison):
|
||||
# Ignore some fields when hashing; ignore interface name case and whitespaces
|
||||
return hash((self.name.lower().replace(" ", ""), self.type))
|
||||
|
||||
def __str__(self):
|
||||
return f"{super().__str__()}\nManagement only: {self.mgmt_only}"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FrontPortComparison(ParentTypedComparison):
|
||||
@ -88,6 +97,9 @@ class FrontPortComparison(ParentTypedComparison):
|
||||
# Ignore some fields when hashing; ignore interface name case and whitespaces
|
||||
return hash((self.name.lower().replace(" ", ""), self.type))
|
||||
|
||||
def __str__(self):
|
||||
return f"{super().__str__()}\nColor: {self.color}\nPosition: {self.rear_port_position}"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RearPortComparison(ParentTypedComparison):
|
||||
@ -112,6 +124,9 @@ class RearPortComparison(ParentTypedComparison):
|
||||
# Ignore some fields when hashing; ignore interface name case and whitespaces
|
||||
return hash((self.name.lower().replace(" ", ""), self.type))
|
||||
|
||||
def __str__(self):
|
||||
return f"{super().__str__()}\nColor: {self.color}\nPositions: {self.positions}"
|
||||
|
||||
|
||||
@dataclass(frozen=True, eq=False)
|
||||
class ConsolePortComparison(ParentTypedComparison):
|
||||
@ -150,6 +165,9 @@ class PowerPortComparison(ParentTypedComparison):
|
||||
# Ignore some fields when hashing; ignore interface name case and whitespaces
|
||||
return hash((self.name.lower().replace(" ", ""), self.type))
|
||||
|
||||
def __str__(self):
|
||||
return f"{super().__str__()}\nMaximum draw: {self.maximum_draw}\nAllocated draw: {self.allocated_draw}"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PowerOutletComparison(ParentTypedComparison):
|
||||
@ -176,6 +194,9 @@ class PowerOutletComparison(ParentTypedComparison):
|
||||
(self.name.lower().replace(" ", ""), self.type, self.power_port_name)
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"{super().__str__()}\nPower port name: {self.power_port_name}\nFeed leg: {self.feed_leg}"
|
||||
|
||||
|
||||
@dataclass(frozen=True, eq=False)
|
||||
class DeviceBayComparison(ParentComparison):
|
||||
|
@ -8,7 +8,7 @@ class DeviceViewExtension(PluginTemplateExtension):
|
||||
def buttons(self):
|
||||
"""Implements a compare interfaces button at the top of the page"""
|
||||
obj = self.context['object']
|
||||
return self.render("netbox_interface_sync/compare_interfaces_button.html", extra_context={
|
||||
return self.render("netbox_interface_sync/compare_components_button.html", extra_context={
|
||||
"device": obj
|
||||
})
|
||||
|
||||
|
@ -0,0 +1,159 @@
|
||||
{% extends 'base/layout.html' %}
|
||||
|
||||
{% block title %}{{ device }} - {{ component_type }} 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;
|
||||
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>
|
||||
|
||||
<form method="post">
|
||||
<!-- Interface templates -->
|
||||
{% 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 }}.
|
||||
</caption>
|
||||
{% else %}
|
||||
<caption class="caption-red">
|
||||
The device and device type have different number of {{ component_type|lower }}.<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_to_device" 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_from_device" onclick="toggle(this)">
|
||||
Remove
|
||||
</label>
|
||||
</th>
|
||||
<th scope="col">
|
||||
<label>
|
||||
<input type="checkbox" id="fix_name" onclick="toggle(this)">
|
||||
Fix the name
|
||||
</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 }}</td>
|
||||
<td {% if not component %}class="table-danger"{% endif %}>
|
||||
{% if not component %}
|
||||
<label>
|
||||
<input type="checkbox" name="add_to_device" 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 }}</td>
|
||||
<td {% if not component_template %}class="table-success"{% endif %}>
|
||||
{% if not component_template %}
|
||||
<label>
|
||||
<input type="checkbox" name="remove_from_device" 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.name != component.name %}
|
||||
<label>
|
||||
<input type="checkbox" name="fix_name" value="{{ component.id }}" onclick="uncheck(this)">
|
||||
Fix name
|
||||
</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 %}
|
@ -1,161 +0,0 @@
|
||||
{% 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 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 %}
|
@ -19,13 +19,13 @@ def human_sorted(iterable: Iterable):
|
||||
return sorted(iterable, key=natural_keys)
|
||||
|
||||
|
||||
def get_components(request, device, components, unified_components, unified_component_templates):
|
||||
# List of interfaces and interface templates presented in the unified format
|
||||
def get_components(request, device, components, unified_components, unified_component_templates, component_type):
|
||||
# List of components and components templates presented in the unified format
|
||||
overall_powers = list(set(unified_component_templates + unified_components))
|
||||
overall_powers.sort(key=lambda o: natural_keys(o.name))
|
||||
|
||||
comparison_templates = []
|
||||
comparison_interfaces = []
|
||||
comparison_components = []
|
||||
for i in overall_powers:
|
||||
try:
|
||||
comparison_templates.append(
|
||||
@ -35,20 +35,21 @@ def get_components(request, device, components, unified_components, unified_comp
|
||||
comparison_templates.append(None)
|
||||
|
||||
try:
|
||||
comparison_interfaces.append(
|
||||
comparison_components.append(
|
||||
unified_components[unified_components.index(i)]
|
||||
)
|
||||
except ValueError:
|
||||
comparison_interfaces.append(None)
|
||||
comparison_components.append(None)
|
||||
|
||||
comparison_items = list(zip(comparison_templates, comparison_interfaces))
|
||||
comparison_items = list(zip(comparison_templates, comparison_components))
|
||||
return render(
|
||||
request,
|
||||
"netbox_interface_sync/interface_comparison.html",
|
||||
"netbox_interface_sync/components_comparison.html",
|
||||
{
|
||||
"component_type": component_type,
|
||||
"comparison_items": comparison_items,
|
||||
"templates_count": len(unified_component_templates),
|
||||
"interfaces_count": len(components),
|
||||
"components_count": len(components),
|
||||
"device": device,
|
||||
},
|
||||
)
|
||||
|
@ -29,7 +29,7 @@ class InterfaceComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View)
|
||||
unified_interface_templates = [
|
||||
InterfaceComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), i.mgmt_only, is_template=True) for i in interface_templates]
|
||||
|
||||
return get_components(request, device, interfaces, unified_interfaces, unified_interface_templates)
|
||||
return get_components(request, device, interfaces, unified_interfaces, unified_interface_templates, "Interfaces")
|
||||
|
||||
def post(self, request, device_id):
|
||||
form = InterfaceComparisonForm(request.POST)
|
||||
@ -76,7 +76,7 @@ class PowerPortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View)
|
||||
unified_powerport_templates = [
|
||||
PowerPortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), i.maximum_draw, i.allocated_draw, is_template=True) for i in powerports_templates]
|
||||
|
||||
return get_components(request, device, powerports, unified_powerports, unified_powerport_templates)
|
||||
return get_components(request, device, powerports, unified_powerports, unified_powerport_templates, "Power ports")
|
||||
|
||||
def post(self, request, device_id):
|
||||
form = InterfaceComparisonForm(request.POST)
|
||||
@ -123,7 +123,7 @@ class ConsolePortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, Vie
|
||||
unified_consoleport_templates = [
|
||||
ConsolePortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), is_template=True) for i in consoleports_templates]
|
||||
|
||||
return get_components(request, device, consoleports, unified_consoleports, unified_consoleport_templates)
|
||||
return get_components(request, device, consoleports, unified_consoleports, unified_consoleport_templates, "Console ports")
|
||||
|
||||
def post(self, request, device_id):
|
||||
form = InterfaceComparisonForm(request.POST)
|
||||
@ -168,7 +168,7 @@ class ConsoleServerPortComparisonView(LoginRequiredMixin, PermissionRequiredMixi
|
||||
unified_consoleserverport_templates = [
|
||||
ConsoleServerPortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), is_template=True) for i in consoleserverports_templates]
|
||||
|
||||
return get_components(request, device, consoleserverports, unified_consoleserverports, unified_consoleserverport_templates)
|
||||
return get_components(request, device, consoleserverports, unified_consoleserverports, unified_consoleserverport_templates, "Console server ports")
|
||||
|
||||
def post(self, request, device_id):
|
||||
form = InterfaceComparisonForm(request.POST)
|
||||
@ -213,7 +213,7 @@ class PowerOutletComparisonView(LoginRequiredMixin, PermissionRequiredMixin, Vie
|
||||
unified_poweroutlet_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]
|
||||
|
||||
return get_components(request, device, poweroutlets, unified_poweroutlets, unified_poweroutlet_templates)
|
||||
return get_components(request, device, poweroutlets, unified_poweroutlets, unified_poweroutlet_templates, "Power outlets")
|
||||
|
||||
def post(self, request, device_id):
|
||||
form = InterfaceComparisonForm(request.POST)
|
||||
@ -347,7 +347,7 @@ class FrontPortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View)
|
||||
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)
|
||||
return get_components(request, device, frontports, unified_frontports, unified_frontports_templates, "Front ports")
|
||||
|
||||
def post(self, request, device_id):
|
||||
form = InterfaceComparisonForm(request.POST)
|
||||
@ -479,7 +479,7 @@ class RearPortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View):
|
||||
unified_rearports_templates = [
|
||||
RearPortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), i.color, i.positions, is_template=True) for i in rearports_templates]
|
||||
|
||||
return get_components(request, device, rearports, unified_rearports, unified_rearports_templates)
|
||||
return get_components(request, device, rearports, unified_rearports, unified_rearports_templates, "Rear ports")
|
||||
|
||||
def post(self, request, device_id):
|
||||
form = InterfaceComparisonForm(request.POST)
|
||||
@ -536,7 +536,7 @@ class DeviceBayComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View)
|
||||
unified_devicebay_templates = [
|
||||
DeviceBayComparison(i.id, i.name, i.label, i.description, is_template=True) for i in devicebays_templates]
|
||||
|
||||
return get_components(request, device, devicebays, unified_devicebays, unified_devicebay_templates)
|
||||
return get_components(request, device, devicebays, unified_devicebays, unified_devicebay_templates, "Device bays")
|
||||
|
||||
def post(self, request, device_id):
|
||||
form = InterfaceComparisonForm(request.POST)
|
||||
|
Loading…
Reference in New Issue
Block a user