diff --git a/docs/installation/netbox.md b/docs/installation/netbox.md index e47e92133..0e116a29f 100644 --- a/docs/installation/netbox.md +++ b/docs/installation/netbox.md @@ -20,7 +20,8 @@ Python 3: ```no-highlight # yum install -y epel-release -# yum install -y gcc python3 python3-devel python3-pip libxml2-devel libxslt-devel libffi-devel graphviz openssl-devel +# yum install -y gcc python34 python34-devel python34-setuptools libxml2-devel libxslt-devel libffi-devel graphviz openssl-devel +# easy_install-3.4 pip ``` Python 2: diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 32b8850f6..80326b137 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -1422,9 +1422,16 @@ class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm): super(InterfaceBulkEditForm, self).__init__(*args, **kwargs) # Limit LAG choices to interfaces which belong to the parent device. + device = None if self.initial.get('device'): - self.fields['lag'].queryset = Interface.objects.filter( - device=self.initial['device'], form_factor=IFACE_FF_LAG + try: + device = Device.objects.get(pk=self.initial.get('device')) + except Device.DoesNotExist: + pass + if device is not None: + interface_ordering = device.device_type.interface_ordering + self.fields['lag'].queryset = Interface.objects.order_naturally(method=interface_ordering).filter( + device=device, form_factor=IFACE_FF_LAG ) else: self.fields['lag'].choices = [] @@ -1706,7 +1713,7 @@ class IPAddressForm(BootstrapMixin, CustomFieldForm): self.fields['interface'].required = True # If this device has only one interface, select it by default. - if len(interfaces) == 1: + if 'interface' not in self.initial and len(interfaces) == 1: self.fields['interface'].initial = interfaces[0] # If this device does not have any IP addresses assigned, default to setting the first IP as its primary. diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index cd1e0cd75..edaa56d22 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -13,7 +13,7 @@ from django.shortcuts import get_object_or_404, redirect, render from django.utils.http import urlencode from django.views.generic import View -from ipam.models import Prefix, IPAddress, Service, VLAN +from ipam.models import Prefix, Service, VLAN from circuits.models import Circuit from extras.models import Graph, TopologyMap, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE from utilities.forms import ConfirmationForm @@ -700,19 +700,15 @@ def device(request, pk): interfaces = Interface.objects.order_naturally(device.device_type.interface_ordering)\ .filter(device=device, mgmt_only=False)\ .select_related('connected_as_a__interface_b__device', 'connected_as_b__interface_a__device', - 'circuit_termination__circuit') + 'circuit_termination__circuit').prefetch_related('ip_addresses') mgmt_interfaces = Interface.objects.order_naturally(device.device_type.interface_ordering)\ .filter(device=device, mgmt_only=True)\ .select_related('connected_as_a__interface_b__device', 'connected_as_b__interface_a__device', - 'circuit_termination__circuit') + 'circuit_termination__circuit').prefetch_related('ip_addresses') device_bays = natsorted( DeviceBay.objects.filter(device=device).select_related('installed_device__device_type__manufacturer'), key=attrgetter('name') ) - - # Gather relevant device objects - ip_addresses = IPAddress.objects.filter(interface__device=device).select_related('interface', 'vrf')\ - .order_by('address') services = Service.objects.filter(device=device) secrets = device.secrets.all() @@ -743,7 +739,6 @@ def device(request, pk): 'interfaces': interfaces, 'mgmt_interfaces': mgmt_interfaces, 'device_bays': device_bays, - 'ip_addresses': ip_addresses, 'services': services, 'secrets': secrets, 'related_devices': related_devices, @@ -1599,9 +1594,7 @@ def ipaddress_assign(request, pk): return redirect('dcim:device', pk=device.pk) else: - form = forms.IPAddressForm(device, initial={ - 'interface': request.GET.get('interface', None) - }) + form = forms.IPAddressForm(device, initial=request.GET) return render(request, 'dcim/ipaddress_assign.html', { 'device': device, diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index 25bc82b91..d31c1aa41 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -210,28 +210,33 @@ class PrefixFromCSVForm(forms.ModelForm): site = self.cleaned_data.get('site') vlan_group_name = self.cleaned_data.get('vlan_group_name') vlan_vid = self.cleaned_data.get('vlan_vid') - - # Validate VLAN vlan_group = None + vlan = None + + # Validate VLAN group if vlan_group_name: try: vlan_group = VLANGroup.objects.get(site=site, name=vlan_group_name) except VLANGroup.DoesNotExist: - self.add_error('vlan_group_name', "Invalid VLAN group ({} - {}).".format(site, vlan_group_name)) - if vlan_vid and vlan_group: + if site: + self.add_error('vlan_group_name', "Invalid VLAN group ({} - {}).".format(site, vlan_group_name)) + else: + self.add_error('vlan_group_name', "Invalid global VLAN group ({}).".format(vlan_group_name)) + + # Validate VLAN + if vlan_vid: try: - self.instance.vlan = VLAN.objects.get(group=vlan_group, vid=vlan_vid) + self.instance.vlan = VLAN.objects.get(site=site, group=vlan_group, vid=vlan_vid) except VLAN.DoesNotExist: - self.add_error('vlan_vid', "Invalid VLAN ID ({} - {}).".format(vlan_group, vlan_vid)) - elif vlan_vid and site: - try: - self.instance.vlan = VLAN.objects.get(site=site, vid=vlan_vid) - except VLAN.DoesNotExist: - self.add_error('vlan_vid', "Invalid VLAN ID ({}) for site {}.".format(vlan_vid, site)) + if site: + self.add_error('vlan_vid', "Invalid VLAN ID ({}) for site {}.".format(vlan_vid, site)) + elif vlan_group: + self.add_error('vlan_vid', "Invalid VLAN ID ({}) for group {}.".format(vlan_vid, vlan_group_name)) + elif not vlan_group_name: + self.add_error('vlan_vid', "Invalid global VLAN ID ({}).".format(vlan_vid)) except VLAN.MultipleObjectsReturned: self.add_error('vlan_vid', "Multiple VLANs found ({} - VID {})".format(site, vlan_vid)) - elif vlan_vid: - self.add_error('vlan_vid', "Must specify site and/or VLAN group when assigning a VLAN.") + self.instance.vlan = vlan def save(self, *args, **kwargs): diff --git a/netbox/project-static/css/base.css b/netbox/project-static/css/base.css index 11ea04b72..0b740159a 100644 --- a/netbox/project-static/css/base.css +++ b/netbox/project-static/css/base.css @@ -313,6 +313,16 @@ li.occupied + li.available { border-top: 1px solid #474747; } +/* Devices */ +table.component-list tr.ipaddress td { + background-color: #eeffff; + padding-bottom: 4px; + padding-top: 4px; +} +table.component-list tr.ipaddress:hover td { + background-color: #e6f7f7; +} + /* Misc */ .banner-bottom { margin-bottom: 50px; diff --git a/netbox/templates/_base.html b/netbox/templates/_base.html index 800739b74..7dcb83d73 100644 --- a/netbox/templates/_base.html +++ b/netbox/templates/_base.html @@ -3,11 +3,11 @@ - NetBox - {% block title %}Home{% endblock %} - + NetBox - {% block title %}Home{% endblock %} + - + @@ -256,10 +256,10 @@ -
+
{% if settings.BANNER_TOP %} {% endif %} {% if settings.MAINTENANCE_MODE %} @@ -268,24 +268,24 @@

