Explorar o código

Merge pull request #438 from digitalocean/develop

Release v1.4.2
Jeremy Stretch %!s(int64=9) %!d(string=hai) anos
pai
achega
93fccd5985

+ 25 - 0
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),
+        ),
+    ]

+ 51 - 8
netbox/dcim/models.py

@@ -57,20 +57,63 @@ DEVICE_ROLE_COLOR_CHOICES = [
 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

+ 6 - 0
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']

+ 14 - 0
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 @@ class IPAddressFilter(django_filters.FilterSet):
             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

+ 6 - 1
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}))

+ 4 - 4
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'
 
 

+ 1 - 1
netbox/netbox/settings.py

@@ -12,7 +12,7 @@ except ImportError:
                                "the documentation.")
 
 
-VERSION = '1.4.1'
+VERSION = '1.4.2'
 
 # Import local configuration
 for setting in ['ALLOWED_HOSTS', 'DATABASE', 'SECRET_KEY']:

+ 1 - 1
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]
     })
 
 

+ 5 - 5
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() {

+ 2 - 2
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) {

+ 1 - 1
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)
 

+ 8 - 0
netbox/templates/ipam/vlan.html

@@ -125,6 +125,14 @@
                 <strong>Prefixes</strong>
             </div>
             {% render_table prefix_table %}
+            {% if perms.ipam.add_prefix %}
+                <div class="panel-footer text-right">
+                    <a href="{% url 'ipam:prefix_add' %}?{% if vlan.tenant %}tenant={{ vlan.tenant.pk }}&{% endif %}site={{ vlan.site.pk }}&vlan={{ vlan.pk }}" class="btn btn-primary btn-xs">
+                        <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
+                        Add a prefix
+                    </a>
+                </div>
+            {% endif %}
         </div>
 	</div>
 </div>

+ 2 - 12
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 %}
-	<h1>Editing Secret: {{ secret }}</h1>
-{% else %}
-	<h1>Add a Secret</h1>
-{% endif %}
 <form action="." method="post" class="form form-horizontal requires-private-key">
     {% csrf_token %}
     {{ form.private_key }}
     <div class="row">
         <div class="col-md-6 col-md-offset-3">
+            <h3>{% if secret.pk %}Editing {{ secret }}{% else %}Add a Secret{% endif %}</h3>
             {% if form.non_field_errors %}
                 <div class="panel panel-danger">
                     <div class="panel-heading"><strong>Errors</strong></div>
@@ -23,10 +19,6 @@
                     </div>
                 </div>
             {% endif %}
-        </div>
-    </div>
-    <div class="row">
-        <div class="col-md-6">
             <div class="panel panel-default">
                 <div class="panel-heading"><strong>Secret Attributes</strong></div>
                 <div class="panel-body">
@@ -41,8 +33,6 @@
                     {% render_field form.userkeys %}
                 </div>
             </div>
-        </div>
-        <div class="col-md-6">
             <div class="panel panel-default">
                 <div class="panel-heading"><strong>Secret Data</strong></div>
                 <div class="panel-body">

+ 2 - 1
netbox/utilities/forms.py

@@ -231,7 +231,8 @@ class BootstrapMixin(forms.BaseForm):
                     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):