diff --git a/netbox/dcim/migrations/0013_add_interface_form_factors.py b/netbox/dcim/migrations/0013_add_interface_form_factors.py new file mode 100644 index 00000000000..310eb1eb687 --- /dev/null +++ b/netbox/dcim/migrations/0013_add_interface_form_factors.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.8 on 2016-08-06 20:24 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0012_site_rack_device_add_tenant'), + ] + + operations = [ + migrations.AlterField( + model_name='interface', + name='form_factor', + field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual']]], [b'Ethernet', [[800, b'100BASE-TX (10/100M)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Modular', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1300, b'XFP (10GE)'], [1200, b'SFP+ (10GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus']]]], default=1200), + ), + migrations.AlterField( + model_name='interfacetemplate', + name='form_factor', + field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual']]], [b'Ethernet', [[800, b'100BASE-TX (10/100M)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Modular', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1300, b'XFP (10GE)'], [1200, b'SFP+ (10GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus']]]], default=1200), + ), + ] diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index 39d075a947a..119336569a1 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -57,20 +57,63 @@ IFACE_FF_VIRTUAL = 0 IFACE_FF_100M_COPPER = 800 IFACE_FF_1GE_COPPER = 1000 +IFACE_FF_GBIC = 1050 IFACE_FF_SFP = 1100 IFACE_FF_10GE_COPPER = 1150 IFACE_FF_SFP_PLUS = 1200 IFACE_FF_XFP = 1300 IFACE_FF_QSFP_PLUS = 1400 +IFACE_FF_CFP = 1500 +IFACE_FF_QSFP28 = 1600 +IFACE_FF_T1 = 4000 +IFACE_FF_E1 = 4010 +IFACE_FF_T3 = 4040 +IFACE_FF_E3 = 4050 +IFACE_FF_STACKWISE = 5000 +IFACE_FF_STACKWISE_PLUS = 5050 IFACE_FF_CHOICES = [ - [IFACE_FF_VIRTUAL, 'Virtual'], - [IFACE_FF_100M_COPPER, '10/100M (100BASE-TX)'], - [IFACE_FF_1GE_COPPER, '1GE (1000BASE-T)'], - [IFACE_FF_SFP, '1GE (SFP)'], - [IFACE_FF_10GE_COPPER, '10GE (10GBASE-T)'], - [IFACE_FF_SFP_PLUS, '10GE (SFP+)'], - [IFACE_FF_XFP, '10GE (XFP)'], - [IFACE_FF_QSFP_PLUS, '40GE (QSFP+)'], + [ + 'Virtual interfaces', + [ + [IFACE_FF_VIRTUAL, 'Virtual'], + ] + ], + [ + 'Ethernet', + [ + [IFACE_FF_100M_COPPER, '100BASE-TX (10/100M)'], + [IFACE_FF_1GE_COPPER, '1000BASE-T (1GE)'], + [IFACE_FF_10GE_COPPER, '10GBASE-T (10GE)'], + ] + ], + [ + 'Modular', + [ + [IFACE_FF_GBIC, 'GBIC (1GE)'], + [IFACE_FF_SFP, 'SFP (1GE)'], + [IFACE_FF_XFP, 'XFP (10GE)'], + [IFACE_FF_SFP_PLUS, 'SFP+ (10GE)'], + [IFACE_FF_QSFP_PLUS, 'QSFP+ (40GE)'], + [IFACE_FF_CFP, 'CFP (100GE)'], + [IFACE_FF_QSFP28, 'QSFP28 (100GE)'], + ] + ], + [ + 'Serial', + [ + [IFACE_FF_T1, 'T1 (1.544 Mbps)'], + [IFACE_FF_E1, 'E1 (2.048 Mbps)'], + [IFACE_FF_T3, 'T3 (45 Mbps)'], + [IFACE_FF_E3, 'E3 (34 Mbps)'], + ] + ], + [ + 'Stacking', + [ + [IFACE_FF_STACKWISE, 'Cisco StackWise'], + [IFACE_FF_STACKWISE_PLUS, 'Cisco StackWise Plus'], + ] + ], ] STATUS_ACTIVE = True diff --git a/netbox/extras/admin.py b/netbox/extras/admin.py index c5aec6732d2..f7ddbbae29d 100644 --- a/netbox/extras/admin.py +++ b/netbox/extras/admin.py @@ -19,3 +19,9 @@ class TopologyMapAdmin(admin.ModelAdmin): prepopulated_fields = { 'slug': ['name'], } + + +@admin.register(UserAction) +class UserActionAdmin(admin.ModelAdmin): + actions = None + list_display = ['user', 'action', 'content_type', 'object_id', 'message'] diff --git a/netbox/ipam/filters.py b/netbox/ipam/filters.py index de8a240bb4f..3a73caf8459 100644 --- a/netbox/ipam/filters.py +++ b/netbox/ipam/filters.py @@ -191,6 +191,10 @@ class IPAddressFilter(django_filters.FilterSet): action='search', label='Search', ) + parent = django_filters.MethodFilter( + action='search_by_parent', + label='Parent prefix', + ) vrf = django_filters.MethodFilter( action='_vrf', label='VRF', @@ -238,6 +242,16 @@ def search(self, queryset, value): pass return queryset.filter(qs_filter) + def search_by_parent(self, queryset, value): + value = value.strip() + if not value: + return queryset + try: + query = str(IPNetwork(value).cidr) + return queryset.filter(address__net_contained_or_equal=query) + except AddrFormatError: + return queryset.none() + def _vrf(self, queryset, value): if str(value) == '': return queryset diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index 134c2933c1c..666b2ee81a4 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -293,7 +293,9 @@ def prefix_role_choices(): class PrefixFilterForm(forms.Form, BootstrapMixin): - parent = forms.CharField(required=False, label='Search Within') + parent = forms.CharField(required=False, label='Search Within', widget=forms.TextInput(attrs={ + 'placeholder': 'Network', + })) vrf = forms.MultipleChoiceField(required=False, choices=prefix_vrf_choices, label='VRF', widget=forms.SelectMultiple(attrs={'size': 6})) tenant = forms.MultipleChoiceField(required=False, choices=tenant_choices, label='Tenant', @@ -444,6 +446,9 @@ def ipaddress_vrf_choices(): class IPAddressFilterForm(forms.Form, BootstrapMixin): + parent = forms.CharField(required=False, label='Search Within', widget=forms.TextInput(attrs={ + 'placeholder': 'Prefix', + })) family = forms.ChoiceField(required=False, choices=ipaddress_family_choices, label='Address Family') vrf = forms.MultipleChoiceField(required=False, choices=ipaddress_vrf_choices, label='VRF', widget=forms.SelectMultiple(attrs={'size': 6})) diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index ce3c2a8f907..6509f8caeb4 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -66,10 +66,10 @@ def add_available_ipaddresses(prefix, ipaddress_list): # Iterate through existing IPs and annotate free ranges for ip in ipaddress_list: if prev_ip: - skipped_count = int(ip.address.ip - prev_ip.address.ip - 1) - if skipped_count: + diff = int(ip.address.ip - prev_ip.address.ip) + if diff > 1: first_skipped = '{}/{}'.format(prev_ip.address.ip + 1, prefix.prefixlen) - output.append((skipped_count, first_skipped)) + output.append((diff - 1, first_skipped)) output.append(ip) prev_ip = ip @@ -373,7 +373,7 @@ class PrefixEditView(PermissionRequiredMixin, ObjectEditView): permission_required = 'ipam.change_prefix' model = Prefix form_class = forms.PrefixForm - fields_initial = ['site', 'vrf', 'prefix'] + fields_initial = ['vrf', 'tenant', 'site', 'prefix', 'vlan'] cancel_url = 'ipam:prefix_list' diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 4c5abcd5613..d1272f4a268 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -12,7 +12,7 @@ "the documentation.") -VERSION = '1.4.1' +VERSION = '1.4.2' # Import local configuration for setting in ['ALLOWED_HOSTS', 'DATABASE', 'SECRET_KEY']: diff --git a/netbox/netbox/views.py b/netbox/netbox/views.py index 801a0a8b93c..2da97a2cf23 100644 --- a/netbox/netbox/views.py +++ b/netbox/netbox/views.py @@ -43,7 +43,7 @@ def home(request): return render(request, 'home.html', { 'stats': stats, - 'recent_activity': UserAction.objects.select_related('user')[:15] + 'recent_activity': UserAction.objects.select_related('user')[:50] }) diff --git a/netbox/project-static/js/forms.js b/netbox/project-static/js/forms.js index fd510af3ffb..f8e3a2b2028 100644 --- a/netbox/project-static/js/forms.js +++ b/netbox/project-static/js/forms.js @@ -13,11 +13,11 @@ $(document).ready(function() { // Slugify function slugify(s, num_chars) { - s = s.replace(/[^\-\.\w\s]/g, ''); // Remove unneeded chars - s = s.replace(/^\s+|\s+$/g, ''); // Trim leading/trailing spaces - s = s.replace(/[\-\.\s]+/g, '-'); // Convert spaces and decimals to hyphens - s = s.toLowerCase(); // Convert to lowercase - return s.substring(0, num_chars); // Trim to first num_chars chars + s = s.replace(/[^\-\.\w\s]/g, ''); // Remove unneeded chars + s = s.replace(/^[\s\.]+|[\s\.]+$/g, ''); // Trim leading/trailing spaces + s = s.replace(/[\-\.\s]+/g, '-'); // Convert spaces and decimals to hyphens + s = s.toLowerCase(); // Convert to lowercase + return s.substring(0, num_chars); // Trim to first num_chars chars } var slug_field = $('#id_slug'); slug_field.change(function() { diff --git a/netbox/project-static/js/secrets.js b/netbox/project-static/js/secrets.js index 181984b7182..7f67cdcda55 100644 --- a/netbox/project-static/js/secrets.js +++ b/netbox/project-static/js/secrets.js @@ -83,8 +83,8 @@ $(document).ready(function() { }, success: function (response, status) { $('#secret_' + secret_id).html(response.plaintext); - $('button.unlock-secret').hide(); - $('button.lock-secret').show(); + $('button.unlock-secret[secret-id=' + secret_id + ']').hide(); + $('button.lock-secret[secret-id=' + secret_id + ']').show(); }, error: function (xhr, ajaxOptions, thrownError) { if (xhr.status == 403) { diff --git a/netbox/secrets/views.py b/netbox/secrets/views.py index 6e50d83fd12..3518716750c 100644 --- a/netbox/secrets/views.py +++ b/netbox/secrets/views.py @@ -92,7 +92,7 @@ def secret_add(request, pk): messages.success(request, "Added new secret: {0}".format(secret)) if '_addanother' in request.POST: - return redirect('secrets:secret_add') + return redirect('dcim:device_addsecret', pk=device.pk) else: return redirect('secrets:secret', pk=secret.pk) diff --git a/netbox/templates/ipam/vlan.html b/netbox/templates/ipam/vlan.html index 4e5037c7c82..d27184824c3 100644 --- a/netbox/templates/ipam/vlan.html +++ b/netbox/templates/ipam/vlan.html @@ -125,6 +125,14 @@

VLAN {{ vlan.display_name }}

Prefixes {% render_table prefix_table %} + {% if perms.ipam.add_prefix %} + + {% endif %} diff --git a/netbox/templates/secrets/secret_edit.html b/netbox/templates/secrets/secret_edit.html index 0698e718423..9a3df1a4550 100644 --- a/netbox/templates/secrets/secret_edit.html +++ b/netbox/templates/secrets/secret_edit.html @@ -2,19 +2,15 @@ {% load static from staticfiles %} {% load form_helpers %} -{% block title %}{% if secret.pk %}Editing Secret: {{ secret }}{% else %}Add a Secret{% endif %}{% endblock %} +{% block title %}{% if secret.pk %}Editing {{ secret }}{% else %}Add a Secret{% endif %}{% endblock %} {% block content %} -{% if secret.pk %} -

Editing Secret: {{ secret }}

-{% else %} -

Add a Secret

-{% endif %}
{% csrf_token %} {{ form.private_key }}
+

{% if secret.pk %}Editing {{ secret }}{% else %}Add a Secret{% endif %}

{% if form.non_field_errors %}
Errors
@@ -23,10 +19,6 @@

Add a Secret

{% endif %} -
- -
-
Secret Attributes
@@ -41,8 +33,6 @@

Add a Secret

{% render_field form.userkeys %}
-
-
Secret Data
diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py index 836fe633f25..979bdd0ad93 100644 --- a/netbox/utilities/forms.py +++ b/netbox/utilities/forms.py @@ -231,7 +231,8 @@ def __init__(self, *args, **kwargs): field.widget.attrs['class'] = 'form-control' if field.required: field.widget.attrs['required'] = 'required' - field.widget.attrs['placeholder'] = field.label + if 'placeholder' not in field.widget.attrs: + field.widget.attrs['placeholder'] = field.label class ConfirmationForm(forms.Form, BootstrapMixin):