netbox-ping 0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- netbox_ping/__init__.py +23 -0
- netbox_ping/config.py +43 -0
- netbox_ping/forms.py +13 -0
- netbox_ping/hooks.py +6 -0
- netbox_ping/migrations/0001_initial.py +30 -0
- netbox_ping/migrations/__init__.py +0 -0
- netbox_ping/migrations/__pycache__/0001_initial.cpython-312.pyc +0 -0
- netbox_ping/migrations/__pycache__/__init__.cpython-312.pyc +0 -0
- netbox_ping/models.py +42 -0
- netbox_ping/navigation.py +17 -0
- netbox_ping/plugin.py +83 -0
- netbox_ping/signals.py +48 -0
- netbox_ping/tables.py +22 -0
- netbox_ping/template_content.py +15 -0
- netbox_ping/template_extensions.py +15 -0
- netbox_ping/templates/netbox_ping/compare_interfaces_button.html +4 -0
- netbox_ping/templates/netbox_ping/interface_comparison.html +161 -0
- netbox_ping/templates/netbox_ping/number_of_interfaces_panel.html +21 -0
- netbox_ping/templates/netbox_ping/ping_home.html +169 -0
- netbox_ping/templates/netbox_ping/ping_subnet_button.html +11 -0
- netbox_ping/templates/netbox_ping/prefix_button.html +4 -0
- netbox_ping/templates/netbox_ping/prefix_content.html +11 -0
- netbox_ping/urls.py +16 -0
- netbox_ping/utils.py +37 -0
- netbox_ping/views.py +466 -0
- netbox_ping-0.2.dist-info/METADATA +176 -0
- netbox_ping-0.2.dist-info/RECORD +29 -0
- netbox_ping-0.2.dist-info/WHEEL +5 -0
- netbox_ping-0.2.dist-info/top_level.txt +1 -0
netbox_ping/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from netbox.plugins import PluginConfig
|
|
2
|
+
|
|
3
|
+
class Config(PluginConfig):
|
|
4
|
+
name = 'netbox_ping'
|
|
5
|
+
verbose_name = 'NetBox Ping'
|
|
6
|
+
description = 'Ping IPs and subnets'
|
|
7
|
+
version = '0.2'
|
|
8
|
+
author = 'Christian Rose'
|
|
9
|
+
default_settings = {
|
|
10
|
+
'exclude_virtual_interfaces': True
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
# Register the custom table
|
|
14
|
+
ipaddress_table = 'netbox_ping.tables.CustomIPAddressTable'
|
|
15
|
+
|
|
16
|
+
# Define which models support custom fields
|
|
17
|
+
custom_field_models = ['ipaddress']
|
|
18
|
+
|
|
19
|
+
# API settings
|
|
20
|
+
base_url = 'netbox-ping'
|
|
21
|
+
default_app_config = 'netbox_ping.apps.NetBoxPingConfig'
|
|
22
|
+
|
|
23
|
+
config = Config
|
netbox_ping/config.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from extras.choices import CustomFieldTypeChoices
|
|
2
|
+
from extras.models import CustomField, Tag
|
|
3
|
+
from django.db.models import Q
|
|
4
|
+
|
|
5
|
+
def create_custom_fields_and_tags():
|
|
6
|
+
"""Create custom fields and tags needed by the plugin"""
|
|
7
|
+
|
|
8
|
+
# Create Up_Down custom field
|
|
9
|
+
custom_field, created = CustomField.objects.get_or_create(
|
|
10
|
+
name='Up_Down',
|
|
11
|
+
defaults={
|
|
12
|
+
'type': CustomFieldTypeChoices.TYPE_BOOLEAN,
|
|
13
|
+
'label': 'Up/Down Status',
|
|
14
|
+
'description': 'Indicates if the IP is responding to ping',
|
|
15
|
+
'required': False,
|
|
16
|
+
'filter_logic': 'exact'
|
|
17
|
+
}
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# Add the custom field to IPAddress content type if not already added
|
|
21
|
+
if created:
|
|
22
|
+
custom_field.content_types.add(
|
|
23
|
+
ContentType.objects.get(app_label='ipam', model='ipaddress')
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# Create online/offline tags
|
|
27
|
+
Tag.objects.get_or_create(
|
|
28
|
+
name='online',
|
|
29
|
+
slug='online',
|
|
30
|
+
defaults={
|
|
31
|
+
'description': 'IP is responding to ping',
|
|
32
|
+
'color': '4CAF50' # Green color
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
Tag.objects.get_or_create(
|
|
37
|
+
name='offline',
|
|
38
|
+
slug='offline',
|
|
39
|
+
defaults={
|
|
40
|
+
'description': 'IP is not responding to ping',
|
|
41
|
+
'color': 'F44336' # Red color
|
|
42
|
+
}
|
|
43
|
+
)
|
netbox_ping/forms.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from django import forms
|
|
2
|
+
from netbox.forms import NetBoxModelForm
|
|
3
|
+
from .models import PluginSettingsModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class InterfaceComparisonForm(forms.Form):
|
|
7
|
+
add_to_device = forms.BooleanField(required=False)
|
|
8
|
+
remove_from_device = forms.BooleanField(required=False)
|
|
9
|
+
|
|
10
|
+
class PluginSettingsForm(NetBoxModelForm):
|
|
11
|
+
class Meta:
|
|
12
|
+
model = PluginSettingsModel
|
|
13
|
+
fields = ('update_tags',)
|
netbox_ping/hooks.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from django.db import migrations, models
|
|
2
|
+
import django.db.models.deletion
|
|
3
|
+
import netbox.models.features
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
initial = True
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
('extras', '0001_initial'), # Base NetBox dependency
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.CreateModel(
|
|
15
|
+
name='PluginSettingsModel',
|
|
16
|
+
fields=[
|
|
17
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
|
|
18
|
+
('created', models.DateTimeField(auto_now_add=True)),
|
|
19
|
+
('last_updated', models.DateTimeField(auto_now=True)),
|
|
20
|
+
('custom_field_data', models.JSONField(blank=True, default=dict, null=True)),
|
|
21
|
+
('update_tags', models.BooleanField(default=True, help_text='Whether to update tags when scanning IPs', verbose_name='Update Tags')),
|
|
22
|
+
],
|
|
23
|
+
options={
|
|
24
|
+
'verbose_name': 'Plugin Settings',
|
|
25
|
+
'verbose_name_plural': 'Plugin Settings',
|
|
26
|
+
'ordering': ['pk'],
|
|
27
|
+
},
|
|
28
|
+
bases=(netbox.models.features.ChangeLoggingMixin, models.Model),
|
|
29
|
+
),
|
|
30
|
+
]
|
|
File without changes
|
|
Binary file
|
|
Binary file
|
netbox_ping/models.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
from netbox.models import NetBoxModel
|
|
3
|
+
from utilities.choices import ChoiceSet
|
|
4
|
+
|
|
5
|
+
class PluginSettingsModel(NetBoxModel):
|
|
6
|
+
"""Store plugin settings"""
|
|
7
|
+
class Meta:
|
|
8
|
+
verbose_name = 'Plugin Settings'
|
|
9
|
+
verbose_name_plural = 'Plugin Settings'
|
|
10
|
+
ordering = ['pk']
|
|
11
|
+
|
|
12
|
+
# Required fields from NetBoxModel
|
|
13
|
+
id = models.BigAutoField(
|
|
14
|
+
primary_key=True
|
|
15
|
+
)
|
|
16
|
+
created = models.DateTimeField(
|
|
17
|
+
auto_now_add=True
|
|
18
|
+
)
|
|
19
|
+
last_updated = models.DateTimeField(
|
|
20
|
+
auto_now=True
|
|
21
|
+
)
|
|
22
|
+
custom_field_data = models.JSONField(
|
|
23
|
+
blank=True,
|
|
24
|
+
null=True,
|
|
25
|
+
default=dict
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Our custom fields
|
|
29
|
+
update_tags = models.BooleanField(
|
|
30
|
+
default=True,
|
|
31
|
+
verbose_name='Update Tags',
|
|
32
|
+
help_text='Whether to update tags when scanning IPs'
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
def __str__(self):
|
|
36
|
+
return "NetBox Ping Settings"
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def get_settings(cls):
|
|
40
|
+
"""Get or create settings"""
|
|
41
|
+
settings, _ = cls.objects.get_or_create(pk=1)
|
|
42
|
+
return settings
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from netbox.plugins import PluginMenuItem, PluginMenuButton
|
|
2
|
+
|
|
3
|
+
menu_items = (
|
|
4
|
+
PluginMenuItem(
|
|
5
|
+
link='plugins:netbox_ping:ping_home',
|
|
6
|
+
link_text='Network Tools',
|
|
7
|
+
permissions=('ipam.view_prefix',),
|
|
8
|
+
buttons=(
|
|
9
|
+
PluginMenuButton(
|
|
10
|
+
link='plugins:netbox_ping:ping_home',
|
|
11
|
+
title='Network Tools',
|
|
12
|
+
icon_class='mdi mdi-lan',
|
|
13
|
+
permissions=('ipam.view_prefix',),
|
|
14
|
+
),
|
|
15
|
+
),
|
|
16
|
+
),
|
|
17
|
+
)
|
netbox_ping/plugin.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from django.contrib.contenttypes.models import ContentType
|
|
2
|
+
from extras.api.serializers import CustomFieldSerializer, TagSerializer
|
|
3
|
+
from extras.models import CustomField, Tag
|
|
4
|
+
from ipam.models import IPAddress
|
|
5
|
+
from rest_framework.exceptions import ValidationError
|
|
6
|
+
|
|
7
|
+
def initialize_plugin():
|
|
8
|
+
"""Initialize plugin custom fields and tags using the API"""
|
|
9
|
+
|
|
10
|
+
# Get ContentType ID for IPAddress
|
|
11
|
+
ipaddress_ct_id = ContentType.objects.get_for_model(IPAddress).id
|
|
12
|
+
|
|
13
|
+
# Create Up_Down custom field
|
|
14
|
+
up_down_data = {
|
|
15
|
+
'name': 'Up_Down',
|
|
16
|
+
'type': 'boolean',
|
|
17
|
+
'label': 'Up/Down Status',
|
|
18
|
+
'description': 'Indicates if the IP is responding to ping',
|
|
19
|
+
'required': False,
|
|
20
|
+
'filter_logic': 'exact',
|
|
21
|
+
'ui_visible': 'always',
|
|
22
|
+
'ui_editable': 'yes',
|
|
23
|
+
'is_cloneable': True,
|
|
24
|
+
'weight': 100,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# Create Auto_discovered custom field
|
|
28
|
+
discovered_data = {
|
|
29
|
+
'name': 'Auto_discovered',
|
|
30
|
+
'type': 'date',
|
|
31
|
+
'label': 'Auto Discovered',
|
|
32
|
+
'description': 'Date when this IP was automatically discovered',
|
|
33
|
+
'required': False,
|
|
34
|
+
'filter_logic': 'exact',
|
|
35
|
+
'ui_visible': 'always',
|
|
36
|
+
'ui_editable': 'yes',
|
|
37
|
+
'is_cloneable': True,
|
|
38
|
+
'weight': 101,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# Create custom fields
|
|
42
|
+
for cf_data in [up_down_data, discovered_data]:
|
|
43
|
+
try:
|
|
44
|
+
custom_field = CustomField.objects.get(name=cf_data['name'])
|
|
45
|
+
except CustomField.DoesNotExist:
|
|
46
|
+
try:
|
|
47
|
+
custom_field = CustomField.objects.create(**cf_data)
|
|
48
|
+
custom_field.object_types.set([ipaddress_ct_id])
|
|
49
|
+
print(f"Created custom field: {custom_field.name}")
|
|
50
|
+
except Exception as e:
|
|
51
|
+
print(f"Failed to create custom field: {str(e)}")
|
|
52
|
+
|
|
53
|
+
# Create tags
|
|
54
|
+
tags_data = [
|
|
55
|
+
{
|
|
56
|
+
'name': 'online',
|
|
57
|
+
'slug': 'online',
|
|
58
|
+
'description': 'IP is responding to ping',
|
|
59
|
+
'color': '4CAF50'
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
'name': 'offline',
|
|
63
|
+
'slug': 'offline',
|
|
64
|
+
'description': 'IP is not responding to ping',
|
|
65
|
+
'color': 'F44336'
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
'name': 'auto-discovered',
|
|
69
|
+
'slug': 'auto-discovered',
|
|
70
|
+
'description': 'IP was automatically discovered by scanning',
|
|
71
|
+
'color': '2196F3' # Blue color
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
for tag_data in tags_data:
|
|
76
|
+
try:
|
|
77
|
+
tag = Tag.objects.get(slug=tag_data['slug'])
|
|
78
|
+
except Tag.DoesNotExist:
|
|
79
|
+
try:
|
|
80
|
+
tag = Tag.objects.create(**tag_data)
|
|
81
|
+
print(f"Created tag: {tag.name}")
|
|
82
|
+
except Exception as e:
|
|
83
|
+
print(f"Failed to create tag: {str(e)}")
|
netbox_ping/signals.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from django.contrib.contenttypes.models import ContentType
|
|
2
|
+
from django.apps import apps
|
|
3
|
+
from django.db.models.signals import post_migrate
|
|
4
|
+
from django.dispatch import receiver
|
|
5
|
+
from extras.choices import CustomFieldTypeChoices
|
|
6
|
+
from extras.models import CustomField, Tag
|
|
7
|
+
|
|
8
|
+
@receiver(post_migrate)
|
|
9
|
+
def create_custom_fields_and_tags(sender, **kwargs):
|
|
10
|
+
"""
|
|
11
|
+
Create required custom fields and tags after database migrations complete
|
|
12
|
+
"""
|
|
13
|
+
if sender.name == 'netbox_ping':
|
|
14
|
+
# Create Up_Down custom field
|
|
15
|
+
custom_field, _ = CustomField.objects.get_or_create(
|
|
16
|
+
name='Up_Down',
|
|
17
|
+
defaults={
|
|
18
|
+
'type': CustomFieldTypeChoices.TYPE_BOOLEAN,
|
|
19
|
+
'label': 'Up/Down Status',
|
|
20
|
+
'description': 'Indicates if the IP is responding to ping',
|
|
21
|
+
'required': False,
|
|
22
|
+
'filter_logic': 'exact'
|
|
23
|
+
}
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# Add the custom field to IPAddress content type
|
|
27
|
+
ipaddress_ct = ContentType.objects.get_for_model(apps.get_model('ipam', 'ipaddress'))
|
|
28
|
+
if ipaddress_ct not in custom_field.content_types.all():
|
|
29
|
+
custom_field.content_types.add(ipaddress_ct)
|
|
30
|
+
|
|
31
|
+
# Create online/offline tags
|
|
32
|
+
Tag.objects.get_or_create(
|
|
33
|
+
name='online',
|
|
34
|
+
slug='online',
|
|
35
|
+
defaults={
|
|
36
|
+
'description': 'IP is responding to ping',
|
|
37
|
+
'color': '4CAF50' # Green color
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
Tag.objects.get_or_create(
|
|
42
|
+
name='offline',
|
|
43
|
+
slug='offline',
|
|
44
|
+
defaults={
|
|
45
|
+
'description': 'IP is not responding to ping',
|
|
46
|
+
'color': 'F44336' # Red color
|
|
47
|
+
}
|
|
48
|
+
)
|
netbox_ping/tables.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import django_tables2 as tables
|
|
2
|
+
from netbox.tables import NetBoxTable, columns
|
|
3
|
+
from ipam.models import IPAddress
|
|
4
|
+
from ipam.tables import IPAddressTable
|
|
5
|
+
|
|
6
|
+
class CustomIPAddressTable(IPAddressTable):
|
|
7
|
+
"""Custom IP Address table that includes the Up_Down status"""
|
|
8
|
+
|
|
9
|
+
up_down = columns.BooleanColumn(
|
|
10
|
+
verbose_name='Ping Status',
|
|
11
|
+
accessor=tables.A('_custom_field_data__Up_Down'),
|
|
12
|
+
order_by='_custom_field_data__Up_Down',
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
online = columns.TagColumn(
|
|
16
|
+
verbose_name='Online/Offline'
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
class Meta(IPAddressTable.Meta):
|
|
20
|
+
model = IPAddress
|
|
21
|
+
fields = IPAddressTable.Meta.fields + ('up_down', 'online')
|
|
22
|
+
default_columns = ('address', 'status', 'up_down', 'online', 'tenant', 'assigned_object', 'description')
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from netbox.plugins.template_content import PluginTemplateContent
|
|
2
|
+
|
|
3
|
+
class PrefixContent(PluginTemplateContent):
|
|
4
|
+
model = 'ipam.prefix'
|
|
5
|
+
|
|
6
|
+
def buttons(self):
|
|
7
|
+
prefix = self.context['object']
|
|
8
|
+
return f'''
|
|
9
|
+
<a href="/plugins/netbox_ping/ping-subnet/{prefix.id}/" class="btn btn-primary">
|
|
10
|
+
<span class="mdi mdi-lan"></span>
|
|
11
|
+
Ping Subnet
|
|
12
|
+
</a>
|
|
13
|
+
'''
|
|
14
|
+
|
|
15
|
+
template_content = [PrefixContent]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from netbox.plugins import PluginTemplateExtension
|
|
2
|
+
|
|
3
|
+
class PrefixExtension(PluginTemplateExtension):
|
|
4
|
+
model = 'ipam.prefix'
|
|
5
|
+
|
|
6
|
+
def buttons(self):
|
|
7
|
+
prefix = self.context['object']
|
|
8
|
+
return f'''
|
|
9
|
+
<a href="/plugins/netbox_ping/ping-subnet/{prefix.id}/" class="btn btn-primary">
|
|
10
|
+
<span class="mdi mdi-lan"></span>
|
|
11
|
+
Ping Subnet
|
|
12
|
+
</a>
|
|
13
|
+
'''
|
|
14
|
+
|
|
15
|
+
template_extensions = [PrefixExtension]
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
{% extends 'base/layout.html' %}
|
|
2
|
+
|
|
3
|
+
{% block title %}{{ device }} - Interface comparison{% endblock %}
|
|
4
|
+
{% block header %}
|
|
5
|
+
<nav class="breadcrumb-container px-3" aria-label="breadcrumb">
|
|
6
|
+
<ol class="breadcrumb">
|
|
7
|
+
<li class="breadcrumb-item"><a href="{% url 'dcim:device_list' %}">Device</a></li>
|
|
8
|
+
<li class="breadcrumb-item"><a href="{% url 'dcim:device_list' %}?site={{ device.site.slug }}">{{ device.site }}</a></li>
|
|
9
|
+
<li class="breadcrumb-item"><a href="{% url 'dcim:device' pk=device.id %}">{{ device }}</a></li>
|
|
10
|
+
</ol>
|
|
11
|
+
</nav>
|
|
12
|
+
{{ block.super }}
|
|
13
|
+
{% endblock %}
|
|
14
|
+
|
|
15
|
+
{% block content %}
|
|
16
|
+
<style>
|
|
17
|
+
.checkbox-group {
|
|
18
|
+
position: absolute;
|
|
19
|
+
}
|
|
20
|
+
</style>
|
|
21
|
+
<script>
|
|
22
|
+
function toggle(event) {
|
|
23
|
+
event = event || window.event;
|
|
24
|
+
var src = event.target || event.srcElement || event;
|
|
25
|
+
checkboxes = document.getElementsByName(src.id);
|
|
26
|
+
for(var checkbox of checkboxes) checkbox.checked = src.checked;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function uncheck(event) {
|
|
30
|
+
event = event || window.event;
|
|
31
|
+
var src = event.target || event.srcElement || event;
|
|
32
|
+
if (src.checked == false) {
|
|
33
|
+
document.getElementById(src.name).checked = false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<p>
|
|
39
|
+
{% if templates_count == interfaces_count %}
|
|
40
|
+
The Device Type and Device have the same number of Interfaces.
|
|
41
|
+
{% else %}
|
|
42
|
+
The Device Type and Device have a different number of Interfaces.<br>
|
|
43
|
+
Device Type: {{ templates_count }}<br>
|
|
44
|
+
Device: {{ interfaces_count }}
|
|
45
|
+
{% endif %}
|
|
46
|
+
</p>
|
|
47
|
+
|
|
48
|
+
<form method="post">
|
|
49
|
+
<!-- Interface templates -->
|
|
50
|
+
{% csrf_token %}
|
|
51
|
+
<table class="table" style="width: 50%; float: left;">
|
|
52
|
+
<tr>
|
|
53
|
+
<th colspan="2">Device Type</th>
|
|
54
|
+
<th>Actions</th>
|
|
55
|
+
</tr>
|
|
56
|
+
<tr>
|
|
57
|
+
<th>Name</th>
|
|
58
|
+
<th>Type</th>
|
|
59
|
+
<th>
|
|
60
|
+
<label class="checkbox-group">
|
|
61
|
+
<input type="checkbox" id="add_to_device" onclick="toggle(this)">
|
|
62
|
+
Add
|
|
63
|
+
</label>
|
|
64
|
+
</th>
|
|
65
|
+
</tr>
|
|
66
|
+
{% for template, interface in comparison_items %}
|
|
67
|
+
{% if template %}
|
|
68
|
+
<tr {% if not interface %}class="success" data-mark-connected="true"{% endif %}>
|
|
69
|
+
<td>
|
|
70
|
+
{% if interface and template.name != interface.name %}
|
|
71
|
+
<span style="background-color: #cde8c2">{{ template.name }}</span>
|
|
72
|
+
{% else %}
|
|
73
|
+
{{ template.name }}
|
|
74
|
+
{% endif %}
|
|
75
|
+
</td>
|
|
76
|
+
<td>{{ template.type_display }}</td>
|
|
77
|
+
<td>
|
|
78
|
+
{% if not interface %}
|
|
79
|
+
<label class="checkbox-group">
|
|
80
|
+
<input type="checkbox" name="add_to_device" value="{{ template.id }}" onclick="uncheck(this)">
|
|
81
|
+
Add
|
|
82
|
+
</label>
|
|
83
|
+
{% endif %}
|
|
84
|
+
</td>
|
|
85
|
+
</tr>
|
|
86
|
+
{% else %}
|
|
87
|
+
<tr>
|
|
88
|
+
<td> </td>
|
|
89
|
+
<td> </td>
|
|
90
|
+
<td> </td>
|
|
91
|
+
</tr>
|
|
92
|
+
{% endif %}
|
|
93
|
+
{% endfor %}
|
|
94
|
+
</table>
|
|
95
|
+
|
|
96
|
+
<table class="table" style="width: 50%; float: right;">
|
|
97
|
+
<!-- Interfaces -->
|
|
98
|
+
<tr>
|
|
99
|
+
<th colspan="2">Device</th>
|
|
100
|
+
<th colspan="2">Actions</th>
|
|
101
|
+
</tr>
|
|
102
|
+
<tr>
|
|
103
|
+
<th>Name</th>
|
|
104
|
+
<th>Type</th>
|
|
105
|
+
<th>
|
|
106
|
+
<label class="checkbox-group">
|
|
107
|
+
<input type="checkbox" id="remove_from_device" onclick="toggle(this)">
|
|
108
|
+
Remove
|
|
109
|
+
</label>
|
|
110
|
+
</th>
|
|
111
|
+
<th>
|
|
112
|
+
<label class="checkbox-group">
|
|
113
|
+
<input type="checkbox" id="fix_name" onclick="toggle(this)">
|
|
114
|
+
Fix Name
|
|
115
|
+
</label>
|
|
116
|
+
</th>
|
|
117
|
+
</tr>
|
|
118
|
+
{% for template, interface in comparison_items %}
|
|
119
|
+
{% if interface %}
|
|
120
|
+
<tr {% if not template %}class="danger" data-enabled="disabled"{% endif %}>
|
|
121
|
+
<td>
|
|
122
|
+
{% if template and template.name != interface.name %}
|
|
123
|
+
<span style="background-color: #eab2b2">{{ interface.name }}</span>
|
|
124
|
+
{% else %}
|
|
125
|
+
{{ interface.name }}
|
|
126
|
+
{% endif %}
|
|
127
|
+
</td>
|
|
128
|
+
<td>{{ interface.type_display }}</td>
|
|
129
|
+
<td>
|
|
130
|
+
{% if not template %}
|
|
131
|
+
<label class="checkbox-group">
|
|
132
|
+
<input type="checkbox" name="remove_from_device" value="{{ interface.id }}" onclick="uncheck(this)">
|
|
133
|
+
Remove
|
|
134
|
+
</label>
|
|
135
|
+
{% endif %}
|
|
136
|
+
</td>
|
|
137
|
+
<td>
|
|
138
|
+
{% if template and template.name != interface.name %}
|
|
139
|
+
<label class="checkbox-group">
|
|
140
|
+
<input type="checkbox" name="fix_name" value="{{ interface.id }}" onclick="uncheck(this)">
|
|
141
|
+
Fix Name
|
|
142
|
+
</label>
|
|
143
|
+
{% endif %}
|
|
144
|
+
</td>
|
|
145
|
+
</tr>
|
|
146
|
+
{% else %}
|
|
147
|
+
<tr>
|
|
148
|
+
<td> </td>
|
|
149
|
+
<td> </td>
|
|
150
|
+
<td> </td>
|
|
151
|
+
<td> </td>
|
|
152
|
+
</tr>
|
|
153
|
+
{% endif %}
|
|
154
|
+
{% endfor %}
|
|
155
|
+
</table>
|
|
156
|
+
<div class="text-right">
|
|
157
|
+
<input type="submit" value="Apply Changes" class="btn btn-green">
|
|
158
|
+
</div>
|
|
159
|
+
</form>
|
|
160
|
+
|
|
161
|
+
{% endblock %}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<div class="panel panel-default">
|
|
2
|
+
<div class="panel-heading">
|
|
3
|
+
<strong>Interfaces</strong>
|
|
4
|
+
</div>
|
|
5
|
+
<div class="panel-body">
|
|
6
|
+
<table class="table table-hover panel-body attr-table">
|
|
7
|
+
<tr>
|
|
8
|
+
<td>Total Interfaces</td>
|
|
9
|
+
<td>{{ interfaces.count }}</td>
|
|
10
|
+
</tr>
|
|
11
|
+
<tr>
|
|
12
|
+
<td>Real Interfaces</td>
|
|
13
|
+
<td>{{ real_interfaces.count }}</td>
|
|
14
|
+
</tr>
|
|
15
|
+
<tr>
|
|
16
|
+
<td>Interface Templates</td>
|
|
17
|
+
<td>{{ interface_templates.count }}</td>
|
|
18
|
+
</tr>
|
|
19
|
+
</table>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|