mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-24 17:38:37 -06:00
commit
300aff71bb
@ -27,9 +27,10 @@ IRC.
|
|||||||
|
|
||||||
## Feature Requests
|
## Feature Requests
|
||||||
|
|
||||||
* First, check the [issues list](https://github.com/digitalocean/netbox/issues) to see if the feature you'd like to see
|
* First, check the [issues list](https://github.com/digitalocean/netbox/issues) to see if the feature you're requesting
|
||||||
has already been requested (and possibly rejected). If it is, be sure to comment with a "+1" and any additional
|
has already been requested (and possibly rejected). If it has, click "add a reaction" in the top right corner of the
|
||||||
justification you have for the feature.
|
issue and add a thumbs up (+1). This ensures that the issue has a better chance of making it onto the roadmap. Also feel
|
||||||
|
free to add a comment with any additional justification for the feature.
|
||||||
|
|
||||||
* While discussion of new features is welcome, it's important to limit the scope of NetBox's feature set to avoid
|
* While discussion of new features is welcome, it's important to limit the scope of NetBox's feature set to avoid
|
||||||
feature creep. For example, the following features would be firmly out of scope for NetBox:
|
feature creep. For example, the following features would be firmly out of scope for NetBox:
|
||||||
|
@ -4,6 +4,8 @@ NetBox is an IP address management (IPAM) and data center infrastructure managem
|
|||||||
|
|
||||||
NetBox runs as a web application atop the [Django](https://www.djangoproject.com/) Python framework with a [PostgreSQL](http://www.postgresql.org/) database. For a complete list of requirements, see `requirements.txt`. The code is available [on GitHub](https://github.com/digitalocean/netbox).
|
NetBox runs as a web application atop the [Django](https://www.djangoproject.com/) Python framework with a [PostgreSQL](http://www.postgresql.org/) database. For a complete list of requirements, see `requirements.txt`. The code is available [on GitHub](https://github.com/digitalocean/netbox).
|
||||||
|
|
||||||
|
The complete documentation for Netbox can be found at [Read the Docs](http://netbox.readthedocs.io/en/latest/).
|
||||||
|
|
||||||
Questions? Comments? Please join us on IRC in **#netbox** on **irc.freenode.net**!
|
Questions? Comments? Please join us on IRC in **#netbox** on **irc.freenode.net**!
|
||||||
|
|
||||||
### Build Status
|
### Build Status
|
||||||
|
@ -13,11 +13,24 @@ ADMINS = [
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## BANNER_TOP
|
||||||
|
|
||||||
|
## BANNER_BOTTOM
|
||||||
|
|
||||||
|
Setting these variables will display content in a banner at the top and/or bottom of the page, respectively. To replicate the content of the top banner in the bottom banner, set:
|
||||||
|
|
||||||
|
```
|
||||||
|
BANNER_TOP = 'Your banner text'
|
||||||
|
BANNER_BOTTOM = BANNER_TOP
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## DEBUG
|
## DEBUG
|
||||||
|
|
||||||
Default: False
|
Default: False
|
||||||
|
|
||||||
This setting enables debugging. This should be done only during development or troubleshooting. Never enable debugging on a production system, as it can expose sensitive data to unauthenticated users.
|
This setting enables debugging. This should be done only during development or troubleshooting. Never enable debugging on a production system, as it can expose sensitive data to unauthenticated users.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -66,6 +79,14 @@ Determine how many objects to display per page within each list of objects.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## PREFER_IPV4
|
||||||
|
|
||||||
|
Default: False
|
||||||
|
|
||||||
|
When determining the primary IP address for a device, IPv6 is preferred over IPv4 by default. Set this to True to prefer IPv4 instead.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## TIME_ZONE
|
## TIME_ZONE
|
||||||
|
|
||||||
Default: UTC
|
Default: UTC
|
||||||
|
@ -4,42 +4,40 @@ As with the initial installation, you can upgrade NetBox by either downloading t
|
|||||||
|
|
||||||
## Option A: Download a Release
|
## Option A: Download a Release
|
||||||
|
|
||||||
Download the [latest stable release](https://github.com/digitalocean/netbox/releases) from GitHub as a tarball or ZIP archive. Extract it to your desired path. In this example, we'll use `/opt/netbox`. For this guide we are using 1.0.4 as the old version and 1.0.7 as the new version.
|
Download the [latest stable release](https://github.com/digitalocean/netbox/releases) from GitHub as a tarball or ZIP archive. Extract it to your desired path. In this example, we'll use `/opt/netbox`.
|
||||||
|
|
||||||
|
Download and extract the latest version:
|
||||||
|
|
||||||
Download & extract latest version:
|
|
||||||
```
|
```
|
||||||
# wget https://github.com/digitalocean/netbox/archive/vX.Y.Z.tar.gz
|
# wget https://github.com/digitalocean/netbox/archive/vX.Y.Z.tar.gz
|
||||||
# tar -xzf vX.Y.Z.tar.gz -C /opt
|
# tar -xzf vX.Y.Z.tar.gz -C /opt
|
||||||
# cd /opt/
|
# cd /opt/
|
||||||
# ln -sf netbox-1.0.7/ netbox
|
# ln -sf netbox-X.Y.Z/ netbox
|
||||||
```
|
```
|
||||||
|
|
||||||
Copy the 'configuration.py' you created when first installing to the new version:
|
Copy the 'configuration.py' you created when first installing to the new version:
|
||||||
|
|
||||||
```
|
```
|
||||||
# cp /opt/netbox-1.0.4/configuration.py /opt/netbox/configuration.py
|
# cp /opt/netbox-X.Y.Z/configuration.py /opt/netbox/configuration.py
|
||||||
|
```
|
||||||
|
|
||||||
|
If you followed the original installation guide to set up gunicorn, be sure to copy its configuration as well:
|
||||||
|
|
||||||
|
```
|
||||||
|
# cp /opt/netbox-X.Y.Z/gunicorn_config.py /opt/netbox/gunicorn_config.py
|
||||||
```
|
```
|
||||||
|
|
||||||
## Option B: Clone the Git Repository (latest master release)
|
## Option B: Clone the Git Repository (latest master release)
|
||||||
|
|
||||||
For this guide, we'll use `/opt/netbox`.
|
This guide assumes that NetBox is installed at `/opt/netbox`. Pull down the most recent iteration of the master branch:
|
||||||
|
|
||||||
Check that your git branch is up to date & is set to master:
|
|
||||||
```
|
```
|
||||||
# cd /opt/netbox
|
# cd /opt/netbox
|
||||||
# git status
|
|
||||||
```
|
|
||||||
|
|
||||||
If not on branch master, set it and verify status:
|
|
||||||
```
|
|
||||||
# git checkout master
|
# git checkout master
|
||||||
|
# git pull origin master
|
||||||
# git status
|
# git status
|
||||||
```
|
```
|
||||||
|
|
||||||
Pull down the set branch from git status above:
|
|
||||||
```
|
|
||||||
# git pull
|
|
||||||
```
|
|
||||||
|
|
||||||
# Run the Upgrade Script
|
# Run the Upgrade Script
|
||||||
|
|
||||||
Once the new code is in place, run the upgrade script (which may need to be run as root depending on how your environment is configured).
|
Once the new code is in place, run the upgrade script (which may need to be run as root depending on how your environment is configured).
|
||||||
|
@ -1 +1 @@
|
|||||||
default_app_config = 'dcim.apps.IPAMConfig'
|
default_app_config = 'dcim.apps.DCIMConfig'
|
||||||
|
@ -180,4 +180,4 @@ class DeviceAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
def get_queryset(self, request):
|
def get_queryset(self, request):
|
||||||
qs = super(DeviceAdmin, self).get_queryset(request)
|
qs = super(DeviceAdmin, self).get_queryset(request)
|
||||||
return qs.select_related('device_type__manufacturer', 'device_role', 'primary_ip', 'rack')
|
return qs.select_related('device_type__manufacturer', 'device_role', 'primary_ip4', 'primary_ip6', 'rack')
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class IPAMConfig(AppConfig):
|
class DCIMConfig(AppConfig):
|
||||||
name = "dcim"
|
name = "dcim"
|
||||||
verbose_name = "DCIM"
|
verbose_name = "DCIM"
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.core.validators import MinValueValidator
|
from django.core.validators import MinValueValidator
|
||||||
@ -713,7 +714,9 @@ class Device(CreatedUpdatedModel):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def primary_ip(self):
|
def primary_ip(self):
|
||||||
if self.primary_ip6:
|
if settings.PREFER_IPV4 and self.primary_ip4:
|
||||||
|
return self.primary_ip4
|
||||||
|
elif self.primary_ip6:
|
||||||
return self.primary_ip6
|
return self.primary_ip6
|
||||||
elif self.primary_ip4:
|
elif self.primary_ip4:
|
||||||
return self.primary_ip4
|
return self.primary_ip4
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
|
from natsort import natsorted
|
||||||
|
from operator import attrgetter
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import permission_required
|
from django.contrib.auth.decorators import permission_required
|
||||||
@ -259,13 +261,22 @@ def devicetype(request, pk):
|
|||||||
devicetype = get_object_or_404(DeviceType, pk=pk)
|
devicetype = get_object_or_404(DeviceType, pk=pk)
|
||||||
|
|
||||||
# Component tables
|
# Component tables
|
||||||
consoleport_table = tables.ConsolePortTemplateTable(ConsolePortTemplate.objects.filter(device_type=devicetype))
|
consoleport_table = tables.ConsolePortTemplateTable(
|
||||||
consoleserverport_table = tables.ConsoleServerPortTemplateTable(ConsoleServerPortTemplate.objects
|
natsorted(ConsolePortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name'))
|
||||||
.filter(device_type=devicetype))
|
)
|
||||||
powerport_table = tables.PowerPortTemplateTable(PowerPortTemplate.objects.filter(device_type=devicetype))
|
consoleserverport_table = tables.ConsoleServerPortTemplateTable(
|
||||||
poweroutlet_table = tables.PowerOutletTemplateTable(PowerOutletTemplate.objects.filter(device_type=devicetype))
|
natsorted(ConsoleServerPortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name'))
|
||||||
|
)
|
||||||
|
powerport_table = tables.PowerPortTemplateTable(
|
||||||
|
natsorted(PowerPortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name'))
|
||||||
|
)
|
||||||
|
poweroutlet_table = tables.PowerOutletTemplateTable(
|
||||||
|
natsorted(PowerOutletTemplate.objects.filter(device_type=devicetype), key=attrgetter('name'))
|
||||||
|
)
|
||||||
interface_table = tables.InterfaceTemplateTable(InterfaceTemplate.objects.filter(device_type=devicetype))
|
interface_table = tables.InterfaceTemplateTable(InterfaceTemplate.objects.filter(device_type=devicetype))
|
||||||
devicebay_table = tables.DeviceBayTemplateTable(DeviceBayTemplate.objects.filter(device_type=devicetype))
|
devicebay_table = tables.DeviceBayTemplateTable(
|
||||||
|
natsorted(DeviceBayTemplate.objects.filter(device_type=devicetype), key=attrgetter('name'))
|
||||||
|
)
|
||||||
if request.user.has_perm('dcim.change_devicetype'):
|
if request.user.has_perm('dcim.change_devicetype'):
|
||||||
consoleport_table.base_columns['pk'].visible = True
|
consoleport_table.base_columns['pk'].visible = True
|
||||||
consoleserverport_table.base_columns['pk'].visible = True
|
consoleserverport_table.base_columns['pk'].visible = True
|
||||||
@ -513,15 +524,26 @@ class DeviceListView(ObjectListView):
|
|||||||
def device(request, pk):
|
def device(request, pk):
|
||||||
|
|
||||||
device = get_object_or_404(Device, pk=pk)
|
device = get_object_or_404(Device, pk=pk)
|
||||||
console_ports = ConsolePort.objects.filter(device=device).select_related('cs_port__device')
|
console_ports = natsorted(
|
||||||
cs_ports = ConsoleServerPort.objects.filter(device=device).select_related('connected_console')
|
ConsolePort.objects.filter(device=device).select_related('cs_port__device'), key=attrgetter('name')
|
||||||
power_ports = PowerPort.objects.filter(device=device).select_related('power_outlet__device')
|
)
|
||||||
power_outlets = PowerOutlet.objects.filter(device=device).select_related('connected_port')
|
cs_ports = natsorted(
|
||||||
|
ConsoleServerPort.objects.filter(device=device).select_related('connected_console'), key=attrgetter('name')
|
||||||
|
)
|
||||||
|
power_ports = natsorted(
|
||||||
|
PowerPort.objects.filter(device=device).select_related('power_outlet__device'), key=attrgetter('name')
|
||||||
|
)
|
||||||
|
power_outlets = natsorted(
|
||||||
|
PowerOutlet.objects.filter(device=device).select_related('connected_port'), key=attrgetter('name')
|
||||||
|
)
|
||||||
interfaces = Interface.objects.filter(device=device, mgmt_only=False)\
|
interfaces = Interface.objects.filter(device=device, mgmt_only=False)\
|
||||||
.select_related('connected_as_a', 'connected_as_b', 'circuit')
|
.select_related('connected_as_a', 'connected_as_b', 'circuit')
|
||||||
mgmt_interfaces = Interface.objects.filter(device=device, mgmt_only=True)\
|
mgmt_interfaces = Interface.objects.filter(device=device, mgmt_only=True)\
|
||||||
.select_related('connected_as_a', 'connected_as_b', 'circuit')
|
.select_related('connected_as_a', 'connected_as_b', 'circuit')
|
||||||
device_bays = DeviceBay.objects.filter(device=device).select_related('installed_device__device_type__manufacturer')
|
device_bays = natsorted(
|
||||||
|
DeviceBay.objects.filter(device=device).select_related('installed_device__device_type__manufacturer'),
|
||||||
|
key=attrgetter('name')
|
||||||
|
)
|
||||||
|
|
||||||
# Gather any secrets which belong to this device
|
# Gather any secrets which belong to this device
|
||||||
secrets = device.secrets.all()
|
secrets = device.secrets.all()
|
||||||
|
@ -123,6 +123,8 @@ class Aggregate(CreatedUpdatedModel):
|
|||||||
|
|
||||||
# Ensure that the aggregate being added does not cover an existing aggregate
|
# Ensure that the aggregate being added does not cover an existing aggregate
|
||||||
covered_aggregates = Aggregate.objects.filter(prefix__net_contained=str(self.prefix))
|
covered_aggregates = Aggregate.objects.filter(prefix__net_contained=str(self.prefix))
|
||||||
|
if self.pk:
|
||||||
|
covered_aggregates = covered_aggregates.exclude(pk=self.pk)
|
||||||
if covered_aggregates:
|
if covered_aggregates:
|
||||||
raise ValidationError("{} is overlaps with an existing aggregate ({})"
|
raise ValidationError("{} is overlaps with an existing aggregate ({})"
|
||||||
.format(self.prefix, covered_aggregates[0]))
|
.format(self.prefix, covered_aggregates[0]))
|
||||||
|
@ -78,3 +78,7 @@ SHORT_DATETIME_FORMAT = 'Y-m-d H:i'
|
|||||||
# banners, define BANNER_TOP and set BANNER_BOTTOM = BANNER_TOP.
|
# banners, define BANNER_TOP and set BANNER_BOTTOM = BANNER_TOP.
|
||||||
BANNER_TOP = ''
|
BANNER_TOP = ''
|
||||||
BANNER_BOTTOM = ''
|
BANNER_BOTTOM = ''
|
||||||
|
|
||||||
|
# When determining the primary IP address for a device, IPv6 is preferred over IPv4 by default. Set this to True to
|
||||||
|
# prefer IPv4 instead.
|
||||||
|
PREFER_IPV4 = False
|
||||||
|
@ -12,7 +12,7 @@ except ImportError:
|
|||||||
"the documentation.")
|
"the documentation.")
|
||||||
|
|
||||||
|
|
||||||
VERSION = '1.2.0'
|
VERSION = '1.2.1'
|
||||||
|
|
||||||
# Import local configuration
|
# Import local configuration
|
||||||
for setting in ['ALLOWED_HOSTS', 'DATABASE', 'SECRET_KEY']:
|
for setting in ['ALLOWED_HOSTS', 'DATABASE', 'SECRET_KEY']:
|
||||||
@ -40,6 +40,7 @@ DATETIME_FORMAT = getattr(configuration, 'DATETIME_FORMAT', 'N j, Y g:i a')
|
|||||||
SHORT_DATETIME_FORMAT = getattr(configuration, 'SHORT_DATETIME_FORMAT', 'Y-m-d H:i')
|
SHORT_DATETIME_FORMAT = getattr(configuration, 'SHORT_DATETIME_FORMAT', 'Y-m-d H:i')
|
||||||
BANNER_TOP = getattr(configuration, 'BANNER_TOP', False)
|
BANNER_TOP = getattr(configuration, 'BANNER_TOP', False)
|
||||||
BANNER_BOTTOM = getattr(configuration, 'BANNER_BOTTOM', False)
|
BANNER_BOTTOM = getattr(configuration, 'BANNER_BOTTOM', False)
|
||||||
|
PREFER_IPV4 = getattr(configuration, 'PREFER_IPV4', False)
|
||||||
CSRF_TRUSTED_ORIGINS = ALLOWED_HOSTS
|
CSRF_TRUSTED_ORIGINS = ALLOWED_HOSTS
|
||||||
|
|
||||||
# Attempt to import LDAP configuration if it has been defined
|
# Attempt to import LDAP configuration if it has been defined
|
||||||
|
@ -42,7 +42,7 @@ class SecretListView(generics.GenericAPIView):
|
|||||||
"""
|
"""
|
||||||
List secrets (filterable). If a private key is POSTed, attempt to decrypt each Secret.
|
List secrets (filterable). If a private key is POSTed, attempt to decrypt each Secret.
|
||||||
"""
|
"""
|
||||||
queryset = Secret.objects.select_related('device__primary_ip', 'role')\
|
queryset = Secret.objects.select_related('device__primary_ip4', 'device__primary_ip6', 'role')\
|
||||||
.prefetch_related('role__users', 'role__groups')
|
.prefetch_related('role__users', 'role__groups')
|
||||||
serializer_class = serializers.SecretSerializer
|
serializer_class = serializers.SecretSerializer
|
||||||
filter_class = SecretFilter
|
filter_class = SecretFilter
|
||||||
@ -87,7 +87,7 @@ class SecretDetailView(generics.GenericAPIView):
|
|||||||
"""
|
"""
|
||||||
Retrieve a single Secret. If a private key is POSTed, attempt to decrypt the Secret.
|
Retrieve a single Secret. If a private key is POSTed, attempt to decrypt the Secret.
|
||||||
"""
|
"""
|
||||||
queryset = Secret.objects.select_related('device__primary_ip', 'role')\
|
queryset = Secret.objects.select_related('device__primary_ip4', 'device__primary_ip6', 'role')\
|
||||||
.prefetch_related('role__users', 'role__groups')
|
.prefetch_related('role__users', 'role__groups')
|
||||||
serializer_class = serializers.SecretSerializer
|
serializer_class = serializers.SecretSerializer
|
||||||
renderer_classes = [FormlessBrowsableAPIRenderer, JSONRenderer, FreeRADIUSClientsRenderer]
|
renderer_classes = [FormlessBrowsableAPIRenderer, JSONRenderer, FreeRADIUSClientsRenderer]
|
||||||
|
@ -15,3 +15,4 @@ py-gfm==0.1.3
|
|||||||
pycrypto==2.6.1
|
pycrypto==2.6.1
|
||||||
sqlparse==0.1.19
|
sqlparse==0.1.19
|
||||||
xmltodict==0.10.2
|
xmltodict==0.10.2
|
||||||
|
natsort>=5.0.0
|
||||||
|
Loading…
Reference in New Issue
Block a user