mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-23 17:08:41 -06:00
commit
66be85a41f
@ -43,8 +43,9 @@ take some time for someone to address your issue.
|
|||||||
* First, check the GitHub [issues list](https://github.com/digitalocean/netbox/issues) to see if the feature you're
|
* First, check the GitHub [issues list](https://github.com/digitalocean/netbox/issues) to see if the feature you're
|
||||||
requesting is already listed. (Be sure to search closed issues as well, since some feature requests are rejected.) If
|
requesting is already listed. (Be sure to search closed issues as well, since some feature requests are rejected.) If
|
||||||
the feature you'd like to see has already been requested, click "add a reaction" in the top right corner of the issue
|
the feature you'd like to see has already been requested, click "add a reaction" in the top right corner of the issue
|
||||||
and add a thumbs up (+1). This ensures that the issue has a better chance of making it onto the roadmap. Also feel free
|
and add a thumbs up. 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.
|
to add a comment with any additional justification for the feature. (However, note that comments with no substance
|
||||||
|
other than a "+1" will be deleted as spam. Please use GitHub's reactions feature to indicate your support.)
|
||||||
|
|
||||||
* While suggestions for new features are welcome, it's important to limit the scope of NetBox's feature set to avoid
|
* While suggestions for new features are 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,10 +4,10 @@ This guide demonstrates how to build and run NetBox as a Docker container. It as
|
|||||||
|
|
||||||
To get NetBox up and running:
|
To get NetBox up and running:
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
git clone -b master https://github.com/digitalocean/netbox.git
|
# git clone -b master https://github.com/digitalocean/netbox.git
|
||||||
cd netbox
|
# cd netbox
|
||||||
docker-compose up -d
|
# docker-compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
The application will be available on http://localhost/ after a few minutes.
|
The application will be available on http://localhost/ after a few minutes.
|
||||||
|
@ -7,19 +7,19 @@ built-in Django users in the event of a failure.
|
|||||||
|
|
||||||
On Ubuntu:
|
On Ubuntu:
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
sudo apt-get install -y python-dev libldap2-dev libsasl2-dev libssl-dev
|
sudo apt-get install -y python-dev libldap2-dev libsasl2-dev libssl-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
On CentOS:
|
On CentOS:
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
sudo yum install -y python-devel openldap-devel
|
sudo yum install -y python-devel openldap-devel
|
||||||
```
|
```
|
||||||
|
|
||||||
## Install django-auth-ldap
|
## Install django-auth-ldap
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
sudo pip install django-auth-ldap
|
sudo pip install django-auth-ldap
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
**Debian/Ubuntu**
|
**Debian/Ubuntu**
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# 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**
|
**CentOS/RHEL**
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# yum install -y epel-release
|
# yum install -y epel-release
|
||||||
# yum install -y gcc python2 python-devel python-pip libxml2-devel libxslt-devel libffi-devel graphviz openssl-devel
|
# yum install -y gcc python2 python-devel python-pip libxml2-devel libxslt-devel libffi-devel graphviz openssl-devel
|
||||||
```
|
```
|
||||||
@ -19,7 +19,7 @@ You may opt to install NetBox either from a numbered release or by cloning the m
|
|||||||
|
|
||||||
Download the [latest stable release](https://github.com/digitalocean/netbox/releases) from GitHub as a tarball or ZIP archive and extract it to your desired path. In this example, we'll use `/opt/netbox`.
|
Download the [latest stable release](https://github.com/digitalocean/netbox/releases) from GitHub as a tarball or ZIP archive and extract it to your desired path. In this example, we'll use `/opt/netbox`.
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# 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/
|
||||||
@ -31,28 +31,27 @@ Download the [latest stable release](https://github.com/digitalocean/netbox/rele
|
|||||||
|
|
||||||
Create the base directory for the NetBox installation. For this guide, we'll use `/opt/netbox`.
|
Create the base directory for the NetBox installation. For this guide, we'll use `/opt/netbox`.
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# mkdir -p /opt/netbox/
|
# mkdir -p /opt/netbox/ && cd /opt/netbox/
|
||||||
# cd /opt/netbox/
|
|
||||||
```
|
```
|
||||||
|
|
||||||
If `git` is not already installed, install it:
|
If `git` is not already installed, install it:
|
||||||
|
|
||||||
**Debian/Ubuntu**
|
**Debian/Ubuntu**
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# apt-get install -y git
|
# apt-get install -y git
|
||||||
```
|
```
|
||||||
|
|
||||||
**CentOS/RHEL**
|
**CentOS/RHEL**
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# yum install -y git
|
# 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:
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# git clone -b master https://github.com/digitalocean/netbox.git .
|
# git clone -b master https://github.com/digitalocean/netbox.git .
|
||||||
Cloning into '.'...
|
Cloning into '.'...
|
||||||
remote: Counting objects: 1994, done.
|
remote: Counting objects: 1994, done.
|
||||||
@ -67,7 +66,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.)
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# pip install -r requirements.txt
|
# pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -75,7 +74,7 @@ Install the required Python packages using pip. (If you encounter any compilatio
|
|||||||
|
|
||||||
Move into the NetBox configuration directory and make a copy of `configuration.example.py` named `configuration.py`.
|
Move into the NetBox configuration directory and make a copy of `configuration.example.py` named `configuration.py`.
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# cd netbox/netbox/
|
# cd netbox/netbox/
|
||||||
# cp configuration.example.py configuration.py
|
# cp configuration.example.py configuration.py
|
||||||
```
|
```
|
||||||
@ -92,7 +91,7 @@ This is a list of the valid hostnames by which this server can be reached. You m
|
|||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```
|
```python
|
||||||
ALLOWED_HOSTS = ['netbox.example.com', '192.0.2.123']
|
ALLOWED_HOSTS = ['netbox.example.com', '192.0.2.123']
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -102,7 +101,7 @@ This parameter holds the database configuration details. You must define the use
|
|||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```
|
```python
|
||||||
DATABASE = {
|
DATABASE = {
|
||||||
'NAME': 'netbox', # Database name
|
'NAME': 'netbox', # Database name
|
||||||
'USER': 'netbox', # PostgreSQL username
|
'USER': 'netbox', # PostgreSQL username
|
||||||
@ -125,7 +124,7 @@ You may use the script located at `netbox/generate_secret_key.py` to generate a
|
|||||||
|
|
||||||
Before NetBox can run, we need to install the database schema. This is done by running `./manage.py migrate` from the `netbox` directory (`/opt/netbox/netbox/` in our example):
|
Before NetBox can run, we need to install the database schema. This is done by running `./manage.py migrate` from the `netbox` directory (`/opt/netbox/netbox/` in our example):
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# cd /opt/netbox/netbox/
|
# cd /opt/netbox/netbox/
|
||||||
# ./manage.py migrate
|
# ./manage.py migrate
|
||||||
Operations to perform:
|
Operations to perform:
|
||||||
@ -144,7 +143,7 @@ If this step results in a PostgreSQL authentication error, ensure that the usern
|
|||||||
|
|
||||||
NetBox does not come with any predefined user accounts. You'll need to create a super user to be able to log into NetBox:
|
NetBox does not come with any predefined user accounts. You'll need to create a super user to be able to log into NetBox:
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# ./manage.py createsuperuser
|
# ./manage.py createsuperuser
|
||||||
Username: admin
|
Username: admin
|
||||||
Email address: admin@example.com
|
Email address: admin@example.com
|
||||||
@ -155,7 +154,7 @@ Superuser created successfully.
|
|||||||
|
|
||||||
# Collect Static Files
|
# Collect Static Files
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# ./manage.py collectstatic
|
# ./manage.py collectstatic
|
||||||
|
|
||||||
You have requested to collect static files at the destination
|
You have requested to collect static files at the destination
|
||||||
@ -176,7 +175,7 @@ NetBox ships with some initial data to help you get started: RIR definitions, co
|
|||||||
!!! note
|
!!! note
|
||||||
This step is optional. It's perfectly fine to start using NetBox without using this initial data if you'd rather create everything from scratch.
|
This step is optional. It's perfectly fine to start using NetBox without using this initial data if you'd rather create everything from scratch.
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# ./manage.py loaddata initial_data
|
# ./manage.py loaddata initial_data
|
||||||
Installed 43 object(s) from 4 fixture(s)
|
Installed 43 object(s) from 4 fixture(s)
|
||||||
```
|
```
|
||||||
@ -185,7 +184,7 @@ Installed 43 object(s) from 4 fixture(s)
|
|||||||
|
|
||||||
At this point, NetBox should be able to run. We can verify this by starting a development instance:
|
At this point, NetBox should be able to run. We can verify this by starting a development instance:
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# ./manage.py runserver 0.0.0.0:8000 --insecure
|
# ./manage.py runserver 0.0.0.0:8000 --insecure
|
||||||
Performing system checks...
|
Performing system checks...
|
||||||
|
|
||||||
|
@ -4,27 +4,27 @@ NetBox requires a PostgreSQL database to store data. MySQL is not supported, as
|
|||||||
|
|
||||||
**Debian/Ubuntu**
|
**Debian/Ubuntu**
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# apt-get install -y postgresql libpq-dev python-psycopg2
|
# apt-get install -y postgresql libpq-dev python-psycopg2
|
||||||
```
|
```
|
||||||
|
|
||||||
**CentOS/RHEL**
|
**CentOS/RHEL**
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# yum install -y postgresql postgresql-server postgresql-devel python-psycopg2
|
# yum install -y postgresql postgresql-server postgresql-devel python-psycopg2
|
||||||
# postgresql-setup initdb
|
# 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:
|
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:
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
host all all 127.0.0.1/32 md5
|
host all all 127.0.0.1/32 md5
|
||||||
host all all ::1/128 md5
|
host all all ::1/128 md5
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, start the service:
|
Then, start the service:
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# systemctl start postgresql
|
# systemctl start postgresql
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ At a minimum, we need to create a database for NetBox and assign it a username a
|
|||||||
!!! danger
|
!!! danger
|
||||||
DO NOT USE THE PASSWORD FROM THE EXAMPLE.
|
DO NOT USE THE PASSWORD FROM THE EXAMPLE.
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# sudo -u postgres psql
|
# sudo -u postgres psql
|
||||||
psql (9.3.13)
|
psql (9.3.13)
|
||||||
Type "help" for help.
|
Type "help" for help.
|
||||||
@ -51,7 +51,7 @@ postgres=# \q
|
|||||||
|
|
||||||
You can verify that authentication works issuing the following command and providing the configured password:
|
You can verify that authentication works issuing the following command and providing the configured password:
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# psql -U netbox -h localhost -W
|
# psql -U netbox -h localhost -W
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ Download the [latest stable release](https://github.com/digitalocean/netbox/rele
|
|||||||
|
|
||||||
Download and extract the latest version:
|
Download and extract the latest version:
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# 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/
|
||||||
@ -17,13 +17,13 @@ Download and extract the latest version:
|
|||||||
|
|
||||||
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:
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# cp /opt/netbox-X.Y.Z/netbox/netbox/configuration.py /opt/netbox/netbox/netbox/configuration.py
|
# cp /opt/netbox-X.Y.Z/netbox/netbox/configuration.py /opt/netbox/netbox/netbox/configuration.py
|
||||||
```
|
```
|
||||||
|
|
||||||
If you followed the original installation guide to set up gunicorn, be sure to copy its configuration as well:
|
If you followed the original installation guide to set up gunicorn, be sure to copy its configuration as well:
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# cp /opt/netbox-X.Y.Z/gunicorn_config.py /opt/netbox/gunicorn_config.py
|
# cp /opt/netbox-X.Y.Z/gunicorn_config.py /opt/netbox/gunicorn_config.py
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ If you followed the original installation guide to set up gunicorn, be sure to c
|
|||||||
|
|
||||||
This guide assumes that NetBox is installed at `/opt/netbox`. Pull down the most recent iteration of the master branch:
|
This guide assumes that NetBox is installed at `/opt/netbox`. Pull down the most recent iteration of the master branch:
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# cd /opt/netbox
|
# cd /opt/netbox
|
||||||
# git checkout master
|
# git checkout master
|
||||||
# git pull origin master
|
# git pull origin master
|
||||||
@ -42,7 +42,7 @@ This guide assumes that NetBox is installed at `/opt/netbox`. Pull down the most
|
|||||||
|
|
||||||
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).
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# ./upgrade.sh
|
# ./upgrade.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -56,6 +56,6 @@ This script:
|
|||||||
|
|
||||||
Finally, restart the WSGI service to run the new code. If you followed this guide for the initial installation, this is done using `supervisorctl`:
|
Finally, restart the WSGI service to run the new code. If you followed this guide for the initial installation, this is done using `supervisorctl`:
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# sudo supervisorctl restart netbox
|
# sudo supervisorctl restart netbox
|
||||||
```
|
```
|
||||||
|
@ -5,7 +5,7 @@ We'll set up a simple WSGI front end using [gunicorn](http://gunicorn.org/) for
|
|||||||
!!! info
|
!!! 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.
|
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.
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# apt-get install -y gunicorn supervisor
|
# apt-get install -y gunicorn supervisor
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -13,13 +13,13 @@ 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.
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# apt-get install -y nginx
|
# apt-get install -y nginx
|
||||||
```
|
```
|
||||||
|
|
||||||
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`.)
|
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`.)
|
||||||
|
|
||||||
```
|
```nginx
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ server {
|
|||||||
|
|
||||||
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.
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# cd /etc/nginx/sites-enabled/
|
# cd /etc/nginx/sites-enabled/
|
||||||
# rm default
|
# rm default
|
||||||
# ln -s /etc/nginx/sites-available/netbox
|
# ln -s /etc/nginx/sites-available/netbox
|
||||||
@ -51,7 +51,7 @@ Then, delete `/etc/nginx/sites-enabled/default` and create a symlink in the `sit
|
|||||||
|
|
||||||
Restart the nginx service to use the new configuration.
|
Restart the nginx service to use the new configuration.
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# service nginx restart
|
# service nginx restart
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -59,13 +59,13 @@ To enable SSL, consider this guide on [securing nginx with Let's Encrypt](https:
|
|||||||
|
|
||||||
## Option B: Apache
|
## Option B: Apache
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# 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):
|
||||||
|
|
||||||
```
|
```apache
|
||||||
<VirtualHost *:80>
|
<VirtualHost *:80>
|
||||||
ProxyPreserveHost On
|
ProxyPreserveHost On
|
||||||
|
|
||||||
@ -90,7 +90,7 @@ Once Apache is installed, proceed with the following configuration (Be sure to m
|
|||||||
|
|
||||||
Save the contents of the above example in `/etc/apache2/sites-available/netbox.conf`, enable the `proxy` and `proxy_http` modules, and reload Apache:
|
Save the contents of the above example in `/etc/apache2/sites-available/netbox.conf`, enable the `proxy` and `proxy_http` modules, and reload Apache:
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# a2enmod proxy
|
# a2enmod proxy
|
||||||
# a2enmod proxy_http
|
# a2enmod proxy_http
|
||||||
# a2ensite netbox
|
# a2ensite netbox
|
||||||
@ -103,7 +103,7 @@ To enable SSL, consider this guide on [securing Apache with Let's Encrypt](https
|
|||||||
|
|
||||||
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`.
|
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`.
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
command = '/usr/bin/gunicorn'
|
command = '/usr/bin/gunicorn'
|
||||||
pythonpath = '/opt/netbox/netbox'
|
pythonpath = '/opt/netbox/netbox'
|
||||||
bind = '127.0.0.1:8001'
|
bind = '127.0.0.1:8001'
|
||||||
@ -115,7 +115,7 @@ user = 'www-data'
|
|||||||
|
|
||||||
Save the following as `/etc/supervisor/conf.d/netbox.conf`. Update the `command` and `directory` paths as needed.
|
Save the following as `/etc/supervisor/conf.d/netbox.conf`. Update the `command` and `directory` paths as needed.
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
[program:netbox]
|
[program:netbox]
|
||||||
command = gunicorn -c /opt/netbox/gunicorn_config.py netbox.wsgi
|
command = gunicorn -c /opt/netbox/gunicorn_config.py netbox.wsgi
|
||||||
directory = /opt/netbox/netbox/
|
directory = /opt/netbox/netbox/
|
||||||
@ -124,7 +124,7 @@ user = www-data
|
|||||||
|
|
||||||
Then, restart the supervisor service to detect and run the gunicorn service:
|
Then, restart the supervisor service to detect and run the gunicorn service:
|
||||||
|
|
||||||
```
|
```no-highlight
|
||||||
# service supervisor restart
|
# service supervisor restart
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ class ProviderBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
|||||||
portal_url = forms.URLField(required=False, label='Portal')
|
portal_url = forms.URLField(required=False, label='Portal')
|
||||||
noc_contact = forms.CharField(required=False, widget=SmallTextarea, label='NOC contact')
|
noc_contact = forms.CharField(required=False, widget=SmallTextarea, label='NOC contact')
|
||||||
admin_contact = forms.CharField(required=False, widget=SmallTextarea, label='Admin contact')
|
admin_contact = forms.CharField(required=False, widget=SmallTextarea, label='Admin contact')
|
||||||
comments = CommentField()
|
comments = CommentField(widget=SmallTextarea)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
nullable_fields = ['asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments']
|
nullable_fields = ['asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments']
|
||||||
@ -183,7 +183,7 @@ class CircuitBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
|||||||
tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
|
tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
|
||||||
port_speed = forms.IntegerField(required=False, label='Port speed (Kbps)')
|
port_speed = forms.IntegerField(required=False, label='Port speed (Kbps)')
|
||||||
commit_rate = forms.IntegerField(required=False, label='Commit rate (Kbps)')
|
commit_rate = forms.IntegerField(required=False, label='Commit rate (Kbps)')
|
||||||
comments = CommentField()
|
comments = CommentField(widget=SmallTextarea)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
nullable_fields = ['tenant', 'port_speed', 'commit_rate', 'comments']
|
nullable_fields = ['tenant', 'port_speed', 'commit_rate', 'comments']
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import django_filters
|
import django_filters
|
||||||
|
from netaddr.core import AddrFormatError
|
||||||
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
@ -146,6 +147,10 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
action='search',
|
action='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
)
|
)
|
||||||
|
mac_address = django_filters.MethodFilter(
|
||||||
|
action='_mac_address',
|
||||||
|
label='MAC address',
|
||||||
|
)
|
||||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='rack__site',
|
name='rack__site',
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
@ -254,6 +259,12 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
Q(comments__icontains=value)
|
Q(comments__icontains=value)
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
|
def _mac_address(self, queryset, value):
|
||||||
|
try:
|
||||||
|
return queryset.filter(interfaces__mac_address=value.strip()).distinct()
|
||||||
|
except AddrFormatError:
|
||||||
|
return queryset.none()
|
||||||
|
|
||||||
|
|
||||||
class ConsolePortFilter(django_filters.FilterSet):
|
class ConsolePortFilter(django_filters.FilterSet):
|
||||||
device_id = django_filters.ModelMultipleChoiceFilter(
|
device_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"name": "Console Server",
|
"name": "Console Server",
|
||||||
"slug": "console-server",
|
"slug": "console-server",
|
||||||
"color": "teal"
|
"color": "009688"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -14,7 +14,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"name": "Core Switch",
|
"name": "Core Switch",
|
||||||
"slug": "core-switch",
|
"slug": "core-switch",
|
||||||
"color": "blue"
|
"color": "2196f3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -23,7 +23,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"name": "Distribution Switch",
|
"name": "Distribution Switch",
|
||||||
"slug": "distribution-switch",
|
"slug": "distribution-switch",
|
||||||
"color": "blue"
|
"color": "2196f3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -32,7 +32,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"name": "Access Switch",
|
"name": "Access Switch",
|
||||||
"slug": "access-switch",
|
"slug": "access-switch",
|
||||||
"color": "blue"
|
"color": "2196f3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -41,7 +41,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"name": "Management Switch",
|
"name": "Management Switch",
|
||||||
"slug": "management-switch",
|
"slug": "management-switch",
|
||||||
"color": "orange"
|
"color": "ff9800"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -50,7 +50,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"name": "Firewall",
|
"name": "Firewall",
|
||||||
"slug": "firewall",
|
"slug": "firewall",
|
||||||
"color": "red"
|
"color": "f44336"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -59,7 +59,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"name": "Router",
|
"name": "Router",
|
||||||
"slug": "router",
|
"slug": "router",
|
||||||
"color": "purple"
|
"color": "9c27b0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -68,7 +68,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"name": "Server",
|
"name": "Server",
|
||||||
"slug": "server",
|
"slug": "server",
|
||||||
"color": "medium_gray"
|
"color": "9e9e9e"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -77,7 +77,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"name": "PDU",
|
"name": "PDU",
|
||||||
"slug": "pdu",
|
"slug": "pdu",
|
||||||
"color": "dark_gray"
|
"color": "607d8b"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -221,7 +221,7 @@ class RackBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
|||||||
type = forms.ChoiceField(choices=add_blank_choice(RACK_TYPE_CHOICES), required=False, label='Type')
|
type = forms.ChoiceField(choices=add_blank_choice(RACK_TYPE_CHOICES), required=False, label='Type')
|
||||||
width = forms.ChoiceField(choices=add_blank_choice(RACK_WIDTH_CHOICES), required=False, label='Width')
|
width = forms.ChoiceField(choices=add_blank_choice(RACK_WIDTH_CHOICES), required=False, label='Width')
|
||||||
u_height = forms.IntegerField(required=False, label='Height (U)')
|
u_height = forms.IntegerField(required=False, label='Height (U)')
|
||||||
comments = CommentField()
|
comments = CommentField(widget=SmallTextarea)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
nullable_fields = ['group', 'tenant', 'role', 'comments']
|
nullable_fields = ['group', 'tenant', 'role', 'comments']
|
||||||
@ -612,6 +612,7 @@ class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
platform = FilterChoiceField(queryset=Platform.objects.annotate(filter_count=Count('devices')),
|
platform = FilterChoiceField(queryset=Platform.objects.annotate(filter_count=Count('devices')),
|
||||||
to_field_name='slug', null_option=(0, 'None'))
|
to_field_name='slug', null_option=(0, 'None'))
|
||||||
status = forms.NullBooleanField(required=False, widget=forms.Select(choices=FORM_STATUS_CHOICES))
|
status = forms.NullBooleanField(required=False, widget=forms.Select(choices=FORM_STATUS_CHOICES))
|
||||||
|
mac_address = forms.CharField(label='MAC address')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
57
netbox/dcim/migrations/0022_color_names_to_rgb.py
Normal file
57
netbox/dcim/migrations/0022_color_names_to_rgb.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10 on 2016-12-06 16:35
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
import utilities.fields
|
||||||
|
|
||||||
|
|
||||||
|
COLOR_CONVERSION = {
|
||||||
|
'teal': '009688',
|
||||||
|
'green': '4caf50',
|
||||||
|
'blue': '2196f3',
|
||||||
|
'purple': '9c27b0',
|
||||||
|
'yellow': 'ffeb3b',
|
||||||
|
'orange': 'ff9800',
|
||||||
|
'red': 'f44336',
|
||||||
|
'light_gray': 'c0c0c0',
|
||||||
|
'medium_gray': '9e9e9e',
|
||||||
|
'dark_gray': '607d8b',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def color_names_to_rgb(apps, schema_editor):
|
||||||
|
RackRole = apps.get_model('dcim', 'RackRole')
|
||||||
|
DeviceRole = apps.get_model('dcim', 'DeviceRole')
|
||||||
|
for color_name, color_rgb in COLOR_CONVERSION.items():
|
||||||
|
RackRole.objects.filter(color=color_name).update(color=color_rgb)
|
||||||
|
DeviceRole.objects.filter(color=color_name).update(color=color_rgb)
|
||||||
|
|
||||||
|
|
||||||
|
def color_rgb_to_name(apps, schema_editor):
|
||||||
|
RackRole = apps.get_model('dcim', 'RackRole')
|
||||||
|
DeviceRole = apps.get_model('dcim', 'DeviceRole')
|
||||||
|
for color_name, color_rgb in COLOR_CONVERSION.items():
|
||||||
|
RackRole.objects.filter(color=color_rgb).update(color=color_name)
|
||||||
|
DeviceRole.objects.filter(color=color_rgb).update(color=color_name)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dcim', '0021_add_ff_flexstack'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(color_names_to_rgb, color_rgb_to_name),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='devicerole',
|
||||||
|
name='color',
|
||||||
|
field=utilities.fields.ColorField(max_length=6),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='rackrole',
|
||||||
|
name='color',
|
||||||
|
field=utilities.fields.ColorField(max_length=6),
|
||||||
|
),
|
||||||
|
]
|
@ -3,7 +3,7 @@ from collections import OrderedDict
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.contenttypes.fields import GenericRelation
|
from django.contrib.contenttypes.fields import GenericRelation
|
||||||
from django.core.exceptions import MultipleObjectsReturned, ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
@ -12,7 +12,7 @@ from django.db.models import Count, Q, ObjectDoesNotExist
|
|||||||
from extras.models import CustomFieldModel, CustomField, CustomFieldValue
|
from extras.models import CustomFieldModel, CustomField, CustomFieldValue
|
||||||
from extras.rpc import RPC_CLIENTS
|
from extras.rpc import RPC_CLIENTS
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.fields import NullableCharField
|
from utilities.fields import ColorField, NullableCharField
|
||||||
from utilities.managers import NaturalOrderByManager
|
from utilities.managers import NaturalOrderByManager
|
||||||
from utilities.models import CreatedUpdatedModel
|
from utilities.models import CreatedUpdatedModel
|
||||||
|
|
||||||
@ -54,29 +54,6 @@ SUBDEVICE_ROLE_CHOICES = (
|
|||||||
(SUBDEVICE_ROLE_CHILD, 'Child'),
|
(SUBDEVICE_ROLE_CHILD, 'Child'),
|
||||||
)
|
)
|
||||||
|
|
||||||
COLOR_TEAL = 'teal'
|
|
||||||
COLOR_GREEN = 'green'
|
|
||||||
COLOR_BLUE = 'blue'
|
|
||||||
COLOR_PURPLE = 'purple'
|
|
||||||
COLOR_YELLOW = 'yellow'
|
|
||||||
COLOR_ORANGE = 'orange'
|
|
||||||
COLOR_RED = 'red'
|
|
||||||
COLOR_GRAY1 = 'light_gray'
|
|
||||||
COLOR_GRAY2 = 'medium_gray'
|
|
||||||
COLOR_GRAY3 = 'dark_gray'
|
|
||||||
ROLE_COLOR_CHOICES = [
|
|
||||||
[COLOR_TEAL, 'Teal'],
|
|
||||||
[COLOR_GREEN, 'Green'],
|
|
||||||
[COLOR_BLUE, 'Blue'],
|
|
||||||
[COLOR_PURPLE, 'Purple'],
|
|
||||||
[COLOR_YELLOW, 'Yellow'],
|
|
||||||
[COLOR_ORANGE, 'Orange'],
|
|
||||||
[COLOR_RED, 'Red'],
|
|
||||||
[COLOR_GRAY1, 'Light Gray'],
|
|
||||||
[COLOR_GRAY2, 'Medium Gray'],
|
|
||||||
[COLOR_GRAY3, 'Dark Gray'],
|
|
||||||
]
|
|
||||||
|
|
||||||
# Virtual
|
# Virtual
|
||||||
IFACE_FF_VIRTUAL = 0
|
IFACE_FF_VIRTUAL = 0
|
||||||
# Ethernet
|
# Ethernet
|
||||||
@ -345,7 +322,7 @@ class RackRole(models.Model):
|
|||||||
"""
|
"""
|
||||||
name = models.CharField(max_length=50, unique=True)
|
name = models.CharField(max_length=50, unique=True)
|
||||||
slug = models.SlugField(unique=True)
|
slug = models.SlugField(unique=True)
|
||||||
color = models.CharField(max_length=30, choices=ROLE_COLOR_CHOICES)
|
color = ColorField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
@ -761,7 +738,7 @@ class DeviceRole(models.Model):
|
|||||||
"""
|
"""
|
||||||
name = models.CharField(max_length=50, unique=True)
|
name = models.CharField(max_length=50, unique=True)
|
||||||
slug = models.SlugField(unique=True)
|
slug = models.SlugField(unique=True)
|
||||||
color = models.CharField(max_length=30, choices=ROLE_COLOR_CHOICES)
|
color = ColorField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
@ -1173,16 +1150,13 @@ class Interface(models.Model):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def get_connected_interface(self):
|
def get_connected_interface(self):
|
||||||
try:
|
connection = InterfaceConnection.objects.select_related().filter(Q(interface_a=self) | Q(interface_b=self))\
|
||||||
connection = InterfaceConnection.objects.select_related().get(Q(interface_a=self) | Q(interface_b=self))
|
.first()
|
||||||
if connection.interface_a == self:
|
if connection and connection.interface_a == self:
|
||||||
return connection.interface_b
|
return connection.interface_b
|
||||||
else:
|
elif connection:
|
||||||
return connection.interface_a
|
return connection.interface_a
|
||||||
except InterfaceConnection.DoesNotExist:
|
return None
|
||||||
return None
|
|
||||||
except InterfaceConnection.MultipleObjectsReturned:
|
|
||||||
raise MultipleObjectsReturned("Multiple connections found for {} interface {}!".format(self.device, self))
|
|
||||||
|
|
||||||
|
|
||||||
class InterfaceConnection(models.Model):
|
class InterfaceConnection(models.Model):
|
||||||
|
@ -11,7 +11,7 @@ from .models import (
|
|||||||
|
|
||||||
|
|
||||||
COLOR_LABEL = """
|
COLOR_LABEL = """
|
||||||
<label class="label {{ record.color }}">{{ record }}</label>
|
<label class="label" style="background-color: #{{ record.color }}">{{ record }}</label>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
DEVICE_LINK = """
|
DEVICE_LINK = """
|
||||||
@ -34,7 +34,7 @@ RACKROLE_ACTIONS = """
|
|||||||
|
|
||||||
RACK_ROLE = """
|
RACK_ROLE = """
|
||||||
{% if record.role %}
|
{% if record.role %}
|
||||||
<label class="label {{ record.role.color }}">{{ value }}</label>
|
<label class="label" style="background-color: #{{ record.role.color }}">{{ value }}</label>
|
||||||
{% else %}
|
{% else %}
|
||||||
—
|
—
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -59,7 +59,7 @@ PLATFORM_ACTIONS = """
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
DEVICE_ROLE = """
|
DEVICE_ROLE = """
|
||||||
<label class="label {{ record.device_role.color }}">{{ value }}</label>
|
<label class="label" style="background-color: #{{ record.device_role.color }}">{{ value }}</label>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
STATUS_ICON = """
|
STATUS_ICON = """
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
from .models import CustomField, CustomFieldChoice, Graph, ExportTemplate, TopologyMap, UserAction
|
from .models import CustomField, CustomFieldChoice, Graph, ExportTemplate, TopologyMap, UserAction
|
||||||
|
|
||||||
@ -54,4 +55,7 @@ class TopologyMapAdmin(admin.ModelAdmin):
|
|||||||
@admin.register(UserAction)
|
@admin.register(UserAction)
|
||||||
class UserActionAdmin(admin.ModelAdmin):
|
class UserActionAdmin(admin.ModelAdmin):
|
||||||
actions = None
|
actions = None
|
||||||
list_display = ['user', 'action', 'content_type', 'object_id', 'message']
|
list_display = ['user', 'action', 'content_type', 'object_id', '_message']
|
||||||
|
|
||||||
|
def _message(self, obj):
|
||||||
|
return mark_safe(obj.message)
|
||||||
|
@ -130,7 +130,7 @@ class CustomField(models.Model):
|
|||||||
if self.type == CF_TYPE_SELECT:
|
if self.type == CF_TYPE_SELECT:
|
||||||
# Could be ModelChoiceField or TypedChoiceField
|
# Could be ModelChoiceField or TypedChoiceField
|
||||||
return str(value.id) if hasattr(value, 'id') else str(value)
|
return str(value.id) if hasattr(value, 'id') else str(value)
|
||||||
return str(value)
|
return value
|
||||||
|
|
||||||
def deserialize_value(self, serialized_value):
|
def deserialize_value(self, serialized_value):
|
||||||
"""
|
"""
|
||||||
@ -165,7 +165,7 @@ class CustomFieldValue(models.Model):
|
|||||||
unique_together = ['field', 'obj_type', 'obj_id']
|
unique_together = ['field', 'obj_type', 'obj_id']
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return '{} {}'.format(self.obj, self.field)
|
return u'{} {}'.format(self.obj, self.field)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self):
|
def value(self):
|
||||||
|
@ -28,7 +28,7 @@ class RIRAdmin(admin.ModelAdmin):
|
|||||||
prepopulated_fields = {
|
prepopulated_fields = {
|
||||||
'slug': ['name'],
|
'slug': ['name'],
|
||||||
}
|
}
|
||||||
list_display = ['name', 'slug']
|
list_display = ['name', 'slug', 'is_private']
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Aggregate)
|
@admin.register(Aggregate)
|
||||||
|
@ -58,13 +58,13 @@ class RIRSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RIR
|
model = RIR
|
||||||
fields = ['id', 'name', 'slug']
|
fields = ['id', 'name', 'slug', 'is_private']
|
||||||
|
|
||||||
|
|
||||||
class RIRNestedSerializer(RIRSerializer):
|
class RIRNestedSerializer(RIRSerializer):
|
||||||
|
|
||||||
class Meta(RIRSerializer.Meta):
|
class Meta(RIRSerializer.Meta):
|
||||||
pass
|
fields = ['id', 'name', 'slug']
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -46,6 +46,13 @@ class VRFFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
fields = ['name', 'rd']
|
fields = ['name', 'rd']
|
||||||
|
|
||||||
|
|
||||||
|
class RIRFilter(django_filters.FilterSet):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = RIR
|
||||||
|
fields = ['is_private']
|
||||||
|
|
||||||
|
|
||||||
class AggregateFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
class AggregateFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||||
q = django_filters.MethodFilter(
|
q = django_filters.MethodFilter(
|
||||||
action='search',
|
action='search',
|
||||||
|
@ -43,7 +43,8 @@
|
|||||||
"pk": 1,
|
"pk": 1,
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "ARIN",
|
"name": "ARIN",
|
||||||
"slug": "arin"
|
"slug": "arin",
|
||||||
|
"is_private": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -51,7 +52,8 @@
|
|||||||
"pk": 2,
|
"pk": 2,
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "RIPE",
|
"name": "RIPE",
|
||||||
"slug": "ripe"
|
"slug": "ripe",
|
||||||
|
"is_private": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -59,7 +61,8 @@
|
|||||||
"pk": 3,
|
"pk": 3,
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "APNIC",
|
"name": "APNIC",
|
||||||
"slug": "apnic"
|
"slug": "apnic",
|
||||||
|
"is_private": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -67,7 +70,8 @@
|
|||||||
"pk": 4,
|
"pk": 4,
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "LACNIC",
|
"name": "LACNIC",
|
||||||
"slug": "lacnic"
|
"slug": "lacnic",
|
||||||
|
"is_private": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -75,7 +79,8 @@
|
|||||||
"pk": 5,
|
"pk": 5,
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "AFRINIC",
|
"name": "AFRINIC",
|
||||||
"slug": "afrinic"
|
"slug": "afrinic",
|
||||||
|
"is_private": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -83,7 +88,8 @@
|
|||||||
"pk": 6,
|
"pk": 6,
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "RFC 1918",
|
"name": "RFC 1918",
|
||||||
"slug": "rfc-1918"
|
"slug": "rfc-1918",
|
||||||
|
"is_private": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -75,7 +75,15 @@ class RIRForm(forms.ModelForm, BootstrapMixin):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RIR
|
model = RIR
|
||||||
fields = ['name', 'slug']
|
fields = ['name', 'slug', 'is_private']
|
||||||
|
|
||||||
|
|
||||||
|
class RIRFilterForm(forms.Form, BootstrapMixin):
|
||||||
|
is_private = forms.NullBooleanField(required=False, label='Private', widget=forms.Select(choices=[
|
||||||
|
('', '---------'),
|
||||||
|
('True', 'Yes'),
|
||||||
|
('False', 'No'),
|
||||||
|
]))
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
20
netbox/ipam/migrations/0011_rir_add_is_private.py
Normal file
20
netbox/ipam/migrations/0011_rir_add_is_private.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10 on 2016-12-06 18:27
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('ipam', '0010_ipaddress_help_texts'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rir',
|
||||||
|
name='is_private',
|
||||||
|
field=models.BooleanField(default=False, help_text=b'IP space managed by this RIR is considered private', verbose_name=b'Private'),
|
||||||
|
),
|
||||||
|
]
|
@ -103,6 +103,8 @@ class RIR(models.Model):
|
|||||||
"""
|
"""
|
||||||
name = models.CharField(max_length=50, unique=True)
|
name = models.CharField(max_length=50, unique=True)
|
||||||
slug = models.SlugField(unique=True)
|
slug = models.SlugField(unique=True)
|
||||||
|
is_private = models.BooleanField(default=False, verbose_name='Private',
|
||||||
|
help_text='IP space managed by this RIR is considered private')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
|
@ -126,6 +126,7 @@ class VRFTable(BaseTable):
|
|||||||
class RIRTable(BaseTable):
|
class RIRTable(BaseTable):
|
||||||
pk = ToggleColumn()
|
pk = ToggleColumn()
|
||||||
name = tables.LinkColumn(verbose_name='Name')
|
name = tables.LinkColumn(verbose_name='Name')
|
||||||
|
is_private = tables.BooleanColumn(verbose_name='Private')
|
||||||
aggregate_count = tables.Column(verbose_name='Aggregates')
|
aggregate_count = tables.Column(verbose_name='Aggregates')
|
||||||
stats_total = tables.Column(accessor='stats.total', verbose_name='Total',
|
stats_total = tables.Column(accessor='stats.total', verbose_name='Total',
|
||||||
footer=lambda table: sum(r.stats['total'] for r in table.data))
|
footer=lambda table: sum(r.stats['total'] for r in table.data))
|
||||||
@ -142,7 +143,8 @@ class RIRTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = RIR
|
model = RIR
|
||||||
fields = ('pk', 'name', 'aggregate_count', 'stats_total', 'stats_active', 'stats_reserved', 'stats_deprecated', 'stats_available', 'utilization', 'actions')
|
fields = ('pk', 'name', 'is_private', 'aggregate_count', 'stats_total', 'stats_active', 'stats_reserved',
|
||||||
|
'stats_deprecated', 'stats_available', 'utilization', 'actions')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -154,6 +154,8 @@ class VRFBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
|||||||
|
|
||||||
class RIRListView(ObjectListView):
|
class RIRListView(ObjectListView):
|
||||||
queryset = RIR.objects.annotate(aggregate_count=Count('aggregates'))
|
queryset = RIR.objects.annotate(aggregate_count=Count('aggregates'))
|
||||||
|
filter = filters.RIRFilter
|
||||||
|
filter_form = forms.RIRFilterForm
|
||||||
table = tables.RIRTable
|
table = tables.RIRTable
|
||||||
edit_permissions = ['ipam.change_rir', 'ipam.delete_rir']
|
edit_permissions = ['ipam.change_rir', 'ipam.delete_rir']
|
||||||
template_name = 'ipam/rir_list.html'
|
template_name = 'ipam/rir_list.html'
|
||||||
|
@ -12,7 +12,7 @@ except ImportError:
|
|||||||
"the documentation.")
|
"the documentation.")
|
||||||
|
|
||||||
|
|
||||||
VERSION = '1.7.1'
|
VERSION = '1.7.2'
|
||||||
|
|
||||||
# Import local configuration
|
# Import local configuration
|
||||||
for setting in ['ALLOWED_HOSTS', 'DATABASE', 'SECRET_KEY']:
|
for setting in ['ALLOWED_HOSTS', 'DATABASE', 'SECRET_KEY']:
|
||||||
@ -188,7 +188,7 @@ REST_FRAMEWORK = {
|
|||||||
|
|
||||||
# Swagger settings (API docs)
|
# Swagger settings (API docs)
|
||||||
SWAGGER_SETTINGS = {
|
SWAGGER_SETTINGS = {
|
||||||
'base_path': '{}/api/docs'.format(ALLOWED_HOSTS[0]),
|
'base_path': '{}/{}api/docs'.format(ALLOWED_HOSTS[0], BASE_PATH),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ nav ul.pagination {
|
|||||||
div.rack_header {
|
div.rack_header {
|
||||||
margin-left: 36px;
|
margin-left: 36px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 200px;
|
width: 230px;
|
||||||
}
|
}
|
||||||
ul.rack_legend {
|
ul.rack_legend {
|
||||||
float: left;
|
float: left;
|
||||||
@ -126,29 +126,16 @@ ul.rack {
|
|||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 200px;
|
width: 230px;
|
||||||
}
|
}
|
||||||
ul.rack li {
|
ul.rack li {
|
||||||
|
border-top: 1px solid #e0e0e0;
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
ul.rack_empty li {
|
|
||||||
background-color: #f7f7f7;
|
|
||||||
border-bottom: 1px solid #dddddd;
|
|
||||||
height: 20px;
|
|
||||||
}
|
|
||||||
ul.rack li.empty:last-child {
|
|
||||||
border-bottom: 0;
|
|
||||||
}
|
|
||||||
ul.rack_far_face {
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
ul.rack_near_face {
|
|
||||||
z-index: 200;
|
|
||||||
}
|
|
||||||
ul.rack li.h2u { height: 40px; }
|
ul.rack li.h2u { height: 40px; }
|
||||||
ul.rack li.h2u a, ul.rack li.h2u span { padding: 10px 0; }
|
ul.rack li.h2u a, ul.rack li.h2u span { padding: 10px 0; }
|
||||||
ul.rack li.h3u { height: 60px; }
|
ul.rack li.h3u { height: 60px; }
|
||||||
@ -247,22 +234,9 @@ ul.rack li.h49u { height: 980px; }
|
|||||||
ul.rack li.h49u a, ul.rack li.h49u span { padding: 480px 0; }
|
ul.rack li.h49u a, ul.rack li.h49u span { padding: 480px 0; }
|
||||||
ul.rack li.h50u { height: 1000px; }
|
ul.rack li.h50u { height: 1000px; }
|
||||||
ul.rack li.h50u a, ul.rack li.h50u span { padding: 490px 0; }
|
ul.rack li.h50u a, ul.rack li.h50u span { padding: 490px 0; }
|
||||||
ul.rack li.occupied a {
|
ul.rack_far_face {
|
||||||
color: #ffffff;
|
background-color: #f7f7f7;
|
||||||
display: block;
|
z-index: 100;
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
ul.rack li.occupied a:hover {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
ul.rack li.occupied span {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
ul.rack_near_face li.empty {
|
|
||||||
border-bottom: 1px solid #e0e0e0;
|
|
||||||
}
|
|
||||||
ul.rack_near_face li.occupied {
|
|
||||||
color: #474747;
|
|
||||||
}
|
}
|
||||||
ul.rack_far_face li.occupied {
|
ul.rack_far_face li.occupied {
|
||||||
background: repeating-linear-gradient(
|
background: repeating-linear-gradient(
|
||||||
@ -272,7 +246,6 @@ ul.rack_far_face li.occupied {
|
|||||||
#f0f0f0 7px,
|
#f0f0f0 7px,
|
||||||
#f0f0f0 14px
|
#f0f0f0 14px
|
||||||
);
|
);
|
||||||
color: #303030;
|
|
||||||
}
|
}
|
||||||
ul.rack_far_face li.blocked {
|
ul.rack_far_face li.blocked {
|
||||||
background: repeating-linear-gradient(
|
background: repeating-linear-gradient(
|
||||||
@ -282,54 +255,46 @@ ul.rack_far_face li.blocked {
|
|||||||
#ffc7c7 7px,
|
#ffc7c7 7px,
|
||||||
#ffc7c7 14px
|
#ffc7c7 14px
|
||||||
);
|
);
|
||||||
border-bottom: 1px solid #e0e0e0;
|
|
||||||
color: #303030;
|
|
||||||
}
|
}
|
||||||
ul.rack_near_face li.empty a {
|
ul.rack_near_face {
|
||||||
|
z-index: 200;
|
||||||
|
}
|
||||||
|
ul.rack_near_face li.occupied {
|
||||||
|
border-top: 1px solid #474747;
|
||||||
|
color: #474747;
|
||||||
|
}
|
||||||
|
ul.rack_near_face li.occupied:hover {
|
||||||
|
background-image: url('../img/tint_20.png');
|
||||||
|
}
|
||||||
|
ul.rack_near_face li:first-child {
|
||||||
|
border-top: 0;
|
||||||
|
}
|
||||||
|
ul.rack_near_face li.available a {
|
||||||
color: #0000ff;
|
color: #0000ff;
|
||||||
display: none;
|
display: none;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
ul.rack_near_face li.empty:hover {
|
ul.rack_near_face li.available:hover {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
}
|
}
|
||||||
ul.rack_near_face li.empty:hover a {
|
ul.rack_near_face li.available:hover a {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
ul.rack li.occupied a {
|
||||||
/* Colors (from http://flatuicolors.com) */
|
color: #ffffff;
|
||||||
.teal { background-color: #1abc9c; }
|
display: block;
|
||||||
.green { background-color: #2ecc71; }
|
font-weight: bold;
|
||||||
.blue { background-color: #3498db; }
|
}
|
||||||
.purple { background-color: #9b59b6; }
|
ul.rack li.occupied a:hover {
|
||||||
.yellow { background-color: #f1c40f; }
|
text-decoration: none;
|
||||||
.orange { background-color: #e67e22; }
|
}
|
||||||
.red { background-color: #e74c3c; }
|
ul.rack li.occupied span {
|
||||||
.light_gray { background-color: #dce2e3; }
|
cursor: default;
|
||||||
.medium_gray { background-color: #95a5a6; }
|
display: block;
|
||||||
.dark_gray { background-color: #34495e; }
|
}
|
||||||
|
li.occupied + li.available {
|
||||||
/* Rack elevation coloring */
|
border-top: 1px solid #474747;
|
||||||
ul.rack .teal { border-bottom: 1px solid #16a085; }
|
}
|
||||||
ul.rack .teal:hover { background-color: #16a085; }
|
|
||||||
ul.rack .green { border-bottom: 1px solid #27ae60; }
|
|
||||||
ul.rack .green:hover { background-color: #27ae60; }
|
|
||||||
ul.rack .blue { border-bottom: 1px solid #2980b9; }
|
|
||||||
ul.rack .blue:hover { background-color: #2980b9; }
|
|
||||||
ul.rack .purple { border-bottom: 1px solid #8e44ad; }
|
|
||||||
ul.rack .purple:hover { background-color: #8e44ad; }
|
|
||||||
ul.rack .yellow { border-bottom: 1px solid #f39c12; }
|
|
||||||
ul.rack .yellow:hover { background-color: #f39c12; }
|
|
||||||
ul.rack .orange { border-bottom: 1px solid #d35400; }
|
|
||||||
ul.rack .orange:hover { background-color: #d35400; }
|
|
||||||
ul.rack .red { border-bottom: 1px solid #c0392b; }
|
|
||||||
ul.rack .red:hover { background-color: #c0392b; }
|
|
||||||
ul.rack .light_gray { border-bottom: 1px solid #bdc3c7; }
|
|
||||||
ul.rack .light_gray:hover { background-color: #bdc3c7; }
|
|
||||||
ul.rack .medium_gray { border-bottom: 1px solid #7f8c8d; }
|
|
||||||
ul.rack .medium_gray:hover { background-color: #7f8c8d; }
|
|
||||||
ul.rack .dark_gray { border-bottom: 1px solid #2c3e50; }
|
|
||||||
ul.rack .dark_gray:hover { background-color: #2c3e50; }
|
|
||||||
|
|
||||||
/* Misc */
|
/* Misc */
|
||||||
.banner-bottom {
|
.banner-bottom {
|
||||||
|
BIN
netbox/project-static/img/tint_20.png
Normal file
BIN
netbox/project-static/img/tint_20.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 109 B |
@ -6,13 +6,6 @@
|
|||||||
|
|
||||||
<div class="rack_frame">
|
<div class="rack_frame">
|
||||||
|
|
||||||
<!-- Render all slots empty -->
|
|
||||||
<ul class="rack rack_empty">
|
|
||||||
{% for u in rack.units %}
|
|
||||||
<li></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<!-- Render rear view of devices on far face -->
|
<!-- Render rear view of devices on far face -->
|
||||||
<ul class="rack rack_far_face">
|
<ul class="rack rack_far_face">
|
||||||
{% for u in secondary_face %}
|
{% for u in secondary_face %}
|
||||||
@ -42,7 +35,7 @@
|
|||||||
{% endifequal %}
|
{% endifequal %}
|
||||||
</li>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="empty">
|
<li class="available">
|
||||||
{% if perms.dcim.add_device %}
|
{% if perms.dcim.add_device %}
|
||||||
<a href="{% url 'dcim:device_add' %}?site={{ rack.site.pk }}&rack={{ rack.pk }}&face={{ face_id }}&position={{ u.id }}" class="add_device" >add device</a>
|
<a href="{% url 'dcim:device_add' %}?site={{ rack.site.pk }}&rack={{ rack.pk }}&face={{ face_id }}&position={{ u.id }}" class="add_device" >add device</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{% extends '_base.html' %}
|
{% extends '_base.html' %}
|
||||||
{% load form_helpers %}
|
{% load form_helpers %}
|
||||||
|
|
||||||
{% block title %}Assign an IP Address{% endblock %}
|
{% block title %}Assign a New IP Address{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form action="." method="post" class="form form-horizontal">
|
<form action="." method="post" class="form form-horizontal">
|
||||||
@ -40,6 +40,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% render_field form.interface %}
|
{% render_field form.interface %}
|
||||||
|
{% render_field form.set_as_primary %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="{% url 'ipam:prefix_list' %}">Prefixes</a></li>
|
<li><a href="{% url 'ipam:prefix_list' %}">Prefixes</a></li>
|
||||||
{% if prefix.vrf %}
|
{% if prefix.vrf %}
|
||||||
<li><a href="{% url 'ipam:prefix_list' %}?vrf={{ prefix.vrf.pk }}">{{ prefix.vrf }}</a></li>
|
<li><a href="{% url 'ipam:vrf' pk=prefix.vrf.pk %}">{{ prefix.vrf }}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li>{{ prefix }}</li>
|
<li>{{ prefix }}</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="{% url 'ipam:ipaddress_list' %}">IP Addresses</a></li>
|
<li><a href="{% url 'ipam:ipaddress_list' %}">IP Addresses</a></li>
|
||||||
{% if ipaddress.vrf %}
|
{% if ipaddress.vrf %}
|
||||||
<li><a href="{% url 'ipam:ipaddress_list' %}?vrf={{ ipaddress.vrf.pk }}">{{ ipaddress.vrf }}</a></li>
|
<li><a href="{% url 'ipam:vrf' pk=ipaddress.vrf.pk %}">{{ ipaddress.vrf }}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li>{{ ipaddress }}</li>
|
<li>{{ ipaddress }}</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load static from staticfiles %}
|
{% load static from staticfiles %}
|
||||||
{% load form_helpers %}
|
{% load form_helpers %}
|
||||||
|
|
||||||
{% block title %}Assign IP Address{% endblock %}
|
{% block title %}Assign an IP Address{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form action="." method="post" class="form form-horizontal">
|
<form action="." method="post" class="form form-horizontal">
|
||||||
@ -19,9 +19,25 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Assign IP Address {{ ipaddress }} ({% if ipaddress.vrf %}VRF {{ ipaddress.vrf }}{% else %}Global Table{% endif %})</strong>
|
<strong>Assign an IP Address</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-3 control-label">IP Address</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<p class="form-control-static">{{ ipaddress }}</p>
|
||||||
|
</div>
|
||||||
|
<label class="col-md-3 control-label">VRF</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<p class="form-control-static">
|
||||||
|
{% if ipaddress.vrf %}
|
||||||
|
<a href="{% url 'ipam:vrf' pk=ipaddress.vrf.pk %}">{{ ipaddress.vrf }}</a> ({{ ipaddress.vrf.rd }})
|
||||||
|
{% else %}
|
||||||
|
<span>Global</span>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<ul class="nav nav-tabs" role="tablist">
|
<ul class="nav nav-tabs" role="tablist">
|
||||||
<li role="presentation" class="active"><a href="#search" aria-controls="search" role="tab" data-toggle="tab">Search</a></li>
|
<li role="presentation" class="active"><a href="#search" aria-controls="search" role="tab" data-toggle="tab">Search</a></li>
|
||||||
<li role="presentation"><a href="#select" aria-controls="home" role="tab" data-toggle="tab">Select</a></li>
|
<li role="presentation"><a href="#select" aria-controls="home" role="tab" data-toggle="tab">Select</a></li>
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
IPv4 Stats
|
IPv4 Stats
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{% url 'ipam:rir_list' %}?family=6" class="btn btn-default">
|
<a href="{% url 'ipam:rir_list' %}?family=6{% if request.GET %}&{{ request.GET.urlencode }}{% endif %}" class="btn btn-default">
|
||||||
<span class="fa fa-table" aria-hidden="true"></span>
|
<span class="fa fa-table" aria-hidden="true"></span>
|
||||||
IPv6 Stats
|
IPv6 Stats
|
||||||
</a>
|
</a>
|
||||||
@ -26,11 +26,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<h1>RIRs</h1>
|
<h1>RIRs</h1>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-9">
|
||||||
{% include 'utilities/obj_table.html' with bulk_delete_url='ipam:rir_bulk_delete' %}
|
{% include 'utilities/obj_table.html' with bulk_delete_url='ipam:rir_bulk_delete' %}
|
||||||
|
{% if request.GET.family == '6' %}
|
||||||
|
<div class="alert alert-info pull-right"><strong>Note:</strong> Numbers shown indicate /64 prefixes.</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
{% include 'inc/filter_panel.html' %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if request.GET.family == '6' %}
|
|
||||||
<div class="pull-right text-muted"><strong>Note:</strong> Numbers shown indicate /64 prefixes.</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -8,7 +8,9 @@
|
|||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="{% url 'tenancy:tenant_list' %}">Tenants</a></li>
|
<li><a href="{% url 'tenancy:tenant_list' %}">Tenants</a></li>
|
||||||
<li><a href="{% url 'tenancy:tenant_list' %}?group={{ tenant.group.slug }}">{{ tenant.group }}</a></li>
|
{% if tenant.group %}
|
||||||
|
<li><a href="{% url 'tenancy:tenant_list' %}?group={{ tenant.group.slug }}">{{ tenant.group }}</a></li>
|
||||||
|
{% endif %}
|
||||||
<li>{{ tenant }}</li>
|
<li>{{ tenant }}</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
@ -50,7 +52,11 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Group</td>
|
<td>Group</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ tenant.group.get_absolute_url }}">{{ tenant.group }}</a>
|
{% if tenant.group %}
|
||||||
|
<a href="{{ tenant.group.get_absolute_url }}">{{ tenant.group }}</a>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-muted">None</span>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Group</td>
|
<td>Group</td>
|
||||||
<td>Tenant group</td>
|
<td>Tenant group (optional)</td>
|
||||||
<td>Customers</td>
|
<td>Customers</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -48,6 +48,6 @@ class Tenant(CreatedUpdatedModel, CustomFieldModel):
|
|||||||
return ','.join([
|
return ','.join([
|
||||||
self.name,
|
self.name,
|
||||||
self.slug,
|
self.slug,
|
||||||
self.group.name,
|
self.group.name if self.group else '',
|
||||||
self.description,
|
self.description,
|
||||||
])
|
])
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
|
from django.core.validators import RegexValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
from .forms import ColorSelect
|
||||||
|
|
||||||
|
|
||||||
|
validate_color = RegexValidator('^[0-9a-f]{6}$', 'Enter a valid hexadecimal RGB color code.', 'invalid')
|
||||||
|
|
||||||
|
|
||||||
class NullableCharField(models.CharField):
|
class NullableCharField(models.CharField):
|
||||||
description = "Stores empty values as NULL rather than ''"
|
description = "Stores empty values as NULL rather than ''"
|
||||||
@ -11,3 +17,16 @@ class NullableCharField(models.CharField):
|
|||||||
|
|
||||||
def get_prep_value(self, value):
|
def get_prep_value(self, value):
|
||||||
return value or None
|
return value or None
|
||||||
|
|
||||||
|
|
||||||
|
class ColorField(models.CharField):
|
||||||
|
default_validators = [validate_color]
|
||||||
|
description = "A hexadecimal RGB color code"
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
kwargs['max_length'] = 6
|
||||||
|
super(ColorField, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def formfield(self, **kwargs):
|
||||||
|
kwargs['widget'] = ColorSelect
|
||||||
|
return super(ColorField, self).formfield(**kwargs)
|
||||||
|
@ -11,6 +11,32 @@ from django.utils.html import format_html
|
|||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
|
|
||||||
|
COLOR_CHOICES = (
|
||||||
|
('aa1409', 'Dark red'),
|
||||||
|
('f44336', 'Red'),
|
||||||
|
('e91e63', 'Pink'),
|
||||||
|
('ff66ff', 'Fuschia'),
|
||||||
|
('9c27b0', 'Purple'),
|
||||||
|
('673ab7', 'Dark purple'),
|
||||||
|
('3f51b5', 'Indigo'),
|
||||||
|
('2196f3', 'Blue'),
|
||||||
|
('03a9f4', 'Light blue'),
|
||||||
|
('00bcd4', 'Cyan'),
|
||||||
|
('009688', 'Teal'),
|
||||||
|
('2f6a31', 'Dark green'),
|
||||||
|
('4caf50', 'Green'),
|
||||||
|
('8bc34a', 'Light green'),
|
||||||
|
('cddc39', 'Lime'),
|
||||||
|
('ffeb3b', 'Yellow'),
|
||||||
|
('ffc107', 'Amber'),
|
||||||
|
('ff9800', 'Orange'),
|
||||||
|
('ff5722', 'Dark orange'),
|
||||||
|
('795548', 'Brown'),
|
||||||
|
('c0c0c0', 'Light grey'),
|
||||||
|
('9e9e9e', 'Grey'),
|
||||||
|
('607d8b', 'Dark grey'),
|
||||||
|
('111111', 'Black'),
|
||||||
|
)
|
||||||
NUMERIC_EXPANSION_PATTERN = '\[(\d+-\d+)\]'
|
NUMERIC_EXPANSION_PATTERN = '\[(\d+-\d+)\]'
|
||||||
IP4_EXPANSION_PATTERN = '\[([0-9]{1,3}-[0-9]{1,3})\]'
|
IP4_EXPANSION_PATTERN = '\[([0-9]{1,3}-[0-9]{1,3})\]'
|
||||||
IP6_EXPANSION_PATTERN = '\[([0-9a-f]{1,4}-[0-9a-f]{1,4})\]'
|
IP6_EXPANSION_PATTERN = '\[([0-9a-f]{1,4}-[0-9a-f]{1,4})\]'
|
||||||
@ -71,6 +97,27 @@ class SmallTextarea(forms.Textarea):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ColorSelect(forms.Select):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
kwargs['choices'] = COLOR_CHOICES
|
||||||
|
super(ColorSelect, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def render_option(self, selected_choices, option_value, option_label):
|
||||||
|
if option_value is None:
|
||||||
|
option_value = ''
|
||||||
|
option_value = force_text(option_value)
|
||||||
|
if option_value in selected_choices:
|
||||||
|
selected_html = mark_safe(' selected')
|
||||||
|
if not self.allow_multiple_selected:
|
||||||
|
# Only allow for a single selection.
|
||||||
|
selected_choices.remove(option_value)
|
||||||
|
else:
|
||||||
|
selected_html = ''
|
||||||
|
return format_html('<option value="{}"{} style="background-color: #{}">{}</option>',
|
||||||
|
option_value, selected_html, option_value, force_text(option_label))
|
||||||
|
|
||||||
|
|
||||||
class SelectWithDisabled(forms.Select):
|
class SelectWithDisabled(forms.Select):
|
||||||
"""
|
"""
|
||||||
Modified the stock Select widget to accept choices using a dict() for a label. The dict for each option must include
|
Modified the stock Select widget to accept choices using a dict() for a label. The dict for each option must include
|
||||||
@ -234,6 +281,7 @@ class CommentField(forms.CharField):
|
|||||||
A textarea with support for GitHub-Flavored Markdown. Exists mostly just to add a standard help_text.
|
A textarea with support for GitHub-Flavored Markdown. Exists mostly just to add a standard help_text.
|
||||||
"""
|
"""
|
||||||
widget = forms.Textarea
|
widget = forms.Textarea
|
||||||
|
default_label = 'Comments'
|
||||||
# TODO: Port GFM syntax cheat sheet to internal documentation
|
# TODO: Port GFM syntax cheat sheet to internal documentation
|
||||||
default_helptext = '<i class="fa fa-info-circle"></i> '\
|
default_helptext = '<i class="fa fa-info-circle"></i> '\
|
||||||
'<a href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet" target="_blank">'\
|
'<a href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet" target="_blank">'\
|
||||||
@ -241,8 +289,9 @@ class CommentField(forms.CharField):
|
|||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
required = kwargs.pop('required', False)
|
required = kwargs.pop('required', False)
|
||||||
|
label = kwargs.pop('label', self.default_label)
|
||||||
help_text = kwargs.pop('help_text', self.default_helptext)
|
help_text = kwargs.pop('help_text', self.default_helptext)
|
||||||
super(CommentField, self).__init__(required=required, help_text=help_text, *args, **kwargs)
|
super(CommentField, self).__init__(required=required, label=label, help_text=help_text, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class FlexibleModelChoiceField(forms.ModelChoiceField):
|
class FlexibleModelChoiceField(forms.ModelChoiceField):
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
cffi>=1.8
|
||||||
cryptography==1.4
|
cryptography==1.4
|
||||||
Django==1.10
|
Django==1.10
|
||||||
django-debug-toolbar==1.4
|
django-debug-toolbar==1.4
|
||||||
|
Loading…
Reference in New Issue
Block a user