mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-20 02:06:42 -06:00
commit
f6d1163ddd
47
.github/ISSUE_TEMPLATE.md
vendored
47
.github/ISSUE_TEMPLATE.md
vendored
@ -1,28 +1,41 @@
|
||||
<!--
|
||||
Please note: GitHub issues are to be used only for feature requests
|
||||
and bug reports. For installation assistance or general discussion,
|
||||
please join us on the mailing list:
|
||||
Before opening a new issue, please search through the existing issues to
|
||||
see if your topic has already been addressed. Note that you may need to
|
||||
remove the "is:open" filter from the search bar to include closed issues.
|
||||
|
||||
Check the appropriate type for your issue below by placing an x between the
|
||||
brackets. If none of the below apply, please raise your issue for
|
||||
discussion on our mailing list:
|
||||
|
||||
https://groups.google.com/forum/#!forum/netbox-discuss
|
||||
|
||||
Please indicate "bug report" or "feature request" below. Be sure to
|
||||
search the existing set of issues (both open and closed) to see if
|
||||
a similar issue has already been raised.
|
||||
-->
|
||||
### Issue type:
|
||||
Please note that issues which do not fall under any of the below categories
|
||||
will be closed.
|
||||
--->
|
||||
### Issue type
|
||||
[ ] Feature request <!-- Requesting the implementation of a new feature -->
|
||||
[ ] Bug report <!-- Reporting unexpected or erroneous behavior -->
|
||||
[ ] Documentation <!-- Proposing a modification to the documentation -->
|
||||
|
||||
<!--
|
||||
If filing a bug, please indicate the version of Python and NetBox
|
||||
you are running. (This is not necessary for feature requests.)
|
||||
Please describe the environment in which you are running NetBox. (Be sure
|
||||
to verify that you are running the latest stable release of NetBox before
|
||||
submitting a bug report.)
|
||||
-->
|
||||
**Python version:**
|
||||
**NetBox version:**
|
||||
### Environment
|
||||
* Python version: <!-- Example: 3.5.4 -->
|
||||
* NetBox version: <!-- Example: 2.1.3 -->
|
||||
|
||||
<!--
|
||||
If filing a bug, please record the exact steps taken to reproduce
|
||||
the bug and any errors messages that are generated.
|
||||
BUG REPORTS must include:
|
||||
* A list of the steps needed to reproduce the bug
|
||||
* A description of the expected behavior
|
||||
* Any relevant error messages (screenshots may also help)
|
||||
|
||||
If filing a feature request, please precisely describe the data
|
||||
model or workflow you would like to see implemented, and provide a
|
||||
use case.
|
||||
FEATURE REQUESTS must include:
|
||||
* A detailed description of the proposed functionality
|
||||
* A use case for the new feature
|
||||
* A rough description of any necessary changes to the database schema
|
||||
* Any relevant third-party libraries which would be needed
|
||||
-->
|
||||
### Description
|
||||
|
@ -135,6 +135,14 @@ An API consumer can request an arbitrary number of objects by appending the "lim
|
||||
|
||||
---
|
||||
|
||||
## MEDIA_ROOT
|
||||
|
||||
Default: $BASE_DIR/netbox/media/
|
||||
|
||||
The file path to the location where media files (such as image attachments) are stored. By default, this is the `netbox/media` directory within the base NetBox installation path.
|
||||
|
||||
---
|
||||
|
||||
## NAPALM_USERNAME
|
||||
|
||||
## NAPALM_PASSWORD
|
||||
|
@ -51,7 +51,7 @@ Restart the nginx service to use the new configuration.
|
||||
# service nginx restart
|
||||
```
|
||||
|
||||
To enable SSL, consider this guide on [securing nginx with Let's Encrypt](https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-14-04).
|
||||
To enable SSL, consider this guide on [securing nginx with Let's Encrypt](https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-16-04).
|
||||
|
||||
## Option B: Apache
|
||||
|
||||
@ -96,7 +96,7 @@ Save the contents of the above example in `/etc/apache2/sites-available/netbox.c
|
||||
# service apache2 restart
|
||||
```
|
||||
|
||||
To enable SSL, consider this guide on [securing Apache with Let's Encrypt](https://www.digitalocean.com/community/tutorials/how-to-secure-apache-with-let-s-encrypt-on-ubuntu-14-04).
|
||||
To enable SSL, consider this guide on [securing Apache with Let's Encrypt](https://www.digitalocean.com/community/tutorials/how-to-secure-apache-with-let-s-encrypt-on-ubuntu-16-04).
|
||||
|
||||
# gunicorn Installation
|
||||
|
||||
|
@ -170,6 +170,7 @@ class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||
queryset=Site.objects.annotate(filter_count=Count('circuit_terminations')),
|
||||
to_field_name='slug'
|
||||
)
|
||||
commit_rate = forms.IntegerField(required=False, min_value=0, label='Commit rate (Kbps)')
|
||||
|
||||
|
||||
#
|
||||
|
@ -13,22 +13,6 @@ from utilities.models import CreatedUpdatedModel
|
||||
from .constants import *
|
||||
|
||||
|
||||
def humanize_speed(speed):
|
||||
"""
|
||||
Humanize speeds given in Kbps (e.g. 10000000 becomes '10 Gbps')
|
||||
"""
|
||||
if speed >= 1000000000 and speed % 1000000000 == 0:
|
||||
return '{} Tbps'.format(speed / 1000000000)
|
||||
elif speed >= 1000000 and speed % 1000000 == 0:
|
||||
return '{} Gbps'.format(speed / 1000000)
|
||||
elif speed >= 1000 and speed % 1000 == 0:
|
||||
return '{} Mbps'.format(speed / 1000)
|
||||
elif speed >= 1000:
|
||||
return '{} Mbps'.format(float(speed) / 1000)
|
||||
else:
|
||||
return '{} Kbps'.format(speed)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Provider(CreatedUpdatedModel, CustomFieldModel):
|
||||
"""
|
||||
@ -139,10 +123,6 @@ class Circuit(CreatedUpdatedModel, CustomFieldModel):
|
||||
def termination_z(self):
|
||||
return self._get_termination('Z')
|
||||
|
||||
def commit_rate_human(self):
|
||||
return '' if not self.commit_rate else humanize_speed(self.commit_rate)
|
||||
commit_rate_human.admin_order_field = 'commit_rate'
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class CircuitTermination(models.Model):
|
||||
@ -173,11 +153,3 @@ class CircuitTermination(models.Model):
|
||||
return CircuitTermination.objects.select_related('site').get(circuit=self.circuit, term_side=peer_side)
|
||||
except CircuitTermination.DoesNotExist:
|
||||
return None
|
||||
|
||||
def port_speed_human(self):
|
||||
return humanize_speed(self.port_speed)
|
||||
port_speed_human.admin_order_field = 'port_speed'
|
||||
|
||||
def upstream_speed_human(self):
|
||||
return '' if not self.upstream_speed else humanize_speed(self.upstream_speed)
|
||||
upstream_speed_human.admin_order_field = 'upstream_speed'
|
||||
|
@ -58,6 +58,7 @@ IFACE_FF_1GE_FIXED = 1000
|
||||
IFACE_FF_1GE_GBIC = 1050
|
||||
IFACE_FF_1GE_SFP = 1100
|
||||
IFACE_FF_10GE_FIXED = 1150
|
||||
IFACE_FF_10GE_CX4 = 1170
|
||||
IFACE_FF_10GE_SFP_PLUS = 1200
|
||||
IFACE_FF_10GE_XFP = 1300
|
||||
IFACE_FF_10GE_XENPAK = 1310
|
||||
@ -106,6 +107,7 @@ IFACE_FF_CHOICES = [
|
||||
[IFACE_FF_100ME_FIXED, '100BASE-TX (10/100ME)'],
|
||||
[IFACE_FF_1GE_FIXED, '1000BASE-T (1GE)'],
|
||||
[IFACE_FF_10GE_FIXED, '10GBASE-T (10GE)'],
|
||||
[IFACE_FF_10GE_CX4, '10GBASE-CX4 (10GE)'],
|
||||
]
|
||||
],
|
||||
[
|
||||
|
@ -273,6 +273,7 @@ class DeviceTypeFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
class DeviceTypeComponentFilterSet(django_filters.FilterSet):
|
||||
devicetype_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=DeviceType.objects.all(),
|
||||
name='device_type_id',
|
||||
label='Device type (ID)',
|
||||
)
|
||||
|
||||
|
25
netbox/dcim/migrations/0042_interface_ff_10ge_cx4.py
Normal file
25
netbox/dcim/migrations/0042_interface_ff_10ge_cx4.py
Normal file
@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.4 on 2017-08-29 21:00
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('dcim', '0041_napalm_integration'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='interface',
|
||||
name='form_factor',
|
||||
field=models.PositiveSmallIntegerField(choices=[['Virtual interfaces', [[0, 'Virtual'], [200, 'Link Aggregation Group (LAG)']]], ['Ethernet (fixed)', [[800, '100BASE-TX (10/100ME)'], [1000, '1000BASE-T (1GE)'], [1150, '10GBASE-T (10GE)'], [1170, '10GBASE-CX4 (10GE)']]], ['Ethernet (modular)', [[1050, 'GBIC (1GE)'], [1100, 'SFP (1GE)'], [1200, 'SFP+ (10GE)'], [1300, 'XFP (10GE)'], [1310, 'XENPAK (10GE)'], [1320, 'X2 (10GE)'], [1350, 'SFP28 (25GE)'], [1400, 'QSFP+ (40GE)'], [1500, 'CFP (100GE)'], [1600, 'QSFP28 (100GE)']]], ['Wireless', [[2600, 'IEEE 802.11a'], [2610, 'IEEE 802.11b/g'], [2620, 'IEEE 802.11n'], [2630, 'IEEE 802.11ac'], [2640, 'IEEE 802.11ad']]], ['FibreChannel', [[3010, 'SFP (1GFC)'], [3020, 'SFP (2GFC)'], [3040, 'SFP (4GFC)'], [3080, 'SFP+ (8GFC)'], [3160, 'SFP+ (16GFC)']]], ['Serial', [[4000, 'T1 (1.544 Mbps)'], [4010, 'E1 (2.048 Mbps)'], [4040, 'T3 (45 Mbps)'], [4050, 'E3 (34 Mbps)']]], ['Stacking', [[5000, 'Cisco StackWise'], [5050, 'Cisco StackWise Plus'], [5100, 'Cisco FlexStack'], [5150, 'Cisco FlexStack Plus'], [5200, 'Juniper VCP']]], ['Other', [[32767, 'Other']]]], default=1200),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interfacetemplate',
|
||||
name='form_factor',
|
||||
field=models.PositiveSmallIntegerField(choices=[['Virtual interfaces', [[0, 'Virtual'], [200, 'Link Aggregation Group (LAG)']]], ['Ethernet (fixed)', [[800, '100BASE-TX (10/100ME)'], [1000, '1000BASE-T (1GE)'], [1150, '10GBASE-T (10GE)'], [1170, '10GBASE-CX4 (10GE)']]], ['Ethernet (modular)', [[1050, 'GBIC (1GE)'], [1100, 'SFP (1GE)'], [1200, 'SFP+ (10GE)'], [1300, 'XFP (10GE)'], [1310, 'XENPAK (10GE)'], [1320, 'X2 (10GE)'], [1350, 'SFP28 (25GE)'], [1400, 'QSFP+ (40GE)'], [1500, 'CFP (100GE)'], [1600, 'QSFP28 (100GE)']]], ['Wireless', [[2600, 'IEEE 802.11a'], [2610, 'IEEE 802.11b/g'], [2620, 'IEEE 802.11n'], [2630, 'IEEE 802.11ac'], [2640, 'IEEE 802.11ad']]], ['FibreChannel', [[3010, 'SFP (1GFC)'], [3020, 'SFP (2GFC)'], [3040, 'SFP (4GFC)'], [3080, 'SFP+ (8GFC)'], [3160, 'SFP+ (16GFC)']]], ['Serial', [[4000, 'T1 (1.544 Mbps)'], [4010, 'E1 (2.048 Mbps)'], [4040, 'T3 (45 Mbps)'], [4050, 'E3 (34 Mbps)']]], ['Stacking', [[5000, 'Cisco StackWise'], [5050, 'Cisco StackWise Plus'], [5100, 'Cisco FlexStack'], [5150, 'Cisco FlexStack Plus'], [5200, 'Juniper VCP']]], ['Other', [[32767, 'Other']]]], default=1200),
|
||||
),
|
||||
]
|
70
netbox/dcim/migrations/0043_device_component_name_lengths.py
Normal file
70
netbox/dcim/migrations/0043_device_component_name_lengths.py
Normal file
@ -0,0 +1,70 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.4 on 2017-08-29 21:26
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('dcim', '0042_interface_ff_10ge_cx4'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='consoleport',
|
||||
name='name',
|
||||
field=models.CharField(max_length=50),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='consoleporttemplate',
|
||||
name='name',
|
||||
field=models.CharField(max_length=50),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='consoleserverport',
|
||||
name='name',
|
||||
field=models.CharField(max_length=50),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='consoleserverporttemplate',
|
||||
name='name',
|
||||
field=models.CharField(max_length=50),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='devicebaytemplate',
|
||||
name='name',
|
||||
field=models.CharField(max_length=50),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interface',
|
||||
name='name',
|
||||
field=models.CharField(max_length=64),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interfacetemplate',
|
||||
name='name',
|
||||
field=models.CharField(max_length=64),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='poweroutlet',
|
||||
name='name',
|
||||
field=models.CharField(max_length=50),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='poweroutlettemplate',
|
||||
name='name',
|
||||
field=models.CharField(max_length=50),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='powerport',
|
||||
name='name',
|
||||
field=models.CharField(max_length=50),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='powerporttemplate',
|
||||
name='name',
|
||||
field=models.CharField(max_length=50),
|
||||
),
|
||||
]
|
@ -574,7 +574,7 @@ class ConsolePortTemplate(models.Model):
|
||||
A template for a ConsolePort to be created for a new Device.
|
||||
"""
|
||||
device_type = models.ForeignKey('DeviceType', related_name='console_port_templates', on_delete=models.CASCADE)
|
||||
name = models.CharField(max_length=30)
|
||||
name = models.CharField(max_length=50)
|
||||
|
||||
class Meta:
|
||||
ordering = ['device_type', 'name']
|
||||
@ -590,7 +590,7 @@ class ConsoleServerPortTemplate(models.Model):
|
||||
A template for a ConsoleServerPort to be created for a new Device.
|
||||
"""
|
||||
device_type = models.ForeignKey('DeviceType', related_name='cs_port_templates', on_delete=models.CASCADE)
|
||||
name = models.CharField(max_length=30)
|
||||
name = models.CharField(max_length=50)
|
||||
|
||||
class Meta:
|
||||
ordering = ['device_type', 'name']
|
||||
@ -606,7 +606,7 @@ class PowerPortTemplate(models.Model):
|
||||
A template for a PowerPort to be created for a new Device.
|
||||
"""
|
||||
device_type = models.ForeignKey('DeviceType', related_name='power_port_templates', on_delete=models.CASCADE)
|
||||
name = models.CharField(max_length=30)
|
||||
name = models.CharField(max_length=50)
|
||||
|
||||
class Meta:
|
||||
ordering = ['device_type', 'name']
|
||||
@ -622,7 +622,7 @@ class PowerOutletTemplate(models.Model):
|
||||
A template for a PowerOutlet to be created for a new Device.
|
||||
"""
|
||||
device_type = models.ForeignKey('DeviceType', related_name='power_outlet_templates', on_delete=models.CASCADE)
|
||||
name = models.CharField(max_length=30)
|
||||
name = models.CharField(max_length=50)
|
||||
|
||||
class Meta:
|
||||
ordering = ['device_type', 'name']
|
||||
@ -685,7 +685,7 @@ class InterfaceTemplate(models.Model):
|
||||
A template for a physical data interface on a new Device.
|
||||
"""
|
||||
device_type = models.ForeignKey('DeviceType', related_name='interface_templates', on_delete=models.CASCADE)
|
||||
name = models.CharField(max_length=30)
|
||||
name = models.CharField(max_length=64)
|
||||
form_factor = models.PositiveSmallIntegerField(choices=IFACE_FF_CHOICES, default=IFACE_FF_10GE_SFP_PLUS)
|
||||
mgmt_only = models.BooleanField(default=False, verbose_name='Management only')
|
||||
|
||||
@ -705,7 +705,7 @@ class DeviceBayTemplate(models.Model):
|
||||
A template for a DeviceBay to be created for a new parent Device.
|
||||
"""
|
||||
device_type = models.ForeignKey('DeviceType', related_name='device_bay_templates', on_delete=models.CASCADE)
|
||||
name = models.CharField(max_length=30)
|
||||
name = models.CharField(max_length=50)
|
||||
|
||||
class Meta:
|
||||
ordering = ['device_type', 'name']
|
||||
@ -1012,7 +1012,7 @@ class ConsolePort(models.Model):
|
||||
A physical console port within a Device. ConsolePorts connect to ConsoleServerPorts.
|
||||
"""
|
||||
device = models.ForeignKey('Device', related_name='console_ports', on_delete=models.CASCADE)
|
||||
name = models.CharField(max_length=30)
|
||||
name = models.CharField(max_length=50)
|
||||
cs_port = models.OneToOneField('ConsoleServerPort', related_name='connected_console', on_delete=models.SET_NULL,
|
||||
verbose_name='Console server port', blank=True, null=True)
|
||||
connection_status = models.NullBooleanField(choices=CONNECTION_STATUS_CHOICES, default=CONNECTION_STATUS_CONNECTED)
|
||||
@ -1062,7 +1062,7 @@ class ConsoleServerPort(models.Model):
|
||||
A physical port within a Device (typically a designated console server) which provides access to ConsolePorts.
|
||||
"""
|
||||
device = models.ForeignKey('Device', related_name='cs_ports', on_delete=models.CASCADE)
|
||||
name = models.CharField(max_length=30)
|
||||
name = models.CharField(max_length=50)
|
||||
|
||||
objects = ConsoleServerPortManager()
|
||||
|
||||
@ -1083,7 +1083,7 @@ class PowerPort(models.Model):
|
||||
A physical power supply (intake) port within a Device. PowerPorts connect to PowerOutlets.
|
||||
"""
|
||||
device = models.ForeignKey('Device', related_name='power_ports', on_delete=models.CASCADE)
|
||||
name = models.CharField(max_length=30)
|
||||
name = models.CharField(max_length=50)
|
||||
power_outlet = models.OneToOneField('PowerOutlet', related_name='connected_port', on_delete=models.SET_NULL,
|
||||
blank=True, null=True)
|
||||
connection_status = models.NullBooleanField(choices=CONNECTION_STATUS_CHOICES, default=CONNECTION_STATUS_CONNECTED)
|
||||
@ -1127,7 +1127,7 @@ class PowerOutlet(models.Model):
|
||||
A physical power outlet (output) within a Device which provides power to a PowerPort.
|
||||
"""
|
||||
device = models.ForeignKey('Device', related_name='power_outlets', on_delete=models.CASCADE)
|
||||
name = models.CharField(max_length=30)
|
||||
name = models.CharField(max_length=50)
|
||||
|
||||
objects = PowerOutletManager()
|
||||
|
||||
@ -1157,7 +1157,7 @@ class Interface(models.Model):
|
||||
blank=True,
|
||||
verbose_name='Parent LAG'
|
||||
)
|
||||
name = models.CharField(max_length=30)
|
||||
name = models.CharField(max_length=64)
|
||||
form_factor = models.PositiveSmallIntegerField(choices=IFACE_FF_CHOICES, default=IFACE_FF_10GE_SFP_PLUS)
|
||||
enabled = models.BooleanField(default=True)
|
||||
mac_address = MACAddressField(null=True, blank=True, verbose_name='MAC Address')
|
||||
|
@ -16,6 +16,7 @@ from utilities.views import (
|
||||
BulkCreateView, BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
||||
)
|
||||
from . import filters, forms, tables
|
||||
from .constants import IPADDRESS_ROLE_ANYCAST
|
||||
from .models import (
|
||||
Aggregate, IPAddress, PREFIX_STATUS_ACTIVE, PREFIX_STATUS_DEPRECATED, PREFIX_STATUS_RESERVED, Prefix, RIR, Role,
|
||||
Service, VLAN, VLANGroup, VRF,
|
||||
@ -624,6 +625,9 @@ class IPAddressView(View):
|
||||
).select_related(
|
||||
'interface__device', 'nat_inside'
|
||||
)
|
||||
# Exclude anycast IPs if this IP is anycast
|
||||
if ipaddress.role == IPADDRESS_ROLE_ANYCAST:
|
||||
duplicate_ips = duplicate_ips.exclude(role=IPADDRESS_ROLE_ANYCAST)
|
||||
duplicate_ips_table = tables.IPAddressTable(list(duplicate_ips), orderable=False)
|
||||
|
||||
# Related IP table
|
||||
|
@ -93,6 +93,10 @@ MAINTENANCE_MODE = False
|
||||
# all objects by specifying "?limit=0".
|
||||
MAX_PAGE_SIZE = 1000
|
||||
|
||||
# The file path where uploaded media such as image attachments are stored. A trailing slash is not needed. Note that
|
||||
# the default value of this setting is derived from the installed location.
|
||||
# MEDIA_ROOT = '/opt/netbox/netbox/media'
|
||||
|
||||
# Credentials that NetBox will uses to authenticate to devices when connecting via NAPALM.
|
||||
NAPALM_USERNAME = ''
|
||||
NAPALM_PASSWORD = ''
|
||||
|
@ -35,7 +35,7 @@ OBJ_TYPE_CHOICES = (
|
||||
|
||||
class SearchForm(BootstrapMixin, forms.Form):
|
||||
q = forms.CharField(
|
||||
label='Query', widget=forms.TextInput(attrs={'style': 'width: 350px'})
|
||||
label='Search', widget=forms.TextInput(attrs={'style': 'width: 350px'})
|
||||
)
|
||||
obj_type = forms.ChoiceField(
|
||||
choices=OBJ_TYPE_CHOICES, required=False, label='Type'
|
||||
|
@ -13,7 +13,9 @@ except ImportError:
|
||||
)
|
||||
|
||||
|
||||
VERSION = '2.1.3'
|
||||
VERSION = '2.1.4'
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
# Import required configuration parameters
|
||||
ALLOWED_HOSTS = DATABASE = SECRET_KEY = None
|
||||
@ -44,14 +46,15 @@ LOGGING = getattr(configuration, 'LOGGING', {})
|
||||
LOGIN_REQUIRED = getattr(configuration, 'LOGIN_REQUIRED', False)
|
||||
MAINTENANCE_MODE = getattr(configuration, 'MAINTENANCE_MODE', False)
|
||||
MAX_PAGE_SIZE = getattr(configuration, 'MAX_PAGE_SIZE', 1000)
|
||||
PAGINATE_COUNT = getattr(configuration, 'PAGINATE_COUNT', 50)
|
||||
PREFER_IPV4 = getattr(configuration, 'PREFER_IPV4', False)
|
||||
MEDIA_ROOT = getattr(configuration, 'MEDIA_ROOT', os.path.join(BASE_DIR, 'media')).rstrip('/')
|
||||
NAPALM_USERNAME = getattr(configuration, 'NAPALM_USERNAME', '')
|
||||
NAPALM_PASSWORD = getattr(configuration, 'NAPALM_PASSWORD', '')
|
||||
NAPALM_TIMEOUT = getattr(configuration, 'NAPALM_TIMEOUT', 30)
|
||||
NAPALM_ARGS = getattr(configuration, 'NAPALM_ARGS', {})
|
||||
NETBOX_USERNAME = getattr(configuration, 'NETBOX_USERNAME', '') # Deprecated
|
||||
NETBOX_PASSWORD = getattr(configuration, 'NETBOX_PASSWORD', '') # Deprecated
|
||||
PAGINATE_COUNT = getattr(configuration, 'PAGINATE_COUNT', 50)
|
||||
PREFER_IPV4 = getattr(configuration, 'PREFER_IPV4', False)
|
||||
SHORT_DATE_FORMAT = getattr(configuration, 'SHORT_DATE_FORMAT', 'Y-m-d')
|
||||
SHORT_DATETIME_FORMAT = getattr(configuration, 'SHORT_DATETIME_FORMAT', 'Y-m-d H:i')
|
||||
SHORT_TIME_FORMAT = getattr(configuration, 'SHORT_TIME_FORMAT', 'H:i:s')
|
||||
@ -104,8 +107,6 @@ if LDAP_CONFIGURED:
|
||||
"netbox/ldap_config.py to disable LDAP."
|
||||
)
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
# Database
|
||||
configuration.DATABASE.update({'ENGINE': 'django.db.backends.postgresql'})
|
||||
DATABASES = {
|
||||
@ -201,7 +202,6 @@ STATICFILES_DIRS = (
|
||||
)
|
||||
|
||||
# Media
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
||||
MEDIA_URL = '/{}media/'.format(BASE_PATH)
|
||||
|
||||
# Disable default limit of 1000 fields per request. Needed for bulk deletion of objects. (Added in Django 1.10.)
|
||||
|
@ -205,20 +205,18 @@
|
||||
<li class="dropdown{% if request.path|contains:'/circuits/' %} active{% endif %}">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Circuits <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{% url 'circuits:provider_list' %}"><strong>Providers</strong></a></li>
|
||||
{% if perms.circuits.add_provider %}
|
||||
<li class="subnav"><a href="{% url 'circuits:provider_add' %}"><i class="fa fa-plus"></i> Add a Provider</a></li>
|
||||
<li class="subnav"><a href="{% url 'circuits:provider_import' %}"><i class="fa fa-download"></i> Import Providers</a></li>
|
||||
{% endif %}
|
||||
{% if perms.circuits.add_circuit or perms.circuits.add_provider %}
|
||||
<li class="divider"></li>
|
||||
{% endif %}
|
||||
<li><a href="{% url 'circuits:circuit_list' %}"><strong>Circuits</strong></a></li>
|
||||
{% if perms.circuits.add_circuit %}
|
||||
<li class="subnav"><a href="{% url 'circuits:circuit_add' %}"><i class="fa fa-plus"></i> Add a Circuit</a></li>
|
||||
<li class="subnav"><a href="{% url 'circuits:circuit_import' %}"><i class="fa fa-download"></i> Import Circuits</a></li>
|
||||
{% endif %}
|
||||
<li class="divider"></li>
|
||||
<li><a href="{% url 'circuits:provider_list' %}"><strong>Providers</strong></a></li>
|
||||
{% if perms.circuits.add_provider %}
|
||||
<li class="subnav"><a href="{% url 'circuits:provider_add' %}"><i class="fa fa-plus"></i> Add a Provider</a></li>
|
||||
<li class="subnav"><a href="{% url 'circuits:provider_import' %}"><i class="fa fa-download"></i> Import Providers</a></li>
|
||||
{% endif %}
|
||||
<li class="divider"></li>
|
||||
<li><a href="{% url 'circuits:circuittype_list' %}"><strong>Circuit Types</strong></a></li>
|
||||
{% if perms.circuits.add_circuittype %}
|
||||
<li class="subnav"><a href="{% url 'circuits:circuittype_add' %}"><i class="fa fa-plus"></i> Add a Circuit Type</a></li>
|
||||
|
@ -88,7 +88,7 @@
|
||||
<td>Commit Rate</td>
|
||||
<td>
|
||||
{% if circuit.commit_rate %}
|
||||
{{ circuit.commit_rate_human }}
|
||||
{{ circuit.commit_rate|humanize_speed }}
|
||||
{% else %}
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endif %}
|
||||
|
@ -9,7 +9,16 @@
|
||||
{% render_field form.cid %}
|
||||
{% render_field form.type %}
|
||||
{% render_field form.install_date %}
|
||||
{% render_field form.commit_rate %}
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label" for="id_commit_rate">{{ form.commit_rate.label }}</label>
|
||||
<div class="col-md-9">
|
||||
<div class="input-group">
|
||||
{{ form.commit_rate }}
|
||||
{% include 'circuits/inc/speed_widget.html' with target_field='commit_rate' %}
|
||||
</div>
|
||||
<span class="help-block">{{ form.commit_rate.help_text }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% render_field form.description %}
|
||||
</div>
|
||||
</div>
|
||||
@ -35,3 +44,12 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block javascript %}
|
||||
<script type="text/javascript">
|
||||
$("a.set_speed").click(function(e) {
|
||||
e.preventDefault();
|
||||
$("#id_" + $(this).attr("target")).val($(this).attr("data"));
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
@ -49,8 +49,26 @@
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><strong>Termination Details</strong></div>
|
||||
<div class="panel-body">
|
||||
{% render_field form.port_speed %}
|
||||
{% render_field form.upstream_speed %}
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label required" for="id_port_speed">{{ form.port_speed.label }}</label>
|
||||
<div class="col-md-9">
|
||||
<div class="input-group">
|
||||
{{ form.port_speed }}
|
||||
{% include 'circuits/inc/speed_widget.html' with target_field='port_speed' %}
|
||||
</div>
|
||||
<span class="help-block">{{ form.port_speed.help_text }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label" for="id_upstream_speed">{{ form.upstream_speed.label }}</label>
|
||||
<div class="col-md-9">
|
||||
<div class="input-group">
|
||||
{{ form.upstream_speed }}
|
||||
{% include 'circuits/inc/speed_widget.html' with target_field='upstream_speed' %}
|
||||
</div>
|
||||
<span class="help-block">{{ form.upstream_speed.help_text }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% render_field form.xconnect_id %}
|
||||
{% render_field form.pp_info %}
|
||||
</div>
|
||||
@ -72,4 +90,10 @@
|
||||
|
||||
{% block javascript %}
|
||||
<script src="{% static 'js/livesearch.js' %}?v{{ settings.VERSION }}"></script>
|
||||
<script type="text/javascript">
|
||||
$("a.set_speed").click(function(e) {
|
||||
e.preventDefault();
|
||||
$("#id_" + $(this).attr("target")).val($(this).attr("data"));
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
@ -1,3 +1,5 @@
|
||||
{% load helpers %}
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<div class="pull-right">
|
||||
@ -49,10 +51,10 @@
|
||||
<td>Speed</td>
|
||||
<td>
|
||||
{% if termination.upstream_speed %}
|
||||
<i class="fa fa-arrow-down" title="Downstream"></i> {{ termination.port_speed_human }}
|
||||
<i class="fa fa-arrow-up" title="Upstream"></i> {{ termination.upstream_speed_human }}
|
||||
<i class="fa fa-arrow-down" title="Downstream"></i> {{ termination.port_speed|humanize_speed }}
|
||||
<i class="fa fa-arrow-up" title="Upstream"></i> {{ termination.upstream_speed|humanize_speed }}
|
||||
{% else %}
|
||||
{{ termination.port_speed_human }}
|
||||
{{ termination.port_speed|humanize_speed }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
17
netbox/templates/circuits/inc/speed_widget.html
Normal file
17
netbox/templates/circuits/inc/speed_widget.html
Normal file
@ -0,0 +1,17 @@
|
||||
<span class="input-group-btn">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right">
|
||||
<li><a href="#" target="{{ target_field }}" data="10000" class="set_speed">10 Mbps</a></li>
|
||||
<li><a href="#" target="{{ target_field }}" data="100000" class="set_speed">100 Mbps</a></li>
|
||||
<li><a href="#" target="{{ target_field }}" data="1000000" class="set_speed">1 Gbps</a></li>
|
||||
<li><a href="#" target="{{ target_field }}" data="10000000" class="set_speed">10 Gbps</a></li>
|
||||
<li><a href="#" target="{{ target_field }}" data="25000000" class="set_speed">25 Gbps</a></li>
|
||||
<li><a href="#" target="{{ target_field }}" data="40000000" class="set_speed">40 Gbps</a></li>
|
||||
<li><a href="#" target="{{ target_field }}" data="100000000" class="set_speed">100 Gbps</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="#" target="{{ target_field }}" data="1544" class="set_speed">T1 (1.544 Mbps)</a></li>
|
||||
<li><a href="#" target="{{ target_field }}" data="2048" class="set_speed">E1 (2.048 Mbps)</a></li>
|
||||
</ul>
|
||||
</span>
|
@ -80,13 +80,19 @@ $(document).ready(function() {
|
||||
$('#model').html(json['get_facts']['model']);
|
||||
$('#serial_number').html(json['get_facts']['serial_number']);
|
||||
$('#os_version').html(json['get_facts']['os_version']);
|
||||
$('#uptime').html(json['get_facts']['uptime']);
|
||||
// Calculate uptime
|
||||
var uptime = json['get_facts']['uptime'];
|
||||
console.log(uptime);
|
||||
var uptime_days = Math.floor(uptime / 86400);
|
||||
var uptime_hours = Math.floor(uptime % 86400 / 3600);
|
||||
var uptime_minutes = Math.floor(uptime % 3600 / 60);
|
||||
$('#uptime').html(uptime_days + "d " + uptime_hours + "h " + uptime_minutes + "m");
|
||||
$.each(json['get_environment']['cpu'], function(name, obj) {
|
||||
var row="<tr><td>" + name + "</td><td>" + obj['%usage'] + "%</td></tr>";
|
||||
$("#cpu").after(row)
|
||||
});
|
||||
$('#memory').after("<tr><td>Used</td><td>" + json['get_environment']['memory']['used_ram'] + "MB</td></tr>");
|
||||
$('#memory').after("<tr><td>Available</td><td>" + json['get_environment']['memory']['available_ram'] + "MB</td></tr>");
|
||||
$('#memory').after("<tr><td>Used</td><td>" + json['get_environment']['memory']['used_ram'] + "</td></tr>");
|
||||
$('#memory').after("<tr><td>Available</td><td>" + json['get_environment']['memory']['available_ram'] + "</td></tr>");
|
||||
$.each(json['get_environment']['temperature'], function(name, obj) {
|
||||
var style = "success";
|
||||
if (obj['is_alert']) {
|
||||
|
@ -24,7 +24,7 @@
|
||||
{% empty %}
|
||||
{% if table.empty_text %}
|
||||
<tr>
|
||||
<td colspan="{{ table.columns|length }}">{{ table.empty_text }}</td>
|
||||
<td colspan="{{ table.columns|length }}" class="text-center text-muted">— {{ table.empty_text }} —</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
@ -83,7 +83,11 @@
|
||||
<tr>
|
||||
<td>Role</td>
|
||||
<td>
|
||||
{% if ipaddress.role %}
|
||||
<a href="{% url 'ipam:ipaddress_list' %}?role={{ ipaddress.role }}">{{ ipaddress.get_role_display }}</a>
|
||||
{% else %}
|
||||
<span class="text-muted">None</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -1,70 +0,0 @@
|
||||
{% extends '_base.html' %}
|
||||
{% load static from staticfiles %}
|
||||
{% load form_helpers %}
|
||||
|
||||
{% block content %}
|
||||
<form action="." method="post" class="form form-horizontal">
|
||||
{% csrf_token %}
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-md-offset-3">
|
||||
{% if form.non_field_errors %}
|
||||
<div class="panel panel-danger">
|
||||
<div class="panel-heading"><strong>Errors</strong></div>
|
||||
<div class="panel-body">
|
||||
{{ form.non_field_errors }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong>{% block title %}Assign an IP Address{% endblock %}</strong>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label">IP Address</label>
|
||||
<div class="col-md-9">
|
||||
<p class="form-control-static">{{ ipaddress }}</p>
|
||||
</div>
|
||||
<label class="col-md-3 control-label">VRF</label>
|
||||
<div class="col-md-9">
|
||||
<p class="form-control-static">
|
||||
{% if ipaddress.vrf %}
|
||||
<a href="{% url 'ipam:vrf' pk=ipaddress.vrf.pk %}">{{ ipaddress.vrf }}</a> ({{ ipaddress.vrf.rd }})
|
||||
{% else %}
|
||||
<span>Global</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li role="presentation" class="active"><a href="#search" aria-controls="search" role="tab" data-toggle="tab">Search</a></li>
|
||||
<li role="presentation"><a href="#select" aria-controls="home" role="tab" data-toggle="tab">Select</a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="search">
|
||||
{% render_field form.livesearch %}
|
||||
</div>
|
||||
<div class="tab-pane" id="select">
|
||||
{% render_field form.site %}
|
||||
{% render_field form.rack %}
|
||||
{% render_field form.device %}
|
||||
</div>
|
||||
</div>
|
||||
{% render_field form.interface %}
|
||||
{% render_field form.set_as_primary %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-md-9 col-md-offset-3">
|
||||
<button type="submit" name="_assign" class="btn btn-primary">Assign</button>
|
||||
<a href="{{ return_url }}" class="btn btn-default">Cancel</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block javascript %}
|
||||
<script src="{% static 'js/livesearch.js' %}?v{{ settings.VERSION }}"></script>
|
||||
{% endblock %}
|
@ -1,8 +0,0 @@
|
||||
{% extends 'utilities/confirmation_form.html' %}
|
||||
{% load form_helpers %}
|
||||
|
||||
{% block title %}Remove {{ ipaddress }} from {{ ipaddress.interface }}?{% endblock %}
|
||||
|
||||
{% block message %}
|
||||
<p>Are you sure you want to remove this IP address from <strong>{{ ipaddress.interface.device }} {{ ipaddress.interface }}</strong>?</p>
|
||||
{% endblock %}
|
@ -3,7 +3,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
{% if perms.dcim.add_devicerole %}
|
||||
{% if perms.ipam.add_role %}
|
||||
<a href="{% url 'ipam:role_add' %}" class="btn btn-primary">
|
||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||
Add a role
|
||||
|
@ -85,7 +85,13 @@ class ValidatedModelSerializer(ModelSerializer):
|
||||
"""
|
||||
Extends the built-in ModelSerializer to enforce calling clean() on the associated model during validation.
|
||||
"""
|
||||
def validate(self, attrs):
|
||||
def validate(self, data):
|
||||
|
||||
# Remove custom field data (if any) prior to model validation
|
||||
attrs = data.copy()
|
||||
attrs.pop('custom_fields', None)
|
||||
|
||||
# Run clean() on an instance of the model
|
||||
if self.instance is None:
|
||||
instance = self.Meta.model(**attrs)
|
||||
else:
|
||||
@ -93,7 +99,8 @@ class ValidatedModelSerializer(ModelSerializer):
|
||||
for k, v in attrs.items():
|
||||
setattr(instance, k, v)
|
||||
instance.clean()
|
||||
return attrs
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class ChoiceFieldSerializer(Field):
|
||||
|
@ -7,9 +7,10 @@ from mptt.forms import TreeNodeMultipleChoiceField
|
||||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.core.validators import URLValidator
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
from .validators import EnhancedURLValidator
|
||||
|
||||
|
||||
COLOR_CHOICES = (
|
||||
('aa1409', 'Dark red'),
|
||||
@ -431,17 +432,11 @@ class FilterTreeNodeMultipleChoiceField(FilterChoiceFieldMixin, TreeNodeMultiple
|
||||
|
||||
class LaxURLField(forms.URLField):
|
||||
"""
|
||||
Custom URLField which allows any valid URL scheme
|
||||
Modifies Django's built-in URLField in two ways:
|
||||
1) Allow any valid scheme per RFC 3986 section 3.1
|
||||
2) Remove the requirement for fully-qualified domain names (e.g. http://myserver/ is valid)
|
||||
"""
|
||||
|
||||
class AnyURLScheme(object):
|
||||
# A fake URL list which "contains" all scheme names abiding by the syntax defined in RFC 3986 section 3.1
|
||||
def __contains__(self, item):
|
||||
if not item or not re.match('^[a-z][0-9a-z+\-.]*$', item.lower()):
|
||||
return False
|
||||
return True
|
||||
|
||||
default_validators = [URLValidator(schemes=AnyURLScheme())]
|
||||
default_validators = [EnhancedURLValidator()]
|
||||
|
||||
|
||||
#
|
||||
|
@ -14,7 +14,7 @@ class BaseTable(tables.Table):
|
||||
|
||||
# Set default empty_text if none was provided
|
||||
if self.empty_text is None:
|
||||
self.empty_text = 'No {} found.'.format(self._meta.model._meta.verbose_name_plural)
|
||||
self.empty_text = 'No {} found'.format(self._meta.model._meta.verbose_name_plural)
|
||||
|
||||
class Meta:
|
||||
attrs = {
|
||||
|
@ -62,6 +62,27 @@ def bettertitle(value):
|
||||
return ' '.join([w[0].upper() + w[1:] for w in value.split()])
|
||||
|
||||
|
||||
@register.filter()
|
||||
def humanize_speed(speed):
|
||||
"""
|
||||
Humanize speeds given in Kbps. Examples:
|
||||
|
||||
1544 => "1.544 Mbps"
|
||||
100000 => "100 Mbps"
|
||||
10000000 => "10 Gbps"
|
||||
"""
|
||||
if speed >= 1000000000 and speed % 1000000000 == 0:
|
||||
return '{} Tbps'.format(int(speed / 1000000000))
|
||||
elif speed >= 1000000 and speed % 1000000 == 0:
|
||||
return '{} Gbps'.format(int(speed / 1000000))
|
||||
elif speed >= 1000 and speed % 1000 == 0:
|
||||
return '{} Mbps'.format(int(speed / 1000))
|
||||
elif speed >= 1000:
|
||||
return '{} Mbps'.format(float(speed) / 1000)
|
||||
else:
|
||||
return '{} Kbps'.format(speed)
|
||||
|
||||
|
||||
@register.filter()
|
||||
def example_choices(field, arg=3):
|
||||
"""
|
||||
|
30
netbox/utilities/validators.py
Normal file
30
netbox/utilities/validators.py
Normal file
@ -0,0 +1,30 @@
|
||||
from __future__ import unicode_literals
|
||||
import re
|
||||
|
||||
from django.core.validators import _lazy_re_compile, URLValidator
|
||||
|
||||
|
||||
class EnhancedURLValidator(URLValidator):
|
||||
"""
|
||||
Extends Django's built-in URLValidator to permit the use of hostnames with no domain extension.
|
||||
"""
|
||||
|
||||
class AnyURLScheme(object):
|
||||
"""
|
||||
A fake URL list which "contains" all scheme names abiding by the syntax defined in RFC 3986 section 3.1
|
||||
"""
|
||||
def __contains__(self, item):
|
||||
if not item or not re.match('^[a-z][0-9a-z+\-.]*$', item.lower()):
|
||||
return False
|
||||
return True
|
||||
|
||||
fqdn_re = URLValidator.hostname_re + URLValidator.domain_re + URLValidator.tld_re
|
||||
host_res = [URLValidator.ipv4_re, URLValidator.ipv6_re, fqdn_re, URLValidator.hostname_re]
|
||||
regex = _lazy_re_compile(
|
||||
r'^(?:[a-z0-9\.\-\+]*)://' # Scheme (previously enforced by AnyURLScheme or schemes kwarg)
|
||||
r'(?:\S+(?::\S*)?@)?' # HTTP basic authentication
|
||||
r'(?:' + '|'.join(host_res) + ')' # IPv4, IPv6, FQDN, or hostname
|
||||
r'(?::\d{2,5})?' # Port number
|
||||
r'(?:[/?#][^\s]*)?' # Path
|
||||
r'\Z', re.IGNORECASE)
|
||||
schemes = AnyURLScheme()
|
Loading…
Reference in New Issue
Block a user