Compare commits

..

30 Commits

Author SHA1 Message Date
Jeremy Stretch
58e3d5ae09 Merge pull request #474 from digitalocean/develop
Release v1.5.2
2016-08-16 09:33:57 -04:00
Jeremy Stretch
2eb8b4fe71 Release v1.5.2 2016-08-16 09:32:53 -04:00
Jeremy Stretch
989ec721d3 Fixes #472: Hide the connection button for interfaces which have a circuit terminated to them 2016-08-16 09:29:20 -04:00
Jeremy Stretch
d045429b51 Fixes #469: Added missing import buttons to list views 2016-08-15 17:57:17 -04:00
Jeremy Stretch
78c3b25f0a Fixes #467: Include prefixes and IPs which inherit tenancy from their VRF in tenant stats 2016-08-15 16:11:17 -04:00
Jeremy Stretch
13136d0ccb Fixes #468: Added validation to prevent a connected interface from having its form factor set to 'virtual' 2016-08-15 15:52:06 -04:00
Jeremy Stretch
8faa16c831 Fixes #460: For real this time 2016-08-15 15:39:48 -04:00
Jeremy Stretch
4f774f8ba6 Fixes #460: Corrected ordering of IP addresses with differing prefix lengths 2016-08-13 01:02:03 -04:00
Jeremy Stretch
bf1b8ab9b8 Enable custom export templates for Tenants 2016-08-12 13:29:24 -04:00
Jeremy Stretch
b74f338aa1 Fixes #463: Prevent prepopulation of livesearch field with '---------' 2016-08-12 11:24:29 -04:00
Jeremy Stretch
35aa8acd09 Post-release version bump 2016-08-11 13:54:07 -04:00
Jeremy Stretch
6a48b310d2 Merge pull request #459 from digitalocean/develop
Release v1.5.1
2016-08-11 13:53:34 -04:00
Jeremy Stretch
0b4d3446bf Release v1.5.1 2016-08-11 13:47:09 -04:00
Jeremy Stretch
2b8e06faa2 Added Device asset_tag field to API tests 2016-08-11 13:46:44 -04:00
Jeremy Stretch
c86a1123f0 Closes #421: Added asset_tag field to devices 2016-08-11 11:58:21 -04:00
Jeremy Stretch
e2ad1d4be0 Closes #456: Added IP search box to home page 2016-08-11 11:06:56 -04:00
Jeremy Stretch
219f084805 Fixes #457: Added role field to rack edit form 2016-08-11 10:19:50 -04:00
Jeremy Stretch
6f12297dcf Fixes #441: Added openssl-devel to list of dependencies 2016-08-10 22:37:51 -04:00
Jeremy Stretch
098ff961e3 Fixes #454: Correct typecasting on rack export 2016-08-10 22:27:27 -04:00
Jeremy Stretch
74528c6036 Colorized roles in rack and device lists 2016-08-10 22:20:09 -04:00
Jeremy Stretch
2509405465 Merge pull request #453 from digitalocean/develop
Release v1.5.0
2016-08-10 17:47:48 -04:00
Jeremy Stretch
93fccd5985 Merge pull request #438 from digitalocean/develop
Release v1.4.2
2016-08-06 16:31:32 -04:00
Jeremy Stretch
946a1b751b Merge pull request #423 from digitalocean/develop
Release v1.4.1
2016-08-03 17:46:13 -04:00
Jeremy Stretch
9889e120bd Merge pull request #408 from digitalocean/develop
Release v1.4.0
2016-08-01 13:43:48 -04:00
Jeremy Stretch
af5dba2e0d Merge pull request #386 from digitalocean/develop
Release v1.3.2
2016-07-26 12:22:29 -04:00
Jeremy Stretch
8cb38de7d5 Merge pull request #357 from digitalocean/develop
Release v1.3.1
2016-07-21 11:48:40 -04:00
Jeremy Stretch
5ba5e8def9 Merge pull request #324 from digitalocean/develop
Release v1.3.0
2016-07-18 13:49:08 -04:00
Jeremy Stretch
4e64e1ea95 Merge pull request #299 from digitalocean/develop
Release v1.2.2
2016-07-14 15:21:40 -04:00
Jeremy Stretch
300aff71bb Merge pull request #286 from digitalocean/develop
Release v1.2.1
2016-07-13 12:08:48 -04:00
Jeremy Stretch
0c3970233e Merge pull request #269 from digitalocean/develop
Release v1.2.0
2016-07-12 11:37:56 -04:00
28 changed files with 210 additions and 52 deletions

