mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-23 04:22:01 -06:00
commit
0c3970233e
54
README.md
54
README.md
@ -15,62 +15,14 @@ Questions? Comments? Please join us on IRC in **#netbox** on **irc.freenode.net*
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
# Installation
|
||||
|
||||
Please see docs/getting-started.md for instructions on installing NetBox.
|
||||
|
||||
To upgrade NetBox, please download the [latest release](https://github.com/digitalocean/netbox/releases) and run `upgrade.sh`.
|
||||
|
||||
# Components
|
||||
|
||||
NetBox understands all of the physical and logical building blocks that comprise network infrastructure, and the manners in which they are all related.
|
||||
|
||||
## DCIM
|
||||
|
||||
DCIM comprises all the physical installations and connections which comprise a network. NetBox tracks where devices are installed, as well as their individual power, console, and network connections.
|
||||
|
||||
**Site:** A physical location (typically a building) where network devices are installed. Devices in different sites cannot be directly connected to one another.
|
||||
|
||||
**Rack:** An equipment rack into which devices are installed. Each rack belongs to a site.
|
||||
|
||||
**Device:** Any type of rack-mounted device. For example, routers, switches, servers, console servers, PDUs, etc. 0U (non-rack-mounted) devices are supported.
|
||||
|
||||
## IPAM
|
||||
|
||||
IPAM deals with the IP addressing and VLANs in use on a network. NetBox makes a distinction between IP prefixes (networks) and individual IP addresses.
|
||||
|
||||
Because NetBox is a combined DCIM/IPAM system, IP addresses can be assigned to device interfaces in the application just as they are in the real world.
|
||||
|
||||
**Aggregate:** A top-level aggregate of IP address space; for example, 10.0.0.0/8 or 2001:db8::/32. Each aggregate belongs to a regional Internet registry (RIR) like ARIN or RIPE, or to an authoritative standard such as RFC 1918.
|
||||
|
||||
**VRF:** A virtual routing table. VRF support is currently still under development.
|
||||
|
||||
**Prefix:** An IPv4 or IPv6 network. A prefix can be assigned to a VRF; if not, it is considered to belong to the global table. Prefixes are grouped by aggregates automatically and can optionally be assigned to sites.
|
||||
|
||||
**IP Address:** An individual IPv4 or IPv6 address (with CIDR mask). IP address can be assigned to device interfaces.
|
||||
|
||||
**VLAN:** VLANs are assigned to sites, and can optionally have one or more IP prefixes assigned to them. VLAN IDs are unique only within the scope of a site.
|
||||
|
||||
## Circuits
|
||||
|
||||
Long-distance data connections are typically referred to as _circuits_. NetBox provides a method for managing circuits and their providers. Individual circuits can be terminated to device interfaces.
|
||||
|
||||
**Provider:** An entity to which a network connects to. This can be a transit provider, peer, or some other organization.
|
||||
|
||||
**Circuit:** A data circuit which connects to a provider. The local end of a circuit can be assigned to a device interface.
|
||||
|
||||
## Secrets
|
||||
|
||||
NetBox provides encrypted storage of sensitive data it calls _secrets_. Each user may be issued an encryption key with which stored secrets can be retrieved.
|
||||
|
||||
Note that NetBox does not merely hash secrets, a function which is only useful for validation. It employs fully reversible AES-256 encryption so that secret data can be retrieved and consumed by other services.
|
||||
|
||||
**Secrets** Any piece of confidential data which must be retrievable. For example: passwords, SNMP communities, RADIUS shared secrets, etc.
|
||||
|
||||
**User Key:** An individual user's encrypted copy of the master key, which can be used to retrieve secret data.
|
||||
|
@ -9,6 +9,7 @@ services:
|
||||
POSTGRES_PASSWORD: J5brHrAXFLQSif0K
|
||||
POSTGRES_DB: netbox
|
||||
netbox:
|
||||
build: .
|
||||
image: digitalocean/netbox
|
||||
links:
|
||||
- postgres
|
||||
|
19
docs/api-integration.md
Normal file
19
docs/api-integration.md
Normal file
@ -0,0 +1,19 @@
|
||||
# API Integration
|
||||
|
||||
NetBox features a read-only REST API which can be used to integrate it with
|
||||
other applications.
|
||||
|
||||
In the future, both read and write actions will be available via the API.
|
||||
|
||||
## Clients
|
||||
|
||||
The easiest way to start integrating your applications with NetBox is to make
|
||||
use of an API client. If you build or discover an API client that is not part
|
||||
of this list, please send a pull request!
|
||||
|
||||
- **Go**: [github.com/digitalocean/go-netbox](https://github.com/digitalocean/go-netbox)
|
||||
|
||||
## Documentation
|
||||
|
||||
If you wish to build a new API client or simply explore the NetBox API,
|
||||
Swagger documentation can be found at the URL `/api/docs/` on a NetBox server.
|
45
docs/configuration/mandatory-settings.md
Normal file
45
docs/configuration/mandatory-settings.md
Normal file
@ -0,0 +1,45 @@
|
||||
NetBox's local configuration is held in `netbox/netbox/configuration.py`. An example configuration is provided at `netbox/netbox/configuration.example.py`. You may copy or rename the example configuration and make changes as appropriate. NetBox will not run without a configuration file.
|
||||
|
||||
## ALLOWED_HOSTS
|
||||
|
||||
This is a list of valid fully-qualified domain names (FQDNs) for the NetBox server. NetBox will not permit write access to the server via any other hostnames. The first FQDN in the list will be treated as the preferred name.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
ALLOWED_HOSTS = ['netbox.example.com', '192.0.2.123']
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## DATABASE
|
||||
|
||||
NetBox requires access to a PostgreSQL database service to store data. This service can run locally or on a remote system. The following parameters must be defined within the `DATABASE` dictionary:
|
||||
|
||||
* NAME - Database name
|
||||
* USER - PostgreSQL username
|
||||
* PASSWORD - PostgreSQL password
|
||||
* HOST - Name or IP address of the database server (use `localhost` if running locally)
|
||||
* PORT - TCP port of the PostgreSQL service; leave blank for default port (5432)
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
DATABASE = {
|
||||
'NAME': 'netbox', # Database name
|
||||
'USER': 'netbox', # PostgreSQL username
|
||||
'PASSWORD': 'J5brHrAXFLQSif0K', # PostgreSQL password
|
||||
'HOST': 'localhost', # Database server
|
||||
'PORT': '', # Database port (leave blank for default)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SECRET_KEY
|
||||
|
||||
This is a secret cryptographic key is used to improve the security of cookies and password resets. The key defined here should not be shared outside of the configuration file. `SECRET_KEY` can be changed at any time, however be aware that doing so will invalidate all existing sessions.
|
||||
|
||||
Please note that this key is **not** used for hashing user passwords or for the encrypted storage of secret data in NetBox.
|
||||
|
||||
`SECRET_KEY` should be at least 50 characters in length and contain a random mix of letters, digits, and symbols. The script located at `netbox/generate_secret_key.py` may be used to generate a suitable key.
|
@ -1,62 +1,6 @@
|
||||
<h1>Configuration</h1>
|
||||
The following are optional settings which may be declared in `netbox/netbox/configuration.py`.
|
||||
|
||||
NetBox's local configuration is held in `netbox/netbox/configuration.py`. An example configuration is provided at `netbox/netbox/configuration.example.py`. You may copy or rename the example configuration and make changes as appropriate. NetBox will not run without a configuration file.
|
||||
|
||||
[TOC]
|
||||
|
||||
# Mandatory Settings
|
||||
|
||||
---
|
||||
|
||||
#### ALLOWED_HOSTS
|
||||
|
||||
This is a list of valid fully-qualified domain names (FQDNs) for the NetBox server. NetBox will not permit write access to the server via any other hostnames. The first FQDN in the list will be treated as the preferred name.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
ALLOWED_HOSTS = ['netbox.example.com', '192.0.2.123']
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### DATABASE
|
||||
|
||||
NetBox requires access to a PostgreSQL database service to store data. This service can run locally or on a remote system. The following parameters must be defined within the `DATABASE` dictionary:
|
||||
|
||||
* NAME - Database name
|
||||
* USER - PostgreSQL username
|
||||
* PASSWORD - PostgreSQL password
|
||||
* HOST - Name or IP address of the database server (use `localhost` if running locally)
|
||||
* PORT - TCP port of the PostgreSQL service; leave blank for default port (5432)
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
DATABASE = {
|
||||
'NAME': 'netbox', # Database name
|
||||
'USER': 'netbox', # PostgreSQL username
|
||||
'PASSWORD': 'J5brHrAXFLQSif0K', # PostgreSQL password
|
||||
'HOST': 'localhost', # Database server
|
||||
'PORT': '', # Database port (leave blank for default)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### SECRET_KEY
|
||||
|
||||
This is a secret cryptographic key is used to improve the security of cookies and password resets. The key defined here should not be shared outside of the configuration file. `SECRET_KEY` can be changed at any time, however be aware that doing so will invalidate all existing sessions.
|
||||
|
||||
Please note that this key is **not** used for hashing user passwords or for the encrypted storage of secret data in NetBox.
|
||||
|
||||
`SECRET_KEY` should be at least 50 characters in length and contain a random mix of letters, digits, and symbols. The script located at `netbox/generate_secret_key.py` may be used to generate a suitable key.
|
||||
|
||||
# Optional Settings
|
||||
|
||||
---
|
||||
|
||||
#### ADMINS
|
||||
## ADMINS
|
||||
|
||||
NetBox will email details about critical errors to the administrators listed here. This should be a list of (name, email) tuples. For example:
|
||||
|
||||
@ -69,7 +13,7 @@ ADMINS = [
|
||||
|
||||
---
|
||||
|
||||
#### DEBUG
|
||||
## DEBUG
|
||||
|
||||
Default: False
|
||||
|
||||
@ -77,7 +21,7 @@ This setting enables debugging. This should be done only during development or t
|
||||
|
||||
---
|
||||
|
||||
#### EMAIL
|
||||
## EMAIL
|
||||
|
||||
In order to send email, NetBox needs an email server configured. The following items can be defined within the `EMAIL` setting:
|
||||
|
||||
@ -90,7 +34,7 @@ In order to send email, NetBox needs an email server configured. The following i
|
||||
|
||||
---
|
||||
|
||||
#### LOGIN_REQUIRED
|
||||
## LOGIN_REQUIRED
|
||||
|
||||
Default: False,
|
||||
|
||||
@ -98,7 +42,7 @@ Setting this to True will permit only authenticated users to access any part of
|
||||
|
||||
---
|
||||
|
||||
#### MAINTENANCE_MODE
|
||||
## MAINTENANCE_MODE
|
||||
|
||||
Default: False
|
||||
|
||||
@ -106,15 +50,15 @@ Setting this to True will display a "maintenance mode" banner at the top of ever
|
||||
|
||||
---
|
||||
|
||||
#### NETBOX_USERNAME
|
||||
## NETBOX_USERNAME
|
||||
|
||||
#### NETBOX_PASSWORD
|
||||
## NETBOX_PASSWORD
|
||||
|
||||
If provided, NetBox will use these credentials to authenticate against devices when collecting data.
|
||||
|
||||
---
|
||||
|
||||
#### PAGINATE_COUNT
|
||||
## PAGINATE_COUNT
|
||||
|
||||
Default: 50
|
||||
|
||||
@ -122,7 +66,7 @@ Determine how many objects to display per page within each list of objects.
|
||||
|
||||
---
|
||||
|
||||
#### TIME_ZONE
|
||||
## TIME_ZONE
|
||||
|
||||
Default: UTC
|
||||
|
||||
@ -130,7 +74,7 @@ The time zone NetBox will use when dealing with dates and times. It is recommend
|
||||
|
||||
---
|
||||
|
||||
#### Date and Time Formatting
|
||||
## Date and Time Formatting
|
||||
|
||||
You may define custom formatting for date and times. For detailed instructions on writing format strings, please see [the Django documentation](https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date).
|
||||
|
@ -1,9 +1,5 @@
|
||||
<h1>Circuits</h1>
|
||||
|
||||
The circuits component of NetBox deals with the management of long-haul Internet and private transit links and providers.
|
||||
|
||||
[TOC]
|
||||
|
||||
# Providers
|
||||
|
||||
A provider is any entity which provides some form of connectivity. This obviously includes carriers which offer Internet and private transit service. However, it might also include Internet exchange (IX) points and even organizations with whom you peer directly.
|
@ -1,9 +1,5 @@
|
||||
<h1>DCIM</h1>
|
||||
|
||||
Data center infrastructure management (DCIM) entails all physical assets: sites, racks, devices, cabling, etc.
|
||||
|
||||
[TOC]
|
||||
|
||||
# Sites
|
||||
|
||||
How you define sites will depend on the nature of your organization, but typically a site will equate a building or campus. For example, a chain of banks might create a site to represent each of its branches, a site for its corporate headquarters, and two additional sites for its presence in two colocation facilities.
|
@ -1,9 +1,5 @@
|
||||
<h1>Extras</h1>
|
||||
|
||||
This section entails features of NetBox which are not crucial to its primary functions, but that provide additional value.
|
||||
|
||||
[TOC]
|
||||
|
||||
# Export Templates
|
||||
|
||||
NetBox allows users to define custom templates that can be used when exporting objects. To create an export template, navigate to Extras > Export Templates under the admin interface.
|
@ -1,9 +1,5 @@
|
||||
<h1>IPAM</h1>
|
||||
|
||||
IP address management (IPAM) entails the allocation of IP networks, addresses, and related numeric resources.
|
||||
|
||||
[TOC]
|
||||
|
||||
# VRFs
|
||||
|
||||
A VRF object in NetBox represents a virtual routing and forwarding (VRF) domain within a network. Each VRF is essentially a separate routing table: the same IP prefix or address can exist in multiple VRFs. VRFs are commonly used to isolate customers or organizations from one another within a network.
|
@ -1,9 +1,5 @@
|
||||
<h1>Secrets</h1>
|
||||
|
||||
"Secrets" are small amounts of data that must be kept confidential; for example, passwords and SNMP community strings. NetBox provides encrypted storage of secret data.
|
||||
|
||||
[TOC]
|
||||
|
||||
# Secrets
|
||||
|
||||
A secret represents a single credential or other string which must be stored securely. Each secret is assigned to a device within NetBox. The plaintext value of a secret is encrypted to a ciphertext immediately prior to storage within the database using a 256-bit AES master key. A SHA256 hash of the plaintext is also stored along with each ciphertext to validate the decrypted plaintext.
|
@ -1,502 +0,0 @@
|
||||
<h1>Getting Started</h1>
|
||||
|
||||
This guide documents the process of installing NetBox on an Ubuntu 14.04 server with [nginx](https://www.nginx.com/) and [gunicorn](http://gunicorn.org/).
|
||||
|
||||
[TOC]
|
||||
|
||||
# PostgreSQL
|
||||
|
||||
## Installation
|
||||
|
||||
The following packages are needed to install PostgreSQL:
|
||||
|
||||
* postgresql
|
||||
* libpq-dev
|
||||
* python-psycopg2
|
||||
|
||||
```
|
||||
# sudo apt-get install -y postgresql libpq-dev python-psycopg2
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
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.
|
||||
|
||||
DO NOT USE THE PASSWORD FROM THE EXAMPLE.
|
||||
|
||||
```
|
||||
# sudo -u postgres psql
|
||||
psql (9.3.13)
|
||||
Type "help" for help.
|
||||
|
||||
postgres=# CREATE DATABASE netbox;
|
||||
CREATE DATABASE
|
||||
postgres=# CREATE USER netbox WITH PASSWORD 'J5brHrAXFLQSif0K';
|
||||
CREATE ROLE
|
||||
postgres=# GRANT ALL PRIVILEGES ON DATABASE netbox TO netbox;
|
||||
GRANT
|
||||
postgres=# \q
|
||||
```
|
||||
|
||||
You can verify that authentication works using the following command:
|
||||
|
||||
```
|
||||
# psql -U netbox -h localhost -W
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# NetBox
|
||||
|
||||
## Installation
|
||||
|
||||
NetBox requires following dependencies:
|
||||
|
||||
* python2.7
|
||||
* python-dev
|
||||
* python-pip
|
||||
* libxml2-dev
|
||||
* libxslt1-dev
|
||||
* libffi-dev
|
||||
* graphviz
|
||||
|
||||
```
|
||||
# sudo apt-get install -y python2.7 python-dev git python-pip libxml2-dev libxslt1-dev libffi-dev graphviz
|
||||
```
|
||||
|
||||
You may opt to install NetBox either from a numbered release or by cloning the master branch of its repository on GitHub.
|
||||
|
||||
### Option A: Download a Release
|
||||
|
||||
Download the [latest stable release](https://github.com/digitalocean/netbox/releases) from GitHub as a tarball or ZIP archive. Extract it to your desired path. In this example, we'll use `/opt/netbox`.
|
||||
|
||||
```
|
||||
# wget https://github.com/digitalocean/netbox/archive/vX.Y.Z.tar.gz
|
||||
# tar -xzf vX.Y.Z.tar.gz -C /opt
|
||||
# cd /opt/
|
||||
# ln -s netbox-1.0.4/ netbox
|
||||
# cd /opt/netbox/
|
||||
```
|
||||
|
||||
### Option B: Clone the Git Repository
|
||||
|
||||
Create the base directory for the NetBox installation. For this guide, we'll use `/opt/netbox`.
|
||||
|
||||
```
|
||||
# mkdir -p /opt/netbox/
|
||||
# cd /opt/netbox/
|
||||
```
|
||||
|
||||
If `git` is not already installed, install it:
|
||||
|
||||
```
|
||||
# sudo apt-get install -y git
|
||||
```
|
||||
|
||||
Next, clone the **master** branch of the NetBox GitHub repository into the current directory:
|
||||
|
||||
```
|
||||
# git clone -b master https://github.com/digitalocean/netbox.git .
|
||||
Cloning into '.'...
|
||||
remote: Counting objects: 1994, done.
|
||||
remote: Compressing objects: 100% (150/150), done.
|
||||
remote: Total 1994 (delta 80), reused 0 (delta 0), pack-reused 1842
|
||||
Receiving objects: 100% (1994/1994), 472.36 KiB | 0 bytes/s, done.
|
||||
Resolving deltas: 100% (1495/1495), done.
|
||||
Checking connectivity... done.
|
||||
```
|
||||
|
||||
### Install Python Packages
|
||||
|
||||
Install the necessary Python packages using pip. (If you encounter any compilation errors during this step, ensure that you've installed all of the required dependencies.)
|
||||
|
||||
```
|
||||
# sudo pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Move into the NetBox configuration directory and make a copy of `configuration.example.py` named `configuration.py`.
|
||||
|
||||
```
|
||||
# cd netbox/netbox/
|
||||
# cp configuration.example.py configuration.py
|
||||
```
|
||||
|
||||
Open `configuration.py` with your preferred editor and set the following variables:
|
||||
|
||||
* ALLOWED_HOSTS
|
||||
* DATABASE
|
||||
* SECRET_KEY
|
||||
|
||||
### ALLOWED_HOSTS
|
||||
|
||||
This is a list of the valid hostnames by which this server can be reached. You must specify at least one name or IP address.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
ALLOWED_HOSTS = ['netbox.example.com', '192.0.2.123']
|
||||
```
|
||||
|
||||
### DATABASE
|
||||
|
||||
This parameter holds the database configuration details. You must define the username and password used when you configured PostgreSQL. If the service is running on a remote host, replace `localhost` with its address.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
DATABASE = {
|
||||
'NAME': 'netbox', # Database name
|
||||
'USER': 'netbox', # PostgreSQL username
|
||||
'PASSWORD': 'J5brHrAXFLQSif0K', # PostgreSQL password
|
||||
'HOST': 'localhost', # Database server
|
||||
'PORT': '', # Database port (leave blank for default)
|
||||
}
|
||||
```
|
||||
|
||||
### SECRET_KEY
|
||||
|
||||
Generate a random secret key of at least 50 alphanumeric characters. This key must be unique to this installation and must not be shared outside the local system.
|
||||
|
||||
You may use the script located at `netbox/generate_secret_key.py` to generate a suitable key.
|
||||
|
||||
## Run Migrations
|
||||
|
||||
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):
|
||||
|
||||
```
|
||||
# cd /opt/netbox/netbox/
|
||||
# ./manage.py migrate
|
||||
Operations to perform:
|
||||
Apply all migrations: dcim, sessions, admin, ipam, utilities, auth, circuits, contenttypes, extras, secrets, users
|
||||
Running migrations:
|
||||
Rendering model states... DONE
|
||||
Applying contenttypes.0001_initial... OK
|
||||
Applying auth.0001_initial... OK
|
||||
Applying admin.0001_initial... OK
|
||||
...
|
||||
```
|
||||
|
||||
If this step results in a PostgreSQL authentication error, ensure that the username and password created in the database match what has been specified in `configuration.py`
|
||||
|
||||
## Create a Super User
|
||||
|
||||
NetBox does not come with any predefined user accounts. You'll need to create a super user to be able to log into NetBox:
|
||||
|
||||
```
|
||||
# ./manage.py createsuperuser
|
||||
Username: admin
|
||||
Email address: admin@example.com
|
||||
Password:
|
||||
Password (again):
|
||||
Superuser created successfully.
|
||||
```
|
||||
|
||||
## Collect Static Files
|
||||
|
||||
```
|
||||
# ./manage.py collectstatic
|
||||
|
||||
You have requested to collect static files at the destination
|
||||
location as specified in your settings:
|
||||
|
||||
/opt/netbox/netbox/static
|
||||
|
||||
This will overwrite existing files!
|
||||
Are you sure you want to do this?
|
||||
|
||||
Type 'yes' to continue, or 'no' to cancel: yes
|
||||
```
|
||||
|
||||
## Test the Application
|
||||
|
||||
At this point, NetBox should be able to run. We can verify this by starting a development instance:
|
||||
|
||||
```
|
||||
# ./manage.py runserver 0.0.0.0:8000 --insecure
|
||||
Performing system checks...
|
||||
|
||||
System check identified no issues (0 silenced).
|
||||
June 17, 2016 - 16:17:36
|
||||
Django version 1.9.7, using settings 'netbox.settings'
|
||||
Starting development server at http://0.0.0.0:8000/
|
||||
Quit the server with CONTROL-C.
|
||||
```
|
||||
|
||||
Now if we navigate to the name or IP of the server (as defined in `ALLOWED_HOSTS`) we should be greeted with the NetBox home page. Note that this built-in web service is for development and testing purposes only. It is not suited for production use.
|
||||
|
||||
If the test service does not run, or you cannot reach the NetBox home page, something has gone wrong. Do not proceed with the rest of this guide until the installation has been corrected.
|
||||
|
||||
# Web Server and gunicorn
|
||||
|
||||
## Installation
|
||||
|
||||
We'll set up a simple HTTP 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/) for service persistence.
|
||||
|
||||
```
|
||||
# sudo apt-get install -y gunicorn supervisor
|
||||
```
|
||||
|
||||
## nginx Configuration
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
Once nginx is installed, proceed with the following configuration:
|
||||
|
||||
```
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
server_name netbox.example.com;
|
||||
|
||||
access_log off;
|
||||
|
||||
location /static/ {
|
||||
alias /opt/netbox/netbox/static/;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8001;
|
||||
proxy_set_header X-Forwarded-Host $server_name;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
add_header P3P 'CP="ALL DSP COR PSAa PSDa OUR NOR ONL UNI COM NAV"';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
```
|
||||
# cd /etc/nginx/sites-enabled/
|
||||
# rm default
|
||||
# ln -s /etc/nginx/sites-available/netbox
|
||||
```
|
||||
|
||||
Restart the nginx service to use the new configuration.
|
||||
|
||||
```
|
||||
# service nginx restart
|
||||
* Restarting nginx nginx
|
||||
```
|
||||
## Apache Configuration
|
||||
|
||||
```
|
||||
# sudo apt-get install -y apache2
|
||||
```
|
||||
|
||||
Once Apache is installed, proceed with the following configuration (Be sure to modify the `ServerName` appropriately):
|
||||
|
||||
```
|
||||
<VirtualHost *:80>
|
||||
ProxyPreserveHost On
|
||||
|
||||
ServerName netbox.example.com
|
||||
|
||||
Alias /static /opt/netbox/netbox/static
|
||||
|
||||
<Directory /opt/netbox/netbox/static>
|
||||
Options Indexes FollowSymLinks MultiViews
|
||||
AllowOverride None
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
<Location /static>
|
||||
ProxyPass !
|
||||
</Location>
|
||||
|
||||
ProxyPass / http://127.0.0.1:8001/
|
||||
ProxyPassReverse / http://127.0.0.1:8001/
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
Save the contents of the above example in `/etc/apache2/sites-available/netbox.conf`, enable the `proxy` and `proxy_http` modules, and reload Apache:
|
||||
|
||||
```
|
||||
# a2enmod proxy
|
||||
# a2enmod proxy_http
|
||||
# a2ensite netbox
|
||||
# service apache2 restart
|
||||
```
|
||||
|
||||
## gunicorn Configuration
|
||||
|
||||
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.
|
||||
|
||||
```
|
||||
command = '/usr/bin/gunicorn'
|
||||
pythonpath = '/opt/netbox/netbox'
|
||||
bind = '127.0.0.1:8001'
|
||||
workers = 3
|
||||
user = 'www-data'
|
||||
```
|
||||
|
||||
## supervisord Configuration
|
||||
|
||||
Save the following as `/etc/supervisor/conf.d/netbox.conf`. Update the `command` and `directory` paths as needed.
|
||||
|
||||
```
|
||||
[program:netbox]
|
||||
command = gunicorn -c /opt/netbox/gunicorn_config.py netbox.wsgi
|
||||
directory = /opt/netbox/netbox/
|
||||
user = www-data
|
||||
```
|
||||
|
||||
Finally, restart the supervisor service to detect and run the gunicorn service:
|
||||
|
||||
```
|
||||
# service supervisor restart
|
||||
```
|
||||
|
||||
At this point, you should be able to connect to the nginx HTTP service at the server name or IP address you provided. If you are unable to connect, check that the nginx service is running and properly configured. If you receive a 502 (bad gateway) error, this indicates that gunicorn is misconfigured or not running.
|
||||
|
||||
Please keep in mind that the configurations provided here are bare minimums required to get NetBox up and running. You will almost certainly want to make some changes to better suit your production environment.
|
||||
|
||||
## Let's Encrypt SSL + nginx
|
||||
|
||||
To add SSL support to the installation we'll start by installing the arbitrary precision calculator language.
|
||||
|
||||
```
|
||||
# sudo apt-get install -y bc
|
||||
```
|
||||
|
||||
Next we'll clone Let's Encrypt into /opt/:
|
||||
|
||||
```
|
||||
# sudo git clone https://github.com/letsencrypt/letsencrypt /opt/letsencrypt
|
||||
```
|
||||
|
||||
To ensure Let's Encrypt can publicly access the directory it needs for certificate validation you'll need to edit `/etc/nginx/sites-available/netbox` and add:
|
||||
|
||||
```
|
||||
location /.well-known/ {
|
||||
alias /opt/netbox/netbox/.well-known/;
|
||||
allow all;
|
||||
}
|
||||
```
|
||||
|
||||
Then restart nginix:
|
||||
|
||||
```
|
||||
# sudo services nginx restart
|
||||
```
|
||||
|
||||
To create the certificate use the following commands ensuring to change `netbox.example.com` to the domain name of the server:
|
||||
|
||||
```
|
||||
# cd /opt/letsencrypt
|
||||
# ./letsencrypt-auto certonly -a webroot --webroot-path=/opt/netbox/netbox/ -d netbox.example.com
|
||||
```
|
||||
|
||||
If you wish to add support for the `www` prefix you'd use:
|
||||
|
||||
```
|
||||
# cd /opt/letsencrypt
|
||||
# ./letsencrypt-auto certonly -a webroot --webroot-path=/opt/netbox/netbox/ -d netbox.example.com -d www.netbox.example.com
|
||||
```
|
||||
|
||||
Make sure you have DNS records setup for the hostnames you use and that they resolve back the netbox server.
|
||||
|
||||
You will be prompted for your email address to receive notifications about your SSL and then asked to accept the subscriber agreement.
|
||||
|
||||
If successful you'll now have four files in `/etc/letsencrypt/live/netbox.example.com` (remember, your hostname is different)
|
||||
|
||||
```
|
||||
cert.pem
|
||||
chain.pem
|
||||
fullchain.pem
|
||||
privkey.pem
|
||||
```
|
||||
|
||||
Now edit your nginx configuration `/etc/nginx/sites-available/netbox` and at the top edit to the following:
|
||||
|
||||
```
|
||||
#listen 80;
|
||||
#listen [::]80;
|
||||
listen 443;
|
||||
listen [::]443;
|
||||
|
||||
ssl on;
|
||||
ssl_certificate /etc/letsencrypt/live/netbox.example.com/cert.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/netbox.example.com/privkey.pem;
|
||||
```
|
||||
|
||||
If you are not using IPv6 then you do not need `listen [::]443;` The two commented lines are for non-SSL for both IPv4 and IPv6.
|
||||
|
||||
Lastly, restart nginx:
|
||||
|
||||
```
|
||||
# sudo services nginx restart
|
||||
```
|
||||
|
||||
You should now have netbox running on a SSL protected connection.
|
||||
|
||||
# Upgrading
|
||||
|
||||
## Installation of Upgrade
|
||||
|
||||
As with the initial installation, you can upgrade NetBox by either downloading the latest release package or by cloning the `master` branch of the git repository.
|
||||
|
||||
### Option A: Download a Release
|
||||
|
||||
Download the [latest stable release](https://github.com/digitalocean/netbox/releases) from GitHub as a tarball or ZIP archive. Extract it to your desired path. In this example, we'll use `/opt/netbox`. For this guide we are using 1.0.4 as the old version and 1.0.7 as the new version.
|
||||
|
||||
Download & extract latest version:
|
||||
```
|
||||
# wget https://github.com/digitalocean/netbox/archive/vX.Y.Z.tar.gz
|
||||
# tar -xzf vX.Y.Z.tar.gz -C /opt
|
||||
# cd /opt/
|
||||
# ln -sf netbox-1.0.7/ netbox
|
||||
```
|
||||
|
||||
Copy the 'configuration.py' you created when first installing to the new version:
|
||||
```
|
||||
# cp /opt/netbox-1.0.4/configuration.py /opt/netbox/configuration.py
|
||||
```
|
||||
|
||||
### Option B: Clone the Git Repository (latest master release)
|
||||
|
||||
For this guide, we'll use `/opt/netbox`.
|
||||
|
||||
Check that your git branch is up to date & is set to master:
|
||||
```
|
||||
# cd /opt/netbox
|
||||
# git status
|
||||
```
|
||||
|
||||
If not on branch master, set it and verify status:
|
||||
```
|
||||
# git checkout master
|
||||
# git status
|
||||
```
|
||||
|
||||
Pull down the set branch from git status above:
|
||||
```
|
||||
# git pull
|
||||
```
|
||||
|
||||
|
||||
## Upgrade Script & Netbox Restart
|
||||
|
||||
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).
|
||||
|
||||
```
|
||||
# ./upgrade.sh
|
||||
```
|
||||
|
||||
This script:
|
||||
|
||||
* Installs or upgrades any new required Python packages
|
||||
* Applies any database migrations that were included in the release
|
||||
* Collects all static files to be served by the HTTP service
|
||||
|
||||
Finally, restart the WSGI service to run the new code. If you followed this guide for the initial installation, this is done using `supervisorctl`:
|
||||
|
||||
```
|
||||
# sudo supervisorctl restart netbox
|
||||
```
|
@ -1,3 +1,53 @@
|
||||
# NetBox Documentation
|
||||
# What is NetBox?
|
||||
|
||||
NetBox is an IP address management (IPAM) and data center infrastructure management (DCIM) application.
|
||||
NetBox is an open source web application designed to help manage and document computer networks. Initially conceived by the network engineering team at [DigitalOcean](https://www.digitalocean.com/), NetBox was developed specifically to address the needs of network and infrastructure engineers. It encompasses the following aspects of network management:
|
||||
|
||||
* **IP address management (IPAM)** - IP networks and addresses, VRFs, and VLANs
|
||||
* **Equipment racks** - Organized by group and site
|
||||
* **Devices** - Types of devices and where they are installed
|
||||
* **Connections** - Network, console, and power connections among devices
|
||||
* **Data circuits** - Long-haul communications circuits and providers
|
||||
* **Secrets** - Encrypted storage of sensitive credentials
|
||||
|
||||
# What NetBox Isn't
|
||||
|
||||
While NetBox strives to cover many areas of network management, the scope of its feature set is necessarily limited. This ensures that development focuses on core functionality and that scope creep is reasonably contained. To that end, it might help to provide some examples of functionality that NetBox **does not** provide:
|
||||
|
||||
* Network monitoring
|
||||
* DNS server
|
||||
* RADIUS server
|
||||
* Configuration management
|
||||
* Facilities management
|
||||
|
||||
That said, NetBox _can_ be used to great effect in populating external tools with the data they need to perform these functions.
|
||||
|
||||
# Design Philosophy
|
||||
|
||||
NetBox was designed with the following tenets foremost in mind.
|
||||
|
||||
## Replicate the Real World
|
||||
|
||||
Careful consideration has been given to the data model to ensure that it can accurately reflect a real-world network. For instance, IP addresses are assigned not to devices, but to specific interfaces attached to a device, and an interface may have multiple IP addresses assigned to it.
|
||||
|
||||
## Serve as a "Source of Truth"
|
||||
|
||||
NetBox intends to represent the _desired_ state of a network versus its _operational_ state. As such, automated import of live network state is strongly discouraged. All data created in NetBox should first be vetted by a human to ensure its integrity. NetBox can then be used to populate monitoring and provisioning systems with a high degree of confidence.
|
||||
|
||||
## Keep it Simple
|
||||
|
||||
When given a choice between a relatively simple [80% solution](https://en.wikipedia.org/wiki/Pareto_principle) and a much more complex complete solution, the former will typically be favored. This ensures a lean codebase with a low learning curve.
|
||||
|
||||
# Application Stack
|
||||
|
||||
NetBox is built on the [Django](https://djangoproject.com/) Python framework and utilizes a [PostgreSQL](https://www.postgresql.org/) database. It runs as a WSGI service behind your choice of HTTP server.
|
||||
|
||||
| Function | Component |
|
||||
|--------------|-------------------|
|
||||
| HTTP Service | nginx or Apache |
|
||||
| WSGI Service | gunicorn or uWSGI |
|
||||
| Application | Django/Python |
|
||||
| Database | PostgreSQL |
|
||||
|
||||
# Getting Started
|
||||
|
||||
See the [getting started](getting-started.md) guide for help with getting NetBox up and running quickly.
|
||||
|
@ -1,13 +1,11 @@
|
||||
<h1>Getting Started with NetBox and Docker</h1>
|
||||
|
||||
This guide assumes that the latest versions of [Docker](https://www.docker.com/) and [docker-compose](https://docs.docker.com/compose/) are already installed in your host.
|
||||
This guide demonstrates how to build and run NetBox as a Docker container. It assumes that the latest versions of [Docker](https://www.docker.com/) and [docker-compose](https://docs.docker.com/compose/) are already installed in your host.
|
||||
|
||||
# Quickstart
|
||||
|
||||
To get NetBox up and running:
|
||||
|
||||
```
|
||||
git clone https://github.com/digitalocean/netbox.git
|
||||
git clone -b master https://github.com/digitalocean/netbox.git
|
||||
cd netbox
|
||||
docker-compose up -d
|
||||
```
|
||||
@ -15,13 +13,13 @@ docker-compose up -d
|
||||
The application will be available on http://localhost/ after a few minutes.
|
||||
|
||||
Default credentials:
|
||||
* user: admin
|
||||
* password: admin
|
||||
|
||||
* Username: **admin**
|
||||
* Password: **admin**
|
||||
|
||||
# Configuration
|
||||
You can configure the app at runtime using variables (see docker-compose.yml).
|
||||
|
||||
Possible environment variables:
|
||||
You can configure the app at runtime using variables (see `docker-compose.yml`). Possible environment variables include:
|
||||
|
||||
* SUPERUSER_NAME
|
||||
* SUPERUSER_EMAIL
|
||||
@ -51,4 +49,3 @@ Possible environment variables:
|
||||
* SHORT_TIME_FORMAT
|
||||
* DATETIME_FORMAT
|
||||
* SHORT_DATETIME_FORMAT
|
||||
|
101
docs/installation/ldap.md
Normal file
101
docs/installation/ldap.md
Normal file
@ -0,0 +1,101 @@
|
||||
This guide explains how to implement LDAP authentication using an external server. User authentication will fall back to
|
||||
built-in Django users in the event of a failure.
|
||||
|
||||
# Requirements
|
||||
|
||||
## Install openldap-devel
|
||||
|
||||
On Ubuntu:
|
||||
|
||||
```
|
||||
sudo apt-get install -y python-dev libldap2-dev libsasl2-dev libssl-dev
|
||||
```
|
||||
|
||||
On CentOS:
|
||||
|
||||
```
|
||||
sudo yum install -y python-devel openldap-devel
|
||||
```
|
||||
|
||||
## Install django-auth-ldap
|
||||
|
||||
```
|
||||
sudo pip install django-auth-ldap
|
||||
```
|
||||
|
||||
# Configuration
|
||||
|
||||
Create a file in the same directory as `configuration.py` (typically `netbox/netbox/`) named `ldap_config.py`. Define all of the parameters required below in `ldap_config.py`.
|
||||
|
||||
## General Server Configuration
|
||||
|
||||
```python
|
||||
import ldap
|
||||
|
||||
# Server URI
|
||||
AUTH_LDAP_SERVER_URI = "ldaps://ad.example.com"
|
||||
|
||||
# The following may be needed if you are binding to Active Directory.
|
||||
AUTH_LDAP_CONNECTION_OPTIONS = {
|
||||
ldap.OPT_REFERRALS: 0
|
||||
}
|
||||
|
||||
# Set the DN and password for the NetBox service account.
|
||||
AUTH_LDAP_BIND_DN = "CN=NETBOXSA, OU=Service Accounts,DC=example,DC=com"
|
||||
AUTH_LDAP_BIND_PASSWORD = "demo"
|
||||
|
||||
# Include this setting if you want to ignore certificate errors. This might be needed to accept a self-signed cert.
|
||||
# Note that this is a NetBox-specific setting which sets:
|
||||
# ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
|
||||
LDAP_IGNORE_CERT_ERRORS = True
|
||||
```
|
||||
|
||||
## User Authentication
|
||||
|
||||
```python
|
||||
from django_auth_ldap.config import LDAPSearch
|
||||
|
||||
# This search matches users with the sAMAccountName equal to the provided username. This is required if the user's
|
||||
# username is not in their DN (Active Directory).
|
||||
AUTH_LDAP_USER_SEARCH = LDAPSearch("ou=Users,dc=example,dc=com",
|
||||
ldap.SCOPE_SUBTREE,
|
||||
"(sAMAccountName=%(user)s)")
|
||||
|
||||
# If a user's DN is producible from their username, we don't need to search.
|
||||
AUTH_LDAP_USER_DN_TEMPLATE = "uid=%(user)s,ou=users,dc=example,dc=com"
|
||||
|
||||
# You can map user attributes to Django attributes as so.
|
||||
AUTH_LDAP_USER_ATTR_MAP = {
|
||||
"first_name": "givenName",
|
||||
"last_name": "sn"
|
||||
}
|
||||
```
|
||||
|
||||
# User Groups for Permissions
|
||||
|
||||
```python
|
||||
from django_auth_ldap.config import LDAPSearch, GroupOfNamesType
|
||||
|
||||
# This search ought to return all groups to which the user belongs. django_auth_ldap uses this to determine group
|
||||
# heirarchy.
|
||||
AUTH_LDAP_GROUP_SEARCH = LDAPSearch("dc=example,dc=com", ldap.SCOPE_SUBTREE,
|
||||
"(objectClass=group)")
|
||||
AUTH_LDAP_GROUP_TYPE = GroupOfNamesType()
|
||||
|
||||
# Define a group required to login.
|
||||
AUTH_LDAP_REQUIRE_GROUP = "CN=NETBOX_USERS,DC=example,DC=com"
|
||||
|
||||
# Define special user types using groups. Exercise great caution when assigning superuser status.
|
||||
AUTH_LDAP_USER_FLAGS_BY_GROUP = {
|
||||
"is_active": "cn=active,ou=groups,dc=example,dc=com",
|
||||
"is_staff": "cn=staff,ou=groups,dc=example,dc=com",
|
||||
"is_superuser": "cn=superuser,ou=groups,dc=example,dc=com"
|
||||
}
|
||||
|
||||
# For more granular permissions, we can map LDAP groups to Django groups.
|
||||
AUTH_LDAP_FIND_GROUP_PERMS = True
|
||||
|
||||
# Cache groups for one hour to reduce LDAP traffic
|
||||
AUTH_LDAP_CACHE_GROUPS = True
|
||||
AUTH_LDAP_GROUP_CACHE_TIMEOUT = 3600
|
||||
```
|
181
docs/installation/netbox.md
Normal file
181
docs/installation/netbox.md
Normal file
@ -0,0 +1,181 @@
|
||||
# Installation
|
||||
|
||||
NetBox requires following system dependencies:
|
||||
|
||||
* python2.7
|
||||
* python-dev
|
||||
* python-pip
|
||||
* libxml2-dev
|
||||
* libxslt1-dev
|
||||
* libffi-dev
|
||||
* graphviz
|
||||
* libpq-dev
|
||||
|
||||
```
|
||||
# sudo apt-get install -y python2.7 python-dev git python-pip libxml2-dev libxslt1-dev libffi-dev graphviz libpq-dev
|
||||
```
|
||||
|
||||
You may opt to install NetBox either from a numbered release or by cloning the master branch of its repository on GitHub.
|
||||
|
||||
## Option A: Download a Release
|
||||
|
||||
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`.
|
||||
|
||||
```
|
||||
# wget https://github.com/digitalocean/netbox/archive/vX.Y.Z.tar.gz
|
||||
# tar -xzf vX.Y.Z.tar.gz -C /opt
|
||||
# cd /opt/
|
||||
# ln -s netbox-X.Y.Z/ netbox
|
||||
# cd /opt/netbox/
|
||||
```
|
||||
|
||||
## Option B: Clone the Git Repository
|
||||
|
||||
Create the base directory for the NetBox installation. For this guide, we'll use `/opt/netbox`.
|
||||
|
||||
```
|
||||
# mkdir -p /opt/netbox/
|
||||
# cd /opt/netbox/
|
||||
```
|
||||
|
||||
If `git` is not already installed, install it:
|
||||
|
||||
```
|
||||
# sudo apt-get install -y git
|
||||
```
|
||||
|
||||
Next, clone the **master** branch of the NetBox GitHub repository into the current directory:
|
||||
|
||||
```
|
||||
# git clone -b master https://github.com/digitalocean/netbox.git .
|
||||
Cloning into '.'...
|
||||
remote: Counting objects: 1994, done.
|
||||
remote: Compressing objects: 100% (150/150), done.
|
||||
remote: Total 1994 (delta 80), reused 0 (delta 0), pack-reused 1842
|
||||
Receiving objects: 100% (1994/1994), 472.36 KiB | 0 bytes/s, done.
|
||||
Resolving deltas: 100% (1495/1495), done.
|
||||
Checking connectivity... done.
|
||||
```
|
||||
|
||||
## Install Python Packages
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
# Configuration
|
||||
|
||||
Move into the NetBox configuration directory and make a copy of `configuration.example.py` named `configuration.py`.
|
||||
|
||||
```
|
||||
# cd netbox/netbox/
|
||||
# cp configuration.example.py configuration.py
|
||||
```
|
||||
|
||||
Open `configuration.py` with your preferred editor and set the following variables:
|
||||
|
||||
* ALLOWED_HOSTS
|
||||
* DATABASE
|
||||
* SECRET_KEY
|
||||
|
||||
## ALLOWED_HOSTS
|
||||
|
||||
This is a list of the valid hostnames by which this server can be reached. You must specify at least one name or IP address.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
ALLOWED_HOSTS = ['netbox.example.com', '192.0.2.123']
|
||||
```
|
||||
|
||||
## DATABASE
|
||||
|
||||
This parameter holds the database configuration details. You must define the username and password used when you configured PostgreSQL. If the service is running on a remote host, replace `localhost` with its address.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
DATABASE = {
|
||||
'NAME': 'netbox', # Database name
|
||||
'USER': 'netbox', # PostgreSQL username
|
||||
'PASSWORD': 'J5brHrAXFLQSif0K', # PostgreSQL password
|
||||
'HOST': 'localhost', # Database server
|
||||
'PORT': '', # Database port (leave blank for default)
|
||||
}
|
||||
```
|
||||
|
||||
## SECRET_KEY
|
||||
|
||||
Generate a random secret key of at least 50 alphanumeric characters. This key must be unique to this installation and must not be shared outside the local system.
|
||||
|
||||
You may use the script located at `netbox/generate_secret_key.py` to generate a suitable key.
|
||||
|
||||
# Run Database Migrations
|
||||
|
||||
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):
|
||||
|
||||
```
|
||||
# cd /opt/netbox/netbox/
|
||||
# ./manage.py migrate
|
||||
Operations to perform:
|
||||
Apply all migrations: dcim, sessions, admin, ipam, utilities, auth, circuits, contenttypes, extras, secrets, users
|
||||
Running migrations:
|
||||
Rendering model states... DONE
|
||||
Applying contenttypes.0001_initial... OK
|
||||
Applying auth.0001_initial... OK
|
||||
Applying admin.0001_initial... OK
|
||||
...
|
||||
```
|
||||
|
||||
If this step results in a PostgreSQL authentication error, ensure that the username and password created in the database match what has been specified in `configuration.py`
|
||||
|
||||
# Create a Super User
|
||||
|
||||
NetBox does not come with any predefined user accounts. You'll need to create a super user to be able to log into NetBox:
|
||||
|
||||
```
|
||||
# ./manage.py createsuperuser
|
||||
Username: admin
|
||||
Email address: admin@example.com
|
||||
Password:
|
||||
Password (again):
|
||||
Superuser created successfully.
|
||||
```
|
||||
|
||||
# Collect Static Files
|
||||
|
||||
```
|
||||
# ./manage.py collectstatic
|
||||
|
||||
You have requested to collect static files at the destination
|
||||
location as specified in your settings:
|
||||
|
||||
/opt/netbox/netbox/static
|
||||
|
||||
This will overwrite existing files!
|
||||
Are you sure you want to do this?
|
||||
|
||||
Type 'yes' to continue, or 'no' to cancel: yes
|
||||
```
|
||||
|
||||
# Test the Application
|
||||
|
||||
At this point, NetBox should be able to run. We can verify this by starting a development instance:
|
||||
|
||||
```
|
||||
# ./manage.py runserver 0.0.0.0:8000 --insecure
|
||||
Performing system checks...
|
||||
|
||||
System check identified no issues (0 silenced).
|
||||
June 17, 2016 - 16:17:36
|
||||
Django version 1.9.7, using settings 'netbox.settings'
|
||||
Starting development server at http://0.0.0.0:8000/
|
||||
Quit the server with CONTROL-C.
|
||||
```
|
||||
|
||||
Now if we navigate to the name or IP of the server (as defined in `ALLOWED_HOSTS`) we should be greeted with the NetBox home page. Note that this built-in web service is for development and testing purposes only. It is not suited for production use.
|
||||
|
||||
!!! warning
|
||||
If the test service does not run, or you cannot reach the NetBox home page, something has gone wrong. Do not proceed with the rest of this guide until the installation has been corrected.
|
42
docs/installation/postgresql.md
Normal file
42
docs/installation/postgresql.md
Normal file
@ -0,0 +1,42 @@
|
||||
NetBox requires a PostgreSQL database to store data. MySQL is not supported, as NetBox leverage's PostgreSQL's built-in [network address types](https://www.postgresql.org/docs/9.1/static/datatype-net-types.html).
|
||||
|
||||
# Installation
|
||||
|
||||
The following packages are needed to install PostgreSQL with Python support:
|
||||
|
||||
* postgresql
|
||||
* libpq-dev
|
||||
* python-psycopg2
|
||||
|
||||
```
|
||||
# sudo apt-get install -y postgresql libpq-dev python-psycopg2
|
||||
```
|
||||
|
||||
# Configuration
|
||||
|
||||
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.
|
||||
|
||||
!!! danger
|
||||
DO NOT USE THE PASSWORD FROM THE EXAMPLE.
|
||||
|
||||
```
|
||||
# sudo -u postgres psql
|
||||
psql (9.3.13)
|
||||
Type "help" for help.
|
||||
|
||||
postgres=# CREATE DATABASE netbox;
|
||||
CREATE DATABASE
|
||||
postgres=# CREATE USER netbox WITH PASSWORD 'J5brHrAXFLQSif0K';
|
||||
CREATE ROLE
|
||||
postgres=# GRANT ALL PRIVILEGES ON DATABASE netbox TO netbox;
|
||||
GRANT
|
||||
postgres=# \q
|
||||
```
|
||||
|
||||
You can verify that authentication works issuing the following command and providing the configured password:
|
||||
|
||||
```
|
||||
# psql -U netbox -h localhost -W
|
||||
```
|
||||
|
||||
If successful, you will enter a `postgres` prompt. Type `\q` to exit.
|
63
docs/installation/upgrading.md
Normal file
63
docs/installation/upgrading.md
Normal file
@ -0,0 +1,63 @@
|
||||
# Install the Latest Code
|
||||
|
||||
As with the initial installation, you can upgrade NetBox by either downloading the latest release package or by cloning the `master` branch of the git repository.
|
||||
|
||||
## Option A: Download a Release
|
||||
|
||||
Download the [latest stable release](https://github.com/digitalocean/netbox/releases) from GitHub as a tarball or ZIP archive. Extract it to your desired path. In this example, we'll use `/opt/netbox`. For this guide we are using 1.0.4 as the old version and 1.0.7 as the new version.
|
||||
|
||||
Download & extract latest version:
|
||||
```
|
||||
# wget https://github.com/digitalocean/netbox/archive/vX.Y.Z.tar.gz
|
||||
# tar -xzf vX.Y.Z.tar.gz -C /opt
|
||||
# cd /opt/
|
||||
# ln -sf netbox-1.0.7/ netbox
|
||||
```
|
||||
|
||||
Copy the 'configuration.py' you created when first installing to the new version:
|
||||
```
|
||||
# cp /opt/netbox-1.0.4/configuration.py /opt/netbox/configuration.py
|
||||
```
|
||||
|
||||
## Option B: Clone the Git Repository (latest master release)
|
||||
|
||||
For this guide, we'll use `/opt/netbox`.
|
||||
|
||||
Check that your git branch is up to date & is set to master:
|
||||
```
|
||||
# cd /opt/netbox
|
||||
# git status
|
||||
```
|
||||
|
||||
If not on branch master, set it and verify status:
|
||||
```
|
||||
# git checkout master
|
||||
# git status
|
||||
```
|
||||
|
||||
Pull down the set branch from git status above:
|
||||
```
|
||||
# git pull
|
||||
```
|
||||
|
||||
# Run the Upgrade Script
|
||||
|
||||
Once the new code is in place, run the upgrade script (which may need to be run as root depending on how your environment is configured).
|
||||
|
||||
```
|
||||
# ./upgrade.sh
|
||||
```
|
||||
|
||||
This script:
|
||||
|
||||
* Installs or upgrades any new required Python packages
|
||||
* Applies any database migrations that were included in the release
|
||||
* Collects all static files to be served by the HTTP service
|
||||
|
||||
# Restart the WSGI Service
|
||||
|
||||
Finally, restart the WSGI service to run the new code. If you followed this guide for the initial installation, this is done using `supervisorctl`:
|
||||
|
||||
```
|
||||
# sudo supervisorctl restart netbox
|
||||
```
|
132
docs/installation/web-server.md
Normal file
132
docs/installation/web-server.md
Normal file
@ -0,0 +1,132 @@
|
||||
# Web Server Installation
|
||||
|
||||
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.
|
||||
|
||||
```
|
||||
# sudo apt-get install -y gunicorn supervisor
|
||||
```
|
||||
|
||||
## Option A: nginx
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
Once nginx is installed, proceed with the following configuration:
|
||||
|
||||
```
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
server_name netbox.example.com;
|
||||
|
||||
access_log off;
|
||||
|
||||
location /static/ {
|
||||
alias /opt/netbox/netbox/static/;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8001;
|
||||
proxy_set_header X-Forwarded-Host $server_name;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
add_header P3P 'CP="ALL DSP COR PSAa PSDa OUR NOR ONL UNI COM NAV"';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
```
|
||||
# cd /etc/nginx/sites-enabled/
|
||||
# rm default
|
||||
# ln -s /etc/nginx/sites-available/netbox
|
||||
```
|
||||
|
||||
Restart the nginx service to use the new configuration.
|
||||
|
||||
```
|
||||
# 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).
|
||||
|
||||
## Option B: Apache
|
||||
|
||||
```
|
||||
# sudo apt-get install -y apache2
|
||||
```
|
||||
|
||||
Once Apache is installed, proceed with the following configuration (Be sure to modify the `ServerName` appropriately):
|
||||
|
||||
```
|
||||
<VirtualHost *:80>
|
||||
ProxyPreserveHost On
|
||||
|
||||
ServerName netbox.example.com
|
||||
|
||||
Alias /static /opt/netbox/netbox/static
|
||||
|
||||
<Directory /opt/netbox/netbox/static>
|
||||
Options Indexes FollowSymLinks MultiViews
|
||||
AllowOverride None
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
<Location /static>
|
||||
ProxyPass !
|
||||
</Location>
|
||||
|
||||
ProxyPass / http://127.0.0.1:8001/
|
||||
ProxyPassReverse / http://127.0.0.1:8001/
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
Save the contents of the above example in `/etc/apache2/sites-available/netbox.conf`, enable the `proxy` and `proxy_http` modules, and reload Apache:
|
||||
|
||||
```
|
||||
# a2enmod proxy
|
||||
# a2enmod proxy_http
|
||||
# a2ensite netbox
|
||||
# service apache2 restart
|
||||
```
|
||||
|
||||
To enable SSL, consider this guide on [securing Apache with Let's Encrypt](https://www.digitalocean.com/community/tutorials/how-to-secure-apache-with-let-s-encrypt-on-ubuntu-14-04).
|
||||
|
||||
# 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.
|
||||
|
||||
```
|
||||
command = '/usr/bin/gunicorn'
|
||||
pythonpath = '/opt/netbox/netbox'
|
||||
bind = '127.0.0.1:8001'
|
||||
workers = 3
|
||||
user = 'www-data'
|
||||
```
|
||||
|
||||
# supervisord Installation
|
||||
|
||||
Save the following as `/etc/supervisor/conf.d/netbox.conf`. Update the `command` and `directory` paths as needed.
|
||||
|
||||
```
|
||||
[program:netbox]
|
||||
command = gunicorn -c /opt/netbox/gunicorn_config.py netbox.wsgi
|
||||
directory = /opt/netbox/netbox/
|
||||
user = www-data
|
||||
```
|
||||
|
||||
Finally, restart the supervisor service to detect and run the gunicorn service:
|
||||
|
||||
```
|
||||
# service supervisor restart
|
||||
```
|
||||
|
||||
At this point, you should be able to connect to the nginx HTTP service at the server name or IP address you provided. If you are unable to connect, check that the nginx service is running and properly configured. If you receive a 502 (bad gateway) error, this indicates that gunicorn is misconfigured or not running.
|
||||
|
||||
!!! info
|
||||
Please keep in mind that the configurations provided here are bare minimums required to get NetBox up and running. You will almost certainly want to make some changes to better suit your production environment.
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 134 KiB |
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 112 KiB |
24
mkdocs.yml
Normal file
24
mkdocs.yml
Normal file
@ -0,0 +1,24 @@
|
||||
site_name: NetBox
|
||||
|
||||
pages:
|
||||
- 'Introduction': 'index.md'
|
||||
- 'Installation':
|
||||
- 'PostgreSQL': 'installation/postgresql.md'
|
||||
- 'NetBox': 'installation/netbox.md'
|
||||
- 'Web Server': 'installation/web-server.md'
|
||||
- 'LDAP (Optional)': 'installation/ldap.md'
|
||||
- 'Upgrading': 'installation/upgrading.md'
|
||||
- 'Alternate Install: Docker': 'installation/docker.md'
|
||||
- 'Configuration':
|
||||
- 'Mandatory Settings': 'configuration/mandatory-settings.md'
|
||||
- 'Optional Settings': 'configuration/optional-settings.md'
|
||||
- 'Data Model':
|
||||
- 'Circuits': 'data-model/circuits.md'
|
||||
- 'DCIM': 'data-model/dcim.md'
|
||||
- 'IPAM': 'data-model/ipam.md'
|
||||
- 'Secrets': 'data-model/secrets.md'
|
||||
- 'Extras': 'data-model/extras.md'
|
||||
- 'API Integration': 'api-integration.md'
|
||||
|
||||
markdown_extensions:
|
||||
- admonition:
|
@ -89,7 +89,7 @@ class DeviceTypeAdmin(admin.ModelAdmin):
|
||||
power_port_count=Count('power_port_templates', distinct=True),
|
||||
power_outlet_count=Count('power_outlet_templates', distinct=True),
|
||||
interface_count=Count('interface_templates', distinct=True),
|
||||
devicebay_count=Count('devicebay_templates', distinct=True),
|
||||
devicebay_count=Count('device_bay_templates', distinct=True),
|
||||
)
|
||||
|
||||
def console_ports(self, instance):
|
||||
|
@ -221,12 +221,14 @@ class DeviceSerializer(serializers.ModelSerializer):
|
||||
platform = PlatformNestedSerializer()
|
||||
rack = RackNestedSerializer()
|
||||
primary_ip = DeviceIPAddressNestedSerializer()
|
||||
primary_ip4 = DeviceIPAddressNestedSerializer()
|
||||
primary_ip6 = DeviceIPAddressNestedSerializer()
|
||||
parent_device = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Device
|
||||
fields = ['id', 'name', 'display_name', 'device_type', 'device_role', 'platform', 'serial', 'rack', 'position',
|
||||
'face', 'parent_device', 'status', 'primary_ip', 'comments']
|
||||
'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', 'primary_ip6', 'comments']
|
||||
|
||||
def get_parent_device(self, obj):
|
||||
try:
|
||||
|
@ -194,7 +194,7 @@ class DeviceListView(generics.ListAPIView):
|
||||
List devices (filterable)
|
||||
"""
|
||||
queryset = Device.objects.select_related('device_type__manufacturer', 'device_role', 'platform', 'rack__site')\
|
||||
.prefetch_related('primary_ip__nat_outside')
|
||||
.prefetch_related('primary_ip4__nat_outside', 'primary_ip6__nat_outside')
|
||||
serializer_class = serializers.DeviceSerializer
|
||||
filter_class = filters.DeviceFilter
|
||||
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES + [BINDZoneRenderer, FlatJSONRenderer]
|
||||
|
@ -1919,7 +1919,8 @@
|
||||
"position": 1,
|
||||
"face": 0,
|
||||
"status": true,
|
||||
"primary_ip": 1,
|
||||
"primary_ip4": 1,
|
||||
"primary_ip6": null,
|
||||
"comments": ""
|
||||
}
|
||||
},
|
||||
@ -1938,7 +1939,8 @@
|
||||
"position": 17,
|
||||
"face": 0,
|
||||
"status": true,
|
||||
"primary_ip": 5,
|
||||
"primary_ip4": 5,
|
||||
"primary_ip6": null,
|
||||
"comments": ""
|
||||
}
|
||||
},
|
||||
@ -1957,7 +1959,8 @@
|
||||
"position": 33,
|
||||
"face": 0,
|
||||
"status": true,
|
||||
"primary_ip": null,
|
||||
"primary_ip4": null,
|
||||
"primary_ip6": null,
|
||||
"comments": ""
|
||||
}
|
||||
},
|
||||
@ -1976,7 +1979,8 @@
|
||||
"position": 34,
|
||||
"face": 0,
|
||||
"status": true,
|
||||
"primary_ip": null,
|
||||
"primary_ip4": null,
|
||||
"primary_ip6": null,
|
||||
"comments": ""
|
||||
}
|
||||
},
|
||||
@ -1995,7 +1999,8 @@
|
||||
"position": 34,
|
||||
"face": 0,
|
||||
"status": true,
|
||||
"primary_ip": null,
|
||||
"primary_ip4": null,
|
||||
"primary_ip6": null,
|
||||
"comments": ""
|
||||
}
|
||||
},
|
||||
@ -2014,7 +2019,8 @@
|
||||
"position": 33,
|
||||
"face": 0,
|
||||
"status": true,
|
||||
"primary_ip": null,
|
||||
"primary_ip4": null,
|
||||
"primary_ip6": null,
|
||||
"comments": ""
|
||||
}
|
||||
},
|
||||
@ -2033,7 +2039,8 @@
|
||||
"position": 1,
|
||||
"face": 0,
|
||||
"status": true,
|
||||
"primary_ip": 3,
|
||||
"primary_ip4": 3,
|
||||
"primary_ip6": null,
|
||||
"comments": ""
|
||||
}
|
||||
},
|
||||
@ -2052,7 +2059,8 @@
|
||||
"position": 17,
|
||||
"face": 0,
|
||||
"status": true,
|
||||
"primary_ip": 19,
|
||||
"primary_ip4": 19,
|
||||
"primary_ip6": null,
|
||||
"comments": ""
|
||||
}
|
||||
},
|
||||
@ -2071,7 +2079,8 @@
|
||||
"position": 42,
|
||||
"face": 0,
|
||||
"status": true,
|
||||
"primary_ip": null,
|
||||
"primary_ip4": null,
|
||||
"primary_ip6": null,
|
||||
"comments": ""
|
||||
}
|
||||
},
|
||||
@ -2090,7 +2099,8 @@
|
||||
"position": null,
|
||||
"face": null,
|
||||
"status": true,
|
||||
"primary_ip": null,
|
||||
"primary_ip4": null,
|
||||
"primary_ip6": null,
|
||||
"comments": ""
|
||||
}
|
||||
},
|
||||
@ -2109,7 +2119,8 @@
|
||||
"position": null,
|
||||
"face": null,
|
||||
"status": true,
|
||||
"primary_ip": null,
|
||||
"primary_ip4": null,
|
||||
"primary_ip6": null,
|
||||
"comments": ""
|
||||
}
|
||||
},
|
||||
|
@ -340,7 +340,7 @@ class DeviceForm(forms.ModelForm, BootstrapMixin):
|
||||
disabled_indicator='device'))
|
||||
manufacturer = forms.ModelChoiceField(queryset=Manufacturer.objects.all(),
|
||||
widget=forms.Select(attrs={'filter-for': 'device_type'}))
|
||||
device_type = forms.ModelChoiceField(queryset=DeviceType.objects.all(), label='Model', widget=APISelect(
|
||||
device_type = forms.ModelChoiceField(queryset=DeviceType.objects.all(), label='Device type', widget=APISelect(
|
||||
api_url='/api/dcim/device-types/?manufacturer_id={{manufacturer}}',
|
||||
display_field='model'
|
||||
))
|
||||
@ -349,7 +349,7 @@ class DeviceForm(forms.ModelForm, BootstrapMixin):
|
||||
class Meta:
|
||||
model = Device
|
||||
fields = ['name', 'device_role', 'device_type', 'serial', 'site', 'rack', 'position', 'face', 'status',
|
||||
'platform', 'primary_ip', 'comments']
|
||||
'platform', 'primary_ip4', 'primary_ip6', 'comments']
|
||||
help_texts = {
|
||||
'device_role': "The function this device serves",
|
||||
'serial': "Chassis serial number",
|
||||
@ -369,20 +369,23 @@ class DeviceForm(forms.ModelForm, BootstrapMixin):
|
||||
self.initial['site'] = self.instance.rack.site
|
||||
self.initial['manufacturer'] = self.instance.device_type.manufacturer
|
||||
|
||||
# Compile list of IPs assigned to this device
|
||||
primary_ip_choices = []
|
||||
interface_ips = IPAddress.objects.filter(interface__device=self.instance)
|
||||
primary_ip_choices += [(ip.id, '{} ({})'.format(ip.address, ip.interface)) for ip in interface_ips]
|
||||
nat_ips = IPAddress.objects.filter(nat_inside__interface__device=self.instance)\
|
||||
.select_related('nat_inside__interface')
|
||||
primary_ip_choices += [(ip.id, '{} ({} NAT)'.format(ip.address, ip.nat_inside.interface)) for ip in nat_ips]
|
||||
self.fields['primary_ip'].choices = [(None, '---------')] + primary_ip_choices
|
||||
# Compile list of choices for primary IPv4 and IPv6 addresses
|
||||
for family in [4, 6]:
|
||||
ip_choices = []
|
||||
interface_ips = IPAddress.objects.filter(family=family, interface__device=self.instance)
|
||||
ip_choices += [(ip.id, '{} ({})'.format(ip.address, ip.interface)) for ip in interface_ips]
|
||||
nat_ips = IPAddress.objects.filter(family=family, nat_inside__interface__device=self.instance)\
|
||||
.select_related('nat_inside__interface')
|
||||
ip_choices += [(ip.id, '{} ({} NAT)'.format(ip.address, ip.nat_inside.interface)) for ip in nat_ips]
|
||||
self.fields['primary_ip{}'.format(family)].choices = [(None, '---------')] + ip_choices
|
||||
|
||||
else:
|
||||
|
||||
# An object that doesn't exist yet can't have any IPs assigned to it
|
||||
self.fields['primary_ip'].choices = []
|
||||
self.fields['primary_ip'].widget.attrs['readonly'] = True
|
||||
self.fields['primary_ip4'].choices = []
|
||||
self.fields['primary_ip4'].widget.attrs['readonly'] = True
|
||||
self.fields['primary_ip6'].choices = []
|
||||
self.fields['primary_ip6'].widget.attrs['readonly'] = True
|
||||
|
||||
# Limit rack choices
|
||||
if self.is_bound:
|
||||
|
27
netbox/dcim/migrations/0006_add_device_primary_ip4_ip6.py
Normal file
27
netbox/dcim/migrations/0006_add_device_primary_ip4_ip6.py
Normal file
@ -0,0 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.7 on 2016-07-11 18:40
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('ipam', '0001_initial'),
|
||||
('dcim', '0005_auto_20160706_1722'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='device',
|
||||
name='primary_ip4',
|
||||
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='primary_ip4_for', to='ipam.IPAddress', verbose_name=b'Primary IPv4'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='device',
|
||||
name='primary_ip6',
|
||||
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='primary_ip6_for', to='ipam.IPAddress', verbose_name=b'Primary IPv6'),
|
||||
),
|
||||
]
|
41
netbox/dcim/migrations/0007_device_copy_primary_ip.py
Normal file
41
netbox/dcim/migrations/0007_device_copy_primary_ip.py
Normal file
@ -0,0 +1,41 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.7 on 2016-07-11 18:40
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def copy_primary_ip(apps, schema_editor):
|
||||
Device = apps.get_model('dcim', 'Device')
|
||||
for d in Device.objects.select_related('primary_ip'):
|
||||
if not d.primary_ip:
|
||||
continue
|
||||
if d.primary_ip.family == 4:
|
||||
d.primary_ip4 = d.primary_ip
|
||||
elif d.primary_ip.family == 6:
|
||||
d.primary_ip6 = d.primary_ip
|
||||
d.save()
|
||||
|
||||
|
||||
def restore_primary_ip(apps, schema_editor):
|
||||
Device = apps.get_model('dcim', 'Device')
|
||||
for d in Device.objects.select_related('primary_ip4', 'primary_ip6'):
|
||||
if d.primary_ip:
|
||||
continue
|
||||
# Prefer IPv6 over IPv4
|
||||
if d.primary_ip6:
|
||||
d.primary_ip = d.primary_ip6
|
||||
elif d.primary_ip4:
|
||||
d.primary_ip = d.primary_ip4
|
||||
d.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('dcim', '0006_add_device_primary_ip4_ip6'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(copy_primary_ip, restore_primary_ip),
|
||||
]
|
19
netbox/dcim/migrations/0008_device_remove_primary_ip.py
Normal file
19
netbox/dcim/migrations/0008_device_remove_primary_ip.py
Normal file
@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.7 on 2016-07-11 19:01
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('dcim', '0007_device_copy_primary_ip'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='device',
|
||||
name='primary_ip',
|
||||
),
|
||||
]
|
@ -263,7 +263,7 @@ class Rack(CreatedUpdatedModel):
|
||||
@property
|
||||
def display_name(self):
|
||||
if self.facility_id:
|
||||
return "{} ({})".format(self.name, self.facility_id)
|
||||
return u"{} ({})".format(self.name, self.facility_id)
|
||||
return self.name
|
||||
|
||||
def get_rack_units(self, face=RACK_FACE_FRONT, exclude=None, remove_redundant=False):
|
||||
@ -605,8 +605,10 @@ class Device(CreatedUpdatedModel):
|
||||
help_text='Number of the lowest U position occupied by the device')
|
||||
face = models.PositiveSmallIntegerField(blank=True, null=True, choices=RACK_FACE_CHOICES, verbose_name='Rack face')
|
||||
status = models.BooleanField(choices=STATUS_CHOICES, default=STATUS_ACTIVE, verbose_name='Status')
|
||||
primary_ip = models.OneToOneField('ipam.IPAddress', related_name='primary_for', on_delete=models.SET_NULL,
|
||||
blank=True, null=True, verbose_name='Primary IP')
|
||||
primary_ip4 = models.OneToOneField('ipam.IPAddress', related_name='primary_ip4_for', on_delete=models.SET_NULL,
|
||||
blank=True, null=True, verbose_name='Primary IPv4')
|
||||
primary_ip6 = models.OneToOneField('ipam.IPAddress', related_name='primary_ip6_for', on_delete=models.SET_NULL,
|
||||
blank=True, null=True, verbose_name='Primary IPv6')
|
||||
comments = models.TextField(blank=True)
|
||||
|
||||
class Meta:
|
||||
@ -696,9 +698,9 @@ class Device(CreatedUpdatedModel):
|
||||
if self.name:
|
||||
return self.name
|
||||
elif self.position:
|
||||
return "{} ({} U{})".format(self.device_type, self.rack.name, self.position)
|
||||
return u"{} ({} U{})".format(self.device_type, self.rack.name, self.position)
|
||||
else:
|
||||
return "{} ({})".format(self.device_type, self.rack.name)
|
||||
return u"{} ({})".format(self.device_type, self.rack.name)
|
||||
|
||||
@property
|
||||
def identifier(self):
|
||||
@ -709,6 +711,15 @@ class Device(CreatedUpdatedModel):
|
||||
return self.name
|
||||
return '{{{}}}'.format(self.pk)
|
||||
|
||||
@property
|
||||
def primary_ip(self):
|
||||
if self.primary_ip6:
|
||||
return self.primary_ip6
|
||||
elif self.primary_ip4:
|
||||
return self.primary_ip4
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_children(self):
|
||||
"""
|
||||
Return the set of child Devices installed in DeviceBays within this Device.
|
||||
|
@ -318,6 +318,8 @@ class DeviceTest(APITestCase):
|
||||
'parent_device',
|
||||
'status',
|
||||
'primary_ip',
|
||||
'primary_ip4',
|
||||
'primary_ip6',
|
||||
'comments',
|
||||
]
|
||||
|
||||
@ -375,6 +377,10 @@ class DeviceTest(APITestCase):
|
||||
'primary_ip_address',
|
||||
'primary_ip_family',
|
||||
'primary_ip_id',
|
||||
'primary_ip4_address',
|
||||
'primary_ip4_family',
|
||||
'primary_ip4_id',
|
||||
'primary_ip6',
|
||||
'rack_display_name',
|
||||
'rack_facility_id',
|
||||
'rack_id',
|
||||
|
@ -501,7 +501,8 @@ class PlatformBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
#
|
||||
|
||||
class DeviceListView(ObjectListView):
|
||||
queryset = Device.objects.select_related('device_type__manufacturer', 'device_role', 'rack__site', 'primary_ip')
|
||||
queryset = Device.objects.select_related('device_type__manufacturer', 'device_role', 'rack__site', 'primary_ip4',
|
||||
'primary_ip6')
|
||||
filter = filters.DeviceFilter
|
||||
filter_form = forms.DeviceFilterForm
|
||||
table = tables.DeviceTable
|
||||
@ -1634,7 +1635,10 @@ def ipaddress_assign(request, pk):
|
||||
ipaddress.interface))
|
||||
|
||||
if form.cleaned_data['set_as_primary']:
|
||||
device.primary_ip = ipaddress
|
||||
if ipaddress.family == 4:
|
||||
device.primary_ip4 = ipaddress
|
||||
elif ipaddress.family == 6:
|
||||
device.primary_ip6 = ipaddress
|
||||
device.save()
|
||||
|
||||
if '_addanother' in request.POST:
|
||||
|
@ -329,7 +329,7 @@ class IPAddressForm(forms.ModelForm, BootstrapMixin):
|
||||
|
||||
class IPAddressFromCSVForm(forms.ModelForm):
|
||||
vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, to_field_name='rd',
|
||||
error_messages={'invalid_choice': 'Site not found.'})
|
||||
error_messages={'invalid_choice': 'VRF not found.'})
|
||||
device = forms.ModelChoiceField(queryset=Device.objects.all(), required=False, to_field_name='name',
|
||||
error_messages={'invalid_choice': 'Device not found.'})
|
||||
interface_name = forms.CharField(required=False)
|
||||
@ -368,7 +368,10 @@ class IPAddressFromCSVForm(forms.ModelForm):
|
||||
name=self.cleaned_data['interface_name'])
|
||||
# Set as primary for device
|
||||
if self.cleaned_data['is_primary']:
|
||||
self.instance.primary_for = self.cleaned_data['device']
|
||||
if self.instance.family == 4:
|
||||
self.instance.primary_ip4_for = self.cleaned_data['device']
|
||||
elif self.instance.family == 6:
|
||||
self.instance.primary_ip6_for = self.cleaned_data['device']
|
||||
|
||||
return super(IPAddressFromCSVForm, self).save(commit=commit)
|
||||
|
||||
|
@ -314,12 +314,20 @@ class IPAddress(CreatedUpdatedModel):
|
||||
super(IPAddress, self).save(*args, **kwargs)
|
||||
|
||||
def to_csv(self):
|
||||
|
||||
# Determine if this IP is primary for a Device
|
||||
is_primary = False
|
||||
if self.family == 4 and getattr(self, 'primary_ip4_for', False):
|
||||
is_primary = True
|
||||
elif self.family == 6 and getattr(self, 'primary_ip6_for', False):
|
||||
is_primary = True
|
||||
|
||||
return ','.join([
|
||||
str(self.address),
|
||||
self.vrf.rd if self.vrf else '',
|
||||
self.device.identifier if self.device else '',
|
||||
self.interface.name if self.interface else '',
|
||||
'True' if getattr(self, 'primary_for', False) else '',
|
||||
'True' if is_primary else '',
|
||||
self.description,
|
||||
])
|
||||
|
||||
@ -367,7 +375,7 @@ class VLAN(CreatedUpdatedModel):
|
||||
|
||||
@property
|
||||
def display_name(self):
|
||||
return "{} ({})".format(self.vid, self.name)
|
||||
return u"{} ({})".format(self.vid, self.name)
|
||||
|
||||
def get_status_class(self):
|
||||
return STATUS_CHOICE_CLASSES[self.status]
|
||||
|
@ -364,7 +364,7 @@ def prefix_ipaddresses(request, pk):
|
||||
|
||||
# Find all IPAddresses belonging to this Prefix
|
||||
ipaddresses = IPAddress.objects.filter(address__net_contained_or_equal=str(prefix.prefix))\
|
||||
.select_related('vrf', 'interface__device', 'primary_for')
|
||||
.select_related('vrf', 'interface__device', 'primary_ip4_for', 'primary_ip6_for')
|
||||
|
||||
ip_table = tables.IPAddressTable(ipaddresses)
|
||||
ip_table.model = IPAddress
|
||||
@ -383,7 +383,7 @@ def prefix_ipaddresses(request, pk):
|
||||
#
|
||||
|
||||
class IPAddressListView(ObjectListView):
|
||||
queryset = IPAddress.objects.select_related('vrf', 'interface__device', 'primary_for')
|
||||
queryset = IPAddress.objects.select_related('vrf', 'interface__device', 'primary_ip4_for', 'primary_ip6_for')
|
||||
filter = filters.IPAddressFilter
|
||||
filter_form = forms.IPAddressFilterForm
|
||||
table = tables.IPAddressTable
|
||||
@ -443,9 +443,14 @@ class IPAddressBulkImportView(PermissionRequiredMixin, BulkImportView):
|
||||
obj.save()
|
||||
# Update primary IP for device if needed
|
||||
try:
|
||||
device = obj.primary_for
|
||||
device.primary_ip = obj
|
||||
device.save()
|
||||
if obj.family == 4 and obj.primary_ip4_for:
|
||||
device = obj.primary_ip4_for
|
||||
device.primary_ip4 = obj
|
||||
device.save()
|
||||
elif obj.family == 6 and obj.primary_ip6_for:
|
||||
device = obj.primary_ip6_for
|
||||
device.primary_ip6 = obj
|
||||
device.save()
|
||||
except Device.DoesNotExist:
|
||||
pass
|
||||
|
||||
|
@ -73,3 +73,8 @@ TIME_FORMAT = 'g:i a'
|
||||
SHORT_TIME_FORMAT = 'H:i:s'
|
||||
DATETIME_FORMAT = 'N j, Y g:i a'
|
||||
SHORT_DATETIME_FORMAT = 'Y-m-d H:i'
|
||||
|
||||
# Optionally display a persistent banner at the top and/or bottom of every page. To display the same content in both
|
||||
# banners, define BANNER_TOP and set BANNER_BOTTOM = BANNER_TOP.
|
||||
BANNER_TOP = ''
|
||||
BANNER_BOTTOM = ''
|
||||
|
@ -1,3 +1,4 @@
|
||||
import logging
|
||||
import os
|
||||
import socket
|
||||
|
||||
@ -11,7 +12,7 @@ except ImportError:
|
||||
"the documentation.")
|
||||
|
||||
|
||||
VERSION = '1.1.0'
|
||||
VERSION = '1.2.0'
|
||||
|
||||
# Import local configuration
|
||||
for setting in ['ALLOWED_HOSTS', 'DATABASE', 'SECRET_KEY']:
|
||||
@ -37,8 +38,39 @@ TIME_FORMAT = getattr(configuration, 'TIME_FORMAT', 'g:i a')
|
||||
SHORT_TIME_FORMAT = getattr(configuration, 'SHORT_TIME_FORMAT', 'H:i:s')
|
||||
DATETIME_FORMAT = getattr(configuration, 'DATETIME_FORMAT', 'N j, Y g:i a')
|
||||
SHORT_DATETIME_FORMAT = getattr(configuration, 'SHORT_DATETIME_FORMAT', 'Y-m-d H:i')
|
||||
BANNER_TOP = getattr(configuration, 'BANNER_TOP', False)
|
||||
BANNER_BOTTOM = getattr(configuration, 'BANNER_BOTTOM', False)
|
||||
CSRF_TRUSTED_ORIGINS = ALLOWED_HOSTS
|
||||
|
||||
# Attempt to import LDAP configuration if it has been defined
|
||||
LDAP_IGNORE_CERT_ERRORS = False
|
||||
try:
|
||||
from ldap_config import *
|
||||
LDAP_CONFIGURED = True
|
||||
except ImportError:
|
||||
LDAP_CONFIGURED = False
|
||||
|
||||
# LDAP configuration (optional)
|
||||
if LDAP_CONFIGURED:
|
||||
try:
|
||||
import ldap
|
||||
import django_auth_ldap
|
||||
# Prepend LDAPBackend to the default ModelBackend
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
'django_auth_ldap.backend.LDAPBackend',
|
||||
'django.contrib.auth.backends.ModelBackend',
|
||||
]
|
||||
# Optionally disable strict certificate checking
|
||||
if LDAP_IGNORE_CERT_ERRORS:
|
||||
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
|
||||
# Enable logging for django_auth_ldap
|
||||
logger = logging.getLogger('django_auth_ldap')
|
||||
logger.addHandler(logging.StreamHandler())
|
||||
logger.setLevel(logging.DEBUG)
|
||||
except ImportError:
|
||||
raise ImproperlyConfigured("LDAP authentication has been configured, but django-auth-ldap is not installed. "
|
||||
"You can remove netbox/ldap.py to disable LDAP.")
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
# Database
|
||||
|
@ -2,7 +2,7 @@ from django.conf.urls import include, url
|
||||
from django.contrib import admin
|
||||
from django.views.defaults import page_not_found
|
||||
|
||||
from views import home, docs, trigger_500
|
||||
from views import home, trigger_500
|
||||
from users.views import login, logout
|
||||
|
||||
|
||||
@ -30,10 +30,6 @@ urlpatterns = [
|
||||
url(r'^api/docs/', include('rest_framework_swagger.urls')),
|
||||
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
|
||||
|
||||
# Dcoumentation
|
||||
url(r'^docs/$', docs, kwargs={'path': 'index'}, name='docs_root'),
|
||||
url(r'^docs/(?P<path>[\w-]+)/$', docs, name='docs'),
|
||||
|
||||
# Error testing
|
||||
url(r'^404/$', page_not_found),
|
||||
url(r'^500/$', trigger_500),
|
||||
|
@ -45,25 +45,6 @@ def home(request):
|
||||
})
|
||||
|
||||
|
||||
def docs(request, path):
|
||||
"""
|
||||
Display a page of Markdown-formatted documentation.
|
||||
"""
|
||||
filename = '{}/docs/{}.md'.format(settings.BASE_DIR.rsplit('/', 1)[0], path)
|
||||
try:
|
||||
with open(filename, 'r') as docfile:
|
||||
markup = docfile.read()
|
||||
except:
|
||||
raise Http404
|
||||
|
||||
content = mark_safe(markdown(markup, extensions=['mdx_gfm', 'toc']))
|
||||
|
||||
return render(request, 'docs.html', {
|
||||
'content': content,
|
||||
'path': path,
|
||||
})
|
||||
|
||||
|
||||
def trigger_500(request):
|
||||
"""Hot-wired method of triggering a server error to test reporting."""
|
||||
|
||||
|
@ -28,6 +28,42 @@ body {
|
||||
footer p {
|
||||
margin: 20px 0;
|
||||
}
|
||||
@media (max-width: 1120px) {
|
||||
.navbar-header {
|
||||
float: none;
|
||||
}
|
||||
.navbar-left,.navbar-right {
|
||||
float: none !important;
|
||||
}
|
||||
.navbar-toggle {
|
||||
display: block;
|
||||
}
|
||||
.navbar-collapse {
|
||||
border-top: 1px solid transparent;
|
||||
box-shadow: inset 0 1px 0 rgba(255,255,255,0.1);
|
||||
}
|
||||
.navbar-fixed-top {
|
||||
top: 0;
|
||||
border-width: 0 0 1px;
|
||||
}
|
||||
.navbar-collapse.collapse {
|
||||
display: none!important;
|
||||
}
|
||||
.navbar-nav {
|
||||
float: none!important;
|
||||
margin-top: 7.5px;
|
||||
}
|
||||
.navbar-nav>li {
|
||||
float: none;
|
||||
}
|
||||
.navbar-nav>li>a {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.collapse.in {
|
||||
display:block !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Forms */
|
||||
label {
|
||||
@ -259,6 +295,9 @@ ul.rack_near_face li.empty:hover a {
|
||||
.dark_gray:hover { background-color: #2c3e50; }
|
||||
|
||||
/* Misc */
|
||||
.banner-bottom {
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
.panel table {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ class SecretListView(generics.GenericAPIView):
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
|
||||
# Attempt to decrypt each Secret if a private key was provided.
|
||||
if private_key is not None:
|
||||
if private_key:
|
||||
try:
|
||||
uk = UserKey.objects.get(user=request.user)
|
||||
except UserKey.DoesNotExist:
|
||||
@ -96,7 +96,7 @@ class SecretDetailView(generics.GenericAPIView):
|
||||
secret = get_object_or_404(Secret, pk=pk)
|
||||
|
||||
# Attempt to decrypt the Secret if a private key was provided.
|
||||
if private_key is not None:
|
||||
if private_key:
|
||||
try:
|
||||
uk = UserKey.objects.get(user=request.user)
|
||||
except UserKey.DoesNotExist:
|
||||
|
@ -224,6 +224,11 @@
|
||||
</div>
|
||||
</nav>
|
||||
<div class="container wrapper">
|
||||
{% if settings.BANNER_TOP %}
|
||||
<div class="alert alert-info text-center" role="alert">
|
||||
{{ settings.BANNER_TOP|safe }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if settings.MAINTENANCE_MODE %}
|
||||
<div class="alert alert-warning text-center" role="alert">
|
||||
<h4><i class="fa fa-exclamation-triangle"></i> Maintenance Mode</h4>
|
||||
@ -239,20 +244,25 @@
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% block content %}{% endblock %}
|
||||
<div class="push"></div>
|
||||
<div class="push"></div>
|
||||
{% if settings.BANNER_BOTTOM %}
|
||||
<div class="alert alert-info text-center banner-bottom" role="alert">
|
||||
{{ settings.BANNER_BOTTOM|safe }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="col-xs-4">
|
||||
<p class="text-muted">{{ settings.HOSTNAME }} (v{{ settings.VERSION }})</p>
|
||||
</div>
|
||||
<div class="col-md-4 text-center">
|
||||
<div class="col-xs-4 text-center">
|
||||
<p class="text-muted">{% now 'Y-m-d H:i:s T' %}</p>
|
||||
</div>
|
||||
<div class="col-md-4 text-right">
|
||||
<div class="col-xs-4 text-right">
|
||||
<p class="text-muted">
|
||||
<i class="fa fa-fw fa-book text-primary"></i> <a href="{% url 'docs_root' %}">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-code text-primary"></i> <a href="https://github.com/digitalocean/netbox">Code</a>
|
||||
</p>
|
||||
|
@ -101,14 +101,29 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Primary IP</td>
|
||||
<td>Primary IPv4</td>
|
||||
<td>
|
||||
{% if device.primary_ip %}
|
||||
<a href="{% url 'ipam:ipaddress' pk=device.primary_ip.pk %}">{{ device.primary_ip.address.ip }}</a>
|
||||
{% if device.primary_ip.nat_inside %}
|
||||
<span>(NAT for {{ device.primary_ip.nat_inside.address.ip }})</span>
|
||||
{% elif device.primary_ip.nat_outside %}
|
||||
<span>(NAT: {{ device.primary_ip.nat_outside.address.ip }})</span>
|
||||
{% if device.primary_ip4 %}
|
||||
<a href="{% url 'ipam:ipaddress' pk=device.primary_ip4.pk %}">{{ device.primary_ip4.address.ip }}</a>
|
||||
{% if device.primary_ip4.nat_inside %}
|
||||
<span>(NAT for {{ device.primary_ip4.nat_inside.address.ip }})</span>
|
||||
{% elif device.primary_ip4.nat_outside %}
|
||||
<span>(NAT: {{ device.primary_ip4.nat_outside.address.ip }})</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="text-muted">Not defined</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Primary IPv6</td>
|
||||
<td>
|
||||
{% if device.primary_ip6 %}
|
||||
<a href="{% url 'ipam:ipaddress' pk=device.primary_ip6.pk %}">{{ device.primary_ip6.address.ip }}</a>
|
||||
{% if device.primary_ip6.nat_inside %}
|
||||
<span>(NAT for {{ device.primary_ip6.nat_inside.address.ip }})</span>
|
||||
{% elif device.primary_ip6.nat_outside %}
|
||||
<span>(NAT: {{ device.primary_ip6.nat_outside.address.ip }})</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="text-muted">Not defined</span>
|
||||
|
@ -31,7 +31,10 @@
|
||||
<div class="panel-body">
|
||||
{% render_field form.platform %}
|
||||
{% render_field form.status %}
|
||||
{% if obj %}{% render_field form.primary_ip %}{% endif %}
|
||||
{% if obj %}
|
||||
{% render_field form.primary_ip4 %}
|
||||
{% render_field form.primary_ip6 %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
|
@ -81,7 +81,7 @@
|
||||
{% include 'dcim/inc/devicetype_component_table.html' with table=powerport_table title='Power Ports' add_url='dcim:devicetype_add_powerport' delete_url='dcim:devicetype_delete_powerport' %}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{% if devicetype.is_network_device %}
|
||||
{% if devicetype.is_parent_device %}
|
||||
{% include 'dcim/inc/devicetype_component_table.html' with table=devicebay_table title='Device Bays' add_url='dcim:devicetype_add_devicebay' delete_url='dcim:devicetype_delete_devicebay' %}
|
||||
{% endif %}
|
||||
{% if devicetype.is_network_device %}
|
||||
|
@ -4,7 +4,7 @@
|
||||
</td>
|
||||
<td>{{ ip.interface }}</td>
|
||||
<td>
|
||||
{% if device.primary_ip == ip %}
|
||||
{% if device.primary_ip4 == ip or device.primary_ip6 == ip %}
|
||||
<span class="label label-success">Primary</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
|
@ -154,17 +154,19 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="rack_header">
|
||||
<div class="row col-md-6">
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<div class="rack_header">
|
||||
<h4>Front</h4>
|
||||
</div>
|
||||
{% include 'dcim/_rack_elevation.html' with primary_face=front_elevation secondary_face=rear_elevation face_id=0 %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
</div>
|
||||
{% include 'dcim/_rack_elevation.html' with primary_face=front_elevation secondary_face=rear_elevation face_id=0 %}
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<div class="rack_header">
|
||||
<h4>Rear</h4>
|
||||
</div>
|
||||
{% include 'dcim/_rack_elevation.html' with primary_face=rear_elevation secondary_face=front_elevation face_id=1 %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -1,29 +0,0 @@
|
||||
{% extends '_base.html' %}
|
||||
{% load render_table from django_tables2 %}
|
||||
|
||||
{% block title %}Documentation{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong>Documentation</strong>
|
||||
</div>
|
||||
<div class="list-group">
|
||||
<a href="{% url 'docs_root' %}" class="list-group-item{% if path == 'index' %} active{% endif %}">Home</a>
|
||||
<a href="{% url 'docs' path='getting-started' %}" class="list-group-item{% if path == 'getting-started' %} active{% endif %}">Getting Started</a>
|
||||
<a href="{% url 'docs' path='configuration' %}" class="list-group-item{% if path == 'configuration' %} active{% endif %}">Configuration</a>
|
||||
<a href="{% url 'docs' path='dcim' %}" class="list-group-item{% if path == 'dcim' %} active{% endif %}">DCIM</a>
|
||||
<a href="{% url 'docs' path='ipam' %}" class="list-group-item{% if path == 'ipam' %} active{% endif %}">IPAM</a>
|
||||
<a href="{% url 'docs' path='circuits' %}" class="list-group-item{% if path == 'circuits' %} active{% endif %}">Circuits</a>
|
||||
<a href="{% url 'docs' path='secrets' %}" class="list-group-item{% if path == 'secrets' %} active{% endif %}">Secrets</a>
|
||||
<a href="{% url 'docs' path='extras' %}" class="list-group-item{% if path == 'extras' %} active{% endif %}">Extras</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
{{ content }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -3,7 +3,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row" style="margin-top: 150px;">
|
||||
<div class="col-md-4 col-md-offset-4">
|
||||
<div class="col-sm-4 col-sm-offset-4">
|
||||
{% if form.non_field_errors %}
|
||||
<div class="panel panel-danger">
|
||||
<div class="panel-heading"><strong>Errors</strong></div>
|
||||
|
Loading…
Reference in New Issue
Block a user