Compare commits

...

47 Commits

Author SHA1 Message Date
Jeremy Stretch
bf44e512ff Post-release version bump 2016-07-14 15:22:14 -04:00
Jeremy Stretch
026403ed38 Release v1.2.2 2016-07-14 15:21:22 -04:00
Jeremy Stretch
f6bd1f0c48 Make the HA warning re: SECRET_KEY a note 2016-07-14 14:03:57 -04:00
Jeremy Stretch
66489438b9 Merge pull request #298 from rekkoner/develop
Updated SECRET_KEY instructions for HA installs. Issue 295
2016-07-14 14:01:06 -04:00
Jeremy Stretch
e5a6a4f05e Fixes #174: Added search and site filter to provider list 2016-07-14 13:53:30 -04:00
brandon whitehead
9e4aa9c056 Updated SECRET_KEY instructions for HA installs. Issue 295 2016-07-14 12:33:21 -05:00
Jeremy Stretch
4ce40891f0 Prettified device type view 2016-07-14 12:39:55 -04:00
Jeremy Stretch
46b1ac23af Allow for setting mgmt_only=True in "Add management interfaces" link 2016-07-14 11:39:53 -04:00
Jeremy Stretch
a5f6e64849 Fixes #290: Added mgmt interfaces table to device type view 2016-07-14 11:30:15 -04:00
Jeremy Stretch
b9db1ac7f7 Merge pull request #283 from ercpe/html-overflow
Use overflow-y: scoll on html element
2016-07-13 16:03:53 -04:00
Jeremy Stretch
124c2acad7 Merge pull request #287 from bellwood/ui-add-glyphicons-to-panel-headers
Add 'filter' glyphicon to filter panel header
2016-07-13 16:02:15 -04:00
bellwood
2691590aa1 Add 'search' glyphicon to filter panel header 2016-07-13 15:36:26 -04:00
bellwood
51cc0d5083 Add 'search' glyphicon to filter panel header 2016-07-13 15:36:07 -04:00
bellwood
9c32943d73 Add 'search' glyphicon to filter panel header 2016-07-13 15:35:41 -04:00
bellwood
4483ba55dd Add 'search' glyphicon to filter panel header 2016-07-13 15:34:23 -04:00
bellwood
f20e0edb35 Add 'search' glyphicon to filter panel header 2016-07-13 15:33:52 -04:00
bellwood
aed2180142 Add 'search' glyphicon to filter panel header 2016-07-13 15:32:39 -04:00
Jeremy Stretch
4913d25d18 Fixes #268: Added support for full 32-bit ASN space 2016-07-13 15:30:15 -04:00
bellwood
9e181c20c7 Add 'filter' glyphicon to filter panel header 2016-07-13 15:26:24 -04:00
Jeremy Stretch
404d934736 Removed redundant template context processor 2016-07-13 14:08:46 -04:00
Jeremy Stretch
024c7da15b Fixes #115: Fix deprecated django.core.context_processors reference 2016-07-13 14:05:21 -04:00
Jeremy Stretch
d3a5b82d93 Fixes #282: De-select "all" checkbox if one or more objects are deselected 2016-07-13 13:50:50 -04:00
Jeremy Stretch
1e3a03c463 Merge branch 'develop' of github.com:digitalocean/netbox into develop 2016-07-13 13:08:17 -04:00
Jeremy Stretch
bafbc052e2 Fixes #270: Add rack group filter for devices 2016-07-13 13:07:55 -04:00
Jeremy Stretch
9421ec040c Fixes #271: Add rack group filter for devices 2016-07-13 13:07:02 -04:00
Jeremy Stretch
07fc2e5502 Merge pull request #273 from bellwood/devices-filter-add-rackgroup
allow filtering by rack group
2016-07-13 12:55:11 -04:00
Jeremy Stretch
9098001bcb Post-release version bump 2016-07-13 12:11:10 -04:00
Jeremy Stretch
d9bf199e75 Version bump: v1.2.1 2016-07-13 12:01:34 -04:00
Jeremy Stretch
6f1ed9fc16 Clarified the process of voting on feature requests 2016-07-13 11:47:20 -04:00
Jeremy Stretch
96b496ffa8 Updated documentation to include banner settings 2016-07-13 11:24:34 -04:00
Jeremy Stretch
f1b6f0cfee Fixes #285: Added PREFER_IPV4 configuration setting 2016-07-13 11:16:09 -04:00
Jeremy Stretch
e19ce043d6 Fixes #275: Exclude self when checking for overlapping aggregates 2016-07-13 10:37:25 -04:00
Johann Schmitz
35a2671525 Use overflow-y: scoll on html element to avoid jumping around when the previous/next page adds a vertical scrollbar. 2016-07-13 15:39:59 +02:00
Jeremy Stretch
03542b400d Renamed IPAMConfig to DCIMConfig 2016-07-13 09:25:13 -04:00
Jeremy Stretch
73d24532c9 Merge pull request #281 from lukerussell/Link-to-docs-in-readme
Added a link to docs in readme.md
2016-07-13 09:04:00 -04:00
Jeremy Stretch
b60f964835 Fixes #272: Added a step to copy the gunicorn config 2016-07-12 16:57:00 -04:00
lukerussell
8e7e02a622 Added a link to docs in readme.md
Adding a direct link for easy access. I couldn't find a link anywhere except digging in through the docs/ directory.
2016-07-13 06:04:29 +10:00
Jeremy Stretch
2c23ca33a2 Fixes #274: Correct reference to old field 2016-07-12 15:48:56 -04:00
bellwood
69affb7a6e fixed "rack group" filter label for/dcim/racks/ 2016-07-12 15:16:32 -04:00
bellwood
6a6cf14a38 Update forms.py
added label
2016-07-12 15:12:36 -04:00
Jeremy Stretch
e1da3b8f10 Related to #243: Implemented natsort on all Device and DeviceType objects (except interfaces) 2016-07-12 14:53:59 -04:00
bellwood
da50cd0f03 allow filtering by rack group
adds the ability to filter devices by rack group
2016-07-12 14:42:47 -04:00
Jeremy Stretch
d80ffd2308 Merge pull request #248 from Zanthras/develop
possible fix for #243 generic sorting for device bays
2016-07-12 14:13:25 -04:00
Jeremy Stretch
18846cf40a Fixes #271: Corrected select_related() in secrets API 2016-07-12 12:27:26 -04:00
Jeremy Stretch
e81a2094df Post-release version bump 2016-07-12 11:40:40 -04:00
Joel
173a4cde8b Update the requirements file to include the natsort library requirement. 2016-07-08 23:27:00 -07:00
Joel
d9867423de Use the natsort library to provide a generic sorting option for better sorting of generic names for device bays. 2016-07-08 22:47:08 -07:00
36 changed files with 319 additions and 56 deletions

