mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-24 17:38:37 -06:00
commit
7336fdf162
@ -6,7 +6,7 @@ NetBox runs as a web application atop the [Django](https://www.djangoproject.com
|
|||||||
|
|
||||||
The complete documentation for Netbox can be found at [Read the Docs](http://netbox.readthedocs.io/en/latest/).
|
The complete documentation for Netbox can be found at [Read the Docs](http://netbox.readthedocs.io/en/latest/).
|
||||||
|
|
||||||
Questions? Comments? Please subscribe to [the netbox-disucss mailing list](https://groups.google.com/forum/#!forum/netbox-discuss), or join us on IRC in **#netbox** on **irc.freenode.net**!
|
Questions? Comments? Please subscribe to [the netbox-discuss mailing list](https://groups.google.com/forum/#!forum/netbox-discuss), or join us on IRC in **#netbox** on **irc.freenode.net**!
|
||||||
|
|
||||||
### Build Status
|
### Build Status
|
||||||
|
|
||||||
|
@ -26,6 +26,18 @@ BANNER_BOTTOM = BANNER_TOP
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## BASE_PATH
|
||||||
|
|
||||||
|
Default: None
|
||||||
|
|
||||||
|
The base URL path to use when accessing NetBox. Do not include the scheme or domain name. For example, if installed at http://example.com/netbox/, set:
|
||||||
|
|
||||||
|
```
|
||||||
|
BASE_PATH = 'netbox/'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## DEBUG
|
## DEBUG
|
||||||
|
|
||||||
Default: False
|
Default: False
|
||||||
|
@ -1,19 +1,16 @@
|
|||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
NetBox requires following system dependencies:
|
**Debian/Ubuntu**
|
||||||
|
|
||||||
* python2.7
|
|
||||||
* python-dev
|
|
||||||
* python-pip
|
|
||||||
* libxml2-dev
|
|
||||||
* libxslt1-dev
|
|
||||||
* libffi-dev
|
|
||||||
* graphviz
|
|
||||||
* libpq-dev
|
|
||||||
* libssl-dev
|
|
||||||
|
|
||||||
```
|
```
|
||||||
# sudo apt-get install -y python2.7 python-dev python-pip libxml2-dev libxslt1-dev libffi-dev graphviz libpq-dev libssl-dev
|
# apt-get install -y python2.7 python-dev python-pip libxml2-dev libxslt1-dev libffi-dev graphviz libpq-dev libssl-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
**CentOS/RHEL**
|
||||||
|
|
||||||
|
```
|
||||||
|
# yum install -y epel-release
|
||||||
|
# yum install -y gcc python2 python-devel python-pip libxml2-devel libxslt-devel libffi-devel graphviz openssl-devel
|
||||||
```
|
```
|
||||||
|
|
||||||
You may opt to install NetBox either from a numbered release or by cloning the master branch of its repository on GitHub.
|
You may opt to install NetBox either from a numbered release or by cloning the master branch of its repository on GitHub.
|
||||||
@ -41,8 +38,16 @@ Create the base directory for the NetBox installation. For this guide, we'll use
|
|||||||
|
|
||||||
If `git` is not already installed, install it:
|
If `git` is not already installed, install it:
|
||||||
|
|
||||||
|
**Debian/Ubuntu**
|
||||||
|
|
||||||
```
|
```
|
||||||
# sudo apt-get install -y git
|
# apt-get install -y git
|
||||||
|
```
|
||||||
|
|
||||||
|
**CentOS/RHEL**
|
||||||
|
|
||||||
|
```
|
||||||
|
# yum install -y git
|
||||||
```
|
```
|
||||||
|
|
||||||
Next, clone the **master** branch of the NetBox GitHub repository into the current directory:
|
Next, clone the **master** branch of the NetBox GitHub repository into the current directory:
|
||||||
@ -63,7 +68,7 @@ Checking connectivity... done.
|
|||||||
Install the required Python packages using pip. (If you encounter any compilation errors during this step, ensure that you've installed all of the system dependencies listed above.)
|
Install the required Python packages using pip. (If you encounter any compilation errors during this step, ensure that you've installed all of the system dependencies listed above.)
|
||||||
|
|
||||||
```
|
```
|
||||||
# sudo pip install -r requirements.txt
|
# pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
|
@ -2,17 +2,33 @@ NetBox requires a PostgreSQL database to store data. MySQL is not supported, as
|
|||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
The following packages are needed to install PostgreSQL with Python support:
|
**Debian/Ubuntu**
|
||||||
|
|
||||||
* postgresql
|
|
||||||
* libpq-dev
|
|
||||||
* python-psycopg2
|
|
||||||
|
|
||||||
```
|
```
|
||||||
# sudo apt-get install -y postgresql libpq-dev python-psycopg2
|
# apt-get install -y postgresql libpq-dev python-psycopg2
|
||||||
```
|
```
|
||||||
|
|
||||||
# Configuration
|
**CentOS/RHEL**
|
||||||
|
|
||||||
|
```
|
||||||
|
# yum install -y postgresql postgresql-server postgresql-devel python-psycopg2
|
||||||
|
# postgresql-setup initdb
|
||||||
|
```
|
||||||
|
|
||||||
|
If using CentOS, modify the PostgreSQL configuration to accept password-based authentication by replacing `ident` with `md5` for all host entries within `/var/lib/pgsql/data/pg_hba.conf`. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
host all all 127.0.0.1/32 md5
|
||||||
|
host all all ::1/128 md5
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, start the service:
|
||||||
|
|
||||||
|
```
|
||||||
|
# systemctl start postgresql
|
||||||
|
```
|
||||||
|
|
||||||
|
# Database Creation
|
||||||
|
|
||||||
At a minimum, we need to create a database for NetBox and assign it a username and password for authentication. This is done with the following commands.
|
At a minimum, we need to create a database for NetBox and assign it a username and password for authentication. This is done with the following commands.
|
||||||
|
|
||||||
|
@ -2,8 +2,11 @@
|
|||||||
|
|
||||||
We'll set up a simple WSGI front end using [gunicorn](http://gunicorn.org/) for the purposes of this guide. For web servers, we provide example configurations for both [nginx](https://www.nginx.com/resources/wiki/) and [Apache](http://httpd.apache.org/docs/2.4). (You are of course free to use whichever combination of HTTP and WSGI services you'd like.) We'll also use [supervisord](http://supervisord.org/) to enable service persistence.
|
We'll set up a simple WSGI front end using [gunicorn](http://gunicorn.org/) for the purposes of this guide. For web servers, we provide example configurations for both [nginx](https://www.nginx.com/resources/wiki/) and [Apache](http://httpd.apache.org/docs/2.4). (You are of course free to use whichever combination of HTTP and WSGI services you'd like.) We'll also use [supervisord](http://supervisord.org/) to enable service persistence.
|
||||||
|
|
||||||
|
!!! info
|
||||||
|
Only Debian/Ubuntu instructions are provided here, but the installation process for CentOS/RHEL does not differ much. Please consult the documentation for those distributions for details.
|
||||||
|
|
||||||
```
|
```
|
||||||
# sudo apt-get install -y gunicorn supervisor
|
# apt-get install -y gunicorn supervisor
|
||||||
```
|
```
|
||||||
|
|
||||||
## Option A: nginx
|
## Option A: nginx
|
||||||
@ -11,10 +14,10 @@ We'll set up a simple WSGI front end using [gunicorn](http://gunicorn.org/) for
|
|||||||
The following will serve as a minimal nginx configuration. Be sure to modify your server name and installation path appropriately.
|
The following will serve as a minimal nginx configuration. Be sure to modify your server name and installation path appropriately.
|
||||||
|
|
||||||
```
|
```
|
||||||
# sudo apt-get install -y nginx
|
# apt-get install -y nginx
|
||||||
```
|
```
|
||||||
|
|
||||||
Once nginx is installed, proceed with the following configuration:
|
Once nginx is installed, save the following configuration to `/etc/nginx/sites-available/netbox`. Be sure to replace `netbox.example.com` with the domain name or IP address of your installation. (This should match the value configured for `ALLOWED_HOSTS` in `configuration.py`.)
|
||||||
|
|
||||||
```
|
```
|
||||||
server {
|
server {
|
||||||
@ -38,7 +41,7 @@ server {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Save this configuration to `/etc/nginx/sites-available/netbox`. Then, delete `/etc/nginx/sites-enabled/default` and create a symlink in the `sites-enabled` directory to the configuration file you just created.
|
Then, delete `/etc/nginx/sites-enabled/default` and create a symlink in the `sites-enabled` directory to the configuration file you just created.
|
||||||
|
|
||||||
```
|
```
|
||||||
# cd /etc/nginx/sites-enabled/
|
# cd /etc/nginx/sites-enabled/
|
||||||
@ -50,7 +53,6 @@ Restart the nginx service to use the new configuration.
|
|||||||
|
|
||||||
```
|
```
|
||||||
# service nginx restart
|
# service nginx restart
|
||||||
* Restarting nginx nginx
|
|
||||||
```
|
```
|
||||||
|
|
||||||
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-14-04).
|
||||||
@ -58,7 +60,7 @@ To enable SSL, consider this guide on [securing nginx with Let's Encrypt](https:
|
|||||||
## Option B: Apache
|
## Option B: Apache
|
||||||
|
|
||||||
```
|
```
|
||||||
# sudo apt-get install -y apache2
|
# apt-get install -y apache2
|
||||||
```
|
```
|
||||||
|
|
||||||
Once Apache is installed, proceed with the following configuration (Be sure to modify the `ServerName` appropriately):
|
Once Apache is installed, proceed with the following configuration (Be sure to modify the `ServerName` appropriately):
|
||||||
@ -99,7 +101,7 @@ To enable SSL, consider this guide on [securing Apache with Let's Encrypt](https
|
|||||||
|
|
||||||
# gunicorn Installation
|
# gunicorn Installation
|
||||||
|
|
||||||
Save the following configuration file in the root netbox installation path (in this example, `/opt/netbox/`) as `gunicorn_config.py`. Be sure to verify the location of the gunicorn executable (e.g. `which gunicorn`) and to update the `pythonpath` variable if needed.
|
Save the following configuration file in the root netbox installation path (in this example, `/opt/netbox/`) as `gunicorn_config.py`. Be sure to verify the location of the gunicorn executable (e.g. `which gunicorn`) and to update the `pythonpath` variable if needed. If using CentOS/RHEL change the username from `www-data` to `nginx` or `apache`.
|
||||||
|
|
||||||
```
|
```
|
||||||
command = '/usr/bin/gunicorn'
|
command = '/usr/bin/gunicorn'
|
||||||
@ -120,7 +122,7 @@ directory = /opt/netbox/netbox/
|
|||||||
user = www-data
|
user = www-data
|
||||||
```
|
```
|
||||||
|
|
||||||
Finally, restart the supervisor service to detect and run the gunicorn service:
|
Then, restart the supervisor service to detect and run the gunicorn service:
|
||||||
|
|
||||||
```
|
```
|
||||||
# service supervisor restart
|
# service supervisor restart
|
||||||
|
@ -5,6 +5,7 @@ from dcim.models import (
|
|||||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, DeviceType,
|
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, DeviceType,
|
||||||
DeviceRole, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer, Module, Platform, PowerOutlet,
|
DeviceRole, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer, Module, Platform, PowerOutlet,
|
||||||
PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackRole, RACK_FACE_FRONT, RACK_FACE_REAR, Site,
|
PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackRole, RACK_FACE_FRONT, RACK_FACE_REAR, Site,
|
||||||
|
SUBDEVICE_ROLE_CHILD, SUBDEVICE_ROLE_PARENT,
|
||||||
)
|
)
|
||||||
from extras.api.serializers import CustomFieldSerializer
|
from extras.api.serializers import CustomFieldSerializer
|
||||||
from tenancy.api.serializers import TenantNestedSerializer
|
from tenancy.api.serializers import TenantNestedSerializer
|
||||||
@ -131,11 +132,19 @@ class ManufacturerNestedSerializer(ManufacturerSerializer):
|
|||||||
|
|
||||||
class DeviceTypeSerializer(serializers.ModelSerializer):
|
class DeviceTypeSerializer(serializers.ModelSerializer):
|
||||||
manufacturer = ManufacturerNestedSerializer()
|
manufacturer = ManufacturerNestedSerializer()
|
||||||
|
subdevice_role = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DeviceType
|
model = DeviceType
|
||||||
fields = ['id', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth',
|
fields = ['id', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth',
|
||||||
'is_console_server', 'is_pdu', 'is_network_device']
|
'is_console_server', 'is_pdu', 'is_network_device', 'subdevice_role']
|
||||||
|
|
||||||
|
def get_subdevice_role(self, obj):
|
||||||
|
return {
|
||||||
|
SUBDEVICE_ROLE_PARENT: 'parent',
|
||||||
|
SUBDEVICE_ROLE_CHILD: 'child',
|
||||||
|
None: None,
|
||||||
|
}[obj.subdevice_role]
|
||||||
|
|
||||||
|
|
||||||
class DeviceTypeNestedSerializer(DeviceTypeSerializer):
|
class DeviceTypeNestedSerializer(DeviceTypeSerializer):
|
||||||
|
@ -593,7 +593,7 @@ class DeviceBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
|||||||
class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||||
model = Device
|
model = Device
|
||||||
site = FilterChoiceField(queryset=Site.objects.annotate(filter_count=Count('racks__devices')), to_field_name='slug')
|
site = FilterChoiceField(queryset=Site.objects.annotate(filter_count=Count('racks__devices')), to_field_name='slug')
|
||||||
rack_group_id = FilterChoiceField(queryset=Site.objects.annotate(filter_count=Count('racks__devices')),
|
rack_group_id = FilterChoiceField(queryset=RackGroup.objects.annotate(filter_count=Count('racks__devices')),
|
||||||
label='Rack Group')
|
label='Rack Group')
|
||||||
role = FilterChoiceField(queryset=DeviceRole.objects.annotate(filter_count=Count('devices')), to_field_name='slug')
|
role = FilterChoiceField(queryset=DeviceRole.objects.annotate(filter_count=Count('devices')), to_field_name='slug')
|
||||||
tenant = FilterChoiceField(queryset=Tenant.objects.annotate(filter_count=Count('devices')), to_field_name='slug',
|
tenant = FilterChoiceField(queryset=Tenant.objects.annotate(filter_count=Count('devices')), to_field_name='slug',
|
||||||
|
@ -576,11 +576,29 @@ class DeviceType(models.Model):
|
|||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return u'{} {}'.format(self.manufacturer, self.model)
|
return u'{} {}'.format(self.manufacturer, self.model)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(DeviceType, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# Save a copy of u_height for validation in clean()
|
||||||
|
self._original_u_height = self.u_height
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('dcim:devicetype', args=[self.pk])
|
return reverse('dcim:devicetype', args=[self.pk])
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
|
||||||
|
# If editing an existing DeviceType to have a larger u_height, first validate that *all* instances of it have
|
||||||
|
# room to expand within their racks. This validation will impose a very high performance penalty when there are
|
||||||
|
# many instances to check, but increasing the u_height of a DeviceType should be a very rare occurrence.
|
||||||
|
if self.pk is not None and self.u_height > self._original_u_height:
|
||||||
|
for d in Device.objects.filter(device_type=self, position__isnull=False):
|
||||||
|
face_required = None if self.is_full_depth else d.face
|
||||||
|
u_available = d.rack.get_available_units(u_height=self.u_height, rack_face=face_required,
|
||||||
|
exclude=[d.pk])
|
||||||
|
if d.position not in u_available:
|
||||||
|
raise ValidationError("Device {} in rack {} does not have sufficient space to accommodate a height "
|
||||||
|
"of {}U".format(d, d.rack, self.u_height))
|
||||||
|
|
||||||
if not self.is_console_server and self.cs_port_templates.count():
|
if not self.is_console_server and self.cs_port_templates.count():
|
||||||
raise ValidationError("Must delete all console server port templates associated with this device before "
|
raise ValidationError("Must delete all console server port templates associated with this device before "
|
||||||
"declassifying it as a console server.")
|
"declassifying it as a console server.")
|
||||||
|
@ -2,6 +2,8 @@ import json
|
|||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.test import APITestCase
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
class SiteTest(APITestCase):
|
class SiteTest(APITestCase):
|
||||||
|
|
||||||
@ -57,7 +59,7 @@ class SiteTest(APITestCase):
|
|||||||
'embed_link',
|
'embed_link',
|
||||||
]
|
]
|
||||||
|
|
||||||
def test_get_list(self, endpoint='/api/dcim/sites/'):
|
def test_get_list(self, endpoint='/{}api/dcim/sites/'.format(settings.BASE_PATH)):
|
||||||
response = self.client.get(endpoint)
|
response = self.client.get(endpoint)
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
@ -67,7 +69,7 @@ class SiteTest(APITestCase):
|
|||||||
sorted(self.standard_fields),
|
sorted(self.standard_fields),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_detail(self, endpoint='/api/dcim/sites/1/'):
|
def test_get_detail(self, endpoint='/{}api/dcim/sites/1/'.format(settings.BASE_PATH)):
|
||||||
response = self.client.get(endpoint)
|
response = self.client.get(endpoint)
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
@ -76,7 +78,7 @@ class SiteTest(APITestCase):
|
|||||||
sorted(self.standard_fields),
|
sorted(self.standard_fields),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_site_list_rack(self, endpoint='/api/dcim/sites/1/racks/'):
|
def test_get_site_list_rack(self, endpoint='/{}api/dcim/sites/1/racks/'.format(settings.BASE_PATH)):
|
||||||
response = self.client.get(endpoint)
|
response = self.client.get(endpoint)
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
@ -91,7 +93,7 @@ class SiteTest(APITestCase):
|
|||||||
sorted(self.nested_fields),
|
sorted(self.nested_fields),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_site_list_graphs(self, endpoint='/api/dcim/sites/1/graphs/'):
|
def test_get_site_list_graphs(self, endpoint='/{}api/dcim/sites/1/graphs/'.format(settings.BASE_PATH)):
|
||||||
response = self.client.get(endpoint)
|
response = self.client.get(endpoint)
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
@ -149,7 +151,7 @@ class RackTest(APITestCase):
|
|||||||
'rear_units'
|
'rear_units'
|
||||||
]
|
]
|
||||||
|
|
||||||
def test_get_list(self, endpoint='/api/dcim/racks/'):
|
def test_get_list(self, endpoint='/{}api/dcim/racks/'.format(settings.BASE_PATH)):
|
||||||
response = self.client.get(endpoint)
|
response = self.client.get(endpoint)
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
@ -163,7 +165,7 @@ class RackTest(APITestCase):
|
|||||||
sorted(SiteTest.nested_fields),
|
sorted(SiteTest.nested_fields),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_detail(self, endpoint='/api/dcim/racks/1/'):
|
def test_get_detail(self, endpoint='/{}api/dcim/racks/1/'.format(settings.BASE_PATH)):
|
||||||
response = self.client.get(endpoint)
|
response = self.client.get(endpoint)
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
@ -192,7 +194,7 @@ class ManufacturersTest(APITestCase):
|
|||||||
|
|
||||||
nested_fields = standard_fields
|
nested_fields = standard_fields
|
||||||
|
|
||||||
def test_get_list(self, endpoint='/api/dcim/manufacturers/'):
|
def test_get_list(self, endpoint='/{}api/dcim/manufacturers/'.format(settings.BASE_PATH)):
|
||||||
response = self.client.get(endpoint)
|
response = self.client.get(endpoint)
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
@ -202,7 +204,7 @@ class ManufacturersTest(APITestCase):
|
|||||||
sorted(self.standard_fields),
|
sorted(self.standard_fields),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_detail(self, endpoint='/api/dcim/manufacturers/1/'):
|
def test_get_detail(self, endpoint='/{}api/dcim/manufacturers/1/'.format(settings.BASE_PATH)):
|
||||||
response = self.client.get(endpoint)
|
response = self.client.get(endpoint)
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
@ -227,6 +229,7 @@ class DeviceTypeTest(APITestCase):
|
|||||||
'is_console_server',
|
'is_console_server',
|
||||||
'is_pdu',
|
'is_pdu',
|
||||||
'is_network_device',
|
'is_network_device',
|
||||||
|
'subdevice_role',
|
||||||
]
|
]
|
||||||
|
|
||||||
nested_fields = [
|
nested_fields = [
|
||||||
@ -236,7 +239,7 @@ class DeviceTypeTest(APITestCase):
|
|||||||
'slug'
|
'slug'
|
||||||
]
|
]
|
||||||
|
|
||||||
def test_get_list(self, endpoint='/api/dcim/device-types/'):
|
def test_get_list(self, endpoint='/{}api/dcim/device-types/'.format(settings.BASE_PATH)):
|
||||||
response = self.client.get(endpoint)
|
response = self.client.get(endpoint)
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
@ -246,7 +249,7 @@ class DeviceTypeTest(APITestCase):
|
|||||||
sorted(self.standard_fields),
|
sorted(self.standard_fields),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_detail_list(self, endpoint='/api/dcim/device-types/1/'):
|
def test_detail_list(self, endpoint='/{}api/dcim/device-types/1/'.format(settings.BASE_PATH)):
|
||||||
# TODO: details returns list view.
|
# TODO: details returns list view.
|
||||||
# response = self.client.get(endpoint)
|
# response = self.client.get(endpoint)
|
||||||
# content = json.loads(response.content)
|
# content = json.loads(response.content)
|
||||||
@ -270,7 +273,7 @@ class DeviceRolesTest(APITestCase):
|
|||||||
|
|
||||||
nested_fields = ['id', 'name', 'slug']
|
nested_fields = ['id', 'name', 'slug']
|
||||||
|
|
||||||
def test_get_list(self, endpoint='/api/dcim/device-roles/'):
|
def test_get_list(self, endpoint='/{}api/dcim/device-roles/'.format(settings.BASE_PATH)):
|
||||||
response = self.client.get(endpoint)
|
response = self.client.get(endpoint)
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
@ -280,7 +283,7 @@ class DeviceRolesTest(APITestCase):
|
|||||||
sorted(self.standard_fields),
|
sorted(self.standard_fields),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_detail(self, endpoint='/api/dcim/device-roles/1/'):
|
def test_get_detail(self, endpoint='/{}api/dcim/device-roles/1/'.format(settings.BASE_PATH)):
|
||||||
response = self.client.get(endpoint)
|
response = self.client.get(endpoint)
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
@ -298,7 +301,7 @@ class PlatformsTest(APITestCase):
|
|||||||
|
|
||||||
nested_fields = ['id', 'name', 'slug']
|
nested_fields = ['id', 'name', 'slug']
|
||||||
|
|
||||||
def test_get_list(self, endpoint='/api/dcim/platforms/'):
|
def test_get_list(self, endpoint='/{}api/dcim/platforms/'.format(settings.BASE_PATH)):
|
||||||
response = self.client.get(endpoint)
|
response = self.client.get(endpoint)
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
@ -308,7 +311,7 @@ class PlatformsTest(APITestCase):
|
|||||||
sorted(self.standard_fields),
|
sorted(self.standard_fields),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_detail(self, endpoint='/api/dcim/platforms/1/'):
|
def test_get_detail(self, endpoint='/{}api/dcim/platforms/1/'.format(settings.BASE_PATH)):
|
||||||
response = self.client.get(endpoint)
|
response = self.client.get(endpoint)
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
@ -346,7 +349,7 @@ class DeviceTest(APITestCase):
|
|||||||
|
|
||||||
nested_fields = ['id', 'name', 'display_name']
|
nested_fields = ['id', 'name', 'display_name']
|
||||||
|
|
||||||
def test_get_list(self, endpoint='/api/dcim/devices/'):
|
def test_get_list(self, endpoint='/{}api/dcim/devices/'.format(settings.BASE_PATH)):
|
||||||
response = self.client.get(endpoint)
|
response = self.client.get(endpoint)
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
@ -373,7 +376,7 @@ class DeviceTest(APITestCase):
|
|||||||
sorted(RackTest.nested_fields),
|
sorted(RackTest.nested_fields),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_list_flat(self, endpoint='/api/dcim/devices/?format=json_flat'):
|
def test_get_list_flat(self, endpoint='/{}api/dcim/devices/?format=json_flat'.format(settings.BASE_PATH)):
|
||||||
|
|
||||||
flat_fields = [
|
flat_fields = [
|
||||||
'asset_tag',
|
'asset_tag',
|
||||||
@ -421,7 +424,7 @@ class DeviceTest(APITestCase):
|
|||||||
sorted(flat_fields),
|
sorted(flat_fields),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_detail(self, endpoint='/api/dcim/devices/1/'):
|
def test_get_detail(self, endpoint='/{}api/dcim/devices/1/'.format(settings.BASE_PATH)):
|
||||||
response = self.client.get(endpoint)
|
response = self.client.get(endpoint)
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
@ -439,7 +442,7 @@ class ConsoleServerPortsTest(APITestCase):
|
|||||||
|
|
||||||
nested_fields = ['id', 'device', 'name']
|
nested_fields = ['id', 'device', 'name']
|
||||||
|
|
||||||
def test_get_list(self, endpoint='/api/dcim/devices/9/console-server-ports/'):
|
def test_get_list(self, endpoint='/{}api/dcim/devices/9/console-server-ports/'.format(settings.BASE_PATH)):
|
||||||
response = self.client.get(endpoint)
|
response = self.client.get(endpoint)
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
@ -461,7 +464,7 @@ class ConsolePortsTest(APITestCase):
|
|||||||
|
|
||||||
nested_fields = ['id', 'device', 'name']
|
nested_fields = ['id', 'device', 'name']
|
||||||
|
|
||||||
def test_get_list(self, endpoint='/api/dcim/devices/1/console-ports/'):
|
def test_get_list(self, endpoint='/{}api/dcim/devices/1/console-ports/'.format(settings.BASE_PATH)):
|
||||||
response = self.client.get(endpoint)
|
response = self.client.get(endpoint)
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
@ -479,7 +482,7 @@ class ConsolePortsTest(APITestCase):
|
|||||||
sorted(ConsoleServerPortsTest.nested_fields),
|
sorted(ConsoleServerPortsTest.nested_fields),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_detail(self, endpoint='/api/dcim/console-ports/1/'):
|
def test_get_detail(self, endpoint='/{}api/dcim/console-ports/1/'.format(settings.BASE_PATH)):
|
||||||
response = self.client.get(endpoint)
|
response = self.client.get(endpoint)
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
@ -500,7 +503,7 @@ class PowerPortsTest(APITestCase):
|
|||||||
|
|
||||||
nested_fields = ['id', 'device', 'name']
|
nested_fields = ['id', 'device', 'name']
|
||||||
|
|
||||||
def test_get_list(self, endpoint='/api/dcim/devices/1/power-ports/'):
|
def test_get_list(self, endpoint='/{}api/dcim/devices/1/power-ports/'.format(settings.BASE_PATH)):
|
||||||
response = self.client.get(endpoint)
|
response = self.client.get(endpoint)
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
@ -514,7 +517,7 @@ class PowerPortsTest(APITestCase):
|
|||||||
sorted(DeviceTest.nested_fields),
|
sorted(DeviceTest.nested_fields),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_detail(self, endpoint='/api/dcim/power-ports/1/'):
|
def test_get_detail(self, endpoint='/{}api/dcim/power-ports/1/'.format(settings.BASE_PATH)):
|
||||||
response = self.client.get(endpoint)
|
response = self.client.get(endpoint)
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
@ -535,7 +538,7 @@ class PowerOutletsTest(APITestCase):
|
|||||||
|
|
||||||
nested_fields = ['id', 'device', 'name']
|
nested_fields = ['id', 'device', 'name']
|
||||||
|
|
||||||
def test_get_list(self, endpoint='/api/dcim/devices/11/power-outlets/'):
|
def test_get_list(self, endpoint='/{}api/dcim/devices/11/power-outlets/'.format(settings.BASE_PATH)):
|
||||||
response = self.client.get(endpoint)
|
response = self.client.get(endpoint)
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
@ -585,7 +588,7 @@ class InterfaceTest(APITestCase):
|
|||||||
'connection_status',
|
'connection_status',
|
||||||
]
|
]
|
||||||
|
|
||||||
def test_get_list(self, endpoint='/api/dcim/devices/1/interfaces/'):
|
def test_get_list(self, endpoint='/{}api/dcim/devices/1/interfaces/'.format(settings.BASE_PATH)):
|
||||||
response = self.client.get(endpoint)
|
response = self.client.get(endpoint)
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
@ -599,7 +602,7 @@ class InterfaceTest(APITestCase):
|
|||||||
sorted(DeviceTest.nested_fields),
|
sorted(DeviceTest.nested_fields),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_detail(self, endpoint='/api/dcim/interfaces/1/'):
|
def test_get_detail(self, endpoint='/{}api/dcim/interfaces/1/'.format(settings.BASE_PATH)):
|
||||||
response = self.client.get(endpoint)
|
response = self.client.get(endpoint)
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
@ -612,7 +615,7 @@ class InterfaceTest(APITestCase):
|
|||||||
sorted(DeviceTest.nested_fields),
|
sorted(DeviceTest.nested_fields),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_graph_list(self, endpoint='/api/dcim/interfaces/1/graphs/'):
|
def test_get_graph_list(self, endpoint='/{}api/dcim/interfaces/1/graphs/'.format(settings.BASE_PATH)):
|
||||||
response = self.client.get(endpoint)
|
response = self.client.get(endpoint)
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
@ -622,7 +625,8 @@ class InterfaceTest(APITestCase):
|
|||||||
sorted(SiteTest.graph_fields),
|
sorted(SiteTest.graph_fields),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_interface_connections(self, endpoint='/api/dcim/interface-connections/4/'):
|
def test_get_interface_connections(self, endpoint='/{}api/dcim/interface-connections/4/'
|
||||||
|
.format(settings.BASE_PATH)):
|
||||||
response = self.client.get(endpoint)
|
response = self.client.get(endpoint)
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
@ -643,9 +647,8 @@ class RelatedConnectionsTest(APITestCase):
|
|||||||
'interfaces',
|
'interfaces',
|
||||||
]
|
]
|
||||||
|
|
||||||
def test_get_list(self, endpoint=(
|
def test_get_list(self, endpoint=('/{}api/dcim/related-connections/?peer-device=test1-edge1&peer-interface=xe-0/0/3'
|
||||||
'/api/dcim/related-connections/'
|
.format(settings.BASE_PATH))):
|
||||||
'?peer-device=test1-edge1&peer-interface=xe-0/0/3')):
|
|
||||||
response = self.client.get(endpoint)
|
response = self.client.get(endpoint)
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
@ -40,7 +40,7 @@ class GraphAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
@admin.register(ExportTemplate)
|
@admin.register(ExportTemplate)
|
||||||
class ExportTemplateAdmin(admin.ModelAdmin):
|
class ExportTemplateAdmin(admin.ModelAdmin):
|
||||||
list_display = ['content_type', 'name', 'mime_type', 'file_extension']
|
list_display = ['name', 'content_type', 'description', 'mime_type', 'file_extension']
|
||||||
|
|
||||||
|
|
||||||
@admin.register(TopologyMap)
|
@admin.register(TopologyMap)
|
||||||
|
@ -3,6 +3,7 @@ from collections import OrderedDict
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
|
from utilities.forms import LaxURLField
|
||||||
from .models import (
|
from .models import (
|
||||||
CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT, CF_TYPE_URL, CustomField, CustomFieldValue
|
CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT, CF_TYPE_URL, CustomField, CustomFieldValue
|
||||||
)
|
)
|
||||||
@ -56,7 +57,7 @@ def get_custom_fields_for_model(content_type, filterable_only=False, bulk_edit=F
|
|||||||
|
|
||||||
# URL
|
# URL
|
||||||
elif cf.type == CF_TYPE_URL:
|
elif cf.type == CF_TYPE_URL:
|
||||||
field = forms.URLField(required=cf.required, initial=cf.default)
|
field = LaxURLField(required=cf.required, initial=cf.default)
|
||||||
|
|
||||||
# Text
|
# Text
|
||||||
else:
|
else:
|
||||||
@ -92,7 +93,7 @@ class CustomFieldForm(forms.ModelForm):
|
|||||||
existing_values = CustomFieldValue.objects.filter(obj_type=self.obj_type, obj_id=self.instance.pk)\
|
existing_values = CustomFieldValue.objects.filter(obj_type=self.obj_type, obj_id=self.instance.pk)\
|
||||||
.select_related('field')
|
.select_related('field')
|
||||||
for cfv in existing_values:
|
for cfv in existing_values:
|
||||||
self.initial['cf_{}'.format(str(cfv.field.name))] = cfv.value
|
self.initial['cf_{}'.format(str(cfv.field.name))] = cfv.serialized_value
|
||||||
|
|
||||||
def _save_custom_fields(self):
|
def _save_custom_fields(self):
|
||||||
|
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10 on 2016-09-27 20:20
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('extras', '0002_custom_fields'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exporttemplate',
|
||||||
|
name='description',
|
||||||
|
field=models.CharField(blank=True, max_length=200),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='exporttemplate',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(max_length=100),
|
||||||
|
),
|
||||||
|
]
|
@ -146,8 +146,10 @@ class CustomField(models.Model):
|
|||||||
# Read date as YYYY-MM-DD
|
# Read date as YYYY-MM-DD
|
||||||
return date(*[int(n) for n in serialized_value.split('-')])
|
return date(*[int(n) for n in serialized_value.split('-')])
|
||||||
if self.type == CF_TYPE_SELECT:
|
if self.type == CF_TYPE_SELECT:
|
||||||
# return CustomFieldChoice.objects.get(pk=int(serialized_value))
|
try:
|
||||||
return self.choices.get(pk=int(serialized_value))
|
return self.choices.get(pk=int(serialized_value))
|
||||||
|
except CustomFieldChoice.DoesNotExist:
|
||||||
|
return None
|
||||||
return serialized_value
|
return serialized_value
|
||||||
|
|
||||||
|
|
||||||
@ -198,6 +200,12 @@ class CustomFieldChoice(models.Model):
|
|||||||
if self.field.type != CF_TYPE_SELECT:
|
if self.field.type != CF_TYPE_SELECT:
|
||||||
raise ValidationError("Custom field choices can only be assigned to selection fields.")
|
raise ValidationError("Custom field choices can only be assigned to selection fields.")
|
||||||
|
|
||||||
|
def delete(self, using=None, keep_parents=False):
|
||||||
|
# When deleting a CustomFieldChoice, delete all CustomFieldValues which point to it
|
||||||
|
pk = self.pk
|
||||||
|
super(CustomFieldChoice, self).delete(using, keep_parents)
|
||||||
|
CustomFieldValue.objects.filter(field__type=CF_TYPE_SELECT, serialized_value=str(pk)).delete()
|
||||||
|
|
||||||
|
|
||||||
class Graph(models.Model):
|
class Graph(models.Model):
|
||||||
type = models.PositiveSmallIntegerField(choices=GRAPH_TYPE_CHOICES)
|
type = models.PositiveSmallIntegerField(choices=GRAPH_TYPE_CHOICES)
|
||||||
@ -225,7 +233,8 @@ class Graph(models.Model):
|
|||||||
|
|
||||||
class ExportTemplate(models.Model):
|
class ExportTemplate(models.Model):
|
||||||
content_type = models.ForeignKey(ContentType, limit_choices_to={'model__in': EXPORTTEMPLATE_MODELS})
|
content_type = models.ForeignKey(ContentType, limit_choices_to={'model__in': EXPORTTEMPLATE_MODELS})
|
||||||
name = models.CharField(max_length=200)
|
name = models.CharField(max_length=100)
|
||||||
|
description = models.CharField(max_length=200, blank=True)
|
||||||
template_code = models.TextField()
|
template_code = models.TextField()
|
||||||
mime_type = models.CharField(max_length=15, blank=True)
|
mime_type = models.CharField(max_length=15, blank=True)
|
||||||
file_extension = models.CharField(max_length=15, blank=True)
|
file_extension = models.CharField(max_length=15, blank=True)
|
||||||
|
@ -52,6 +52,10 @@ EMAIL = {
|
|||||||
# are permitted to access most data in NetBox (excluding secrets) but not make any changes.
|
# are permitted to access most data in NetBox (excluding secrets) but not make any changes.
|
||||||
LOGIN_REQUIRED = os.environ.get('LOGIN_REQUIRED', False)
|
LOGIN_REQUIRED = os.environ.get('LOGIN_REQUIRED', False)
|
||||||
|
|
||||||
|
# Base URL path if accessing NetBox within a directory. For example, if installed at http://example.com/netbox/, set:
|
||||||
|
# BASE_PATH = 'netbox/'
|
||||||
|
BASE_PATH = os.environ.get('BASE_PATH', '')
|
||||||
|
|
||||||
# Setting this to True will display a "maintenance mode" banner at the top of every page.
|
# Setting this to True will display a "maintenance mode" banner at the top of every page.
|
||||||
MAINTENANCE_MODE = os.environ.get('MAINTENANCE_MODE', False)
|
MAINTENANCE_MODE = os.environ.get('MAINTENANCE_MODE', False)
|
||||||
|
|
||||||
|
@ -52,6 +52,10 @@ EMAIL = {
|
|||||||
# are permitted to access most data in NetBox (excluding secrets) but not make any changes.
|
# are permitted to access most data in NetBox (excluding secrets) but not make any changes.
|
||||||
LOGIN_REQUIRED = False
|
LOGIN_REQUIRED = False
|
||||||
|
|
||||||
|
# Base URL path if accessing NetBox within a directory. For example, if installed at http://example.com/netbox/, set:
|
||||||
|
# BASE_PATH = 'netbox/'
|
||||||
|
BASE_PATH = ''
|
||||||
|
|
||||||
# Setting this to True will display a "maintenance mode" banner at the top of every page.
|
# Setting this to True will display a "maintenance mode" banner at the top of every page.
|
||||||
MAINTENANCE_MODE = False
|
MAINTENANCE_MODE = False
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ except ImportError:
|
|||||||
"the documentation.")
|
"the documentation.")
|
||||||
|
|
||||||
|
|
||||||
VERSION = '1.6.1-r1'
|
VERSION = '1.6.2'
|
||||||
|
|
||||||
# Import local configuration
|
# Import local configuration
|
||||||
for setting in ['ALLOWED_HOSTS', 'DATABASE', 'SECRET_KEY']:
|
for setting in ['ALLOWED_HOSTS', 'DATABASE', 'SECRET_KEY']:
|
||||||
@ -27,6 +27,9 @@ ADMINS = getattr(configuration, 'ADMINS', [])
|
|||||||
DEBUG = getattr(configuration, 'DEBUG', False)
|
DEBUG = getattr(configuration, 'DEBUG', False)
|
||||||
EMAIL = getattr(configuration, 'EMAIL', {})
|
EMAIL = getattr(configuration, 'EMAIL', {})
|
||||||
LOGIN_REQUIRED = getattr(configuration, 'LOGIN_REQUIRED', False)
|
LOGIN_REQUIRED = getattr(configuration, 'LOGIN_REQUIRED', False)
|
||||||
|
BASE_PATH = getattr(configuration, 'BASE_PATH', '')
|
||||||
|
if BASE_PATH:
|
||||||
|
BASE_PATH = BASE_PATH.strip('/') + '/' # Enforce trailing slash only
|
||||||
MAINTENANCE_MODE = getattr(configuration, 'MAINTENANCE_MODE', False)
|
MAINTENANCE_MODE = getattr(configuration, 'MAINTENANCE_MODE', False)
|
||||||
PAGINATE_COUNT = getattr(configuration, 'PAGINATE_COUNT', 50)
|
PAGINATE_COUNT = getattr(configuration, 'PAGINATE_COUNT', 50)
|
||||||
NETBOX_USERNAME = getattr(configuration, 'NETBOX_USERNAME', '')
|
NETBOX_USERNAME = getattr(configuration, 'NETBOX_USERNAME', '')
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from django.conf import settings
|
||||||
from django.conf.urls import include, url
|
from django.conf.urls import include, url
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.views.defaults import page_not_found
|
from django.views.defaults import page_not_found
|
||||||
@ -8,7 +9,7 @@ from users.views import login, logout
|
|||||||
|
|
||||||
handler500 = handle_500
|
handler500 = handle_500
|
||||||
|
|
||||||
urlpatterns = [
|
_patterns = [
|
||||||
|
|
||||||
# Default page
|
# Default page
|
||||||
url(r'^$', home, name='home'),
|
url(r'^$', home, name='home'),
|
||||||
@ -42,3 +43,8 @@ urlpatterns = [
|
|||||||
url(r'^admin/', include(admin.site.urls)),
|
url(r'^admin/', include(admin.site.urls)),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Prepend BASE_PATH
|
||||||
|
urlpatterns = [
|
||||||
|
url(r'^{}'.format(settings.BASE_PATH), include(_patterns))
|
||||||
|
]
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
|
||||||
// "Select all" checkbox in a table header
|
// "Toggle all" checkbox in a table header
|
||||||
$('th input:checkbox[name=_all]').click(function (event) {
|
$('#toggle_all').click(function (event) {
|
||||||
$(this).parents('table').find('td input:checkbox').prop('checked', $(this).prop('checked'));
|
$('td input:checkbox[name=pk]').prop('checked', $(this).prop('checked'));
|
||||||
|
if ($(this).is(':checked')) {
|
||||||
|
$('#select_all_box').removeClass('hidden');
|
||||||
|
} else {
|
||||||
|
$('#select_all').prop('checked', false);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// Uncheck the "select all" checkbox if an item is unchecked
|
// Uncheck the "toggle all" checkbox if an item is unchecked
|
||||||
$('input:checkbox[name=pk]').click(function (event) {
|
$('input:checkbox[name=pk]').click(function (event) {
|
||||||
if (!$(this).attr('checked')) {
|
if (!$(this).attr('checked')) {
|
||||||
$(this).parents('table').find('input:checkbox[name=_all]').prop('checked', false);
|
$('#select_all, #toggle_all').prop('checked', false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ class SecretImportForm(BulkImportForm, BootstrapMixin):
|
|||||||
|
|
||||||
class SecretBulkEditForm(forms.Form, BootstrapMixin):
|
class SecretBulkEditForm(forms.Form, BootstrapMixin):
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=Secret.objects.all(), widget=forms.MultipleHiddenInput)
|
pk = forms.ModelMultipleChoiceField(queryset=Secret.objects.all(), widget=forms.MultipleHiddenInput)
|
||||||
role = forms.ModelChoiceField(queryset=SecretRole.objects.all())
|
role = forms.ModelChoiceField(queryset=SecretRole.objects.all(), required=False)
|
||||||
name = forms.CharField(max_length=100, required=False)
|
name = forms.CharField(max_length=100, required=False)
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
<pre><strong>{{ exception }}</strong><br />
|
<pre><strong>{{ exception }}</strong><br />
|
||||||
{{ error }}</pre>
|
{{ error }}</pre>
|
||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
<a href="/" class="btn btn-primary">Home Page</a>
|
<a href="{% url 'home' %}" class="btn btn-primary">Home Page</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
<link rel="stylesheet" href="{% static 'jquery-ui-1.11.4/jquery-ui.css' %}">
|
<link rel="stylesheet" href="{% static 'jquery-ui-1.11.4/jquery-ui.css' %}">
|
||||||
<link rel="stylesheet" href="{% static 'css/base.css' %}">
|
<link rel="stylesheet" href="{% static 'css/base.css' %}">
|
||||||
<link rel="icon" type="image/png" href="{% static 'img/netbox.ico' %}" />
|
<link rel="icon" type="image/png" href="{% static 'img/netbox.ico' %}" />
|
||||||
|
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<nav class="navbar navbar-default navbar-fixed-top">
|
<nav class="navbar navbar-default navbar-fixed-top">
|
||||||
@ -20,7 +21,7 @@
|
|||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
</button>
|
</button>
|
||||||
<a class="navbar-brand" href="/">
|
<a class="navbar-brand" href="{% url 'home' %}">
|
||||||
<img src="{% static 'img/netbox_logo.png' %}" />
|
<img src="{% static 'img/netbox_logo.png' %}" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@ -288,7 +289,7 @@
|
|||||||
<div class="col-xs-4 text-right">
|
<div class="col-xs-4 text-right">
|
||||||
<p class="text-muted">
|
<p class="text-muted">
|
||||||
<i class="fa fa-fw fa-book text-primary"></i> <a href="http://netbox.readthedocs.io/" target="_blank">Docs</a> ·
|
<i class="fa fa-fw fa-book text-primary"></i> <a href="http://netbox.readthedocs.io/" target="_blank">Docs</a> ·
|
||||||
<i class="fa fa-fw fa-cloud text-primary"></i> <a href="/api/docs/">API</a> ·
|
<i class="fa fa-fw fa-cloud text-primary"></i> <a href="{% url 'django.swagger.base.view' %}">API</a> ·
|
||||||
<i class="fa fa-fw fa-code text-primary"></i> <a href="https://github.com/digitalocean/netbox">Code</a>
|
<i class="fa fa-fw fa-code text-primary"></i> <a href="https://github.com/digitalocean/netbox">Code</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,14 +3,21 @@
|
|||||||
|
|
||||||
{% block title %}Circuit Bulk Edit{% endblock %}
|
{% block title %}Circuit Bulk Edit{% endblock %}
|
||||||
|
|
||||||
{% block select_objects_table %}
|
{% block selected_objects_table %}
|
||||||
|
<tr>
|
||||||
|
<th>Circuit</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Provider</th>
|
||||||
|
<th>Port speed</th>
|
||||||
|
<th>Commit rate</th>
|
||||||
|
</tr>
|
||||||
{% for circuit in selected_objects %}
|
{% for circuit in selected_objects %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{% url 'circuits:circuit' pk=circuit.pk %}">{{ circuit }}</a></td>
|
<td><a href="{% url 'circuits:circuit' pk=circuit.pk %}">{{ circuit }}</a></td>
|
||||||
<td>{{ circuit.type }}</td>
|
<td>{{ circuit.type }}</td>
|
||||||
<td>{{ circuit.provider }}</td>
|
<td>{{ circuit.provider }}</td>
|
||||||
<td>{{ circuit.port_speed }} Kbps</td>
|
<td>{{ circuit.port_speed_human }}</td>
|
||||||
<td>{{ circuit.commit_rate }}</td>
|
<td>{{ circuit.commit_rate_human }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -3,7 +3,12 @@
|
|||||||
|
|
||||||
{% block title %}Provider Bulk Edit{% endblock %}
|
{% block title %}Provider Bulk Edit{% endblock %}
|
||||||
|
|
||||||
{% block select_objects_table %}
|
{% block selected_objects_table %}
|
||||||
|
<tr>
|
||||||
|
<th>Provider</th>
|
||||||
|
<th>Account</th>
|
||||||
|
<th>ASN</th>
|
||||||
|
</tr>
|
||||||
{% for provider in selected_objects %}
|
{% for provider in selected_objects %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{% url 'circuits:provider' slug=provider.slug %}">{{ provider }}</a></td>
|
<td><a href="{% url 'circuits:provider' slug=provider.slug %}">{{ provider }}</a></td>
|
||||||
|
@ -186,18 +186,23 @@
|
|||||||
{% include 'dcim/inc/_ipaddress.html' %}
|
{% include 'dcim/inc/_ipaddress.html' %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
{% else %}
|
{% elif interfaces or mgmt_interfaces %}
|
||||||
<div class="panel-body text-muted">
|
<div class="panel-body text-muted">
|
||||||
None found
|
None assigned
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="panel-body">
|
||||||
|
<a href="{% url 'dcim:interface_add' pk=device.pk %}">Create an interface</a> to assign an IP.
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.ipam.add_ipaddress %}
|
{% if perms.ipam.add_ipaddress %}
|
||||||
<div class="panel-footer text-right">
|
{% if interfaces or mgmt_interfaces %}
|
||||||
<a href="{% url 'dcim:ipaddress_assign' pk=device.pk %}" class="btn btn-xs btn-primary">
|
<div class="panel-footer text-right">
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
<a href="{% url 'dcim:ipaddress_assign' pk=device.pk %}" class="btn btn-xs btn-primary">
|
||||||
Assign IP address
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Assign IP address
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
@ -210,7 +215,7 @@
|
|||||||
{% empty %}
|
{% empty %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5" class="alert-warning">
|
<td colspan="5" class="alert-warning">
|
||||||
<i class="fa fa-fw fa-warning"></i> No management interfaces defined!
|
<i class="fa fa-fw fa-warning"></i> No management interfaces defined
|
||||||
{% if perms.dcim.add_interface %}
|
{% if perms.dcim.add_interface %}
|
||||||
<a href="{% url 'dcim:interface_add' pk=device.pk %}?mgmt_only=1" class="btn btn-primary btn-xs pull-right"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span></a>
|
<a href="{% url 'dcim:interface_add' pk=device.pk %}?mgmt_only=1" class="btn btn-primary btn-xs pull-right"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -222,7 +227,7 @@
|
|||||||
{% empty %}
|
{% empty %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5" class="alert-warning">
|
<td colspan="5" class="alert-warning">
|
||||||
<i class="fa fa-fw fa-warning"></i> No console ports defined!
|
<i class="fa fa-fw fa-warning"></i> No console ports defined
|
||||||
{% if perms.dcim.add_consoleport %}
|
{% if perms.dcim.add_consoleport %}
|
||||||
<a href="{% url 'dcim:consoleport_add' pk=device.pk %}" class="btn btn-primary btn-xs pull-right"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span></a>
|
<a href="{% url 'dcim:consoleport_add' pk=device.pk %}" class="btn btn-primary btn-xs pull-right"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -235,7 +240,7 @@
|
|||||||
{% if not device.device_type.is_pdu %}
|
{% if not device.device_type.is_pdu %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5" class="alert-warning">
|
<td colspan="5" class="alert-warning">
|
||||||
<i class="fa fa-fw fa-warning"></i> No power ports defined!
|
<i class="fa fa-fw fa-warning"></i> No power ports defined
|
||||||
{% if perms.dcim.add_powerport %}
|
{% if perms.dcim.add_powerport %}
|
||||||
<a href="{% url 'dcim:powerport_add' pk=device.pk %}" class="btn btn-primary btn-xs pull-right"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span></a>
|
<a href="{% url 'dcim:powerport_add' pk=device.pk %}" class="btn btn-primary btn-xs pull-right"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -248,20 +253,17 @@
|
|||||||
<div class="panel-footer text-right">
|
<div class="panel-footer text-right">
|
||||||
{% if perms.dcim.add_interface %}
|
{% if perms.dcim.add_interface %}
|
||||||
<a href="{% url 'dcim:interface_add' pk=device.pk %}?mgmt_only=1" class="btn btn-xs btn-primary">
|
<a href="{% url 'dcim:interface_add' pk=device.pk %}?mgmt_only=1" class="btn btn-xs btn-primary">
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add interface
|
||||||
Add interface
|
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_consoleport %}
|
{% if perms.dcim.add_consoleport %}
|
||||||
<a href="{% url 'dcim:consoleport_add' pk=device.pk %}" class="btn btn-xs btn-primary">
|
<a href="{% url 'dcim:consoleport_add' pk=device.pk %}" class="btn btn-xs btn-primary">
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add console port
|
||||||
Add console
|
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_powerport and not device.device_type.is_pdu %}
|
{% if perms.dcim.add_powerport and not device.device_type.is_pdu %}
|
||||||
<a href="{% url 'dcim:powerport_add' pk=device.pk %}" class="btn btn-xs btn-primary">
|
<a href="{% url 'dcim:powerport_add' pk=device.pk %}" class="btn btn-xs btn-primary">
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add power port
|
||||||
Add power
|
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@ -312,6 +314,13 @@
|
|||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Device Bays</strong>
|
<strong>Device Bays</strong>
|
||||||
|
{% if perms.dcim.add_devicebay and device_bays|length > 10 %}
|
||||||
|
<div class="pull-right">
|
||||||
|
<a href="{% url 'dcim:devicebay_add' pk=device.pk %}" class="btn btn-primary btn-xs">
|
||||||
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add device bays
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<table class="table table-hover panel-body">
|
<table class="table table-hover panel-body">
|
||||||
{% for devicebay in device_bays %}
|
{% for devicebay in device_bays %}
|
||||||
@ -324,23 +333,19 @@
|
|||||||
</table>
|
</table>
|
||||||
{% if perms.dcim.add_devicebay or perms.dcim.delete_devicebay %}
|
{% if perms.dcim.add_devicebay or perms.dcim.delete_devicebay %}
|
||||||
<div class="panel-footer">
|
<div class="panel-footer">
|
||||||
<div class="row">
|
{% if device_bays and perms.dcim.delete_devicebay %}
|
||||||
<div class="col-md-6">
|
<button type="submit" class="btn btn-danger btn-xs">
|
||||||
{% if device_bays and perms.dcim.delete_devicebay %}
|
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete selected
|
||||||
<button type="submit" class="btn btn-xs btn-danger">
|
</button>
|
||||||
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete Selected
|
{% endif %}
|
||||||
</button>
|
{% if perms.dcim.add_devicebay %}
|
||||||
{% endif %}
|
<div class="pull-right">
|
||||||
|
<a href="{% url 'dcim:devicebay_add' pk=device.pk %}" class="btn btn-primary btn-xs">
|
||||||
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add device bays
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 text-right">
|
<div class="clearfix"></div>
|
||||||
{% if perms.dcim.add_devicebay %}
|
{% endif %}
|
||||||
<a href="{% url 'dcim:devicebay_add' pk=device.pk %}" class="btn btn-primary btn-xs">
|
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
|
||||||
Add device bay
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@ -356,6 +361,13 @@
|
|||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Interfaces</strong>
|
<strong>Interfaces</strong>
|
||||||
|
{% if perms.dcim.add_interface and interfaces|length > 10 %}
|
||||||
|
<div class="pull-right">
|
||||||
|
<a href="{% url 'dcim:interface_add' pk=device.pk %}" class="btn btn-primary btn-xs">
|
||||||
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add interfaces
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<table class="table table-hover panel-body">
|
<table class="table table-hover panel-body">
|
||||||
{% for iface in interfaces %}
|
{% for iface in interfaces %}
|
||||||
@ -368,23 +380,19 @@
|
|||||||
</table>
|
</table>
|
||||||
{% if perms.dcim.add_interface or perms.dcim.delete_interface %}
|
{% if perms.dcim.add_interface or perms.dcim.delete_interface %}
|
||||||
<div class="panel-footer">
|
<div class="panel-footer">
|
||||||
<div class="row">
|
{% if interfaces and perms.dcim.delete_interface %}
|
||||||
<div class="col-md-6">
|
<button type="submit" class="btn btn-danger btn-xs">
|
||||||
{% if interfaces and perms.dcim.delete_interface %}
|
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete selected
|
||||||
<button type="submit" class="btn btn-xs btn-danger">
|
</button>
|
||||||
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete Selected
|
{% endif %}
|
||||||
</button>
|
{% if perms.dcim.add_interface %}
|
||||||
{% endif %}
|
<div class="pull-right">
|
||||||
|
<a href="{% url 'dcim:interface_add' pk=device.pk %}" class="btn btn-primary btn-xs">
|
||||||
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add interfaces
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 text-right">
|
<div class="clearfix"></div>
|
||||||
{% if perms.dcim.add_interface %}
|
{% endif %}
|
||||||
<a href="{% url 'dcim:interface_add' pk=device.pk %}" class="btn btn-primary btn-xs">
|
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
|
||||||
Add interface
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@ -400,6 +408,13 @@
|
|||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Console Server Ports</strong>
|
<strong>Console Server Ports</strong>
|
||||||
|
{% if perms.dcim.add_consoleserverport and cs_ports|length > 10 %}
|
||||||
|
<div class="pull-right">
|
||||||
|
<a href="{% url 'dcim:consoleserverport_add' pk=device.pk %}" class="btn btn-primary btn-xs">
|
||||||
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add console server ports
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<table class="table table-hover panel-body">
|
<table class="table table-hover panel-body">
|
||||||
{% for csp in cs_ports %}
|
{% for csp in cs_ports %}
|
||||||
@ -412,23 +427,19 @@
|
|||||||
</table>
|
</table>
|
||||||
{% if perms.dcim.add_consoleserverport or perms.dcim.delete_consoleserverport %}
|
{% if perms.dcim.add_consoleserverport or perms.dcim.delete_consoleserverport %}
|
||||||
<div class="panel-footer">
|
<div class="panel-footer">
|
||||||
<div class="row">
|
{% if cs_ports and perms.dcim.delete_consoleserverport %}
|
||||||
<div class="col-md-6">
|
<button type="submit" class="btn btn-danger btn-xs">
|
||||||
{% if cs_ports and perms.dcim.delete_consoleserverport %}
|
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete selected
|
||||||
<button type="submit" class="btn btn-xs btn-danger">
|
</button>
|
||||||
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete Selected
|
{% endif %}
|
||||||
</button>
|
{% if perms.dcim.add_consoleserverport %}
|
||||||
{% endif %}
|
<div class="pull-right">
|
||||||
|
<a href="{% url 'dcim:consoleserverport_add' pk=device.pk %}" class="btn btn-primary btn-xs">
|
||||||
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add console server ports
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 text-right">
|
<div class="clearfix"></div>
|
||||||
{% if perms.dcim.add_consoleserverport %}
|
{% endif %}
|
||||||
<a href="{% url 'dcim:consoleserverport_add' pk=device.pk %}" class="btn btn-primary btn-xs">
|
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
|
||||||
Add console server ports
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@ -444,6 +455,13 @@
|
|||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Power Outlets</strong>
|
<strong>Power Outlets</strong>
|
||||||
|
{% if perms.dcim.add_poweroutlet and power_outlets|length > 10 %}
|
||||||
|
<div class="pull-right">
|
||||||
|
<a href="{% url 'dcim:poweroutlet_add' pk=device.pk %}" class="btn btn-primary btn-xs">
|
||||||
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add power outlets
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<table class="table table-hover panel-body">
|
<table class="table table-hover panel-body">
|
||||||
{% for po in power_outlets %}
|
{% for po in power_outlets %}
|
||||||
@ -456,23 +474,19 @@
|
|||||||
</table>
|
</table>
|
||||||
{% if perms.dcim.add_poweroutlet or perms.dcim.delete_poweroutlet %}
|
{% if perms.dcim.add_poweroutlet or perms.dcim.delete_poweroutlet %}
|
||||||
<div class="panel-footer">
|
<div class="panel-footer">
|
||||||
<div class="row">
|
{% if power_outlets and perms.dcim.delete_poweroutlet %}
|
||||||
<div class="col-md-6">
|
<button type="submit" class="btn btn-danger btn-xs">
|
||||||
{% if power_outlets and perms.dcim.delete_poweroutlet %}
|
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete selected
|
||||||
<button type="submit" class="btn btn-xs btn-danger">
|
</button>
|
||||||
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete Selected
|
{% endif %}
|
||||||
</button>
|
{% if perms.dcim.add_poweroutlet %}
|
||||||
{% endif %}
|
<div class="pull-right">
|
||||||
|
<a href="{% url 'dcim:poweroutlet_add' pk=device.pk %}" class="btn btn-primary btn-xs">
|
||||||
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add power outlets
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 text-right">
|
<div class="clearfix"></div>
|
||||||
{% if perms.dcim.add_poweroutlet %}
|
{% endif %}
|
||||||
<a href="{% url 'dcim:poweroutlet_add' pk=device.pk %}" class="btn btn-primary btn-xs">
|
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
|
||||||
Add power outlets
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@ -531,13 +545,13 @@ function toggleConnection(elem, api_url) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$(".consoleport-toggle").click(function() {
|
$(".consoleport-toggle").click(function() {
|
||||||
return toggleConnection($(this), "/api/dcim/console-ports/");
|
return toggleConnection($(this), "/{{ settings.BASE_PATH }}api/dcim/console-ports/");
|
||||||
});
|
});
|
||||||
$(".powerport-toggle").click(function() {
|
$(".powerport-toggle").click(function() {
|
||||||
return toggleConnection($(this), "/api/dcim/power-ports/");
|
return toggleConnection($(this), "/{{ settings.BASE_PATH }}api/dcim/power-ports/");
|
||||||
});
|
});
|
||||||
$(".interface-toggle").click(function() {
|
$(".interface-toggle").click(function() {
|
||||||
return toggleConnection($(this), "/api/dcim/interface-connections/");
|
return toggleConnection($(this), "/{{ settings.BASE_PATH }}api/dcim/interface-connections/");
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<script src="{% static 'js/graphs.js' %}"></script>
|
<script src="{% static 'js/graphs.js' %}"></script>
|
||||||
|
@ -3,7 +3,14 @@
|
|||||||
|
|
||||||
{% block title %}Device Bulk Edit{% endblock %}
|
{% block title %}Device Bulk Edit{% endblock %}
|
||||||
|
|
||||||
{% block select_objects_table %}
|
{% block selected_objects_table %}
|
||||||
|
<tr>
|
||||||
|
<th>Device</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Role</th>
|
||||||
|
<th>Tenant</th>
|
||||||
|
<th>Serial</th>
|
||||||
|
</tr>
|
||||||
{% for device in selected_objects %}
|
{% for device in selected_objects %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{% url 'dcim:device' pk=device.pk %}">{{ device }}</a></td>
|
<td><a href="{% url 'dcim:device' pk=device.pk %}">{{ device }}</a></td>
|
||||||
|
@ -3,11 +3,15 @@
|
|||||||
|
|
||||||
{% block title %}Device Type Bulk Edit{% endblock %}
|
{% block title %}Device Type Bulk Edit{% endblock %}
|
||||||
|
|
||||||
{% block select_objects_table %}
|
{% block selected_objects_table %}
|
||||||
|
<tr>
|
||||||
|
<th>Device type</th>
|
||||||
|
<th>Manufacturer</th>
|
||||||
|
<th>Height</th>
|
||||||
|
</tr>
|
||||||
{% for devicetype in selected_objects %}
|
{% for devicetype in selected_objects %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{% url 'dcim:devicetype' pk=devicetype.pk %}">{{ devicetype }}</a></td>
|
<td><a href="{% url 'dcim:devicetype' pk=devicetype.pk %}">{{ devicetype.model }}</a></td>
|
||||||
<td>{{ devicetype.model }}</td>
|
|
||||||
<td>{{ devicetype.manufacturer }}</td>
|
<td>{{ devicetype.manufacturer }}</td>
|
||||||
<td>{{ devicetype.u_height }}U</td>
|
<td>{{ devicetype.u_height }}U</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -1,30 +1,9 @@
|
|||||||
{% load render_table from django_tables2 %}
|
{% extends 'utilities/obj_table.html' %}
|
||||||
{% load helpers %}
|
|
||||||
{% if table.model|user_can_change:request.user or table.model|user_can_delete:request.user %}
|
{% block extra_actions %}
|
||||||
<form method="post" class="form form-horizontal">
|
{% if perms.dcim.add_interface %}
|
||||||
{% csrf_token %}
|
<button type="submit" name="_edit" formaction="{% url 'dcim:interface_add_multi' %}" class="btn btn-primary btn-sm">
|
||||||
<input type="hidden" name="redirect_url" value="{{ request.path }}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" />
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add Interfaces
|
||||||
<input type="hidden" name="pk_all" value="{% for row in table.rows %}{{ row.record.pk|default:'' }}{% if not forloop.last %},{% endif %}{% endfor %}" />
|
</button>
|
||||||
{% render_table table table_template|default:'table.html' %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_interface %}
|
{% endblock %}
|
||||||
<button type="submit" name="_edit" formaction="{% url 'dcim:interface_add_multi' %}" class="btn btn-primary btn-sm">
|
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
|
||||||
Add Interfaces
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% if bulk_edit_url and table.model|user_can_change:request.user %}
|
|
||||||
<button type="submit" name="_edit" formaction="{% url bulk_edit_url %}" class="btn btn-warning btn-sm">
|
|
||||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
|
|
||||||
Edit Selected
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% if bulk_delete_url and table.model|user_can_delete:request.user %}
|
|
||||||
<button type="submit" name="_delete" formaction="{% url bulk_delete_url %}" class="btn btn-danger btn-sm">
|
|
||||||
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
|
|
||||||
Delete Selected
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
|
||||||
{% else %}
|
|
||||||
{% render_table table table_template|default:'table.html' %}
|
|
||||||
{% endif %}
|
|
||||||
|
@ -4,11 +4,15 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<a href="{% url add_url pk=devicetype.pk %}{{ add_url_extra }}" class="btn btn-primary btn-xs pull-right">
|
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
|
||||||
Add {{ title }}
|
|
||||||
</a>
|
|
||||||
<strong>{{ title }}</strong>
|
<strong>{{ title }}</strong>
|
||||||
|
{% if table.rows|length > 10 %}
|
||||||
|
<div class="pull-right">
|
||||||
|
<a href="{% url add_url pk=devicetype.pk %}{{ add_url_extra }}" class="btn btn-primary btn-xs">
|
||||||
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||||
|
Add {{ title }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% render_table table 'table.html' %}
|
{% render_table table 'table.html' %}
|
||||||
{% if table.rows %}
|
{% if table.rows %}
|
||||||
@ -16,6 +20,12 @@
|
|||||||
<button type="submit" class="btn btn-xs btn-danger">
|
<button type="submit" class="btn btn-xs btn-danger">
|
||||||
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete Selected
|
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete Selected
|
||||||
</button>
|
</button>
|
||||||
|
<div class="pull-right">
|
||||||
|
<a href="{% url add_url pk=devicetype.pk %}{{ add_url_extra }}" class="btn btn-primary btn-xs">
|
||||||
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||||
|
Add {{ title }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,7 +7,12 @@
|
|||||||
|
|
||||||
{% block form_title %}Interface(s) to Add{% endblock %}
|
{% block form_title %}Interface(s) to Add{% endblock %}
|
||||||
|
|
||||||
{% block select_objects_table %}
|
{% block selected_objects_table %}
|
||||||
|
<tr>
|
||||||
|
<th>Device</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Role</th>
|
||||||
|
</tr>
|
||||||
{% for device in selected_objects %}
|
{% for device in selected_objects %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{% url 'dcim:device' pk=device.pk %}">{{ device }}</a></td>
|
<td><a href="{% url 'dcim:device' pk=device.pk %}">{{ device }}</a></td>
|
||||||
|
@ -3,12 +3,13 @@
|
|||||||
|
|
||||||
{% block title %}Rack Bulk Edit{% endblock %}
|
{% block title %}Rack Bulk Edit{% endblock %}
|
||||||
|
|
||||||
{% block select_objects_table %}
|
{% block selected_objects_table %}
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Site</th>
|
<th>Site</th>
|
||||||
<th>Group</th>
|
<th>Group</th>
|
||||||
<th>Tenant</th>
|
<th>Tenant</th>
|
||||||
|
<th>Role</th>
|
||||||
<th>Type</th>
|
<th>Type</th>
|
||||||
<th>Width</th>
|
<th>Width</th>
|
||||||
<th>Height</th>
|
<th>Height</th>
|
||||||
@ -19,6 +20,7 @@
|
|||||||
<td>{{ rack.site }}</td>
|
<td>{{ rack.site }}</td>
|
||||||
<td>{{ rack.group }}</td>
|
<td>{{ rack.group }}</td>
|
||||||
<td>{{ rack.tenant }}</td>
|
<td>{{ rack.tenant }}</td>
|
||||||
|
<td>{{ rack.role }}</td>
|
||||||
<td>{{ rack.get_type_display }}</td>
|
<td>{{ rack.get_type_display }}</td>
|
||||||
<td>{{ rack.get_width_display }}</td>
|
<td>{{ rack.get_width_display }}</td>
|
||||||
<td>{{ rack.u_height }}U</td>
|
<td>{{ rack.u_height }}U</td>
|
||||||
|
@ -3,7 +3,11 @@
|
|||||||
|
|
||||||
{% block title %}Site Bulk Edit{% endblock %}
|
{% block title %}Site Bulk Edit{% endblock %}
|
||||||
|
|
||||||
{% block select_objects_table %}
|
{% block selected_objects_table %}
|
||||||
|
<tr>
|
||||||
|
<th>Site</th>
|
||||||
|
<th>Tenant</th>
|
||||||
|
</tr>
|
||||||
{% for site in selected_objects %}
|
{% for site in selected_objects %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{% url 'dcim:site' slug=site.slug %}">{{ site }}</a></td>
|
<td><a href="{% url 'dcim:site' slug=site.slug %}">{{ site }}</a></td>
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<li><a href="?{% if request.GET %}{{ request.GET.urlencode }}&{% endif %}export">CSV (default)</a></li>
|
<li><a href="?{% if request.GET %}{{ request.GET.urlencode }}&{% endif %}export">CSV (default)</a></li>
|
||||||
<li class="divider"></li>
|
<li class="divider"></li>
|
||||||
{% for et in export_templates %}
|
{% for et in export_templates %}
|
||||||
<li><a href="?{% if request.GET %}{{ request.GET.urlencode }}&{% endif %}export={{ et.name }}">{{ et.name }}</a></li>
|
<li><a href="?{% if request.GET %}{{ request.GET.urlencode }}&{% endif %}export={{ et.name }}"{% if et.description %} title="{{ et.description }}"{% endif %}>{{ et.name }}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,7 +3,13 @@
|
|||||||
|
|
||||||
{% block title %}Aggregate Bulk Edit{% endblock %}
|
{% block title %}Aggregate Bulk Edit{% endblock %}
|
||||||
|
|
||||||
{% block select_objects_table %}
|
{% block selected_objects_table %}
|
||||||
|
<tr>
|
||||||
|
<th>Aggregate</th>
|
||||||
|
<th>RIR</th>
|
||||||
|
<th>Date Added</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
{% for aggregate in selected_objects %}
|
{% for aggregate in selected_objects %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{% url 'ipam:aggregate' pk=aggregate.pk %}">{{ aggregate }}</a></td>
|
<td><a href="{% url 'ipam:aggregate' pk=aggregate.pk %}">{{ aggregate }}</a></td>
|
||||||
|
@ -3,14 +3,20 @@
|
|||||||
|
|
||||||
{% block title %}IP Address Bulk Edit{% endblock %}
|
{% block title %}IP Address Bulk Edit{% endblock %}
|
||||||
|
|
||||||
{% block select_objects_table %}
|
{% block selected_objects_table %}
|
||||||
|
<tr>
|
||||||
|
<th>IP Address</th>
|
||||||
|
<th>VRF</th>
|
||||||
|
<th>Tenant</th>
|
||||||
|
<th>Assigned</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
{% for ipaddress in selected_objects %}
|
{% for ipaddress in selected_objects %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{% url 'ipam:ipaddress' pk=ipaddress.pk %}">{{ ipaddress }}</a></td>
|
<td><a href="{% url 'ipam:ipaddress' pk=ipaddress.pk %}">{{ ipaddress }}</a></td>
|
||||||
<td>{{ ipaddress.vrf|default:"Global" }}</td>
|
<td>{{ ipaddress.vrf|default:"Global" }}</td>
|
||||||
<td>{{ ipaddress.tenant }}</td>
|
<td>{{ ipaddress.tenant }}</td>
|
||||||
<td>{{ ipaddress.interface.device }}</td>
|
<td>{% if ipaddress.interface %}<i class="glyphicon glyphicon-ok text-success" title="{{ ipaddress.interface.device }} {{ ipaddress.interface }}"></i>{% endif %}</td>
|
||||||
<td>{{ ipaddress.interface }}</td>
|
|
||||||
<td>{{ ipaddress.description }}</td>
|
<td>{{ ipaddress.description }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -3,16 +3,23 @@
|
|||||||
|
|
||||||
{% block title %}Prefix Bulk Edit{% endblock %}
|
{% block title %}Prefix Bulk Edit{% endblock %}
|
||||||
|
|
||||||
{% block select_objects_table %}
|
{% block selected_objects_table %}
|
||||||
|
<tr>
|
||||||
|
<th>Prefix</th>
|
||||||
|
<th>Site</th>
|
||||||
|
<th>VRF</th>
|
||||||
|
<th>Tenant</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Role</th>
|
||||||
|
</tr>
|
||||||
{% for prefix in selected_objects %}
|
{% for prefix in selected_objects %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{% url 'ipam:prefix' pk=prefix.pk %}">{{ prefix }}</a></td>
|
<td><a href="{% url 'ipam:prefix' pk=prefix.pk %}">{{ prefix }}</a></td>
|
||||||
|
<td>{{ prefix.site }}</td>
|
||||||
<td>{{ prefix.vrf|default:"Global" }}</td>
|
<td>{{ prefix.vrf|default:"Global" }}</td>
|
||||||
<td>{{ prefix.tenant }}</td>
|
<td>{{ prefix.tenant }}</td>
|
||||||
<td>{{ prefix.site }}</td>
|
<td>{{ prefix.get_status_display }}</td>
|
||||||
<td>{{ prefix.status }}</td>
|
|
||||||
<td>{{ prefix.role }}</td>
|
<td>{{ prefix.role }}</td>
|
||||||
<td>{{ prefix.description }}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -3,16 +3,23 @@
|
|||||||
|
|
||||||
{% block title %}VLAN Bulk Edit{% endblock %}
|
{% block title %}VLAN Bulk Edit{% endblock %}
|
||||||
|
|
||||||
{% block select_objects_table %}
|
{% block selected_objects_table %}
|
||||||
|
<tr>
|
||||||
|
<th>VLAN</th>
|
||||||
|
<th>Site</th>
|
||||||
|
<th>Group</th>
|
||||||
|
<th>Tenant</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Role</th>
|
||||||
|
</tr>
|
||||||
{% for vlan in selected_objects %}
|
{% for vlan in selected_objects %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{% url 'ipam:vlan' pk=vlan.pk %}">{{ vlan.vid }}</a></td>
|
<td><a href="{% url 'ipam:vlan' pk=vlan.pk %}">{{ vlan }}</a></td>
|
||||||
<td>{{ vlan.name }}</td>
|
|
||||||
<td>{{ vlan.site }}</td>
|
<td>{{ vlan.site }}</td>
|
||||||
|
<td>{{ vlan.group }}</td>
|
||||||
<td>{{ vlan.tenant }}</td>
|
<td>{{ vlan.tenant }}</td>
|
||||||
<td>{{ vlan.get_status_display }}</td>
|
<td>{{ vlan.get_status_display }}</td>
|
||||||
<td>{{ vlan.role }}</td>
|
<td>{{ vlan.role }}</td>
|
||||||
<td>{{ vlan.description }}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -3,7 +3,13 @@
|
|||||||
|
|
||||||
{% block title %}VRF Bulk Edit{% endblock %}
|
{% block title %}VRF Bulk Edit{% endblock %}
|
||||||
|
|
||||||
{% block select_objects_table %}
|
{% block selected_objects_table %}
|
||||||
|
<tr>
|
||||||
|
<th>VRF</th>
|
||||||
|
<th>RD</th>
|
||||||
|
<th>Tenant</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
{% for vrf in selected_objects %}
|
{% for vrf in selected_objects %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{% url 'ipam:vrf' pk=vrf.pk %}">{{ vrf.name }}</a></td>
|
<td><a href="{% url 'ipam:vrf' pk=vrf.pk %}">{{ vrf.name }}</a></td>
|
||||||
|
@ -3,11 +3,15 @@
|
|||||||
|
|
||||||
{% block title %}Secret Bulk Edit{% endblock %}
|
{% block title %}Secret Bulk Edit{% endblock %}
|
||||||
|
|
||||||
{% block select_objects_table %}
|
{% block selected_objects_table %}
|
||||||
|
<tr>
|
||||||
|
<th>Device</th>
|
||||||
|
<th>Role</th>
|
||||||
|
<th>Name</th>
|
||||||
|
</tr>
|
||||||
{% for secret in selected_objects %}
|
{% for secret in selected_objects %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{% url 'secrets:secret' pk=secret.pk %}">{{ secret }}</a></td>
|
<td><a href="{% url 'secrets:secret' pk=secret.pk %}">{{ secret.device }}</a></td>
|
||||||
<td>{{ secret.device }}</td>
|
|
||||||
<td>{{ secret.role }}</td>
|
<td>{{ secret.role }}</td>
|
||||||
<td>{{ secret.name }}</td>
|
<td>{{ secret.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -3,7 +3,11 @@
|
|||||||
|
|
||||||
{% block title %}Tenant Bulk Edit{% endblock %}
|
{% block title %}Tenant Bulk Edit{% endblock %}
|
||||||
|
|
||||||
{% block select_objects_table %}
|
{% block selected_objects_table %}
|
||||||
|
<tr>
|
||||||
|
<th>Tenant</th>
|
||||||
|
<th>Group</th>
|
||||||
|
</tr>
|
||||||
{% for tenant in selected_objects %}
|
{% for tenant in selected_objects %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{% url 'tenancy:tenant' slug=tenant.slug %}">{{ tenant }}</a></td>
|
<td><a href="{% url 'tenancy:tenant' slug=tenant.slug %}">{{ tenant }}</a></td>
|
||||||
|
@ -11,9 +11,9 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-7">
|
<div class="col-md-7">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading"><strong>{% block selected_objects_title %}Selected For Editing{% endblock %}</strong></div>
|
<div class="panel-heading"><strong>{% block selected_objects_title %}{{ selected_objects|length }} Selected For Editing{% endblock %}</strong></div>
|
||||||
<table class="panel-body table table-hover">
|
<table class="panel-body table table-hover">
|
||||||
{% block select_objects_table %}{% endblock %}
|
{% block selected_objects_table %}{% endblock %}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,17 +5,26 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="redirect_url" value="{{ request.path }}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" />
|
<input type="hidden" name="redirect_url" value="{{ request.path }}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" />
|
||||||
<input type="hidden" name="pk_all" value="{% for row in table.rows %}{{ row.record.pk|default:'' }}{% if not forloop.last %},{% endif %}{% endfor %}" />
|
<input type="hidden" name="pk_all" value="{% for row in table.rows %}{{ row.record.pk|default:'' }}{% if not forloop.last %},{% endif %}{% endfor %}" />
|
||||||
|
{% if table.paginator.num_pages > 1 %}
|
||||||
|
<div id="select_all_box" class="hidden alert alert-info">
|
||||||
|
<div class="checkbox-inline">
|
||||||
|
<label for="select_all">
|
||||||
|
<input type="checkbox" id="select_all" name="_all" />
|
||||||
|
Select <strong>all {{ table.rows|length }} {{ table.data.verbose_name_plural }}</strong> matching query
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% render_table table table_template|default:'table.html' %}
|
{% render_table table table_template|default:'table.html' %}
|
||||||
|
{% block extra_actions %}{% endblock %}
|
||||||
{% if bulk_edit_url and table.model|user_can_change:request.user %}
|
{% if bulk_edit_url and table.model|user_can_change:request.user %}
|
||||||
<button type="submit" name="_edit" formaction="{% url bulk_edit_url %}" class="btn btn-warning btn-sm">
|
<button type="submit" name="_edit" formaction="{% url bulk_edit_url %}" class="btn btn-warning btn-sm">
|
||||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
|
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit Selected
|
||||||
Edit Selected
|
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if bulk_delete_url and table.model|user_can_delete:request.user %}
|
{% if bulk_delete_url and table.model|user_can_delete:request.user %}
|
||||||
<button type="submit" name="_delete" formaction="{% url bulk_delete_url %}" class="btn btn-danger btn-sm">
|
<button type="submit" name="_delete" formaction="{% url bulk_delete_url %}" class="btn btn-danger btn-sm">
|
||||||
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
|
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete Selected
|
||||||
Delete Selected
|
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
|
@ -3,7 +3,9 @@ import itertools
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.conf import settings
|
||||||
from django.core.urlresolvers import reverse_lazy
|
from django.core.urlresolvers import reverse_lazy
|
||||||
|
from django.core.validators import URLValidator
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
@ -90,7 +92,7 @@ class APISelect(SelectWithDisabled):
|
|||||||
super(APISelect, self).__init__(*args, **kwargs)
|
super(APISelect, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.attrs['class'] = 'api-select'
|
self.attrs['class'] = 'api-select'
|
||||||
self.attrs['api-url'] = api_url
|
self.attrs['api-url'] = '/{}{}'.format(settings.BASE_PATH, api_url.lstrip('/')) # Inject BASE_PATH
|
||||||
if display_field:
|
if display_field:
|
||||||
self.attrs['display-field'] = display_field
|
self.attrs['display-field'] = display_field
|
||||||
if disabled_indicator:
|
if disabled_indicator:
|
||||||
@ -253,6 +255,21 @@ class FilterChoiceField(forms.ModelMultipleChoiceField):
|
|||||||
choices = property(_get_choices, forms.ChoiceField._set_choices)
|
choices = property(_get_choices, forms.ChoiceField._set_choices)
|
||||||
|
|
||||||
|
|
||||||
|
class LaxURLField(forms.URLField):
|
||||||
|
"""
|
||||||
|
Custom URLField which allows any valid URL scheme
|
||||||
|
"""
|
||||||
|
|
||||||
|
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())]
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Forms
|
# Forms
|
||||||
#
|
#
|
||||||
|
@ -27,4 +27,4 @@ class ToggleColumn(tables.CheckBoxColumn):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def header(self):
|
def header(self):
|
||||||
return mark_safe('<input type="checkbox" name="_all" title="Select all" />')
|
return mark_safe('<input type="checkbox" id="toggle_all" title="Toggle all" />')
|
||||||
|
Loading…
Reference in New Issue
Block a user