NetBox is currently in maintenance mode. Functionality may be limited.

{% endif %} - {% for message in messages %} - - {% endfor %} - {% block content %}{% endblock %} + {% for message in messages %} + + {% endfor %} + {% block content %}{% endblock %}
- {% if settings.BANNER_BOTTOM %} - -
+ diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index d38f60cb3..2aa1666c8 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -194,35 +194,6 @@ {% endif %} {% endif %} -
-
- IP Addresses -
- {% if ip_addresses %} - - {% for ip in ip_addresses %} - {% include 'dcim/inc/ipaddress.html' %} - {% endfor %} -
- {% elif interfaces or mgmt_interfaces %} -
- None assigned -
- {% else %} -
- Create an interface to assign an IP. -
- {% endif %} - {% if perms.ipam.add_ipaddress %} - {% if interfaces or mgmt_interfaces %} - - {% endif %} - {% endif %} -
Services @@ -250,7 +221,7 @@
Critical Connections
- +
{% for iface in mgmt_interfaces %} {% include 'dcim/inc/interface.html' with icon='wrench' %} {% empty %} @@ -375,7 +346,7 @@ {% endif %} -
+
{% for devicebay in device_bays %} {% include 'dcim/inc/devicebay.html' with selectable=True %} {% empty %} @@ -416,6 +387,9 @@
Interfaces
+ {% if perms.dcim.change_interface and interfaces|length > 1 %}
-
+
{% for iface in interfaces %} {% include 'dcim/inc/interface.html' with selectable=True %} {% empty %} @@ -485,7 +459,7 @@ {% endif %} -
+
{% for csp in cs_ports %} {% include 'dcim/inc/consoleserverport.html' with selectable=True %} {% empty %} @@ -537,7 +511,7 @@ {% endif %} -
+
{% for po in power_outlets %} {% include 'dcim/inc/poweroutlet.html' with selectable=True %} {% empty %} @@ -628,6 +602,18 @@ $(".powerport-toggle").click(function() { $(".interface-toggle").click(function() { return toggleConnection($(this), "dcim/interface-connections/"); }); +// Toggle the display of IP addresses under interfaces +$('button.toggle-ips').click(function() { + var selected = $(this).attr('selected'); + if (selected) { + $('table.component-list tr.ipaddress').hide(); + } else { + $('table.component-list tr.ipaddress').show(); + } + $(this).attr('selected', !selected); + $(this).children('span').toggleClass('glyphicon-check glyphicon-unchecked'); + return false; +}); diff --git a/netbox/templates/dcim/inc/interface.html b/netbox/templates/dcim/inc/interface.html index 472727e4a..7fe068304 100644 --- a/netbox/templates/dcim/inc/interface.html +++ b/netbox/templates/dcim/inc/interface.html @@ -1,4 +1,4 @@ - + {% if selectable and perms.dcim.change_interface or perms.dcim.delete_interface %} - - {% if iface.is_virtual %} + {% if iface.is_lag %} + + {% elif iface.is_virtual %} {% elif iface.connection %} {% with iface.connected_interface as connected_iface %} @@ -51,7 +50,7 @@ Not connected {% endif %} - -{% if ip_addresses %} - {% for ip in ip_addresses %} - {% if ip.interface_id == iface.id %} - +{% for ip in iface.ip_addresses.all %} + + {% if selectable and perms.dcim.change_interface or perms.dcim.delete_interface %} - - - - - {% endif %} - {% endfor %} -{% endif %} + + + + + +{% endfor %} diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index 9a8b5ba1d..4edf87ce8 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -12,7 +12,9 @@ from django.forms import CharField, ModelMultipleChoiceField, MultipleHiddenInpu from django.http import HttpResponse from django.shortcuts import get_object_or_404, redirect, render from django.template import TemplateSyntaxError +from django.utils.html import escape from django.utils.http import is_safe_url +from django.utils.safestring import mark_safe from django.views.generic import View from extras.forms import CustomFieldForm @@ -194,10 +196,10 @@ class ObjectEditView(View): msg = u'Created ' if obj_created else u'Modified ' msg += self.model._meta.verbose_name if hasattr(obj, 'get_absolute_url'): - msg = u'{} {}'.format(msg, obj.get_absolute_url(), obj) + msg = u'{} {}'.format(msg, obj.get_absolute_url(), escape(obj)) else: - msg = u'{} {}'.format(msg, obj) - messages.success(request, msg) + msg = u'{} {}'.format(msg, escape(obj)) + messages.success(request, mark_safe(msg)) if obj_created: UserAction.objects.log_create(request.user, obj, msg) else:
@@ -16,10 +16,9 @@
{{ iface.member_interfaces.all|join:", "|default:"No members" }} {% endif %}
- {{ iface.mac_address|default:'' }} - LAG interfaceVirtual interface + {% if show_graphs %} {% if iface.circuit_termination or iface.connection %} - + {% else %} @@ -109,19 +108,41 @@ {% endif %}
{{ ip }}{{ ip.vrf|default:"Global" }}{% if device.primary_ip4 == ip or device.primary_ip6 == ip %} - Primary - {% endif %} - {{ ip.description }}
+ {{ ip }} + {% if ip.description %} + + {% endif %} + {% if device.primary_ip4 == ip or device.primary_ip6 == ip %} + Primary + {% endif %} + + {% if ip.vrf %} + {{ ip.vrf }} + {% else %} + Global + {% endif %} + + {{ ip.get_status_display }} + + {% if perms.ipam.edit_ipaddress %} + + + + {% endif %} + {% if perms.ipam.delete_ipaddress %} + + + + {% endif %} +