View File

@@ -27,9 +27,10 @@ IRC.
## Feature Requests
* First, check the [issues list](https://github.com/digitalocean/netbox/issues) to see if the feature you'd like to see
has already been requested (and possibly rejected). If it is, be sure to comment with a "+1" and any additional
justification you have for the feature.
* First, check the [issues list](https://github.com/digitalocean/netbox/issues) to see if the feature you're requesting
has already been requested (and possibly rejected). If it has, click "add a reaction" in the top right corner of the
issue and add a thumbs up (+1). This ensures that the issue has a better chance of making it onto the roadmap. Also feel
free to add a comment with any additional justification for the feature.
* While discussion of new features is welcome, it's important to limit the scope of NetBox's feature set to avoid
feature creep. For example, the following features would be firmly out of scope for NetBox:

View File

@@ -4,6 +4,8 @@ NetBox is an IP address management (IPAM) and data center infrastructure managem
NetBox runs as a web application atop the [Django](https://www.djangoproject.com/) Python framework with a [PostgreSQL](http://www.postgresql.org/) database. For a complete list of requirements, see `requirements.txt`. The code is available [on GitHub](https://github.com/digitalocean/netbox).
The complete documentation for Netbox can be found at [Read the Docs](http://netbox.readthedocs.io/en/latest/).
Questions? Comments? Please join us on IRC in **#netbox** on **irc.freenode.net**!
### Build Status

View File

@@ -13,11 +13,24 @@ ADMINS = [
---
## BANNER_TOP
## BANNER_BOTTOM
Setting these variables will display content in a banner at the top and/or bottom of the page, respectively. To replicate the content of the top banner in the bottom banner, set:
```
BANNER_TOP = 'Your banner text'
BANNER_BOTTOM = BANNER_TOP
```
---
## DEBUG
Default: False
This setting enables debugging. This should be done only during development or troubleshooting. Never enable debugging on a production system, as it can expose sensitive data to unauthenticated users.
This setting enables debugging. This should be done only during development or troubleshooting. Never enable debugging on a production system, as it can expose sensitive data to unauthenticated users.
---
@@ -66,6 +79,14 @@ Determine how many objects to display per page within each list of objects.
---
## PREFER_IPV4
Default: False
When determining the primary IP address for a device, IPv6 is preferred over IPv4 by default. Set this to True to prefer IPv4 instead.
---
## TIME_ZONE
Default: UTC

View File

@@ -112,6 +112,9 @@ Generate a random secret key of at least 50 alphanumeric characters. This key mu
You may use the script located at `netbox/generate_secret_key.py` to generate a suitable key.
!!! note
In the case of a highly available installation with multiple web servers, `SECRET_KEY` must be identical among all servers in order to maintain a persistent user session state.
# Run Database Migrations
Before NetBox can run, we need to install the database schema. This is done by running `./manage.py migrate` from the `netbox` directory (`/opt/netbox/netbox/` in our example):

View File

@@ -4,42 +4,40 @@ As with the initial installation, you can upgrade NetBox by either downloading t
## Option A: Download a Release
Download the [latest stable release](https://github.com/digitalocean/netbox/releases) from GitHub as a tarball or ZIP archive. Extract it to your desired path. In this example, we'll use `/opt/netbox`. For this guide we are using 1.0.4 as the old version and 1.0.7 as the new version.
Download the [latest stable release](https://github.com/digitalocean/netbox/releases) from GitHub as a tarball or ZIP archive. Extract it to your desired path. In this example, we'll use `/opt/netbox`.
Download and extract the latest version:
Download & extract latest version:
```
# wget https://github.com/digitalocean/netbox/archive/vX.Y.Z.tar.gz
# tar -xzf vX.Y.Z.tar.gz -C /opt
# cd /opt/
# ln -sf netbox-1.0.7/ netbox
# ln -sf netbox-X.Y.Z/ netbox
```
Copy the 'configuration.py' you created when first installing to the new version:
```
# cp /opt/netbox-1.0.4/configuration.py /opt/netbox/configuration.py
# cp /opt/netbox-X.Y.Z/configuration.py /opt/netbox/configuration.py
```
If you followed the original installation guide to set up gunicorn, be sure to copy its configuration as well:
```
# cp /opt/netbox-X.Y.Z/gunicorn_config.py /opt/netbox/gunicorn_config.py
```
## Option B: Clone the Git Repository (latest master release)
For this guide, we'll use `/opt/netbox`.
This guide assumes that NetBox is installed at `/opt/netbox`. Pull down the most recent iteration of the master branch:
Check that your git branch is up to date & is set to master:
```
# cd /opt/netbox
# git status
```
If not on branch master, set it and verify status:
```
# git checkout master
# git pull origin master
# git status
```
Pull down the set branch from git status above:
```
# git pull
```
# Run the Upgrade Script
Once the new code is in place, run the upgrade script (which may need to be run as root depending on how your environment is configured).

View File

@@ -1,9 +1,40 @@
import django_filters
from django.db.models import Q
from dcim.models import Site
from .models import Provider, Circuit, CircuitType
class ProviderFilter(django_filters.FilterSet):
q = django_filters.MethodFilter(
action='search',
label='Search',
)
site_id = django_filters.ModelMultipleChoiceFilter(
name='circuits__site',
queryset=Site.objects.all(),
label='Site',
)
site = django_filters.ModelMultipleChoiceFilter(
name='circuits__site',
queryset=Site.objects.all(),
to_field_name='slug',
label='Site (slug)',
)
class Meta:
model = Provider
fields = ['q', 'name', 'account', 'asn']
def search(self, queryset, value):
value = value.strip()
return queryset.filter(
Q(name__icontains=value) |
Q(account__icontains=value)
)
class CircuitFilter(django_filters.FilterSet):
q = django_filters.MethodFilter(
action='search',

View File

@@ -59,6 +59,16 @@ class ProviderBulkDeleteForm(ConfirmationForm):
pk = forms.ModelMultipleChoiceField(queryset=Provider.objects.all(), widget=forms.MultipleHiddenInput)
def provider_site_choices():
site_choices = Site.objects.all()
return [(s.slug, s.name) for s in site_choices]
class ProviderFilterForm(forms.Form, BootstrapMixin):
site = forms.MultipleChoiceField(required=False, choices=provider_site_choices,
widget=forms.SelectMultiple(attrs={'size': 8}))
#
# Circuit types
#

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-07-13 19:24
from __future__ import unicode_literals
import dcim.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('circuits', '0002_auto_20160622_1821'),
]
operations = [
migrations.AlterField(
model_name='provider',
name='asn',
field=dcim.fields.ASNField(blank=True, null=True, verbose_name=b'ASN'),
),
]

View File

@@ -1,6 +1,7 @@
from django.core.urlresolvers import reverse
from django.db import models
from dcim.fields import ASNField
from dcim.models import Site, Interface
from utilities.models import CreatedUpdatedModel
@@ -12,7 +13,7 @@ class Provider(CreatedUpdatedModel):
"""
name = models.CharField(max_length=50, unique=True)
slug = models.SlugField(unique=True)
asn = models.PositiveIntegerField(blank=True, null=True, verbose_name='ASN')
asn = ASNField(blank=True, null=True, verbose_name='ASN')
account = models.CharField(max_length=30, blank=True, verbose_name='Account number')
portal_url = models.URLField(blank=True, verbose_name='Portal')
noc_contact = models.TextField(blank=True, verbose_name='NOC contact')

View File

@@ -16,6 +16,8 @@ from .models import Circuit, CircuitType, Provider
class ProviderListView(ObjectListView):
queryset = Provider.objects.annotate(count_circuits=Count('circuits'))
filter = filters.ProviderFilter
filter_form = forms.ProviderFilterForm
table = tables.ProviderTable
edit_permissions = ['circuits.change_provider', 'circuits.delete_provider']
template_name = 'circuits/provider_list.html'

View File

@@ -1 +1 @@
default_app_config = 'dcim.apps.IPAMConfig'
default_app_config = 'dcim.apps.DCIMConfig'

View File

@@ -180,4 +180,4 @@ class DeviceAdmin(admin.ModelAdmin):
def get_queryset(self, request):
qs = super(DeviceAdmin, self).get_queryset(request)
return qs.select_related('device_type__manufacturer', 'device_role', 'primary_ip', 'rack')
return qs.select_related('device_type__manufacturer', 'device_role', 'primary_ip4', 'primary_ip6', 'rack')

View File

@@ -1,6 +1,6 @@
from django.apps import AppConfig
class IPAMConfig(AppConfig):
class DCIMConfig(AppConfig):
name = "dcim"
verbose_name = "DCIM"

View File

@@ -1,11 +1,20 @@
from netaddr import EUI, mac_unix_expanded
from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator, MaxValueValidator
from django.db import models
from .formfields import MACAddressFormField
class ASNField(models.BigIntegerField):
description = "32-bit ASN field"
default_validators = [
MinValueValidator(1),
MaxValueValidator(4294967295),
]
class mac_unix_expanded_uppercase(mac_unix_expanded):
word_fmt = '%.2X'

View File

@@ -122,6 +122,11 @@ class DeviceFilter(django_filters.FilterSet):
to_field_name='slug',
label='Site name (slug)',
)
rack_group_id = django_filters.ModelMultipleChoiceFilter(
name='rack__group',
queryset=RackGroup.objects.all(),
label='Rack group (ID)',
)
rack_id = django_filters.ModelMultipleChoiceFilter(
name='rack',
queryset=Rack.objects.all(),

View File

@@ -186,7 +186,7 @@ def rack_group_choices():
class RackFilterForm(forms.Form, BootstrapMixin):
site = forms.MultipleChoiceField(required=False, choices=rack_site_choices,
widget=forms.SelectMultiple(attrs={'size': 8}))
group_id = forms.MultipleChoiceField(required=False, choices=rack_group_choices,
group_id = forms.MultipleChoiceField(required=False, choices=rack_group_choices, label='Rack Group',
widget=forms.SelectMultiple(attrs={'size': 8}))
@@ -502,6 +502,11 @@ def device_site_choices():
return [(s.slug, '{} ({})'.format(s.name, s.device_count)) for s in site_choices]
def device_rack_group_choices():
group_choices = RackGroup.objects.select_related('site').annotate(device_count=Count('racks__devices'))
return [(g.pk, '{} ({})'.format(g, g.device_count)) for g in group_choices]
def device_role_choices():
role_choices = DeviceRole.objects.annotate(device_count=Count('devices'))
return [(r.slug, '{} ({})'.format(r.name, r.device_count)) for r in role_choices]
@@ -520,6 +525,8 @@ def device_platform_choices():
class DeviceFilterForm(forms.Form, BootstrapMixin):
site = forms.MultipleChoiceField(required=False, choices=device_site_choices,
widget=forms.SelectMultiple(attrs={'size': 8}))
rack_group_id = forms.MultipleChoiceField(required=False, choices=device_rack_group_choices, label='Rack Group',
widget=forms.SelectMultiple(attrs={'size': 8}))
role = forms.MultipleChoiceField(required=False, choices=device_role_choices,
widget=forms.SelectMultiple(attrs={'size': 8}))
device_type_id = forms.MultipleChoiceField(required=False, choices=device_type_choices, label='Type',

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-07-13 19:24
from __future__ import unicode_literals
import dcim.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('dcim', '0008_device_remove_primary_ip'),
]
operations = [
migrations.AlterField(
model_name='site',
name='asn',
field=dcim.fields.ASNField(blank=True, null=True, verbose_name=b'ASN'),
),
]

View File

@@ -1,5 +1,6 @@
from collections import OrderedDict
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django.core.validators import MinValueValidator
@@ -10,7 +11,7 @@ from extras.rpc import RPC_CLIENTS
from utilities.fields import NullableCharField
from utilities.models import CreatedUpdatedModel
from .fields import MACAddressField
from .fields import ASNField, MACAddressField
RACK_FACE_FRONT = 0
RACK_FACE_REAR = 1
@@ -144,7 +145,7 @@ class Site(CreatedUpdatedModel):
name = models.CharField(max_length=50, unique=True)
slug = models.SlugField(unique=True)
facility = models.CharField(max_length=50, blank=True)
asn = models.PositiveIntegerField(blank=True, null=True, verbose_name='ASN')
asn = ASNField(blank=True, null=True, verbose_name='ASN')
physical_address = models.CharField(max_length=200, blank=True)
shipping_address = models.CharField(max_length=200, blank=True)
comments = models.TextField(blank=True)
@@ -713,7 +714,9 @@ class Device(CreatedUpdatedModel):
@property
def primary_ip(self):
if self.primary_ip6:
if settings.PREFER_IPV4 and self.primary_ip4:
return self.primary_ip4
elif self.primary_ip6:
return self.primary_ip6
elif self.primary_ip4:
return self.primary_ip4

View File

@@ -1,4 +1,6 @@
import re
from natsort import natsorted
from operator import attrgetter
from django.contrib import messages
from django.contrib.auth.decorators import permission_required
@@ -259,18 +261,31 @@ def devicetype(request, pk):
devicetype = get_object_or_404(DeviceType, pk=pk)
# Component tables
consoleport_table = tables.ConsolePortTemplateTable(ConsolePortTemplate.objects.filter(device_type=devicetype))
consoleserverport_table = tables.ConsoleServerPortTemplateTable(ConsoleServerPortTemplate.objects
.filter(device_type=devicetype))
powerport_table = tables.PowerPortTemplateTable(PowerPortTemplate.objects.filter(device_type=devicetype))
poweroutlet_table = tables.PowerOutletTemplateTable(PowerOutletTemplate.objects.filter(device_type=devicetype))
interface_table = tables.InterfaceTemplateTable(InterfaceTemplate.objects.filter(device_type=devicetype))
devicebay_table = tables.DeviceBayTemplateTable(DeviceBayTemplate.objects.filter(device_type=devicetype))
consoleport_table = tables.ConsolePortTemplateTable(
natsorted(ConsolePortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name'))
)
consoleserverport_table = tables.ConsoleServerPortTemplateTable(
natsorted(ConsoleServerPortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name'))
)
powerport_table = tables.PowerPortTemplateTable(
natsorted(PowerPortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name'))
)
poweroutlet_table = tables.PowerOutletTemplateTable(
natsorted(PowerOutletTemplate.objects.filter(device_type=devicetype), key=attrgetter('name'))
)
mgmt_interface_table = tables.InterfaceTemplateTable(InterfaceTemplate.objects.filter(device_type=devicetype,
mgmt_only=True))
interface_table = tables.InterfaceTemplateTable(InterfaceTemplate.objects.filter(device_type=devicetype,
mgmt_only=False))
devicebay_table = tables.DeviceBayTemplateTable(
natsorted(DeviceBayTemplate.objects.filter(device_type=devicetype), key=attrgetter('name'))
)
if request.user.has_perm('dcim.change_devicetype'):
consoleport_table.base_columns['pk'].visible = True
consoleserverport_table.base_columns['pk'].visible = True
powerport_table.base_columns['pk'].visible = True
poweroutlet_table.base_columns['pk'].visible = True
mgmt_interface_table.base_columns['pk'].visible = True
interface_table.base_columns['pk'].visible = True
devicebay_table.base_columns['pk'].visible = True
@@ -280,6 +295,7 @@ def devicetype(request, pk):
'consoleserverport_table': consoleserverport_table,
'powerport_table': powerport_table,
'poweroutlet_table': poweroutlet_table,
'mgmt_interface_table': mgmt_interface_table,
'interface_table': interface_table,
'devicebay_table': devicebay_table,
})
@@ -337,7 +353,7 @@ class ComponentTemplateCreateView(View):
return render(request, 'dcim/component_template_add.html', {
'devicetype': devicetype,
'component_type': self.model._meta.verbose_name,
'form': self.form(),
'form': self.form(initial=request.GET),
'cancel_url': reverse('dcim:devicetype', kwargs={'pk': devicetype.pk}),
})
@@ -513,15 +529,26 @@ class DeviceListView(ObjectListView):
def device(request, pk):
device = get_object_or_404(Device, pk=pk)
console_ports = ConsolePort.objects.filter(device=device).select_related('cs_port__device')
cs_ports = ConsoleServerPort.objects.filter(device=device).select_related('connected_console')
power_ports = PowerPort.objects.filter(device=device).select_related('power_outlet__device')
power_outlets = PowerOutlet.objects.filter(device=device).select_related('connected_port')
console_ports = natsorted(
ConsolePort.objects.filter(device=device).select_related('cs_port__device'), key=attrgetter('name')
)
cs_ports = natsorted(
ConsoleServerPort.objects.filter(device=device).select_related('connected_console'), key=attrgetter('name')
)
power_ports = natsorted(
PowerPort.objects.filter(device=device).select_related('power_outlet__device'), key=attrgetter('name')
)
power_outlets = natsorted(
PowerOutlet.objects.filter(device=device).select_related('connected_port'), key=attrgetter('name')
)
interfaces = Interface.objects.filter(device=device, mgmt_only=False)\
.select_related('connected_as_a', 'connected_as_b', 'circuit')
mgmt_interfaces = Interface.objects.filter(device=device, mgmt_only=True)\
.select_related('connected_as_a', 'connected_as_b', 'circuit')
device_bays = DeviceBay.objects.filter(device=device).select_related('installed_device__device_type__manufacturer')
device_bays = natsorted(
DeviceBay.objects.filter(device=device).select_related('installed_device__device_type__manufacturer'),
key=attrgetter('name')
)
# Gather any secrets which belong to this device
secrets = device.secrets.all()

View File

@@ -123,6 +123,8 @@ class Aggregate(CreatedUpdatedModel):
# Ensure that the aggregate being added does not cover an existing aggregate
covered_aggregates = Aggregate.objects.filter(prefix__net_contained=str(self.prefix))
if self.pk:
covered_aggregates = covered_aggregates.exclude(pk=self.pk)
if covered_aggregates:
raise ValidationError("{} is overlaps with an existing aggregate ({})"
.format(self.prefix, covered_aggregates[0]))

View File

@@ -78,3 +78,7 @@ SHORT_DATETIME_FORMAT = 'Y-m-d H:i'
# banners, define BANNER_TOP and set BANNER_BOTTOM = BANNER_TOP.
BANNER_TOP = ''
BANNER_BOTTOM = ''
# When determining the primary IP address for a device, IPv6 is preferred over IPv4 by default. Set this to True to
# prefer IPv4 instead.
PREFER_IPV4 = False

View File

@@ -12,7 +12,7 @@ except ImportError:
"the documentation.")
VERSION = '1.2.0'
VERSION = '1.2.3-dev'
# Import local configuration
for setting in ['ALLOWED_HOSTS', 'DATABASE', 'SECRET_KEY']:
@@ -40,6 +40,7 @@ DATETIME_FORMAT = getattr(configuration, 'DATETIME_FORMAT', 'N j, Y g:i a')
SHORT_DATETIME_FORMAT = getattr(configuration, 'SHORT_DATETIME_FORMAT', 'Y-m-d H:i')
BANNER_TOP = getattr(configuration, 'BANNER_TOP', False)
BANNER_BOTTOM = getattr(configuration, 'BANNER_BOTTOM', False)
PREFER_IPV4 = getattr(configuration, 'PREFER_IPV4', False)
CSRF_TRUSTED_ORIGINS = ALLOWED_HOSTS
# Attempt to import LDAP configuration if it has been defined
@@ -137,7 +138,6 @@ TEMPLATES = [
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'utilities.context_processors.settings',
'django.core.context_processors.request',
],
},
},

View File

@@ -2,6 +2,9 @@
* {
margin: 0;
}
html {
overflow-y: scroll;
}
html, body {
height: 100%;
}

View File

@@ -1,9 +1,15 @@
$(document).ready(function() {
// "Select all" checkbox in a table header
$('th input:checkbox').click(function (event) {
$('th input:checkbox[name=_all]').click(function (event) {
$(this).parents('table').find('td input:checkbox').prop('checked', $(this).prop('checked'));
});
// Uncheck the "select all" checkbox if an item is unchecked
$('input:checkbox[name=pk]').click(function (event) {
if (!$(this).attr('checked')) {
$(this).parents('table').find('input:checkbox[name=_all]').prop('checked', false);
}
});
// Slugify
function slugify(s, num_chars) {

View File

@@ -42,7 +42,7 @@ class SecretListView(generics.GenericAPIView):
"""
List secrets (filterable). If a private key is POSTed, attempt to decrypt each Secret.
"""
queryset = Secret.objects.select_related('device__primary_ip', 'role')\
queryset = Secret.objects.select_related('device__primary_ip4', 'device__primary_ip6', 'role')\
.prefetch_related('role__users', 'role__groups')
serializer_class = serializers.SecretSerializer
filter_class = SecretFilter
@@ -87,7 +87,7 @@ class SecretDetailView(generics.GenericAPIView):
"""
Retrieve a single Secret. If a private key is POSTed, attempt to decrypt the Secret.
"""
queryset = Secret.objects.select_related('device__primary_ip', 'role')\
queryset = Secret.objects.select_related('device__primary_ip4', 'device__primary_ip6', 'role')\
.prefetch_related('role__users', 'role__groups')
serializer_class = serializers.SecretSerializer
renderer_classes = [FormlessBrowsableAPIRenderer, JSONRenderer, FreeRADIUSClientsRenderer]

View File

@@ -14,8 +14,28 @@
</div>
<h1>Providers</h1>
<div class="row">
<div class="col-md-12">
<div class="col-md-9">
{% include 'utilities/obj_table.html' with bulk_edit_url='circuits:provider_bulk_edit' bulk_delete_url='circuits:provider_bulk_delete' %}
</div>
<div class="col-md-3">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Search</strong>
</div>
<div class="panel-body">
<form action="{% url 'circuits:provider_list' %}" method="get">
<div class="input-group">
<input type="text" name="q" class="form-control" placeholder="Name" {% if request.GET.q %}value="{{ request.GET.q }}" {% endif %}/>
<span class="input-group-btn">
<button type="submit" class="btn btn-primary">
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
</button>
</span>
</div>
</form>
</div>
</div>
{% include 'inc/filter_panel.html' %}
</div>
</div>
{% endblock %}

View File

@@ -25,6 +25,7 @@
<div class="col-md-3">
<div class="panel panel-default">
<div class="panel-heading">
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
<strong>Search</strong>
</div>
<div class="panel-body">

View File

@@ -42,7 +42,7 @@
<table class="table table-hover panel-body">
<tr>
<td>Manufacturer</td>
<td>{{ devicetype.manufacturer }}</td>
<td><a href="{% url 'dcim:devicetype_list' %}?manufacturer={{ devicetype.manufacturer.slug }}">{{ devicetype.manufacturer }}</a></td>
</tr>
<tr>
<td>Model Name</td>
@@ -54,7 +54,13 @@
</tr>
<tr>
<td>Full Depth</td>
<td>{{ devicetype.is_full_depth|yesno|capfirst }}</td>
<td>
{% if devicetype.is_full_depth %}
<i class="glyphicon glyphicon-ok text-success" title="Yes"></i>
{% else %}
<i class="glyphicon glyphicon-remove text-danger" title="No"></i>
{% endif %}
</td>
</tr>
</table>
</div>
@@ -64,21 +70,70 @@
</div>
<table class="table table-hover panel-body">
<tr>
<td>Is a Console Server</td>
<td>{{ devicetype.is_console_server|yesno|capfirst }}</td>
<td class="text-right">
{% if devicetype.is_console_server %}
<i class="glyphicon glyphicon-ok text-success" title="Yes"></i>
{% else %}
<i class="glyphicon glyphicon-remove text-danger" title="No"></i>
{% endif %}
</td>
<td>
<strong>Console Server</strong><br />
<small class="text-muted">This device {% if devicetype.is_console_server %}has{% else %}does not have{% endif %} console server ports</small>
</td>
</tr>
<tr>
<td>Is a PDU</td>
<td>{{ devicetype.is_pdu|yesno|capfirst }}</td>
<td class="text-right">
{% if devicetype.is_pdu %}
<i class="glyphicon glyphicon-ok text-success" title="Yes"></i>
{% else %}
<i class="glyphicon glyphicon-remove text-danger" title="No"></i>
{% endif %}
</td>
<td>
<strong>PDU</strong><br />
<small class="text-muted">This device {% if devicetype.is_pdu %}has{% else %}does not have{% endif %} power outlets</small>
</td>
</tr>
<tr>
<td>Is a Network Device</td>
<td>{{ devicetype.is_network_device|yesno|capfirst }}</td>
<td class="text-right">
{% if devicetype.is_network_device %}
<i class="glyphicon glyphicon-ok text-success" title="Yes"></i>
{% else %}
<i class="glyphicon glyphicon-remove text-danger" title="No"></i>
{% endif %}
</td>
<td>
<strong>Network Device</strong><br />
<small class="text-muted">This device {% if devicetype.is_network_device %}has{% else %}does not have{% endif %} non-management network interfaces</small>
</td>
</tr>
<tr>
<td class="text-right">
{% if devicetype.subdevice_role == True %}
<label class="label label-primary">Parent</label>
{% elif devicetype.subdevice_role == False %}
<label class="label label-info">Child</label>
{% else %}
<label class="label label-default">None</label>
{% endif %}
</td>
<td>
<strong>Parent/Child</strong><br />
{% if devicetype.subdevice_role == True %}
<small class="text-muted">This device has device bays for mounting child devices</small>
{% elif devicetype.subdevice_role == False %}
<small class="text-muted">This device can only be mounted in a parent device</small>
{% else %}
<small class="text-muted">This device does not have device bays</small>
{% endif %}
</td>
</tr>
</table>
</div>
{% include 'dcim/inc/devicetype_component_table.html' with table=consoleport_table title='Console Ports' add_url='dcim:devicetype_add_consoleport' delete_url='dcim:devicetype_delete_consoleport' %}
{% include 'dcim/inc/devicetype_component_table.html' with table=powerport_table title='Power Ports' add_url='dcim:devicetype_add_powerport' delete_url='dcim:devicetype_delete_powerport' %}
{% include 'dcim/inc/devicetype_component_table.html' with table=mgmt_interface_table title='Management Interfaces' add_url='dcim:devicetype_add_interface' add_url_extra='?mgmt_only=1' delete_url='dcim:devicetype_delete_interface' %}
</div>
<div class="col-md-6">
{% if devicetype.is_parent_device %}

View File

@@ -4,7 +4,10 @@
{% csrf_token %}
<div class="panel panel-default">
<div class="panel-heading">
<a href="{% url add_url pk=devicetype.pk %}" class="btn btn-primary btn-xs pull-right"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add {{ title }}</a>
<a href="{% url add_url pk=devicetype.pk %}{{ add_url_extra }}" class="btn btn-primary btn-xs pull-right">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
Add {{ title }}
</a>
<strong>{{ title }}</strong>
</div>
{% render_table table 'table.html' %}

View File

@@ -25,6 +25,7 @@
<div class="col-md-3">
<div class="panel panel-default">
<div class="panel-heading">
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
<strong>Search</strong>
</div>
<div class="panel-body">

View File

@@ -21,6 +21,7 @@
<div class="col-md-3">
<div class="panel panel-default">
<div class="panel-heading">
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
<strong>Search</strong>
</div>
<div class="panel-body">

View File

@@ -2,6 +2,7 @@
<div class="panel panel-default">
<div class="panel-heading">
<span class="glyphicon glyphicon-filter" aria-hidden="true"></span>
<strong>Filter</strong>
</div>
<div class="panel-body">

View File

@@ -26,6 +26,7 @@
<div class="col-md-3">
<div class="panel panel-default">
<div class="panel-heading">
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
<strong>Search</strong>
</div>
<div class="panel-body">

View File

@@ -26,6 +26,7 @@
<div class="col-md-3">
<div class="panel panel-default">
<div class="panel-heading">
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
<strong>Search</strong>
</div>
<div class="panel-body">

View File

@@ -26,6 +26,7 @@
<div class="col-md-3">
<div class="panel panel-default">
<div class="panel-heading">
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
<strong>Search by ID</strong>
</div>
<div class="panel-body">

View File

@@ -15,3 +15,4 @@ py-gfm==0.1.3
pycrypto==2.6.1
sqlparse==0.1.19
xmltodict==0.10.2
natsort>=5.0.0