View File

@@ -10,9 +10,10 @@ NetBox requires following system dependencies:
* libffi-dev
* graphviz
* libpq-dev
* libssl-dev
```
# sudo apt-get install -y python2.7 python-dev git python-pip libxml2-dev libxslt1-dev libffi-dev graphviz libpq-dev
# sudo apt-get install -y python2.7 python-dev python-pip libxml2-dev libxslt1-dev libffi-dev graphviz libpq-dev libssl-dev
```
You may opt to install NetBox either from a numbered release or by cloning the master branch of its repository on GitHub.

View File

@@ -183,7 +183,8 @@ class DeviceAdmin(admin.ModelAdmin):
DeviceBayAdmin,
ModuleAdmin,
]
list_display = ['display_name', 'device_type', 'device_role', 'primary_ip', 'rack', 'position', 'serial']
list_display = ['display_name', 'device_type', 'device_role', 'primary_ip', 'rack', 'position', 'asset_tag',
'serial']
list_filter = ['device_role']
def get_queryset(self, request):

View File

@@ -250,8 +250,9 @@ class DeviceSerializer(serializers.ModelSerializer):
class Meta:
model = Device
fields = ['id', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'rack',
'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', 'primary_ip6', 'comments']
fields = ['id', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform', 'serial',
'asset_tag', 'rack', 'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4',
'primary_ip6', 'comments']
def get_parent_device(self, obj):
try:

View File

@@ -239,15 +239,16 @@ class DeviceFilter(django_filters.FilterSet):
class Meta:
model = Device
fields = ['q', 'name', 'site_id', 'site', 'rack_id', 'role_id', 'role', 'device_type_id', 'manufacturer_id',
'manufacturer', 'model', 'platform_id', 'platform', 'status', 'is_console_server', 'is_pdu',
'is_network_device']
fields = ['q', 'name', 'serial', 'asset_tag', 'site_id', 'site', 'rack_id', 'role_id', 'role', 'device_type_id',
'manufacturer_id', 'manufacturer', 'model', 'platform_id', 'platform', 'status', 'is_console_server',
'is_pdu', 'is_network_device']
def search(self, queryset, value):
return queryset.filter(
Q(name__icontains=value) |
Q(serial__icontains=value) |
Q(modules__serial__icontains=value) |
Q(serial__icontains=value.strip()) |
Q(modules__serial__icontains=value.strip()) |
Q(asset_tag=value.strip()) |
Q(comments__icontains=value)
).distinct()

View File

