mirror of
https://github.com/netbox-community/netbox.git
synced 2026-01-10 22:02:17 -06:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec0cb7a8bc | ||
|
|
841471104b | ||
|
|
ac71416eb9 | ||
|
|
779d685335 | ||
|
|
4d1e798c56 | ||
|
|
a598035236 | ||
|
|
50395aa821 | ||
|
|
6d9c8fd85b | ||
|
|
c3599bacf2 | ||
|
|
c10481b99d | ||
|
|
1cebc1248b | ||
|
|
c97f7041a7 | ||
|
|
89bfb4f722 | ||
|
|
da3935ff36 | ||
|
|
06810bff91 | ||
|
|
a9af75bbd1 | ||
|
|
be6ef15ffa | ||
|
|
e98f0c39d1 | ||
|
|
5666079d92 | ||
|
|
85f5ba9a25 | ||
|
|
df141a48d9 | ||
|
|
fed6fc131b | ||
|
|
cf49891853 | ||
|
|
de2a894269 | ||
|
|
34d10f8db7 | ||
|
|
68f76465cf | ||
|
|
45d6955260 | ||
|
|
30df060357 | ||
|
|
252be84bf0 | ||
|
|
40ab272995 | ||
|
|
0ec3b5db8b | ||
|
|
5dc9723585 |
@@ -123,7 +123,7 @@ $ curl -X PATCH -H "Authorization: Token d2f763479f703d80de0ec15254237bc651f9cdc
|
|||||||
Send an authenticated `DELETE` request to the site detail endpoint.
|
Send an authenticated `DELETE` request to the site detail endpoint.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ curl -v X DELETE -H "Authorization: Token d2f763479f703d80de0ec15254237bc651f9cdc0" -H "Content-Type: application/json" -H "Accept: application/json; indent=4" http://localhost:8000/api/dcim/sites/16/
|
$ curl -v -X DELETE -H "Authorization: Token d2f763479f703d80de0ec15254237bc651f9cdc0" -H "Content-Type: application/json" -H "Accept: application/json; indent=4" http://localhost:8000/api/dcim/sites/16/
|
||||||
* Connected to localhost (127.0.0.1) port 8000 (#0)
|
* Connected to localhost (127.0.0.1) port 8000 (#0)
|
||||||
> DELETE /api/dcim/sites/16/ HTTP/1.1
|
> DELETE /api/dcim/sites/16/ HTTP/1.1
|
||||||
> User-Agent: curl/7.35.0
|
> User-Agent: curl/7.35.0
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ sudo pip install django-auth-ldap
|
|||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
|
|
||||||
Create a file in the same directory as `configuration.py` (typically `netbox/netbox/`) named `ldap_config.py`. Define all of the parameters required below in `ldap_config.py`.
|
Create a file in the same directory as `configuration.py` (typically `netbox/netbox/`) named `ldap_config.py`. Define all of the parameters required below in `ldap_config.py`. Complete documentation of all `django-auth-ldap` configuration options is included in the project's [official documentation](http://django-auth-ldap.readthedocs.io/).
|
||||||
|
|
||||||
## General Server Configuration
|
## General Server Configuration
|
||||||
|
|
||||||
@@ -52,6 +52,8 @@ AUTH_LDAP_BIND_PASSWORD = "demo"
|
|||||||
LDAP_IGNORE_CERT_ERRORS = True
|
LDAP_IGNORE_CERT_ERRORS = True
|
||||||
```
|
```
|
||||||
|
|
||||||
|
STARTTLS can be configured by setting `AUTH_LDAP_START_TLS = True` and using the `ldap://` URI scheme.
|
||||||
|
|
||||||
## User Authentication
|
## User Authentication
|
||||||
|
|
||||||
!!! info
|
!!! info
|
||||||
@@ -78,7 +80,7 @@ AUTH_LDAP_USER_ATTR_MAP = {
|
|||||||
```
|
```
|
||||||
|
|
||||||
# User Groups for Permissions
|
# User Groups for Permissions
|
||||||
!!! Info
|
!!! info
|
||||||
When using Microsoft Active Directory, Support for nested Groups can be activated by using `GroupOfNamesType()` instead of `NestedGroupOfNamesType()` for `AUTH_LDAP_GROUP_TYPE`.
|
When using Microsoft Active Directory, Support for nested Groups can be activated by using `GroupOfNamesType()` instead of `NestedGroupOfNamesType()` for `AUTH_LDAP_GROUP_TYPE`.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Rack
|
model = Rack
|
||||||
fields = ['serial', 'type', 'width', 'u_height', 'desc_units']
|
fields = ['name', 'serial', 'type', 'width', 'u_height', 'desc_units']
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@@ -330,7 +330,7 @@ class DeviceRoleFilter(django_filters.FilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DeviceRole
|
model = DeviceRole
|
||||||
fields = ['name', 'slug', 'color']
|
fields = ['name', 'slug', 'color', 'vm_role']
|
||||||
|
|
||||||
|
|
||||||
class PlatformFilter(django_filters.FilterSet):
|
class PlatformFilter(django_filters.FilterSet):
|
||||||
@@ -455,7 +455,7 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Device
|
model = Device
|
||||||
fields = ['serial']
|
fields = ['serial', 'position']
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
|
|||||||
@@ -143,6 +143,11 @@ class Site(CreatedUpdatedModel, CustomFieldModel):
|
|||||||
def count_circuits(self):
|
def count_circuits(self):
|
||||||
return Circuit.objects.filter(terminations__site=self).count()
|
return Circuit.objects.filter(terminations__site=self).count()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def count_vms(self):
|
||||||
|
from virtualization.models import VirtualMachine
|
||||||
|
return VirtualMachine.objects.filter(cluster__site=self).count()
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Racks
|
# Racks
|
||||||
@@ -1089,16 +1094,11 @@ class ConsolePort(models.Model):
|
|||||||
class ConsoleServerPortManager(models.Manager):
|
class ConsoleServerPortManager(models.Manager):
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""
|
# Pad any trailing digits to effect natural sorting
|
||||||
Include the trailing numeric portion of each port name to allow for proper ordering.
|
|
||||||
For example:
|
|
||||||
Port 1, Port 2, Port 3 ... Port 9, Port 10, Port 11 ...
|
|
||||||
Instead of:
|
|
||||||
Port 1, Port 10, Port 11 ... Port 19, Port 2, Port 20 ...
|
|
||||||
"""
|
|
||||||
return super(ConsoleServerPortManager, self).get_queryset().extra(select={
|
return super(ConsoleServerPortManager, self).get_queryset().extra(select={
|
||||||
'name_as_integer': "CAST(substring(dcim_consoleserverport.name FROM '[0-9]+$') AS INTEGER)",
|
'name_padded': "CONCAT(REGEXP_REPLACE(dcim_consoleserverport.name, '\d+$', ''), "
|
||||||
}).order_by('device', 'name_as_integer')
|
"LPAD(SUBSTRING(dcim_consoleserverport.name FROM '\d+$'), 8, '0'))",
|
||||||
|
}).order_by('device', 'name_padded')
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
@@ -1171,9 +1171,10 @@ class PowerPort(models.Model):
|
|||||||
class PowerOutletManager(models.Manager):
|
class PowerOutletManager(models.Manager):
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
# Pad any trailing digits to effect natural sorting
|
||||||
return super(PowerOutletManager, self).get_queryset().extra(select={
|
return super(PowerOutletManager, self).get_queryset().extra(select={
|
||||||
'name_padded': "CONCAT(SUBSTRING(dcim_poweroutlet.name FROM '^[^0-9]+'), "
|
'name_padded': "CONCAT(REGEXP_REPLACE(dcim_poweroutlet.name, '\d+$', ''), "
|
||||||
"LPAD(SUBSTRING(dcim_poweroutlet.name FROM '[0-9\/]+$'), 8, '0'))",
|
"LPAD(SUBSTRING(dcim_poweroutlet.name FROM '\d+$'), 8, '0'))",
|
||||||
}).order_by('device', 'name_padded')
|
}).order_by('device', 'name_padded')
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -153,11 +153,12 @@ class SiteDetailTable(SiteTable):
|
|||||||
prefix_count = tables.Column(accessor=Accessor('count_prefixes'), orderable=False, verbose_name='Prefixes')
|
prefix_count = tables.Column(accessor=Accessor('count_prefixes'), orderable=False, verbose_name='Prefixes')
|
||||||
vlan_count = tables.Column(accessor=Accessor('count_vlans'), orderable=False, verbose_name='VLANs')
|
vlan_count = tables.Column(accessor=Accessor('count_vlans'), orderable=False, verbose_name='VLANs')
|
||||||
circuit_count = tables.Column(accessor=Accessor('count_circuits'), orderable=False, verbose_name='Circuits')
|
circuit_count = tables.Column(accessor=Accessor('count_circuits'), orderable=False, verbose_name='Circuits')
|
||||||
|
vm_count = tables.Column(accessor=Accessor('count_vms'), orderable=False, verbose_name='VMs')
|
||||||
|
|
||||||
class Meta(SiteTable.Meta):
|
class Meta(SiteTable.Meta):
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'facility', 'region', 'tenant', 'asn', 'rack_count', 'device_count', 'prefix_count',
|
'pk', 'name', 'facility', 'region', 'tenant', 'asn', 'rack_count', 'device_count', 'prefix_count',
|
||||||
'vlan_count', 'circuit_count',
|
'vlan_count', 'circuit_count', 'vm_count',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ from utilities.views import (
|
|||||||
BulkComponentCreateView, BulkDeleteView, BulkEditView, BulkImportView, ComponentCreateView, ComponentDeleteView,
|
BulkComponentCreateView, BulkDeleteView, BulkEditView, BulkImportView, ComponentCreateView, ComponentDeleteView,
|
||||||
ComponentEditView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
ComponentEditView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
||||||
)
|
)
|
||||||
|
from virtualization.models import VirtualMachine
|
||||||
from . import filters, forms, tables
|
from . import filters, forms, tables
|
||||||
from .constants import CONNECTION_STATUS_CONNECTED
|
from .constants import CONNECTION_STATUS_CONNECTED
|
||||||
from .models import (
|
from .models import (
|
||||||
@@ -134,6 +135,7 @@ class SiteView(View):
|
|||||||
'prefix_count': Prefix.objects.filter(site=site).count(),
|
'prefix_count': Prefix.objects.filter(site=site).count(),
|
||||||
'vlan_count': VLAN.objects.filter(site=site).count(),
|
'vlan_count': VLAN.objects.filter(site=site).count(),
|
||||||
'circuit_count': Circuit.objects.filter(terminations__site=site).count(),
|
'circuit_count': Circuit.objects.filter(terminations__site=site).count(),
|
||||||
|
'vm_count': VirtualMachine.objects.filter(cluster__site=site).count(),
|
||||||
}
|
}
|
||||||
rack_groups = RackGroup.objects.filter(site=site).annotate(rack_count=Count('racks'))
|
rack_groups = RackGroup.objects.filter(site=site).annotate(rack_count=Count('racks'))
|
||||||
topology_maps = TopologyMap.objects.filter(site=site)
|
topology_maps = TopologyMap.objects.filter(site=site)
|
||||||
@@ -808,15 +810,11 @@ class DeviceView(View):
|
|||||||
console_ports = natsorted(
|
console_ports = natsorted(
|
||||||
ConsolePort.objects.filter(device=device).select_related('cs_port__device'), key=attrgetter('name')
|
ConsolePort.objects.filter(device=device).select_related('cs_port__device'), key=attrgetter('name')
|
||||||
)
|
)
|
||||||
cs_ports = natsorted(
|
cs_ports = ConsoleServerPort.objects.filter(device=device).select_related('connected_console')
|
||||||
ConsoleServerPort.objects.filter(device=device).select_related('connected_console'), key=attrgetter('name')
|
|
||||||
)
|
|
||||||
power_ports = natsorted(
|
power_ports = natsorted(
|
||||||
PowerPort.objects.filter(device=device).select_related('power_outlet__device'), key=attrgetter('name')
|
PowerPort.objects.filter(device=device).select_related('power_outlet__device'), key=attrgetter('name')
|
||||||
)
|
)
|
||||||
power_outlets = natsorted(
|
power_outlets = PowerOutlet.objects.filter(device=device).select_related('connected_port')
|
||||||
PowerOutlet.objects.filter(device=device).select_related('connected_port'), key=attrgetter('name')
|
|
||||||
)
|
|
||||||
interfaces = Interface.objects.order_naturally(
|
interfaces = Interface.objects.order_naturally(
|
||||||
device.device_type.interface_ordering
|
device.device_type.interface_ordering
|
||||||
).filter(
|
).filter(
|
||||||
|
|||||||
@@ -5,10 +5,7 @@ from django.db import models
|
|||||||
from netaddr import IPNetwork
|
from netaddr import IPNetwork
|
||||||
|
|
||||||
from .formfields import IPFormField
|
from .formfields import IPFormField
|
||||||
from .lookups import (
|
from . import lookups
|
||||||
EndsWith, IEndsWith, IRegex, IStartsWith, NetContained, NetContainedOrEqual, NetContains, NetContainsOrEquals,
|
|
||||||
NetHost, NetHostContained, NetMaskLength, Regex, StartsWith,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def prefix_validator(prefix):
|
def prefix_validator(prefix):
|
||||||
@@ -57,17 +54,18 @@ class IPNetworkField(BaseIPField):
|
|||||||
return 'cidr'
|
return 'cidr'
|
||||||
|
|
||||||
|
|
||||||
IPNetworkField.register_lookup(EndsWith)
|
IPNetworkField.register_lookup(lookups.IExact)
|
||||||
IPNetworkField.register_lookup(IEndsWith)
|
IPNetworkField.register_lookup(lookups.EndsWith)
|
||||||
IPNetworkField.register_lookup(StartsWith)
|
IPNetworkField.register_lookup(lookups.IEndsWith)
|
||||||
IPNetworkField.register_lookup(IStartsWith)
|
IPNetworkField.register_lookup(lookups.StartsWith)
|
||||||
IPNetworkField.register_lookup(Regex)
|
IPNetworkField.register_lookup(lookups.IStartsWith)
|
||||||
IPNetworkField.register_lookup(IRegex)
|
IPNetworkField.register_lookup(lookups.Regex)
|
||||||
IPNetworkField.register_lookup(NetContained)
|
IPNetworkField.register_lookup(lookups.IRegex)
|
||||||
IPNetworkField.register_lookup(NetContainedOrEqual)
|
IPNetworkField.register_lookup(lookups.NetContained)
|
||||||
IPNetworkField.register_lookup(NetContains)
|
IPNetworkField.register_lookup(lookups.NetContainedOrEqual)
|
||||||
IPNetworkField.register_lookup(NetContainsOrEquals)
|
IPNetworkField.register_lookup(lookups.NetContains)
|
||||||
IPNetworkField.register_lookup(NetMaskLength)
|
IPNetworkField.register_lookup(lookups.NetContainsOrEquals)
|
||||||
|
IPNetworkField.register_lookup(lookups.NetMaskLength)
|
||||||
|
|
||||||
|
|
||||||
class IPAddressField(BaseIPField):
|
class IPAddressField(BaseIPField):
|
||||||
@@ -80,16 +78,17 @@ class IPAddressField(BaseIPField):
|
|||||||
return 'inet'
|
return 'inet'
|
||||||
|
|
||||||
|
|
||||||
IPAddressField.register_lookup(EndsWith)
|
IPAddressField.register_lookup(lookups.IExact)
|
||||||
IPAddressField.register_lookup(IEndsWith)
|
IPAddressField.register_lookup(lookups.EndsWith)
|
||||||
IPAddressField.register_lookup(StartsWith)
|
IPAddressField.register_lookup(lookups.IEndsWith)
|
||||||
IPAddressField.register_lookup(IStartsWith)
|
IPAddressField.register_lookup(lookups.StartsWith)
|
||||||
IPAddressField.register_lookup(Regex)
|
IPAddressField.register_lookup(lookups.IStartsWith)
|
||||||
IPAddressField.register_lookup(IRegex)
|
IPAddressField.register_lookup(lookups.Regex)
|
||||||
IPAddressField.register_lookup(NetContained)
|
IPAddressField.register_lookup(lookups.IRegex)
|
||||||
IPAddressField.register_lookup(NetContainedOrEqual)
|
IPAddressField.register_lookup(lookups.NetContained)
|
||||||
IPAddressField.register_lookup(NetContains)
|
IPAddressField.register_lookup(lookups.NetContainedOrEqual)
|
||||||
IPAddressField.register_lookup(NetContainsOrEquals)
|
IPAddressField.register_lookup(lookups.NetContains)
|
||||||
IPAddressField.register_lookup(NetHost)
|
IPAddressField.register_lookup(lookups.NetContainsOrEquals)
|
||||||
IPAddressField.register_lookup(NetHostContained)
|
IPAddressField.register_lookup(lookups.NetHost)
|
||||||
IPAddressField.register_lookup(NetMaskLength)
|
IPAddressField.register_lookup(lookups.NetHostContained)
|
||||||
|
IPAddressField.register_lookup(lookups.NetMaskLength)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import django_filters
|
import django_filters
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from netaddr import IPNetwork
|
import netaddr
|
||||||
from netaddr.core import AddrFormatError
|
from netaddr.core import AddrFormatError
|
||||||
|
|
||||||
from dcim.models import Site, Device, Interface
|
from dcim.models import Site, Device, Interface
|
||||||
@@ -79,7 +79,7 @@ class AggregateFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
return queryset
|
return queryset
|
||||||
qs_filter = Q(description__icontains=value)
|
qs_filter = Q(description__icontains=value)
|
||||||
try:
|
try:
|
||||||
prefix = str(IPNetwork(value.strip()).cidr)
|
prefix = str(netaddr.IPNetwork(value.strip()).cidr)
|
||||||
qs_filter |= Q(prefix__net_contains_or_equals=prefix)
|
qs_filter |= Q(prefix__net_contains_or_equals=prefix)
|
||||||
except (AddrFormatError, ValueError):
|
except (AddrFormatError, ValueError):
|
||||||
pass
|
pass
|
||||||
@@ -112,6 +112,10 @@ class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
method='search_within_include',
|
method='search_within_include',
|
||||||
label='Within and including prefix',
|
label='Within and including prefix',
|
||||||
)
|
)
|
||||||
|
contains = django_filters.CharFilter(
|
||||||
|
method='search_contains',
|
||||||
|
label='Prefixes which contain this prefix or IP',
|
||||||
|
)
|
||||||
mask_length = django_filters.NumberFilter(
|
mask_length = django_filters.NumberFilter(
|
||||||
method='filter_mask_length',
|
method='filter_mask_length',
|
||||||
label='Mask length',
|
label='Mask length',
|
||||||
@@ -178,7 +182,7 @@ class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
return queryset
|
return queryset
|
||||||
qs_filter = Q(description__icontains=value)
|
qs_filter = Q(description__icontains=value)
|
||||||
try:
|
try:
|
||||||
prefix = str(IPNetwork(value.strip()).cidr)
|
prefix = str(netaddr.IPNetwork(value.strip()).cidr)
|
||||||
qs_filter |= Q(prefix__net_contains_or_equals=prefix)
|
qs_filter |= Q(prefix__net_contains_or_equals=prefix)
|
||||||
except (AddrFormatError, ValueError):
|
except (AddrFormatError, ValueError):
|
||||||
pass
|
pass
|
||||||
@@ -189,7 +193,7 @@ class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
if not value:
|
if not value:
|
||||||
return queryset
|
return queryset
|
||||||
try:
|
try:
|
||||||
query = str(IPNetwork(value).cidr)
|
query = str(netaddr.IPNetwork(value).cidr)
|
||||||
return queryset.filter(prefix__net_contained=query)
|
return queryset.filter(prefix__net_contained=query)
|
||||||
except (AddrFormatError, ValueError):
|
except (AddrFormatError, ValueError):
|
||||||
return queryset.none()
|
return queryset.none()
|
||||||
@@ -199,11 +203,25 @@ class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
if not value:
|
if not value:
|
||||||
return queryset
|
return queryset
|
||||||
try:
|
try:
|
||||||
query = str(IPNetwork(value).cidr)
|
query = str(netaddr.IPNetwork(value).cidr)
|
||||||
return queryset.filter(prefix__net_contained_or_equal=query)
|
return queryset.filter(prefix__net_contained_or_equal=query)
|
||||||
except (AddrFormatError, ValueError):
|
except (AddrFormatError, ValueError):
|
||||||
return queryset.none()
|
return queryset.none()
|
||||||
|
|
||||||
|
def search_contains(self, queryset, name, value):
|
||||||
|
value = value.strip()
|
||||||
|
if not value:
|
||||||
|
return queryset
|
||||||
|
try:
|
||||||
|
# Searching by prefix
|
||||||
|
if '/' in value:
|
||||||
|
return queryset.filter(prefix__net_contains_or_equals=str(netaddr.IPNetwork(value).cidr))
|
||||||
|
# Searching by IP address
|
||||||
|
else:
|
||||||
|
return queryset.filter(prefix__net_contains=str(netaddr.IPAddress(value)))
|
||||||
|
except (AddrFormatError, ValueError):
|
||||||
|
return queryset.none()
|
||||||
|
|
||||||
def filter_mask_length(self, queryset, name, value):
|
def filter_mask_length(self, queryset, name, value):
|
||||||
if not value:
|
if not value:
|
||||||
return queryset
|
return queryset
|
||||||
@@ -296,7 +314,7 @@ class IPAddressFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
if not value:
|
if not value:
|
||||||
return queryset
|
return queryset
|
||||||
try:
|
try:
|
||||||
query = str(IPNetwork(value.strip()).cidr)
|
query = str(netaddr.IPNetwork(value.strip()).cidr)
|
||||||
return queryset.filter(address__net_host_contained=query)
|
return queryset.filter(address__net_host_contained=query)
|
||||||
except (AddrFormatError, ValueError):
|
except (AddrFormatError, ValueError):
|
||||||
return queryset.none()
|
return queryset.none()
|
||||||
|
|||||||
@@ -13,12 +13,21 @@ class NetFieldDecoratorMixin(object):
|
|||||||
return lhs_string, lhs_params
|
return lhs_string, lhs_params
|
||||||
|
|
||||||
|
|
||||||
|
class IExact(NetFieldDecoratorMixin, lookups.IExact):
|
||||||
|
|
||||||
|
def get_rhs_op(self, connection, rhs):
|
||||||
|
return '= LOWER(%s)' % rhs
|
||||||
|
|
||||||
|
|
||||||
class EndsWith(NetFieldDecoratorMixin, lookups.EndsWith):
|
class EndsWith(NetFieldDecoratorMixin, lookups.EndsWith):
|
||||||
lookup_name = 'endswith'
|
pass
|
||||||
|
|
||||||
|
|
||||||
class IEndsWith(NetFieldDecoratorMixin, lookups.IEndsWith):
|
class IEndsWith(NetFieldDecoratorMixin, lookups.IEndsWith):
|
||||||
lookup_name = 'iendswith'
|
pass
|
||||||
|
|
||||||
|
def get_rhs_op(self, connection, rhs):
|
||||||
|
return 'LIKE LOWER(%s)' % rhs
|
||||||
|
|
||||||
|
|
||||||
class StartsWith(NetFieldDecoratorMixin, lookups.StartsWith):
|
class StartsWith(NetFieldDecoratorMixin, lookups.StartsWith):
|
||||||
@@ -26,15 +35,18 @@ class StartsWith(NetFieldDecoratorMixin, lookups.StartsWith):
|
|||||||
|
|
||||||
|
|
||||||
class IStartsWith(NetFieldDecoratorMixin, lookups.IStartsWith):
|
class IStartsWith(NetFieldDecoratorMixin, lookups.IStartsWith):
|
||||||
lookup_name = 'istartswith'
|
pass
|
||||||
|
|
||||||
|
def get_rhs_op(self, connection, rhs):
|
||||||
|
return 'LIKE LOWER(%s)' % rhs
|
||||||
|
|
||||||
|
|
||||||
class Regex(NetFieldDecoratorMixin, lookups.Regex):
|
class Regex(NetFieldDecoratorMixin, lookups.Regex):
|
||||||
lookup_name = 'regex'
|
pass
|
||||||
|
|
||||||
|
|
||||||
class IRegex(NetFieldDecoratorMixin, lookups.IRegex):
|
class IRegex(NetFieldDecoratorMixin, lookups.IRegex):
|
||||||
lookup_name = 'iregex'
|
pass
|
||||||
|
|
||||||
|
|
||||||
class NetContainsOrEquals(Lookup):
|
class NetContainsOrEquals(Lookup):
|
||||||
|
|||||||
@@ -281,12 +281,28 @@ class Prefix(CreatedUpdatedModel, CustomFieldModel):
|
|||||||
def get_duplicates(self):
|
def get_duplicates(self):
|
||||||
return Prefix.objects.filter(vrf=self.vrf, prefix=str(self.prefix)).exclude(pk=self.pk)
|
return Prefix.objects.filter(vrf=self.vrf, prefix=str(self.prefix)).exclude(pk=self.pk)
|
||||||
|
|
||||||
|
def get_child_prefixes(self):
|
||||||
|
"""
|
||||||
|
Return all Prefixes within this Prefix and VRF.
|
||||||
|
"""
|
||||||
|
return Prefix.objects.filter(prefix__net_contained=str(self.prefix), vrf=self.vrf)
|
||||||
|
|
||||||
def get_child_ips(self):
|
def get_child_ips(self):
|
||||||
"""
|
"""
|
||||||
Return all IPAddresses within this Prefix.
|
Return all IPAddresses within this Prefix and VRF.
|
||||||
"""
|
"""
|
||||||
return IPAddress.objects.filter(address__net_host_contained=str(self.prefix), vrf=self.vrf)
|
return IPAddress.objects.filter(address__net_host_contained=str(self.prefix), vrf=self.vrf)
|
||||||
|
|
||||||
|
def get_available_prefixes(self):
|
||||||
|
"""
|
||||||
|
Return all available Prefixes within this prefix as an IPSet.
|
||||||
|
"""
|
||||||
|
prefix = netaddr.IPSet(self.prefix)
|
||||||
|
child_prefixes = netaddr.IPSet([child.prefix for child in self.get_child_prefixes()])
|
||||||
|
available_prefixes = prefix - child_prefixes
|
||||||
|
|
||||||
|
return available_prefixes
|
||||||
|
|
||||||
def get_available_ips(self):
|
def get_available_ips(self):
|
||||||
"""
|
"""
|
||||||
Return all available IPs within this prefix as an IPSet.
|
Return all available IPs within this prefix as an IPSet.
|
||||||
@@ -304,15 +320,23 @@ class Prefix(CreatedUpdatedModel, CustomFieldModel):
|
|||||||
|
|
||||||
return available_ips
|
return available_ips
|
||||||
|
|
||||||
|
def get_first_available_prefix(self):
|
||||||
|
"""
|
||||||
|
Return the first available child prefix within the prefix (or None).
|
||||||
|
"""
|
||||||
|
available_prefixes = self.get_available_prefixes()
|
||||||
|
if not available_prefixes:
|
||||||
|
return None
|
||||||
|
return available_prefixes.iter_cidrs()[0]
|
||||||
|
|
||||||
def get_first_available_ip(self):
|
def get_first_available_ip(self):
|
||||||
"""
|
"""
|
||||||
Return the first available IP within the prefix (or None).
|
Return the first available IP within the prefix (or None).
|
||||||
"""
|
"""
|
||||||
available_ips = self.get_available_ips()
|
available_ips = self.get_available_ips()
|
||||||
if available_ips:
|
if not available_ips:
|
||||||
return '{}/{}'.format(next(available_ips.__iter__()), self.prefix.prefixlen)
|
|
||||||
else:
|
|
||||||
return None
|
return None
|
||||||
|
return '{}/{}'.format(next(available_ips.__iter__()), self.prefix.prefixlen)
|
||||||
|
|
||||||
def get_utilization(self):
|
def get_utilization(self):
|
||||||
"""
|
"""
|
||||||
@@ -330,17 +354,6 @@ class Prefix(CreatedUpdatedModel, CustomFieldModel):
|
|||||||
prefix_size -= 2
|
prefix_size -= 2
|
||||||
return int(float(child_count) / prefix_size * 100)
|
return int(float(child_count) / prefix_size * 100)
|
||||||
|
|
||||||
@property
|
|
||||||
def new_subnet(self):
|
|
||||||
if self.family == 4:
|
|
||||||
if self.prefix.prefixlen <= 30:
|
|
||||||
return netaddr.IPNetwork('{}/{}'.format(self.prefix.network, self.prefix.prefixlen + 1))
|
|
||||||
return None
|
|
||||||
if self.family == 6:
|
|
||||||
if self.prefix.prefixlen <= 126:
|
|
||||||
return netaddr.IPNetwork('{}/{}'.format(self.prefix.network, self.prefix.prefixlen + 1))
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class IPAddressManager(models.Manager):
|
class IPAddressManager(models.Manager):
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ urlpatterns = [
|
|||||||
url(r'^prefixes/(?P<pk>\d+)/$', views.PrefixView.as_view(), name='prefix'),
|
url(r'^prefixes/(?P<pk>\d+)/$', views.PrefixView.as_view(), name='prefix'),
|
||||||
url(r'^prefixes/(?P<pk>\d+)/edit/$', views.PrefixEditView.as_view(), name='prefix_edit'),
|
url(r'^prefixes/(?P<pk>\d+)/edit/$', views.PrefixEditView.as_view(), name='prefix_edit'),
|
||||||
url(r'^prefixes/(?P<pk>\d+)/delete/$', views.PrefixDeleteView.as_view(), name='prefix_delete'),
|
url(r'^prefixes/(?P<pk>\d+)/delete/$', views.PrefixDeleteView.as_view(), name='prefix_delete'),
|
||||||
|
url(r'^prefixes/(?P<pk>\d+)/prefixes/$', views.PrefixPrefixesView.as_view(), name='prefix_prefixes'),
|
||||||
url(r'^prefixes/(?P<pk>\d+)/ip-addresses/$', views.PrefixIPAddressesView.as_view(), name='prefix_ipaddresses'),
|
url(r'^prefixes/(?P<pk>\d+)/ip-addresses/$', views.PrefixIPAddressesView.as_view(), name='prefix_ipaddresses'),
|
||||||
|
|
||||||
# IP addresses
|
# IP addresses
|
||||||
|
|||||||
@@ -454,9 +454,6 @@ class PrefixView(View):
|
|||||||
except Aggregate.DoesNotExist:
|
except Aggregate.DoesNotExist:
|
||||||
aggregate = None
|
aggregate = None
|
||||||
|
|
||||||
# Count child IP addresses
|
|
||||||
ipaddress_count = prefix.get_child_ips().count()
|
|
||||||
|
|
||||||
# Parent prefixes table
|
# Parent prefixes table
|
||||||
parent_prefixes = Prefix.objects.filter(
|
parent_prefixes = Prefix.objects.filter(
|
||||||
Q(vrf=prefix.vrf) | Q(vrf__isnull=True)
|
Q(vrf=prefix.vrf) | Q(vrf__isnull=True)
|
||||||
@@ -479,6 +476,20 @@ class PrefixView(View):
|
|||||||
duplicate_prefix_table = tables.PrefixTable(list(duplicate_prefixes), orderable=False)
|
duplicate_prefix_table = tables.PrefixTable(list(duplicate_prefixes), orderable=False)
|
||||||
duplicate_prefix_table.exclude = ('vrf',)
|
duplicate_prefix_table.exclude = ('vrf',)
|
||||||
|
|
||||||
|
return render(request, 'ipam/prefix.html', {
|
||||||
|
'prefix': prefix,
|
||||||
|
'aggregate': aggregate,
|
||||||
|
'parent_prefix_table': parent_prefix_table,
|
||||||
|
'duplicate_prefix_table': duplicate_prefix_table,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class PrefixPrefixesView(View):
|
||||||
|
|
||||||
|
def get(self, request, pk):
|
||||||
|
|
||||||
|
prefix = get_object_or_404(Prefix.objects.all(), pk=pk)
|
||||||
|
|
||||||
# Child prefixes table
|
# Child prefixes table
|
||||||
child_prefixes = Prefix.objects.filter(
|
child_prefixes = Prefix.objects.filter(
|
||||||
vrf=prefix.vrf, prefix__net_contained=str(prefix.prefix)
|
vrf=prefix.vrf, prefix__net_contained=str(prefix.prefix)
|
||||||
@@ -487,15 +498,16 @@ class PrefixView(View):
|
|||||||
).annotate_depth(limit=0)
|
).annotate_depth(limit=0)
|
||||||
if child_prefixes:
|
if child_prefixes:
|
||||||
child_prefixes = add_available_prefixes(prefix.prefix, child_prefixes)
|
child_prefixes = add_available_prefixes(prefix.prefix, child_prefixes)
|
||||||
child_prefix_table = tables.PrefixDetailTable(child_prefixes)
|
|
||||||
|
prefix_table = tables.PrefixDetailTable(child_prefixes)
|
||||||
if request.user.has_perm('ipam.change_prefix') or request.user.has_perm('ipam.delete_prefix'):
|
if request.user.has_perm('ipam.change_prefix') or request.user.has_perm('ipam.delete_prefix'):
|
||||||
child_prefix_table.columns.show('pk')
|
prefix_table.columns.show('pk')
|
||||||
|
|
||||||
paginate = {
|
paginate = {
|
||||||
'klass': EnhancedPaginator,
|
'klass': EnhancedPaginator,
|
||||||
'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT)
|
'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT)
|
||||||
}
|
}
|
||||||
RequestConfig(request, paginate).configure(child_prefix_table)
|
RequestConfig(request, paginate).configure(prefix_table)
|
||||||
|
|
||||||
# Compile permissions list for rendering the object table
|
# Compile permissions list for rendering the object table
|
||||||
permissions = {
|
permissions = {
|
||||||
@@ -504,16 +516,12 @@ class PrefixView(View):
|
|||||||
'delete': request.user.has_perm('ipam.delete_prefix'),
|
'delete': request.user.has_perm('ipam.delete_prefix'),
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, 'ipam/prefix.html', {
|
return render(request, 'ipam/prefix_prefixes.html', {
|
||||||
'prefix': prefix,
|
'prefix': prefix,
|
||||||
'aggregate': aggregate,
|
'first_available_prefix': prefix.get_first_available_prefix(),
|
||||||
'ipaddress_count': ipaddress_count,
|
'prefix_table': prefix_table,
|
||||||
'parent_prefix_table': parent_prefix_table,
|
|
||||||
'child_prefix_table': child_prefix_table,
|
|
||||||
'duplicate_prefix_table': duplicate_prefix_table,
|
|
||||||
'bulk_querystring': 'vrf_id={}&within={}'.format(prefix.vrf or '0', prefix.prefix),
|
|
||||||
'permissions': permissions,
|
'permissions': permissions,
|
||||||
'return_url': prefix.get_absolute_url(),
|
'bulk_querystring': 'vrf_id={}&within={}'.format(prefix.vrf.pk if prefix.vrf else '0', prefix.prefix),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -548,6 +556,7 @@ class PrefixIPAddressesView(View):
|
|||||||
|
|
||||||
return render(request, 'ipam/prefix_ipaddresses.html', {
|
return render(request, 'ipam/prefix_ipaddresses.html', {
|
||||||
'prefix': prefix,
|
'prefix': prefix,
|
||||||
|
'first_available_ip': prefix.get_first_available_ip(),
|
||||||
'ip_table': ip_table,
|
'ip_table': ip_table,
|
||||||
'permissions': permissions,
|
'permissions': permissions,
|
||||||
'bulk_querystring': 'vrf_id={}&parent={}'.format(prefix.vrf.pk if prefix.vrf else '0', prefix.prefix),
|
'bulk_querystring': 'vrf_id={}&parent={}'.format(prefix.vrf.pk if prefix.vrf else '0', prefix.prefix),
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ class FormlessBrowsableAPIRenderer(BrowsableAPIRenderer):
|
|||||||
def show_form_for_method(self, *args, **kwargs):
|
def show_form_for_method(self, *args, **kwargs):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def get_filter_form(self, data, view, request):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Authentication
|
# Authentication
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ OBJ_TYPE_CHOICES = (
|
|||||||
|
|
||||||
class SearchForm(BootstrapMixin, forms.Form):
|
class SearchForm(BootstrapMixin, forms.Form):
|
||||||
q = forms.CharField(
|
q = forms.CharField(
|
||||||
label='Search', widget=forms.TextInput(attrs={'style': 'width: 350px'})
|
label='Search'
|
||||||
)
|
)
|
||||||
obj_type = forms.ChoiceField(
|
obj_type = forms.ChoiceField(
|
||||||
choices=OBJ_TYPE_CHOICES, required=False, label='Type'
|
choices=OBJ_TYPE_CHOICES, required=False, label='Type'
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ except ImportError:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
VERSION = '2.2.6'
|
VERSION = '2.2.8'
|
||||||
|
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
from django.db.models import Count
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
@@ -58,7 +59,7 @@ SEARCH_TYPES = OrderedDict((
|
|||||||
'url': 'dcim:rack_list',
|
'url': 'dcim:rack_list',
|
||||||
}),
|
}),
|
||||||
('devicetype', {
|
('devicetype', {
|
||||||
'queryset': DeviceType.objects.select_related('manufacturer'),
|
'queryset': DeviceType.objects.select_related('manufacturer').annotate(instance_count=Count('instances')),
|
||||||
'filter': DeviceTypeFilter,
|
'filter': DeviceTypeFilter,
|
||||||
'table': DeviceTypeTable,
|
'table': DeviceTypeTable,
|
||||||
'url': 'dcim:devicetype_list',
|
'url': 'dcim:devicetype_list',
|
||||||
|
|||||||
@@ -303,6 +303,7 @@ class Secret(CreatedUpdatedModel):
|
|||||||
|LL|MySecret|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|
|
|LL|MySecret|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|
|
||||||
+--+--------+-------------------------------------------+
|
+--+--------+-------------------------------------------+
|
||||||
"""
|
"""
|
||||||
|
s = s.encode('utf8')
|
||||||
if len(s) > 65535:
|
if len(s) > 65535:
|
||||||
raise ValueError("Maximum plaintext size is 65535 bytes.")
|
raise ValueError("Maximum plaintext size is 65535 bytes.")
|
||||||
# Minimum ciphertext size is 64 bytes to conceal the length of short secrets.
|
# Minimum ciphertext size is 64 bytes to conceal the length of short secrets.
|
||||||
@@ -315,7 +316,7 @@ class Secret(CreatedUpdatedModel):
|
|||||||
return (
|
return (
|
||||||
chr(len(s) >> 8).encode() +
|
chr(len(s) >> 8).encode() +
|
||||||
chr(len(s) % 256).encode() +
|
chr(len(s) % 256).encode() +
|
||||||
s.encode() +
|
s +
|
||||||
os.urandom(pad_length)
|
os.urandom(pad_length)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -324,11 +325,11 @@ class Secret(CreatedUpdatedModel):
|
|||||||
Consume the first two bytes of s as a plaintext length indicator and return only that many bytes as the
|
Consume the first two bytes of s as a plaintext length indicator and return only that many bytes as the
|
||||||
plaintext.
|
plaintext.
|
||||||
"""
|
"""
|
||||||
if isinstance(s[0], int):
|
if isinstance(s[0], str):
|
||||||
plaintext_length = (s[0] << 8) + s[1]
|
|
||||||
elif isinstance(s[0], str):
|
|
||||||
plaintext_length = (ord(s[0]) << 8) + ord(s[1])
|
plaintext_length = (ord(s[0]) << 8) + ord(s[1])
|
||||||
return s[2:plaintext_length + 2].decode()
|
else:
|
||||||
|
plaintext_length = (s[0] << 8) + s[1]
|
||||||
|
return s[2:plaintext_length + 2].decode('utf8')
|
||||||
|
|
||||||
def encrypt(self, secret_key):
|
def encrypt(self, secret_key):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ def secret_edit(request, pk):
|
|||||||
# Create and encrypt the new Secret
|
# Create and encrypt the new Secret
|
||||||
if master_key is not None:
|
if master_key is not None:
|
||||||
secret = form.save(commit=False)
|
secret = form.save(commit=False)
|
||||||
secret.plaintext = str(form.cleaned_data['plaintext'])
|
secret.plaintext = form.cleaned_data['plaintext']
|
||||||
secret.encrypt(master_key)
|
secret.encrypt(master_key)
|
||||||
secret.save()
|
secret.save()
|
||||||
messages.success(request, "Modified secret {}.".format(secret))
|
messages.success(request, "Modified secret {}.".format(secret))
|
||||||
|
|||||||
@@ -535,7 +535,7 @@
|
|||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Power Outlets</strong>
|
<strong>Power Outlets</strong>
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
{% if perms.dcim.change_poweroutlet and cs_ports|length > 1 %}
|
{% if perms.dcim.change_poweroutlet and power_outlets|length > 1 %}
|
||||||
<button class="btn btn-default btn-xs toggle">
|
<button class="btn btn-default btn-xs toggle">
|
||||||
<span class="glyphicon glyphicon-unchecked" aria-hidden="true"></span> Select all
|
<span class="glyphicon glyphicon-unchecked" aria-hidden="true"></span> Select all
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
Import device types
|
Import device types
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% include 'inc/export_button.html' with obj_type='devicetypes' %}
|
{% include 'inc/export_button.html' with obj_type='device types' %}
|
||||||
</div>
|
</div>
|
||||||
<h1>{% block title %}Device Types{% endblock %}</h1>
|
<h1>{% block title %}Device Types{% endblock %}</h1>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
Import rack groups
|
Import rack groups
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% include 'inc/export_button.html' with obj_type='rackgroups' %}
|
{% include 'inc/export_button.html' with obj_type='rack groups' %}
|
||||||
</div>
|
</div>
|
||||||
<h1>{% block title %}Rack Groups{% endblock %}</h1>
|
<h1>{% block title %}Rack Groups{% endblock %}</h1>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
@@ -211,6 +211,10 @@
|
|||||||
<h2><a href="{% url 'circuits:circuit_list' %}?site={{ site.slug }}" class="btn {% if stats.circuit_count %}btn-primary{% else %}btn-default{% endif %} btn-lg">{{ stats.circuit_count }}</a></h2>
|
<h2><a href="{% url 'circuits:circuit_list' %}?site={{ site.slug }}" class="btn {% if stats.circuit_count %}btn-primary{% else %}btn-default{% endif %} btn-lg">{{ stats.circuit_count }}</a></h2>
|
||||||
<p>Circuits</p>
|
<p>Circuits</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-4 text-center">
|
||||||
|
<h2><a href="{% url 'virtualization:virtualmachine_list' %}?site={{ site.slug }}" class="btn {% if stats.vm_count %}btn-primary{% else %}btn-default{% endif %} btn-lg">{{ stats.vm_count }}</a></h2>
|
||||||
|
<p>Virtual Machines</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
|
|||||||
@@ -22,8 +22,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
{% if perms.ipam.add_ipaddress %}
|
{% if perms.ipam.add_prefix and active_tab == 'prefixes' and first_available_prefix %}
|
||||||
<a href="{% url 'ipam:ipaddress_add' %}?address={{ prefix.get_first_available_ip }}{% if prefix.vrf %}&vrf={{ prefix.vrf.pk }}{% endif %}{% if prefix.tenant %}&tenant={{ prefix.tenant.pk }}{% endif %}" class="btn btn-success">
|
<a href="{% url 'ipam:prefix_add' %}?prefix={{ first_available_prefix }}&vrf={{ prefix.vrf.pk }}&site={{ prefix.site.pk }}&tenant_group={{ prefix.tenant.group.pk }}&tenant={{ prefix.tenant.pk }}" class="btn btn-success">
|
||||||
|
<i class="fa fa-plus" aria-hidden="true"></i> Add Child Prefix
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.ipam.add_ipaddress and active_tab == 'ip-addresses' and first_available_ip %}
|
||||||
|
<a href="{% url 'ipam:ipaddress_add' %}?address={{ first_available_ip }}&vrf={{ prefix.vrf.pk }}&tenant_group={{ prefix.tenant.group.pk }}&tenant={{ prefix.tenant.pk }}" class="btn btn-success">
|
||||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||||
Add an IP Address
|
Add an IP Address
|
||||||
</a>
|
</a>
|
||||||
@@ -45,5 +50,6 @@
|
|||||||
{% include 'inc/created_updated.html' with obj=prefix %}
|
{% include 'inc/created_updated.html' with obj=prefix %}
|
||||||
<ul class="nav nav-tabs" style="margin-bottom: 20px">
|
<ul class="nav nav-tabs" style="margin-bottom: 20px">
|
||||||
<li role="presentation"{% if active_tab == 'prefix' %} class="active"{% endif %}><a href="{% url 'ipam:prefix' pk=prefix.pk %}">Prefix</a></li>
|
<li role="presentation"{% if active_tab == 'prefix' %} class="active"{% endif %}><a href="{% url 'ipam:prefix' pk=prefix.pk %}">Prefix</a></li>
|
||||||
<li role="presentation"{% if active_tab == 'ip-addresses' %} class="active"{% endif %}><a href="{% url 'ipam:prefix_ipaddresses' pk=prefix.pk %}">IP Addresses</a></li>
|
<li role="presentation"{% if active_tab == 'prefixes' %} class="active"{% endif %}><a href="{% url 'ipam:prefix_prefixes' pk=prefix.pk %}">Child Prefixes <span class="badge">{{ prefix.get_child_prefixes.count }}</span></a></li>
|
||||||
|
<li role="presentation"{% if active_tab == 'ip-addresses' %} class="active"{% endif %}><a href="{% url 'ipam:prefix_ipaddresses' pk=prefix.pk %}">IP Addresses <span class="badge">{{ prefix.get_child_ips.count }}</span></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{% extends '_base.html' %}
|
{% extends '_base.html' %}
|
||||||
|
{% load helpers %}
|
||||||
|
|
||||||
{% block title %}{{ prefix }}{% endblock %}
|
{% block title %}{{ prefix }}{% endblock %}
|
||||||
|
|
||||||
@@ -100,16 +101,6 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td>Is a pool</td>
|
|
||||||
<td>
|
|
||||||
{% if prefix.is_pool %}
|
|
||||||
<i class="glyphicon glyphicon-ok text-success" title="Yes"></i>
|
|
||||||
{% else %}
|
|
||||||
<i class="glyphicon glyphicon-remove text-danger" title="No"></i>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>Description</td>
|
<td>Description</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -120,9 +111,19 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Is a pool</td>
|
||||||
|
<td>
|
||||||
|
{% if prefix.is_pool %}
|
||||||
|
<i class="glyphicon glyphicon-ok text-success" title="Yes"></i>
|
||||||
|
{% else %}
|
||||||
|
<i class="glyphicon glyphicon-remove text-danger" title="No"></i>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Utilization</td>
|
<td>Utilization</td>
|
||||||
<td><a href="{% url 'ipam:prefix_ipaddresses' pk=prefix.pk %}">{{ ipaddress_count }} IP addresses</a> ({{ prefix.get_utilization }}%)</td>
|
<td>{% utilization_graph prefix.get_utilization %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -138,15 +139,4 @@
|
|||||||
{% include 'panel_table.html' with table=parent_prefix_table heading='Parent Prefixes' %}
|
{% include 'panel_table.html' with table=parent_prefix_table heading='Parent Prefixes' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-12">
|
|
||||||
{% if child_prefix_table.rows %}
|
|
||||||
{% include 'utilities/obj_table.html' with table=child_prefix_table table_template='panel_table.html' heading='Child Prefixes' parent=prefix bulk_edit_url='ipam:prefix_bulk_edit' bulk_delete_url='ipam:prefix_bulk_delete' %}
|
|
||||||
{% elif prefix.new_subnet %}
|
|
||||||
<a href="{% url 'ipam:prefix_add' %}?prefix={{ prefix.new_subnet }}{% if prefix.vrf %}&vrf={{ prefix.vrf.pk }}{% endif %}{% if prefix.site %}&site={{ prefix.site.pk }}{% endif %}" class="btn btn-success">
|
|
||||||
<i class="fa fa-plus" aria-hidden="true"></i> Add Child Prefix
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
{% block title %}{{ prefix }} - IP Addresses{% endblock %}
|
{% block title %}{{ prefix }} - IP Addresses{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% include 'ipam/inc/prefix_header.html' with active_tab='ip-addresses' %}
|
{% include 'ipam/inc/prefix_header.html' with active_tab='ip-addresses' %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
{% include 'utilities/obj_table.html' with table=ip_table table_template='panel_table.html' heading='IP Addresses' bulk_edit_url='ipam:ipaddress_bulk_edit' bulk_delete_url='ipam:ipaddress_bulk_delete' %}
|
{% include 'utilities/obj_table.html' with table=ip_table table_template='panel_table.html' heading='IP Addresses' bulk_edit_url='ipam:ipaddress_bulk_edit' bulk_delete_url='ipam:ipaddress_bulk_delete' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
12
netbox/templates/ipam/prefix_prefixes.html
Normal file
12
netbox/templates/ipam/prefix_prefixes.html
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{% extends '_base.html' %}
|
||||||
|
|
||||||
|
{% block title %}{{ prefix }} - Prefixes{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% include 'ipam/inc/prefix_header.html' with active_tab='prefixes' %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
{% include 'utilities/obj_table.html' with table=prefix_table table_template='panel_table.html' heading='Child Prefixes' bulk_edit_url='ipam:prefix_bulk_edit' bulk_delete_url='ipam:prefix_bulk_delete' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -13,12 +13,14 @@
|
|||||||
{% for obj_type in results %}
|
{% for obj_type in results %}
|
||||||
<h3 id="{{ obj_type.name|lower }}">{{ obj_type.name|bettertitle }}</h3>
|
<h3 id="{{ obj_type.name|lower }}">{{ obj_type.name|bettertitle }}</h3>
|
||||||
{% include 'panel_table.html' with table=obj_type.table hide_paginator=True %}
|
{% include 'panel_table.html' with table=obj_type.table hide_paginator=True %}
|
||||||
{% if obj_type.table.page.has_next %}
|
|
||||||
<a href="{{ obj_type.url }}" class="btn btn-primary pull-right">
|
<a href="{{ obj_type.url }}" class="btn btn-primary pull-right">
|
||||||
<span class="fa fa-arrow-right" aria-hidden="true"></span>
|
<span class="fa fa-arrow-right" aria-hidden="true"></span>
|
||||||
|
{% if obj_type.table.page.has_next %}
|
||||||
See all {{ obj_type.table.page.paginator.count }} results
|
See all {{ obj_type.table.page.paginator.count }} results
|
||||||
</a>
|
{% else %}
|
||||||
|
Refine search
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</a>
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<div class="row" style="padding-bottom: 20px">
|
<div class="row" style="padding-bottom: 20px">
|
||||||
<div class="col-md-12 text-center">
|
<div class="col-md-12 text-center">
|
||||||
<form action="{% url 'search' %}" method="get" class="form-inline">
|
<form action="{% url 'search' %}" method="get" class="form-inline">
|
||||||
{{ search_form.q }}
|
<input type="text" name="q" value="{{ request.GET.q }}" placeholder="Search" id="id_q" class="form-control" style="width: 350px" />
|
||||||
{{ search_form.obj_type }}
|
{{ search_form.obj_type }}
|
||||||
<button type="submit" class="btn btn-primary">Search</button>
|
<button type="submit" class="btn btn-primary">Search</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -55,10 +55,16 @@ class LoginView(View):
|
|||||||
class LogoutView(View):
|
class LogoutView(View):
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
|
|
||||||
|
# Log out the user
|
||||||
auth_logout(request)
|
auth_logout(request)
|
||||||
messages.info(request, "You have logged out.")
|
messages.info(request, "You have logged out.")
|
||||||
|
|
||||||
return HttpResponseRedirect(reverse('home'))
|
# Delete session key cookie (if set) upon logout
|
||||||
|
response = HttpResponseRedirect(reverse('home'))
|
||||||
|
response.delete_cookie('session_key')
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import sys
|
|||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import ProgrammingError
|
from django.db import ProgrammingError
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import Http404, HttpResponseRedirect
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
@@ -61,6 +61,10 @@ class ExceptionHandlingMiddleware(object):
|
|||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Ignore Http404s (defer to Django's built-in 404 handling)
|
||||||
|
if isinstance(exception, Http404):
|
||||||
|
return
|
||||||
|
|
||||||
# Determine the type of exception
|
# Determine the type of exception
|
||||||
if isinstance(exception, ProgrammingError):
|
if isinstance(exception, ProgrammingError):
|
||||||
template_name = 'exceptions/programming_error.html'
|
template_name = 'exceptions/programming_error.html'
|
||||||
|
|||||||
@@ -308,8 +308,14 @@ class BulkCreateView(View):
|
|||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
|
|
||||||
|
# Set initial values for visible form fields from query args
|
||||||
|
initial = {}
|
||||||
|
for field in getattr(self.model_form._meta, 'fields', []):
|
||||||
|
if request.GET.get(field):
|
||||||
|
initial[field] = request.GET[field]
|
||||||
|
|
||||||
form = self.form()
|
form = self.form()
|
||||||
model_form = self.model_form()
|
model_form = self.model_form(initial=initial)
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
'obj_type': self.model_form._meta.model._meta.verbose_name,
|
'obj_type': self.model_form._meta.model._meta.verbose_name,
|
||||||
|
|||||||
@@ -84,10 +84,32 @@ class VirtualMachineFilter(CustomFieldFilterSet):
|
|||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Cluster group (slug)',
|
label='Cluster group (slug)',
|
||||||
)
|
)
|
||||||
|
cluster_type_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
name='cluster__type',
|
||||||
|
queryset=ClusterType.objects.all(),
|
||||||
|
label='Cluster type (ID)',
|
||||||
|
)
|
||||||
|
cluster_type = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
name='cluster__type__slug',
|
||||||
|
queryset=ClusterType.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
label='Cluster type (slug)',
|
||||||
|
)
|
||||||
cluster_id = django_filters.ModelMultipleChoiceFilter(
|
cluster_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=Cluster.objects.all(),
|
queryset=Cluster.objects.all(),
|
||||||
label='Cluster (ID)',
|
label='Cluster (ID)',
|
||||||
)
|
)
|
||||||
|
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
name='cluster__site',
|
||||||
|
queryset=Site.objects.all(),
|
||||||
|
label='Site (ID)',
|
||||||
|
)
|
||||||
|
site = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
name='cluster__site__slug',
|
||||||
|
queryset=Site.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
label='Site (slug)',
|
||||||
|
)
|
||||||
role_id = django_filters.ModelMultipleChoiceFilter(
|
role_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=DeviceRole.objects.all(),
|
queryset=DeviceRole.objects.all(),
|
||||||
label='Role (ID)',
|
label='Role (ID)',
|
||||||
|
|||||||
@@ -340,10 +340,20 @@ class VirtualMachineFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_option=(0, 'None')
|
null_option=(0, 'None')
|
||||||
)
|
)
|
||||||
|
cluster_type = FilterChoiceField(
|
||||||
|
queryset=ClusterType.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
null_option=(0, 'None')
|
||||||
|
)
|
||||||
cluster_id = FilterChoiceField(
|
cluster_id = FilterChoiceField(
|
||||||
queryset=Cluster.objects.annotate(filter_count=Count('virtual_machines')),
|
queryset=Cluster.objects.annotate(filter_count=Count('virtual_machines')),
|
||||||
label='Cluster'
|
label='Cluster'
|
||||||
)
|
)
|
||||||
|
site = FilterChoiceField(
|
||||||
|
queryset=Site.objects.annotate(filter_count=Count('clusters__virtual_machines')),
|
||||||
|
to_field_name='slug',
|
||||||
|
null_option=(0, 'None')
|
||||||
|
)
|
||||||
role = FilterChoiceField(
|
role = FilterChoiceField(
|
||||||
queryset=DeviceRole.objects.filter(vm_role=True).annotate(filter_count=Count('virtual_machines')),
|
queryset=DeviceRole.objects.filter(vm_role=True).annotate(filter_count=Count('virtual_machines')),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ class Cluster(CreatedUpdatedModel, CustomFieldModel):
|
|||||||
self.name,
|
self.name,
|
||||||
self.type.name,
|
self.type.name,
|
||||||
self.group.name if self.group else None,
|
self.group.name if self.group else None,
|
||||||
|
self.site.name if self.site else None,
|
||||||
self.comments,
|
self.comments,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user