@@ -425,8 +425,8 @@ class DeviceForm(forms.ModelForm, BootstrapMixin):
class Meta:
model = Device
fields = ['name', 'device_role', 'tenant', 'device_type', 'serial', 'site', 'rack', 'position', 'face', 'status',
'platform', 'primary_ip4', 'primary_ip6', 'comments']
fields = ['name', 'device_role', 'tenant', 'device_type', 'serial', 'asset_tag', 'site', 'rack', 'position',
'face', 'status', 'platform', 'primary_ip4', 'primary_ip6', 'comments']
help_texts = {
'device_role': "The function this device serves",
'serial': "Chassis serial number",
@@ -546,8 +546,8 @@ class DeviceFromCSVForm(BaseDeviceFromCSVForm):
face = forms.CharField(required=False)
class Meta(BaseDeviceFromCSVForm.Meta):
fields = ['name', 'device_role', 'tenant', 'manufacturer', 'model_name', 'platform', 'serial', 'site',
'rack_name', 'position', 'face']
fields = ['name', 'device_role', 'tenant', 'manufacturer', 'model_name', 'platform', 'serial', 'asset_tag',
'site', 'rack_name', 'position', 'face']
def clean(self):
@@ -582,8 +582,8 @@ class ChildDeviceFromCSVForm(BaseDeviceFromCSVForm):
device_bay_name = forms.CharField(required=False)
class Meta(BaseDeviceFromCSVForm.Meta):
fields = ['name', 'device_role', 'tenant', 'manufacturer', 'model_name', 'platform', 'serial', 'parent',
'device_bay_name']
fields = ['name', 'device_role', 'tenant', 'manufacturer', 'model_name', 'platform', 'serial', 'asset_tag',
'parent', 'device_bay_name']
def clean(self):

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2016-08-11 15:42
from __future__ import unicode_literals
from django.db import migrations
import utilities.fields
class Migration(migrations.Migration):
dependencies = [
('dcim', '0017_rack_add_role'),
]
operations = [
migrations.AddField(
model_name='device',
name='asset_tag',
field=utilities.fields.NullableCharField(blank=True, help_text=b'A unique tag used to identify this device', max_length=50, null=True, unique=True, verbose_name=b'Asset tag'),
),
]

View File

@@ -373,7 +373,7 @@ class Rack(CreatedUpdatedModel):
self.tenant.name if self.tenant else '',
self.role.name if self.role else '',
self.get_type_display() if self.type else '',
self.width,
str(self.width),
str(self.u_height),
])
@@ -737,6 +737,8 @@ class Device(CreatedUpdatedModel):
platform = models.ForeignKey('Platform', related_name='devices', blank=True, null=True, on_delete=models.SET_NULL)
name = NullableCharField(max_length=50, blank=True, null=True, unique=True)
serial = models.CharField(max_length=50, blank=True, verbose_name='Serial number')
asset_tag = NullableCharField(max_length=50, blank=True, null=True, unique=True, verbose_name='Asset tag',
help_text='A unique tag used to identify this device')
rack = models.ForeignKey('Rack', related_name='devices', on_delete=models.PROTECT)
position = models.PositiveSmallIntegerField(blank=True, null=True, validators=[MinValueValidator(1)],
verbose_name='Position (U)',
@@ -832,6 +834,7 @@ class Device(CreatedUpdatedModel):
self.device_type.model,
self.platform.name if self.platform else '',
self.serial,
self.asset_tag if self.asset_tag else '',
self.rack.site.name,
self.rack.name,
str(self.position) if self.position else '',
@@ -1029,6 +1032,13 @@ class Interface(models.Model):
def __unicode__(self):
return self.name
def clean(self):
if self.form_factor == IFACE_FF_VIRTUAL and self.is_connected:
raise ValidationError({'form_factor': "Virtual interfaces cannot be connected to another interface or "
"circuit. Disconnect the interface or choose a physical form "
"factor."})
@property
def is_physical(self):
return self.form_factor != IFACE_FF_VIRTUAL

View File

@@ -1,7 +1,7 @@
import django_tables2 as tables
from django_tables2.utils import Accessor
from utilities.tables import BaseTable, ColorColumn, ToggleColumn
from utilities.tables import BaseTable, ToggleColumn
from .models import (
ConsolePort, ConsolePortTemplate, ConsoleServerPortTemplate, Device, DeviceBayTemplate, DeviceRole, DeviceType,
@@ -10,6 +10,10 @@ from .models import (
)
COLOR_LABEL = """
<label class="label {{ record.color }}">{{ record }}</label>
"""
DEVICE_LINK = """
<a href="{% url 'dcim:device' pk=record.pk %}">
{{ record.name|default:'<span class="label label-info">Unnamed device</span>' }}
@@ -28,6 +32,14 @@ RACKROLE_ACTIONS = """
{% endif %}
"""
RACK_ROLE = """
{% if record.role %}
<label class="label {{ record.role.color }}">{{ value }}</label>
{% else %}
&mdash;
{% endif %}
"""
DEVICEROLE_ACTIONS = """
{% if perms.dcim.change_devicerole %}
<a href="{% url 'dcim:devicerole_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
@@ -46,6 +58,10 @@ PLATFORM_ACTIONS = """
{% endif %}
"""
DEVICE_ROLE = """
<label class="label {{ record.device_role.color }}">{{ value }}</label>
"""
STATUS_ICON = """
{% if record.status %}
<span class="glyphicon glyphicon-ok-sign text-success" title="Active" aria-hidden="true"></span>
@@ -108,7 +124,7 @@ class RackRoleTable(BaseTable):
pk = ToggleColumn()
name = tables.LinkColumn(verbose_name='Name')
rack_count = tables.Column(verbose_name='Racks')
color = ColorColumn(verbose_name='Color')
color = tables.TemplateColumn(COLOR_LABEL, verbose_name='Color')
slug = tables.Column(verbose_name='Slug')
actions = tables.TemplateColumn(template_code=RACKROLE_ACTIONS, attrs={'td': {'class': 'text-right'}},
verbose_name='')
@@ -129,7 +145,7 @@ class RackTable(BaseTable):
group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
facility_id = tables.Column(verbose_name='Facility ID')
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
role = tables.Column(verbose_name='Role')
role = tables.TemplateColumn(RACK_ROLE, verbose_name='Role')
u_height = tables.TemplateColumn("{{ record.u_height }}U", verbose_name='Height')
devices = tables.Column(accessor=Accessor('device_count'), verbose_name='Devices')
u_consumed = tables.TemplateColumn("{{ record.u_consumed|default:'0' }}U", verbose_name='Used')
@@ -258,7 +274,7 @@ class DeviceRoleTable(BaseTable):
pk = ToggleColumn()
name = tables.LinkColumn(verbose_name='Name')
device_count = tables.Column(verbose_name='Devices')
color = ColorColumn(verbose_name='Color')
color = tables.TemplateColumn(COLOR_LABEL, verbose_name='Color')
slug = tables.Column(verbose_name='Slug')
actions = tables.TemplateColumn(template_code=DEVICEROLE_ACTIONS, attrs={'td': {'class': 'text-right'}},
verbose_name='')
@@ -295,7 +311,7 @@ class DeviceTable(BaseTable):
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
site = tables.Column(accessor=Accessor('rack.site'), verbose_name='Site')
rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')], verbose_name='Rack')
device_role = tables.Column(verbose_name='Role')
device_role = tables.TemplateColumn(DEVICE_ROLE, verbose_name='Role')
device_type = tables.Column(verbose_name='Type')
primary_ip = tables.TemplateColumn(orderable=False, verbose_name='IP Address',
template_code="{{ record.primary_ip.address.ip }}")

View File

@@ -327,6 +327,7 @@ class DeviceTest(APITestCase):
'tenant',
'platform',
'serial',
'asset_tag',
'rack',
'position',
'face',
@@ -370,6 +371,7 @@ class DeviceTest(APITestCase):
def test_get_list_flat(self, endpoint='/api/dcim/devices/?format=json_flat'):
flat_fields = [
'asset_tag',
'comments',
'device_role_id',
'device_role_name',

View File

@@ -18,9 +18,10 @@ GRAPH_TYPE_CHOICES = (
)
EXPORTTEMPLATE_MODELS = [
'site', 'rack', 'device', 'consoleport', 'powerport', 'interfaceconnection',
'aggregate', 'prefix', 'ipaddress', 'vlan',
'provider', 'circuit'
'site', 'rack', 'device', 'consoleport', 'powerport', 'interfaceconnection', # DCIM
'aggregate', 'prefix', 'ipaddress', 'vlan', # IPAM
'provider', 'circuit', # Circuits
'tenant', # Tenants
]
ACTION_CREATE = 1

View File

@@ -5,6 +5,7 @@ from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.db.models.expressions import RawSQL
from dcim.models import Interface
from tenancy.models import Tenant
@@ -295,6 +296,20 @@ class Prefix(CreatedUpdatedModel):
return STATUS_CHOICE_CLASSES[self.status]
class IPAddressManager(models.Manager):
def get_queryset(self):
"""
By default, PostgreSQL will order INETs with shorter (larger) prefix lengths ahead of those with longer
(smaller) masks. This makes no sense when ordering IPs, which should be ordered solely by family and host
address. We can use HOST() to extract just the host portion of the address (ignoring its mask), but we must
then re-cast this value to INET() so that records will be ordered properly. We are essentially re-casting each
IP address as a /32 or /128.
"""
qs = super(IPAddressManager, self).get_queryset()
return qs.annotate(host=RawSQL('INET(HOST(ipam_ipaddress.address))', [])).order_by('family', 'host')
class IPAddress(CreatedUpdatedModel):
"""
An IPAddress represents an individual IPv4 or IPv6 address and its mask. The mask length should match what is
@@ -317,6 +332,8 @@ class IPAddress(CreatedUpdatedModel):
null=True, verbose_name='NAT IP (inside)')
description = models.CharField(max_length=100, blank=True)
objects = IPAddressManager()
class Meta:
ordering = ['family', 'address']
verbose_name = 'IP address'

View File

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

View File

@@ -8,9 +8,15 @@ $(document).ready(function() {
}
// Update livesearch text when real field changes
search_field.val(real_field.children('option:selected').text());
real_field.change(function() {
if (real_field.val()) {
search_field.val(real_field.children('option:selected').text());
}
real_field.change(function() {
if (real_field.val()) {
search_field.val(real_field.children('option:selected').text());
} else {
search_field.val('');
}
});
search_field.autocomplete({

View File

@@ -10,6 +10,10 @@
<span class="fa fa-plus" aria-hidden="true"></span>
Add a circuit
</a>
<a href="{% url 'circuits:circuit_import' %}" class="btn btn-info">
<span class="fa fa-download" aria-hidden="true"></span>
Import circuits
</a>
{% endif %}
{% include 'inc/export_button.html' with obj_type='circuits' %}
</div>

View File

@@ -9,6 +9,10 @@
<span class="fa fa-plus" aria-hidden="true"></span>
Add a provider
</a>
<a href="{% url 'circuits:provider_import' %}" class="btn btn-info">
<span class="fa fa-download" aria-hidden="true"></span>
Import providers
</a>
{% endif %}
{% include 'inc/export_button.html' with obj_type='providers' %}
</div>

View File

@@ -60,7 +60,7 @@
</td>
</tr>
<tr>
<td>Serial</td>
<td>Serial Number</td>
<td>
{% if device.serial %}
<span>{{ device.serial }}</span>
@@ -69,6 +69,16 @@
{% endif %}
</td>
</tr>
<tr>
<td>Asset Tag</td>
<td>
{% if device.asset_tag %}
<span>{{ device.asset_tag }}</span>
{% else %}
<span class="text-muted">N/A</span>
{% endif %}
</td>
</tr>
<tr>
<td>Created</td>
<td>{{ device.created }}</td>
@@ -87,7 +97,7 @@
<tr>
<td>Role</td>
<td>
<a href="{% url 'dcim:device_list' %}?role={{ device.device_role.slug }}">{{ device.device_role }}</a>
<a href="{{ device.device_role.get_absolute_url }}">{{ device.device_role }}</a>
</td>
</tr>
<tr>

View File

@@ -16,6 +16,7 @@
{% render_field form.manufacturer %}
{% render_field form.device_type %}
{% render_field form.serial %}
{% render_field form.asset_tag %}
</div>
</div>
<div class="panel panel-default">

View File

@@ -57,10 +57,15 @@
<td>Juniper Junos</td>
</tr>
<tr>
<td>Serial</td>
<td>Serial number (optional)</td>
<td>Serial number</td>
<td>Physical serial number (optional)</td>
<td>CAB00577291</td>
</tr>
<tr>
<td>Asset tag</td>
<td>Unique alphanumeric tag (optional)</td>
<td>ABC123456</td>
</tr>
<tr>
<td>Site</td>
<td>Site name</td>
@@ -84,7 +89,7 @@
</tbody>
</table>
<h4>Example</h4>
<pre>rack101_sw1,ToR Switch,Pied Piper,Juniper,EX4300-48T,Juniper Junos,CAB00577291,Ashburn-VA,R101,21,Rear</pre>
<pre>rack101_sw1,ToR Switch,Pied Piper,Juniper,EX4300-48T,Juniper Junos,CAB00577291,ABC123456,Ashburn-VA,R101,21,Rear</pre>
</div>
</div>
{% endblock %}

View File

@@ -57,10 +57,15 @@
<td>Linux</td>
</tr>
<tr>
<td>Serial</td>
<td>Serial number (optional)</td>
<td>Serial number</td>
<td>Physical serial number (optional)</td>
<td>CAB00577291</td>
</tr>
<tr>
<td>Asset tag</td>
<td>Unique alphanumeric tag (optional)</td>
<td>ABC123456</td>
</tr>
<tr>
<td>Parent device</td>
<td>Parent device</td>
@@ -74,7 +79,7 @@
</tbody>
</table>
<h4>Example</h4>
<pre>Blade12,Blade Server,Pied Piper,Dell,BS2000T,Linux,CAB00577291,Server101,Slot4</pre>
<pre>Blade12,Blade Server,Pied Piper,Dell,BS2000T,Linux,CAB00577291,ABC123456,Server101,Slot4</pre>
</div>
</div>
{% endblock %}

View File

@@ -17,7 +17,23 @@
</tr>
<tr>
<td>Serial Number</td>
<td>{{ device.serial }}</td>
<td>
{% if device.serial %}
<span>{{ device.serial }}</span>
{% else %}
<span class="text-muted">N/A</span>
{% endif %}
</td>
</tr>
<tr>
<td>Asset Tag</td>
<td>
{% if device.asset_tag %}
<span>{{ device.asset_tag }}</span>
{% else %}
<span class="text-muted">N/A</span>
{% endif %}
</td>
</tr>
</table>
</div>

View File

@@ -56,6 +56,10 @@
<a href="{% url 'dcim:interfaceconnection_delete' pk=iface.connection.pk %}?device={{ device.pk }}" class="btn btn-danger btn-xs" title="Delete connection">
<i class="glyphicon glyphicon-remove" aria-hidden="true"></i>
</a>
{% elif iface.circuit and perms.circuits.change_circuit %}
<a href="{% url 'circuits:circuit_edit' pk=iface.circuit.pk %}" class="btn btn-danger btn-xs" title="Edit circuit">
<i class="glyphicon glyphicon-remove" aria-hidden="true"></i>
</a>
{% else %}
<a href="{% url 'dcim:interfaceconnection_add' pk=device.pk %}?interface={{ iface.pk }}" class="btn btn-success btn-xs" title="Connect">
<i class="glyphicon glyphicon-plus" aria-hidden="true"></i>

View File

@@ -96,6 +96,16 @@
{% endif %}
</td>
</tr>
<tr>
<td>Role</td>
<td>
{% if rack.role %}
<a href="{{ rack.role.get_absolute_url }}">{{ rack.role }}</a>
{% else %}
<span class="text-muted">None</span>
{% endif %}
</td>
</tr>
<tr>
<td>Type</td>
<td>

View File

@@ -10,6 +10,7 @@
{% render_field form.name %}
{% render_field form.facility_id %}
{% render_field form.tenant %}
{% render_field form.role %}
{% render_field form.type %}
{% render_field form.width %}
{% render_field form.u_height %}

View File

@@ -3,9 +3,9 @@
{% block content %}
<div class="row home-search" style="padding: 15px 0px 20px">
<div class="col-md-4">
<div class="col-md-3">
<form action="{% url 'dcim:device_list' %}" method="get">
<div class="input-group input-group-lg">
<div class="input-group">
<input type="text" name="q" placeholder="Search devices" class="form-control" />
<span class="input-group-btn">
<button type="submit" class="btn btn-primary">
@@ -17,9 +17,9 @@
</form>
<p></p>
</div>
<div class="col-md-4">
<div class="col-md-3">
<form action="{% url 'ipam:prefix_list' %}" method="get">
<div class="input-group input-group-lg">
<div class="input-group">
<input type="text" name="q" placeholder="Search prefixes" class="form-control" />
<span class="input-group-btn">
<button type="submit" class="btn btn-primary">
@@ -31,9 +31,23 @@
</form>
<p></p>
</div>
<div class="col-md-4">
<div class="col-md-3">
<form action="{% url 'ipam:ipaddress_list' %}" method="get">
<div class="input-group">
<input type="text" name="q" placeholder="Search IPs" class="form-control" />
<span class="input-group-btn">
<button type="submit" class="btn btn-primary">
<span class="fa fa-search" aria-hidden="true"></span>
IPs
</button>
</span>
</div>
</form>
<p></p>
</div>
<div class="col-md-3">
<form action="{% url 'circuits:circuit_list' %}" method="get">
<div class="input-group input-group-lg">
<div class="input-group">
<input type="text" name="q" placeholder="Search circuits" class="form-control" />
<span class="input-group-btn">
<button type="submit" class="btn btn-primary">

View File

@@ -11,6 +11,10 @@
<span class="fa fa-plus" aria-hidden="true"></span>
Add an aggregate
</a>
<a href="{% url 'ipam:aggregate_import' %}" class="btn btn-info">
<span class="fa fa-download" aria-hidden="true"></span>
Import aggregates
</a>
{% endif %}
{% include 'inc/export_button.html' with obj_type='aggregates' %}
</div>

View File

@@ -10,6 +10,10 @@
<span class="fa fa-plus" aria-hidden="true"></span>
Add a tenant
</a>
<a href="{% url 'tenancy:tenant_import' %}" class="btn btn-info">
<span class="fa fa-download" aria-hidden="true"></span>
Import tenants
</a>
{% endif %}
{% include 'inc/export_button.html' with obj_type='tenants' %}
</div>

View File

@@ -1,5 +1,5 @@
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.db.models import Count
from django.db.models import Count, Q
from django.shortcuts import get_object_or_404, render
from circuits.models import Circuit
@@ -59,8 +59,14 @@ def tenant(request, slug):
'rack_count': Rack.objects.filter(tenant=tenant).count(),
'device_count': Device.objects.filter(tenant=tenant).count(),
'vrf_count': VRF.objects.filter(tenant=tenant).count(),
'prefix_count': Prefix.objects.filter(tenant=tenant).count(),
'ipaddress_count': IPAddress.objects.filter(tenant=tenant).count(),
'prefix_count': Prefix.objects.filter(
Q(tenant=tenant) |
Q(tenant__isnull=True, vrf__tenant=tenant)
).count(),
'ipaddress_count': IPAddress.objects.filter(
Q(tenant=tenant) |
Q(tenant__isnull=True, vrf__tenant=tenant)
).count(),
'vlan_count': VLAN.objects.filter(tenant=tenant).count(),
'circuit_count': Circuit.objects.filter(tenant=tenant).count(),
}

View File

@@ -28,10 +28,3 @@ class ToggleColumn(tables.CheckBoxColumn):
@property
def header(self):
return mark_safe('<input type="checkbox" name="_all" title="Select all" />')
class ColorColumn(tables.Column):
def render(self, record):
html = '<label class="label {}">{}</label>'.format(record.color, record.get_color_display())
return mark_safe(html)