mirror of
https://github.com/netbox-community/netbox.git
synced 2026-04-15 13:39:54 +02:00
Compare commits
63 Commits
v2.10-beta
...
v2.10-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1159976d99 | ||
|
|
407b16c98c | ||
|
|
96ce9122f4 | ||
|
|
a80f13243e | ||
|
|
c166852054 | ||
|
|
ba00d57d37 | ||
|
|
f5133c6737 | ||
|
|
5a4234fcb9 | ||
|
|
3cbe8ddf02 | ||
|
|
8461832a7b | ||
|
|
f504d1aa99 | ||
|
|
2b0ddd2a98 | ||
|
|
cc271aefe1 | ||
|
|
0b57389af6 | ||
|
|
ef77961cf4 | ||
|
|
915cf3e715 | ||
|
|
cadba74b1f | ||
|
|
23b305847e | ||
|
|
f55e966c8f | ||
|
|
77bbe5730b | ||
|
|
f8b523b97b | ||
|
|
d9c503a9ec | ||
|
|
52aa123a9f | ||
|
|
4e25c70f96 | ||
|
|
d250fd91a9 | ||
|
|
b4c7727a82 | ||
|
|
9ac64d57d0 | ||
|
|
6f68be5aa7 | ||
|
|
b68c79be6b | ||
|
|
b3d674e170 | ||
|
|
bff9bd5dbd | ||
|
|
e7e63796ac | ||
|
|
e1911a624b | ||
|
|
825cc9a86f | ||
|
|
869c87e396 | ||
|
|
e9fb91b197 | ||
|
|
a081d0d93a | ||
|
|
54af29eb40 | ||
|
|
6cae4d7ee4 | ||
|
|
65d2994823 | ||
|
|
8e0a6479ca | ||
|
|
98f2d2501b | ||
|
|
a05fe69043 | ||
|
|
6fb080ff4c | ||
|
|
9923548628 | ||
|
|
181f5c991b | ||
|
|
3dd2e4c84c | ||
|
|
83f7f9f0e5 | ||
|
|
507fb5387b | ||
|
|
1d7bee5eee | ||
|
|
1d922ee813 | ||
|
|
d237b97776 | ||
|
|
790e1ee2e4 | ||
|
|
e5caea04c1 | ||
|
|
2f1ca902f7 | ||
|
|
d5d87e0fdd | ||
|
|
3e7cf416f1 | ||
|
|
e2d2ff8586 | ||
|
|
11fa348575 | ||
|
|
58668a6807 | ||
|
|
6d379ad029 | ||
|
|
a05fc091c8 | ||
|
|
f2bc6f0f96 |
@@ -93,6 +93,10 @@ appropriate labels will be applied for categorization.
|
|||||||
|
|
||||||
## Submitting Pull Requests
|
## Submitting Pull Requests
|
||||||
|
|
||||||
|
* If you're interested in contributing to NetBox, be sure to check out our
|
||||||
|
[getting started](https://netbox.readthedocs.io/en/stable/development/getting-started/)
|
||||||
|
documentation for tips on setting up your development environment.
|
||||||
|
|
||||||
* Be sure to open an issue **before** starting work on a pull request, and
|
* Be sure to open an issue **before** starting work on a pull request, and
|
||||||
discuss your idea with the NetBox maintainers before beginning work. This will
|
discuss your idea with the NetBox maintainers before beginning work. This will
|
||||||
help prevent wasting time on something that might we might not be able to
|
help prevent wasting time on something that might we might not be able to
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ The lifetime (in seconds) of the authentication cookie issued to a NetBox user u
|
|||||||
|
|
||||||
Default: False
|
Default: False
|
||||||
|
|
||||||
Setting this to True will display a "maintenance mode" banner at the top of every page.
|
Setting this to True will display a "maintenance mode" banner at the top of every page. Additionally, NetBox will no longer update a user's "last active" time upon login. This is to allow new logins when the database is in a read-only state. Recording of login times will resume when maintenance mode is disabled.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
# Application Registry
|
# Application Registry
|
||||||
|
|
||||||
The registry is an in-memory data structure which houses various miscellaneous application-wide parameters, such as installed plugins. It is not exposed to the user and is not intended to be modified by any code outside of NetBox core.
|
The registry is an in-memory data structure which houses various application-wide parameters, such as the list of enabled plugins. It is not exposed to the user and is not intended to be modified by any code outside of NetBox core.
|
||||||
|
|
||||||
The registry behaves essentially like a Python dictionary, with the notable exception that once a store (key) has been declared, it cannot be deleted or overwritten. The value of a store can, however, me modified; e.g. by appending a value to a list. Store values generally do not change once the application has been initialized.
|
The registry behaves essentially like a Python dictionary, with the notable exception that once a store (key) has been declared, it cannot be deleted or overwritten. The value of a store can, however, be modified; e.g. by appending a value to a list. Store values generally do not change once the application has been initialized.
|
||||||
|
|
||||||
|
The registry can be inspected by importing `registry` from `extras.registry`.
|
||||||
|
|
||||||
## Stores
|
## Stores
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# Extending Models
|
# Extending Models
|
||||||
|
|
||||||
Below is a list of items to consider when adding a new field to a model:
|
Below is a list of tasks to consider when adding a new field to a core model.
|
||||||
|
|
||||||
## 1. Generate and run database migration
|
## 1. Generate and run database migrations
|
||||||
|
|
||||||
Django migrations are used to express changes to the database schema. In most cases, Django can generate these automatically, however very complex changes may require manual intervention. Always remember to specify a short but descriptive name when generating a new migration.
|
Django migrations are used to express changes to the database schema. In most cases, Django can generate these automatically, however very complex changes may require manual intervention. Always remember to specify a short but descriptive name when generating a new migration.
|
||||||
|
|
||||||
@@ -14,18 +14,18 @@ Django migrations are used to express changes to the database schema. In most ca
|
|||||||
Where possible, try to merge related changes into a single migration. For example, if three new fields are being added to different models within an app, these can be expressed in the same migration. You can merge a new migration with an existing one by combining their `operations` lists.
|
Where possible, try to merge related changes into a single migration. For example, if three new fields are being added to different models within an app, these can be expressed in the same migration. You can merge a new migration with an existing one by combining their `operations` lists.
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
Migrations can only be merged within a release. Once a new release has been published, its migrations cannot be altered.
|
Migrations can only be merged within a release. Once a new release has been published, its migrations cannot be altered (other than for the purpose of correcting a bug).
|
||||||
|
|
||||||
## 2. Add validation logic to `clean()`
|
## 2. Add validation logic to `clean()`
|
||||||
|
|
||||||
If the new field introduces additional validation requirements (beyond what's included with the field itself), implement them in the model's `clean()` method. Remember to call the model's original method using `super()` before or agter your custom validation as appropriate:
|
If the new field introduces additional validation requirements (beyond what's included with the field itself), implement them in the model's `clean()` method. Remember to call the model's original method using `super()` before or after your custom validation as appropriate:
|
||||||
|
|
||||||
```
|
```
|
||||||
class Foo(models.Model):
|
class Foo(models.Model):
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
|
||||||
super(DeviceCSVForm, self).clean()
|
super().clean()
|
||||||
|
|
||||||
# Custom validation goes here
|
# Custom validation goes here
|
||||||
if self.bar is None:
|
if self.bar is None:
|
||||||
@@ -38,7 +38,7 @@ Add the name of the new field to `csv_headers` and included a CSV-friendly repre
|
|||||||
|
|
||||||
## 4. Update relevant querysets
|
## 4. Update relevant querysets
|
||||||
|
|
||||||
If you're adding a relational field (e.g. `ForeignKey`) and intend to include the data when retreiving a list of objects, be sure to include the field using `prefetch_related()` as appropriate. This will optimize the view and avoid excessive database lookups.
|
If you're adding a relational field (e.g. `ForeignKey`) and intend to include the data when retreiving a list of objects, be sure to include the field using `prefetch_related()` as appropriate. This will optimize the view and avoid extraneous database queries.
|
||||||
|
|
||||||
## 5. Update API serializer
|
## 5. Update API serializer
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ Extend the model's API serializer in `<app>.api.serializers` to include the new
|
|||||||
Extend any forms to include the new field as appropriate. Common forms include:
|
Extend any forms to include the new field as appropriate. Common forms include:
|
||||||
|
|
||||||
* **Credit/edit** - Manipulating a single object
|
* **Credit/edit** - Manipulating a single object
|
||||||
* **Bulk edit** - Performing a change on mnay objects at once
|
* **Bulk edit** - Performing a change on many objects at once
|
||||||
* **CSV import** - The form used when bulk importing objects in CSV format
|
* **CSV import** - The form used when bulk importing objects in CSV format
|
||||||
* **Filter** - Displays the options available for filtering a list of objects (both UI and API)
|
* **Filter** - Displays the options available for filtering a list of objects (both UI and API)
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ If the new field should be filterable, add it to the `FilterSet` for the model.
|
|||||||
|
|
||||||
## 8. Add column to object table
|
## 8. Add column to object table
|
||||||
|
|
||||||
If the new field will be included in the object list view, add a column to the model's table. For simple fields, adding the field name to `Meta.fields` will be sufficient. More complex fields may require explicitly declaring a new column.
|
If the new field will be included in the object list view, add a column to the model's table. For simple fields, adding the field name to `Meta.fields` will be sufficient. More complex fields may require declaring a custom column.
|
||||||
|
|
||||||
## 9. Update the UI templates
|
## 9. Update the UI templates
|
||||||
|
|
||||||
@@ -76,3 +76,7 @@ Create or extend the relevant test cases to verify that the new field and any ac
|
|||||||
* View tests
|
* View tests
|
||||||
|
|
||||||
Be diligent to ensure all of the relevant test suites are adapted or extended as necessary to test any new functionality.
|
Be diligent to ensure all of the relevant test suites are adapted or extended as necessary to test any new functionality.
|
||||||
|
|
||||||
|
## 11. Update the model's documentation
|
||||||
|
|
||||||
|
Each model has a dedicated page in the documentation, at `models/<app>/<model>.md`. Update this file to include any relevant information about the new field.
|
||||||
|
|||||||
140
docs/development/getting-started.md
Normal file
140
docs/development/getting-started.md
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
# Getting Started
|
||||||
|
|
||||||
|
## Setting up a Development Environment
|
||||||
|
|
||||||
|
Getting started with NetBox development is pretty straightforward, and should feel very familiar to anyone with Django development experience. There are a few things you'll need:
|
||||||
|
|
||||||
|
* A Linux system or environment
|
||||||
|
* A PostgreSQL server, which can be installed locally [per the documentation](/installation/1-postgresql/)
|
||||||
|
* A Redis server, which can also be [installed locally](/installation/2-redis/)
|
||||||
|
* A supported version of Python
|
||||||
|
|
||||||
|
### Fork the Repo
|
||||||
|
|
||||||
|
Assuming you'll be working on your own fork, your first step will be to fork the [official git repository](https://github.com/netbox-community/netbox). (If you're a maintainer who's going to be working directly with the official repo, skip this step.) You can then clone your GitHub fork locally for development:
|
||||||
|
|
||||||
|
```no-highlight
|
||||||
|
$ git clone https://github.com/youruseraccount/netbox.git
|
||||||
|
Cloning into 'netbox'...
|
||||||
|
remote: Enumerating objects: 231, done.
|
||||||
|
remote: Counting objects: 100% (231/231), done.
|
||||||
|
remote: Compressing objects: 100% (147/147), done.
|
||||||
|
remote: Total 56705 (delta 134), reused 145 (delta 84), pack-reused 56474
|
||||||
|
Receiving objects: 100% (56705/56705), 27.96 MiB | 34.92 MiB/s, done.
|
||||||
|
Resolving deltas: 100% (44177/44177), done.
|
||||||
|
$ ls netbox/
|
||||||
|
base_requirements.txt contrib docs mkdocs.yml NOTICE requirements.txt upgrade.sh
|
||||||
|
CHANGELOG.md CONTRIBUTING.md LICENSE.txt netbox README.md scripts
|
||||||
|
```
|
||||||
|
|
||||||
|
The NetBox project utilizes three long-term branches:
|
||||||
|
|
||||||
|
* `master` - Serves as a snapshot of the current stable release
|
||||||
|
* `develop` - All development on the upcoming stable release occurs here
|
||||||
|
* `develop-x.y` - Tracks work on an upcoming major release
|
||||||
|
|
||||||
|
Typically, you'll base pull requests off of the `develop` branch, or off of `develop-x.y` if you're working on a new major release. **Never** base pull requests off of the master branch, which receives merged only from the `develop` branch.
|
||||||
|
|
||||||
|
### Enable Pre-Commit Hooks
|
||||||
|
|
||||||
|
NetBox ships with a [git pre-commit hook](https://githooks.com/) script that automatically checks for style compliance and missing database migrations prior to committing changes. This helps avoid erroneous commits that result in CI test failures. You are encouraged to enable it by creating a link to `scripts/git-hooks/pre-commit`:
|
||||||
|
|
||||||
|
```no-highlight
|
||||||
|
$ cd .git/hooks/
|
||||||
|
$ ln -s ../../scripts/git-hooks/pre-commit
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create a Python Virtual Environment
|
||||||
|
|
||||||
|
A [virtual environment](https://docs.python.org/3/tutorial/venv.html) is like a container for a set of Python packages. They allow you to build environments suited to specific projects without interfering with system packages or other projects. When installed per the documentation, NetBox uses a virtual environment in production.
|
||||||
|
|
||||||
|
Create a virtual environment using the `venv` Python module:
|
||||||
|
|
||||||
|
```no-highlight
|
||||||
|
$ mkdir ~/.venv
|
||||||
|
$ python3 -m venv ~/.venv/netbox
|
||||||
|
```
|
||||||
|
|
||||||
|
This will create a directory named `.venv/netbox/` in your home directory, which houses a virtual copy of the Python executable and its related libraries and tooling. When running NetBox for development, it will be run using the Python binary at `~/.venv/netbox/bin/python`.
|
||||||
|
|
||||||
|
!!! info
|
||||||
|
Keeping virtual environments in `~/.venv/` is a common convention but entirely optional: Virtual environments can be created wherever you please.
|
||||||
|
|
||||||
|
Once created, activate the virtual environment:
|
||||||
|
|
||||||
|
```no-highlight
|
||||||
|
$ source ~/.venv/netbox/bin/activate
|
||||||
|
(netbox) $
|
||||||
|
```
|
||||||
|
|
||||||
|
Notice that the console prompt changes to indicate the active environment. This updates the necessary system environment variables to ensure that any Python scripts are run within the virtual environment.
|
||||||
|
|
||||||
|
### Install Dependencies
|
||||||
|
|
||||||
|
With the virtual environment activated, install the project's required Python packages using the `pip` module:
|
||||||
|
|
||||||
|
```no-highlight
|
||||||
|
(netbox) $ python -m pip install -r requirements.txt
|
||||||
|
Collecting Django==3.1 (from -r requirements.txt (line 1))
|
||||||
|
Cache entry deserialization failed, entry ignored
|
||||||
|
Using cached https://files.pythonhosted.org/packages/2b/5a/4bd5624546912082a1bd2709d0edc0685f5c7827a278d806a20cf6adea28/Django-3.1-py3-none-any.whl
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configure NetBox
|
||||||
|
|
||||||
|
Within the `netbox/netbox/` directory, copy `configuration.example.py` to `configuration.py` and update the following parameters:
|
||||||
|
|
||||||
|
* `ALLOWED_HOSTS`: This can be set to `['*']` for development purposes
|
||||||
|
* `DATABASE`: PostgreSQL database connection parameters
|
||||||
|
* `REDIS`: Redis configuration, if different from the defaults
|
||||||
|
* `SECRET_KEY`: Set to a random string (use `generate_secret_key.py` in the parent directory to generate a suitable key)
|
||||||
|
* `DEBUG`: Set to `True`
|
||||||
|
* `DEVELOPER`: Set to `True` (this enables the creation of new database migrations)
|
||||||
|
|
||||||
|
### Start the Development Server
|
||||||
|
|
||||||
|
Django provides a lightweight, auto-updating HTTP/WSGI server for development use. NetBox extends this slightly to automatically import models and other utilities. Run the NetBox development server with the `nbshell` management command:
|
||||||
|
|
||||||
|
```no-highlight
|
||||||
|
$ python netbox/manage.py runserver
|
||||||
|
Performing system checks...
|
||||||
|
|
||||||
|
System check identified no issues (0 silenced).
|
||||||
|
November 18, 2020 - 15:52:31
|
||||||
|
Django version 3.1, using settings 'netbox.settings'
|
||||||
|
Starting development server at http://127.0.0.1:8000/
|
||||||
|
Quit the server with CONTROL-C.
|
||||||
|
```
|
||||||
|
|
||||||
|
This ensures that your development environment is now complete and operational. Any changes you make to the code base will be automatically adapted by the development server.
|
||||||
|
|
||||||
|
## Running Tests
|
||||||
|
|
||||||
|
Throughout the course of development, it's a good idea to occasionally run NetBox's test suite to catch any potential errors. Tests are run using the `test` management command:
|
||||||
|
|
||||||
|
```no-highlight
|
||||||
|
$ python netbox/manage.py test
|
||||||
|
```
|
||||||
|
|
||||||
|
In cases where you haven't made any changes to the database (which is most of the time), you can append the `--keepdb` argument to this command to reuse the test database between runs. This cuts down on the time it takes to run the test suite since the database doesn't have to be rebuilt each time. (Note that this argument will cause errors if you've modified any model fields since the previous test run.)
|
||||||
|
|
||||||
|
```no-highlight
|
||||||
|
$ python netbox/manage.py test --keepdb
|
||||||
|
```
|
||||||
|
|
||||||
|
## Submitting Pull Requests
|
||||||
|
|
||||||
|
Once you're happy with your work and have verified that all tests pass, commit your changes and push it upstream to your fork. Always provide descriptive (but not excessively verbose) commit messages. When working on a specific issue, be sure to reference it.
|
||||||
|
|
||||||
|
```no-highlight
|
||||||
|
$ git commit -m "Closes #1234: Add IPv5 support"
|
||||||
|
$ git push origin
|
||||||
|
```
|
||||||
|
|
||||||
|
Once your fork has the new commit, submit a [pull request](https://github.com/netbox-community/netbox/compare) to the NetBox repo to propose the changes. Be sure to provide a detailed accounting of the changes being made and the reasons for doing so.
|
||||||
|
|
||||||
|
Once submitted, a maintainer will review your pull request and either merge it or request changes. If changes are needed, you can make them via new commits to your fork: The pull request will update automatically.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
Remember, pull requests are entertained only for **accepted** issues. If an issue you want to work on hasn't been approved by a maintainer yet, it's best to avoid risking your time and effort on a change that might not be accepted.
|
||||||
@@ -18,7 +18,7 @@ NetBox follows the [benevolent dictator](http://oss-watch.ac.uk/resources/benevo
|
|||||||
|
|
||||||
All development of the current NetBox release occurs in the `develop` branch; releases are packaged from the `master` branch. The `master` branch should _always_ represent the current stable release in its entirety, such that installing NetBox by either downloading a packaged release or cloning the `master` branch provides the same code base.
|
All development of the current NetBox release occurs in the `develop` branch; releases are packaged from the `master` branch. The `master` branch should _always_ represent the current stable release in its entirety, such that installing NetBox by either downloading a packaged release or cloning the `master` branch provides the same code base.
|
||||||
|
|
||||||
NetBox components are arranged into functional subsections called _apps_ (a carryover from Django verancular). Each app holds the models, views, and templates relevant to a particular function:
|
NetBox components are arranged into functional subsections called _apps_ (a carryover from Django vernacular). Each app holds the models, views, and templates relevant to a particular function:
|
||||||
|
|
||||||
* `circuits`: Communications circuits and providers (not to be confused with power circuits)
|
* `circuits`: Communications circuits and providers (not to be confused with power circuits)
|
||||||
* `dcim`: Datacenter infrastructure management (sites, racks, and devices)
|
* `dcim`: Datacenter infrastructure management (sites, racks, and devices)
|
||||||
@@ -26,5 +26,6 @@ NetBox components are arranged into functional subsections called _apps_ (a carr
|
|||||||
* `ipam`: IP address management (VRFs, prefixes, IP addresses, and VLANs)
|
* `ipam`: IP address management (VRFs, prefixes, IP addresses, and VLANs)
|
||||||
* `secrets`: Encrypted storage of sensitive data (e.g. login credentials)
|
* `secrets`: Encrypted storage of sensitive data (e.g. login credentials)
|
||||||
* `tenancy`: Tenants (such as customers) to which NetBox objects may be assigned
|
* `tenancy`: Tenants (such as customers) to which NetBox objects may be assigned
|
||||||
|
* `users`: Authentication and user preferences
|
||||||
* `utilities`: Resources which are not user-facing (extendable classes, etc.)
|
* `utilities`: Resources which are not user-facing (extendable classes, etc.)
|
||||||
* `virtualization`: Virtual machines and clusters
|
* `virtualization`: Virtual machines and clusters
|
||||||
|
|||||||
@@ -16,28 +16,24 @@ The other file is `requirements.txt`, which lists each of the required packages
|
|||||||
Every minor version release should refresh `requirements.txt` so that it lists the most recent stable release of each package. To do this:
|
Every minor version release should refresh `requirements.txt` so that it lists the most recent stable release of each package. To do this:
|
||||||
|
|
||||||
1. Create a new virtual environment.
|
1. Create a new virtual environment.
|
||||||
2. Install the latest version of all required packages via pip:
|
2. Install the latest version of all required packages `pip install -U -r base_requirements.txt`).
|
||||||
|
|
||||||
```
|
|
||||||
pip install -U -r base_requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Run all tests and check that the UI and API function as expected.
|
3. Run all tests and check that the UI and API function as expected.
|
||||||
4. Update the package versions in `requirements.txt` as appropriate.
|
4. Review each requirement's release notes for any breaking or otherwise noteworthy changes.
|
||||||
|
5. Update the package versions in `requirements.txt` as appropriate.
|
||||||
|
|
||||||
### Update Static Libraries
|
### Update Static Libraries
|
||||||
|
|
||||||
Update the following static libraries to their most recent stable release:
|
Update the following static libraries to their most recent stable release:
|
||||||
|
|
||||||
* Bootstrap 3
|
* Bootstrap 3
|
||||||
* Font Awesome 4
|
* Material Design Icons
|
||||||
* Select2
|
* Select2
|
||||||
* jQuery
|
* jQuery
|
||||||
* jQuery UI
|
* jQuery UI
|
||||||
|
|
||||||
### Create a new Release Notes Page
|
### Link to the Release Notes Page
|
||||||
|
|
||||||
Create a file at `/docs/release-notes/X.Y.md` to establish the release notes for the new release. Add the file to the table of contents within `mkdocs.yml`, and point `index.md` to the new file.
|
Add the release notes (`/docs/release-notes/X.Y.md`) to the table of contents within `mkdocs.yml`, and point `index.md` to the new file.
|
||||||
|
|
||||||
### Manually Perform a New Install
|
### Manually Perform a New Install
|
||||||
|
|
||||||
@@ -52,7 +48,14 @@ Follow these instructions to perform a new installation of NetBox. This process
|
|||||||
|
|
||||||
### Close the Release Milestone
|
### Close the Release Milestone
|
||||||
|
|
||||||
Close the release milestone on GitHub. Ensure that there are no remaining open issues associated with it.
|
Close the release milestone on GitHub after ensuring there are no remaining open issues associated with it.
|
||||||
|
|
||||||
|
### Merge the Release Branch
|
||||||
|
|
||||||
|
Submit a pull request to merge the release branch `develop-x.y` into the `develop` branch in preparation for its releases.
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
No further releases for the current major version can be published once this pull request is merged.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -64,11 +67,11 @@ Ensure that continuous integration testing on the `develop` branch is completing
|
|||||||
|
|
||||||
### Update Version and Changelog
|
### Update Version and Changelog
|
||||||
|
|
||||||
Update the `VERSION` constant in `settings.py` to the new release version and annotate the current data in the release notes for the new version.
|
Update the `VERSION` constant in `settings.py` to the new release version and annotate the current data in the release notes for the new version. Commit these changes to the `develop` branch.
|
||||||
|
|
||||||
### Submit a Pull Request
|
### Submit a Pull Request
|
||||||
|
|
||||||
Submit a pull request title **"Release vX.Y.Z"** to merge the `develop` branch into `master`. Include a brief change log listing the features, improvements, and/or bugs addressed in the release.
|
Submit a pull request title **"Release vX.Y.Z"** to merge the `develop` branch into `master`. Copy the documented release notes into the pull request's body.
|
||||||
|
|
||||||
Once CI has completed on the PR, merge it.
|
Once CI has completed on the PR, merge it.
|
||||||
|
|
||||||
@@ -76,16 +79,16 @@ Once CI has completed on the PR, merge it.
|
|||||||
|
|
||||||
Draft a [new release](https://github.com/netbox-community/netbox/releases/new) with the following parameters.
|
Draft a [new release](https://github.com/netbox-community/netbox/releases/new) with the following parameters.
|
||||||
|
|
||||||
* **Tag:** Current version (e.g. `v2.3.4`)
|
* **Tag:** Current version (e.g. `v2.9.9`)
|
||||||
* **Target:** `master`
|
* **Target:** `master`
|
||||||
* **Title:** Version and date (e.g. `v2.3.4 - 2018-08-02`)
|
* **Title:** Version and date (e.g. `v2.9.9 - 2020-11-09`)
|
||||||
|
|
||||||
Copy the description from the pull request into the release notes.
|
Copy the description from the pull request to the release.
|
||||||
|
|
||||||
### Update the Development Version
|
### Update the Development Version
|
||||||
|
|
||||||
On the `develop` branch, update `VERSION` in `settings.py` to point to the next release. For example, if you just released v2.3.4, set:
|
On the `develop` branch, update `VERSION` in `settings.py` to point to the next release. For example, if you just released v2.9.9, set:
|
||||||
|
|
||||||
```
|
```
|
||||||
VERSION = 'v2.3.5-dev'
|
VERSION = 'v2.9.10-dev'
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ NetBox generally follows the [Django style guide](https://docs.djangoproject.com
|
|||||||
## PEP 8 Exceptions
|
## PEP 8 Exceptions
|
||||||
|
|
||||||
* Wildcard imports (for example, `from .constants import *`) are acceptable under any of the following conditions:
|
* Wildcard imports (for example, `from .constants import *`) are acceptable under any of the following conditions:
|
||||||
* The library being import contains only constant declarations (`constants.py`)
|
* The library being import contains only constant declarations (e.g. `constants.py`)
|
||||||
* The library being imported explicitly defines `__all__` (e.g. `<app>.api.nested_serializers`)
|
* The library being imported explicitly defines `__all__`
|
||||||
|
|
||||||
* Maximum line length is 120 characters (E501)
|
* Maximum line length is 120 characters (E501)
|
||||||
* This does not apply to HTML templates or to automatically generated code (e.g. database migrations).
|
* This does not apply to HTML templates or to automatically generated code (e.g. database migrations).
|
||||||
@@ -45,7 +45,7 @@ When adding a new dependency, a short description of the package and the URL of
|
|||||||
|
|
||||||
* When in doubt, remain consistent: It is better to be consistently incorrect than inconsistently correct. If you notice in the course of unrelated work a pattern that should be corrected, continue to follow the pattern for now and open a bug so that the entire code base can be evaluated at a later point.
|
* When in doubt, remain consistent: It is better to be consistently incorrect than inconsistently correct. If you notice in the course of unrelated work a pattern that should be corrected, continue to follow the pattern for now and open a bug so that the entire code base can be evaluated at a later point.
|
||||||
|
|
||||||
* Prioritize readability over concision. Python is a very flexible language that typically gives us several options for expressing a given piece of logic, but some may be more friendly to the reader than others. (List comprehensions are particularly vulnerable to over-optimization.) Always remain considerate of the future reader who may need to interpret your code without the benefit of the context within which you are writing it.
|
* Prioritize readability over concision. Python is a very flexible language that typically offers several options for expressing a given piece of logic, but some may be more friendly to the reader than others. (List comprehensions are particularly vulnerable to over-optimization.) Always remain considerate of the future reader who may need to interpret your code without the benefit of the context within which you are writing it.
|
||||||
|
|
||||||
* No easter eggs. While they can be fun, NetBox must be considered as a business-critical tool. The potential, however minor, for introducing a bug caused by unnecessary logic is best avoided entirely.
|
* No easter eggs. While they can be fun, NetBox must be considered as a business-critical tool. The potential, however minor, for introducing a bug caused by unnecessary logic is best avoided entirely.
|
||||||
|
|
||||||
|
|||||||
@@ -8,4 +8,4 @@ The `users.UserConfig` model holds individual preferences for each user in the f
|
|||||||
| ---- | ----------- |
|
| ---- | ----------- |
|
||||||
| extras.configcontext.format | Preferred format when rendering config context data (JSON or YAML) |
|
| extras.configcontext.format | Preferred format when rendering config context data (JSON or YAML) |
|
||||||
| pagination.per_page | The number of items to display per page of a paginated table |
|
| pagination.per_page | The number of items to display per page of a paginated table |
|
||||||
| tables.${table_name}.columns | The ordered list of columns to display when viewing the table |
|
| tables.TABLE_NAME.columns | The ordered list of columns to display when viewing the table |
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
# Utility Views
|
|
||||||
|
|
||||||
Utility views are reusable views that handle common CRUD tasks, such as listing and updating objects. Some views operate on individual objects, whereas others (referred to as "bulk" views) operate on multiple objects at once.
|
|
||||||
|
|
||||||
## Individual Views
|
|
||||||
|
|
||||||
### ObjectView
|
|
||||||
|
|
||||||
Retrieve and display a single object.
|
|
||||||
|
|
||||||
### ObjectListView
|
|
||||||
|
|
||||||
Generates a paginated table of objects from a given queryset, which may optionally be filtered.
|
|
||||||
|
|
||||||
### ObjectEditView
|
|
||||||
|
|
||||||
Updates an object identified by a primary key (PK) or slug. If no existing object is specified, a new object will be created.
|
|
||||||
|
|
||||||
### ObjectDeleteView
|
|
||||||
|
|
||||||
Deletes an object. The user is redirected to a confirmation page before the deletion is executed.
|
|
||||||
|
|
||||||
## Bulk Views
|
|
||||||
|
|
||||||
### BulkCreateView
|
|
||||||
|
|
||||||
Creates multiple objects at once based on a given pattern. Currently used only for IP addresses.
|
|
||||||
|
|
||||||
### BulkImportView
|
|
||||||
|
|
||||||
Accepts CSV-formatted data and creates a new object for each line. Creation is all-or-none.
|
|
||||||
|
|
||||||
### BulkEditView
|
|
||||||
|
|
||||||
Applies changes to multiple objects at once in a two-step operation. First, the list of PKs for selected objects is POSTed and an edit form is presented to the user. On submission of that form, the specified changes are made to all selected objects.
|
|
||||||
|
|
||||||
### BulkDeleteView
|
|
||||||
|
|
||||||
Deletes multiple objects. The user selects the objects to be deleted and confirms the deletion.
|
|
||||||
|
|
||||||
## Component Views
|
|
||||||
|
|
||||||
### ComponentCreateView
|
|
||||||
|
|
||||||
Create one or more component objects beloning to a parent object (e.g. interfaces attached to a device).
|
|
||||||
|
|
||||||
### ComponentEditView
|
|
||||||
|
|
||||||
A subclass of `ObjectEditView`: Updates an individual component object.
|
|
||||||
|
|
||||||
### ComponentDeleteView
|
|
||||||
|
|
||||||
A subclass of `ObjectDeleteView`: Deletes an individual component object.
|
|
||||||
|
|
||||||
### BulkComponentCreateView
|
|
||||||
|
|
||||||
Create a set of components objects for each of a selected set of parent objects. This view can be used e.g. to create multiple interfaces on multiple devices at once.
|
|
||||||
@@ -1,5 +1,26 @@
|
|||||||
# NetBox v2.10
|
# NetBox v2.10
|
||||||
|
|
||||||
|
## v2.10-beta2 (2020-12-03)
|
||||||
|
|
||||||
|
### Enhancements
|
||||||
|
|
||||||
|
* [#5274](https://github.com/netbox-community/netbox/issues/5274) - Add REST API support for custom fields
|
||||||
|
* [#5399](https://github.com/netbox-community/netbox/issues/5399) - Show options for cable endpoint types during bulk import
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* [#5176](https://github.com/netbox-community/netbox/issues/5176) - Enforce content type restrictions when creating objects via the REST API
|
||||||
|
* [#5358](https://github.com/netbox-community/netbox/issues/5358) - Fix user table configuration for VM interfaces
|
||||||
|
* [#5374](https://github.com/netbox-community/netbox/issues/5374) - Fix exception thrown when tracing mid-point
|
||||||
|
* [#5376](https://github.com/netbox-community/netbox/issues/5376) - Correct invalid custom field filter logic values
|
||||||
|
* [#5395](https://github.com/netbox-community/netbox/issues/5395) - Fix cable tracing for rear ports with no corresponding front port
|
||||||
|
|
||||||
|
### Other Changes
|
||||||
|
|
||||||
|
* [#4711](https://github.com/netbox-community/netbox/issues/4711) - Renamed Webhook `obj_type` to `content_types`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## v2.10-beta1 (2020-11-17)
|
## v2.10-beta1 (2020-11-17)
|
||||||
|
|
||||||
**NOTE:** This release completely removes support for embedded graphs.
|
**NOTE:** This release completely removes support for embedded graphs.
|
||||||
@@ -78,6 +99,7 @@ All end-to-end cable paths are now cached using the new CablePath backend model.
|
|||||||
### Other Changes
|
### Other Changes
|
||||||
|
|
||||||
* [#1846](https://github.com/netbox-community/netbox/issues/1846) - Enable MPTT for InventoryItem hierarchy
|
* [#1846](https://github.com/netbox-community/netbox/issues/1846) - Enable MPTT for InventoryItem hierarchy
|
||||||
|
* [#2755](https://github.com/netbox-community/netbox/issues/2755) - Switched from Font Awesome/Glyphicons to Material Design icons
|
||||||
* [#4349](https://github.com/netbox-community/netbox/issues/4349) - Dropped support for embedded graphs
|
* [#4349](https://github.com/netbox-community/netbox/issues/4349) - Dropped support for embedded graphs
|
||||||
* [#4360](https://github.com/netbox-community/netbox/issues/4360) - Dropped support for the Django template language from export templates
|
* [#4360](https://github.com/netbox-community/netbox/issues/4360) - Dropped support for the Django template language from export templates
|
||||||
* [#4941](https://github.com/netbox-community/netbox/issues/4941) - `commit` argument is now required argument in a custom script's `run()` method
|
* [#4941](https://github.com/netbox-community/netbox/issues/4941) - `commit` argument is now required argument in a custom script's `run()` method
|
||||||
@@ -89,6 +111,8 @@ All end-to-end cable paths are now cached using the new CablePath backend model.
|
|||||||
|
|
||||||
* Added support for `PUT`, `PATCH`, and `DELETE` operations on list endpoints (bulk update and delete)
|
* Added support for `PUT`, `PATCH`, and `DELETE` operations on list endpoints (bulk update and delete)
|
||||||
* Added the `/extras/content-types/` endpoint for Django ContentTypes
|
* Added the `/extras/content-types/` endpoint for Django ContentTypes
|
||||||
|
* Added the `/extras/custom-fields/` endpoint for custom fields
|
||||||
|
* Removed the `/extras/_custom_field_choices/` endpoint (replaced by new custom fields endpoint)
|
||||||
* Added the `/status/` endpoint to convey NetBox's current status
|
* Added the `/status/` endpoint to convey NetBox's current status
|
||||||
* circuits.CircuitTermination:
|
* circuits.CircuitTermination:
|
||||||
* Added the `/trace/` endpoint
|
* Added the `/trace/` endpoint
|
||||||
|
|||||||
@@ -1,16 +1,29 @@
|
|||||||
# NetBox v2.9
|
# NetBox v2.9
|
||||||
|
|
||||||
## v2.9.10 (FUTURE)
|
## v2.9.11 (FUTURE)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* [#5383](https://github.com/netbox-community/netbox/issues/5383) - Fix setting user password via REST API
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v2.9.10 (2020-11-24)
|
||||||
|
|
||||||
### Enhancements
|
### Enhancements
|
||||||
|
|
||||||
|
* [#5319](https://github.com/netbox-community/netbox/issues/5319) - Add USB types for power ports and outlets
|
||||||
* [#5337](https://github.com/netbox-community/netbox/issues/5337) - Add "splice" type for pass-through ports
|
* [#5337](https://github.com/netbox-community/netbox/issues/5337) - Add "splice" type for pass-through ports
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* [#5235](https://github.com/netbox-community/netbox/issues/5235) - Fix exception when editing IP address with a NAT IP assigned to a non-racked device
|
* [#5235](https://github.com/netbox-community/netbox/issues/5235) - Fix exception when editing IP address with a NAT IP assigned to a non-racked device
|
||||||
|
* [#5309](https://github.com/netbox-community/netbox/issues/5309) - Avoid extraneous database queries when manipulating objects
|
||||||
* [#5345](https://github.com/netbox-community/netbox/issues/5345) - Fix non-deterministic ordering of prefixes and IP addresses
|
* [#5345](https://github.com/netbox-community/netbox/issues/5345) - Fix non-deterministic ordering of prefixes and IP addresses
|
||||||
* [#5350](https://github.com/netbox-community/netbox/issues/5350) - Filter available racks by selected group when creating a rack reservation
|
* [#5350](https://github.com/netbox-community/netbox/issues/5350) - Filter available racks by selected group when creating a rack reservation
|
||||||
|
* [#5355](https://github.com/netbox-community/netbox/issues/5355) - Limit rack groups by selected site when editing a rack
|
||||||
|
* [#5356](https://github.com/netbox-community/netbox/issues/5356) - Populate manufacturer field when adding a device component template
|
||||||
|
* [#5360](https://github.com/netbox-community/netbox/issues/5360) - Clear VLAN assignments when setting interface mode to none
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -68,8 +68,8 @@ nav:
|
|||||||
- Working with Secrets: 'rest-api/working-with-secrets.md'
|
- Working with Secrets: 'rest-api/working-with-secrets.md'
|
||||||
- Development:
|
- Development:
|
||||||
- Introduction: 'development/index.md'
|
- Introduction: 'development/index.md'
|
||||||
|
- Getting Started: 'development/getting-started.md'
|
||||||
- Style Guide: 'development/style-guide.md'
|
- Style Guide: 'development/style-guide.md'
|
||||||
- Utility Views: 'development/utility-views.md'
|
|
||||||
- Extending Models: 'development/extending-models.md'
|
- Extending Models: 'development/extending-models.md'
|
||||||
- Application Registry: 'development/application-registry.md'
|
- Application Registry: 'development/application-registry.md'
|
||||||
- User Preferences: 'development/user-preferences.md'
|
- User Preferences: 'development/user-preferences.md'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from django.db.models import Count, Prefetch
|
from django.db.models import Prefetch
|
||||||
from rest_framework.routers import APIRootView
|
from rest_framework.routers import APIRootView
|
||||||
|
|
||||||
from circuits import filters
|
from circuits import filters
|
||||||
@@ -6,6 +6,7 @@ from circuits.models import Provider, CircuitTermination, CircuitType, Circuit
|
|||||||
from dcim.api.views import PathEndpointMixin
|
from dcim.api.views import PathEndpointMixin
|
||||||
from extras.api.views import CustomFieldModelViewSet
|
from extras.api.views import CustomFieldModelViewSet
|
||||||
from netbox.api.views import ModelViewSet
|
from netbox.api.views import ModelViewSet
|
||||||
|
from utilities.utils import get_subquery
|
||||||
from . import serializers
|
from . import serializers
|
||||||
|
|
||||||
|
|
||||||
@@ -23,8 +24,8 @@ class CircuitsRootView(APIRootView):
|
|||||||
|
|
||||||
class ProviderViewSet(CustomFieldModelViewSet):
|
class ProviderViewSet(CustomFieldModelViewSet):
|
||||||
queryset = Provider.objects.prefetch_related('tags').annotate(
|
queryset = Provider.objects.prefetch_related('tags').annotate(
|
||||||
circuit_count=Count('circuits')
|
circuit_count=get_subquery(Circuit, 'provider')
|
||||||
).order_by(*Provider._meta.ordering)
|
)
|
||||||
serializer_class = serializers.ProviderSerializer
|
serializer_class = serializers.ProviderSerializer
|
||||||
filterset_class = filters.ProviderFilterSet
|
filterset_class = filters.ProviderFilterSet
|
||||||
|
|
||||||
@@ -35,8 +36,8 @@ class ProviderViewSet(CustomFieldModelViewSet):
|
|||||||
|
|
||||||
class CircuitTypeViewSet(ModelViewSet):
|
class CircuitTypeViewSet(ModelViewSet):
|
||||||
queryset = CircuitType.objects.annotate(
|
queryset = CircuitType.objects.annotate(
|
||||||
circuit_count=Count('circuits')
|
circuit_count=get_subquery(Circuit, 'type')
|
||||||
).order_by(*CircuitType._meta.ordering)
|
)
|
||||||
serializer_class = serializers.CircuitTypeSerializer
|
serializer_class = serializers.CircuitTypeSerializer
|
||||||
filterset_class = filters.CircuitTypeFilterSet
|
filterset_class = filters.CircuitTypeFilterSet
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from django.db.models import Q
|
|||||||
|
|
||||||
from dcim.filters import CableTerminationFilterSet, PathEndpointFilterSet
|
from dcim.filters import CableTerminationFilterSet, PathEndpointFilterSet
|
||||||
from dcim.models import Region, Site
|
from dcim.models import Region, Site
|
||||||
from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet
|
from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet
|
||||||
from tenancy.filters import TenancyFilterSet
|
from tenancy.filters import TenancyFilterSet
|
||||||
from utilities.filters import (
|
from utilities.filters import (
|
||||||
BaseFilterSet, NameSlugSearchFilterSet, TagFilter, TreeNodeMultipleChoiceFilter
|
BaseFilterSet, NameSlugSearchFilterSet, TagFilter, TreeNodeMultipleChoiceFilter
|
||||||
@@ -19,7 +19,7 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ProviderFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
class ProviderFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@@ -73,7 +73,7 @@ class CircuitTypeFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
|
|||||||
fields = ['id', 'name', 'slug']
|
fields = ['id', 'name', 'slug']
|
||||||
|
|
||||||
|
|
||||||
class CircuitFilterSet(BaseFilterSet, CustomFieldFilterSet, TenancyFilterSet, CreatedUpdatedFilterSet):
|
class CircuitFilterSet(BaseFilterSet, CustomFieldModelFilterSet, TenancyFilterSet, CreatedUpdatedFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import Count
|
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django_tables2 import RequestConfig
|
from django_tables2 import RequestConfig
|
||||||
|
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from utilities.forms import ConfirmationForm
|
from utilities.forms import ConfirmationForm
|
||||||
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
||||||
|
from utilities.utils import get_subquery
|
||||||
from . import filters, forms, tables
|
from . import filters, forms, tables
|
||||||
from .choices import CircuitTerminationSideChoices
|
from .choices import CircuitTerminationSideChoices
|
||||||
from .models import Circuit, CircuitTermination, CircuitType, Provider
|
from .models import Circuit, CircuitTermination, CircuitType, Provider
|
||||||
@@ -17,7 +17,9 @@ from .models import Circuit, CircuitTermination, CircuitType, Provider
|
|||||||
#
|
#
|
||||||
|
|
||||||
class ProviderListView(generic.ObjectListView):
|
class ProviderListView(generic.ObjectListView):
|
||||||
queryset = Provider.objects.annotate(count_circuits=Count('circuits')).order_by(*Provider._meta.ordering)
|
queryset = Provider.objects.annotate(
|
||||||
|
count_circuits=get_subquery(Circuit, 'provider')
|
||||||
|
)
|
||||||
filterset = filters.ProviderFilterSet
|
filterset = filters.ProviderFilterSet
|
||||||
filterset_form = forms.ProviderFilterForm
|
filterset_form = forms.ProviderFilterForm
|
||||||
table = tables.ProviderTable
|
table = tables.ProviderTable
|
||||||
@@ -26,11 +28,9 @@ class ProviderListView(generic.ObjectListView):
|
|||||||
class ProviderView(generic.ObjectView):
|
class ProviderView(generic.ObjectView):
|
||||||
queryset = Provider.objects.all()
|
queryset = Provider.objects.all()
|
||||||
|
|
||||||
def get(self, request, slug):
|
def get_extra_context(self, request, instance):
|
||||||
|
|
||||||
provider = get_object_or_404(self.queryset, slug=slug)
|
|
||||||
circuits = Circuit.objects.restrict(request.user, 'view').filter(
|
circuits = Circuit.objects.restrict(request.user, 'view').filter(
|
||||||
provider=provider
|
provider=instance
|
||||||
).prefetch_related(
|
).prefetch_related(
|
||||||
'type', 'tenant', 'terminations__site'
|
'type', 'tenant', 'terminations__site'
|
||||||
).annotate_sites()
|
).annotate_sites()
|
||||||
@@ -44,10 +44,9 @@ class ProviderView(generic.ObjectView):
|
|||||||
}
|
}
|
||||||
RequestConfig(request, paginate).configure(circuits_table)
|
RequestConfig(request, paginate).configure(circuits_table)
|
||||||
|
|
||||||
return render(request, 'circuits/provider.html', {
|
return {
|
||||||
'provider': provider,
|
|
||||||
'circuits_table': circuits_table,
|
'circuits_table': circuits_table,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class ProviderEditView(generic.ObjectEditView):
|
class ProviderEditView(generic.ObjectEditView):
|
||||||
@@ -67,14 +66,18 @@ class ProviderBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
|
|
||||||
class ProviderBulkEditView(generic.BulkEditView):
|
class ProviderBulkEditView(generic.BulkEditView):
|
||||||
queryset = Provider.objects.annotate(count_circuits=Count('circuits')).order_by(*Provider._meta.ordering)
|
queryset = Provider.objects.annotate(
|
||||||
|
count_circuits=get_subquery(Circuit, 'provider')
|
||||||
|
)
|
||||||
filterset = filters.ProviderFilterSet
|
filterset = filters.ProviderFilterSet
|
||||||
table = tables.ProviderTable
|
table = tables.ProviderTable
|
||||||
form = forms.ProviderBulkEditForm
|
form = forms.ProviderBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
class ProviderBulkDeleteView(generic.BulkDeleteView):
|
class ProviderBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Provider.objects.annotate(count_circuits=Count('circuits')).order_by(*Provider._meta.ordering)
|
queryset = Provider.objects.annotate(
|
||||||
|
count_circuits=get_subquery(Circuit, 'provider')
|
||||||
|
)
|
||||||
filterset = filters.ProviderFilterSet
|
filterset = filters.ProviderFilterSet
|
||||||
table = tables.ProviderTable
|
table = tables.ProviderTable
|
||||||
|
|
||||||
@@ -84,7 +87,9 @@ class ProviderBulkDeleteView(generic.BulkDeleteView):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class CircuitTypeListView(generic.ObjectListView):
|
class CircuitTypeListView(generic.ObjectListView):
|
||||||
queryset = CircuitType.objects.annotate(circuit_count=Count('circuits')).order_by(*CircuitType._meta.ordering)
|
queryset = CircuitType.objects.annotate(
|
||||||
|
circuit_count=get_subquery(Circuit, 'type')
|
||||||
|
)
|
||||||
table = tables.CircuitTypeTable
|
table = tables.CircuitTypeTable
|
||||||
|
|
||||||
|
|
||||||
@@ -104,7 +109,9 @@ class CircuitTypeBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
|
|
||||||
class CircuitTypeBulkDeleteView(generic.BulkDeleteView):
|
class CircuitTypeBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = CircuitType.objects.annotate(circuit_count=Count('circuits')).order_by(*CircuitType._meta.ordering)
|
queryset = CircuitType.objects.annotate(
|
||||||
|
circuit_count=get_subquery(Circuit, 'type')
|
||||||
|
)
|
||||||
table = tables.CircuitTypeTable
|
table = tables.CircuitTypeTable
|
||||||
|
|
||||||
|
|
||||||
@@ -124,30 +131,30 @@ class CircuitListView(generic.ObjectListView):
|
|||||||
class CircuitView(generic.ObjectView):
|
class CircuitView(generic.ObjectView):
|
||||||
queryset = Circuit.objects.all()
|
queryset = Circuit.objects.all()
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
circuit = get_object_or_404(self.queryset, pk=pk)
|
|
||||||
|
|
||||||
|
# A-side termination
|
||||||
termination_a = CircuitTermination.objects.restrict(request.user, 'view').prefetch_related(
|
termination_a = CircuitTermination.objects.restrict(request.user, 'view').prefetch_related(
|
||||||
'site__region'
|
'site__region'
|
||||||
).filter(
|
).filter(
|
||||||
circuit=circuit, term_side=CircuitTerminationSideChoices.SIDE_A
|
circuit=instance, term_side=CircuitTerminationSideChoices.SIDE_A
|
||||||
).first()
|
).first()
|
||||||
if termination_a and termination_a.connected_endpoint:
|
if termination_a and termination_a.connected_endpoint:
|
||||||
termination_a.ip_addresses = termination_a.connected_endpoint.ip_addresses.restrict(request.user, 'view')
|
termination_a.ip_addresses = termination_a.connected_endpoint.ip_addresses.restrict(request.user, 'view')
|
||||||
|
|
||||||
|
# Z-side termination
|
||||||
termination_z = CircuitTermination.objects.restrict(request.user, 'view').prefetch_related(
|
termination_z = CircuitTermination.objects.restrict(request.user, 'view').prefetch_related(
|
||||||
'site__region'
|
'site__region'
|
||||||
).filter(
|
).filter(
|
||||||
circuit=circuit, term_side=CircuitTerminationSideChoices.SIDE_Z
|
circuit=instance, term_side=CircuitTerminationSideChoices.SIDE_Z
|
||||||
).first()
|
).first()
|
||||||
if termination_z and termination_z.connected_endpoint:
|
if termination_z and termination_z.connected_endpoint:
|
||||||
termination_z.ip_addresses = termination_z.connected_endpoint.ip_addresses.restrict(request.user, 'view')
|
termination_z.ip_addresses = termination_z.connected_endpoint.ip_addresses.restrict(request.user, 'view')
|
||||||
|
|
||||||
return render(request, 'circuits/circuit.html', {
|
return {
|
||||||
'circuit': circuit,
|
|
||||||
'termination_a': termination_a,
|
'termination_a': termination_a,
|
||||||
'termination_z': termination_z,
|
'termination_z': termination_z,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class CircuitEditView(generic.ObjectEditView):
|
class CircuitEditView(generic.ObjectEditView):
|
||||||
|
|||||||
@@ -584,22 +584,15 @@ class InterfaceSerializer(TaggedObjectSerializer, CableTerminationSerializer, Co
|
|||||||
'count_ipaddresses',
|
'count_ipaddresses',
|
||||||
]
|
]
|
||||||
|
|
||||||
# TODO: This validation should be handled by Interface.clean()
|
|
||||||
def validate(self, data):
|
def validate(self, data):
|
||||||
|
|
||||||
# All associated VLANs be global or assigned to the parent device's site.
|
# Validate many-to-many VLAN assignments
|
||||||
device = self.instance.device if self.instance else data.get('device')
|
device = self.instance.device if self.instance else data.get('device')
|
||||||
untagged_vlan = data.get('untagged_vlan')
|
|
||||||
if untagged_vlan and untagged_vlan.site not in [device.site, None]:
|
|
||||||
raise serializers.ValidationError({
|
|
||||||
'untagged_vlan': "VLAN {} must belong to the same site as the interface's parent device, or it must be "
|
|
||||||
"global.".format(untagged_vlan)
|
|
||||||
})
|
|
||||||
for vlan in data.get('tagged_vlans', []):
|
for vlan in data.get('tagged_vlans', []):
|
||||||
if vlan.site not in [device.site, None]:
|
if vlan.site not in [device.site, None]:
|
||||||
raise serializers.ValidationError({
|
raise serializers.ValidationError({
|
||||||
'tagged_vlans': "VLAN {} must belong to the same site as the interface's parent device, or it must "
|
'tagged_vlans': f"VLAN {vlan} must belong to the same site as the interface's parent device, or "
|
||||||
"be global.".format(vlan)
|
f"it must be global."
|
||||||
})
|
})
|
||||||
|
|
||||||
return super().validate(data)
|
return super().validate(data)
|
||||||
@@ -773,11 +766,10 @@ class CablePathSerializer(serializers.ModelSerializer):
|
|||||||
@swagger_serializer_method(serializer_or_field=serializers.ListField)
|
@swagger_serializer_method(serializer_or_field=serializers.ListField)
|
||||||
def get_path(self, obj):
|
def get_path(self, obj):
|
||||||
ret = []
|
ret = []
|
||||||
for node in obj.path:
|
for node in obj.get_path():
|
||||||
ct_id, object_id = decompile_path_node(node)
|
serializer = get_serializer_for_model(node, prefix='Nested')
|
||||||
ct = ContentType.objects.get_for_id(ct_id)
|
context = {'request': self.context['request']}
|
||||||
# TODO: Return the object URL
|
ret.append(serializer(node, context=context).data)
|
||||||
ret.append(f'{ct.app_label}.{ct.model}:{object_id}')
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import socket
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models import Count, F
|
from django.db.models import F
|
||||||
from django.http import HttpResponseForbidden, HttpResponse
|
from django.http import HttpResponseForbidden, HttpResponse
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from drf_yasg import openapi
|
from drf_yasg import openapi
|
||||||
@@ -125,7 +125,7 @@ class SiteViewSet(CustomFieldModelViewSet):
|
|||||||
vlan_count=get_subquery(VLAN, 'site'),
|
vlan_count=get_subquery(VLAN, 'site'),
|
||||||
circuit_count=get_subquery(Circuit, 'terminations__site'),
|
circuit_count=get_subquery(Circuit, 'terminations__site'),
|
||||||
virtualmachine_count=get_subquery(VirtualMachine, 'cluster__site'),
|
virtualmachine_count=get_subquery(VirtualMachine, 'cluster__site'),
|
||||||
).order_by(*Site._meta.ordering)
|
)
|
||||||
serializer_class = serializers.SiteSerializer
|
serializer_class = serializers.SiteSerializer
|
||||||
filterset_class = filters.SiteFilterSet
|
filterset_class = filters.SiteFilterSet
|
||||||
|
|
||||||
@@ -152,8 +152,8 @@ class RackGroupViewSet(ModelViewSet):
|
|||||||
|
|
||||||
class RackRoleViewSet(ModelViewSet):
|
class RackRoleViewSet(ModelViewSet):
|
||||||
queryset = RackRole.objects.annotate(
|
queryset = RackRole.objects.annotate(
|
||||||
rack_count=Count('racks')
|
rack_count=get_subquery(Rack, 'role')
|
||||||
).order_by(*RackRole._meta.ordering)
|
)
|
||||||
serializer_class = serializers.RackRoleSerializer
|
serializer_class = serializers.RackRoleSerializer
|
||||||
filterset_class = filters.RackRoleFilterSet
|
filterset_class = filters.RackRoleFilterSet
|
||||||
|
|
||||||
@@ -168,7 +168,7 @@ class RackViewSet(CustomFieldModelViewSet):
|
|||||||
).annotate(
|
).annotate(
|
||||||
device_count=get_subquery(Device, 'rack'),
|
device_count=get_subquery(Device, 'rack'),
|
||||||
powerfeed_count=get_subquery(PowerFeed, 'rack')
|
powerfeed_count=get_subquery(PowerFeed, 'rack')
|
||||||
).order_by(*Rack._meta.ordering)
|
)
|
||||||
serializer_class = serializers.RackSerializer
|
serializer_class = serializers.RackSerializer
|
||||||
filterset_class = filters.RackFilterSet
|
filterset_class = filters.RackFilterSet
|
||||||
|
|
||||||
@@ -243,7 +243,7 @@ class ManufacturerViewSet(ModelViewSet):
|
|||||||
devicetype_count=get_subquery(DeviceType, 'manufacturer'),
|
devicetype_count=get_subquery(DeviceType, 'manufacturer'),
|
||||||
inventoryitem_count=get_subquery(InventoryItem, 'manufacturer'),
|
inventoryitem_count=get_subquery(InventoryItem, 'manufacturer'),
|
||||||
platform_count=get_subquery(Platform, 'manufacturer')
|
platform_count=get_subquery(Platform, 'manufacturer')
|
||||||
).order_by(*Manufacturer._meta.ordering)
|
)
|
||||||
serializer_class = serializers.ManufacturerSerializer
|
serializer_class = serializers.ManufacturerSerializer
|
||||||
filterset_class = filters.ManufacturerFilterSet
|
filterset_class = filters.ManufacturerFilterSet
|
||||||
|
|
||||||
@@ -254,8 +254,8 @@ class ManufacturerViewSet(ModelViewSet):
|
|||||||
|
|
||||||
class DeviceTypeViewSet(CustomFieldModelViewSet):
|
class DeviceTypeViewSet(CustomFieldModelViewSet):
|
||||||
queryset = DeviceType.objects.prefetch_related('manufacturer', 'tags').annotate(
|
queryset = DeviceType.objects.prefetch_related('manufacturer', 'tags').annotate(
|
||||||
device_count=Count('instances')
|
device_count=get_subquery(Device, 'device_type')
|
||||||
).order_by(*DeviceType._meta.ordering)
|
)
|
||||||
serializer_class = serializers.DeviceTypeSerializer
|
serializer_class = serializers.DeviceTypeSerializer
|
||||||
filterset_class = filters.DeviceTypeFilterSet
|
filterset_class = filters.DeviceTypeFilterSet
|
||||||
|
|
||||||
@@ -320,7 +320,7 @@ class DeviceRoleViewSet(ModelViewSet):
|
|||||||
queryset = DeviceRole.objects.annotate(
|
queryset = DeviceRole.objects.annotate(
|
||||||
device_count=get_subquery(Device, 'device_role'),
|
device_count=get_subquery(Device, 'device_role'),
|
||||||
virtualmachine_count=get_subquery(VirtualMachine, 'role')
|
virtualmachine_count=get_subquery(VirtualMachine, 'role')
|
||||||
).order_by(*DeviceRole._meta.ordering)
|
)
|
||||||
serializer_class = serializers.DeviceRoleSerializer
|
serializer_class = serializers.DeviceRoleSerializer
|
||||||
filterset_class = filters.DeviceRoleFilterSet
|
filterset_class = filters.DeviceRoleFilterSet
|
||||||
|
|
||||||
@@ -333,7 +333,7 @@ class PlatformViewSet(ModelViewSet):
|
|||||||
queryset = Platform.objects.annotate(
|
queryset = Platform.objects.annotate(
|
||||||
device_count=get_subquery(Device, 'platform'),
|
device_count=get_subquery(Device, 'platform'),
|
||||||
virtualmachine_count=get_subquery(VirtualMachine, 'platform')
|
virtualmachine_count=get_subquery(VirtualMachine, 'platform')
|
||||||
).order_by(*Platform._meta.ordering)
|
)
|
||||||
serializer_class = serializers.PlatformSerializer
|
serializer_class = serializers.PlatformSerializer
|
||||||
filterset_class = filters.PlatformFilterSet
|
filterset_class = filters.PlatformFilterSet
|
||||||
|
|
||||||
@@ -596,8 +596,8 @@ class CableViewSet(ModelViewSet):
|
|||||||
|
|
||||||
class VirtualChassisViewSet(ModelViewSet):
|
class VirtualChassisViewSet(ModelViewSet):
|
||||||
queryset = VirtualChassis.objects.prefetch_related('tags').annotate(
|
queryset = VirtualChassis.objects.prefetch_related('tags').annotate(
|
||||||
member_count=Count('members', distinct=True)
|
member_count=get_subquery(Device, 'virtual_chassis')
|
||||||
).order_by(*VirtualChassis._meta.ordering)
|
)
|
||||||
serializer_class = serializers.VirtualChassisSerializer
|
serializer_class = serializers.VirtualChassisSerializer
|
||||||
filterset_class = filters.VirtualChassisFilterSet
|
filterset_class = filters.VirtualChassisFilterSet
|
||||||
|
|
||||||
@@ -610,8 +610,8 @@ class PowerPanelViewSet(ModelViewSet):
|
|||||||
queryset = PowerPanel.objects.prefetch_related(
|
queryset = PowerPanel.objects.prefetch_related(
|
||||||
'site', 'rack_group'
|
'site', 'rack_group'
|
||||||
).annotate(
|
).annotate(
|
||||||
powerfeed_count=Count('powerfeeds')
|
powerfeed_count=get_subquery(PowerFeed, 'power_panel')
|
||||||
).order_by(*PowerPanel._meta.ordering)
|
)
|
||||||
serializer_class = serializers.PowerPanelSerializer
|
serializer_class = serializers.PowerPanelSerializer
|
||||||
filterset_class = filters.PowerPanelFilterSet
|
filterset_class = filters.PowerPanelFilterSet
|
||||||
|
|
||||||
|
|||||||
@@ -304,6 +304,16 @@ class PowerPortTypeChoices(ChoiceSet):
|
|||||||
TYPE_ITA_M = 'ita-m'
|
TYPE_ITA_M = 'ita-m'
|
||||||
TYPE_ITA_N = 'ita-n'
|
TYPE_ITA_N = 'ita-n'
|
||||||
TYPE_ITA_O = 'ita-o'
|
TYPE_ITA_O = 'ita-o'
|
||||||
|
# USB
|
||||||
|
TYPE_USB_A = 'usb-a'
|
||||||
|
TYPE_USB_B = 'usb-b'
|
||||||
|
TYPE_USB_C = 'usb-c'
|
||||||
|
TYPE_USB_MINI_A = 'usb-mini-a'
|
||||||
|
TYPE_USB_MINI_B = 'usb-mini-b'
|
||||||
|
TYPE_USB_MICRO_A = 'usb-micro-a'
|
||||||
|
TYPE_USB_MICRO_B = 'usb-micro-b'
|
||||||
|
TYPE_USB_3_B = 'usb-3-b'
|
||||||
|
TYPE_USB_3_MICROB = 'usb-3-micro-b'
|
||||||
|
|
||||||
CHOICES = (
|
CHOICES = (
|
||||||
('IEC 60320', (
|
('IEC 60320', (
|
||||||
@@ -393,6 +403,17 @@ class PowerPortTypeChoices(ChoiceSet):
|
|||||||
(TYPE_ITA_N, 'ITA Type N'),
|
(TYPE_ITA_N, 'ITA Type N'),
|
||||||
(TYPE_ITA_O, 'ITA Type O'),
|
(TYPE_ITA_O, 'ITA Type O'),
|
||||||
)),
|
)),
|
||||||
|
('USB', (
|
||||||
|
(TYPE_USB_A, 'USB Type A'),
|
||||||
|
(TYPE_USB_B, 'USB Type B'),
|
||||||
|
(TYPE_USB_C, 'USB Type C'),
|
||||||
|
(TYPE_USB_MINI_A, 'USB Mini A'),
|
||||||
|
(TYPE_USB_MINI_B, 'USB Mini B'),
|
||||||
|
(TYPE_USB_MICRO_A, 'USB Micro A'),
|
||||||
|
(TYPE_USB_MICRO_B, 'USB Micro B'),
|
||||||
|
(TYPE_USB_3_B, 'USB 3.0 Type B'),
|
||||||
|
(TYPE_USB_3_MICROB, 'USB 3.0 Micro B'),
|
||||||
|
)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -482,6 +503,10 @@ class PowerOutletTypeChoices(ChoiceSet):
|
|||||||
TYPE_ITA_M = 'ita-m'
|
TYPE_ITA_M = 'ita-m'
|
||||||
TYPE_ITA_N = 'ita-n'
|
TYPE_ITA_N = 'ita-n'
|
||||||
TYPE_ITA_O = 'ita-o'
|
TYPE_ITA_O = 'ita-o'
|
||||||
|
# USB
|
||||||
|
TYPE_USB_A = 'usb-a'
|
||||||
|
TYPE_USB_MICROB = 'usb-micro-b'
|
||||||
|
TYPE_USB_C = 'usb-c'
|
||||||
# Proprietary
|
# Proprietary
|
||||||
TYPE_HDOT_CX = 'hdot-cx'
|
TYPE_HDOT_CX = 'hdot-cx'
|
||||||
|
|
||||||
@@ -572,6 +597,11 @@ class PowerOutletTypeChoices(ChoiceSet):
|
|||||||
(TYPE_ITA_N, 'ITA Type N'),
|
(TYPE_ITA_N, 'ITA Type N'),
|
||||||
(TYPE_ITA_O, 'ITA Type O'),
|
(TYPE_ITA_O, 'ITA Type O'),
|
||||||
)),
|
)),
|
||||||
|
('USB', (
|
||||||
|
(TYPE_USB_A, 'USB Type A'),
|
||||||
|
(TYPE_USB_MICROB, 'USB Micro B'),
|
||||||
|
(TYPE_USB_C, 'USB Type C'),
|
||||||
|
)),
|
||||||
('Proprietary', (
|
('Proprietary', (
|
||||||
(TYPE_HDOT_CX, 'HDOT Cx'),
|
(TYPE_HDOT_CX, 'HDOT Cx'),
|
||||||
)),
|
)),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import django_filters
|
|||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
|
|
||||||
from extras.filters import CustomFieldFilterSet, LocalConfigContextFilterSet, CreatedUpdatedFilterSet
|
from extras.filters import CustomFieldModelFilterSet, LocalConfigContextFilterSet, CreatedUpdatedFilterSet
|
||||||
from tenancy.filters import TenancyFilterSet
|
from tenancy.filters import TenancyFilterSet
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.choices import ColorChoices
|
from utilities.choices import ColorChoices
|
||||||
@@ -80,7 +80,7 @@ class RegionFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
|
|||||||
fields = ['id', 'name', 'slug', 'description']
|
fields = ['id', 'name', 'slug', 'description']
|
||||||
|
|
||||||
|
|
||||||
class SiteFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
class SiteFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@@ -179,7 +179,7 @@ class RackRoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
|
|||||||
fields = ['id', 'name', 'slug', 'color']
|
fields = ['id', 'name', 'slug', 'color']
|
||||||
|
|
||||||
|
|
||||||
class RackFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
class RackFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@@ -325,7 +325,7 @@ class ManufacturerFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
|
|||||||
fields = ['id', 'name', 'slug', 'description']
|
fields = ['id', 'name', 'slug', 'description']
|
||||||
|
|
||||||
|
|
||||||
class DeviceTypeFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
class DeviceTypeFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@@ -504,7 +504,7 @@ class DeviceFilterSet(
|
|||||||
BaseFilterSet,
|
BaseFilterSet,
|
||||||
TenancyFilterSet,
|
TenancyFilterSet,
|
||||||
LocalConfigContextFilterSet,
|
LocalConfigContextFilterSet,
|
||||||
CustomFieldFilterSet,
|
CustomFieldModelFilterSet,
|
||||||
CreatedUpdatedFilterSet
|
CreatedUpdatedFilterSet
|
||||||
):
|
):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
@@ -1246,7 +1246,7 @@ class PowerFeedFilterSet(
|
|||||||
BaseFilterSet,
|
BaseFilterSet,
|
||||||
CableTerminationFilterSet,
|
CableTerminationFilterSet,
|
||||||
PathEndpointFilterSet,
|
PathEndpointFilterSet,
|
||||||
CustomFieldFilterSet,
|
CustomFieldModelFilterSet,
|
||||||
CreatedUpdatedFilterSet
|
CreatedUpdatedFilterSet
|
||||||
):
|
):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
|
|||||||
@@ -22,9 +22,10 @@ from tenancy.forms import TenancyFilterForm, TenancyForm
|
|||||||
from tenancy.models import Tenant, TenantGroup
|
from tenancy.models import Tenant, TenantGroup
|
||||||
from utilities.forms import (
|
from utilities.forms import (
|
||||||
APISelect, add_blank_choice, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
|
APISelect, add_blank_choice, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
|
||||||
ColorSelect, CommentField, CSVChoiceField, CSVModelChoiceField, CSVModelForm, DynamicModelChoiceField,
|
ColorSelect, CommentField, CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, CSVModelForm,
|
||||||
DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model, JSONField, NumericArrayField, SelectWithPK,
|
DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model, JSONField,
|
||||||
SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES,
|
NumericArrayField, SelectWithPK, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField,
|
||||||
|
BOOLEAN_WITH_BLANK_CHOICES,
|
||||||
)
|
)
|
||||||
from virtualization.models import Cluster, ClusterGroup
|
from virtualization.models import Cluster, ClusterGroup
|
||||||
from .choices import *
|
from .choices import *
|
||||||
@@ -88,13 +89,12 @@ class DeviceComponentFilterForm(BootstrapMixin, forms.Form):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class InterfaceCommonForm:
|
class InterfaceCommonForm(forms.Form):
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
|
||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
# Validate VLAN assignments
|
parent_field = 'device' if 'device' in self.cleaned_data else 'virtual_machine'
|
||||||
tagged_vlans = self.cleaned_data['tagged_vlans']
|
tagged_vlans = self.cleaned_data['tagged_vlans']
|
||||||
|
|
||||||
# Untagged interfaces cannot be assigned tagged VLANs
|
# Untagged interfaces cannot be assigned tagged VLANs
|
||||||
@@ -109,13 +109,13 @@ class InterfaceCommonForm:
|
|||||||
|
|
||||||
# Validate tagged VLANs; must be a global VLAN or in the same site
|
# Validate tagged VLANs; must be a global VLAN or in the same site
|
||||||
elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_TAGGED:
|
elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_TAGGED:
|
||||||
valid_sites = [None, self.cleaned_data['device'].site]
|
valid_sites = [None, self.cleaned_data[parent_field].site]
|
||||||
invalid_vlans = [str(v) for v in tagged_vlans if v.site not in valid_sites]
|
invalid_vlans = [str(v) for v in tagged_vlans if v.site not in valid_sites]
|
||||||
|
|
||||||
if invalid_vlans:
|
if invalid_vlans:
|
||||||
raise forms.ValidationError({
|
raise forms.ValidationError({
|
||||||
'tagged_vlans': "The tagged VLANs ({}) must belong to the same site as the interface's parent "
|
'tagged_vlans': f"The tagged VLANs ({', '.join(invalid_vlans)}) must belong to the same site as "
|
||||||
"device/VM, or they must be global".format(', '.join(invalid_vlans))
|
f"the interface's parent device/VM, or they must be global"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -470,6 +470,13 @@ class RackForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
|||||||
'region_id': '$region'
|
'region_id': '$region'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
group = DynamicModelChoiceField(
|
||||||
|
queryset=RackGroup.objects.all(),
|
||||||
|
required=False,
|
||||||
|
query_params={
|
||||||
|
'site_id': '$site'
|
||||||
|
}
|
||||||
|
)
|
||||||
role = DynamicModelChoiceField(
|
role = DynamicModelChoiceField(
|
||||||
queryset=RackRole.objects.all(),
|
queryset=RackRole.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
@@ -1027,7 +1034,10 @@ class ComponentTemplateCreateForm(ComponentForm):
|
|||||||
"""
|
"""
|
||||||
manufacturer = DynamicModelChoiceField(
|
manufacturer = DynamicModelChoiceField(
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
required=False
|
required=False,
|
||||||
|
initial_params={
|
||||||
|
'device_types': 'device_type'
|
||||||
|
}
|
||||||
)
|
)
|
||||||
device_type = DynamicModelChoiceField(
|
device_type = DynamicModelChoiceField(
|
||||||
queryset=DeviceType.objects.all(),
|
queryset=DeviceType.objects.all(),
|
||||||
@@ -2671,7 +2681,7 @@ class InterfaceFilterForm(DeviceComponentFilterForm):
|
|||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
|
|
||||||
class InterfaceForm(InterfaceCommonForm, BootstrapMixin, forms.ModelForm):
|
class InterfaceForm(BootstrapMixin, InterfaceCommonForm, forms.ModelForm):
|
||||||
untagged_vlan = DynamicModelChoiceField(
|
untagged_vlan = DynamicModelChoiceField(
|
||||||
queryset=VLAN.objects.all(),
|
queryset=VLAN.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
@@ -3748,10 +3758,9 @@ class CableCSVForm(CSVModelForm):
|
|||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text='Side A device'
|
help_text='Side A device'
|
||||||
)
|
)
|
||||||
side_a_type = CSVModelChoiceField(
|
side_a_type = CSVContentTypeField(
|
||||||
queryset=ContentType.objects.all(),
|
queryset=ContentType.objects.all(),
|
||||||
limit_choices_to=CABLE_TERMINATION_MODELS,
|
limit_choices_to=CABLE_TERMINATION_MODELS,
|
||||||
to_field_name='model',
|
|
||||||
help_text='Side A type'
|
help_text='Side A type'
|
||||||
)
|
)
|
||||||
side_a_name = forms.CharField(
|
side_a_name = forms.CharField(
|
||||||
@@ -3764,10 +3773,9 @@ class CableCSVForm(CSVModelForm):
|
|||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text='Side B device'
|
help_text='Side B device'
|
||||||
)
|
)
|
||||||
side_b_type = CSVModelChoiceField(
|
side_b_type = CSVContentTypeField(
|
||||||
queryset=ContentType.objects.all(),
|
queryset=ContentType.objects.all(),
|
||||||
limit_choices_to=CABLE_TERMINATION_MODELS,
|
limit_choices_to=CABLE_TERMINATION_MODELS,
|
||||||
to_field_name='model',
|
|
||||||
help_text='Side B type'
|
help_text='Side B type'
|
||||||
)
|
)
|
||||||
side_b_name = forms.CharField(
|
side_b_name = forms.CharField(
|
||||||
@@ -3801,58 +3809,36 @@ class CableCSVForm(CSVModelForm):
|
|||||||
'color': mark_safe('RGB color in hexadecimal (e.g. <code>00ff00</code>)'),
|
'color': mark_safe('RGB color in hexadecimal (e.g. <code>00ff00</code>)'),
|
||||||
}
|
}
|
||||||
|
|
||||||
# TODO: Merge the clean() methods for either end
|
def _clean_side(self, side):
|
||||||
def clean_side_a_name(self):
|
"""
|
||||||
|
Derive a Cable's A/B termination objects.
|
||||||
|
|
||||||
device = self.cleaned_data.get('side_a_device')
|
:param side: 'a' or 'b'
|
||||||
content_type = self.cleaned_data.get('side_a_type')
|
"""
|
||||||
name = self.cleaned_data.get('side_a_name')
|
assert side in 'ab', f"Invalid side designation: {side}"
|
||||||
|
|
||||||
|
device = self.cleaned_data.get(f'side_{side}_device')
|
||||||
|
content_type = self.cleaned_data.get(f'side_{side}_type')
|
||||||
|
name = self.cleaned_data.get(f'side_{side}_name')
|
||||||
if not device or not content_type or not name:
|
if not device or not content_type or not name:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
model = content_type.model_class()
|
model = content_type.model_class()
|
||||||
try:
|
try:
|
||||||
termination_object = model.objects.get(
|
termination_object = model.objects.get(device=device, name=name)
|
||||||
device=device,
|
|
||||||
name=name
|
|
||||||
)
|
|
||||||
if termination_object.cable is not None:
|
if termination_object.cable is not None:
|
||||||
raise forms.ValidationError(
|
raise forms.ValidationError(f"Side {side.upper()}: {device} {termination_object} is already connected")
|
||||||
"Side A: {} {} is already connected".format(device, termination_object)
|
|
||||||
)
|
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
raise forms.ValidationError(
|
raise forms.ValidationError(f"{side.upper()} side termination not found: {device} {name}")
|
||||||
"A side termination not found: {} {}".format(device, name)
|
|
||||||
)
|
|
||||||
|
|
||||||
self.instance.termination_a = termination_object
|
setattr(self.instance, f'termination_{side}', termination_object)
|
||||||
return termination_object
|
return termination_object
|
||||||
|
|
||||||
|
def clean_side_a_name(self):
|
||||||
|
return self._clean_side('a')
|
||||||
|
|
||||||
def clean_side_b_name(self):
|
def clean_side_b_name(self):
|
||||||
|
return self._clean_side('b')
|
||||||
device = self.cleaned_data.get('side_b_device')
|
|
||||||
content_type = self.cleaned_data.get('side_b_type')
|
|
||||||
name = self.cleaned_data.get('side_b_name')
|
|
||||||
if not device or not content_type or not name:
|
|
||||||
return None
|
|
||||||
|
|
||||||
model = content_type.model_class()
|
|
||||||
try:
|
|
||||||
termination_object = model.objects.get(
|
|
||||||
device=device,
|
|
||||||
name=name
|
|
||||||
)
|
|
||||||
if termination_object.cable is not None:
|
|
||||||
raise forms.ValidationError(
|
|
||||||
"Side B: {} {} is already connected".format(device, termination_object)
|
|
||||||
)
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
raise forms.ValidationError(
|
|
||||||
"B side termination not found: {} {}".format(device, name)
|
|
||||||
)
|
|
||||||
|
|
||||||
self.instance.termination_b = termination_object
|
|
||||||
return termination_object
|
|
||||||
|
|
||||||
def clean_length_unit(self):
|
def clean_length_unit(self):
|
||||||
# Avoid trying to save as NULL
|
# Avoid trying to save as NULL
|
||||||
|
|||||||
@@ -30,6 +30,13 @@ class Command(BaseCommand):
|
|||||||
help="Do not prompt user for any input/confirmation"
|
help="Do not prompt user for any input/confirmation"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def draw_progress_bar(self, percentage):
|
||||||
|
"""
|
||||||
|
Draw a simple progress bar 20 increments wide illustrating the specified percentage.
|
||||||
|
"""
|
||||||
|
bar_size = int(percentage / 5)
|
||||||
|
self.stdout.write(f"\r [{'#' * bar_size}{' ' * (20-bar_size)}] {int(percentage)}%", ending='')
|
||||||
|
|
||||||
def handle(self, *model_names, **options):
|
def handle(self, *model_names, **options):
|
||||||
|
|
||||||
# If --force was passed, first delete all existing CablePaths
|
# If --force was passed, first delete all existing CablePaths
|
||||||
@@ -67,15 +74,15 @@ class Command(BaseCommand):
|
|||||||
origins = origins.filter(_path__isnull=True)
|
origins = origins.filter(_path__isnull=True)
|
||||||
origins_count = origins.count()
|
origins_count = origins.count()
|
||||||
if not origins_count:
|
if not origins_count:
|
||||||
print(f'Found no missing {model._meta.verbose_name} paths; skipping')
|
self.stdout.write(f'Found no missing {model._meta.verbose_name} paths; skipping')
|
||||||
continue
|
continue
|
||||||
print(f'Retracing {origins_count} cabled {model._meta.verbose_name_plural}...')
|
self.stdout.write(f'Retracing {origins_count} cabled {model._meta.verbose_name_plural}...')
|
||||||
i = 0
|
i = 0
|
||||||
for i, obj in enumerate(origins, start=1):
|
for i, obj in enumerate(origins, start=1):
|
||||||
create_cablepath(obj)
|
create_cablepath(obj)
|
||||||
# TODO: Come up with a better progress indicator
|
if not i % 100:
|
||||||
if not i % 1000:
|
self.draw_progress_bar(i * 100 / origins_count)
|
||||||
self.stdout.write(f' {i}')
|
self.draw_progress_bar(100)
|
||||||
self.stdout.write(self.style.SUCCESS(f' Retraced {i} {model._meta.verbose_name_plural}'))
|
self.stdout.write(self.style.SUCCESS(f'\n Retraced {i} {model._meta.verbose_name_plural}'))
|
||||||
|
|
||||||
self.stdout.write(self.style.SUCCESS('Finished.'))
|
self.stdout.write(self.style.SUCCESS('Finished.'))
|
||||||
|
|||||||
@@ -411,21 +411,27 @@ class CablePath(models.Model):
|
|||||||
position_stack.append(peer_termination.rear_port_position)
|
position_stack.append(peer_termination.rear_port_position)
|
||||||
path.append(object_to_path_node(node))
|
path.append(object_to_path_node(node))
|
||||||
|
|
||||||
# Follow a RearPort to its corresponding FrontPort
|
# Follow a RearPort to its corresponding FrontPort (if any)
|
||||||
elif isinstance(peer_termination, RearPort):
|
elif isinstance(peer_termination, RearPort):
|
||||||
path.append(object_to_path_node(peer_termination))
|
path.append(object_to_path_node(peer_termination))
|
||||||
|
|
||||||
|
# Determine the peer FrontPort's position
|
||||||
if peer_termination.positions == 1:
|
if peer_termination.positions == 1:
|
||||||
node = FrontPort.objects.get(rear_port=peer_termination, rear_port_position=1)
|
position = 1
|
||||||
path.append(object_to_path_node(node))
|
|
||||||
elif position_stack:
|
elif position_stack:
|
||||||
position = position_stack.pop()
|
position = position_stack.pop()
|
||||||
node = FrontPort.objects.get(rear_port=peer_termination, rear_port_position=position)
|
|
||||||
path.append(object_to_path_node(node))
|
|
||||||
else:
|
else:
|
||||||
# No position indicated: path has split, so we stop at the RearPort
|
# No position indicated: path has split, so we stop at the RearPort
|
||||||
is_split = True
|
is_split = True
|
||||||
break
|
break
|
||||||
|
|
||||||
|
try:
|
||||||
|
node = FrontPort.objects.get(rear_port=peer_termination, rear_port_position=position)
|
||||||
|
path.append(object_to_path_node(node))
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
# No corresponding FrontPort found for the RearPort
|
||||||
|
break
|
||||||
|
|
||||||
# Anything else marks the end of the path
|
# Anything else marks the end of the path
|
||||||
else:
|
else:
|
||||||
destination = peer_termination
|
destination = peer_termination
|
||||||
|
|||||||
@@ -464,6 +464,18 @@ class BaseInterface(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
|
||||||
|
# Remove untagged VLAN assignment for non-802.1Q interfaces
|
||||||
|
if not self.mode:
|
||||||
|
self.untagged_vlan = None
|
||||||
|
|
||||||
|
# Only "tagged" interfaces may have tagged VLANs assigned. ("tagged all" implies all VLANs are assigned.)
|
||||||
|
if self.pk and self.mode != InterfaceModeChoices.MODE_TAGGED:
|
||||||
|
self.tagged_vlans.clear()
|
||||||
|
|
||||||
|
return super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('export_templates', 'webhooks')
|
@extras_features('export_templates', 'webhooks')
|
||||||
class Interface(CableTermination, PathEndpoint, ComponentModel, BaseInterface):
|
class Interface(CableTermination, PathEndpoint, ComponentModel, BaseInterface):
|
||||||
@@ -580,18 +592,6 @@ class Interface(CableTermination, PathEndpoint, ComponentModel, BaseInterface):
|
|||||||
"device, or it must be global".format(self.untagged_vlan)
|
"device, or it must be global".format(self.untagged_vlan)
|
||||||
})
|
})
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
|
|
||||||
# Remove untagged VLAN assignment for non-802.1Q interfaces
|
|
||||||
if self.mode is None:
|
|
||||||
self.untagged_vlan = None
|
|
||||||
|
|
||||||
# Only "tagged" interfaces may have tagged VLANs assigned. ("tagged all" implies all VLANs are assigned.)
|
|
||||||
if self.pk and self.mode != InterfaceModeChoices.MODE_TAGGED:
|
|
||||||
self.tagged_vlans.clear()
|
|
||||||
|
|
||||||
return super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def parent(self):
|
def parent(self):
|
||||||
return self.device
|
return self.device
|
||||||
|
|||||||
@@ -45,9 +45,15 @@ INTERFACE_IPADDRESSES = """
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
INTERFACE_TAGGED_VLANS = """
|
INTERFACE_TAGGED_VLANS = """
|
||||||
{% for vlan in record.tagged_vlans.unrestricted %}
|
{% if record.mode == 'tagged' %}
|
||||||
<a href="{{ vlan.get_absolute_url }}">{{ vlan }}</a><br />
|
{% for vlan in record.tagged_vlans.all %}
|
||||||
{% endfor %}
|
<a href="{{ vlan.get_absolute_url }}">{{ vlan }}</a><br />
|
||||||
|
{% endfor %}
|
||||||
|
{% elif record.mode == 'tagged-all' %}
|
||||||
|
All
|
||||||
|
{% else %}
|
||||||
|
—
|
||||||
|
{% endif %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
MPTT_LINK = """
|
MPTT_LINK = """
|
||||||
@@ -100,9 +106,9 @@ CONSOLEPORT_BUTTONS = """
|
|||||||
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span>
|
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-right">
|
<ul class="dropdown-menu dropdown-menu-right">
|
||||||
<li><a href="{% url 'dcim:consoleport_connect' termination_a_id=record.pk termination_b_type='console-server-port' %}?return_url={% url 'dcim:device_consoleports' pk=device.pk %}">Console Server Port</a></li>
|
<li><a href="{% url 'dcim:consoleport_connect' termination_a_id=record.pk termination_b_type='console-server-port' %}?return_url={% url 'dcim:device_consoleports' pk=object.pk %}">Console Server Port</a></li>
|
||||||
<li><a href="{% url 'dcim:consoleport_connect' termination_a_id=record.pk termination_b_type='front-port' %}?return_url={% url 'dcim:device_consoleports' pk=device.pk %}">Front Port</a></li>
|
<li><a href="{% url 'dcim:consoleport_connect' termination_a_id=record.pk termination_b_type='front-port' %}?return_url={% url 'dcim:device_consoleports' pk=object.pk %}">Front Port</a></li>
|
||||||
<li><a href="{% url 'dcim:consoleport_connect' termination_a_id=record.pk termination_b_type='rear-port' %}?return_url={% url 'dcim:device_consoleports' pk=device.pk %}">Rear Port</a></li>
|
<li><a href="{% url 'dcim:consoleport_connect' termination_a_id=record.pk termination_b_type='rear-port' %}?return_url={% url 'dcim:device_consoleports' pk=object.pk %}">Rear Port</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -120,9 +126,9 @@ CONSOLESERVERPORT_BUTTONS = """
|
|||||||
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span>
|
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-right">
|
<ul class="dropdown-menu dropdown-menu-right">
|
||||||
<li><a href="{% url 'dcim:consoleserverport_connect' termination_a_id=record.pk termination_b_type='console-port' %}?return_url={% url 'dcim:device_consoleserverports' pk=device.pk %}">Console Port</a></li>
|
<li><a href="{% url 'dcim:consoleserverport_connect' termination_a_id=record.pk termination_b_type='console-port' %}?return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}">Console Port</a></li>
|
||||||
<li><a href="{% url 'dcim:consoleserverport_connect' termination_a_id=record.pk termination_b_type='front-port' %}?return_url={% url 'dcim:device_consoleserverports' pk=device.pk %}">Front Port</a></li>
|
<li><a href="{% url 'dcim:consoleserverport_connect' termination_a_id=record.pk termination_b_type='front-port' %}?return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}">Front Port</a></li>
|
||||||
<li><a href="{% url 'dcim:consoleserverport_connect' termination_a_id=record.pk termination_b_type='rear-port' %}?return_url={% url 'dcim:device_consoleserverports' pk=device.pk %}">Rear Port</a></li>
|
<li><a href="{% url 'dcim:consoleserverport_connect' termination_a_id=record.pk termination_b_type='rear-port' %}?return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}">Rear Port</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -140,8 +146,8 @@ POWERPORT_BUTTONS = """
|
|||||||
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span>
|
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-right">
|
<ul class="dropdown-menu dropdown-menu-right">
|
||||||
<li><a href="{% url 'dcim:powerport_connect' termination_a_id=record.pk termination_b_type='power-outlet' %}?return_url={% url 'dcim:device_powerports' pk=device.pk %}">Power Outlet</a></li>
|
<li><a href="{% url 'dcim:powerport_connect' termination_a_id=record.pk termination_b_type='power-outlet' %}?return_url={% url 'dcim:device_powerports' pk=object.pk %}">Power Outlet</a></li>
|
||||||
<li><a href="{% url 'dcim:powerport_connect' termination_a_id=record.pk termination_b_type='power-feed' %}?return_url={% url 'dcim:device_powerports' pk=device.pk %}">Power Feed</a></li>
|
<li><a href="{% url 'dcim:powerport_connect' termination_a_id=record.pk termination_b_type='power-feed' %}?return_url={% url 'dcim:device_powerports' pk=object.pk %}">Power Feed</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -154,7 +160,7 @@ POWEROUTLET_BUTTONS = """
|
|||||||
{% elif perms.dcim.add_cable %}
|
{% elif perms.dcim.add_cable %}
|
||||||
<a href="#" class="btn btn-default btn-xs disabled"><i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i></a>
|
<a href="#" class="btn btn-default btn-xs disabled"><i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i></a>
|
||||||
<a href="#" class="btn btn-default btn-xs disabled"><i class="mdi mdi-lan-connect" aria-hidden="true"></i></a>
|
<a href="#" class="btn btn-default btn-xs disabled"><i class="mdi mdi-lan-connect" aria-hidden="true"></i></a>
|
||||||
<a href="{% url 'dcim:poweroutlet_connect' termination_a_id=record.pk termination_b_type='power-port' %}?return_url={% url 'dcim:device_poweroutlets' pk=device.pk %}" title="Connect" class="btn btn-success btn-xs">
|
<a href="{% url 'dcim:poweroutlet_connect' termination_a_id=record.pk termination_b_type='power-port' %}?return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" title="Connect" class="btn btn-success btn-xs">
|
||||||
<i class="mdi mdi-ethernet-cable" aria-hidden="true"></i>
|
<i class="mdi mdi-ethernet-cable" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -162,7 +168,7 @@ POWEROUTLET_BUTTONS = """
|
|||||||
|
|
||||||
INTERFACE_BUTTONS = """
|
INTERFACE_BUTTONS = """
|
||||||
{% if perms.ipam.add_ipaddress %}
|
{% if perms.ipam.add_ipaddress %}
|
||||||
<a href="{% url 'ipam:ipaddress_add' %}?interface={{ record.pk }}&return_url={% url 'dcim:device_interfaces' pk=device.pk %}" class="btn btn-xs btn-success" title="Add IP address">
|
<a href="{% url 'ipam:ipaddress_add' %}?interface={{ record.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}" class="btn btn-xs btn-success" title="Add IP address">
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i>
|
<i class="mdi mdi-plus-thick" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -177,10 +183,10 @@ INTERFACE_BUTTONS = """
|
|||||||
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span>
|
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-right">
|
<ul class="dropdown-menu dropdown-menu-right">
|
||||||
<li><a href="{% url 'dcim:interface_connect' termination_a_id=record.pk termination_b_type='interface' %}?return_url={% url 'dcim:device_interfaces' pk=device.pk %}">Interface</a></li>
|
<li><a href="{% url 'dcim:interface_connect' termination_a_id=record.pk termination_b_type='interface' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}">Interface</a></li>
|
||||||
<li><a href="{% url 'dcim:interface_connect' termination_a_id=record.pk termination_b_type='front-port' %}?return_url={% url 'dcim:device_interfaces' pk=device.pk %}">Front Port</a></li>
|
<li><a href="{% url 'dcim:interface_connect' termination_a_id=record.pk termination_b_type='front-port' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}">Front Port</a></li>
|
||||||
<li><a href="{% url 'dcim:interface_connect' termination_a_id=record.pk termination_b_type='rear-port' %}?return_url={% url 'dcim:device_interfaces' pk=device.pk %}">Rear Port</a></li>
|
<li><a href="{% url 'dcim:interface_connect' termination_a_id=record.pk termination_b_type='rear-port' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}">Rear Port</a></li>
|
||||||
<li><a href="{% url 'dcim:interface_connect' termination_a_id=record.pk termination_b_type='circuit-termination' %}?return_url={% url 'dcim:device_interfaces' pk=device.pk %}">Circuit Termination</a></li>
|
<li><a href="{% url 'dcim:interface_connect' termination_a_id=record.pk termination_b_type='circuit-termination' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}">Circuit Termination</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -198,12 +204,12 @@ FRONTPORT_BUTTONS = """
|
|||||||
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span>
|
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-right">
|
<ul class="dropdown-menu dropdown-menu-right">
|
||||||
<li><a href="{% url 'dcim:frontport_connect' termination_a_id=record.pk termination_b_type='interface' %}?return_url={% url 'dcim:device_frontports' pk=device.pk %}">Interface</a></li>
|
<li><a href="{% url 'dcim:frontport_connect' termination_a_id=record.pk termination_b_type='interface' %}?return_url={% url 'dcim:device_frontports' pk=object.pk %}">Interface</a></li>
|
||||||
<li><a href="{% url 'dcim:frontport_connect' termination_a_id=record.pk termination_b_type='console-server-port' %}?return_url={% url 'dcim:device_frontports' pk=device.pk %}">Console Server Port</a></li>
|
<li><a href="{% url 'dcim:frontport_connect' termination_a_id=record.pk termination_b_type='console-server-port' %}?return_url={% url 'dcim:device_frontports' pk=object.pk %}">Console Server Port</a></li>
|
||||||
<li><a href="{% url 'dcim:frontport_connect' termination_a_id=record.pk termination_b_type='console-port' %}?return_url={% url 'dcim:device_frontports' pk=device.pk %}">Console Port</a></li>
|
<li><a href="{% url 'dcim:frontport_connect' termination_a_id=record.pk termination_b_type='console-port' %}?return_url={% url 'dcim:device_frontports' pk=object.pk %}">Console Port</a></li>
|
||||||
<li><a href="{% url 'dcim:frontport_connect' termination_a_id=record.pk termination_b_type='front-port' %}?return_url={% url 'dcim:device_frontports' pk=device.pk %}">Front Port</a></li>
|
<li><a href="{% url 'dcim:frontport_connect' termination_a_id=record.pk termination_b_type='front-port' %}?return_url={% url 'dcim:device_frontports' pk=object.pk %}">Front Port</a></li>
|
||||||
<li><a href="{% url 'dcim:frontport_connect' termination_a_id=record.pk termination_b_type='rear-port' %}?return_url={% url 'dcim:device_frontports' pk=device.pk %}">Rear Port</a></li>
|
<li><a href="{% url 'dcim:frontport_connect' termination_a_id=record.pk termination_b_type='rear-port' %}?return_url={% url 'dcim:device_frontports' pk=object.pk %}">Rear Port</a></li>
|
||||||
<li><a href="{% url 'dcim:frontport_connect' termination_a_id=record.pk termination_b_type='circuit-termination' %}?return_url={% url 'dcim:device_frontports' pk=device.pk %}">Circuit Termination</a></li>
|
<li><a href="{% url 'dcim:frontport_connect' termination_a_id=record.pk termination_b_type='circuit-termination' %}?return_url={% url 'dcim:device_frontports' pk=object.pk %}">Circuit Termination</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -221,10 +227,10 @@ REARPORT_BUTTONS = """
|
|||||||
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span>
|
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-right">
|
<ul class="dropdown-menu dropdown-menu-right">
|
||||||
<li><a href="{% url 'dcim:rearport_connect' termination_a_id=record.pk termination_b_type='interface' %}?return_url={% url 'dcim:device_rearports' pk=device.pk %}">Interface</a></li>
|
<li><a href="{% url 'dcim:rearport_connect' termination_a_id=record.pk termination_b_type='interface' %}?return_url={% url 'dcim:device_rearports' pk=object.pk %}">Interface</a></li>
|
||||||
<li><a href="{% url 'dcim:rearport_connect' termination_a_id=record.pk termination_b_type='front-port' %}?return_url={% url 'dcim:device_rearports' pk=device.pk %}">Front Port</a></li>
|
<li><a href="{% url 'dcim:rearport_connect' termination_a_id=record.pk termination_b_type='front-port' %}?return_url={% url 'dcim:device_rearports' pk=object.pk %}">Front Port</a></li>
|
||||||
<li><a href="{% url 'dcim:rearport_connect' termination_a_id=record.pk termination_b_type='rear-port' %}?return_url={% url 'dcim:device_rearports' pk=device.pk %}">Rear Port</a></li>
|
<li><a href="{% url 'dcim:rearport_connect' termination_a_id=record.pk termination_b_type='rear-port' %}?return_url={% url 'dcim:device_rearports' pk=object.pk %}">Rear Port</a></li>
|
||||||
<li><a href="{% url 'dcim:rearport_connect' termination_a_id=record.pk termination_b_type='circuit-termination' %}?return_url={% url 'dcim:device_rearports' pk=device.pk %}">Circuit Termination</a></li>
|
<li><a href="{% url 'dcim:rearport_connect' termination_a_id=record.pk termination_b_type='circuit-termination' %}?return_url={% url 'dcim:device_rearports' pk=object.pk %}">Circuit Termination</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -233,11 +239,11 @@ REARPORT_BUTTONS = """
|
|||||||
DEVICEBAY_BUTTONS = """
|
DEVICEBAY_BUTTONS = """
|
||||||
{% if perms.dcim.change_devicebay %}
|
{% if perms.dcim.change_devicebay %}
|
||||||
{% if record.installed_device %}
|
{% if record.installed_device %}
|
||||||
<a href="{% url 'dcim:devicebay_depopulate' pk=record.pk %}?return_url={% url 'dcim:device_devicebays' pk=device.pk %}" class="btn btn-danger btn-xs">
|
<a href="{% url 'dcim:devicebay_depopulate' pk=record.pk %}?return_url={% url 'dcim:device_devicebays' pk=object.pk %}" class="btn btn-danger btn-xs">
|
||||||
<i class="mdi mdi-minus-thick" aria-hidden="true" title="Remove device"></i>
|
<i class="mdi mdi-minus-thick" aria-hidden="true" title="Remove device"></i>
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{% url 'dcim:devicebay_populate' pk=record.pk %}?return_url={% url 'dcim:device_devicebays' pk=device.pk %}" class="btn btn-success btn-xs">
|
<a href="{% url 'dcim:devicebay_populate' pk=record.pk %}?return_url={% url 'dcim:device_devicebays' pk=object.pk %}" class="btn btn-success btn-xs">
|
||||||
<i class="mdi mdi-plus-thick" aria-hidden="true" title="Install device"></i>
|
<i class="mdi mdi-plus-thick" aria-hidden="true" title="Install device"></i>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -796,6 +796,30 @@ class CablePathTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
self.assertEqual(CablePath.objects.count(), 2)
|
self.assertEqual(CablePath.objects.count(), 2)
|
||||||
|
|
||||||
|
def test_207_rearport_without_frontport(self):
|
||||||
|
"""
|
||||||
|
[IF1] --C1-- [FP1] [RP1] --C2-- [RP2]
|
||||||
|
"""
|
||||||
|
interface1 = Interface.objects.create(device=self.device, name='Interface 1')
|
||||||
|
rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=1)
|
||||||
|
rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2', positions=1)
|
||||||
|
frontport1 = FrontPort.objects.create(
|
||||||
|
device=self.device, name='Front Port 1', rear_port=rearport1, rear_port_position=1
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create cables
|
||||||
|
cable1 = Cable(termination_a=interface1, termination_b=frontport1)
|
||||||
|
cable1.save()
|
||||||
|
cable2 = Cable(termination_a=rearport1, termination_b=rearport2)
|
||||||
|
cable2.save()
|
||||||
|
self.assertPathExists(
|
||||||
|
origin=interface1,
|
||||||
|
destination=None,
|
||||||
|
path=(cable1, frontport1, rearport1, cable2, rearport2),
|
||||||
|
is_active=False
|
||||||
|
)
|
||||||
|
self.assertEqual(CablePath.objects.count(), 1)
|
||||||
|
|
||||||
def test_301_create_path_via_existing_cable(self):
|
def test_301_create_path_via_existing_cable(self):
|
||||||
"""
|
"""
|
||||||
[IF1] --C1-- [FP1] [RP2] --C2-- [RP2] [FP2] --C3-- [IF2]
|
[IF1] --C1-- [FP1] [RP2] --C2-- [RP2] [FP2] --C3-- [IF2]
|
||||||
|
|||||||
@@ -997,6 +997,130 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
'status': DeviceStatusChoices.STATUS_DECOMMISSIONING,
|
'status': DeviceStatusChoices.STATUS_DECOMMISSIONING,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||||
|
def test_device_consoleports(self):
|
||||||
|
device = Device.objects.first()
|
||||||
|
console_ports = (
|
||||||
|
ConsolePort(device=device, name='Console Port 1'),
|
||||||
|
ConsolePort(device=device, name='Console Port 2'),
|
||||||
|
ConsolePort(device=device, name='Console Port 3'),
|
||||||
|
)
|
||||||
|
ConsolePort.objects.bulk_create(console_ports)
|
||||||
|
|
||||||
|
url = reverse('dcim:device_consoleports', kwargs={'pk': device.pk})
|
||||||
|
self.assertHttpStatus(self.client.get(url), 200)
|
||||||
|
|
||||||
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||||
|
def test_device_consoleserverports(self):
|
||||||
|
device = Device.objects.first()
|
||||||
|
console_server_ports = (
|
||||||
|
ConsoleServerPort(device=device, name='Console Server Port 1'),
|
||||||
|
ConsoleServerPort(device=device, name='Console Server Port 2'),
|
||||||
|
ConsoleServerPort(device=device, name='Console Server Port 3'),
|
||||||
|
)
|
||||||
|
ConsoleServerPort.objects.bulk_create(console_server_ports)
|
||||||
|
|
||||||
|
url = reverse('dcim:device_consoleserverports', kwargs={'pk': device.pk})
|
||||||
|
self.assertHttpStatus(self.client.get(url), 200)
|
||||||
|
|
||||||
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||||
|
def test_device_powerports(self):
|
||||||
|
device = Device.objects.first()
|
||||||
|
power_ports = (
|
||||||
|
PowerPort(device=device, name='Power Port 1'),
|
||||||
|
PowerPort(device=device, name='Power Port 2'),
|
||||||
|
PowerPort(device=device, name='Power Port 3'),
|
||||||
|
)
|
||||||
|
PowerPort.objects.bulk_create(power_ports)
|
||||||
|
|
||||||
|
url = reverse('dcim:device_powerports', kwargs={'pk': device.pk})
|
||||||
|
self.assertHttpStatus(self.client.get(url), 200)
|
||||||
|
|
||||||
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||||
|
def test_device_poweroutlets(self):
|
||||||
|
device = Device.objects.first()
|
||||||
|
power_outlets = (
|
||||||
|
PowerOutlet(device=device, name='Power Outlet 1'),
|
||||||
|
PowerOutlet(device=device, name='Power Outlet 2'),
|
||||||
|
PowerOutlet(device=device, name='Power Outlet 3'),
|
||||||
|
)
|
||||||
|
PowerOutlet.objects.bulk_create(power_outlets)
|
||||||
|
|
||||||
|
url = reverse('dcim:device_poweroutlets', kwargs={'pk': device.pk})
|
||||||
|
self.assertHttpStatus(self.client.get(url), 200)
|
||||||
|
|
||||||
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||||
|
def test_device_interfaces(self):
|
||||||
|
device = Device.objects.first()
|
||||||
|
interfaces = (
|
||||||
|
Interface(device=device, name='Interface 1'),
|
||||||
|
Interface(device=device, name='Interface 2'),
|
||||||
|
Interface(device=device, name='Interface 3'),
|
||||||
|
)
|
||||||
|
Interface.objects.bulk_create(interfaces)
|
||||||
|
|
||||||
|
url = reverse('dcim:device_interfaces', kwargs={'pk': device.pk})
|
||||||
|
self.assertHttpStatus(self.client.get(url), 200)
|
||||||
|
|
||||||
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||||
|
def test_device_rearports(self):
|
||||||
|
device = Device.objects.first()
|
||||||
|
rear_ports = (
|
||||||
|
RearPort(device=device, name='Rear Port 1'),
|
||||||
|
RearPort(device=device, name='Rear Port 2'),
|
||||||
|
RearPort(device=device, name='Rear Port 3'),
|
||||||
|
)
|
||||||
|
RearPort.objects.bulk_create(rear_ports)
|
||||||
|
|
||||||
|
url = reverse('dcim:device_rearports', kwargs={'pk': device.pk})
|
||||||
|
self.assertHttpStatus(self.client.get(url), 200)
|
||||||
|
|
||||||
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||||
|
def test_device_frontports(self):
|
||||||
|
device = Device.objects.first()
|
||||||
|
rear_ports = (
|
||||||
|
RearPort(device=device, name='Rear Port 1'),
|
||||||
|
RearPort(device=device, name='Rear Port 2'),
|
||||||
|
RearPort(device=device, name='Rear Port 3'),
|
||||||
|
)
|
||||||
|
RearPort.objects.bulk_create(rear_ports)
|
||||||
|
front_ports = (
|
||||||
|
FrontPort(device=device, name='Front Port 1', rear_port=rear_ports[0], rear_port_position=1),
|
||||||
|
FrontPort(device=device, name='Front Port 2', rear_port=rear_ports[1], rear_port_position=1),
|
||||||
|
FrontPort(device=device, name='Front Port 3', rear_port=rear_ports[2], rear_port_position=1),
|
||||||
|
)
|
||||||
|
FrontPort.objects.bulk_create(front_ports)
|
||||||
|
|
||||||
|
url = reverse('dcim:device_frontports', kwargs={'pk': device.pk})
|
||||||
|
self.assertHttpStatus(self.client.get(url), 200)
|
||||||
|
|
||||||
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||||
|
def test_device_devicebays(self):
|
||||||
|
device = Device.objects.first()
|
||||||
|
device_bays = (
|
||||||
|
DeviceBay(device=device, name='Device Bay 1'),
|
||||||
|
DeviceBay(device=device, name='Device Bay 2'),
|
||||||
|
DeviceBay(device=device, name='Device Bay 3'),
|
||||||
|
)
|
||||||
|
DeviceBay.objects.bulk_create(device_bays)
|
||||||
|
|
||||||
|
url = reverse('dcim:device_devicebays', kwargs={'pk': device.pk})
|
||||||
|
self.assertHttpStatus(self.client.get(url), 200)
|
||||||
|
|
||||||
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||||
|
def test_device_inventory(self):
|
||||||
|
device = Device.objects.first()
|
||||||
|
inventory_items = (
|
||||||
|
InventoryItem(device=device, name='Inventory Item 1'),
|
||||||
|
InventoryItem(device=device, name='Inventory Item 2'),
|
||||||
|
InventoryItem(device=device, name='Inventory Item 3'),
|
||||||
|
)
|
||||||
|
for item in inventory_items:
|
||||||
|
item.save()
|
||||||
|
|
||||||
|
url = reverse('dcim:device_inventory', kwargs={'pk': device.pk})
|
||||||
|
self.assertHttpStatus(self.client.get(url), 200)
|
||||||
|
|
||||||
|
|
||||||
class ConsolePortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
class ConsolePortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||||
model = ConsolePort
|
model = ConsolePort
|
||||||
@@ -1544,9 +1668,9 @@ class CableTestCase(
|
|||||||
|
|
||||||
cls.csv_data = (
|
cls.csv_data = (
|
||||||
"side_a_device,side_a_type,side_a_name,side_b_device,side_b_type,side_b_name",
|
"side_a_device,side_a_type,side_a_name,side_b_device,side_b_type,side_b_name",
|
||||||
"Device 3,interface,Interface 1,Device 4,interface,Interface 1",
|
"Device 3,dcim.interface,Interface 1,Device 4,dcim.interface,Interface 1",
|
||||||
"Device 3,interface,Interface 2,Device 4,interface,Interface 2",
|
"Device 3,dcim.interface,Interface 2,Device 4,dcim.interface,Interface 2",
|
||||||
"Device 3,interface,Interface 3,Device 4,interface,Interface 3",
|
"Device 3,dcim.interface,Interface 3,Device 4,dcim.interface,Interface 3",
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.bulk_edit_data = {
|
cls.bulk_edit_data = {
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ urlpatterns = [
|
|||||||
path('devices/<int:pk>/device-bays/', views.DeviceDeviceBaysView.as_view(), name='device_devicebays'),
|
path('devices/<int:pk>/device-bays/', views.DeviceDeviceBaysView.as_view(), name='device_devicebays'),
|
||||||
path('devices/<int:pk>/inventory/', views.DeviceInventoryView.as_view(), name='device_inventory'),
|
path('devices/<int:pk>/inventory/', views.DeviceInventoryView.as_view(), name='device_inventory'),
|
||||||
path('devices/<int:pk>/config-context/', views.DeviceConfigContextView.as_view(), name='device_configcontext'),
|
path('devices/<int:pk>/config-context/', views.DeviceConfigContextView.as_view(), name='device_configcontext'),
|
||||||
path('devices/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='device_changelog', kwargs={'model': Device}),
|
path('devices/<int:pk>/changelog/', views.DeviceChangeLogView.as_view(), name='device_changelog', kwargs={'model': Device}),
|
||||||
path('devices/<int:pk>/status/', views.DeviceStatusView.as_view(), name='device_status'),
|
path('devices/<int:pk>/status/', views.DeviceStatusView.as_view(), name='device_status'),
|
||||||
path('devices/<int:pk>/lldp-neighbors/', views.DeviceLLDPNeighborsView.as_view(), name='device_lldp_neighbors'),
|
path('devices/<int:pk>/lldp-neighbors/', views.DeviceLLDPNeighborsView.as_view(), name='device_lldp_neighbors'),
|
||||||
path('devices/<int:pk>/config/', views.DeviceConfigView.as_view(), name='device_config'),
|
path('devices/<int:pk>/config/', views.DeviceConfigView.as_view(), name='device_config'),
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from django.contrib import messages
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.paginator import EmptyPage, PageNotAnInteger
|
from django.core.paginator import EmptyPage, PageNotAnInteger
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import Count, F, Prefetch
|
from django.db.models import F, Prefetch
|
||||||
from django.forms import ModelMultipleChoiceField, MultipleHiddenInput, modelformset_factory
|
from django.forms import ModelMultipleChoiceField, MultipleHiddenInput, modelformset_factory
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
@@ -12,7 +12,7 @@ from django.utils.safestring import mark_safe
|
|||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
|
|
||||||
from circuits.models import Circuit
|
from circuits.models import Circuit
|
||||||
from extras.views import ObjectConfigContextView
|
from extras.views import ObjectChangeLogView, ObjectConfigContextView
|
||||||
from ipam.models import IPAddress, Prefix, Service, VLAN
|
from ipam.models import IPAddress, Prefix, Service, VLAN
|
||||||
from ipam.tables import InterfaceIPAddressTable, InterfaceVLANTable
|
from ipam.tables import InterfaceIPAddressTable, InterfaceVLANTable
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
@@ -152,16 +152,14 @@ class SiteListView(generic.ObjectListView):
|
|||||||
class SiteView(generic.ObjectView):
|
class SiteView(generic.ObjectView):
|
||||||
queryset = Site.objects.prefetch_related('region', 'tenant__group')
|
queryset = Site.objects.prefetch_related('region', 'tenant__group')
|
||||||
|
|
||||||
def get(self, request, slug):
|
def get_extra_context(self, request, instance):
|
||||||
|
|
||||||
site = get_object_or_404(self.queryset, slug=slug)
|
|
||||||
stats = {
|
stats = {
|
||||||
'rack_count': Rack.objects.restrict(request.user, 'view').filter(site=site).count(),
|
'rack_count': Rack.objects.restrict(request.user, 'view').filter(site=instance).count(),
|
||||||
'device_count': Device.objects.restrict(request.user, 'view').filter(site=site).count(),
|
'device_count': Device.objects.restrict(request.user, 'view').filter(site=instance).count(),
|
||||||
'prefix_count': Prefix.objects.restrict(request.user, 'view').filter(site=site).count(),
|
'prefix_count': Prefix.objects.restrict(request.user, 'view').filter(site=instance).count(),
|
||||||
'vlan_count': VLAN.objects.restrict(request.user, 'view').filter(site=site).count(),
|
'vlan_count': VLAN.objects.restrict(request.user, 'view').filter(site=instance).count(),
|
||||||
'circuit_count': Circuit.objects.restrict(request.user, 'view').filter(terminations__site=site).count(),
|
'circuit_count': Circuit.objects.restrict(request.user, 'view').filter(terminations__site=instance).count(),
|
||||||
'vm_count': VirtualMachine.objects.restrict(request.user, 'view').filter(cluster__site=site).count(),
|
'vm_count': VirtualMachine.objects.restrict(request.user, 'view').filter(cluster__site=instance).count(),
|
||||||
}
|
}
|
||||||
rack_groups = RackGroup.objects.add_related_count(
|
rack_groups = RackGroup.objects.add_related_count(
|
||||||
RackGroup.objects.all(),
|
RackGroup.objects.all(),
|
||||||
@@ -169,13 +167,12 @@ class SiteView(generic.ObjectView):
|
|||||||
'group',
|
'group',
|
||||||
'rack_count',
|
'rack_count',
|
||||||
cumulative=True
|
cumulative=True
|
||||||
).restrict(request.user, 'view').filter(site=site)
|
).restrict(request.user, 'view').filter(site=instance)
|
||||||
|
|
||||||
return render(request, 'dcim/site.html', {
|
return {
|
||||||
'site': site,
|
|
||||||
'stats': stats,
|
'stats': stats,
|
||||||
'rack_groups': rack_groups,
|
'rack_groups': rack_groups,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class SiteEditView(generic.ObjectEditView):
|
class SiteEditView(generic.ObjectEditView):
|
||||||
@@ -256,7 +253,9 @@ class RackGroupBulkDeleteView(generic.BulkDeleteView):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class RackRoleListView(generic.ObjectListView):
|
class RackRoleListView(generic.ObjectListView):
|
||||||
queryset = RackRole.objects.annotate(rack_count=Count('racks')).order_by(*RackRole._meta.ordering)
|
queryset = RackRole.objects.annotate(
|
||||||
|
rack_count=get_subquery(Rack, 'role')
|
||||||
|
)
|
||||||
table = tables.RackRoleTable
|
table = tables.RackRoleTable
|
||||||
|
|
||||||
|
|
||||||
@@ -276,7 +275,9 @@ class RackRoleBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
|
|
||||||
class RackRoleBulkDeleteView(generic.BulkDeleteView):
|
class RackRoleBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = RackRole.objects.annotate(rack_count=Count('racks')).order_by(*RackRole._meta.ordering)
|
queryset = RackRole.objects.annotate(
|
||||||
|
rack_count=get_subquery(Rack, 'role')
|
||||||
|
)
|
||||||
table = tables.RackRoleTable
|
table = tables.RackRoleTable
|
||||||
|
|
||||||
|
|
||||||
@@ -285,9 +286,11 @@ class RackRoleBulkDeleteView(generic.BulkDeleteView):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class RackListView(generic.ObjectListView):
|
class RackListView(generic.ObjectListView):
|
||||||
queryset = Rack.objects.annotate(
|
queryset = Rack.objects.prefetch_related(
|
||||||
device_count=Count('devices')
|
'site', 'group', 'tenant', 'role', 'devices__device_type'
|
||||||
).order_by(*Rack._meta.ordering)
|
).annotate(
|
||||||
|
device_count=get_subquery(Device, 'rack')
|
||||||
|
)
|
||||||
filterset = filters.RackFilterSet
|
filterset = filters.RackFilterSet
|
||||||
filterset_form = forms.RackFilterForm
|
filterset_form = forms.RackFilterForm
|
||||||
table = tables.RackDetailTable
|
table = tables.RackDetailTable
|
||||||
@@ -338,36 +341,37 @@ class RackElevationListView(generic.ObjectListView):
|
|||||||
class RackView(generic.ObjectView):
|
class RackView(generic.ObjectView):
|
||||||
queryset = Rack.objects.prefetch_related('site__region', 'tenant__group', 'group', 'role')
|
queryset = Rack.objects.prefetch_related('site__region', 'tenant__group', 'group', 'role')
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
rack = get_object_or_404(self.queryset, pk=pk)
|
|
||||||
|
|
||||||
# Get 0U and child devices located within the rack
|
# Get 0U and child devices located within the rack
|
||||||
nonracked_devices = Device.objects.filter(
|
nonracked_devices = Device.objects.filter(
|
||||||
rack=rack,
|
rack=instance,
|
||||||
position__isnull=True
|
position__isnull=True
|
||||||
).prefetch_related('device_type__manufacturer')
|
).prefetch_related('device_type__manufacturer')
|
||||||
|
|
||||||
peer_racks = Rack.objects.restrict(request.user, 'view').filter(site=rack.site)
|
peer_racks = Rack.objects.restrict(request.user, 'view').filter(site=instance.site)
|
||||||
|
|
||||||
if rack.group:
|
if instance.group:
|
||||||
peer_racks = peer_racks.filter(group=rack.group)
|
peer_racks = peer_racks.filter(group=instance.group)
|
||||||
else:
|
else:
|
||||||
peer_racks = peer_racks.filter(group__isnull=True)
|
peer_racks = peer_racks.filter(group__isnull=True)
|
||||||
next_rack = peer_racks.filter(name__gt=rack.name).order_by('name').first()
|
next_rack = peer_racks.filter(name__gt=instance.name).order_by('name').first()
|
||||||
prev_rack = peer_racks.filter(name__lt=rack.name).order_by('-name').first()
|
prev_rack = peer_racks.filter(name__lt=instance.name).order_by('-name').first()
|
||||||
|
|
||||||
reservations = RackReservation.objects.restrict(request.user, 'view').filter(rack=rack)
|
reservations = RackReservation.objects.restrict(request.user, 'view').filter(rack=instance)
|
||||||
power_feeds = PowerFeed.objects.restrict(request.user, 'view').filter(rack=rack).prefetch_related('power_panel')
|
power_feeds = PowerFeed.objects.restrict(request.user, 'view').filter(rack=instance).prefetch_related(
|
||||||
|
'power_panel'
|
||||||
|
)
|
||||||
|
|
||||||
return render(request, 'dcim/rack.html', {
|
device_count = Device.objects.restrict(request.user, 'view').filter(rack=instance).count()
|
||||||
'rack': rack,
|
|
||||||
'device_count': Device.objects.restrict(request.user, 'view').filter(rack=rack).count(),
|
return {
|
||||||
|
'device_count': device_count,
|
||||||
'reservations': reservations,
|
'reservations': reservations,
|
||||||
'power_feeds': power_feeds,
|
'power_feeds': power_feeds,
|
||||||
'nonracked_devices': nonracked_devices,
|
'nonracked_devices': nonracked_devices,
|
||||||
'next_rack': next_rack,
|
'next_rack': next_rack,
|
||||||
'prev_rack': prev_rack,
|
'prev_rack': prev_rack,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class RackEditView(generic.ObjectEditView):
|
class RackEditView(generic.ObjectEditView):
|
||||||
@@ -413,14 +417,6 @@ class RackReservationListView(generic.ObjectListView):
|
|||||||
class RackReservationView(generic.ObjectView):
|
class RackReservationView(generic.ObjectView):
|
||||||
queryset = RackReservation.objects.prefetch_related('rack')
|
queryset = RackReservation.objects.prefetch_related('rack')
|
||||||
|
|
||||||
def get(self, request, pk):
|
|
||||||
|
|
||||||
rackreservation = get_object_or_404(self.queryset, pk=pk)
|
|
||||||
|
|
||||||
return render(request, 'dcim/rackreservation.html', {
|
|
||||||
'rackreservation': rackreservation,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class RackReservationEditView(generic.ObjectEditView):
|
class RackReservationEditView(generic.ObjectEditView):
|
||||||
queryset = RackReservation.objects.all()
|
queryset = RackReservation.objects.all()
|
||||||
@@ -498,8 +494,8 @@ class ManufacturerBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class ManufacturerBulkDeleteView(generic.BulkDeleteView):
|
class ManufacturerBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Manufacturer.objects.annotate(
|
queryset = Manufacturer.objects.annotate(
|
||||||
devicetype_count=Count('device_types')
|
devicetype_count=get_subquery(DeviceType, 'manufacturer')
|
||||||
).order_by(*Manufacturer._meta.ordering)
|
)
|
||||||
table = tables.ManufacturerTable
|
table = tables.ManufacturerTable
|
||||||
|
|
||||||
|
|
||||||
@@ -508,9 +504,9 @@ class ManufacturerBulkDeleteView(generic.BulkDeleteView):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class DeviceTypeListView(generic.ObjectListView):
|
class DeviceTypeListView(generic.ObjectListView):
|
||||||
queryset = DeviceType.objects.annotate(
|
queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
|
||||||
instance_count=Count('instances')
|
instance_count=get_subquery(Device, 'device_type')
|
||||||
).order_by(*DeviceType._meta.ordering)
|
)
|
||||||
filterset = filters.DeviceTypeFilterSet
|
filterset = filters.DeviceTypeFilterSet
|
||||||
filterset_form = forms.DeviceTypeFilterForm
|
filterset_form = forms.DeviceTypeFilterForm
|
||||||
table = tables.DeviceTypeTable
|
table = tables.DeviceTypeTable
|
||||||
@@ -519,42 +515,40 @@ class DeviceTypeListView(generic.ObjectListView):
|
|||||||
class DeviceTypeView(generic.ObjectView):
|
class DeviceTypeView(generic.ObjectView):
|
||||||
queryset = DeviceType.objects.prefetch_related('manufacturer')
|
queryset = DeviceType.objects.prefetch_related('manufacturer')
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
|
instance_count = Device.objects.restrict(request.user).filter(device_type=instance).count()
|
||||||
devicetype = get_object_or_404(self.queryset, pk=pk)
|
|
||||||
instance_count = Device.objects.restrict(request.user).filter(device_type=devicetype).count()
|
|
||||||
|
|
||||||
# Component tables
|
# Component tables
|
||||||
consoleport_table = tables.ConsolePortTemplateTable(
|
consoleport_table = tables.ConsolePortTemplateTable(
|
||||||
ConsolePortTemplate.objects.restrict(request.user, 'view').filter(device_type=devicetype),
|
ConsolePortTemplate.objects.restrict(request.user, 'view').filter(device_type=instance),
|
||||||
orderable=False
|
orderable=False
|
||||||
)
|
)
|
||||||
consoleserverport_table = tables.ConsoleServerPortTemplateTable(
|
consoleserverport_table = tables.ConsoleServerPortTemplateTable(
|
||||||
ConsoleServerPortTemplate.objects.restrict(request.user, 'view').filter(device_type=devicetype),
|
ConsoleServerPortTemplate.objects.restrict(request.user, 'view').filter(device_type=instance),
|
||||||
orderable=False
|
orderable=False
|
||||||
)
|
)
|
||||||
powerport_table = tables.PowerPortTemplateTable(
|
powerport_table = tables.PowerPortTemplateTable(
|
||||||
PowerPortTemplate.objects.restrict(request.user, 'view').filter(device_type=devicetype),
|
PowerPortTemplate.objects.restrict(request.user, 'view').filter(device_type=instance),
|
||||||
orderable=False
|
orderable=False
|
||||||
)
|
)
|
||||||
poweroutlet_table = tables.PowerOutletTemplateTable(
|
poweroutlet_table = tables.PowerOutletTemplateTable(
|
||||||
PowerOutletTemplate.objects.restrict(request.user, 'view').filter(device_type=devicetype),
|
PowerOutletTemplate.objects.restrict(request.user, 'view').filter(device_type=instance),
|
||||||
orderable=False
|
orderable=False
|
||||||
)
|
)
|
||||||
interface_table = tables.InterfaceTemplateTable(
|
interface_table = tables.InterfaceTemplateTable(
|
||||||
list(InterfaceTemplate.objects.restrict(request.user, 'view').filter(device_type=devicetype)),
|
list(InterfaceTemplate.objects.restrict(request.user, 'view').filter(device_type=instance)),
|
||||||
orderable=False
|
orderable=False
|
||||||
)
|
)
|
||||||
front_port_table = tables.FrontPortTemplateTable(
|
front_port_table = tables.FrontPortTemplateTable(
|
||||||
FrontPortTemplate.objects.restrict(request.user, 'view').filter(device_type=devicetype),
|
FrontPortTemplate.objects.restrict(request.user, 'view').filter(device_type=instance),
|
||||||
orderable=False
|
orderable=False
|
||||||
)
|
)
|
||||||
rear_port_table = tables.RearPortTemplateTable(
|
rear_port_table = tables.RearPortTemplateTable(
|
||||||
RearPortTemplate.objects.restrict(request.user, 'view').filter(device_type=devicetype),
|
RearPortTemplate.objects.restrict(request.user, 'view').filter(device_type=instance),
|
||||||
orderable=False
|
orderable=False
|
||||||
)
|
)
|
||||||
devicebay_table = tables.DeviceBayTemplateTable(
|
devicebay_table = tables.DeviceBayTemplateTable(
|
||||||
DeviceBayTemplate.objects.restrict(request.user, 'view').filter(device_type=devicetype),
|
DeviceBayTemplate.objects.restrict(request.user, 'view').filter(device_type=instance),
|
||||||
orderable=False
|
orderable=False
|
||||||
)
|
)
|
||||||
if request.user.has_perm('dcim.change_devicetype'):
|
if request.user.has_perm('dcim.change_devicetype'):
|
||||||
@@ -567,8 +561,7 @@ class DeviceTypeView(generic.ObjectView):
|
|||||||
rear_port_table.columns.show('pk')
|
rear_port_table.columns.show('pk')
|
||||||
devicebay_table.columns.show('pk')
|
devicebay_table.columns.show('pk')
|
||||||
|
|
||||||
return render(request, 'dcim/devicetype.html', {
|
return {
|
||||||
'devicetype': devicetype,
|
|
||||||
'instance_count': instance_count,
|
'instance_count': instance_count,
|
||||||
'consoleport_table': consoleport_table,
|
'consoleport_table': consoleport_table,
|
||||||
'consoleserverport_table': consoleserverport_table,
|
'consoleserverport_table': consoleserverport_table,
|
||||||
@@ -578,7 +571,7 @@ class DeviceTypeView(generic.ObjectView):
|
|||||||
'front_port_table': front_port_table,
|
'front_port_table': front_port_table,
|
||||||
'rear_port_table': rear_port_table,
|
'rear_port_table': rear_port_table,
|
||||||
'devicebay_table': devicebay_table,
|
'devicebay_table': devicebay_table,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class DeviceTypeEditView(generic.ObjectEditView):
|
class DeviceTypeEditView(generic.ObjectEditView):
|
||||||
@@ -619,8 +612,8 @@ class DeviceTypeImportView(generic.ObjectImportView):
|
|||||||
|
|
||||||
class DeviceTypeBulkEditView(generic.BulkEditView):
|
class DeviceTypeBulkEditView(generic.BulkEditView):
|
||||||
queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
|
queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
|
||||||
instance_count=Count('instances')
|
instance_count=get_subquery(Device, 'device_type')
|
||||||
).order_by(*DeviceType._meta.ordering)
|
)
|
||||||
filterset = filters.DeviceTypeFilterSet
|
filterset = filters.DeviceTypeFilterSet
|
||||||
table = tables.DeviceTypeTable
|
table = tables.DeviceTypeTable
|
||||||
form = forms.DeviceTypeBulkEditForm
|
form = forms.DeviceTypeBulkEditForm
|
||||||
@@ -628,8 +621,8 @@ class DeviceTypeBulkEditView(generic.BulkEditView):
|
|||||||
|
|
||||||
class DeviceTypeBulkDeleteView(generic.BulkDeleteView):
|
class DeviceTypeBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
|
queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
|
||||||
instance_count=Count('instances')
|
instance_count=get_subquery(Device, 'device_type')
|
||||||
).order_by(*DeviceType._meta.ordering)
|
)
|
||||||
filterset = filters.DeviceTypeFilterSet
|
filterset = filters.DeviceTypeFilterSet
|
||||||
table = tables.DeviceTypeTable
|
table = tables.DeviceTypeTable
|
||||||
|
|
||||||
@@ -995,50 +988,45 @@ class DeviceView(generic.ObjectView):
|
|||||||
'site__region', 'rack__group', 'tenant__group', 'device_role', 'platform', 'primary_ip4', 'primary_ip6'
|
'site__region', 'rack__group', 'tenant__group', 'device_role', 'platform', 'primary_ip4', 'primary_ip6'
|
||||||
)
|
)
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
|
|
||||||
device = get_object_or_404(self.queryset, pk=pk)
|
|
||||||
|
|
||||||
# VirtualChassis members
|
# VirtualChassis members
|
||||||
if device.virtual_chassis is not None:
|
if instance.virtual_chassis is not None:
|
||||||
vc_members = Device.objects.restrict(request.user, 'view').filter(
|
vc_members = Device.objects.restrict(request.user, 'view').filter(
|
||||||
virtual_chassis=device.virtual_chassis
|
virtual_chassis=instance.virtual_chassis
|
||||||
).order_by('vc_position')
|
).order_by('vc_position')
|
||||||
else:
|
else:
|
||||||
vc_members = []
|
vc_members = []
|
||||||
|
|
||||||
# Services
|
# Services
|
||||||
services = Service.objects.restrict(request.user, 'view').filter(device=device)
|
services = Service.objects.restrict(request.user, 'view').filter(device=instance)
|
||||||
|
|
||||||
# Secrets
|
# Secrets
|
||||||
secrets = Secret.objects.restrict(request.user, 'view').filter(device=device)
|
secrets = Secret.objects.restrict(request.user, 'view').filter(device=instance)
|
||||||
|
|
||||||
# Find up to ten devices in the same site with the same functional role for quick reference.
|
# Find up to ten devices in the same site with the same functional role for quick reference.
|
||||||
related_devices = Device.objects.restrict(request.user, 'view').filter(
|
related_devices = Device.objects.restrict(request.user, 'view').filter(
|
||||||
site=device.site, device_role=device.device_role
|
site=instance.site, device_role=instance.device_role
|
||||||
).exclude(
|
).exclude(
|
||||||
pk=device.pk
|
pk=instance.pk
|
||||||
).prefetch_related(
|
).prefetch_related(
|
||||||
'rack', 'device_type__manufacturer'
|
'rack', 'device_type__manufacturer'
|
||||||
)[:10]
|
)[:10]
|
||||||
|
|
||||||
return render(request, 'dcim/device/device.html', {
|
return {
|
||||||
'device': device,
|
|
||||||
'services': services,
|
'services': services,
|
||||||
'secrets': secrets,
|
'secrets': secrets,
|
||||||
'vc_members': vc_members,
|
'vc_members': vc_members,
|
||||||
'related_devices': related_devices,
|
'related_devices': related_devices,
|
||||||
'active_tab': 'device',
|
'active_tab': 'device',
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class DeviceConsolePortsView(generic.ObjectView):
|
class DeviceConsolePortsView(generic.ObjectView):
|
||||||
queryset = Device.objects.all()
|
queryset = Device.objects.all()
|
||||||
|
template_name = 'dcim/device/consoleports.html'
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
device = get_object_or_404(self.queryset, pk=pk)
|
consoleports = ConsolePort.objects.restrict(request.user, 'view').filter(device=instance).prefetch_related(
|
||||||
|
|
||||||
consoleports = ConsolePort.objects.restrict(request.user, 'view').filter(device=device).prefetch_related(
|
|
||||||
'cable', '_path__destination',
|
'cable', '_path__destination',
|
||||||
)
|
)
|
||||||
consoleport_table = tables.DeviceConsolePortTable(
|
consoleport_table = tables.DeviceConsolePortTable(
|
||||||
@@ -1049,21 +1037,19 @@ class DeviceConsolePortsView(generic.ObjectView):
|
|||||||
if request.user.has_perm('dcim.change_consoleport') or request.user.has_perm('dcim.delete_consoleport'):
|
if request.user.has_perm('dcim.change_consoleport') or request.user.has_perm('dcim.delete_consoleport'):
|
||||||
consoleport_table.columns.show('pk')
|
consoleport_table.columns.show('pk')
|
||||||
|
|
||||||
return render(request, 'dcim/device/consoleports.html', {
|
return {
|
||||||
'device': device,
|
|
||||||
'consoleport_table': consoleport_table,
|
'consoleport_table': consoleport_table,
|
||||||
'active_tab': 'console-ports',
|
'active_tab': 'console-ports',
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class DeviceConsoleServerPortsView(generic.ObjectView):
|
class DeviceConsoleServerPortsView(generic.ObjectView):
|
||||||
queryset = Device.objects.all()
|
queryset = Device.objects.all()
|
||||||
|
template_name = 'dcim/device/consoleserverports.html'
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
device = get_object_or_404(self.queryset, pk=pk)
|
|
||||||
|
|
||||||
consoleserverports = ConsoleServerPort.objects.restrict(request.user, 'view').filter(
|
consoleserverports = ConsoleServerPort.objects.restrict(request.user, 'view').filter(
|
||||||
device=device
|
device=instance
|
||||||
).prefetch_related(
|
).prefetch_related(
|
||||||
'cable', '_path__destination',
|
'cable', '_path__destination',
|
||||||
)
|
)
|
||||||
@@ -1076,20 +1062,18 @@ class DeviceConsoleServerPortsView(generic.ObjectView):
|
|||||||
request.user.has_perm('dcim.delete_consoleserverport'):
|
request.user.has_perm('dcim.delete_consoleserverport'):
|
||||||
consoleserverport_table.columns.show('pk')
|
consoleserverport_table.columns.show('pk')
|
||||||
|
|
||||||
return render(request, 'dcim/device/consoleserverports.html', {
|
return {
|
||||||
'device': device,
|
|
||||||
'consoleserverport_table': consoleserverport_table,
|
'consoleserverport_table': consoleserverport_table,
|
||||||
'active_tab': 'console-server-ports',
|
'active_tab': 'console-server-ports',
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class DevicePowerPortsView(generic.ObjectView):
|
class DevicePowerPortsView(generic.ObjectView):
|
||||||
queryset = Device.objects.all()
|
queryset = Device.objects.all()
|
||||||
|
template_name = 'dcim/device/powerports.html'
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
device = get_object_or_404(self.queryset, pk=pk)
|
powerports = PowerPort.objects.restrict(request.user, 'view').filter(device=instance).prefetch_related(
|
||||||
|
|
||||||
powerports = PowerPort.objects.restrict(request.user, 'view').filter(device=device).prefetch_related(
|
|
||||||
'cable', '_path__destination',
|
'cable', '_path__destination',
|
||||||
)
|
)
|
||||||
powerport_table = tables.DevicePowerPortTable(
|
powerport_table = tables.DevicePowerPortTable(
|
||||||
@@ -1100,20 +1084,18 @@ class DevicePowerPortsView(generic.ObjectView):
|
|||||||
if request.user.has_perm('dcim.change_powerport') or request.user.has_perm('dcim.delete_powerport'):
|
if request.user.has_perm('dcim.change_powerport') or request.user.has_perm('dcim.delete_powerport'):
|
||||||
powerport_table.columns.show('pk')
|
powerport_table.columns.show('pk')
|
||||||
|
|
||||||
return render(request, 'dcim/device/powerports.html', {
|
return {
|
||||||
'device': device,
|
|
||||||
'powerport_table': powerport_table,
|
'powerport_table': powerport_table,
|
||||||
'active_tab': 'power-ports',
|
'active_tab': 'power-ports',
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class DevicePowerOutletsView(generic.ObjectView):
|
class DevicePowerOutletsView(generic.ObjectView):
|
||||||
queryset = Device.objects.all()
|
queryset = Device.objects.all()
|
||||||
|
template_name = 'dcim/device/poweroutlets.html'
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
device = get_object_or_404(self.queryset, pk=pk)
|
poweroutlets = PowerOutlet.objects.restrict(request.user, 'view').filter(device=instance).prefetch_related(
|
||||||
|
|
||||||
poweroutlets = PowerOutlet.objects.restrict(request.user, 'view').filter(device=device).prefetch_related(
|
|
||||||
'cable', 'power_port', '_path__destination',
|
'cable', 'power_port', '_path__destination',
|
||||||
)
|
)
|
||||||
poweroutlet_table = tables.DevicePowerOutletTable(
|
poweroutlet_table = tables.DevicePowerOutletTable(
|
||||||
@@ -1124,20 +1106,18 @@ class DevicePowerOutletsView(generic.ObjectView):
|
|||||||
if request.user.has_perm('dcim.change_poweroutlet') or request.user.has_perm('dcim.delete_poweroutlet'):
|
if request.user.has_perm('dcim.change_poweroutlet') or request.user.has_perm('dcim.delete_poweroutlet'):
|
||||||
poweroutlet_table.columns.show('pk')
|
poweroutlet_table.columns.show('pk')
|
||||||
|
|
||||||
return render(request, 'dcim/device/poweroutlets.html', {
|
return {
|
||||||
'device': device,
|
|
||||||
'poweroutlet_table': poweroutlet_table,
|
'poweroutlet_table': poweroutlet_table,
|
||||||
'active_tab': 'power-outlets',
|
'active_tab': 'power-outlets',
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class DeviceInterfacesView(generic.ObjectView):
|
class DeviceInterfacesView(generic.ObjectView):
|
||||||
queryset = Device.objects.all()
|
queryset = Device.objects.all()
|
||||||
|
template_name = 'dcim/device/interfaces.html'
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
device = get_object_or_404(self.queryset, pk=pk)
|
interfaces = instance.vc_interfaces.restrict(request.user, 'view').prefetch_related(
|
||||||
|
|
||||||
interfaces = device.vc_interfaces.restrict(request.user, 'view').prefetch_related(
|
|
||||||
Prefetch('ip_addresses', queryset=IPAddress.objects.restrict(request.user)),
|
Prefetch('ip_addresses', queryset=IPAddress.objects.restrict(request.user)),
|
||||||
Prefetch('member_interfaces', queryset=Interface.objects.restrict(request.user)),
|
Prefetch('member_interfaces', queryset=Interface.objects.restrict(request.user)),
|
||||||
'lag', 'cable', '_path__destination', 'tags',
|
'lag', 'cable', '_path__destination', 'tags',
|
||||||
@@ -1150,20 +1130,18 @@ class DeviceInterfacesView(generic.ObjectView):
|
|||||||
if request.user.has_perm('dcim.change_interface') or request.user.has_perm('dcim.delete_interface'):
|
if request.user.has_perm('dcim.change_interface') or request.user.has_perm('dcim.delete_interface'):
|
||||||
interface_table.columns.show('pk')
|
interface_table.columns.show('pk')
|
||||||
|
|
||||||
return render(request, 'dcim/device/interfaces.html', {
|
return {
|
||||||
'device': device,
|
|
||||||
'interface_table': interface_table,
|
'interface_table': interface_table,
|
||||||
'active_tab': 'interfaces',
|
'active_tab': 'interfaces',
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class DeviceFrontPortsView(generic.ObjectView):
|
class DeviceFrontPortsView(generic.ObjectView):
|
||||||
queryset = Device.objects.all()
|
queryset = Device.objects.all()
|
||||||
|
template_name = 'dcim/device/frontports.html'
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
device = get_object_or_404(self.queryset, pk=pk)
|
frontports = FrontPort.objects.restrict(request.user, 'view').filter(device=instance).prefetch_related(
|
||||||
|
|
||||||
frontports = FrontPort.objects.restrict(request.user, 'view').filter(device=device).prefetch_related(
|
|
||||||
'rear_port', 'cable',
|
'rear_port', 'cable',
|
||||||
)
|
)
|
||||||
frontport_table = tables.DeviceFrontPortTable(
|
frontport_table = tables.DeviceFrontPortTable(
|
||||||
@@ -1174,20 +1152,18 @@ class DeviceFrontPortsView(generic.ObjectView):
|
|||||||
if request.user.has_perm('dcim.change_frontport') or request.user.has_perm('dcim.delete_frontport'):
|
if request.user.has_perm('dcim.change_frontport') or request.user.has_perm('dcim.delete_frontport'):
|
||||||
frontport_table.columns.show('pk')
|
frontport_table.columns.show('pk')
|
||||||
|
|
||||||
return render(request, 'dcim/device/frontports.html', {
|
return {
|
||||||
'device': device,
|
|
||||||
'frontport_table': frontport_table,
|
'frontport_table': frontport_table,
|
||||||
'active_tab': 'front-ports',
|
'active_tab': 'front-ports',
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class DeviceRearPortsView(generic.ObjectView):
|
class DeviceRearPortsView(generic.ObjectView):
|
||||||
queryset = Device.objects.all()
|
queryset = Device.objects.all()
|
||||||
|
template_name = 'dcim/device/rearports.html'
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
device = get_object_or_404(self.queryset, pk=pk)
|
rearports = RearPort.objects.restrict(request.user, 'view').filter(device=instance).prefetch_related('cable')
|
||||||
|
|
||||||
rearports = RearPort.objects.restrict(request.user, 'view').filter(device=device).prefetch_related('cable')
|
|
||||||
rearport_table = tables.DeviceRearPortTable(
|
rearport_table = tables.DeviceRearPortTable(
|
||||||
data=rearports,
|
data=rearports,
|
||||||
user=request.user,
|
user=request.user,
|
||||||
@@ -1196,20 +1172,18 @@ class DeviceRearPortsView(generic.ObjectView):
|
|||||||
if request.user.has_perm('dcim.change_rearport') or request.user.has_perm('dcim.delete_rearport'):
|
if request.user.has_perm('dcim.change_rearport') or request.user.has_perm('dcim.delete_rearport'):
|
||||||
rearport_table.columns.show('pk')
|
rearport_table.columns.show('pk')
|
||||||
|
|
||||||
return render(request, 'dcim/device/rearports.html', {
|
return {
|
||||||
'device': device,
|
|
||||||
'rearport_table': rearport_table,
|
'rearport_table': rearport_table,
|
||||||
'active_tab': 'rear-ports',
|
'active_tab': 'rear-ports',
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class DeviceDeviceBaysView(generic.ObjectView):
|
class DeviceDeviceBaysView(generic.ObjectView):
|
||||||
queryset = Device.objects.all()
|
queryset = Device.objects.all()
|
||||||
|
template_name = 'dcim/device/devicebays.html'
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
device = get_object_or_404(self.queryset, pk=pk)
|
devicebays = DeviceBay.objects.restrict(request.user, 'view').filter(device=instance).prefetch_related(
|
||||||
|
|
||||||
devicebays = DeviceBay.objects.restrict(request.user, 'view').filter(device=device).prefetch_related(
|
|
||||||
'installed_device__device_type__manufacturer',
|
'installed_device__device_type__manufacturer',
|
||||||
)
|
)
|
||||||
devicebay_table = tables.DeviceDeviceBayTable(
|
devicebay_table = tables.DeviceDeviceBayTable(
|
||||||
@@ -1220,21 +1194,19 @@ class DeviceDeviceBaysView(generic.ObjectView):
|
|||||||
if request.user.has_perm('dcim.change_devicebay') or request.user.has_perm('dcim.delete_devicebay'):
|
if request.user.has_perm('dcim.change_devicebay') or request.user.has_perm('dcim.delete_devicebay'):
|
||||||
devicebay_table.columns.show('pk')
|
devicebay_table.columns.show('pk')
|
||||||
|
|
||||||
return render(request, 'dcim/device/devicebays.html', {
|
return {
|
||||||
'device': device,
|
|
||||||
'devicebay_table': devicebay_table,
|
'devicebay_table': devicebay_table,
|
||||||
'active_tab': 'device-bays',
|
'active_tab': 'device-bays',
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class DeviceInventoryView(generic.ObjectView):
|
class DeviceInventoryView(generic.ObjectView):
|
||||||
queryset = Device.objects.all()
|
queryset = Device.objects.all()
|
||||||
|
template_name = 'dcim/device/inventory.html'
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
device = get_object_or_404(self.queryset, pk=pk)
|
|
||||||
|
|
||||||
inventoryitems = InventoryItem.objects.restrict(request.user, 'view').filter(
|
inventoryitems = InventoryItem.objects.restrict(request.user, 'view').filter(
|
||||||
device=device
|
device=instance
|
||||||
).prefetch_related('manufacturer')
|
).prefetch_related('manufacturer')
|
||||||
inventoryitem_table = tables.DeviceInventoryItemTable(
|
inventoryitem_table = tables.DeviceInventoryItemTable(
|
||||||
data=inventoryitems,
|
data=inventoryitems,
|
||||||
@@ -1244,56 +1216,50 @@ class DeviceInventoryView(generic.ObjectView):
|
|||||||
if request.user.has_perm('dcim.change_inventoryitem') or request.user.has_perm('dcim.delete_inventoryitem'):
|
if request.user.has_perm('dcim.change_inventoryitem') or request.user.has_perm('dcim.delete_inventoryitem'):
|
||||||
inventoryitem_table.columns.show('pk')
|
inventoryitem_table.columns.show('pk')
|
||||||
|
|
||||||
return render(request, 'dcim/device/inventory.html', {
|
return {
|
||||||
'device': device,
|
|
||||||
'inventoryitem_table': inventoryitem_table,
|
'inventoryitem_table': inventoryitem_table,
|
||||||
'active_tab': 'inventory',
|
'active_tab': 'inventory',
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class DeviceStatusView(generic.ObjectView):
|
class DeviceStatusView(generic.ObjectView):
|
||||||
additional_permissions = ['dcim.napalm_read_device']
|
additional_permissions = ['dcim.napalm_read_device']
|
||||||
queryset = Device.objects.all()
|
queryset = Device.objects.all()
|
||||||
|
template_name = 'dcim/device/status.html'
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
device = get_object_or_404(self.queryset, pk=pk)
|
return {
|
||||||
|
|
||||||
return render(request, 'dcim/device/status.html', {
|
|
||||||
'device': device,
|
|
||||||
'active_tab': 'status',
|
'active_tab': 'status',
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class DeviceLLDPNeighborsView(generic.ObjectView):
|
class DeviceLLDPNeighborsView(generic.ObjectView):
|
||||||
additional_permissions = ['dcim.napalm_read_device']
|
additional_permissions = ['dcim.napalm_read_device']
|
||||||
queryset = Device.objects.all()
|
queryset = Device.objects.all()
|
||||||
|
template_name = 'dcim/device/lldp_neighbors.html'
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
|
interfaces = instance.vc_interfaces.restrict(request.user, 'view').prefetch_related(
|
||||||
device = get_object_or_404(self.queryset, pk=pk)
|
'_path__destination'
|
||||||
interfaces = device.vc_interfaces.restrict(request.user, 'view').prefetch_related('_path__destination').exclude(
|
).exclude(
|
||||||
type__in=NONCONNECTABLE_IFACE_TYPES
|
type__in=NONCONNECTABLE_IFACE_TYPES
|
||||||
)
|
)
|
||||||
|
|
||||||
return render(request, 'dcim/device/lldp_neighbors.html', {
|
return {
|
||||||
'device': device,
|
|
||||||
'interfaces': interfaces,
|
'interfaces': interfaces,
|
||||||
'active_tab': 'lldp-neighbors',
|
'active_tab': 'lldp-neighbors',
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class DeviceConfigView(generic.ObjectView):
|
class DeviceConfigView(generic.ObjectView):
|
||||||
additional_permissions = ['dcim.napalm_read_device']
|
additional_permissions = ['dcim.napalm_read_device']
|
||||||
queryset = Device.objects.all()
|
queryset = Device.objects.all()
|
||||||
|
template_name = 'dcim/device/config.html'
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
|
return {
|
||||||
device = get_object_or_404(self.queryset, pk=pk)
|
|
||||||
|
|
||||||
return render(request, 'dcim/device/config.html', {
|
|
||||||
'device': device,
|
|
||||||
'active_tab': 'config',
|
'active_tab': 'config',
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class DeviceConfigContextView(ObjectConfigContextView):
|
class DeviceConfigContextView(ObjectConfigContextView):
|
||||||
@@ -1301,6 +1267,10 @@ class DeviceConfigContextView(ObjectConfigContextView):
|
|||||||
base_template = 'dcim/device/base.html'
|
base_template = 'dcim/device/base.html'
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceChangeLogView(ObjectChangeLogView):
|
||||||
|
base_template = 'dcim/device/base.html'
|
||||||
|
|
||||||
|
|
||||||
class DeviceEditView(generic.ObjectEditView):
|
class DeviceEditView(generic.ObjectEditView):
|
||||||
queryset = Device.objects.all()
|
queryset = Device.objects.all()
|
||||||
model_form = forms.DeviceForm
|
model_form = forms.DeviceForm
|
||||||
@@ -1364,6 +1334,11 @@ class ConsolePortListView(generic.ObjectListView):
|
|||||||
class ConsolePortView(generic.ObjectView):
|
class ConsolePortView(generic.ObjectView):
|
||||||
queryset = ConsolePort.objects.all()
|
queryset = ConsolePort.objects.all()
|
||||||
|
|
||||||
|
def get_extra_context(self, request, instance):
|
||||||
|
return {
|
||||||
|
'breadcrumb_url': 'dcim:device_consoleports'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class ConsolePortCreateView(generic.ComponentCreateView):
|
class ConsolePortCreateView(generic.ComponentCreateView):
|
||||||
queryset = ConsolePort.objects.all()
|
queryset = ConsolePort.objects.all()
|
||||||
@@ -1424,6 +1399,11 @@ class ConsoleServerPortListView(generic.ObjectListView):
|
|||||||
class ConsoleServerPortView(generic.ObjectView):
|
class ConsoleServerPortView(generic.ObjectView):
|
||||||
queryset = ConsoleServerPort.objects.all()
|
queryset = ConsoleServerPort.objects.all()
|
||||||
|
|
||||||
|
def get_extra_context(self, request, instance):
|
||||||
|
return {
|
||||||
|
'breadcrumb_url': 'dcim:device_consoleserverports'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortCreateView(generic.ComponentCreateView):
|
class ConsoleServerPortCreateView(generic.ComponentCreateView):
|
||||||
queryset = ConsoleServerPort.objects.all()
|
queryset = ConsoleServerPort.objects.all()
|
||||||
@@ -1484,6 +1464,11 @@ class PowerPortListView(generic.ObjectListView):
|
|||||||
class PowerPortView(generic.ObjectView):
|
class PowerPortView(generic.ObjectView):
|
||||||
queryset = PowerPort.objects.all()
|
queryset = PowerPort.objects.all()
|
||||||
|
|
||||||
|
def get_extra_context(self, request, instance):
|
||||||
|
return {
|
||||||
|
'breadcrumb_url': 'dcim:device_powerports'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class PowerPortCreateView(generic.ComponentCreateView):
|
class PowerPortCreateView(generic.ComponentCreateView):
|
||||||
queryset = PowerPort.objects.all()
|
queryset = PowerPort.objects.all()
|
||||||
@@ -1544,6 +1529,11 @@ class PowerOutletListView(generic.ObjectListView):
|
|||||||
class PowerOutletView(generic.ObjectView):
|
class PowerOutletView(generic.ObjectView):
|
||||||
queryset = PowerOutlet.objects.all()
|
queryset = PowerOutlet.objects.all()
|
||||||
|
|
||||||
|
def get_extra_context(self, request, instance):
|
||||||
|
return {
|
||||||
|
'breadcrumb_url': 'dcim:device_poweroutlets'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletCreateView(generic.ComponentCreateView):
|
class PowerOutletCreateView(generic.ComponentCreateView):
|
||||||
queryset = PowerOutlet.objects.all()
|
queryset = PowerOutlet.objects.all()
|
||||||
@@ -1604,35 +1594,32 @@ class InterfaceListView(generic.ObjectListView):
|
|||||||
class InterfaceView(generic.ObjectView):
|
class InterfaceView(generic.ObjectView):
|
||||||
queryset = Interface.objects.all()
|
queryset = Interface.objects.all()
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
|
|
||||||
interface = get_object_or_404(self.queryset, pk=pk)
|
|
||||||
|
|
||||||
# Get assigned IP addresses
|
# Get assigned IP addresses
|
||||||
ipaddress_table = InterfaceIPAddressTable(
|
ipaddress_table = InterfaceIPAddressTable(
|
||||||
data=interface.ip_addresses.restrict(request.user, 'view').prefetch_related('vrf', 'tenant'),
|
data=instance.ip_addresses.restrict(request.user, 'view').prefetch_related('vrf', 'tenant'),
|
||||||
orderable=False
|
orderable=False
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get assigned VLANs and annotate whether each is tagged or untagged
|
# Get assigned VLANs and annotate whether each is tagged or untagged
|
||||||
vlans = []
|
vlans = []
|
||||||
if interface.untagged_vlan is not None:
|
if instance.untagged_vlan is not None:
|
||||||
vlans.append(interface.untagged_vlan)
|
vlans.append(instance.untagged_vlan)
|
||||||
vlans[0].tagged = False
|
vlans[0].tagged = False
|
||||||
for vlan in interface.tagged_vlans.restrict(request.user).prefetch_related('site', 'group', 'tenant', 'role'):
|
for vlan in instance.tagged_vlans.restrict(request.user).prefetch_related('site', 'group', 'tenant', 'role'):
|
||||||
vlan.tagged = True
|
vlan.tagged = True
|
||||||
vlans.append(vlan)
|
vlans.append(vlan)
|
||||||
vlan_table = InterfaceVLANTable(
|
vlan_table = InterfaceVLANTable(
|
||||||
interface=interface,
|
interface=instance,
|
||||||
data=vlans,
|
data=vlans,
|
||||||
orderable=False
|
orderable=False
|
||||||
)
|
)
|
||||||
|
|
||||||
return render(request, 'dcim/interface.html', {
|
return {
|
||||||
'instance': interface,
|
|
||||||
'ipaddress_table': ipaddress_table,
|
'ipaddress_table': ipaddress_table,
|
||||||
'vlan_table': vlan_table,
|
'vlan_table': vlan_table,
|
||||||
})
|
'breadcrumb_url': 'dcim:device_interfaces'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class InterfaceCreateView(generic.ComponentCreateView):
|
class InterfaceCreateView(generic.ComponentCreateView):
|
||||||
@@ -1694,6 +1681,11 @@ class FrontPortListView(generic.ObjectListView):
|
|||||||
class FrontPortView(generic.ObjectView):
|
class FrontPortView(generic.ObjectView):
|
||||||
queryset = FrontPort.objects.all()
|
queryset = FrontPort.objects.all()
|
||||||
|
|
||||||
|
def get_extra_context(self, request, instance):
|
||||||
|
return {
|
||||||
|
'breadcrumb_url': 'dcim:device_frontports'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class FrontPortCreateView(generic.ComponentCreateView):
|
class FrontPortCreateView(generic.ComponentCreateView):
|
||||||
queryset = FrontPort.objects.all()
|
queryset = FrontPort.objects.all()
|
||||||
@@ -1754,6 +1746,11 @@ class RearPortListView(generic.ObjectListView):
|
|||||||
class RearPortView(generic.ObjectView):
|
class RearPortView(generic.ObjectView):
|
||||||
queryset = RearPort.objects.all()
|
queryset = RearPort.objects.all()
|
||||||
|
|
||||||
|
def get_extra_context(self, request, instance):
|
||||||
|
return {
|
||||||
|
'breadcrumb_url': 'dcim:device_rearports'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class RearPortCreateView(generic.ComponentCreateView):
|
class RearPortCreateView(generic.ComponentCreateView):
|
||||||
queryset = RearPort.objects.all()
|
queryset = RearPort.objects.all()
|
||||||
@@ -1814,6 +1811,11 @@ class DeviceBayListView(generic.ObjectListView):
|
|||||||
class DeviceBayView(generic.ObjectView):
|
class DeviceBayView(generic.ObjectView):
|
||||||
queryset = DeviceBay.objects.all()
|
queryset = DeviceBay.objects.all()
|
||||||
|
|
||||||
|
def get_extra_context(self, request, instance):
|
||||||
|
return {
|
||||||
|
'breadcrumb_url': 'dcim:device_devicebays'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class DeviceBayCreateView(generic.ComponentCreateView):
|
class DeviceBayCreateView(generic.ComponentCreateView):
|
||||||
queryset = DeviceBay.objects.all()
|
queryset = DeviceBay.objects.all()
|
||||||
@@ -1935,6 +1937,11 @@ class InventoryItemListView(generic.ObjectListView):
|
|||||||
class InventoryItemView(generic.ObjectView):
|
class InventoryItemView(generic.ObjectView):
|
||||||
queryset = InventoryItem.objects.all()
|
queryset = InventoryItem.objects.all()
|
||||||
|
|
||||||
|
def get_extra_context(self, request, instance):
|
||||||
|
return {
|
||||||
|
'breadcrumb_url': 'dcim:device_inventory'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class InventoryItemEditView(generic.ObjectEditView):
|
class InventoryItemEditView(generic.ObjectEditView):
|
||||||
queryset = InventoryItem.objects.all()
|
queryset = InventoryItem.objects.all()
|
||||||
@@ -2093,20 +2100,13 @@ class CableListView(generic.ObjectListView):
|
|||||||
class CableView(generic.ObjectView):
|
class CableView(generic.ObjectView):
|
||||||
queryset = Cable.objects.all()
|
queryset = Cable.objects.all()
|
||||||
|
|
||||||
def get(self, request, pk):
|
|
||||||
|
|
||||||
cable = get_object_or_404(self.queryset, pk=pk)
|
|
||||||
|
|
||||||
return render(request, 'dcim/cable.html', {
|
|
||||||
'cable': cable,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class PathTraceView(generic.ObjectView):
|
class PathTraceView(generic.ObjectView):
|
||||||
"""
|
"""
|
||||||
Trace a cable path beginning from the given path endpoint (origin).
|
Trace a cable path beginning from the given path endpoint (origin).
|
||||||
"""
|
"""
|
||||||
additional_permissions = ['dcim.view_cable']
|
additional_permissions = ['dcim.view_cable']
|
||||||
|
template_name = 'dcim/cable_trace.html'
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
model = kwargs.pop('model')
|
model = kwargs.pop('model')
|
||||||
@@ -2114,17 +2114,16 @@ class PathTraceView(generic.ObjectView):
|
|||||||
|
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
obj = get_object_or_404(self.queryset, pk=pk)
|
|
||||||
related_paths = []
|
related_paths = []
|
||||||
|
|
||||||
# If tracing a PathEndpoint, locate the CablePath (if one exists) by its origin
|
# If tracing a PathEndpoint, locate the CablePath (if one exists) by its origin
|
||||||
if isinstance(obj, PathEndpoint):
|
if isinstance(instance, PathEndpoint):
|
||||||
path = obj._path
|
path = instance._path
|
||||||
|
|
||||||
# Otherwise, find all CablePaths which traverse the specified object
|
# Otherwise, find all CablePaths which traverse the specified object
|
||||||
else:
|
else:
|
||||||
related_paths = CablePath.objects.filter(path__contains=obj).prefetch_related('origin')
|
related_paths = CablePath.objects.filter(path__contains=instance).prefetch_related('origin')
|
||||||
# Check for specification of a particular path (when tracing pass-through ports)
|
# Check for specification of a particular path (when tracing pass-through ports)
|
||||||
try:
|
try:
|
||||||
path_id = int(request.GET.get('cablepath_id'))
|
path_id = int(request.GET.get('cablepath_id'))
|
||||||
@@ -2135,12 +2134,11 @@ class PathTraceView(generic.ObjectView):
|
|||||||
else:
|
else:
|
||||||
path = related_paths.first()
|
path = related_paths.first()
|
||||||
|
|
||||||
return render(request, 'dcim/cable_trace.html', {
|
return {
|
||||||
'obj': obj,
|
|
||||||
'path': path,
|
'path': path,
|
||||||
'related_paths': related_paths,
|
'related_paths': related_paths,
|
||||||
'total_length': path.get_total_length() if path else None,
|
'total_length': path.get_total_length() if path else None,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class CableCreateView(generic.ObjectEditView):
|
class CableCreateView(generic.ObjectEditView):
|
||||||
@@ -2336,9 +2334,9 @@ class InterfaceConnectionsListView(generic.ObjectListView):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class VirtualChassisListView(generic.ObjectListView):
|
class VirtualChassisListView(generic.ObjectListView):
|
||||||
queryset = VirtualChassis.objects.annotate(
|
queryset = VirtualChassis.objects.prefetch_related('master').annotate(
|
||||||
member_count=Count('members', distinct=True)
|
member_count=get_subquery(Device, 'virtual_chassis')
|
||||||
).order_by(*VirtualChassis._meta.ordering)
|
)
|
||||||
table = tables.VirtualChassisTable
|
table = tables.VirtualChassisTable
|
||||||
filterset = filters.VirtualChassisFilterSet
|
filterset = filters.VirtualChassisFilterSet
|
||||||
filterset_form = forms.VirtualChassisFilterForm
|
filterset_form = forms.VirtualChassisFilterForm
|
||||||
@@ -2347,14 +2345,12 @@ class VirtualChassisListView(generic.ObjectListView):
|
|||||||
class VirtualChassisView(generic.ObjectView):
|
class VirtualChassisView(generic.ObjectView):
|
||||||
queryset = VirtualChassis.objects.all()
|
queryset = VirtualChassis.objects.all()
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
virtualchassis = get_object_or_404(self.queryset, pk=pk)
|
members = Device.objects.restrict(request.user).filter(virtual_chassis=instance)
|
||||||
members = Device.objects.restrict(request.user).filter(virtual_chassis=virtualchassis)
|
|
||||||
|
|
||||||
return render(request, 'dcim/virtualchassis.html', {
|
return {
|
||||||
'virtualchassis': virtualchassis,
|
|
||||||
'members': members,
|
'members': members,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class VirtualChassisCreateView(generic.ObjectEditView):
|
class VirtualChassisCreateView(generic.ObjectEditView):
|
||||||
@@ -2566,9 +2562,11 @@ class VirtualChassisBulkDeleteView(generic.BulkDeleteView):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class PowerPanelListView(generic.ObjectListView):
|
class PowerPanelListView(generic.ObjectListView):
|
||||||
queryset = PowerPanel.objects.annotate(
|
queryset = PowerPanel.objects.prefetch_related(
|
||||||
powerfeed_count=Count('powerfeeds')
|
'site', 'rack_group'
|
||||||
).order_by(*PowerPanel._meta.ordering)
|
).annotate(
|
||||||
|
powerfeed_count=get_subquery(PowerFeed, 'power_panel')
|
||||||
|
)
|
||||||
filterset = filters.PowerPanelFilterSet
|
filterset = filters.PowerPanelFilterSet
|
||||||
filterset_form = forms.PowerPanelFilterForm
|
filterset_form = forms.PowerPanelFilterForm
|
||||||
table = tables.PowerPanelTable
|
table = tables.PowerPanelTable
|
||||||
@@ -2577,20 +2575,17 @@ class PowerPanelListView(generic.ObjectListView):
|
|||||||
class PowerPanelView(generic.ObjectView):
|
class PowerPanelView(generic.ObjectView):
|
||||||
queryset = PowerPanel.objects.prefetch_related('site', 'rack_group')
|
queryset = PowerPanel.objects.prefetch_related('site', 'rack_group')
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
|
power_feeds = PowerFeed.objects.restrict(request.user).filter(power_panel=instance).prefetch_related('rack')
|
||||||
powerpanel = get_object_or_404(self.queryset, pk=pk)
|
|
||||||
power_feeds = PowerFeed.objects.restrict(request.user).filter(power_panel=powerpanel).prefetch_related('rack')
|
|
||||||
powerfeed_table = tables.PowerFeedTable(
|
powerfeed_table = tables.PowerFeedTable(
|
||||||
data=power_feeds,
|
data=power_feeds,
|
||||||
orderable=False
|
orderable=False
|
||||||
)
|
)
|
||||||
powerfeed_table.exclude = ['power_panel']
|
powerfeed_table.exclude = ['power_panel']
|
||||||
|
|
||||||
return render(request, 'dcim/powerpanel.html', {
|
return {
|
||||||
'powerpanel': powerpanel,
|
|
||||||
'powerfeed_table': powerfeed_table,
|
'powerfeed_table': powerfeed_table,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class PowerPanelEditView(generic.ObjectEditView):
|
class PowerPanelEditView(generic.ObjectEditView):
|
||||||
@@ -2620,8 +2615,8 @@ class PowerPanelBulkDeleteView(generic.BulkDeleteView):
|
|||||||
queryset = PowerPanel.objects.prefetch_related(
|
queryset = PowerPanel.objects.prefetch_related(
|
||||||
'site', 'rack_group'
|
'site', 'rack_group'
|
||||||
).annotate(
|
).annotate(
|
||||||
rack_count=Count('powerfeeds')
|
powerfeed_count=get_subquery(PowerFeed, 'power_panel')
|
||||||
).order_by(*PowerPanel._meta.ordering)
|
)
|
||||||
filterset = filters.PowerPanelFilterSet
|
filterset = filters.PowerPanelFilterSet
|
||||||
table = tables.PowerPanelTable
|
table = tables.PowerPanelTable
|
||||||
|
|
||||||
@@ -2640,14 +2635,6 @@ class PowerFeedListView(generic.ObjectListView):
|
|||||||
class PowerFeedView(generic.ObjectView):
|
class PowerFeedView(generic.ObjectView):
|
||||||
queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack')
|
queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack')
|
||||||
|
|
||||||
def get(self, request, pk):
|
|
||||||
|
|
||||||
powerfeed = get_object_or_404(self.queryset, pk=pk)
|
|
||||||
|
|
||||||
return render(request, 'dcim/powerfeed.html', {
|
|
||||||
'powerfeed': powerfeed,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class PowerFeedEditView(generic.ObjectEditView):
|
class PowerFeedEditView(generic.ObjectEditView):
|
||||||
queryset = PowerFeed.objects.all()
|
queryset = PowerFeed.objects.all()
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from django import forms
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from utilities.forms import LaxURLField
|
from utilities.forms import LaxURLField
|
||||||
from .choices import CustomFieldTypeChoices
|
|
||||||
from .models import CustomField, CustomLink, ExportTemplate, JobResult, Webhook
|
from .models import CustomField, CustomLink, ExportTemplate, JobResult, Webhook
|
||||||
|
|
||||||
|
|
||||||
@@ -30,8 +29,8 @@ class WebhookForm(forms.ModelForm):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
if 'obj_type' in self.fields:
|
if 'content_types' in self.fields:
|
||||||
order_content_types(self.fields['obj_type'])
|
order_content_types(self.fields['content_types'])
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Webhook)
|
@admin.register(Webhook)
|
||||||
@@ -41,12 +40,12 @@ class WebhookAdmin(admin.ModelAdmin):
|
|||||||
'ssl_verification',
|
'ssl_verification',
|
||||||
]
|
]
|
||||||
list_filter = [
|
list_filter = [
|
||||||
'enabled', 'type_create', 'type_update', 'type_delete', 'obj_type',
|
'enabled', 'type_create', 'type_update', 'type_delete', 'content_types',
|
||||||
]
|
]
|
||||||
form = WebhookForm
|
form = WebhookForm
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, {
|
(None, {
|
||||||
'fields': ('name', 'obj_type', 'enabled')
|
'fields': ('name', 'content_types', 'enabled')
|
||||||
}),
|
}),
|
||||||
('Events', {
|
('Events', {
|
||||||
'fields': ('type_create', 'type_update', 'type_delete')
|
'fields': ('type_create', 'type_update', 'type_delete')
|
||||||
@@ -63,7 +62,7 @@ class WebhookAdmin(admin.ModelAdmin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def models(self, obj):
|
def models(self, obj):
|
||||||
return ', '.join([ct.name for ct in obj.obj_type.all()])
|
return ', '.join([ct.name for ct in obj.content_types.all()])
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -76,6 +75,7 @@ class CustomFieldForm(forms.ModelForm):
|
|||||||
model = CustomField
|
model = CustomField
|
||||||
exclude = []
|
exclude = []
|
||||||
widgets = {
|
widgets = {
|
||||||
|
'default': forms.TextInput(),
|
||||||
'validation_regex': forms.Textarea(
|
'validation_regex': forms.Textarea(
|
||||||
attrs={
|
attrs={
|
||||||
'cols': 80,
|
'cols': 80,
|
||||||
|
|||||||
@@ -26,15 +26,8 @@ class CustomFieldDefaultValues:
|
|||||||
# Populate the default value for each CustomField
|
# Populate the default value for each CustomField
|
||||||
value = {}
|
value = {}
|
||||||
for field in fields:
|
for field in fields:
|
||||||
if field.default:
|
if field.default is not None:
|
||||||
if field.type == CustomFieldTypeChoices.TYPE_INTEGER:
|
value[field.name] = field.default
|
||||||
field_value = int(field.default)
|
|
||||||
elif field.type == CustomFieldTypeChoices.TYPE_BOOLEAN:
|
|
||||||
# TODO: Fix default value assignment for boolean custom fields
|
|
||||||
field_value = False if field.default.lower() == 'false' else bool(field.default)
|
|
||||||
else:
|
|
||||||
field_value = field.default
|
|
||||||
value[field.name] = field_value
|
|
||||||
else:
|
else:
|
||||||
value[field.name] = None
|
value[field.name] = None
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from users.api.nested_serializers import NestedUserSerializer
|
|||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'NestedConfigContextSerializer',
|
'NestedConfigContextSerializer',
|
||||||
|
'NestedCustomFieldSerializer',
|
||||||
'NestedExportTemplateSerializer',
|
'NestedExportTemplateSerializer',
|
||||||
'NestedImageAttachmentSerializer',
|
'NestedImageAttachmentSerializer',
|
||||||
'NestedJobResultSerializer',
|
'NestedJobResultSerializer',
|
||||||
@@ -13,6 +14,14 @@ __all__ = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class NestedCustomFieldSerializer(WritableNestedSerializer):
|
||||||
|
url = serializers.HyperlinkedIdentityField(view_name='extras-api:customfield-detail')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.CustomField
|
||||||
|
fields = ['id', 'url', 'name']
|
||||||
|
|
||||||
|
|
||||||
class NestedConfigContextSerializer(WritableNestedSerializer):
|
class NestedConfigContextSerializer(WritableNestedSerializer):
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='extras-api:configcontext-detail')
|
url = serializers.HyperlinkedIdentityField(view_name='extras-api:configcontext-detail')
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from dcim.api.nested_serializers import (
|
|||||||
from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site
|
from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site
|
||||||
from extras.choices import *
|
from extras.choices import *
|
||||||
from extras.models import (
|
from extras.models import (
|
||||||
ConfigContext, ExportTemplate, ImageAttachment, ObjectChange, JobResult, Tag,
|
ConfigContext, CustomField, ExportTemplate, ImageAttachment, ObjectChange, JobResult, Tag,
|
||||||
)
|
)
|
||||||
from extras.utils import FeatureQuery
|
from extras.utils import FeatureQuery
|
||||||
from netbox.api import ChoiceField, ContentTypeField, SerializedPKRelatedField, ValidatedModelSerializer
|
from netbox.api import ChoiceField, ContentTypeField, SerializedPKRelatedField, ValidatedModelSerializer
|
||||||
@@ -24,6 +24,27 @@ from virtualization.models import Cluster, ClusterGroup
|
|||||||
from .nested_serializers import *
|
from .nested_serializers import *
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Custom fields
|
||||||
|
#
|
||||||
|
|
||||||
|
class CustomFieldSerializer(ValidatedModelSerializer):
|
||||||
|
url = serializers.HyperlinkedIdentityField(view_name='extras-api:customfield-detail')
|
||||||
|
content_types = ContentTypeField(
|
||||||
|
queryset=ContentType.objects.filter(FeatureQuery('custom_fields').get_query()),
|
||||||
|
many=True
|
||||||
|
)
|
||||||
|
type = ChoiceField(choices=CustomFieldTypeChoices)
|
||||||
|
filter_logic = ChoiceField(choices=CustomFieldFilterLogicChoices, required=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = CustomField
|
||||||
|
fields = [
|
||||||
|
'id', 'url', 'content_types', 'type', 'name', 'label', 'description', 'required', 'filter_logic',
|
||||||
|
'default', 'weight', 'validation_minimum', 'validation_maximum', 'validation_regex', 'choices',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Export templates
|
# Export templates
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ from . import views
|
|||||||
router = OrderedDefaultRouter()
|
router = OrderedDefaultRouter()
|
||||||
router.APIRootView = views.ExtrasRootView
|
router.APIRootView = views.ExtrasRootView
|
||||||
|
|
||||||
|
# Custom fields
|
||||||
|
router.register('custom-fields', views.CustomFieldViewSet)
|
||||||
|
|
||||||
# Export templates
|
# Export templates
|
||||||
router.register('export-templates', views.ExportTemplateViewSet)
|
router.register('export-templates', views.ExportTemplateViewSet)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db.models import Count
|
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django_rq.queues import get_connection
|
from django_rq.queues import get_connection
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
@@ -12,17 +11,28 @@ from rq import Worker
|
|||||||
|
|
||||||
from extras import filters
|
from extras import filters
|
||||||
from extras.choices import JobResultStatusChoices
|
from extras.choices import JobResultStatusChoices
|
||||||
from extras.models import ConfigContext, ExportTemplate, ImageAttachment, ObjectChange, JobResult, Tag
|
from extras.models import (
|
||||||
|
ConfigContext, ExportTemplate, ImageAttachment, ObjectChange, JobResult, Tag, TaggedItem,
|
||||||
|
)
|
||||||
|
from extras.models import CustomField
|
||||||
from extras.reports import get_report, get_reports, run_report
|
from extras.reports import get_report, get_reports, run_report
|
||||||
from extras.scripts import get_script, get_scripts, run_script
|
from extras.scripts import get_script, get_scripts, run_script
|
||||||
from netbox.api.views import ModelViewSet
|
|
||||||
from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
|
from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
|
||||||
from netbox.api.metadata import ContentTypeMetadata
|
from netbox.api.metadata import ContentTypeMetadata
|
||||||
|
from netbox.api.views import ModelViewSet
|
||||||
from utilities.exceptions import RQWorkerNotRunningException
|
from utilities.exceptions import RQWorkerNotRunningException
|
||||||
from utilities.utils import copy_safe_request
|
from utilities.utils import copy_safe_request, get_subquery
|
||||||
from . import serializers
|
from . import serializers
|
||||||
|
|
||||||
|
|
||||||
|
class ExtrasRootView(APIRootView):
|
||||||
|
"""
|
||||||
|
Extras API root view
|
||||||
|
"""
|
||||||
|
def get_view_name(self):
|
||||||
|
return 'Extras'
|
||||||
|
|
||||||
|
|
||||||
class ConfigContextQuerySetMixin:
|
class ConfigContextQuerySetMixin:
|
||||||
"""
|
"""
|
||||||
Used by views that work with config context models (device and virtual machine).
|
Used by views that work with config context models (device and virtual machine).
|
||||||
@@ -46,18 +56,17 @@ class ConfigContextQuerySetMixin:
|
|||||||
return self.queryset.annotate_config_context_data()
|
return self.queryset.annotate_config_context_data()
|
||||||
|
|
||||||
|
|
||||||
class ExtrasRootView(APIRootView):
|
|
||||||
"""
|
|
||||||
Extras API root view
|
|
||||||
"""
|
|
||||||
def get_view_name(self):
|
|
||||||
return 'Extras'
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Custom fields
|
# Custom fields
|
||||||
#
|
#
|
||||||
|
|
||||||
|
class CustomFieldViewSet(ModelViewSet):
|
||||||
|
metadata_class = ContentTypeMetadata
|
||||||
|
queryset = CustomField.objects.all()
|
||||||
|
serializer_class = serializers.CustomFieldSerializer
|
||||||
|
filterset_class = filters.CustomFieldFilterSet
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldModelViewSet(ModelViewSet):
|
class CustomFieldModelViewSet(ModelViewSet):
|
||||||
"""
|
"""
|
||||||
Include the applicable set of CustomFields in the ModelViewSet context.
|
Include the applicable set of CustomFields in the ModelViewSet context.
|
||||||
@@ -93,8 +102,8 @@ class ExportTemplateViewSet(ModelViewSet):
|
|||||||
|
|
||||||
class TagViewSet(ModelViewSet):
|
class TagViewSet(ModelViewSet):
|
||||||
queryset = Tag.objects.annotate(
|
queryset = Tag.objects.annotate(
|
||||||
tagged_items=Count('extras_taggeditem_items')
|
tagged_items=get_subquery(TaggedItem, 'tag')
|
||||||
).order_by(*Tag._meta.ordering)
|
)
|
||||||
serializer_class = serializers.TagSerializer
|
serializer_class = serializers.TagSerializer
|
||||||
filterset_class = filters.TagFilterSet
|
filterset_class = filters.TagFilterSet
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ __all__ = (
|
|||||||
'ContentTypeFilterSet',
|
'ContentTypeFilterSet',
|
||||||
'CreatedUpdatedFilterSet',
|
'CreatedUpdatedFilterSet',
|
||||||
'CustomFieldFilter',
|
'CustomFieldFilter',
|
||||||
'CustomFieldFilterSet',
|
'CustomFieldModelFilterSet',
|
||||||
'ExportTemplateFilterSet',
|
'ExportTemplateFilterSet',
|
||||||
'ImageAttachmentFilterSet',
|
'ImageAttachmentFilterSet',
|
||||||
'LocalConfigContextFilterSet',
|
'LocalConfigContextFilterSet',
|
||||||
@@ -58,7 +58,7 @@ class CustomFieldFilter(django_filters.Filter):
|
|||||||
return queryset.filter(**kwargs)
|
return queryset.filter(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldFilterSet(django_filters.FilterSet):
|
class CustomFieldModelFilterSet(django_filters.FilterSet):
|
||||||
"""
|
"""
|
||||||
Dynamically add a Filter for each CustomField applicable to the parent model.
|
Dynamically add a Filter for each CustomField applicable to the parent model.
|
||||||
"""
|
"""
|
||||||
@@ -74,6 +74,13 @@ class CustomFieldFilterSet(django_filters.FilterSet):
|
|||||||
self.filters['cf_{}'.format(cf.name)] = CustomFieldFilter(field_name=cf.name, custom_field=cf)
|
self.filters['cf_{}'.format(cf.name)] = CustomFieldFilter(field_name=cf.name, custom_field=cf)
|
||||||
|
|
||||||
|
|
||||||
|
class CustomFieldFilterSet(django_filters.FilterSet):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = CustomField
|
||||||
|
fields = ['id', 'content_types', 'name', 'required', 'filter_logic', 'weight']
|
||||||
|
|
||||||
|
|
||||||
class ExportTemplateFilterSet(BaseFilterSet):
|
class ExportTemplateFilterSet(BaseFilterSet):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@@ -34,6 +34,12 @@ class Migration(migrations.Migration):
|
|||||||
size=None
|
size=None
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
# Introduce new default field (to be renamed later)
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='customfield',
|
||||||
|
name='default2',
|
||||||
|
field=models.JSONField(blank=True, null=True),
|
||||||
|
),
|
||||||
# Rename obj_type to content_types
|
# Rename obj_type to content_types
|
||||||
migrations.RenameField(
|
migrations.RenameField(
|
||||||
model_name='customfield',
|
model_name='customfield',
|
||||||
|
|||||||
@@ -16,6 +16,28 @@ def deserialize_value(field, value):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_customfield_defaults(apps, schema_editor):
|
||||||
|
"""
|
||||||
|
Copy old serialized defaults to native JSON types.
|
||||||
|
"""
|
||||||
|
CustomField = apps.get_model('extras', 'CustomField')
|
||||||
|
|
||||||
|
for customfield in CustomField.objects.exclude(default=''):
|
||||||
|
try:
|
||||||
|
if customfield.type == CustomFieldTypeChoices.TYPE_INTEGER:
|
||||||
|
value = int(customfield.default)
|
||||||
|
elif customfield.type == CustomFieldTypeChoices.TYPE_BOOLEAN:
|
||||||
|
value = customfield.default in ['true', 'yes', '1']
|
||||||
|
else:
|
||||||
|
value = customfield.default
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError(
|
||||||
|
f'Invalid default value "{customfield.default}" found for {customfield.type} '
|
||||||
|
f'custom field {customfield.name}'
|
||||||
|
)
|
||||||
|
CustomField.objects.filter(pk=customfield.pk).update(default2=value)
|
||||||
|
|
||||||
|
|
||||||
def migrate_customfieldchoices(apps, schema_editor):
|
def migrate_customfieldchoices(apps, schema_editor):
|
||||||
"""
|
"""
|
||||||
Collect all CustomFieldChoices for each applicable CustomField, and save them locally as an array on
|
Collect all CustomFieldChoices for each applicable CustomField, and save them locally as an array on
|
||||||
@@ -51,6 +73,15 @@ def migrate_customfieldvalues(apps, schema_editor):
|
|||||||
model.objects.filter(pk=cfv.obj_id).update(**cf_data)
|
model.objects.filter(pk=cfv.obj_id).update(**cf_data)
|
||||||
|
|
||||||
|
|
||||||
|
def fix_filter_logic_values(apps, schema_editor):
|
||||||
|
"""
|
||||||
|
Fix invalid values for CustomField.filter_logic (see #5376)
|
||||||
|
"""
|
||||||
|
CustomField = apps.get_model('extras', 'CustomField')
|
||||||
|
|
||||||
|
CustomField.objects.filter(filter_logic="integer").update(filter_logic="loose")
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
@@ -64,10 +95,16 @@ class Migration(migrations.Migration):
|
|||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
migrations.RunPython(
|
||||||
|
code=migrate_customfield_defaults
|
||||||
|
),
|
||||||
migrations.RunPython(
|
migrations.RunPython(
|
||||||
code=migrate_customfieldchoices
|
code=migrate_customfieldchoices
|
||||||
),
|
),
|
||||||
migrations.RunPython(
|
migrations.RunPython(
|
||||||
code=migrate_customfieldvalues
|
code=migrate_customfieldvalues
|
||||||
),
|
),
|
||||||
|
migrations.RunPython(
|
||||||
|
code=fix_filter_logic_values
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -8,6 +8,15 @@ class Migration(migrations.Migration):
|
|||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='CustomField',
|
||||||
|
name='default',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='CustomField',
|
||||||
|
old_name='default2',
|
||||||
|
new_name='default'
|
||||||
|
),
|
||||||
migrations.DeleteModel(
|
migrations.DeleteModel(
|
||||||
name='CustomFieldChoice',
|
name='CustomFieldChoice',
|
||||||
),
|
),
|
||||||
18
netbox/extras/migrations/0053_rename_webhook_obj_type.py
Normal file
18
netbox/extras/migrations/0053_rename_webhook_obj_type.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.1 on 2020-12-02 19:41
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('extras', '0052_customfield_cleanup'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='webhook',
|
||||||
|
old_name='obj_type',
|
||||||
|
new_name='content_types',
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -13,6 +13,7 @@ from django.utils.safestring import mark_safe
|
|||||||
from extras.choices import *
|
from extras.choices import *
|
||||||
from extras.utils import FeatureQuery
|
from extras.utils import FeatureQuery
|
||||||
from utilities.forms import CSVChoiceField, DatePicker, LaxURLField, StaticSelect2, add_blank_choice
|
from utilities.forms import CSVChoiceField, DatePicker, LaxURLField, StaticSelect2, add_blank_choice
|
||||||
|
from utilities.querysets import RestrictedQuerySet
|
||||||
from utilities.validators import validate_regex
|
from utilities.validators import validate_regex
|
||||||
|
|
||||||
|
|
||||||
@@ -63,7 +64,7 @@ class CustomFieldModel(models.Model):
|
|||||||
raise ValidationError(f"Missing required custom field '{cf.name}'.")
|
raise ValidationError(f"Missing required custom field '{cf.name}'.")
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldManager(models.Manager):
|
class CustomFieldManager(models.Manager.from_queryset(RestrictedQuerySet)):
|
||||||
use_in_migrations = True
|
use_in_migrations = True
|
||||||
|
|
||||||
def get_for_model(self, model):
|
def get_for_model(self, model):
|
||||||
@@ -114,10 +115,11 @@ class CustomField(models.Model):
|
|||||||
help_text='Loose matches any instance of a given string; exact '
|
help_text='Loose matches any instance of a given string; exact '
|
||||||
'matches the entire field.'
|
'matches the entire field.'
|
||||||
)
|
)
|
||||||
default = models.CharField(
|
default = models.JSONField(
|
||||||
max_length=100,
|
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text='Default value for the field. Use "true" or "false" for booleans.'
|
null=True,
|
||||||
|
help_text='Default value for the field (must be a JSON value). Encapsulate '
|
||||||
|
'strings with double quotes (e.g. "Foo").'
|
||||||
)
|
)
|
||||||
weight = models.PositiveSmallIntegerField(
|
weight = models.PositiveSmallIntegerField(
|
||||||
default=100,
|
default=100,
|
||||||
@@ -170,6 +172,15 @@ class CustomField(models.Model):
|
|||||||
obj.save()
|
obj.save()
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
# Validate the field's default value (if any)
|
||||||
|
if self.default is not None:
|
||||||
|
try:
|
||||||
|
self.validate(self.default)
|
||||||
|
except ValidationError as err:
|
||||||
|
raise ValidationError({
|
||||||
|
'default': f'Invalid default value "{self.default}": {err.message}'
|
||||||
|
})
|
||||||
|
|
||||||
# Minimum/maximum values can be set only for numeric fields
|
# Minimum/maximum values can be set only for numeric fields
|
||||||
if self.validation_minimum is not None and self.type != CustomFieldTypeChoices.TYPE_INTEGER:
|
if self.validation_minimum is not None and self.type != CustomFieldTypeChoices.TYPE_INTEGER:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
@@ -193,7 +204,7 @@ class CustomField(models.Model):
|
|||||||
})
|
})
|
||||||
|
|
||||||
# A selection field must have at least two choices defined
|
# A selection field must have at least two choices defined
|
||||||
if self.type == CustomFieldTypeChoices.TYPE_SELECT and len(self.choices) < 2:
|
if self.type == CustomFieldTypeChoices.TYPE_SELECT and self.choices and len(self.choices) < 2:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'choices': "Selection fields must specify at least two choices."
|
'choices': "Selection fields must specify at least two choices."
|
||||||
})
|
})
|
||||||
@@ -231,8 +242,6 @@ class CustomField(models.Model):
|
|||||||
(True, 'True'),
|
(True, 'True'),
|
||||||
(False, 'False'),
|
(False, 'False'),
|
||||||
)
|
)
|
||||||
if initial is not None:
|
|
||||||
initial = bool(initial)
|
|
||||||
field = forms.NullBooleanField(
|
field = forms.NullBooleanField(
|
||||||
required=required, initial=initial, widget=StaticSelect2(choices=choices)
|
required=required, initial=initial, widget=StaticSelect2(choices=choices)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -31,8 +31,7 @@ class Webhook(models.Model):
|
|||||||
delete in NetBox. The request will contain a representation of the object, which the remote application can act on.
|
delete in NetBox. The request will contain a representation of the object, which the remote application can act on.
|
||||||
Each Webhook can be limited to firing only on certain actions or certain object types.
|
Each Webhook can be limited to firing only on certain actions or certain object types.
|
||||||
"""
|
"""
|
||||||
# TODO: Rename obj_type to content_types (see #4711)
|
content_types = models.ManyToManyField(
|
||||||
obj_type = models.ManyToManyField(
|
|
||||||
to=ContentType,
|
to=ContentType,
|
||||||
related_name='webhooks',
|
related_name='webhooks',
|
||||||
verbose_name='Object types',
|
verbose_name='Object types',
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from rq import Worker
|
|||||||
|
|
||||||
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Rack, RackGroup, RackRole, Site
|
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Rack, RackGroup, RackRole, Site
|
||||||
from extras.api.views import ReportViewSet, ScriptViewSet
|
from extras.api.views import ReportViewSet, ScriptViewSet
|
||||||
from extras.models import ConfigContext, ExportTemplate, ImageAttachment, Tag
|
from extras.models import ConfigContext, CustomField, ExportTemplate, ImageAttachment, Tag
|
||||||
from extras.reports import Report
|
from extras.reports import Report
|
||||||
from extras.scripts import BooleanVar, IntegerVar, Script, StringVar
|
from extras.scripts import BooleanVar, IntegerVar, Script, StringVar
|
||||||
from utilities.testing import APITestCase, APIViewTestCases
|
from utilities.testing import APITestCase, APIViewTestCases
|
||||||
@@ -30,6 +30,53 @@ class AppTest(APITestCase):
|
|||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
|
||||||
|
class CustomFieldTest(APIViewTestCases.APIViewTestCase):
|
||||||
|
model = CustomField
|
||||||
|
brief_fields = ['id', 'name', 'url']
|
||||||
|
create_data = [
|
||||||
|
{
|
||||||
|
'content_types': ['dcim.site'],
|
||||||
|
'name': 'cf4',
|
||||||
|
'type': 'date',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'content_types': ['dcim.site'],
|
||||||
|
'name': 'cf5',
|
||||||
|
'type': 'url',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'content_types': ['dcim.site'],
|
||||||
|
'name': 'cf6',
|
||||||
|
'type': 'select',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
site_ct = ContentType.objects.get_for_model(Site)
|
||||||
|
|
||||||
|
custom_fields = (
|
||||||
|
CustomField(
|
||||||
|
name='cf1',
|
||||||
|
type='text'
|
||||||
|
),
|
||||||
|
CustomField(
|
||||||
|
name='cf2',
|
||||||
|
type='integer'
|
||||||
|
),
|
||||||
|
CustomField(
|
||||||
|
name='cf3',
|
||||||
|
type='boolean'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
CustomField.objects.bulk_create(custom_fields)
|
||||||
|
for cf in custom_fields:
|
||||||
|
cf.content_types.add(site_ct)
|
||||||
|
|
||||||
|
|
||||||
class ExportTemplateTest(APIViewTestCases.APIViewTestCase):
|
class ExportTemplateTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = ExportTemplate
|
model = ExportTemplate
|
||||||
brief_fields = ['id', 'name', 'url']
|
brief_fields = ['id', 'name', 'url']
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class WebhookTest(APITestCase):
|
|||||||
Webhook(name='Site Delete Webhook', type_delete=True, payload_url=DUMMY_URL, secret=DUMMY_SECRET),
|
Webhook(name='Site Delete Webhook', type_delete=True, payload_url=DUMMY_URL, secret=DUMMY_SECRET),
|
||||||
))
|
))
|
||||||
for webhook in webhooks:
|
for webhook in webhooks:
|
||||||
webhook.obj_type.set([site_ct])
|
webhook.content_types.set([site_ct])
|
||||||
|
|
||||||
def test_enqueue_webhook_create(self):
|
def test_enqueue_webhook_create(self):
|
||||||
# Create an object via the REST API
|
# Create an object via the REST API
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from django import template
|
from django import template
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db.models import Count, Prefetch, Q
|
from django.db.models import Q
|
||||||
from django.http import Http404, HttpResponseForbidden
|
from django.http import Http404, HttpResponseForbidden
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
@@ -9,17 +9,14 @@ from django_rq.queues import get_connection
|
|||||||
from django_tables2 import RequestConfig
|
from django_tables2 import RequestConfig
|
||||||
from rq import Worker
|
from rq import Worker
|
||||||
|
|
||||||
from dcim.models import DeviceRole, Platform, Region, Site
|
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from tenancy.models import Tenant, TenantGroup
|
|
||||||
from utilities.forms import ConfirmationForm
|
from utilities.forms import ConfirmationForm
|
||||||
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
||||||
from utilities.utils import copy_safe_request, shallow_compare_dict
|
from utilities.utils import copy_safe_request, get_subquery, shallow_compare_dict
|
||||||
from utilities.views import ContentTypePermissionRequiredMixin
|
from utilities.views import ContentTypePermissionRequiredMixin
|
||||||
from virtualization.models import Cluster, ClusterGroup
|
|
||||||
from . import filters, forms, tables
|
from . import filters, forms, tables
|
||||||
from .choices import JobResultStatusChoices
|
from .choices import JobResultStatusChoices
|
||||||
from .models import ConfigContext, ImageAttachment, ObjectChange, JobResult, Tag
|
from .models import ConfigContext, ImageAttachment, ObjectChange, JobResult, Tag, TaggedItem
|
||||||
from .reports import get_report, get_reports, run_report
|
from .reports import get_report, get_reports, run_report
|
||||||
from .scripts import get_scripts, run_script
|
from .scripts import get_scripts, run_script
|
||||||
|
|
||||||
@@ -30,8 +27,8 @@ from .scripts import get_scripts, run_script
|
|||||||
|
|
||||||
class TagListView(generic.ObjectListView):
|
class TagListView(generic.ObjectListView):
|
||||||
queryset = Tag.objects.annotate(
|
queryset = Tag.objects.annotate(
|
||||||
items=Count('extras_taggeditem_items')
|
items=get_subquery(TaggedItem, 'tag')
|
||||||
).order_by(*Tag._meta.ordering)
|
)
|
||||||
filterset = filters.TagFilterSet
|
filterset = filters.TagFilterSet
|
||||||
filterset_form = forms.TagFilterForm
|
filterset_form = forms.TagFilterForm
|
||||||
table = tables.TagTable
|
table = tables.TagTable
|
||||||
@@ -55,16 +52,16 @@ class TagBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class TagBulkEditView(generic.BulkEditView):
|
class TagBulkEditView(generic.BulkEditView):
|
||||||
queryset = Tag.objects.annotate(
|
queryset = Tag.objects.annotate(
|
||||||
items=Count('extras_taggeditem_items')
|
items=get_subquery(TaggedItem, 'tag')
|
||||||
).order_by(*Tag._meta.ordering)
|
)
|
||||||
table = tables.TagTable
|
table = tables.TagTable
|
||||||
form = forms.TagBulkEditForm
|
form = forms.TagBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
class TagBulkDeleteView(generic.BulkDeleteView):
|
class TagBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Tag.objects.annotate(
|
queryset = Tag.objects.annotate(
|
||||||
items=Count('extras_taggeditem_items')
|
items=get_subquery(TaggedItem, 'tag')
|
||||||
).order_by(*Tag._meta.ordering)
|
)
|
||||||
table = tables.TagTable
|
table = tables.TagTable
|
||||||
|
|
||||||
|
|
||||||
@@ -83,21 +80,7 @@ class ConfigContextListView(generic.ObjectListView):
|
|||||||
class ConfigContextView(generic.ObjectView):
|
class ConfigContextView(generic.ObjectView):
|
||||||
queryset = ConfigContext.objects.all()
|
queryset = ConfigContext.objects.all()
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
# Extend queryset to prefetch related objects
|
|
||||||
self.queryset = self.queryset.prefetch_related(
|
|
||||||
Prefetch('regions', queryset=Region.objects.restrict(request.user)),
|
|
||||||
Prefetch('sites', queryset=Site.objects.restrict(request.user)),
|
|
||||||
Prefetch('roles', queryset=DeviceRole.objects.restrict(request.user)),
|
|
||||||
Prefetch('platforms', queryset=Platform.objects.restrict(request.user)),
|
|
||||||
Prefetch('clusters', queryset=Cluster.objects.restrict(request.user)),
|
|
||||||
Prefetch('cluster_groups', queryset=ClusterGroup.objects.restrict(request.user)),
|
|
||||||
Prefetch('tenants', queryset=Tenant.objects.restrict(request.user)),
|
|
||||||
Prefetch('tenant_groups', queryset=TenantGroup.objects.restrict(request.user)),
|
|
||||||
)
|
|
||||||
|
|
||||||
configcontext = get_object_or_404(self.queryset, pk=pk)
|
|
||||||
|
|
||||||
# Determine user's preferred output format
|
# Determine user's preferred output format
|
||||||
if request.GET.get('format') in ['json', 'yaml']:
|
if request.GET.get('format') in ['json', 'yaml']:
|
||||||
format = request.GET.get('format')
|
format = request.GET.get('format')
|
||||||
@@ -108,10 +91,9 @@ class ConfigContextView(generic.ObjectView):
|
|||||||
else:
|
else:
|
||||||
format = 'json'
|
format = 'json'
|
||||||
|
|
||||||
return render(request, 'extras/configcontext.html', {
|
return {
|
||||||
'configcontext': configcontext,
|
|
||||||
'format': format,
|
'format': format,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class ConfigContextEditView(generic.ObjectEditView):
|
class ConfigContextEditView(generic.ObjectEditView):
|
||||||
@@ -138,12 +120,10 @@ class ConfigContextBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class ObjectConfigContextView(generic.ObjectView):
|
class ObjectConfigContextView(generic.ObjectView):
|
||||||
base_template = None
|
base_template = None
|
||||||
|
template_name = 'extras/object_configcontext.html'
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
|
source_contexts = ConfigContext.objects.restrict(request.user, 'view').get_for_object(instance)
|
||||||
obj = get_object_or_404(self.queryset, pk=pk)
|
|
||||||
source_contexts = ConfigContext.objects.restrict(request.user, 'view').get_for_object(obj)
|
|
||||||
model_name = self.queryset.model._meta.model_name
|
|
||||||
|
|
||||||
# Determine user's preferred output format
|
# Determine user's preferred output format
|
||||||
if request.GET.get('format') in ['json', 'yaml']:
|
if request.GET.get('format') in ['json', 'yaml']:
|
||||||
@@ -155,15 +135,13 @@ class ObjectConfigContextView(generic.ObjectView):
|
|||||||
else:
|
else:
|
||||||
format = 'json'
|
format = 'json'
|
||||||
|
|
||||||
return render(request, 'extras/object_configcontext.html', {
|
return {
|
||||||
model_name: obj,
|
'rendered_context': instance.get_config_context(),
|
||||||
'obj': obj,
|
|
||||||
'rendered_context': obj.get_config_context(),
|
|
||||||
'source_contexts': source_contexts,
|
'source_contexts': source_contexts,
|
||||||
'format': format,
|
'format': format,
|
||||||
'base_template': self.base_template,
|
'base_template': self.base_template,
|
||||||
'active_tab': 'config-context',
|
'active_tab': 'config-context',
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -182,14 +160,11 @@ class ObjectChangeListView(generic.ObjectListView):
|
|||||||
class ObjectChangeView(generic.ObjectView):
|
class ObjectChangeView(generic.ObjectView):
|
||||||
queryset = ObjectChange.objects.all()
|
queryset = ObjectChange.objects.all()
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
|
|
||||||
objectchange = get_object_or_404(self.queryset, pk=pk)
|
|
||||||
|
|
||||||
related_changes = ObjectChange.objects.restrict(request.user, 'view').filter(
|
related_changes = ObjectChange.objects.restrict(request.user, 'view').filter(
|
||||||
request_id=objectchange.request_id
|
request_id=instance.request_id
|
||||||
).exclude(
|
).exclude(
|
||||||
pk=objectchange.pk
|
pk=instance.pk
|
||||||
)
|
)
|
||||||
related_changes_table = tables.ObjectChangeTable(
|
related_changes_table = tables.ObjectChangeTable(
|
||||||
data=related_changes[:50],
|
data=related_changes[:50],
|
||||||
@@ -197,39 +172,41 @@ class ObjectChangeView(generic.ObjectView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
objectchanges = ObjectChange.objects.restrict(request.user, 'view').filter(
|
objectchanges = ObjectChange.objects.restrict(request.user, 'view').filter(
|
||||||
changed_object_type=objectchange.changed_object_type,
|
changed_object_type=instance.changed_object_type,
|
||||||
changed_object_id=objectchange.changed_object_id,
|
changed_object_id=instance.changed_object_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
next_change = objectchanges.filter(time__gt=objectchange.time).order_by('time').first()
|
next_change = objectchanges.filter(time__gt=instance.time).order_by('time').first()
|
||||||
prev_change = objectchanges.filter(time__lt=objectchange.time).order_by('-time').first()
|
prev_change = objectchanges.filter(time__lt=instance.time).order_by('-time').first()
|
||||||
|
|
||||||
if prev_change:
|
if prev_change:
|
||||||
diff_added = shallow_compare_dict(
|
diff_added = shallow_compare_dict(
|
||||||
prev_change.object_data,
|
prev_change.object_data,
|
||||||
objectchange.object_data,
|
instance.object_data,
|
||||||
exclude=['last_updated'],
|
exclude=['last_updated'],
|
||||||
)
|
)
|
||||||
diff_removed = {x: prev_change.object_data.get(x) for x in diff_added}
|
diff_removed = {x: prev_change.object_data.get(x) for x in diff_added}
|
||||||
else:
|
else:
|
||||||
# No previous change; this is the initial change that added the object
|
# No previous change; this is the initial change that added the object
|
||||||
diff_added = diff_removed = objectchange.object_data
|
diff_added = diff_removed = instance.object_data
|
||||||
|
|
||||||
return render(request, 'extras/objectchange.html', {
|
return {
|
||||||
'objectchange': objectchange,
|
|
||||||
'diff_added': diff_added,
|
'diff_added': diff_added,
|
||||||
'diff_removed': diff_removed,
|
'diff_removed': diff_removed,
|
||||||
'next_change': next_change,
|
'next_change': next_change,
|
||||||
'prev_change': prev_change,
|
'prev_change': prev_change,
|
||||||
'related_changes_table': related_changes_table,
|
'related_changes_table': related_changes_table,
|
||||||
'related_changes_count': related_changes.count()
|
'related_changes_count': related_changes.count()
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class ObjectChangeLogView(View):
|
class ObjectChangeLogView(View):
|
||||||
"""
|
"""
|
||||||
Present a history of changes made to a particular object.
|
Present a history of changes made to a particular object.
|
||||||
|
|
||||||
|
base_template: The name of the template to extend. If not provided, "<app>/<model>.html" will be used.
|
||||||
"""
|
"""
|
||||||
|
base_template = None
|
||||||
|
|
||||||
def get(self, request, model, **kwargs):
|
def get(self, request, model, **kwargs):
|
||||||
|
|
||||||
@@ -259,20 +236,20 @@ class ObjectChangeLogView(View):
|
|||||||
}
|
}
|
||||||
RequestConfig(request, paginate).configure(objectchanges_table)
|
RequestConfig(request, paginate).configure(objectchanges_table)
|
||||||
|
|
||||||
# Check whether a header template exists for this model
|
# Default to using "<app>/<model>.html" as the template, if it exists. Otherwise,
|
||||||
base_template = '{}/{}.html'.format(model._meta.app_label, model._meta.model_name)
|
# fall back to using base.html.
|
||||||
try:
|
if self.base_template is None:
|
||||||
template.loader.get_template(base_template)
|
self.base_template = f"{model._meta.app_label}/{model._meta.model_name}.html"
|
||||||
object_var = model._meta.model_name
|
# TODO: This can be removed once an object view has been established for every model.
|
||||||
except template.TemplateDoesNotExist:
|
try:
|
||||||
base_template = 'base.html'
|
template.loader.get_template(self.base_template)
|
||||||
object_var = 'obj'
|
except template.TemplateDoesNotExist:
|
||||||
|
self.base_template = 'base.html'
|
||||||
|
|
||||||
return render(request, 'extras/object_changelog.html', {
|
return render(request, 'extras/object_changelog.html', {
|
||||||
object_var: obj,
|
'object': obj,
|
||||||
'instance': obj, # We'll eventually standardize on 'instance` for the object variable name
|
|
||||||
'table': objectchanges_table,
|
'table': objectchanges_table,
|
||||||
'base_template': base_template,
|
'base_template': self.base_template,
|
||||||
'active_tab': 'changelog',
|
'active_tab': 'changelog',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django_rq import get_queue
|
from django_rq import get_queue
|
||||||
|
|
||||||
from extras.models import Webhook
|
|
||||||
from utilities.api import get_serializer_for_model
|
from utilities.api import get_serializer_for_model
|
||||||
from .choices import *
|
from .choices import *
|
||||||
from .utils import FeatureQuery
|
from .models import Webhook
|
||||||
|
from .registry import registry
|
||||||
|
|
||||||
|
|
||||||
def generate_signature(request_body, secret):
|
def generate_signature(request_body, secret):
|
||||||
@@ -28,19 +28,20 @@ def enqueue_webhooks(instance, user, request_id, action):
|
|||||||
Find Webhook(s) assigned to this instance + action and enqueue them
|
Find Webhook(s) assigned to this instance + action and enqueue them
|
||||||
to be processed
|
to be processed
|
||||||
"""
|
"""
|
||||||
obj_type = ContentType.objects.get_for_model(instance.__class__)
|
# Determine whether this type of object supports webhooks
|
||||||
|
app_label = instance._meta.app_label
|
||||||
webhook_models = ContentType.objects.filter(FeatureQuery('webhooks').get_query())
|
model_name = instance._meta.model_name
|
||||||
if obj_type not in webhook_models:
|
if model_name not in registry['model_features']['webhooks'].get(app_label, []):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Retrieve any applicable Webhooks
|
# Retrieve any applicable Webhooks
|
||||||
|
content_type = ContentType.objects.get_for_model(instance)
|
||||||
action_flag = {
|
action_flag = {
|
||||||
ObjectChangeActionChoices.ACTION_CREATE: 'type_create',
|
ObjectChangeActionChoices.ACTION_CREATE: 'type_create',
|
||||||
ObjectChangeActionChoices.ACTION_UPDATE: 'type_update',
|
ObjectChangeActionChoices.ACTION_UPDATE: 'type_update',
|
||||||
ObjectChangeActionChoices.ACTION_DELETE: 'type_delete',
|
ObjectChangeActionChoices.ACTION_DELETE: 'type_delete',
|
||||||
}[action]
|
}[action]
|
||||||
webhooks = Webhook.objects.filter(obj_type=obj_type, enabled=True, **{action_flag: True})
|
webhooks = Webhook.objects.filter(content_types=content_type, enabled=True, **{action_flag: True})
|
||||||
|
|
||||||
if webhooks.exists():
|
if webhooks.exists():
|
||||||
# Get the Model's API serializer class and serialize the object
|
# Get the Model's API serializer class and serialize the object
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models import Count
|
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django_pglocks import advisory_lock
|
from django_pglocks import advisory_lock
|
||||||
from drf_yasg.utils import swagger_auto_schema
|
from drf_yasg.utils import swagger_auto_schema
|
||||||
@@ -35,7 +34,7 @@ class VRFViewSet(CustomFieldModelViewSet):
|
|||||||
).annotate(
|
).annotate(
|
||||||
ipaddress_count=get_subquery(IPAddress, 'vrf'),
|
ipaddress_count=get_subquery(IPAddress, 'vrf'),
|
||||||
prefix_count=get_subquery(Prefix, 'vrf')
|
prefix_count=get_subquery(Prefix, 'vrf')
|
||||||
).order_by(*VRF._meta.ordering)
|
)
|
||||||
serializer_class = serializers.VRFSerializer
|
serializer_class = serializers.VRFSerializer
|
||||||
filterset_class = filters.VRFFilterSet
|
filterset_class = filters.VRFFilterSet
|
||||||
|
|
||||||
@@ -56,8 +55,8 @@ class RouteTargetViewSet(CustomFieldModelViewSet):
|
|||||||
|
|
||||||
class RIRViewSet(ModelViewSet):
|
class RIRViewSet(ModelViewSet):
|
||||||
queryset = RIR.objects.annotate(
|
queryset = RIR.objects.annotate(
|
||||||
aggregate_count=Count('aggregates')
|
aggregate_count=get_subquery(Aggregate, 'rir')
|
||||||
).order_by(*RIR._meta.ordering)
|
)
|
||||||
serializer_class = serializers.RIRSerializer
|
serializer_class = serializers.RIRSerializer
|
||||||
filterset_class = filters.RIRFilterSet
|
filterset_class = filters.RIRFilterSet
|
||||||
|
|
||||||
@@ -80,7 +79,7 @@ class RoleViewSet(ModelViewSet):
|
|||||||
queryset = Role.objects.annotate(
|
queryset = Role.objects.annotate(
|
||||||
prefix_count=get_subquery(Prefix, 'role'),
|
prefix_count=get_subquery(Prefix, 'role'),
|
||||||
vlan_count=get_subquery(VLAN, 'role')
|
vlan_count=get_subquery(VLAN, 'role')
|
||||||
).order_by(*Role._meta.ordering)
|
)
|
||||||
serializer_class = serializers.RoleSerializer
|
serializer_class = serializers.RoleSerializer
|
||||||
filterset_class = filters.RoleFilterSet
|
filterset_class = filters.RoleFilterSet
|
||||||
|
|
||||||
@@ -92,7 +91,7 @@ class RoleViewSet(ModelViewSet):
|
|||||||
class PrefixViewSet(CustomFieldModelViewSet):
|
class PrefixViewSet(CustomFieldModelViewSet):
|
||||||
queryset = Prefix.objects.prefetch_related(
|
queryset = Prefix.objects.prefetch_related(
|
||||||
'site', 'vrf__tenant', 'tenant', 'vlan', 'role', 'tags'
|
'site', 'vrf__tenant', 'tenant', 'vlan', 'role', 'tags'
|
||||||
).order_by(*Prefix._meta.ordering)
|
)
|
||||||
serializer_class = serializers.PrefixSerializer
|
serializer_class = serializers.PrefixSerializer
|
||||||
filterset_class = filters.PrefixFilterSet
|
filterset_class = filters.PrefixFilterSet
|
||||||
|
|
||||||
@@ -262,7 +261,7 @@ class PrefixViewSet(CustomFieldModelViewSet):
|
|||||||
class IPAddressViewSet(CustomFieldModelViewSet):
|
class IPAddressViewSet(CustomFieldModelViewSet):
|
||||||
queryset = IPAddress.objects.prefetch_related(
|
queryset = IPAddress.objects.prefetch_related(
|
||||||
'vrf__tenant', 'tenant', 'nat_inside', 'nat_outside', 'tags', 'assigned_object'
|
'vrf__tenant', 'tenant', 'nat_inside', 'nat_outside', 'tags', 'assigned_object'
|
||||||
).order_by(*IPAddress._meta.ordering)
|
)
|
||||||
serializer_class = serializers.IPAddressSerializer
|
serializer_class = serializers.IPAddressSerializer
|
||||||
filterset_class = filters.IPAddressFilterSet
|
filterset_class = filters.IPAddressFilterSet
|
||||||
|
|
||||||
@@ -273,8 +272,8 @@ class IPAddressViewSet(CustomFieldModelViewSet):
|
|||||||
|
|
||||||
class VLANGroupViewSet(ModelViewSet):
|
class VLANGroupViewSet(ModelViewSet):
|
||||||
queryset = VLANGroup.objects.prefetch_related('site').annotate(
|
queryset = VLANGroup.objects.prefetch_related('site').annotate(
|
||||||
vlan_count=Count('vlans')
|
vlan_count=get_subquery(VLAN, 'group')
|
||||||
).order_by(*VLANGroup._meta.ordering)
|
)
|
||||||
serializer_class = serializers.VLANGroupSerializer
|
serializer_class = serializers.VLANGroupSerializer
|
||||||
filterset_class = filters.VLANGroupFilterSet
|
filterset_class = filters.VLANGroupFilterSet
|
||||||
|
|
||||||
@@ -288,7 +287,7 @@ class VLANViewSet(CustomFieldModelViewSet):
|
|||||||
'site', 'group', 'tenant', 'role', 'tags'
|
'site', 'group', 'tenant', 'role', 'tags'
|
||||||
).annotate(
|
).annotate(
|
||||||
prefix_count=get_subquery(Prefix, 'vlan')
|
prefix_count=get_subquery(Prefix, 'vlan')
|
||||||
).order_by(*VLAN._meta.ordering)
|
)
|
||||||
serializer_class = serializers.VLANSerializer
|
serializer_class = serializers.VLANSerializer
|
||||||
filterset_class = filters.VLANFilterSet
|
filterset_class = filters.VLANFilterSet
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from django.db.models import Q
|
|||||||
from netaddr.core import AddrFormatError
|
from netaddr.core import AddrFormatError
|
||||||
|
|
||||||
from dcim.models import Device, Interface, Region, Site
|
from dcim.models import Device, Interface, Region, Site
|
||||||
from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet
|
from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet
|
||||||
from tenancy.filters import TenancyFilterSet
|
from tenancy.filters import TenancyFilterSet
|
||||||
from utilities.filters import (
|
from utilities.filters import (
|
||||||
BaseFilterSet, MultiValueCharFilter, MultiValueNumberFilter, NameSlugSearchFilterSet, NumericArrayFilter, TagFilter,
|
BaseFilterSet, MultiValueCharFilter, MultiValueNumberFilter, NameSlugSearchFilterSet, NumericArrayFilter, TagFilter,
|
||||||
@@ -30,7 +30,7 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class VRFFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
class VRFFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@@ -73,7 +73,7 @@ class VRFFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, Create
|
|||||||
fields = ['id', 'name', 'rd', 'enforce_unique']
|
fields = ['id', 'name', 'rd', 'enforce_unique']
|
||||||
|
|
||||||
|
|
||||||
class RouteTargetFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
class RouteTargetFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@@ -122,7 +122,7 @@ class RIRFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
|
|||||||
fields = ['id', 'name', 'slug', 'is_private', 'description']
|
fields = ['id', 'name', 'slug', 'is_private', 'description']
|
||||||
|
|
||||||
|
|
||||||
class AggregateFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
class AggregateFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@@ -183,7 +183,7 @@ class RoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
|
|||||||
fields = ['id', 'name', 'slug']
|
fields = ['id', 'name', 'slug']
|
||||||
|
|
||||||
|
|
||||||
class PrefixFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
class PrefixFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@@ -355,7 +355,7 @@ class PrefixFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, Cre
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class IPAddressFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
class IPAddressFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@@ -551,7 +551,7 @@ class VLANGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
|
|||||||
fields = ['id', 'name', 'slug', 'description']
|
fields = ['id', 'name', 'slug', 'description']
|
||||||
|
|
||||||
|
|
||||||
class VLANFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
class VLANFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
|
|||||||
@@ -856,6 +856,7 @@ class IPAddressCSVForm(CustomFieldModelCSVForm):
|
|||||||
)
|
)
|
||||||
status = CSVChoiceField(
|
status = CSVChoiceField(
|
||||||
choices=IPAddressStatusChoices,
|
choices=IPAddressStatusChoices,
|
||||||
|
required=False,
|
||||||
help_text='Operational status'
|
help_text='Operational status'
|
||||||
)
|
)
|
||||||
role = CSVChoiceField(
|
role = CSVChoiceField(
|
||||||
@@ -888,7 +889,10 @@ class IPAddressCSVForm(CustomFieldModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
fields = IPAddress.csv_headers
|
fields = [
|
||||||
|
'address', 'vrf', 'tenant', 'status', 'role', 'device', 'virtual_machine', 'interface', 'is_primary',
|
||||||
|
'dns_name', 'description',
|
||||||
|
]
|
||||||
|
|
||||||
def __init__(self, data=None, *args, **kwargs):
|
def __init__(self, data=None, *args, **kwargs):
|
||||||
super().__init__(data, *args, **kwargs)
|
super().__init__(data, *args, **kwargs)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class Migration(migrations.Migration):
|
|||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('tenancy', '0010_custom_field_data'),
|
('tenancy', '0010_custom_field_data'),
|
||||||
('extras', '0052_delete_customfieldchoice_customfieldvalue'),
|
('extras', '0052_customfield_cleanup'),
|
||||||
('ipam', '0040_service_drop_port'),
|
('ipam', '0040_service_drop_port'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from django.db.models import Count, Prefetch
|
from django.db.models import Prefetch
|
||||||
from django.db.models.expressions import RawSQL
|
from django.db.models.expressions import RawSQL
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django_tables2 import RequestConfig
|
from django_tables2 import RequestConfig
|
||||||
@@ -28,26 +28,23 @@ class VRFListView(generic.ObjectListView):
|
|||||||
class VRFView(generic.ObjectView):
|
class VRFView(generic.ObjectView):
|
||||||
queryset = VRF.objects.all()
|
queryset = VRF.objects.all()
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
|
prefix_count = Prefix.objects.restrict(request.user, 'view').filter(vrf=instance).count()
|
||||||
vrf = get_object_or_404(self.queryset, pk=pk)
|
|
||||||
prefix_count = Prefix.objects.restrict(request.user, 'view').filter(vrf=vrf).count()
|
|
||||||
|
|
||||||
import_targets_table = tables.RouteTargetTable(
|
import_targets_table = tables.RouteTargetTable(
|
||||||
vrf.import_targets.prefetch_related('tenant'),
|
instance.import_targets.prefetch_related('tenant'),
|
||||||
orderable=False
|
orderable=False
|
||||||
)
|
)
|
||||||
export_targets_table = tables.RouteTargetTable(
|
export_targets_table = tables.RouteTargetTable(
|
||||||
vrf.export_targets.prefetch_related('tenant'),
|
instance.export_targets.prefetch_related('tenant'),
|
||||||
orderable=False
|
orderable=False
|
||||||
)
|
)
|
||||||
|
|
||||||
return render(request, 'ipam/vrf.html', {
|
return {
|
||||||
'vrf': vrf,
|
|
||||||
'prefix_count': prefix_count,
|
'prefix_count': prefix_count,
|
||||||
'import_targets_table': import_targets_table,
|
'import_targets_table': import_targets_table,
|
||||||
'export_targets_table': export_targets_table,
|
'export_targets_table': export_targets_table,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class VRFEditView(generic.ObjectEditView):
|
class VRFEditView(generic.ObjectEditView):
|
||||||
@@ -93,23 +90,20 @@ class RouteTargetListView(generic.ObjectListView):
|
|||||||
class RouteTargetView(generic.ObjectView):
|
class RouteTargetView(generic.ObjectView):
|
||||||
queryset = RouteTarget.objects.all()
|
queryset = RouteTarget.objects.all()
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
routetarget = get_object_or_404(self.queryset, pk=pk)
|
|
||||||
|
|
||||||
importing_vrfs_table = tables.VRFTable(
|
importing_vrfs_table = tables.VRFTable(
|
||||||
routetarget.importing_vrfs.prefetch_related('tenant'),
|
instance.importing_vrfs.prefetch_related('tenant'),
|
||||||
orderable=False
|
orderable=False
|
||||||
)
|
)
|
||||||
exporting_vrfs_table = tables.VRFTable(
|
exporting_vrfs_table = tables.VRFTable(
|
||||||
routetarget.exporting_vrfs.prefetch_related('tenant'),
|
instance.exporting_vrfs.prefetch_related('tenant'),
|
||||||
orderable=False
|
orderable=False
|
||||||
)
|
)
|
||||||
|
|
||||||
return render(request, 'ipam/routetarget.html', {
|
return {
|
||||||
'routetarget': routetarget,
|
|
||||||
'importing_vrfs_table': importing_vrfs_table,
|
'importing_vrfs_table': importing_vrfs_table,
|
||||||
'exporting_vrfs_table': exporting_vrfs_table,
|
'exporting_vrfs_table': exporting_vrfs_table,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class RouteTargetEditView(generic.ObjectEditView):
|
class RouteTargetEditView(generic.ObjectEditView):
|
||||||
@@ -145,7 +139,9 @@ class RouteTargetBulkDeleteView(generic.BulkDeleteView):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class RIRListView(generic.ObjectListView):
|
class RIRListView(generic.ObjectListView):
|
||||||
queryset = RIR.objects.annotate(aggregate_count=Count('aggregates')).order_by(*RIR._meta.ordering)
|
queryset = RIR.objects.annotate(
|
||||||
|
aggregate_count=get_subquery(Aggregate, 'rir')
|
||||||
|
)
|
||||||
filterset = filters.RIRFilterSet
|
filterset = filters.RIRFilterSet
|
||||||
filterset_form = forms.RIRFilterForm
|
filterset_form = forms.RIRFilterForm
|
||||||
table = tables.RIRTable
|
table = tables.RIRTable
|
||||||
@@ -168,7 +164,9 @@ class RIRBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
|
|
||||||
class RIRBulkDeleteView(generic.BulkDeleteView):
|
class RIRBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = RIR.objects.annotate(aggregate_count=Count('aggregates')).order_by(*RIR._meta.ordering)
|
queryset = RIR.objects.annotate(
|
||||||
|
aggregate_count=get_subquery(Aggregate, 'rir')
|
||||||
|
)
|
||||||
filterset = filters.RIRFilterSet
|
filterset = filters.RIRFilterSet
|
||||||
table = tables.RIRTable
|
table = tables.RIRTable
|
||||||
|
|
||||||
@@ -180,7 +178,7 @@ class RIRBulkDeleteView(generic.BulkDeleteView):
|
|||||||
class AggregateListView(generic.ObjectListView):
|
class AggregateListView(generic.ObjectListView):
|
||||||
queryset = Aggregate.objects.annotate(
|
queryset = Aggregate.objects.annotate(
|
||||||
child_count=RawSQL('SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix', ())
|
child_count=RawSQL('SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix', ())
|
||||||
).order_by(*Aggregate._meta.ordering)
|
)
|
||||||
filterset = filters.AggregateFilterSet
|
filterset = filters.AggregateFilterSet
|
||||||
filterset_form = forms.AggregateFilterForm
|
filterset_form = forms.AggregateFilterForm
|
||||||
table = tables.AggregateDetailTable
|
table = tables.AggregateDetailTable
|
||||||
@@ -206,13 +204,10 @@ class AggregateListView(generic.ObjectListView):
|
|||||||
class AggregateView(generic.ObjectView):
|
class AggregateView(generic.ObjectView):
|
||||||
queryset = Aggregate.objects.all()
|
queryset = Aggregate.objects.all()
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
|
|
||||||
aggregate = get_object_or_404(self.queryset, pk=pk)
|
|
||||||
|
|
||||||
# Find all child prefixes contained by this aggregate
|
# Find all child prefixes contained by this aggregate
|
||||||
child_prefixes = Prefix.objects.restrict(request.user, 'view').filter(
|
child_prefixes = Prefix.objects.restrict(request.user, 'view').filter(
|
||||||
prefix__net_contained_or_equal=str(aggregate.prefix)
|
prefix__net_contained_or_equal=str(instance.prefix)
|
||||||
).prefetch_related(
|
).prefetch_related(
|
||||||
'site', 'role'
|
'site', 'role'
|
||||||
).order_by(
|
).order_by(
|
||||||
@@ -221,7 +216,7 @@ class AggregateView(generic.ObjectView):
|
|||||||
|
|
||||||
# Add available prefixes to the table if requested
|
# Add available prefixes to the table if requested
|
||||||
if request.GET.get('show_available', 'true') == 'true':
|
if request.GET.get('show_available', 'true') == 'true':
|
||||||
child_prefixes = add_available_prefixes(aggregate.prefix, child_prefixes)
|
child_prefixes = add_available_prefixes(instance.prefix, child_prefixes)
|
||||||
|
|
||||||
prefix_table = tables.PrefixDetailTable(child_prefixes)
|
prefix_table = tables.PrefixDetailTable(child_prefixes)
|
||||||
if request.user.has_perm('ipam.change_prefix') or request.user.has_perm('ipam.delete_prefix'):
|
if request.user.has_perm('ipam.change_prefix') or request.user.has_perm('ipam.delete_prefix'):
|
||||||
@@ -240,12 +235,11 @@ class AggregateView(generic.ObjectView):
|
|||||||
'delete': request.user.has_perm('ipam.delete_prefix'),
|
'delete': request.user.has_perm('ipam.delete_prefix'),
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, 'ipam/aggregate.html', {
|
return {
|
||||||
'aggregate': aggregate,
|
|
||||||
'prefix_table': prefix_table,
|
'prefix_table': prefix_table,
|
||||||
'permissions': permissions,
|
'permissions': permissions,
|
||||||
'show_available': request.GET.get('show_available', 'true') == 'true',
|
'show_available': request.GET.get('show_available', 'true') == 'true',
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class AggregateEditView(generic.ObjectEditView):
|
class AggregateEditView(generic.ObjectEditView):
|
||||||
@@ -324,22 +318,19 @@ class PrefixListView(generic.ObjectListView):
|
|||||||
class PrefixView(generic.ObjectView):
|
class PrefixView(generic.ObjectView):
|
||||||
queryset = Prefix.objects.prefetch_related('vrf', 'site__region', 'tenant__group', 'vlan__group', 'role')
|
queryset = Prefix.objects.prefetch_related('vrf', 'site__region', 'tenant__group', 'vlan__group', 'role')
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
|
|
||||||
prefix = get_object_or_404(self.queryset, pk=pk)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
aggregate = Aggregate.objects.restrict(request.user, 'view').get(
|
aggregate = Aggregate.objects.restrict(request.user, 'view').get(
|
||||||
prefix__net_contains_or_equals=str(prefix.prefix)
|
prefix__net_contains_or_equals=str(instance.prefix)
|
||||||
)
|
)
|
||||||
except Aggregate.DoesNotExist:
|
except Aggregate.DoesNotExist:
|
||||||
aggregate = None
|
aggregate = None
|
||||||
|
|
||||||
# Parent prefixes table
|
# Parent prefixes table
|
||||||
parent_prefixes = Prefix.objects.restrict(request.user, 'view').filter(
|
parent_prefixes = Prefix.objects.restrict(request.user, 'view').filter(
|
||||||
Q(vrf=prefix.vrf) | Q(vrf__isnull=True)
|
Q(vrf=instance.vrf) | Q(vrf__isnull=True)
|
||||||
).filter(
|
).filter(
|
||||||
prefix__net_contains=str(prefix.prefix)
|
prefix__net_contains=str(instance.prefix)
|
||||||
).prefetch_related(
|
).prefetch_related(
|
||||||
'site', 'role'
|
'site', 'role'
|
||||||
).annotate_tree()
|
).annotate_tree()
|
||||||
@@ -348,38 +339,35 @@ class PrefixView(generic.ObjectView):
|
|||||||
|
|
||||||
# Duplicate prefixes table
|
# Duplicate prefixes table
|
||||||
duplicate_prefixes = Prefix.objects.restrict(request.user, 'view').filter(
|
duplicate_prefixes = Prefix.objects.restrict(request.user, 'view').filter(
|
||||||
vrf=prefix.vrf, prefix=str(prefix.prefix)
|
vrf=instance.vrf, prefix=str(instance.prefix)
|
||||||
).exclude(
|
).exclude(
|
||||||
pk=prefix.pk
|
pk=instance.pk
|
||||||
).prefetch_related(
|
).prefetch_related(
|
||||||
'site', 'role'
|
'site', 'role'
|
||||||
)
|
)
|
||||||
duplicate_prefix_table = tables.PrefixTable(list(duplicate_prefixes), orderable=False)
|
duplicate_prefix_table = tables.PrefixTable(list(duplicate_prefixes), orderable=False)
|
||||||
duplicate_prefix_table.exclude = ('vrf',)
|
duplicate_prefix_table.exclude = ('vrf',)
|
||||||
|
|
||||||
return render(request, 'ipam/prefix.html', {
|
return {
|
||||||
'prefix': prefix,
|
|
||||||
'aggregate': aggregate,
|
'aggregate': aggregate,
|
||||||
'parent_prefix_table': parent_prefix_table,
|
'parent_prefix_table': parent_prefix_table,
|
||||||
'duplicate_prefix_table': duplicate_prefix_table,
|
'duplicate_prefix_table': duplicate_prefix_table,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class PrefixPrefixesView(generic.ObjectView):
|
class PrefixPrefixesView(generic.ObjectView):
|
||||||
queryset = Prefix.objects.all()
|
queryset = Prefix.objects.all()
|
||||||
|
template_name = 'ipam/prefix_prefixes.html'
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
|
|
||||||
prefix = get_object_or_404(self.queryset, pk=pk)
|
|
||||||
|
|
||||||
# Child prefixes table
|
# Child prefixes table
|
||||||
child_prefixes = prefix.get_child_prefixes().restrict(request.user, 'view').prefetch_related(
|
child_prefixes = instance.get_child_prefixes().restrict(request.user, 'view').prefetch_related(
|
||||||
'site', 'vlan', 'role',
|
'site', 'vlan', 'role',
|
||||||
).annotate_tree()
|
).annotate_tree()
|
||||||
|
|
||||||
# Add available prefixes to the table if requested
|
# Add available prefixes to the table if requested
|
||||||
if child_prefixes and request.GET.get('show_available', 'true') == 'true':
|
if child_prefixes and request.GET.get('show_available', 'true') == 'true':
|
||||||
child_prefixes = add_available_prefixes(prefix.prefix, child_prefixes)
|
child_prefixes = add_available_prefixes(instance.prefix, child_prefixes)
|
||||||
|
|
||||||
prefix_table = tables.PrefixDetailTable(child_prefixes)
|
prefix_table = tables.PrefixDetailTable(child_prefixes)
|
||||||
if request.user.has_perm('ipam.change_prefix') or request.user.has_perm('ipam.delete_prefix'):
|
if request.user.has_perm('ipam.change_prefix') or request.user.has_perm('ipam.delete_prefix'):
|
||||||
@@ -398,32 +386,31 @@ class PrefixPrefixesView(generic.ObjectView):
|
|||||||
'delete': request.user.has_perm('ipam.delete_prefix'),
|
'delete': request.user.has_perm('ipam.delete_prefix'),
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, 'ipam/prefix_prefixes.html', {
|
bulk_querystring = 'vrf_id={}&within={}'.format(instance.vrf.pk if instance.vrf else '0', instance.prefix)
|
||||||
'prefix': prefix,
|
|
||||||
'first_available_prefix': prefix.get_first_available_prefix(),
|
return {
|
||||||
|
'first_available_prefix': instance.get_first_available_prefix(),
|
||||||
'prefix_table': prefix_table,
|
'prefix_table': prefix_table,
|
||||||
'permissions': permissions,
|
'permissions': permissions,
|
||||||
'bulk_querystring': 'vrf_id={}&within={}'.format(prefix.vrf.pk if prefix.vrf else '0', prefix.prefix),
|
'bulk_querystring': bulk_querystring,
|
||||||
'active_tab': 'prefixes',
|
'active_tab': 'prefixes',
|
||||||
'show_available': request.GET.get('show_available', 'true') == 'true',
|
'show_available': request.GET.get('show_available', 'true') == 'true',
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class PrefixIPAddressesView(generic.ObjectView):
|
class PrefixIPAddressesView(generic.ObjectView):
|
||||||
queryset = Prefix.objects.all()
|
queryset = Prefix.objects.all()
|
||||||
|
template_name = 'ipam/prefix_ipaddresses.html'
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
|
|
||||||
prefix = get_object_or_404(self.queryset, pk=pk)
|
|
||||||
|
|
||||||
# Find all IPAddresses belonging to this Prefix
|
# Find all IPAddresses belonging to this Prefix
|
||||||
ipaddresses = prefix.get_child_ips().restrict(request.user, 'view').prefetch_related(
|
ipaddresses = instance.get_child_ips().restrict(request.user, 'view').prefetch_related(
|
||||||
'vrf', 'primary_ip4_for', 'primary_ip6_for'
|
'vrf', 'primary_ip4_for', 'primary_ip6_for'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add available IP addresses to the table if requested
|
# Add available IP addresses to the table if requested
|
||||||
if request.GET.get('show_available', 'true') == 'true':
|
if request.GET.get('show_available', 'true') == 'true':
|
||||||
ipaddresses = add_available_ipaddresses(prefix.prefix, ipaddresses, prefix.is_pool)
|
ipaddresses = add_available_ipaddresses(instance.prefix, ipaddresses, instance.is_pool)
|
||||||
|
|
||||||
ip_table = tables.IPAddressTable(ipaddresses)
|
ip_table = tables.IPAddressTable(ipaddresses)
|
||||||
if request.user.has_perm('ipam.change_ipaddress') or request.user.has_perm('ipam.delete_ipaddress'):
|
if request.user.has_perm('ipam.change_ipaddress') or request.user.has_perm('ipam.delete_ipaddress'):
|
||||||
@@ -442,15 +429,16 @@ class PrefixIPAddressesView(generic.ObjectView):
|
|||||||
'delete': request.user.has_perm('ipam.delete_ipaddress'),
|
'delete': request.user.has_perm('ipam.delete_ipaddress'),
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, 'ipam/prefix_ipaddresses.html', {
|
bulk_querystring = 'vrf_id={}&parent={}'.format(instance.vrf.pk if instance.vrf else '0', instance.prefix)
|
||||||
'prefix': prefix,
|
|
||||||
'first_available_ip': prefix.get_first_available_ip(),
|
return {
|
||||||
|
'first_available_ip': instance.get_first_available_ip(),
|
||||||
'ip_table': ip_table,
|
'ip_table': ip_table,
|
||||||
'permissions': permissions,
|
'permissions': permissions,
|
||||||
'bulk_querystring': 'vrf_id={}&parent={}'.format(prefix.vrf.pk if prefix.vrf else '0', prefix.prefix),
|
'bulk_querystring': bulk_querystring,
|
||||||
'active_tab': 'ip-addresses',
|
'active_tab': 'ip-addresses',
|
||||||
'show_available': request.GET.get('show_available', 'true') == 'true',
|
'show_available': request.GET.get('show_available', 'true') == 'true',
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class PrefixEditView(generic.ObjectEditView):
|
class PrefixEditView(generic.ObjectEditView):
|
||||||
@@ -497,13 +485,11 @@ class IPAddressListView(generic.ObjectListView):
|
|||||||
class IPAddressView(generic.ObjectView):
|
class IPAddressView(generic.ObjectView):
|
||||||
queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant')
|
queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant')
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
|
|
||||||
ipaddress = get_object_or_404(self.queryset, pk=pk)
|
|
||||||
|
|
||||||
# Parent prefixes table
|
# Parent prefixes table
|
||||||
parent_prefixes = Prefix.objects.restrict(request.user, 'view').filter(
|
parent_prefixes = Prefix.objects.restrict(request.user, 'view').filter(
|
||||||
vrf=ipaddress.vrf, prefix__net_contains=str(ipaddress.address.ip)
|
vrf=instance.vrf,
|
||||||
|
prefix__net_contains=str(instance.address.ip)
|
||||||
).prefetch_related(
|
).prefetch_related(
|
||||||
'site', 'role'
|
'site', 'role'
|
||||||
)
|
)
|
||||||
@@ -512,23 +498,24 @@ class IPAddressView(generic.ObjectView):
|
|||||||
|
|
||||||
# Duplicate IPs table
|
# Duplicate IPs table
|
||||||
duplicate_ips = IPAddress.objects.restrict(request.user, 'view').filter(
|
duplicate_ips = IPAddress.objects.restrict(request.user, 'view').filter(
|
||||||
vrf=ipaddress.vrf, address=str(ipaddress.address)
|
vrf=instance.vrf,
|
||||||
|
address=str(instance.address)
|
||||||
).exclude(
|
).exclude(
|
||||||
pk=ipaddress.pk
|
pk=instance.pk
|
||||||
).prefetch_related(
|
).prefetch_related(
|
||||||
'nat_inside'
|
'nat_inside'
|
||||||
)
|
)
|
||||||
# Exclude anycast IPs if this IP is anycast
|
# Exclude anycast IPs if this IP is anycast
|
||||||
if ipaddress.role == IPAddressRoleChoices.ROLE_ANYCAST:
|
if instance.role == IPAddressRoleChoices.ROLE_ANYCAST:
|
||||||
duplicate_ips = duplicate_ips.exclude(role=IPAddressRoleChoices.ROLE_ANYCAST)
|
duplicate_ips = duplicate_ips.exclude(role=IPAddressRoleChoices.ROLE_ANYCAST)
|
||||||
# Limit to a maximum of 10 duplicates displayed here
|
# Limit to a maximum of 10 duplicates displayed here
|
||||||
duplicate_ips_table = tables.IPAddressTable(duplicate_ips[:10], orderable=False)
|
duplicate_ips_table = tables.IPAddressTable(duplicate_ips[:10], orderable=False)
|
||||||
|
|
||||||
# Related IP table
|
# Related IP table
|
||||||
related_ips = IPAddress.objects.restrict(request.user, 'view').exclude(
|
related_ips = IPAddress.objects.restrict(request.user, 'view').exclude(
|
||||||
address=str(ipaddress.address)
|
address=str(instance.address)
|
||||||
).filter(
|
).filter(
|
||||||
vrf=ipaddress.vrf, address__net_contained_or_equal=str(ipaddress.address)
|
vrf=instance.vrf, address__net_contained_or_equal=str(instance.address)
|
||||||
)
|
)
|
||||||
related_ips_table = tables.IPAddressTable(related_ips, orderable=False)
|
related_ips_table = tables.IPAddressTable(related_ips, orderable=False)
|
||||||
|
|
||||||
@@ -538,13 +525,12 @@ class IPAddressView(generic.ObjectView):
|
|||||||
}
|
}
|
||||||
RequestConfig(request, paginate).configure(related_ips_table)
|
RequestConfig(request, paginate).configure(related_ips_table)
|
||||||
|
|
||||||
return render(request, 'ipam/ipaddress.html', {
|
return {
|
||||||
'ipaddress': ipaddress,
|
|
||||||
'parent_prefixes_table': parent_prefixes_table,
|
'parent_prefixes_table': parent_prefixes_table,
|
||||||
'duplicate_ips_table': duplicate_ips_table,
|
'duplicate_ips_table': duplicate_ips_table,
|
||||||
'more_duplicate_ips': duplicate_ips.count() > 10,
|
'more_duplicate_ips': duplicate_ips.count() > 10,
|
||||||
'related_ips_table': related_ips_table,
|
'related_ips_table': related_ips_table,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class IPAddressEditView(generic.ObjectEditView):
|
class IPAddressEditView(generic.ObjectEditView):
|
||||||
@@ -569,6 +555,7 @@ class IPAddressEditView(generic.ObjectEditView):
|
|||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Standardize or remove this view
|
||||||
class IPAddressAssignView(generic.ObjectView):
|
class IPAddressAssignView(generic.ObjectView):
|
||||||
"""
|
"""
|
||||||
Search for IPAddresses to be assigned to an Interface.
|
Search for IPAddresses to be assigned to an Interface.
|
||||||
@@ -645,9 +632,9 @@ class IPAddressBulkDeleteView(generic.BulkDeleteView):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class VLANGroupListView(generic.ObjectListView):
|
class VLANGroupListView(generic.ObjectListView):
|
||||||
queryset = VLANGroup.objects.annotate(
|
queryset = VLANGroup.objects.prefetch_related('site').annotate(
|
||||||
vlan_count=Count('vlans')
|
vlan_count=get_subquery(VLAN, 'group')
|
||||||
).order_by(*VLANGroup._meta.ordering)
|
)
|
||||||
filterset = filters.VLANGroupFilterSet
|
filterset = filters.VLANGroupFilterSet
|
||||||
filterset_form = forms.VLANGroupFilterForm
|
filterset_form = forms.VLANGroupFilterForm
|
||||||
table = tables.VLANGroupTable
|
table = tables.VLANGroupTable
|
||||||
@@ -670,22 +657,21 @@ class VLANGroupBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class VLANGroupBulkDeleteView(generic.BulkDeleteView):
|
class VLANGroupBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = VLANGroup.objects.prefetch_related('site').annotate(
|
queryset = VLANGroup.objects.prefetch_related('site').annotate(
|
||||||
vlan_count=Count('vlans')
|
vlan_count=get_subquery(VLAN, 'group')
|
||||||
).order_by(*VLANGroup._meta.ordering)
|
)
|
||||||
filterset = filters.VLANGroupFilterSet
|
filterset = filters.VLANGroupFilterSet
|
||||||
table = tables.VLANGroupTable
|
table = tables.VLANGroupTable
|
||||||
|
|
||||||
|
|
||||||
class VLANGroupVLANsView(generic.ObjectView):
|
class VLANGroupVLANsView(generic.ObjectView):
|
||||||
queryset = VLANGroup.objects.all()
|
queryset = VLANGroup.objects.all()
|
||||||
|
template_name = 'ipam/vlangroup_vlans.html'
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
vlan_group = get_object_or_404(self.queryset, pk=pk)
|
vlans = VLAN.objects.restrict(request.user, 'view').filter(group=instance).prefetch_related(
|
||||||
|
|
||||||
vlans = VLAN.objects.restrict(request.user, 'view').filter(group_id=pk).prefetch_related(
|
|
||||||
Prefetch('prefixes', queryset=Prefix.objects.restrict(request.user))
|
Prefetch('prefixes', queryset=Prefix.objects.restrict(request.user))
|
||||||
)
|
)
|
||||||
vlans = add_available_vlans(vlan_group, vlans)
|
vlans = add_available_vlans(instance, vlans)
|
||||||
|
|
||||||
vlan_table = tables.VLANDetailTable(vlans)
|
vlan_table = tables.VLANDetailTable(vlans)
|
||||||
if request.user.has_perm('ipam.change_vlan') or request.user.has_perm('ipam.delete_vlan'):
|
if request.user.has_perm('ipam.change_vlan') or request.user.has_perm('ipam.delete_vlan'):
|
||||||
@@ -706,13 +692,12 @@ class VLANGroupVLANsView(generic.ObjectView):
|
|||||||
'delete': request.user.has_perm('ipam.delete_vlan'),
|
'delete': request.user.has_perm('ipam.delete_vlan'),
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, 'ipam/vlangroup_vlans.html', {
|
return {
|
||||||
'vlan_group': vlan_group,
|
'first_available_vlan': instance.get_next_available_vid(),
|
||||||
'first_available_vlan': vlan_group.get_next_available_vid(),
|
'bulk_querystring': f'group_id={instance.pk}',
|
||||||
'bulk_querystring': 'group_id={}'.format(vlan_group.pk),
|
|
||||||
'vlan_table': vlan_table,
|
'vlan_table': vlan_table,
|
||||||
'permissions': permissions,
|
'permissions': permissions,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -729,27 +714,24 @@ class VLANListView(generic.ObjectListView):
|
|||||||
class VLANView(generic.ObjectView):
|
class VLANView(generic.ObjectView):
|
||||||
queryset = VLAN.objects.prefetch_related('site__region', 'tenant__group', 'role')
|
queryset = VLAN.objects.prefetch_related('site__region', 'tenant__group', 'role')
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
|
prefixes = Prefix.objects.restrict(request.user, 'view').filter(vlan=instance).prefetch_related(
|
||||||
vlan = get_object_or_404(self.queryset, pk=pk)
|
|
||||||
prefixes = Prefix.objects.restrict(request.user, 'view').filter(vlan=vlan).prefetch_related(
|
|
||||||
'vrf', 'site', 'role'
|
'vrf', 'site', 'role'
|
||||||
)
|
)
|
||||||
prefix_table = tables.PrefixTable(list(prefixes), orderable=False)
|
prefix_table = tables.PrefixTable(list(prefixes), orderable=False)
|
||||||
prefix_table.exclude = ('vlan',)
|
prefix_table.exclude = ('vlan',)
|
||||||
|
|
||||||
return render(request, 'ipam/vlan.html', {
|
return {
|
||||||
'vlan': vlan,
|
|
||||||
'prefix_table': prefix_table,
|
'prefix_table': prefix_table,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class VLANInterfacesView(generic.ObjectView):
|
class VLANInterfacesView(generic.ObjectView):
|
||||||
queryset = VLAN.objects.all()
|
queryset = VLAN.objects.all()
|
||||||
|
template_name = 'ipam/vlan_interfaces.html'
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
vlan = get_object_or_404(self.queryset, pk=pk)
|
interfaces = instance.get_interfaces().prefetch_related('device')
|
||||||
interfaces = vlan.get_interfaces().prefetch_related('device')
|
|
||||||
members_table = tables.VLANDevicesTable(interfaces)
|
members_table = tables.VLANDevicesTable(interfaces)
|
||||||
|
|
||||||
paginate = {
|
paginate = {
|
||||||
@@ -758,19 +740,18 @@ class VLANInterfacesView(generic.ObjectView):
|
|||||||
}
|
}
|
||||||
RequestConfig(request, paginate).configure(members_table)
|
RequestConfig(request, paginate).configure(members_table)
|
||||||
|
|
||||||
return render(request, 'ipam/vlan_interfaces.html', {
|
return {
|
||||||
'vlan': vlan,
|
|
||||||
'members_table': members_table,
|
'members_table': members_table,
|
||||||
'active_tab': 'interfaces',
|
'active_tab': 'interfaces',
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class VLANVMInterfacesView(generic.ObjectView):
|
class VLANVMInterfacesView(generic.ObjectView):
|
||||||
queryset = VLAN.objects.all()
|
queryset = VLAN.objects.all()
|
||||||
|
template_name = 'ipam/vlan_vminterfaces.html'
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
vlan = get_object_or_404(self.queryset, pk=pk)
|
interfaces = instance.get_vminterfaces().prefetch_related('virtual_machine')
|
||||||
interfaces = vlan.get_vminterfaces().prefetch_related('virtual_machine')
|
|
||||||
members_table = tables.VLANVirtualMachinesTable(interfaces)
|
members_table = tables.VLANVirtualMachinesTable(interfaces)
|
||||||
|
|
||||||
paginate = {
|
paginate = {
|
||||||
@@ -779,11 +760,10 @@ class VLANVMInterfacesView(generic.ObjectView):
|
|||||||
}
|
}
|
||||||
RequestConfig(request, paginate).configure(members_table)
|
RequestConfig(request, paginate).configure(members_table)
|
||||||
|
|
||||||
return render(request, 'ipam/vlan_vminterfaces.html', {
|
return {
|
||||||
'vlan': vlan,
|
|
||||||
'members_table': members_table,
|
'members_table': members_table,
|
||||||
'active_tab': 'vminterfaces',
|
'active_tab': 'vminterfaces',
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class VLANEditView(generic.ObjectEditView):
|
class VLANEditView(generic.ObjectEditView):
|
||||||
@@ -830,14 +810,6 @@ class ServiceListView(generic.ObjectListView):
|
|||||||
class ServiceView(generic.ObjectView):
|
class ServiceView(generic.ObjectView):
|
||||||
queryset = Service.objects.prefetch_related('ipaddresses')
|
queryset = Service.objects.prefetch_related('ipaddresses')
|
||||||
|
|
||||||
def get(self, request, pk):
|
|
||||||
|
|
||||||
service = get_object_or_404(self.queryset, pk=pk)
|
|
||||||
|
|
||||||
return render(request, 'ipam/service.html', {
|
|
||||||
'service': service,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class ServiceEditView(generic.ObjectEditView):
|
class ServiceEditView(generic.ObjectEditView):
|
||||||
queryset = Service.objects.prefetch_related('ipaddresses')
|
queryset = Service.objects.prefetch_related('ipaddresses')
|
||||||
|
|||||||
@@ -94,14 +94,14 @@ class ContentTypeField(RelatedField):
|
|||||||
def to_internal_value(self, data):
|
def to_internal_value(self, data):
|
||||||
try:
|
try:
|
||||||
app_label, model = data.split('.')
|
app_label, model = data.split('.')
|
||||||
return ContentType.objects.get_by_natural_key(app_label=app_label, model=model)
|
return self.queryset.get(app_label=app_label, model=model)
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
self.fail('does_not_exist', content_type=data)
|
self.fail('does_not_exist', content_type=data)
|
||||||
except (TypeError, ValueError):
|
except (AttributeError, TypeError, ValueError):
|
||||||
self.fail('invalid')
|
self.fail('invalid')
|
||||||
|
|
||||||
def to_representation(self, obj):
|
def to_representation(self, obj):
|
||||||
return "{}.{}".format(obj.app_label, obj.model)
|
return f"{obj.app_label}.{obj.model}"
|
||||||
|
|
||||||
|
|
||||||
class TimeZoneField(serializers.Field):
|
class TimeZoneField(serializers.Field):
|
||||||
|
|||||||
@@ -6,11 +6,10 @@ from rest_framework.exceptions import ValidationError
|
|||||||
from utilities.utils import dict_to_filter_params
|
from utilities.utils import dict_to_filter_params
|
||||||
|
|
||||||
|
|
||||||
# TODO: We should probably take a fresh look at exactly what we're doing with this. There might be a more elegant
|
|
||||||
# way to enforce model validation on the serializer.
|
|
||||||
class ValidatedModelSerializer(serializers.ModelSerializer):
|
class ValidatedModelSerializer(serializers.ModelSerializer):
|
||||||
"""
|
"""
|
||||||
Extends the built-in ModelSerializer to enforce calling clean() on the associated model during validation.
|
Extends the built-in ModelSerializer to enforce calling full_clean() on a copy of the associated instance during
|
||||||
|
validation. (DRF does not do this by default; see https://github.com/encode/django-rest-framework/issues/3144)
|
||||||
"""
|
"""
|
||||||
def validate(self, data):
|
def validate(self, data):
|
||||||
|
|
||||||
@@ -31,8 +30,7 @@ class ValidatedModelSerializer(serializers.ModelSerializer):
|
|||||||
instance = self.instance
|
instance = self.instance
|
||||||
for k, v in attrs.items():
|
for k, v in attrs.items():
|
||||||
setattr(instance, k, v)
|
setattr(instance, k, v)
|
||||||
instance.clean()
|
instance.full_clean()
|
||||||
instance.validate_unique()
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ SEARCH_TYPES = OrderedDict((
|
|||||||
# Circuits
|
# Circuits
|
||||||
('provider', {
|
('provider', {
|
||||||
'queryset': Provider.objects.annotate(
|
'queryset': Provider.objects.annotate(
|
||||||
count_circuits=Count('circuits')
|
count_circuits=get_subquery(Circuit, 'provider')
|
||||||
).order_by(*Provider._meta.ordering),
|
),
|
||||||
'filterset': ProviderFilterSet,
|
'filterset': ProviderFilterSet,
|
||||||
'table': ProviderTable,
|
'table': ProviderTable,
|
||||||
'url': 'circuits:provider_list',
|
'url': 'circuits:provider_list',
|
||||||
@@ -61,17 +61,21 @@ SEARCH_TYPES = OrderedDict((
|
|||||||
'url': 'dcim:rack_list',
|
'url': 'dcim:rack_list',
|
||||||
}),
|
}),
|
||||||
('rackgroup', {
|
('rackgroup', {
|
||||||
'queryset': RackGroup.objects.prefetch_related('site').annotate(
|
'queryset': RackGroup.objects.add_related_count(
|
||||||
rack_count=Count('racks')
|
RackGroup.objects.all(),
|
||||||
).order_by(*RackGroup._meta.ordering),
|
Rack,
|
||||||
|
'group',
|
||||||
|
'rack_count',
|
||||||
|
cumulative=True
|
||||||
|
).prefetch_related('site'),
|
||||||
'filterset': RackGroupFilterSet,
|
'filterset': RackGroupFilterSet,
|
||||||
'table': RackGroupTable,
|
'table': RackGroupTable,
|
||||||
'url': 'dcim:rackgroup_list',
|
'url': 'dcim:rackgroup_list',
|
||||||
}),
|
}),
|
||||||
('devicetype', {
|
('devicetype', {
|
||||||
'queryset': DeviceType.objects.prefetch_related('manufacturer').annotate(
|
'queryset': DeviceType.objects.prefetch_related('manufacturer').annotate(
|
||||||
instance_count=Count('instances')
|
instance_count=get_subquery(Device, 'device_type')
|
||||||
).order_by(*DeviceType._meta.ordering),
|
),
|
||||||
'filterset': DeviceTypeFilterSet,
|
'filterset': DeviceTypeFilterSet,
|
||||||
'table': DeviceTypeTable,
|
'table': DeviceTypeTable,
|
||||||
'url': 'dcim:devicetype_list',
|
'url': 'dcim:devicetype_list',
|
||||||
@@ -86,8 +90,8 @@ SEARCH_TYPES = OrderedDict((
|
|||||||
}),
|
}),
|
||||||
('virtualchassis', {
|
('virtualchassis', {
|
||||||
'queryset': VirtualChassis.objects.prefetch_related('master').annotate(
|
'queryset': VirtualChassis.objects.prefetch_related('master').annotate(
|
||||||
member_count=Count('members', distinct=True)
|
member_count=get_subquery(Device, 'virtual_chassis')
|
||||||
).order_by(*VirtualChassis._meta.ordering),
|
),
|
||||||
'filterset': VirtualChassisFilterSet,
|
'filterset': VirtualChassisFilterSet,
|
||||||
'table': VirtualChassisTable,
|
'table': VirtualChassisTable,
|
||||||
'url': 'dcim:virtualchassis_list',
|
'url': 'dcim:virtualchassis_list',
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ from django.core.validators import URLValidator
|
|||||||
# Environment setup
|
# Environment setup
|
||||||
#
|
#
|
||||||
|
|
||||||
VERSION = '2.10-beta1'
|
VERSION = '2.10-beta2'
|
||||||
|
|
||||||
# Hostname
|
# Hostname
|
||||||
HOSTNAME = platform.node()
|
HOSTNAME = platform.node()
|
||||||
@@ -211,9 +211,6 @@ TASKS_REDIS_SENTINEL_TIMEOUT = TASKS_REDIS.get('SENTINEL_TIMEOUT', 10)
|
|||||||
TASKS_REDIS_PASSWORD = TASKS_REDIS.get('PASSWORD', '')
|
TASKS_REDIS_PASSWORD = TASKS_REDIS.get('PASSWORD', '')
|
||||||
TASKS_REDIS_DATABASE = TASKS_REDIS.get('DATABASE', 0)
|
TASKS_REDIS_DATABASE = TASKS_REDIS.get('DATABASE', 0)
|
||||||
TASKS_REDIS_SSL = TASKS_REDIS.get('SSL', False)
|
TASKS_REDIS_SSL = TASKS_REDIS.get('SSL', False)
|
||||||
# TODO: Remove in v2.10 (see #5171)
|
|
||||||
if 'DEFAULT_TIMEOUT' in TASKS_REDIS:
|
|
||||||
warnings.warn('DEFAULT_TIMEOUT is no longer supported under REDIS configuration. Set RQ_DEFAULT_TIMEOUT instead.')
|
|
||||||
|
|
||||||
# Caching
|
# Caching
|
||||||
if 'caching' not in REDIS:
|
if 'caching' not in REDIS:
|
||||||
|
|||||||
@@ -212,6 +212,7 @@ class ObjectPermissionAPIViewTestCase(TestCase):
|
|||||||
|
|
||||||
# Assign object permission
|
# Assign object permission
|
||||||
obj_perm = ObjectPermission(
|
obj_perm = ObjectPermission(
|
||||||
|
name='Test permission',
|
||||||
constraints={'site__name': 'Site 1'},
|
constraints={'site__name': 'Site 1'},
|
||||||
actions=['view']
|
actions=['view']
|
||||||
)
|
)
|
||||||
@@ -239,6 +240,7 @@ class ObjectPermissionAPIViewTestCase(TestCase):
|
|||||||
|
|
||||||
# Assign object permission
|
# Assign object permission
|
||||||
obj_perm = ObjectPermission(
|
obj_perm = ObjectPermission(
|
||||||
|
name='Test permission',
|
||||||
constraints={'site__name': 'Site 1'},
|
constraints={'site__name': 'Site 1'},
|
||||||
actions=['view']
|
actions=['view']
|
||||||
)
|
)
|
||||||
@@ -266,6 +268,7 @@ class ObjectPermissionAPIViewTestCase(TestCase):
|
|||||||
|
|
||||||
# Assign object permission
|
# Assign object permission
|
||||||
obj_perm = ObjectPermission(
|
obj_perm = ObjectPermission(
|
||||||
|
name='Test permission',
|
||||||
constraints={'site__name': 'Site 1'},
|
constraints={'site__name': 'Site 1'},
|
||||||
actions=['add']
|
actions=['add']
|
||||||
)
|
)
|
||||||
@@ -295,6 +298,7 @@ class ObjectPermissionAPIViewTestCase(TestCase):
|
|||||||
|
|
||||||
# Assign object permission
|
# Assign object permission
|
||||||
obj_perm = ObjectPermission(
|
obj_perm = ObjectPermission(
|
||||||
|
name='Test permission',
|
||||||
constraints={'site__name': 'Site 1'},
|
constraints={'site__name': 'Site 1'},
|
||||||
actions=['change']
|
actions=['change']
|
||||||
)
|
)
|
||||||
@@ -330,6 +334,7 @@ class ObjectPermissionAPIViewTestCase(TestCase):
|
|||||||
|
|
||||||
# Assign object permission
|
# Assign object permission
|
||||||
obj_perm = ObjectPermission(
|
obj_perm = ObjectPermission(
|
||||||
|
name='Test permission',
|
||||||
constraints={'site__name': 'Site 1'},
|
constraints={'site__name': 'Site 1'},
|
||||||
actions=['delete']
|
actions=['delete']
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -32,9 +32,11 @@ class ObjectView(ObjectPermissionRequiredMixin, View):
|
|||||||
"""
|
"""
|
||||||
Retrieve a single object for display.
|
Retrieve a single object for display.
|
||||||
|
|
||||||
queryset: The base queryset for retrieving the object.
|
queryset: The base queryset for retrieving the object
|
||||||
|
template_name: Name of the template to use
|
||||||
"""
|
"""
|
||||||
queryset = None
|
queryset = None
|
||||||
|
template_name = None
|
||||||
|
|
||||||
def get_required_permission(self):
|
def get_required_permission(self):
|
||||||
return get_permission_for_model(self.queryset.model, 'view')
|
return get_permission_for_model(self.queryset.model, 'view')
|
||||||
@@ -43,19 +45,29 @@ class ObjectView(ObjectPermissionRequiredMixin, View):
|
|||||||
"""
|
"""
|
||||||
Return self.template_name if set. Otherwise, resolve the template path by model app_label and name.
|
Return self.template_name if set. Otherwise, resolve the template path by model app_label and name.
|
||||||
"""
|
"""
|
||||||
if hasattr(self, 'template_name'):
|
if self.template_name is not None:
|
||||||
return self.template_name
|
return self.template_name
|
||||||
model_opts = self.queryset.model._meta
|
model_opts = self.queryset.model._meta
|
||||||
return f'{model_opts.app_label}/{model_opts.model_name}.html'
|
return f'{model_opts.app_label}/{model_opts.model_name}.html'
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get_extra_context(self, request, instance):
|
||||||
"""
|
"""
|
||||||
Generic GET handler for accessing an object by PK
|
Return any additional context data for the template.
|
||||||
|
|
||||||
|
request: The current request
|
||||||
|
instance: The object being viewed
|
||||||
"""
|
"""
|
||||||
instance = get_object_or_404(self.queryset, pk=pk)
|
return {}
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Generic GET handler for accessing an object by PK or slug
|
||||||
|
"""
|
||||||
|
instance = get_object_or_404(self.queryset, **kwargs)
|
||||||
|
|
||||||
return render(request, self.get_template_name(), {
|
return render(request, self.get_template_name(), {
|
||||||
'instance': instance,
|
'object': instance,
|
||||||
|
**self.get_extra_context(request, instance),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -377,6 +377,9 @@ table.report th a {
|
|||||||
ul.nav-tabs, ul.nav-pills {
|
ul.nav-tabs, ul.nav-pills {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
ul.nav-tabs li a {
|
||||||
|
padding: 8px 10px;
|
||||||
|
}
|
||||||
/* Fix progress bar margin inside table cells */
|
/* Fix progress bar margin inside table cells */
|
||||||
td .progress {
|
td .progress {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import base64
|
import base64
|
||||||
|
|
||||||
from Crypto.PublicKey import RSA
|
from Crypto.PublicKey import RSA
|
||||||
from django.db.models import Count
|
|
||||||
from django.http import HttpResponseBadRequest
|
from django.http import HttpResponseBadRequest
|
||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
@@ -13,6 +12,7 @@ from netbox.api.views import ModelViewSet
|
|||||||
from secrets import filters
|
from secrets import filters
|
||||||
from secrets.exceptions import InvalidKey
|
from secrets.exceptions import InvalidKey
|
||||||
from secrets.models import Secret, SecretRole, SessionKey, UserKey
|
from secrets.models import Secret, SecretRole, SessionKey, UserKey
|
||||||
|
from utilities.utils import get_subquery
|
||||||
from . import serializers
|
from . import serializers
|
||||||
|
|
||||||
ERR_USERKEY_MISSING = "No UserKey found for the current user."
|
ERR_USERKEY_MISSING = "No UserKey found for the current user."
|
||||||
@@ -35,8 +35,8 @@ class SecretsRootView(APIRootView):
|
|||||||
|
|
||||||
class SecretRoleViewSet(ModelViewSet):
|
class SecretRoleViewSet(ModelViewSet):
|
||||||
queryset = SecretRole.objects.annotate(
|
queryset = SecretRole.objects.annotate(
|
||||||
secret_count=Count('secrets')
|
secret_count=get_subquery(Secret, 'role')
|
||||||
).order_by(*SecretRole._meta.ordering)
|
)
|
||||||
serializer_class = serializers.SecretRoleSerializer
|
serializer_class = serializers.SecretRoleSerializer
|
||||||
filterset_class = filters.SecretRoleFilterSet
|
filterset_class = filters.SecretRoleFilterSet
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import django_filters
|
|||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from dcim.models import Device
|
from dcim.models import Device
|
||||||
from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet
|
from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet
|
||||||
from utilities.filters import BaseFilterSet, NameSlugSearchFilterSet, TagFilter
|
from utilities.filters import BaseFilterSet, NameSlugSearchFilterSet, TagFilter
|
||||||
from virtualization.models import VirtualMachine
|
from virtualization.models import VirtualMachine
|
||||||
from .models import Secret, SecretRole
|
from .models import Secret, SecretRole
|
||||||
@@ -21,7 +21,7 @@ class SecretRoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
|
|||||||
fields = ['id', 'name', 'slug']
|
fields = ['id', 'name', 'slug']
|
||||||
|
|
||||||
|
|
||||||
class SecretFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
class SecretFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
|
|||||||
@@ -143,31 +143,56 @@ class SecretForm(BootstrapMixin, CustomFieldModelForm):
|
|||||||
|
|
||||||
|
|
||||||
class SecretCSVForm(CustomFieldModelCSVForm):
|
class SecretCSVForm(CustomFieldModelCSVForm):
|
||||||
assigned_object_type = CSVModelChoiceField(
|
|
||||||
queryset=ContentType.objects.all(),
|
|
||||||
limit_choices_to=SECRET_ASSIGNMENT_MODELS,
|
|
||||||
to_field_name='model',
|
|
||||||
help_text='Side A type'
|
|
||||||
)
|
|
||||||
role = CSVModelChoiceField(
|
role = CSVModelChoiceField(
|
||||||
queryset=SecretRole.objects.all(),
|
queryset=SecretRole.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text='Assigned role'
|
help_text='Assigned role'
|
||||||
)
|
)
|
||||||
|
device = CSVModelChoiceField(
|
||||||
|
queryset=Device.objects.all(),
|
||||||
|
required=False,
|
||||||
|
to_field_name='name',
|
||||||
|
help_text='Assigned device'
|
||||||
|
)
|
||||||
|
virtual_machine = CSVModelChoiceField(
|
||||||
|
queryset=VirtualMachine.objects.all(),
|
||||||
|
required=False,
|
||||||
|
to_field_name='name',
|
||||||
|
help_text='Assigned VM'
|
||||||
|
)
|
||||||
plaintext = forms.CharField(
|
plaintext = forms.CharField(
|
||||||
help_text='Plaintext secret data'
|
help_text='Plaintext secret data'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Secret
|
model = Secret
|
||||||
fields = Secret.csv_headers
|
fields = ['role', 'name', 'plaintext', 'device', 'virtual_machine']
|
||||||
help_texts = {
|
help_texts = {
|
||||||
'name': 'Name or username',
|
'name': 'Name or username',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
super().clean()
|
||||||
|
|
||||||
|
device = self.cleaned_data.get('device')
|
||||||
|
virtual_machine = self.cleaned_data.get('virtual_machine')
|
||||||
|
|
||||||
|
# Validate device OR VM is assigned
|
||||||
|
if not device and not virtual_machine:
|
||||||
|
raise forms.ValidationError("Secret must be assigned to a device or a virtual machine")
|
||||||
|
if device and virtual_machine:
|
||||||
|
raise forms.ValidationError("Secret cannot be assigned to both a device and a virtual machine")
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
|
||||||
|
# Set device/VM assignment
|
||||||
|
self.instance.assigned_object = self.cleaned_data['device'] or self.cleaned_data['virtual_machine']
|
||||||
|
|
||||||
s = super().save(*args, **kwargs)
|
s = super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
# Set plaintext on instance
|
||||||
s.plaintext = str(self.cleaned_data['plaintext'])
|
s.plaintext = str(self.cleaned_data['plaintext'])
|
||||||
|
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -103,10 +103,10 @@ class SecretTestCase(
|
|||||||
|
|
||||||
device = Device.objects.get(name='Device 1')
|
device = Device.objects.get(name='Device 1')
|
||||||
csv_data = (
|
csv_data = (
|
||||||
"assigned_object_type,assigned_object_id,role,name,plaintext",
|
"device,role,name,plaintext",
|
||||||
f"device,{device.pk},Secret Role 1,Secret 4,abcdefghij",
|
f"{device.name},Secret Role 1,Secret 4,abcdefghij",
|
||||||
f"device,{device.pk},Secret Role 1,Secret 5,abcdefghij",
|
f"{device.name},Secret Role 1,Secret 5,abcdefghij",
|
||||||
f"device,{device.pk},Secret Role 1,Secret 6,abcdefghij",
|
f"{device.name},Secret Role 1,Secret 6,abcdefghij",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Set the session_key cookie on the request
|
# Set the session_key cookie on the request
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import base64
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.db.models import Count
|
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
|
from utilities.utils import get_subquery
|
||||||
from . import filters, forms, tables
|
from . import filters, forms, tables
|
||||||
from .models import SecretRole, Secret, SessionKey, UserKey
|
from .models import SecretRole, Secret, SessionKey, UserKey
|
||||||
|
|
||||||
@@ -27,7 +27,9 @@ def get_session_key(request):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class SecretRoleListView(generic.ObjectListView):
|
class SecretRoleListView(generic.ObjectListView):
|
||||||
queryset = SecretRole.objects.annotate(secret_count=Count('secrets')).order_by(*SecretRole._meta.ordering)
|
queryset = SecretRole.objects.annotate(
|
||||||
|
secret_count=get_subquery(Secret, 'role')
|
||||||
|
)
|
||||||
table = tables.SecretRoleTable
|
table = tables.SecretRoleTable
|
||||||
|
|
||||||
|
|
||||||
@@ -47,7 +49,9 @@ class SecretRoleBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
|
|
||||||
class SecretRoleBulkDeleteView(generic.BulkDeleteView):
|
class SecretRoleBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = SecretRole.objects.annotate(secret_count=Count('secrets')).order_by(*SecretRole._meta.ordering)
|
queryset = SecretRole.objects.annotate(
|
||||||
|
secret_count=get_subquery(Secret, 'role')
|
||||||
|
)
|
||||||
table = tables.SecretRoleTable
|
table = tables.SecretRoleTable
|
||||||
|
|
||||||
|
|
||||||
@@ -66,14 +70,6 @@ class SecretListView(generic.ObjectListView):
|
|||||||
class SecretView(generic.ObjectView):
|
class SecretView(generic.ObjectView):
|
||||||
queryset = Secret.objects.all()
|
queryset = Secret.objects.all()
|
||||||
|
|
||||||
def get(self, request, pk):
|
|
||||||
|
|
||||||
secret = get_object_or_404(self.queryset, pk=pk)
|
|
||||||
|
|
||||||
return render(request, 'secrets/secret.html', {
|
|
||||||
'secret': secret,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class SecretEditView(generic.ObjectEditView):
|
class SecretEditView(generic.ObjectEditView):
|
||||||
queryset = Secret.objects.all()
|
queryset = Secret.objects.all()
|
||||||
|
|||||||
@@ -4,15 +4,15 @@
|
|||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% load plugins %}
|
{% load plugins %}
|
||||||
|
|
||||||
{% block title %}{{ circuit }}{% endblock %}
|
{% block title %}{{ object }}{% endblock %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<div class="row noprint">
|
<div class="row noprint">
|
||||||
<div class="col-sm-8 col-md-9">
|
<div class="col-sm-8 col-md-9">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="{% url 'circuits:circuit_list' %}">Circuits</a></li>
|
<li><a href="{% url 'circuits:circuit_list' %}">Circuits</a></li>
|
||||||
<li><a href="{% url 'circuits:circuit_list' %}?provider={{ circuit.provider.slug }}">{{ circuit.provider }}</a></li>
|
<li><a href="{% url 'circuits:circuit_list' %}?provider={{ object.provider.slug }}">{{ object.provider }}</a></li>
|
||||||
<li>{{ circuit.cid }}</li>
|
<li>{{ object.cid }}</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-4 col-md-3">
|
<div class="col-sm-4 col-md-3">
|
||||||
@@ -29,29 +29,29 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right noprint">
|
<div class="pull-right noprint">
|
||||||
{% plugin_buttons circuit %}
|
{% plugin_buttons object %}
|
||||||
{% if perms.circuits.add_circuit %}
|
{% if perms.circuits.add_circuit %}
|
||||||
{% clone_button circuit %}
|
{% clone_button object %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.circuits.change_circuit %}
|
{% if perms.circuits.change_circuit %}
|
||||||
{% edit_button circuit %}
|
{% edit_button object %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.circuits.delete_circuit %}
|
{% if perms.circuits.delete_circuit %}
|
||||||
{% delete_button circuit %}
|
{% delete_button object %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<h1>{{ circuit }}</h1>
|
<h1>{{ object }}</h1>
|
||||||
{% include 'inc/created_updated.html' with obj=circuit %}
|
{% include 'inc/created_updated.html' %}
|
||||||
<div class="pull-right noprint">
|
<div class="pull-right noprint">
|
||||||
{% custom_links circuit %}
|
{% custom_links object %}
|
||||||
</div>
|
</div>
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
||||||
<a href="{{ circuit.get_absolute_url }}">Circuit</a>
|
<a href="{{ object.get_absolute_url }}">Circuit</a>
|
||||||
</li>
|
</li>
|
||||||
{% if perms.extras.view_objectchange %}
|
{% if perms.extras.view_objectchange %}
|
||||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||||
<a href="{% url 'circuits:circuit_changelog' pk=circuit.pk %}">Change Log</a>
|
<a href="{% url 'circuits:circuit_changelog' pk=object.pk %}">Change Log</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -68,31 +68,31 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Status</td>
|
<td>Status</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="label label-{{ circuit.get_status_class }}">{{ circuit.get_status_display }}</span>
|
<span class="label label-{{ object.get_status_class }}">{{ object.get_status_display }}</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Provider</td>
|
<td>Provider</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'circuits:provider' slug=circuit.provider.slug %}">{{ circuit.provider }}</a>
|
<a href="{% url 'circuits:provider' slug=object.provider.slug %}">{{ object.provider }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Circuit ID</td>
|
<td>Circuit ID</td>
|
||||||
<td>{{ circuit.cid }}</td>
|
<td>{{ object.cid }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Type</td>
|
<td>Type</td>
|
||||||
<td><a href="{{ circuit.type.get_absolute_url }}">{{ circuit.type }}</a></td>
|
<td><a href="{{ object.type.get_absolute_url }}">{{ object.type }}</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Tenant</td>
|
<td>Tenant</td>
|
||||||
<td>
|
<td>
|
||||||
{% if circuit.tenant %}
|
{% if object.tenant %}
|
||||||
{% if circuit.tenant.group %}
|
{% if object.tenant.group %}
|
||||||
<a href="{{ circuit.tenant.group.get_absolute_url }}">{{ circuit.tenant.group }}</a> /
|
<a href="{{ object.tenant.group.get_absolute_url }}">{{ object.tenant.group }}</a> /
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{{ circuit.tenant.get_absolute_url }}">{{ circuit.tenant }}</a>
|
<a href="{{ object.tenant.get_absolute_url }}">{{ object.tenant }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">None</span>
|
<span class="text-muted">None</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -100,43 +100,43 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Install Date</td>
|
<td>Install Date</td>
|
||||||
<td>{{ circuit.install_date|placeholder }}</td>
|
<td>{{ object.install_date|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Commit Rate</td>
|
<td>Commit Rate</td>
|
||||||
<td>{{ circuit.commit_rate|humanize_speed|placeholder }}</td>
|
<td>{{ object.commit_rate|humanize_speed|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Description</td>
|
<td>Description</td>
|
||||||
<td>{{ circuit.description|placeholder }}</td>
|
<td>{{ object.description|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% include 'inc/custom_fields_panel.html' with obj=circuit %}
|
{% include 'inc/custom_fields_panel.html' %}
|
||||||
{% include 'extras/inc/tags_panel.html' with tags=circuit.tags.all url='circuits:circuit_list' %}
|
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='circuits:circuit_list' %}
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Comments</strong>
|
<strong>Comments</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body rendered-markdown">
|
<div class="panel-body rendered-markdown">
|
||||||
{% if circuit.comments %}
|
{% if object.comments %}
|
||||||
{{ circuit.comments|render_markdown }}
|
{{ object.comments|render_markdown }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">None</span>
|
<span class="text-muted">None</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% plugin_left_page circuit %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
{% include 'circuits/inc/circuit_termination.html' with termination=termination_a side='A' %}
|
{% include 'circuits/inc/circuit_termination.html' with termination=termination_a side='A' %}
|
||||||
{% include 'circuits/inc/circuit_termination.html' with termination=termination_z side='Z' %}
|
{% include 'circuits/inc/circuit_termination.html' with termination=termination_z side='Z' %}
|
||||||
{% plugin_right_page circuit %}
|
{% plugin_right_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
{% plugin_full_width_page circuit %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
{% if not termination and perms.circuits.add_circuittermination %}
|
{% if not termination and perms.circuits.add_circuittermination %}
|
||||||
<a href="{% url 'circuits:circuittermination_add' circuit=circuit.pk %}?term_side={{ side }}" class="btn btn-xs btn-success">
|
<a href="{% url 'circuits:circuittermination_add' circuit=object.pk %}?term_side={{ side }}" class="btn btn-xs btn-success">
|
||||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add
|
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -12,12 +12,12 @@
|
|||||||
<a href="{% url 'circuits:circuittermination_edit' pk=termination.pk %}" class="btn btn-xs btn-warning">
|
<a href="{% url 'circuits:circuittermination_edit' pk=termination.pk %}" class="btn btn-xs btn-warning">
|
||||||
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
|
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'circuits:circuit_terminations_swap' pk=circuit.pk %}" class="btn btn-xs btn-primary">
|
<a href="{% url 'circuits:circuit_terminations_swap' pk=object.pk %}" class="btn btn-xs btn-primary">
|
||||||
<span class="mdi mdi-swap-vertical" aria-hidden="true"></span> Swap
|
<span class="mdi mdi-swap-vertical" aria-hidden="true"></span> Swap
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if termination and perms.circuits.delete_circuittermination %}
|
{% if termination and perms.circuits.delete_circuittermination %}
|
||||||
<a href="{% url 'circuits:circuittermination_delete' pk=termination.pk %}?return_url={{ circuit.get_absolute_url }}" class="btn btn-xs btn-danger">
|
<a href="{% url 'circuits:circuittermination_delete' pk=termination.pk %}?return_url={{ object.get_absolute_url }}" class="btn btn-xs btn-danger">
|
||||||
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
|
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -67,10 +67,10 @@
|
|||||||
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span> Connect
|
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span> Connect
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-right">
|
<ul class="dropdown-menu dropdown-menu-right">
|
||||||
<li><a href="{% url 'circuits:circuittermination_connect' termination_a_id=termination.pk termination_b_type='interface' %}?termination_b_site={{ termination.site.pk }}&return_url={{ circuit.get_absolute_url }}">Interface</a></li>
|
<li><a href="{% url 'circuits:circuittermination_connect' termination_a_id=termination.pk termination_b_type='interface' %}?termination_b_site={{ termination.site.pk }}&return_url={{ object.get_absolute_url }}">Interface</a></li>
|
||||||
<li><a href="{% url 'circuits:circuittermination_connect' termination_a_id=termination.pk termination_b_type='front-port' %}?termination_b_site={{ termination.site.pk }}&return_url={{ circuit.get_absolute_url }}">Front Port</a></li>
|
<li><a href="{% url 'circuits:circuittermination_connect' termination_a_id=termination.pk termination_b_type='front-port' %}?termination_b_site={{ termination.site.pk }}&return_url={{ object.get_absolute_url }}">Front Port</a></li>
|
||||||
<li><a href="{% url 'circuits:circuittermination_connect' termination_a_id=termination.pk termination_b_type='rear-port' %}?termination_b_site={{ termination.site.pk }}&return_url={{ circuit.get_absolute_url }}">Rear Port</a></li>
|
<li><a href="{% url 'circuits:circuittermination_connect' termination_a_id=termination.pk termination_b_type='rear-port' %}?termination_b_site={{ termination.site.pk }}&return_url={{ object.get_absolute_url }}">Rear Port</a></li>
|
||||||
<li><a href="{% url 'circuits:circuittermination_connect' termination_a_id=termination.pk termination_b_type='circuit-termination' %}?termination_b_site={{ termination.site.pk }}&return_url={{ circuit.get_absolute_url }}">Circuit Termination</a></li>
|
<li><a href="{% url 'circuits:circuittermination_connect' termination_a_id=termination.pk termination_b_type='circuit-termination' %}?termination_b_site={{ termination.site.pk }}&return_url={{ object.get_absolute_url }}">Circuit Termination</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,14 +5,14 @@
|
|||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% load plugins %}
|
{% load plugins %}
|
||||||
|
|
||||||
{% block title %}{{ provider }}{% endblock %}
|
{% block title %}{{ object }}{% endblock %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<div class="row noprint">
|
<div class="row noprint">
|
||||||
<div class="col-sm-8 col-md-9">
|
<div class="col-sm-8 col-md-9">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="{% url 'circuits:provider_list' %}">Providers</a></li>
|
<li><a href="{% url 'circuits:provider_list' %}">Providers</a></li>
|
||||||
<li>{{ provider }}</li>
|
<li>{{ object }}</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-4 col-md-3">
|
<div class="col-sm-4 col-md-3">
|
||||||
@@ -29,29 +29,29 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right noprint">
|
<div class="pull-right noprint">
|
||||||
{% plugin_buttons provider %}
|
{% plugin_buttons object %}
|
||||||
{% if perms.circuits.add_provider %}
|
{% if perms.circuits.add_provider %}
|
||||||
{% clone_button provider %}
|
{% clone_button object %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.circuits.change_provider %}
|
{% if perms.circuits.change_provider %}
|
||||||
{% edit_button provider %}
|
{% edit_button object %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.circuits.delete_provider %}
|
{% if perms.circuits.delete_provider %}
|
||||||
{% delete_button provider %}
|
{% delete_button object %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<h1>{{ provider }}</h1>
|
<h1>{{ object }}</h1>
|
||||||
{% include 'inc/created_updated.html' with obj=provider %}
|
{% include 'inc/created_updated.html' %}
|
||||||
<div class="pull-right noprint">
|
<div class="pull-right noprint">
|
||||||
{% custom_links provider %}
|
{% custom_links object %}
|
||||||
</div>
|
</div>
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
||||||
<a href="{{ provider.get_absolute_url }}">Provider</a>
|
<a href="{{ object.get_absolute_url }}">Provider</a>
|
||||||
</li>
|
</li>
|
||||||
{% if perms.extras.view_objectchange %}
|
{% if perms.extras.view_objectchange %}
|
||||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||||
<a href="{% url 'circuits:provider_changelog' slug=provider.slug %}">Change Log</a>
|
<a href="{% url 'circuits:provider_changelog' slug=object.slug %}">Change Log</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -67,17 +67,17 @@
|
|||||||
<table class="table table-hover panel-body attr-table">
|
<table class="table table-hover panel-body attr-table">
|
||||||
<tr>
|
<tr>
|
||||||
<td>ASN</td>
|
<td>ASN</td>
|
||||||
<td>{{ provider.asn|placeholder }}</td>
|
<td>{{ object.asn|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Account</td>
|
<td>Account</td>
|
||||||
<td>{{ provider.account|placeholder }}</td>
|
<td>{{ object.account|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Customer Portal</td>
|
<td>Customer Portal</td>
|
||||||
<td>
|
<td>
|
||||||
{% if provider.portal_url %}
|
{% if object.portal_url %}
|
||||||
<a href="{{ provider.portal_url }}">{{ provider.portal_url }}</a>
|
<a href="{{ object.portal_url }}">{{ object.portal_url }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">—</span>
|
<span class="text-muted">—</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -85,35 +85,35 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>NOC Contact</td>
|
<td>NOC Contact</td>
|
||||||
<td class="rendered-markdown">{{ provider.noc_contact|render_markdown|placeholder }}</td>
|
<td class="rendered-markdown">{{ object.noc_contact|render_markdown|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Admin Contact</td>
|
<td>Admin Contact</td>
|
||||||
<td class="rendered-markdown">{{ provider.admin_contact|render_markdown|placeholder }}</td>
|
<td class="rendered-markdown">{{ object.admin_contact|render_markdown|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Circuits</td>
|
<td>Circuits</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'circuits:circuit_list' %}?provider={{ provider.slug }}">{{ circuits_table.rows|length }}</a>
|
<a href="{% url 'circuits:circuit_list' %}?provider={{ object.slug }}">{{ circuits_table.rows|length }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% include 'inc/custom_fields_panel.html' with obj=provider %}
|
{% include 'inc/custom_fields_panel.html' %}
|
||||||
{% include 'extras/inc/tags_panel.html' with tags=provider.tags.all url='circuits:provider_list' %}
|
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='circuits:object_list' %}
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Comments</strong>
|
<strong>Comments</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body rendered-markdown">
|
<div class="panel-body rendered-markdown">
|
||||||
{% if provider.comments %}
|
{% if object.comments %}
|
||||||
{{ provider.comments|render_markdown }}
|
{{ object.comments|render_markdown }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">None</span>
|
<span class="text-muted">None</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% plugin_left_page provider %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
@@ -123,19 +123,19 @@
|
|||||||
{% include 'inc/table.html' with table=circuits_table %}
|
{% include 'inc/table.html' with table=circuits_table %}
|
||||||
{% if perms.circuits.add_circuit %}
|
{% if perms.circuits.add_circuit %}
|
||||||
<div class="panel-footer text-right noprint">
|
<div class="panel-footer text-right noprint">
|
||||||
<a href="{% url 'circuits:circuit_add' %}?provider={{ provider.pk }}" class="btn btn-xs btn-primary">
|
<a href="{% url 'circuits:circuit_add' %}?provider={{ object.pk }}" class="btn btn-xs btn-primary">
|
||||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add circuit
|
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add circuit
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% include 'inc/paginator.html' with paginator=circuits_table.paginator page=circuits_table.page %}
|
{% include 'inc/paginator.html' with paginator=circuits_table.paginator page=circuits_table.page %}
|
||||||
{% plugin_right_page provider %}
|
{% plugin_right_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
{% plugin_full_width_page provider %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -9,31 +9,31 @@
|
|||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="{% url 'dcim:cable_list' %}">Cables</a></li>
|
<li><a href="{% url 'dcim:cable_list' %}">Cables</a></li>
|
||||||
<li>{{ cable }}</li>
|
<li>{{ object }}</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right noprint">
|
<div class="pull-right noprint">
|
||||||
{% plugin_buttons cable %}
|
{% plugin_buttons object %}
|
||||||
{% if perms.dcim.change_cable %}
|
{% if perms.dcim.change_cable %}
|
||||||
{% edit_button cable %}
|
{% edit_button object %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.delete_cable %}
|
{% if perms.dcim.delete_cable %}
|
||||||
{% delete_button cable %}
|
{% delete_button object %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<h1>{% block title %}Cable {{ cable }}{% endblock %}</h1>
|
<h1>{% block title %}Cable {{ object }}{% endblock %}</h1>
|
||||||
{% include 'inc/created_updated.html' with obj=cable %}
|
{% include 'inc/created_updated.html' %}
|
||||||
<div class="pull-right noprint">
|
<div class="pull-right noprint">
|
||||||
{% custom_links cable %}
|
{% custom_links object %}
|
||||||
</div>
|
</div>
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
||||||
<a href="{{ cable.get_absolute_url }}">Cable</a>
|
<a href="{{ object.get_absolute_url }}">Cable</a>
|
||||||
</li>
|
</li>
|
||||||
{% if perms.extras.view_objectchange %}
|
{% if perms.extras.view_objectchange %}
|
||||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||||
<a href="{% url 'dcim:cable_changelog' pk=cable.pk %}">Change Log</a>
|
<a href="{% url 'dcim:cable_changelog' pk=object.pk %}">Change Log</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -49,23 +49,23 @@
|
|||||||
<table class="table table-hover panel-body attr-table">
|
<table class="table table-hover panel-body attr-table">
|
||||||
<tr>
|
<tr>
|
||||||
<td>Type</td>
|
<td>Type</td>
|
||||||
<td>{{ cable.get_type_display|placeholder }}</td>
|
<td>{{ object.get_type_display|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Status</td>
|
<td>Status</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="label label-{{ cable.get_status_class }}">{{ cable.get_status_display }}</span>
|
<span class="label label-{{ object.get_status_class }}">{{ object.get_status_display }}</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Label</td>
|
<td>Label</td>
|
||||||
<td>{{ cable.label|placeholder }}</td>
|
<td>{{ object.label|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Color</td>
|
<td>Color</td>
|
||||||
<td>
|
<td>
|
||||||
{% if cable.color %}
|
{% if object.color %}
|
||||||
<span class="label color-block" style="background-color: #{{ cable.color }}"> </span>
|
<span class="label color-block" style="background-color: #{{ object.color }}"> </span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">—</span>
|
<span class="text-muted">—</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -74,8 +74,8 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Length</td>
|
<td>Length</td>
|
||||||
<td>
|
<td>
|
||||||
{% if cable.length %}
|
{% if object.length %}
|
||||||
{{ cable.length }} {{ cable.get_length_unit_display }}
|
{{ object.length }} {{ object.get_length_unit_display }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">—</span>
|
<span class="text-muted">—</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -83,29 +83,29 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% include 'inc/custom_fields_panel.html' with obj=cable %}
|
{% include 'inc/custom_fields_panel.html' %}
|
||||||
{% include 'extras/inc/tags_panel.html' with tags=cable.tags.all url='dcim:cable_list' %}
|
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='dcim:cable_list' %}
|
||||||
{% plugin_left_page cable %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Termination A</strong>
|
<strong>Termination A</strong>
|
||||||
</div>
|
</div>
|
||||||
{% include 'dcim/inc/cable_termination.html' with termination=cable.termination_a %}
|
{% include 'dcim/inc/cable_termination.html' with termination=object.termination_a %}
|
||||||
</div>
|
</div>
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Termination B</strong>
|
<strong>Termination B</strong>
|
||||||
</div>
|
</div>
|
||||||
{% include 'dcim/inc/cable_termination.html' with termination=cable.termination_b %}
|
{% include 'dcim/inc/cable_termination.html' with termination=object.termination_b %}
|
||||||
</div>
|
</div>
|
||||||
{% plugin_right_page cable %}
|
{% plugin_right_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
{% plugin_full_width_page cable %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<h1>{% block title %}Cable Trace for {{ obj|meta:"verbose_name"|bettertitle }} {{ obj }}{% endblock %}</h1>
|
<h1>{% block title %}Cable Trace for {{ object|meta:"verbose_name"|bettertitle }} {{ object }}{% endblock %}</h1>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|||||||
@@ -13,71 +13,71 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Device</td>
|
<td>Device</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ instance.device.get_absolute_url }}">{{ instance.device }}</a>
|
<a href="{{ object.device.get_absolute_url }}">{{ object.device }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Name</td>
|
<td>Name</td>
|
||||||
<td>{{ instance.name }}</td>
|
<td>{{ object.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Label</td>
|
<td>Label</td>
|
||||||
<td>{{ instance.label|placeholder }}</td>
|
<td>{{ object.label|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Type</td>
|
<td>Type</td>
|
||||||
<td>{{ instance.get_type_display }}</td>
|
<td>{{ object.get_type_display }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Description</td>
|
<td>Description</td>
|
||||||
<td>{{ instance.description|placeholder }}</td>
|
<td>{{ object.description|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% include 'extras/inc/tags_panel.html' with tags=instance.tags.all %}
|
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all %}
|
||||||
{% plugin_left_page instance %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Connection</strong>
|
<strong>Connection</strong>
|
||||||
</div>
|
</div>
|
||||||
{% if instance.cable %}
|
{% if object.cable %}
|
||||||
<table class="table table-hover panel-body attr-table">
|
<table class="table table-hover panel-body attr-table">
|
||||||
<tr>
|
<tr>
|
||||||
<td>Cable</td>
|
<td>Cable</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ instance.cable.get_absolute_url }}">{{ instance.cable }}</a>
|
<a href="{{ object.cable.get_absolute_url }}">{{ object.cable }}</a>
|
||||||
<a href="{% url 'dcim:consoleport_trace' pk=instance.pk %}" class="btn btn-primary btn-xs" title="Trace">
|
<a href="{% url 'dcim:consoleport_trace' pk=object.pk %}" class="btn btn-primary btn-xs" title="Trace">
|
||||||
<i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
|
<i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if instance.connected_endpoint %}
|
{% if object.connected_endpoint %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>Device</td>
|
<td>Device</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ instance.connected_endpoint.device.get_absolute_url }}">{{ instance.connected_endpoint.device }}</a>
|
<a href="{{ object.connected_endpoint.device.get_absolute_url }}">{{ object.connected_endpoint.device }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Name</td>
|
<td>Name</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ instance.connected_endpoint.get_absolute_url }}">{{ instance.connected_endpoint.name }}</a>
|
<a href="{{ object.connected_endpoint.get_absolute_url }}">{{ object.connected_endpoint.name }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Type</td>
|
<td>Type</td>
|
||||||
<td>{{ instance.connected_endpoint.get_type_display|placeholder }}</td>
|
<td>{{ object.connected_endpoint.get_type_display|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Description</td>
|
<td>Description</td>
|
||||||
<td>{{ instance.connected_endpoint.description|placeholder }}</td>
|
<td>{{ object.connected_endpoint.description|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Path Status</td>
|
<td>Path Status</td>
|
||||||
<td>
|
<td>
|
||||||
{% if instance.path.is_active %}
|
{% if object.path.is_active %}
|
||||||
<span class="label label-success">Reachable</span>
|
<span class="label label-success">Reachable</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="label label-danger">Not Reachable</span>
|
<span class="label label-danger">Not Reachable</span>
|
||||||
@@ -95,21 +95,21 @@
|
|||||||
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span> Connect
|
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span> Connect
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-right">
|
<ul class="dropdown-menu dropdown-menu-right">
|
||||||
<li><a href="{% url 'dcim:consoleport_connect' termination_a_id=instance.pk termination_b_type='console-server-port' %}?return_url={{ instance.get_absolute_url }}">Console Server Port</a></li>
|
<li><a href="{% url 'dcim:consoleport_connect' termination_a_id=object.pk termination_b_type='console-server-port' %}?return_url={{ object.get_absolute_url }}">Console Server Port</a></li>
|
||||||
<li><a href="{% url 'dcim:consoleport_connect' termination_a_id=instance.pk termination_b_type='front-port' %}?return_url={{ instance.get_absolute_url }}">Front Port</a></li>
|
<li><a href="{% url 'dcim:consoleport_connect' termination_a_id=object.pk termination_b_type='front-port' %}?return_url={{ object.get_absolute_url }}">Front Port</a></li>
|
||||||
<li><a href="{% url 'dcim:consoleport_connect' termination_a_id=instance.pk termination_b_type='rear-port' %}?return_url={{ instance.get_absolute_url }}">Rear Port</a></li>
|
<li><a href="{% url 'dcim:consoleport_connect' termination_a_id=object.pk termination_b_type='rear-port' %}?return_url={{ object.get_absolute_url }}">Rear Port</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% plugin_right_page instance %}
|
{% plugin_right_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
{% plugin_full_width_page instance %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -13,71 +13,71 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Device</td>
|
<td>Device</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ instance.device.get_absolute_url }}">{{ instance.device }}</a>
|
<a href="{{ object.device.get_absolute_url }}">{{ object.device }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Name</td>
|
<td>Name</td>
|
||||||
<td>{{ instance.name }}</td>
|
<td>{{ object.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Label</td>
|
<td>Label</td>
|
||||||
<td>{{ instance.label|placeholder }}</td>
|
<td>{{ object.label|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Type</td>
|
<td>Type</td>
|
||||||
<td>{{ instance.get_type_display }}</td>
|
<td>{{ object.get_type_display }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Description</td>
|
<td>Description</td>
|
||||||
<td>{{ instance.description|placeholder }}</td>
|
<td>{{ object.description|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% include 'extras/inc/tags_panel.html' with tags=instance.tags.all %}
|
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all %}
|
||||||
{% plugin_left_page instance %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Connection</strong>
|
<strong>Connection</strong>
|
||||||
</div>
|
</div>
|
||||||
{% if instance.cable %}
|
{% if object.cable %}
|
||||||
<table class="table table-hover panel-body attr-table">
|
<table class="table table-hover panel-body attr-table">
|
||||||
<tr>
|
<tr>
|
||||||
<td>Cable</td>
|
<td>Cable</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ instance.cable.get_absolute_url }}">{{ instance.cable }}</a>
|
<a href="{{ object.cable.get_absolute_url }}">{{ object.cable }}</a>
|
||||||
<a href="{% url 'dcim:consoleserverport_trace' pk=instance.pk %}" class="btn btn-primary btn-xs" title="Trace">
|
<a href="{% url 'dcim:consoleserverport_trace' pk=object.pk %}" class="btn btn-primary btn-xs" title="Trace">
|
||||||
<i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
|
<i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if instance.connected_endpoint %}
|
{% if object.connected_endpoint %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>Device</td>
|
<td>Device</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ instance.connected_endpoint.device.get_absolute_url }}">{{ instance.connected_endpoint.device }}</a>
|
<a href="{{ object.connected_endpoint.device.get_absolute_url }}">{{ object.connected_endpoint.device }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Name</td>
|
<td>Name</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ instance.connected_endpoint.get_absolute_url }}">{{ instance.connected_endpoint.name }}</a>
|
<a href="{{ object.connected_endpoint.get_absolute_url }}">{{ object.connected_endpoint.name }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Type</td>
|
<td>Type</td>
|
||||||
<td>{{ instance.connected_endpoint.get_type_display|placeholder }}</td>
|
<td>{{ object.connected_endpoint.get_type_display|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Description</td>
|
<td>Description</td>
|
||||||
<td>{{ instance.connected_endpoint.description|placeholder }}</td>
|
<td>{{ object.connected_endpoint.description|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Path Status</td>
|
<td>Path Status</td>
|
||||||
<td>
|
<td>
|
||||||
{% if instance.path.is_active %}
|
{% if object.path.is_active %}
|
||||||
<span class="label label-success">Reachable</span>
|
<span class="label label-success">Reachable</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="label label-danger">Not Reachable</span>
|
<span class="label label-danger">Not Reachable</span>
|
||||||
@@ -95,21 +95,21 @@
|
|||||||
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span> Connect
|
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span> Connect
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-right">
|
<ul class="dropdown-menu dropdown-menu-right">
|
||||||
<li><a href="{% url 'dcim:consoleserverport_connect' termination_a_id=instance.pk termination_b_type='console-port' %}?return_url={{ instance.get_absolute_url }}">Console Port</a></li>
|
<li><a href="{% url 'dcim:consoleserverport_connect' termination_a_id=object.pk termination_b_type='console-port' %}?return_url={{ object.get_absolute_url }}">Console Port</a></li>
|
||||||
<li><a href="{% url 'dcim:consoleserverport_connect' termination_a_id=instance.pk termination_b_type='front-port' %}?return_url={{ instance.get_absolute_url }}">Front Port</a></li>
|
<li><a href="{% url 'dcim:consoleserverport_connect' termination_a_id=object.pk termination_b_type='front-port' %}?return_url={{ object.get_absolute_url }}">Front Port</a></li>
|
||||||
<li><a href="{% url 'dcim:consoleserverport_connect' termination_a_id=instance.pk termination_b_type='rear-port' %}?return_url={{ instance.get_absolute_url }}">Rear Port</a></li>
|
<li><a href="{% url 'dcim:consoleserverport_connect' termination_a_id=object.pk termination_b_type='rear-port' %}?return_url={{ object.get_absolute_url }}">Rear Port</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% plugin_right_page instance %}
|
{% plugin_right_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
{% plugin_full_width_page instance %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -20,20 +20,20 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Site</td>
|
<td>Site</td>
|
||||||
<td>
|
<td>
|
||||||
{% if device.site.region %}
|
{% if object.site.region %}
|
||||||
<a href="{{ device.site.region.get_absolute_url }}">{{ device.site.region }}</a> /
|
<a href="{{ object.site.region.get_absolute_url }}">{{ object.site.region }}</a> /
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{% url 'dcim:site' slug=device.site.slug %}">{{ device.site }}</a>
|
<a href="{% url 'dcim:site' slug=object.site.slug %}">{{ object.site }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Rack</td>
|
<td>Rack</td>
|
||||||
<td>
|
<td>
|
||||||
{% if device.rack %}
|
{% if object.rack %}
|
||||||
{% if device.rack.group %}
|
{% if object.rack.group %}
|
||||||
<a href="{{ device.rack.group.get_absolute_url }}">{{ device.rack.group }}</a> /
|
<a href="{{ object.rack.group.get_absolute_url }}">{{ object.rack.group }}</a> /
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{% url 'dcim:rack' pk=device.rack.pk %}">{{ device.rack }}</a>
|
<a href="{% url 'dcim:rack' pk=object.rack.pk %}">{{ object.rack }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">None</span>
|
<span class="text-muted">None</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -42,16 +42,16 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Position</td>
|
<td>Position</td>
|
||||||
<td>
|
<td>
|
||||||
{% if device.parent_bay %}
|
{% if object.parent_bay %}
|
||||||
{% with device.parent_bay.device as parent %}
|
{% with object.parent_bay.device as parent %}
|
||||||
<a href="{{ parent.get_absolute_url }}">{{ parent }}</a> / {{ device.parent_bay }}
|
<a href="{{ parent.get_absolute_url }}">{{ parent }}</a> / {{ object.parent_bay }}
|
||||||
{% if parent.position %}
|
{% if parent.position %}
|
||||||
(U{{ parent.position }} / {{ parent.get_face_display }})
|
(U{{ parent.position }} / {{ parent.get_face_display }})
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% elif device.rack and device.position %}
|
{% elif object.rack and object.position %}
|
||||||
<span>U{{ device.position }} / {{ device.get_face_display }}</span>
|
<span>U{{ object.position }} / {{ object.get_face_display }}</span>
|
||||||
{% elif device.rack and device.device_type.u_height %}
|
{% elif object.rack and object.device_type.u_height %}
|
||||||
<span class="label label-warning">Not racked</span>
|
<span class="label label-warning">Not racked</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">—</span>
|
<span class="text-muted">—</span>
|
||||||
@@ -61,11 +61,11 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Tenant</td>
|
<td>Tenant</td>
|
||||||
<td>
|
<td>
|
||||||
{% if device.tenant %}
|
{% if object.tenant %}
|
||||||
{% if device.tenant.group %}
|
{% if object.tenant.group %}
|
||||||
<a href="{{ device.tenant.group.get_absolute_url }}">{{ device.tenant.group }}</a> /
|
<a href="{{ object.tenant.group.get_absolute_url }}">{{ object.tenant.group }}</a> /
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{{ device.tenant.get_absolute_url }}">{{ device.tenant }}</a>
|
<a href="{{ object.tenant.get_absolute_url }}">{{ object.tenant }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">None</span>
|
<span class="text-muted">None</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -74,16 +74,16 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Device Type</td>
|
<td>Device Type</td>
|
||||||
<td>
|
<td>
|
||||||
<span><a href="{% url 'dcim:devicetype' pk=device.device_type.pk %}">{{ device.device_type.display_name }}</a> ({{ device.device_type.u_height }}U)</span>
|
<span><a href="{% url 'dcim:devicetype' pk=object.device_type.pk %}">{{ object.device_type.display_name }}</a> ({{ object.device_type.u_height }}U)</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Serial Number</td>
|
<td>Serial Number</td>
|
||||||
<td><span>{{ device.serial|placeholder }}</span></td>
|
<td><span>{{ object.serial|placeholder }}</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Asset Tag</td>
|
<td>Asset Tag</td>
|
||||||
<td><span>{{ device.asset_tag|placeholder }}</span></td>
|
<td><span>{{ object.asset_tag|placeholder }}</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -100,18 +100,18 @@
|
|||||||
<th>Priority</th>
|
<th>Priority</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% for vc_member in vc_members %}
|
{% for vc_member in vc_members %}
|
||||||
<tr{% if vc_member == device %} class="info"{% endif %}>
|
<tr{% if vc_member == object %} class="info"{% endif %}>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ vc_member.get_absolute_url }}">{{ vc_member }}</a>
|
<a href="{{ vc_member.get_absolute_url }}">{{ vc_member }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td><span class="badge badge-default">{{ vc_member.vc_position }}</span></td>
|
<td><span class="badge badge-default">{{ vc_member.vc_position }}</span></td>
|
||||||
<td>{% if device.virtual_chassis.master == vc_member %}<i class="mdi mdi-check-bold"></i>{% endif %}</td>
|
<td>{% if object.virtual_chassis.master == vc_member %}<i class="mdi mdi-check-bold"></i>{% endif %}</td>
|
||||||
<td>{{ vc_member.vc_priority|default:"" }}</td>
|
<td>{{ vc_member.vc_priority|default:"" }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
<div class="panel-footer text-right noprint">
|
<div class="panel-footer text-right noprint">
|
||||||
<a href="{{ device.virtual_chassis.get_absolute_url }}" class="btn btn-primary btn-xs">
|
<a href="{{ object.virtual_chassis.get_absolute_url }}" class="btn btn-primary btn-xs">
|
||||||
<span class="mdi mdi-arrow-right-bold" aria-hidden="true"></span> View Virtual Chassis
|
<span class="mdi mdi-arrow-right-bold" aria-hidden="true"></span> View Virtual Chassis
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -125,14 +125,14 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Role</td>
|
<td>Role</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'dcim:device_list' %}?role={{ device.device_role.slug }}">{{ device.device_role }}</a>
|
<a href="{% url 'dcim:device_list' %}?role={{ object.device_role.slug }}">{{ object.device_role }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Platform</td>
|
<td>Platform</td>
|
||||||
<td>
|
<td>
|
||||||
{% if device.platform %}
|
{% if object.platform %}
|
||||||
<a href="{{ device.platform.get_absolute_url }}">{{ device.platform }}</a>
|
<a href="{{ object.platform.get_absolute_url }}">{{ object.platform }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">None</span>
|
<span class="text-muted">None</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -141,18 +141,18 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Status</td>
|
<td>Status</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="label label-{{ device.get_status_class }}">{{ device.get_status_display }}</span>
|
<span class="label label-{{ object.get_status_class }}">{{ object.get_status_display }}</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Primary IPv4</td>
|
<td>Primary IPv4</td>
|
||||||
<td>
|
<td>
|
||||||
{% if device.primary_ip4 %}
|
{% if object.primary_ip4 %}
|
||||||
<a href="{% url 'ipam:ipaddress' pk=device.primary_ip4.pk %}">{{ device.primary_ip4.address.ip }}</a>
|
<a href="{% url 'ipam:ipaddress' pk=object.primary_ip4.pk %}">{{ object.primary_ip4.address.ip }}</a>
|
||||||
{% if device.primary_ip4.nat_inside %}
|
{% if object.primary_ip4.nat_inside %}
|
||||||
<span>(NAT for {{ device.primary_ip4.nat_inside.address.ip }})</span>
|
<span>(NAT for {{ object.primary_ip4.nat_inside.address.ip }})</span>
|
||||||
{% elif device.primary_ip4.nat_outside %}
|
{% elif object.primary_ip4.nat_outside %}
|
||||||
<span>(NAT: {{ device.primary_ip4.nat_outside.address.ip }})</span>
|
<span>(NAT: {{ object.primary_ip4.nat_outside.address.ip }})</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">—</span>
|
<span class="text-muted">—</span>
|
||||||
@@ -162,46 +162,46 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Primary IPv6</td>
|
<td>Primary IPv6</td>
|
||||||
<td>
|
<td>
|
||||||
{% if device.primary_ip6 %}
|
{% if object.primary_ip6 %}
|
||||||
<a href="{% url 'ipam:ipaddress' pk=device.primary_ip6.pk %}">{{ device.primary_ip6.address.ip }}</a>
|
<a href="{% url 'ipam:ipaddress' pk=object.primary_ip6.pk %}">{{ object.primary_ip6.address.ip }}</a>
|
||||||
{% if device.primary_ip6.nat_inside %}
|
{% if object.primary_ip6.nat_inside %}
|
||||||
<span>(NAT for {{ device.primary_ip6.nat_inside.address.ip }})</span>
|
<span>(NAT for {{ object.primary_ip6.nat_inside.address.ip }})</span>
|
||||||
{% elif device.primary_ip6.nat_outside %}
|
{% elif object.primary_ip6.nat_outside %}
|
||||||
<span>(NAT: {{ device.primary_ip6.nat_outside.address.ip }})</span>
|
<span>(NAT: {{ object.primary_ip6.nat_outside.address.ip }})</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">—</span>
|
<span class="text-muted">—</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if device.cluster %}
|
{% if object.cluster %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>Cluster</td>
|
<td>Cluster</td>
|
||||||
<td>
|
<td>
|
||||||
{% if device.cluster.group %}
|
{% if object.cluster.group %}
|
||||||
<a href="{{ device.cluster.group.get_absolute_url }}">{{ device.cluster.group }}</a> /
|
<a href="{{ object.cluster.group.get_absolute_url }}">{{ object.cluster.group }}</a> /
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{{ device.cluster.get_absolute_url }}">{{ device.cluster }}</a>
|
<a href="{{ object.cluster.get_absolute_url }}">{{ object.cluster }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% include 'inc/custom_fields_panel.html' with obj=device %}
|
{% include 'inc/custom_fields_panel.html' %}
|
||||||
{% include 'extras/inc/tags_panel.html' with tags=device.tags.all url='dcim:device_list' %}
|
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='dcim:device_list' %}
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Comments</strong>
|
<strong>Comments</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body rendered-markdown">
|
<div class="panel-body rendered-markdown">
|
||||||
{% if device.comments %}
|
{% if object.comments %}
|
||||||
{{ device.comments|render_markdown }}
|
{{ object.comments|render_markdown }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">None</span>
|
<span class="text-muted">None</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% plugin_left_page device %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
{% if power_ports and poweroutlets %}
|
{% if power_ports and poweroutlets %}
|
||||||
@@ -255,7 +255,7 @@
|
|||||||
{% include 'secrets/inc/assigned_secrets.html' %}
|
{% include 'secrets/inc/assigned_secrets.html' %}
|
||||||
{% if perms.secrets.add_secret %}
|
{% if perms.secrets.add_secret %}
|
||||||
<div class="panel-footer text-right noprint">
|
<div class="panel-footer text-right noprint">
|
||||||
<a href="{% url 'secrets:secret_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-xs btn-primary">
|
<a href="{% url 'secrets:secret_add' %}?device={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-xs btn-primary">
|
||||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add secret
|
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add secret
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -279,7 +279,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.ipam.add_service %}
|
{% if perms.ipam.add_service %}
|
||||||
<div class="panel-footer text-right noprint">
|
<div class="panel-footer text-right noprint">
|
||||||
<a href="{% url 'dcim:device_service_assign' device=device.pk %}" class="btn btn-xs btn-primary">
|
<a href="{% url 'dcim:device_service_assign' device=object.pk %}" class="btn btn-xs btn-primary">
|
||||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Assign service
|
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Assign service
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -289,10 +289,10 @@
|
|||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Images</strong>
|
<strong>Images</strong>
|
||||||
</div>
|
</div>
|
||||||
{% include 'inc/image_attachments.html' with images=device.images.all %}
|
{% include 'inc/image_attachments.html' with images=object.images.all %}
|
||||||
{% if perms.extras.add_imageattachment %}
|
{% if perms.extras.add_imageattachment %}
|
||||||
<div class="panel-footer text-right noprint">
|
<div class="panel-footer text-right noprint">
|
||||||
<a href="{% url 'dcim:device_add_image' object_id=device.pk %}" class="btn btn-primary btn-xs">
|
<a href="{% url 'dcim:device_add_image' object_id=object.pk %}" class="btn btn-primary btn-xs">
|
||||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span>
|
<span class="mdi mdi-plus-thick" aria-hidden="true"></span>
|
||||||
Attach an image
|
Attach an image
|
||||||
</a>
|
</a>
|
||||||
@@ -325,12 +325,12 @@
|
|||||||
<div class="panel-body text-muted">None found</div>
|
<div class="panel-body text-muted">None found</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% plugin_right_page device %}
|
{% plugin_right_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
{% plugin_full_width_page device %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -5,19 +5,19 @@
|
|||||||
{% load custom_links %}
|
{% load custom_links %}
|
||||||
{% load plugins %}
|
{% load plugins %}
|
||||||
|
|
||||||
{% block title %}{{ device }}{% endblock %}
|
{% block title %}{{ object }}{% endblock %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<div class="row noprint">
|
<div class="row noprint">
|
||||||
<div class="col-sm-8 col-md-9">
|
<div class="col-sm-8 col-md-9">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="{% url 'dcim:device_list' %}">Devices</a></li>
|
<li><a href="{% url 'dcim:device_list' %}">Devices</a></li>
|
||||||
<li><a href="{% url 'dcim:device_list' %}?site={{ device.site.slug }}">{{ device.site }}</a></li>
|
<li><a href="{% url 'dcim:device_list' %}?site={{ object.site.slug }}">{{ object.site }}</a></li>
|
||||||
{% if device.parent_bay %}
|
{% if object.parent_bay %}
|
||||||
<li><a href="{% url 'dcim:device' pk=device.parent_bay.device.pk %}">{{ device.parent_bay.device }}</a></li>
|
<li><a href="{% url 'dcim:device' pk=object.parent_bay.object.pk %}">{{ object.parent_bay.device }}</a></li>
|
||||||
<li>{{ device.parent_bay }}</li>
|
<li>{{ object.parent_bay }}</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li>{{ device }}</li>
|
<li>{{ object }}</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-4 col-md-3">
|
<div class="col-sm-4 col-md-3">
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right noprint">
|
<div class="pull-right noprint">
|
||||||
{% plugin_buttons device %}
|
{% plugin_buttons object %}
|
||||||
{% if perms.dcim.change_device %}
|
{% if perms.dcim.change_device %}
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
@@ -42,123 +42,123 @@
|
|||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
{% if perms.dcim.add_consoleport %}
|
{% if perms.dcim.add_consoleport %}
|
||||||
<li><a href="{% url 'dcim:consoleport_add' %}?device={{ device.pk }}&return_url={% url 'dcim:device_consoleports' pk=device.pk %}">Console Ports</a></li>
|
<li><a href="{% url 'dcim:consoleport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_consoleports' pk=object.pk %}">Console Ports</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_consoleserverport %}
|
{% if perms.dcim.add_consoleserverport %}
|
||||||
<li><a href="{% url 'dcim:consoleserverport_add' %}?device={{ device.pk }}&return_url={% url 'dcim:device_consoleserverports' pk=device.pk %}">Console Server Ports</a></li>
|
<li><a href="{% url 'dcim:consoleserverport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}">Console Server Ports</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_powerport %}
|
{% if perms.dcim.add_powerport %}
|
||||||
<li><a href="{% url 'dcim:powerport_add' %}?device={{ device.pk }}&return_url={% url 'dcim:device_powerports' pk=device.pk %}">Power Ports</a></li>
|
<li><a href="{% url 'dcim:powerport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_powerports' pk=object.pk %}">Power Ports</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_poweroutlet %}
|
{% if perms.dcim.add_poweroutlet %}
|
||||||
<li><a href="{% url 'dcim:poweroutlet_add' %}?device={{ device.pk }}&return_url={% url 'dcim:device_poweroutlets' pk=device.pk %}">Power Outlets</a></li>
|
<li><a href="{% url 'dcim:poweroutlet_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}">Power Outlets</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_interface %}
|
{% if perms.dcim.add_interface %}
|
||||||
<li><a href="{% url 'dcim:interface_add' %}?device={{ device.pk }}&return_url={% url 'dcim:device_interfaces' pk=device.pk %}">Interfaces</a></li>
|
<li><a href="{% url 'dcim:interface_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}">Interfaces</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_frontport %}
|
{% if perms.dcim.add_frontport %}
|
||||||
<li><a href="{% url 'dcim:frontport_add' %}?device={{ device.pk }}&return_url={% url 'dcim:device_frontports' pk=device.pk %}">Front Ports</a></li>
|
<li><a href="{% url 'dcim:frontport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_frontports' pk=object.pk %}">Front Ports</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_rearport %}
|
{% if perms.dcim.add_rearport %}
|
||||||
<li><a href="{% url 'dcim:rearport_add' %}?device={{ device.pk }}&return_url={% url 'dcim:device_rearports' pk=device.pk %}">Rear Ports</a></li>
|
<li><a href="{% url 'dcim:rearport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_rearports' pk=object.pk %}">Rear Ports</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_devicebay %}
|
{% if perms.dcim.add_devicebay %}
|
||||||
<li><a href="{% url 'dcim:devicebay_add' %}?device={{ device.pk }}&return_url={% url 'dcim:device_devicebays' pk=device.pk %}">Device Bays</a></li>
|
<li><a href="{% url 'dcim:devicebay_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_devicebays' pk=object.pk %}">Device Bays</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_inventoryitem %}
|
{% if perms.dcim.add_inventoryitem %}
|
||||||
<li><a href="{% url 'dcim:inventoryitem_add' %}?device={{ device.pk }}&return_url={% url 'dcim:device_inventory' pk=device.pk %}">Inventory Items</a></li>
|
<li><a href="{% url 'dcim:inventoryitem_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_inventory' pk=object.pk %}">Inventory Items</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_device %}
|
{% if perms.dcim.add_device %}
|
||||||
{% clone_button device %}
|
{% clone_button object %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.change_device %}
|
{% if perms.dcim.change_device %}
|
||||||
{% edit_button device %}
|
{% edit_button object %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.delete_device %}
|
{% if perms.dcim.delete_device %}
|
||||||
{% delete_button device %}
|
{% delete_button object %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<h1>{{ device }}</h1>
|
<h1>{{ object }}</h1>
|
||||||
{% include 'inc/created_updated.html' with obj=device %}
|
{% include 'inc/created_updated.html' %}
|
||||||
<div class="pull-right noprint">
|
<div class="pull-right noprint">
|
||||||
{% custom_links device %}
|
{% custom_links object %}
|
||||||
</div>
|
</div>
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
<li role="presentation" {% if active_tab == 'device' %} class="active"{% endif %}>
|
<li role="presentation" {% if active_tab == 'device' %} class="active"{% endif %}>
|
||||||
<a href="{% url 'dcim:device' pk=device.pk %}">Device</a>
|
<a href="{% url 'dcim:device' pk=object.pk %}">Device</a>
|
||||||
</li>
|
</li>
|
||||||
{% with interface_count=device.interfaces.count %}
|
{% with interface_count=object.interfaces.count %}
|
||||||
{% if interface_count %}
|
{% if interface_count %}
|
||||||
<li role="presentation" {% if active_tab == 'interfaces' %} class="active"{% endif %}>
|
<li role="presentation" {% if active_tab == 'interfaces' %} class="active"{% endif %}>
|
||||||
<a href="{% url 'dcim:device_interfaces' pk=device.pk %}">Interfaces {% badge interface_count %}</a>
|
<a href="{% url 'dcim:device_interfaces' pk=object.pk %}">Interfaces {% badge interface_count %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% with frontport_count=device.frontports.count %}
|
{% with frontport_count=object.frontports.count %}
|
||||||
{% if frontport_count %}
|
{% if frontport_count %}
|
||||||
<li role="presentation" {% if active_tab == 'front-ports' %} class="active"{% endif %}>
|
<li role="presentation" {% if active_tab == 'front-ports' %} class="active"{% endif %}>
|
||||||
<a href="{% url 'dcim:device_frontports' pk=device.pk %}">Front Ports {% badge frontport_count %}</a>
|
<a href="{% url 'dcim:device_frontports' pk=object.pk %}">Front Ports {% badge frontport_count %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% with rearport_count=device.rearports.count %}
|
{% with rearport_count=object.rearports.count %}
|
||||||
{% if rearport_count %}
|
{% if rearport_count %}
|
||||||
<li role="presentation" {% if active_tab == 'rear-ports' %} class="active"{% endif %}>
|
<li role="presentation" {% if active_tab == 'rear-ports' %} class="active"{% endif %}>
|
||||||
<a href="{% url 'dcim:device_rearports' pk=device.pk %}">Rear Ports {% badge rearport_count %}</a>
|
<a href="{% url 'dcim:device_rearports' pk=object.pk %}">Rear Ports {% badge rearport_count %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% with consoleport_count=device.consoleports.count %}
|
{% with consoleport_count=object.consoleports.count %}
|
||||||
{% if consoleport_count %}
|
{% if consoleport_count %}
|
||||||
<li role="presentation" {% if active_tab == 'console-ports' %} class="active"{% endif %}>
|
<li role="presentation" {% if active_tab == 'console-ports' %} class="active"{% endif %}>
|
||||||
<a href="{% url 'dcim:device_consoleports' pk=device.pk %}">Console Ports {% badge consoleport_count %}</a>
|
<a href="{% url 'dcim:device_consoleports' pk=object.pk %}">Console Ports {% badge consoleport_count %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% with consoleserverport_count=device.consoleserverports.count %}
|
{% with consoleserverport_count=object.consoleserverports.count %}
|
||||||
{% if consoleserverport_count %}
|
{% if consoleserverport_count %}
|
||||||
<li role="presentation" {% if active_tab == 'console-server-ports' %} class="active"{% endif %}>
|
<li role="presentation" {% if active_tab == 'console-server-ports' %} class="active"{% endif %}>
|
||||||
<a href="{% url 'dcim:device_consoleserverports' pk=device.pk %}">Console Server Ports {% badge consoleserverport_count %}</a>
|
<a href="{% url 'dcim:device_consoleserverports' pk=object.pk %}">Console Server Ports {% badge consoleserverport_count %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% with powerport_count=device.powerports.count %}
|
{% with powerport_count=object.powerports.count %}
|
||||||
{% if powerport_count %}
|
{% if powerport_count %}
|
||||||
<li role="presentation" {% if active_tab == 'power-ports' %} class="active"{% endif %}>
|
<li role="presentation" {% if active_tab == 'power-ports' %} class="active"{% endif %}>
|
||||||
<a href="{% url 'dcim:device_powerports' pk=device.pk %}">Power Ports {% badge powerport_count %}</a>
|
<a href="{% url 'dcim:device_powerports' pk=object.pk %}">Power Ports {% badge powerport_count %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% with poweroutlet_count=device.poweroutlets.count %}
|
{% with poweroutlet_count=object.poweroutlets.count %}
|
||||||
{% if poweroutlet_count %}
|
{% if poweroutlet_count %}
|
||||||
<li role="presentation" {% if active_tab == 'power-outlets' %} class="active"{% endif %}>
|
<li role="presentation" {% if active_tab == 'power-outlets' %} class="active"{% endif %}>
|
||||||
<a href="{% url 'dcim:device_poweroutlets' pk=device.pk %}">Power Outlets {% badge poweroutlet_count %}</a>
|
<a href="{% url 'dcim:device_poweroutlets' pk=object.pk %}">Power Outlets {% badge poweroutlet_count %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% with devicebay_count=device.devicebays.count %}
|
{% with devicebay_count=object.devicebays.count %}
|
||||||
{% if devicebay_count %}
|
{% if devicebay_count %}
|
||||||
<li role="presentation" {% if active_tab == 'device-bays' %} class="active"{% endif %}>
|
<li role="presentation" {% if active_tab == 'device-bays' %} class="active"{% endif %}>
|
||||||
<a href="{% url 'dcim:device_devicebays' pk=device.pk %}">Device Bays {% badge devicebay_count %}</a>
|
<a href="{% url 'dcim:device_devicebays' pk=object.pk %}">Device Bays {% badge devicebay_count %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% with inventoryitem_count=device.inventoryitems.count %}
|
{% with inventoryitem_count=object.inventoryitems.count %}
|
||||||
{% if inventoryitem_count %}
|
{% if inventoryitem_count %}
|
||||||
<li role="presentation" {% if active_tab == 'inventory' %} class="active"{% endif %}>
|
<li role="presentation" {% if active_tab == 'inventory' %} class="active"{% endif %}>
|
||||||
<a href="{% url 'dcim:device_inventory' pk=device.pk %}">Inventory {% badge inventoryitem_count %}</a>
|
<a href="{% url 'dcim:device_inventory' pk=object.pk %}">Inventory {% badge inventoryitem_count %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% if perms.dcim.napalm_read_device %}
|
{% if perms.dcim.napalm_read_device %}
|
||||||
{% if device.status != 'active' %}
|
{% if object.status != 'active' %}
|
||||||
{% include 'dcim/inc/device_napalm_tabs.html' with disabled_message='Device must be in active status' %}
|
{% include 'dcim/inc/device_napalm_tabs.html' with disabled_message='Device must be in active status' %}
|
||||||
{% elif not device.platform %}
|
{% elif not object.platform %}
|
||||||
{% include 'dcim/inc/device_napalm_tabs.html' with disabled_message='No platform assigned to this device' %}
|
{% include 'dcim/inc/device_napalm_tabs.html' with disabled_message='No platform assigned to this device' %}
|
||||||
{% elif not device.platform.napalm_driver %}
|
{% elif not object.platform.napalm_driver %}
|
||||||
{% include 'dcim/inc/device_napalm_tabs.html' with disabled_message='No NAPALM driver assigned for this platform' %}
|
{% include 'dcim/inc/device_napalm_tabs.html' with disabled_message='No NAPALM driver assigned for this platform' %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% include 'dcim/inc/device_napalm_tabs.html' %}
|
{% include 'dcim/inc/device_napalm_tabs.html' %}
|
||||||
@@ -166,12 +166,12 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.extras.view_configcontext %}
|
{% if perms.extras.view_configcontext %}
|
||||||
<li role="presentation"{% if active_tab == 'config-context' %} class="active"{% endif %}>
|
<li role="presentation"{% if active_tab == 'config-context' %} class="active"{% endif %}>
|
||||||
<a href="{% url 'dcim:device_configcontext' pk=device.pk %}">Config Context</a>
|
<a href="{% url 'dcim:device_configcontext' pk=object.pk %}">Config Context</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.extras.view_objectchange %}
|
{% if perms.extras.view_objectchange %}
|
||||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||||
<a href="{% url 'dcim:device_changelog' pk=device.pk %}">Change Log</a>
|
<a href="{% url 'dcim:device_changelog' pk=object.pk %}">Change Log</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{% extends 'dcim/device/base.html' %}
|
{% extends 'dcim/device/base.html' %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
{% block title %}{{ device }} - Config{% endblock %}
|
{% block title %}{{ object }} - Config{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% include 'inc/ajax_loader.html' %}
|
{% include 'inc/ajax_loader.html' %}
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "{% url 'dcim-api:device-napalm' pk=device.pk %}?method=get_config",
|
url: "{% url 'dcim-api:device-napalm' pk=object.pk %}?method=get_config",
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
success: function(json) {
|
success: function(json) {
|
||||||
$('#running_config').html($.trim(json['get_config']['running']));
|
$('#running_config').html($.trim(json['get_config']['running']));
|
||||||
|
|||||||
@@ -18,24 +18,24 @@
|
|||||||
{% render_table consoleport_table 'inc/table.html' %}
|
{% render_table consoleport_table 'inc/table.html' %}
|
||||||
<div class="panel-footer noprint">
|
<div class="panel-footer noprint">
|
||||||
{% if perms.dcim.change_consoleport %}
|
{% if perms.dcim.change_consoleport %}
|
||||||
<button type="submit" name="_rename" formaction="{% url 'dcim:consoleport_bulk_rename' %}?return_url={% url 'dcim:device_consoleports' pk=device.pk %}" class="btn btn-warning btn-xs">
|
<button type="submit" name="_rename" formaction="{% url 'dcim:consoleport_bulk_rename' %}?return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-warning btn-xs">
|
||||||
<span class="mdi mdi-pencil" aria-hidden="true"></span> Rename
|
<span class="mdi mdi-pencil" aria-hidden="true"></span> Rename
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="_edit" formaction="{% url 'dcim:consoleport_bulk_edit' %}?device={{ device.pk }}&return_url={% url 'dcim:device_consoleports' pk=device.pk %}" class="btn btn-warning btn-xs">
|
<button type="submit" name="_edit" formaction="{% url 'dcim:consoleport_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-warning btn-xs">
|
||||||
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
|
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="_disconnect" formaction="{% url 'dcim:consoleport_bulk_disconnect' %}?return_url={% url 'dcim:device_consoleports' pk=device.pk %}" class="btn btn-danger btn-xs">
|
<button type="submit" name="_disconnect" formaction="{% url 'dcim:consoleport_bulk_disconnect' %}?return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-danger btn-xs">
|
||||||
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
|
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.delete_consoleport %}
|
{% if perms.dcim.delete_consoleport %}
|
||||||
<button type="submit" name="_delete" formaction="{% url 'dcim:consoleport_bulk_delete' %}?return_url={% url 'dcim:device_consoleports' pk=device.pk %}" class="btn btn-danger btn-xs">
|
<button type="submit" name="_delete" formaction="{% url 'dcim:consoleport_bulk_delete' %}?return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-danger btn-xs">
|
||||||
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
|
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_consoleport %}
|
{% if perms.dcim.add_consoleport %}
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
<a href="{% url 'dcim:consoleport_add' %}?device={{ device.pk }}&return_url={% url 'dcim:device_consoleports' pk=device.pk %}" class="btn btn-xs btn-primary">
|
<a href="{% url 'dcim:consoleport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_consoleports' pk=object.pk %}" class="btn btn-xs btn-primary">
|
||||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add console port
|
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add console port
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,24 +18,24 @@
|
|||||||
{% render_table consoleserverport_table 'inc/table.html' %}
|
{% render_table consoleserverport_table 'inc/table.html' %}
|
||||||
<div class="panel-footer noprint">
|
<div class="panel-footer noprint">
|
||||||
{% if perms.dcim.change_consoleserverport %}
|
{% if perms.dcim.change_consoleserverport %}
|
||||||
<button type="submit" name="_rename" formaction="{% url 'dcim:consoleserverport_bulk_rename' %}?return_url={% url 'dcim:device_consoleserverports' pk=device.pk %}" class="btn btn-warning btn-xs">
|
<button type="submit" name="_rename" formaction="{% url 'dcim:consoleserverport_bulk_rename' %}?return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-warning btn-xs">
|
||||||
<span class="mdi mdi-pencil" aria-hidden="true"></span> Rename
|
<span class="mdi mdi-pencil" aria-hidden="true"></span> Rename
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="_edit" formaction="{% url 'dcim:consoleserverport_bulk_edit' %}?device={{ device.pk }}&return_url={% url 'dcim:device_consoleserverports' pk=device.pk %}" class="btn btn-warning btn-xs">
|
<button type="submit" name="_edit" formaction="{% url 'dcim:consoleserverport_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-warning btn-xs">
|
||||||
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
|
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="_disconnect" formaction="{% url 'dcim:consoleserverport_bulk_disconnect' %}?return_url={% url 'dcim:device_consoleserverports' pk=device.pk %}" class="btn btn-danger btn-xs">
|
<button type="submit" name="_disconnect" formaction="{% url 'dcim:consoleserverport_bulk_disconnect' %}?return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-danger btn-xs">
|
||||||
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
|
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.delete_consoleserverport %}
|
{% if perms.dcim.delete_consoleserverport %}
|
||||||
<button type="submit" formaction="{% url 'dcim:consoleserverport_bulk_delete' %}?return_url={% url 'dcim:device_consoleserverports' pk=device.pk %}" class="btn btn-danger btn-xs">
|
<button type="submit" formaction="{% url 'dcim:consoleserverport_bulk_delete' %}?return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-danger btn-xs">
|
||||||
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
|
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_consoleserverport %}
|
{% if perms.dcim.add_consoleserverport %}
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
<a href="{% url 'dcim:consoleserverport_add' %}?device={{ device.pk }}&return_url={% url 'dcim:device_consoleserverports' pk=device.pk %}" class="btn btn-primary btn-xs">
|
<a href="{% url 'dcim:consoleserverport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}" class="btn btn-primary btn-xs">
|
||||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add console server ports
|
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add console server ports
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,21 +18,21 @@
|
|||||||
{% render_table devicebay_table 'inc/table.html' %}
|
{% render_table devicebay_table 'inc/table.html' %}
|
||||||
<div class="panel-footer noprint">
|
<div class="panel-footer noprint">
|
||||||
{% if perms.dcim.change_devicebay %}
|
{% if perms.dcim.change_devicebay %}
|
||||||
<button type="submit" name="_rename" formaction="{% url 'dcim:devicebay_bulk_rename' %}?return_url={{ device.get_absolute_url }}%23tab_devicebays" class="btn btn-warning btn-xs">
|
<button type="submit" name="_rename" formaction="{% url 'dcim:devicebay_bulk_rename' %}?return_url={{ object.get_absolute_url }}%23tab_devicebays" class="btn btn-warning btn-xs">
|
||||||
<span class="mdi mdi-pencil" aria-hidden="true"></span> Rename
|
<span class="mdi mdi-pencil" aria-hidden="true"></span> Rename
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="_edit" formaction="{% url 'dcim:devicebay_bulk_edit' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}%23tab_devicebays" class="btn btn-warning btn-xs">
|
<button type="submit" name="_edit" formaction="{% url 'dcim:devicebay_bulk_edit' %}?device={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_devicebays" class="btn btn-warning btn-xs">
|
||||||
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
|
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.delete_devicebay %}
|
{% if perms.dcim.delete_devicebay %}
|
||||||
<button type="submit" formaction="{% url 'dcim:devicebay_bulk_delete' %}?return_url={{ device.get_absolute_url }}%23tab_devicebays" class="btn btn-danger btn-xs">
|
<button type="submit" formaction="{% url 'dcim:devicebay_bulk_delete' %}?return_url={{ object.get_absolute_url }}%23tab_devicebays" class="btn btn-danger btn-xs">
|
||||||
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete selected
|
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete selected
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_devicebay %}
|
{% if perms.dcim.add_devicebay %}
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
<a href="{% url 'dcim:devicebay_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}%23tab_devicebays" class="btn btn-primary btn-xs">
|
<a href="{% url 'dcim:devicebay_add' %}?device={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_devicebays" class="btn btn-primary btn-xs">
|
||||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add device bays
|
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add device bays
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,24 +18,24 @@
|
|||||||
{% render_table frontport_table 'inc/table.html' %}
|
{% render_table frontport_table 'inc/table.html' %}
|
||||||
<div class="panel-footer noprint">
|
<div class="panel-footer noprint">
|
||||||
{% if perms.dcim.change_frontport %}
|
{% if perms.dcim.change_frontport %}
|
||||||
<button type="submit" name="_rename" formaction="{% url 'dcim:frontport_bulk_rename' %}?return_url={% url 'dcim:device_frontports' pk=device.pk %}" class="btn btn-warning btn-xs">
|
<button type="submit" name="_rename" formaction="{% url 'dcim:frontport_bulk_rename' %}?return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-warning btn-xs">
|
||||||
<span class="mdi mdi-pencil" aria-hidden="true"></span> Rename
|
<span class="mdi mdi-pencil" aria-hidden="true"></span> Rename
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="_edit" formaction="{% url 'dcim:frontport_bulk_edit' %}?device={{ device.pk }}&return_url={% url 'dcim:device_frontports' pk=device.pk %}" class="btn btn-warning btn-xs">
|
<button type="submit" name="_edit" formaction="{% url 'dcim:frontport_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-warning btn-xs">
|
||||||
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
|
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="_disconnect" formaction="{% url 'dcim:frontport_bulk_disconnect' %}?return_url={% url 'dcim:device_frontports' pk=device.pk %}" class="btn btn-danger btn-xs">
|
<button type="submit" name="_disconnect" formaction="{% url 'dcim:frontport_bulk_disconnect' %}?return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-danger btn-xs">
|
||||||
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
|
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.delete_frontport %}
|
{% if perms.dcim.delete_frontport %}
|
||||||
<button type="submit" formaction="{% url 'dcim:frontport_bulk_delete' %}?return_url={% url 'dcim:device_frontports' pk=device.pk %}" class="btn btn-danger btn-xs">
|
<button type="submit" formaction="{% url 'dcim:frontport_bulk_delete' %}?return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-danger btn-xs">
|
||||||
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
|
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_frontport %}
|
{% if perms.dcim.add_frontport %}
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
<a href="{% url 'dcim:frontport_add' %}?device={{ device.pk }}&return_url={% url 'dcim:device_frontports' pk=device.pk %}" class="btn btn-primary btn-xs">
|
<a href="{% url 'dcim:frontport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_frontports' pk=object.pk %}" class="btn btn-primary btn-xs">
|
||||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add front ports
|
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add front ports
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,24 +21,24 @@
|
|||||||
{% render_table interface_table 'inc/table.html' %}
|
{% render_table interface_table 'inc/table.html' %}
|
||||||
<div class="panel-footer noprint">
|
<div class="panel-footer noprint">
|
||||||
{% if perms.dcim.change_interface %}
|
{% if perms.dcim.change_interface %}
|
||||||
<button type="submit" name="_rename" formaction="{% url 'dcim:interface_bulk_rename' %}?return_url={% url 'dcim:device_interfaces' pk=device.pk %}" class="btn btn-warning btn-xs">
|
<button type="submit" name="_rename" formaction="{% url 'dcim:interface_bulk_rename' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}" class="btn btn-warning btn-xs">
|
||||||
<span class="mdi mdi-pencil" aria-hidden="true"></span> Rename
|
<span class="mdi mdi-pencil" aria-hidden="true"></span> Rename
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="_edit" formaction="{% url 'dcim:interface_bulk_edit' %}?device={{ device.pk }}&return_url={% url 'dcim:device_interfaces' pk=device.pk %}" class="btn btn-warning btn-xs">
|
<button type="submit" name="_edit" formaction="{% url 'dcim:interface_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}" class="btn btn-warning btn-xs">
|
||||||
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
|
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="_disconnect" formaction="{% url 'dcim:interface_bulk_disconnect' %}?return_url={% url 'dcim:device_interfaces' pk=device.pk %}" class="btn btn-danger btn-xs">
|
<button type="submit" name="_disconnect" formaction="{% url 'dcim:interface_bulk_disconnect' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}" class="btn btn-danger btn-xs">
|
||||||
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
|
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.delete_interface %}
|
{% if perms.dcim.delete_interface %}
|
||||||
<button type="submit" name="_delete" formaction="{% url 'dcim:interface_bulk_delete' %}?return_url={% url 'dcim:device_interfaces' pk=device.pk %}" class="btn btn-danger btn-xs">
|
<button type="submit" name="_delete" formaction="{% url 'dcim:interface_bulk_delete' %}?return_url={% url 'dcim:device_interfaces' pk=object.pk %}" class="btn btn-danger btn-xs">
|
||||||
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
|
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_interface %}
|
{% if perms.dcim.add_interface %}
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
<a href="{% url 'dcim:interface_add' %}?device={{ device.pk }}&return_url={% url 'dcim:device_interfaces' pk=device.pk %}" class="btn btn-primary btn-xs">
|
<a href="{% url 'dcim:interface_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}" class="btn btn-primary btn-xs">
|
||||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add interfaces
|
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add interfaces
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,21 +18,21 @@
|
|||||||
{% render_table inventoryitem_table 'inc/table.html' %}
|
{% render_table inventoryitem_table 'inc/table.html' %}
|
||||||
<div class="panel-footer noprint">
|
<div class="panel-footer noprint">
|
||||||
{% if perms.dcim.change_inventoryitem %}
|
{% if perms.dcim.change_inventoryitem %}
|
||||||
<button type="submit" name="_rename" formaction="{% url 'dcim:inventoryitem_bulk_rename' %}?return_url={% url 'dcim:device_inventory' pk=device.pk %}" class="btn btn-warning btn-xs">
|
<button type="submit" name="_rename" formaction="{% url 'dcim:inventoryitem_bulk_rename' %}?return_url={% url 'dcim:device_inventory' pk=object.pk %}" class="btn btn-warning btn-xs">
|
||||||
<span class="mdi mdi-pencil" aria-hidden="true"></span> Rename
|
<span class="mdi mdi-pencil" aria-hidden="true"></span> Rename
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="_edit" formaction="{% url 'dcim:inventoryitem_bulk_edit' %}?device={{ device.pk }}&return_url={% url 'dcim:device_inventory' pk=device.pk %}" class="btn btn-warning btn-xs">
|
<button type="submit" name="_edit" formaction="{% url 'dcim:inventoryitem_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_inventory' pk=object.pk %}" class="btn btn-warning btn-xs">
|
||||||
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
|
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.delete_inventoryitem %}
|
{% if perms.dcim.delete_inventoryitem %}
|
||||||
<button type="submit" name="_delete" formaction="{% url 'dcim:inventoryitem_bulk_delete' %}?return_url={% url 'dcim:device_inventory' pk=device.pk %}" class="btn btn-danger btn-xs">
|
<button type="submit" name="_delete" formaction="{% url 'dcim:inventoryitem_bulk_delete' %}?return_url={% url 'dcim:device_inventory' pk=object.pk %}" class="btn btn-danger btn-xs">
|
||||||
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
|
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_inventoryitem %}
|
{% if perms.dcim.add_inventoryitem %}
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
<a href="{% url 'dcim:inventoryitem_add' %}?device={{ device.pk }}&return_url={% url 'dcim:device_inventory' pk=device.pk %}" class="btn btn-primary btn-xs">
|
<a href="{% url 'dcim:inventoryitem_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_inventory' pk=object.pk %}" class="btn btn-primary btn-xs">
|
||||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add Inventory Item
|
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add Inventory Item
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends 'dcim/device/base.html' %}
|
{% extends 'dcim/device/base.html' %}
|
||||||
|
|
||||||
{% block title %}{{ device }} - LLDP Neighbors{% endblock %}
|
{% block title %}{{ object }} - LLDP Neighbors{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% include 'inc/ajax_loader.html' %}
|
{% include 'inc/ajax_loader.html' %}
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "{% url 'dcim-api:device-napalm' pk=device.pk %}?method=get_lldp_neighbors_detail",
|
url: "{% url 'dcim-api:device-napalm' pk=object.pk %}?method=get_lldp_neighbors_detail",
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
success: function(json) {
|
success: function(json) {
|
||||||
$.each(json['get_lldp_neighbors_detail'], function(iface, neighbors) {
|
$.each(json['get_lldp_neighbors_detail'], function(iface, neighbors) {
|
||||||
|
|||||||
@@ -18,24 +18,24 @@
|
|||||||
{% render_table poweroutlet_table 'inc/table.html' %}
|
{% render_table poweroutlet_table 'inc/table.html' %}
|
||||||
<div class="panel-footer noprint">
|
<div class="panel-footer noprint">
|
||||||
{% if perms.dcim.change_powerport %}
|
{% if perms.dcim.change_powerport %}
|
||||||
<button type="submit" name="_rename" formaction="{% url 'dcim:poweroutlet_bulk_rename' %}?return_url={% url 'dcim:device_poweroutlets' pk=device.pk %}" class="btn btn-warning btn-xs">
|
<button type="submit" name="_rename" formaction="{% url 'dcim:poweroutlet_bulk_rename' %}?return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-warning btn-xs">
|
||||||
<span class="mdi mdi-pencil" aria-hidden="true"></span> Rename
|
<span class="mdi mdi-pencil" aria-hidden="true"></span> Rename
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="_edit" formaction="{% url 'dcim:poweroutlet_bulk_edit' %}?device={{ device.pk }}&return_url={% url 'dcim:device_poweroutlets' pk=device.pk %}" class="btn btn-warning btn-xs">
|
<button type="submit" name="_edit" formaction="{% url 'dcim:poweroutlet_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-warning btn-xs">
|
||||||
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
|
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="_disconnect" formaction="{% url 'dcim:poweroutlet_bulk_disconnect' %}?return_url={% url 'dcim:device_poweroutlets' pk=device.pk %}" class="btn btn-danger btn-xs">
|
<button type="submit" name="_disconnect" formaction="{% url 'dcim:poweroutlet_bulk_disconnect' %}?return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-danger btn-xs">
|
||||||
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
|
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.delete_poweroutlet %}
|
{% if perms.dcim.delete_poweroutlet %}
|
||||||
<button type="submit" formaction="{% url 'dcim:poweroutlet_bulk_delete' %}?return_url={% url 'dcim:device_poweroutlets' pk=device.pk %}" class="btn btn-danger btn-xs">
|
<button type="submit" formaction="{% url 'dcim:poweroutlet_bulk_delete' %}?return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-danger btn-xs">
|
||||||
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
|
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_poweroutlet %}
|
{% if perms.dcim.add_poweroutlet %}
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
<a href="{% url 'dcim:poweroutlet_add' %}?device={{ device.pk }}&return_url={% url 'dcim:device_poweroutlets' pk=device.pk %}" class="btn btn-primary btn-xs">
|
<a href="{% url 'dcim:poweroutlet_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}" class="btn btn-primary btn-xs">
|
||||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add power outlets
|
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add power outlets
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,24 +18,24 @@
|
|||||||
{% render_table powerport_table 'inc/table.html' %}
|
{% render_table powerport_table 'inc/table.html' %}
|
||||||
<div class="panel-footer noprint">
|
<div class="panel-footer noprint">
|
||||||
{% if perms.dcim.change_powerport %}
|
{% if perms.dcim.change_powerport %}
|
||||||
<button type="submit" name="_rename" formaction="{% url 'dcim:powerport_bulk_rename' %}?return_url={% url 'dcim:device_powerports' pk=device.pk %}" class="btn btn-warning btn-xs">
|
<button type="submit" name="_rename" formaction="{% url 'dcim:powerport_bulk_rename' %}?return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-warning btn-xs">
|
||||||
<span class="mdi mdi-pencil" aria-hidden="true"></span> Rename
|
<span class="mdi mdi-pencil" aria-hidden="true"></span> Rename
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="_edit" formaction="{% url 'dcim:powerport_bulk_edit' %}?device={{ device.pk }}&return_url={% url 'dcim:device_powerports' pk=device.pk %}" class="btn btn-warning btn-xs">
|
<button type="submit" name="_edit" formaction="{% url 'dcim:powerport_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-warning btn-xs">
|
||||||
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
|
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="_disconnect" formaction="{% url 'dcim:powerport_bulk_disconnect' %}?return_url={% url 'dcim:device_powerports' pk=device.pk %}" class="btn btn-danger btn-xs">
|
<button type="submit" name="_disconnect" formaction="{% url 'dcim:powerport_bulk_disconnect' %}?return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-danger btn-xs">
|
||||||
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
|
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.delete_powerport %}
|
{% if perms.dcim.delete_powerport %}
|
||||||
<button type="submit" name="_delete" formaction="{% url 'dcim:powerport_bulk_delete' %}?return_url={% url 'dcim:device_powerports' pk=device.pk %}" class="btn btn-danger btn-xs">
|
<button type="submit" name="_delete" formaction="{% url 'dcim:powerport_bulk_delete' %}?return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-danger btn-xs">
|
||||||
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
|
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_powerport %}
|
{% if perms.dcim.add_powerport %}
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
<a href="{% url 'dcim:powerport_add' %}?device={{ device.pk }}&return_url={% url 'dcim:device_powerports' pk=device.pk %}" class="btn btn-xs btn-primary">
|
<a href="{% url 'dcim:powerport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_powerports' pk=object.pk %}" class="btn btn-xs btn-primary">
|
||||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add power port
|
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add power port
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,24 +18,24 @@
|
|||||||
{% render_table rearport_table 'inc/table.html' %}
|
{% render_table rearport_table 'inc/table.html' %}
|
||||||
<div class="panel-footer noprint">
|
<div class="panel-footer noprint">
|
||||||
{% if perms.dcim.change_rearport %}
|
{% if perms.dcim.change_rearport %}
|
||||||
<button type="submit" name="_rename" formaction="{% url 'dcim:rearport_bulk_rename' %}?return_url={% url 'dcim:device_rearports' pk=device.pk %}" class="btn btn-warning btn-xs">
|
<button type="submit" name="_rename" formaction="{% url 'dcim:rearport_bulk_rename' %}?return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-warning btn-xs">
|
||||||
<span class="mdi mdi-pencil" aria-hidden="true"></span> Rename
|
<span class="mdi mdi-pencil" aria-hidden="true"></span> Rename
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="_edit" formaction="{% url 'dcim:rearport_bulk_edit' %}?device={{ device.pk }}&return_url={% url 'dcim:device_rearports' pk=device.pk %}" class="btn btn-warning btn-xs">
|
<button type="submit" name="_edit" formaction="{% url 'dcim:rearport_bulk_edit' %}?device={{ object.pk }}&return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-warning btn-xs">
|
||||||
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
|
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="_disconnect" formaction="{% url 'dcim:rearport_bulk_disconnect' %}?return_url={% url 'dcim:device_rearports' pk=device.pk %}" class="btn btn-danger btn-xs">
|
<button type="submit" name="_disconnect" formaction="{% url 'dcim:rearport_bulk_disconnect' %}?return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-danger btn-xs">
|
||||||
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
|
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> Disconnect
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.delete_rearport %}
|
{% if perms.dcim.delete_rearport %}
|
||||||
<button type="submit" formaction="{% url 'dcim:rearport_bulk_delete' %}?return_url={% url 'dcim:device_rearports' pk=device.pk %}" class="btn btn-danger btn-xs">
|
<button type="submit" formaction="{% url 'dcim:rearport_bulk_delete' %}?return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-danger btn-xs">
|
||||||
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
|
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_rearport %}
|
{% if perms.dcim.add_rearport %}
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
<a href="{% url 'dcim:rearport_add' %}?device={{ device.pk }}&return_url={% url 'dcim:device_rearports' pk=device.pk %}" class="btn btn-primary btn-xs">
|
<a href="{% url 'dcim:rearport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_rearports' pk=object.pk %}" class="btn btn-primary btn-xs">
|
||||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add rear ports
|
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add rear ports
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -70,7 +70,7 @@
|
|||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "{% url 'dcim-api:device-napalm' pk=device.pk %}?method=get_facts&method=get_environment",
|
url: "{% url 'dcim-api:device-napalm' pk=object.pk %}?method=get_facts&method=get_environment",
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
success: function(json) {
|
success: function(json) {
|
||||||
if (!json['get_facts']['error']) {
|
if (!json['get_facts']['error']) {
|
||||||
|
|||||||
@@ -8,33 +8,35 @@
|
|||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="{% url 'dcim:device_list' %}">Devices</a></li>
|
<li><a href="{% url 'dcim:device_list' %}">Devices</a></li>
|
||||||
<li><a href="{{ instance.device.get_absolute_url }}">{{ instance.device }}</a></li>
|
<li><a href="{{ object.device.get_absolute_url }}">{{ object.device }}</a></li>
|
||||||
<li><a href="{% url instance|viewname:"list" %}?device_id={{ instance.device.pk }}">{{ instance|meta:"verbose_name_plural"|bettertitle }}</a></li>
|
{% if breadcrumb_url %}
|
||||||
<li>{{ instance }}</li>
|
<li><a href="{% url breadcrumb_url pk=object.device.pk %}">{{ object|meta:"verbose_name_plural"|bettertitle }}</a></li>
|
||||||
|
{% endif %}
|
||||||
|
<li>{{ object }}</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right noprint">
|
<div class="pull-right noprint">
|
||||||
{% plugin_buttons instance %}
|
{% plugin_buttons object %}
|
||||||
{% if request.user|can_change:instance %}
|
{% if request.user|can_change:object %}
|
||||||
<a href="{% url instance|viewname:"edit" pk=instance.pk %}" class="btn btn-warning">
|
<a href="{% url object|viewname:"edit" pk=object.pk %}" class="btn btn-warning">
|
||||||
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
|
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if request.user|can_delete:instance %}
|
{% if request.user|can_delete:object %}
|
||||||
<a href="{% url instance|viewname:"delete" pk=instance.pk %}" class="btn btn-danger">
|
<a href="{% url object|viewname:"delete" pk=object.pk %}" class="btn btn-danger">
|
||||||
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
|
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<h1>{% block title %}{{ instance.device }} / {{ instance }}{% endblock %}</h1>
|
<h1>{% block title %}{{ object.device }} / {{ object }}{% endblock %}</h1>
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
||||||
<a href="{{ instance.get_absolute_url }}">{{ instance|meta:"verbose_name"|bettertitle }}</a>
|
<a href="{{ object.get_absolute_url }}">{{ object|meta:"verbose_name"|bettertitle }}</a>
|
||||||
</li>
|
</li>
|
||||||
{% if perms.extras.view_objectchange %}
|
{% if perms.extras.view_objectchange %}
|
||||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||||
<a href="{% url instance|viewname:"changelog" pk=instance.pk %}">Change Log</a>
|
<a href="{% url object|viewname:"changelog" pk=object.pk %}">Change Log</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -13,33 +13,33 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Device</td>
|
<td>Device</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ instance.device.get_absolute_url }}">{{ instance.device }}</a>
|
<a href="{{ object.device.get_absolute_url }}">{{ object.device }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Name</td>
|
<td>Name</td>
|
||||||
<td>{{ instance.name }}</td>
|
<td>{{ object.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Label</td>
|
<td>Label</td>
|
||||||
<td>{{ instance.label|placeholder }}</td>
|
<td>{{ object.label|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Description</td>
|
<td>Description</td>
|
||||||
<td>{{ instance.description|placeholder }}</td>
|
<td>{{ object.description|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% include 'extras/inc/tags_panel.html' with tags=instance.tags.all %}
|
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all %}
|
||||||
{% plugin_left_page instance %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Installed Device</strong>
|
<strong>Installed Device</strong>
|
||||||
</div>
|
</div>
|
||||||
{% if instance.installed_device %}
|
{% if object.installed_device %}
|
||||||
{% with device=instance.installed_device %}
|
{% with device=object.installed_device %}
|
||||||
<table class="table table-hover panel-body attr-table">
|
<table class="table table-hover panel-body attr-table">
|
||||||
<tr>
|
<tr>
|
||||||
<td>Device</td>
|
<td>Device</td>
|
||||||
@@ -59,12 +59,12 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% plugin_right_page instance %}
|
{% plugin_right_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
{% plugin_full_width_page instance %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -4,20 +4,20 @@
|
|||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% load plugins %}
|
{% load plugins %}
|
||||||
|
|
||||||
{% block title %}{{ devicetype.manufacturer }} {{ devicetype.model }}{% endblock %}
|
{% block title %}{{ object.manufacturer }} {{ object.model }}{% endblock %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<div class="row noprint">
|
<div class="row noprint">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="{% url 'dcim:devicetype_list' %}">Device Types</a></li>
|
<li><a href="{% url 'dcim:devicetype_list' %}">Device Types</a></li>
|
||||||
<li><a href="{% url 'dcim:devicetype_list' %}?manufacturer={{ devicetype.manufacturer.slug }}">{{ devicetype.manufacturer }}</a></li>
|
<li><a href="{% url 'dcim:devicetype_list' %}?manufacturer={{ object.manufacturer.slug }}">{{ object.manufacturer }}</a></li>
|
||||||
<li>{{ devicetype.model }}</li>
|
<li>{{ object.model }}</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right noprint">
|
<div class="pull-right noprint">
|
||||||
{% plugin_buttons devicetype %}
|
{% plugin_buttons object %}
|
||||||
{% if perms.dcim.change_devicetype %}
|
{% if perms.dcim.change_devicetype %}
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
@@ -25,54 +25,54 @@
|
|||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
{% if perms.dcim.add_consoleporttemplate %}
|
{% if perms.dcim.add_consoleporttemplate %}
|
||||||
<li><a href="{% url 'dcim:consoleporttemplate_add' %}?device_type={{ devicetype.pk }}&return_url={{ devicetype.get_absolute_url }}%23tab_consoleports">Console Ports</a></li>
|
<li><a href="{% url 'dcim:consoleporttemplate_add' %}?device_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_consoleports">Console Ports</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_consoleserverporttemplate %}
|
{% if perms.dcim.add_consoleserverporttemplate %}
|
||||||
<li><a href="{% url 'dcim:consoleserverporttemplate_add' %}?device_type={{ devicetype.pk }}&return_url={{ devicetype.get_absolute_url }}%23tab_consoleserverports">Console Server Ports</a></li>
|
<li><a href="{% url 'dcim:consoleserverporttemplate_add' %}?device_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_consoleserverports">Console Server Ports</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_powerporttemplate %}
|
{% if perms.dcim.add_powerporttemplate %}
|
||||||
<li><a href="{% url 'dcim:powerporttemplate_add' %}?device_type={{ devicetype.pk }}&return_url={{ devicetype.get_absolute_url }}%23tab_powerports">Power Ports</a></li>
|
<li><a href="{% url 'dcim:powerporttemplate_add' %}?device_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_powerports">Power Ports</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_poweroutlettemplate %}
|
{% if perms.dcim.add_poweroutlettemplate %}
|
||||||
<li><a href="{% url 'dcim:poweroutlettemplate_add' %}?device_type={{ devicetype.pk }}&return_url={{ devicetype.get_absolute_url }}%23tab_poweroutlets">Power Outlets</a></li>
|
<li><a href="{% url 'dcim:poweroutlettemplate_add' %}?device_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_poweroutlets">Power Outlets</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_interfacetemplate %}
|
{% if perms.dcim.add_interfacetemplate %}
|
||||||
<li><a href="{% url 'dcim:interfacetemplate_add' %}?device_type={{ devicetype.pk }}&return_url={{ devicetype.get_absolute_url }}%23tab_interfaces">Interfaces</a></li>
|
<li><a href="{% url 'dcim:interfacetemplate_add' %}?device_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_interfaces">Interfaces</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_frontporttemplate %}
|
{% if perms.dcim.add_frontporttemplate %}
|
||||||
<li><a href="{% url 'dcim:frontporttemplate_add' %}?device_type={{ devicetype.pk }}&return_url={{ devicetype.get_absolute_url }}%23tab_frontports">Front Ports</a></li>
|
<li><a href="{% url 'dcim:frontporttemplate_add' %}?device_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_frontports">Front Ports</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_rearporttemplate %}
|
{% if perms.dcim.add_rearporttemplate %}
|
||||||
<li><a href="{% url 'dcim:rearporttemplate_add' %}?device_type={{ devicetype.pk }}&return_url={{ devicetype.get_absolute_url }}%23tab_rearports">Rear Ports</a></li>
|
<li><a href="{% url 'dcim:rearporttemplate_add' %}?device_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_rearports">Rear Ports</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_devicebaytemplate %}
|
{% if perms.dcim.add_devicebaytemplate %}
|
||||||
<li><a href="{% url 'dcim:devicebaytemplate_add' %}?device_type={{ devicetype.pk }}&return_url={{ devicetype.get_absolute_url }}%23tab_devicebays">Device Bays</a></li>
|
<li><a href="{% url 'dcim:devicebaytemplate_add' %}?device_type={{ object.pk }}&return_url={{ object.get_absolute_url }}%23tab_devicebays">Device Bays</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_devicetype %}
|
{% if perms.dcim.add_devicetype %}
|
||||||
{% clone_button devicetype %}
|
{% clone_button object %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.change_devicetype %}
|
{% if perms.dcim.change_devicetype %}
|
||||||
{% edit_button devicetype use_pk=True %}
|
{% edit_button object use_pk=True %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.delete_devicetype %}
|
{% if perms.dcim.delete_devicetype %}
|
||||||
{% delete_button devicetype use_pk=True %}
|
{% delete_button object use_pk=True %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<h1>{{ devicetype.manufacturer }} {{ devicetype.model }}</h1>
|
<h1>{{ object.manufacturer }} {{ object.model }}</h1>
|
||||||
{% include 'inc/created_updated.html' with obj=devicetype %}
|
{% include 'inc/created_updated.html' %}
|
||||||
<div class="pull-right noprint">
|
<div class="pull-right noprint">
|
||||||
{% custom_links devicetype %}
|
{% custom_links object %}
|
||||||
</div>
|
</div>
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
||||||
<a href="{{ devicetype.get_absolute_url }}">Device Type</a>
|
<a href="{{ object.get_absolute_url }}">Device Type</a>
|
||||||
</li>
|
</li>
|
||||||
{% if perms.extras.view_objectchange %}
|
{% if perms.extras.view_objectchange %}
|
||||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||||
<a href="{% url 'dcim:devicetype_changelog' pk=devicetype.pk %}">Change Log</a>
|
<a href="{% url 'dcim:devicetype_changelog' pk=object.pk %}">Change Log</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -88,27 +88,27 @@
|
|||||||
<table class="table table-hover panel-body attr-table">
|
<table class="table table-hover panel-body attr-table">
|
||||||
<tr>
|
<tr>
|
||||||
<td>Manufacturer</td>
|
<td>Manufacturer</td>
|
||||||
<td><a href="{% url 'dcim:devicetype_list' %}?manufacturer={{ devicetype.manufacturer.slug }}">{{ devicetype.manufacturer }}</a></td>
|
<td><a href="{% url 'dcim:devicetype_list' %}?manufacturer={{ object.manufacturer.slug }}">{{ object.manufacturer }}</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Model Name</td>
|
<td>Model Name</td>
|
||||||
<td>
|
<td>
|
||||||
{{ devicetype.model }}<br/>
|
{{ object.model }}<br/>
|
||||||
<small class="text-muted">{{ devicetype.slug }}</small>
|
<small class="text-muted">{{ object.slug }}</small>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Part Number</td>
|
<td>Part Number</td>
|
||||||
<td>{{ devicetype.part_number|placeholder }}</td>
|
<td>{{ object.part_number|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Height (U)</td>
|
<td>Height (U)</td>
|
||||||
<td>{{ devicetype.u_height }}</td>
|
<td>{{ object.u_height }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Full Depth</td>
|
<td>Full Depth</td>
|
||||||
<td>
|
<td>
|
||||||
{% if devicetype.is_full_depth %}
|
{% if object.is_full_depth %}
|
||||||
<i class="mdi mdi-check-bold text-success" title="Yes"></i>
|
<i class="mdi mdi-check-bold text-success" title="Yes"></i>
|
||||||
{% else %}
|
{% else %}
|
||||||
<i class="mdi mdi-close-thick text-danger" title="No"></i>
|
<i class="mdi mdi-close-thick text-danger" title="No"></i>
|
||||||
@@ -118,15 +118,15 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Parent/Child</td>
|
<td>Parent/Child</td>
|
||||||
<td>
|
<td>
|
||||||
{{ devicetype.get_subdevice_role_display|placeholder }}
|
{{ object.get_subdevice_role_display|placeholder }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Front Image</td>
|
<td>Front Image</td>
|
||||||
<td>
|
<td>
|
||||||
{% if devicetype.front_image %}
|
{% if object.front_image %}
|
||||||
<a href="{{ devicetype.front_image.url }}">
|
<a href="{{ object.front_image.url }}">
|
||||||
<img src="{{ devicetype.front_image.url }}" alt="{{ devicetype.front_image.name }}" class="img-responsive" />
|
<img src="{{ object.front_image.url }}" alt="{{ object.front_image.name }}" class="img-responsive" />
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">—</span>
|
<span class="text-muted">—</span>
|
||||||
@@ -136,9 +136,9 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Rear Image</td>
|
<td>Rear Image</td>
|
||||||
<td>
|
<td>
|
||||||
{% if devicetype.rear_image %}
|
{% if object.rear_image %}
|
||||||
<a href="{{ devicetype.rear_image.url }}">
|
<a href="{{ object.rear_image.url }}">
|
||||||
<img src="{{ devicetype.rear_image.url }}" alt="{{ devicetype.rear_image.name }}" class="img-responsive" />
|
<img src="{{ object.rear_image.url }}" alt="{{ object.rear_image.name }}" class="img-responsive" />
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">—</span>
|
<span class="text-muted">—</span>
|
||||||
@@ -147,33 +147,33 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Instances</td>
|
<td>Instances</td>
|
||||||
<td><a href="{% url 'dcim:device_list' %}?device_type_id={{ devicetype.pk }}">{{ instance_count }}</a></td>
|
<td><a href="{% url 'dcim:device_list' %}?device_type_id={{ object.pk }}">{{ instance_count }}</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% plugin_left_page devicetype %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
{% include 'inc/custom_fields_panel.html' with obj=devicetype %}
|
{% include 'inc/custom_fields_panel.html' %}
|
||||||
{% include 'extras/inc/tags_panel.html' with tags=devicetype.tags.all url='dcim:devicetype_list' %}
|
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='dcim:devicetype_list' %}
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Comments</strong>
|
<strong>Comments</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body rendered-markdown">
|
<div class="panel-body rendered-markdown">
|
||||||
{% if devicetype.comments %}
|
{% if object.comments %}
|
||||||
{{ devicetype.comments|render_markdown }}
|
{{ object.comments|render_markdown }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">None</span>
|
<span class="text-muted">None</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% plugin_right_page devicetype %}
|
{% plugin_right_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
{% plugin_full_width_page devicetype %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
@@ -13,52 +13,52 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Device</td>
|
<td>Device</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ instance.device.get_absolute_url }}">{{ instance.device }}</a>
|
<a href="{{ object.device.get_absolute_url }}">{{ object.device }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Name</td>
|
<td>Name</td>
|
||||||
<td>{{ instance.name }}</td>
|
<td>{{ object.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Label</td>
|
<td>Label</td>
|
||||||
<td>{{ instance.label|placeholder }}</td>
|
<td>{{ object.label|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Type</td>
|
<td>Type</td>
|
||||||
<td>{{ instance.get_type_display }}</td>
|
<td>{{ object.get_type_display }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Rear Port</td>
|
<td>Rear Port</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ instance.rear_port.get_absolute_url }}">{{ instance.rear_port }}</a>
|
<a href="{{ object.rear_port.get_absolute_url }}">{{ object.rear_port }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Rear Port Position</td>
|
<td>Rear Port Position</td>
|
||||||
<td>{{ instance.rear_port_position }}</td>
|
<td>{{ object.rear_port_position }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Description</td>
|
<td>Description</td>
|
||||||
<td>{{ instance.description|placeholder }}</td>
|
<td>{{ object.description|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% include 'extras/inc/tags_panel.html' with tags=instance.tags.all %}
|
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all %}
|
||||||
{% plugin_left_page instance %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Connection</strong>
|
<strong>Connection</strong>
|
||||||
</div>
|
</div>
|
||||||
{% if instance.cable %}
|
{% if object.cable %}
|
||||||
<table class="table table-hover panel-body attr-table">
|
<table class="table table-hover panel-body attr-table">
|
||||||
<tr>
|
<tr>
|
||||||
<td>Cable</td>
|
<td>Cable</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ instance.cable.get_absolute_url }}">{{ instance.cable }}</a>
|
<a href="{{ object.cable.get_absolute_url }}">{{ object.cable }}</a>
|
||||||
<a href="{% url 'dcim:frontport_trace' pk=instance.pk %}" class="btn btn-primary btn-xs" title="Trace">
|
<a href="{% url 'dcim:frontport_trace' pk=object.pk %}" class="btn btn-primary btn-xs" title="Trace">
|
||||||
<i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
|
<i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
@@ -66,10 +66,10 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Connection Status</td>
|
<td>Connection Status</td>
|
||||||
<td>
|
<td>
|
||||||
{% if instance.cable.status %}
|
{% if object.cable.status %}
|
||||||
<span class="label label-success">{{ instance.cable.get_status_display }}</span>
|
<span class="label label-success">{{ object.cable.get_status_display }}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="label label-info">{{ instance.cable.get_status_display }}</span>
|
<span class="label label-info">{{ object.cable.get_status_display }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -83,24 +83,24 @@
|
|||||||
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span> Connect
|
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span> Connect
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-right">
|
<ul class="dropdown-menu dropdown-menu-right">
|
||||||
<li><a href="{% url 'dcim:frontport_connect' termination_a_id=instance.pk termination_b_type='interface' %}?return_url={{ instance.get_absolute_url }}">Interface</a></li>
|
<li><a href="{% url 'dcim:frontport_connect' termination_a_id=object.pk termination_b_type='interface' %}?return_url={{ object.get_absolute_url }}">Interface</a></li>
|
||||||
<li><a href="{% url 'dcim:frontport_connect' termination_a_id=instance.pk termination_b_type='console-server-port' %}?return_url={{ instance.get_absolute_url }}">Console Server Port</a></li>
|
<li><a href="{% url 'dcim:frontport_connect' termination_a_id=object.pk termination_b_type='console-server-port' %}?return_url={{ object.get_absolute_url }}">Console Server Port</a></li>
|
||||||
<li><a href="{% url 'dcim:frontport_connect' termination_a_id=instance.pk termination_b_type='console-port' %}?return_url={{ instance.get_absolute_url }}">Console Port</a></li>
|
<li><a href="{% url 'dcim:frontport_connect' termination_a_id=object.pk termination_b_type='console-port' %}?return_url={{ object.get_absolute_url }}">Console Port</a></li>
|
||||||
<li><a href="{% url 'dcim:frontport_connect' termination_a_id=instance.pk termination_b_type='front-port' %}?return_url={{ instance.get_absolute_url }}">Front Port</a></li>
|
<li><a href="{% url 'dcim:frontport_connect' termination_a_id=object.pk termination_b_type='front-port' %}?return_url={{ object.get_absolute_url }}">Front Port</a></li>
|
||||||
<li><a href="{% url 'dcim:frontport_connect' termination_a_id=instance.pk termination_b_type='rear-port' %}?return_url={{ instance.get_absolute_url }}">Rear Port</a></li>
|
<li><a href="{% url 'dcim:frontport_connect' termination_a_id=object.pk termination_b_type='rear-port' %}?return_url={{ object.get_absolute_url }}">Rear Port</a></li>
|
||||||
<li><a href="{% url 'dcim:frontport_connect' termination_a_id=instance.pk termination_b_type='circuit-termination' %}?return_url={{ instance.get_absolute_url }}">Circuit Termination</a></li>
|
<li><a href="{% url 'dcim:frontport_connect' termination_a_id=object.pk termination_b_type='circuit-termination' %}?return_url={{ object.get_absolute_url }}">Circuit Termination</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% plugin_right_page instance %}
|
{% plugin_right_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
{% plugin_full_width_page instance %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
{% if not disabled_message %}
|
{% if not disabled_message %}
|
||||||
<li role="presentation"{% if active_tab == 'status' %} class="active"{% endif %}>
|
<li role="presentation"{% if active_tab == 'status' %} class="active"{% endif %}>
|
||||||
<a href="{% url 'dcim:device_status' pk=device.pk %}">Status</a>
|
<a href="{% url 'dcim:device_status' pk=object.pk %}">Status</a>
|
||||||
</li>
|
</li>
|
||||||
<li role="presentation"{% if active_tab == 'lldp-neighbors' %} class="active"{% endif %}>
|
<li role="presentation"{% if active_tab == 'lldp-neighbors' %} class="active"{% endif %}>
|
||||||
<a href="{% url 'dcim:device_lldp_neighbors' pk=device.pk %}">LLDP Neighbors</a>
|
<a href="{% url 'dcim:device_lldp_neighbors' pk=object.pk %}">LLDP Neighbors</a>
|
||||||
</li>
|
</li>
|
||||||
<li role="presentation"{% if active_tab == 'config' %} class="active"{% endif %}>
|
<li role="presentation"{% if active_tab == 'config' %} class="active"{% endif %}>
|
||||||
<a href="{% url 'dcim:device_config' pk=device.pk %}">Configuration</a>
|
<a href="{% url 'dcim:device_config' pk=object.pk %}">Configuration</a>
|
||||||
</li>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li role="presentation" class="disabled"><a href="#" title="{{ disabled_message }}">Status</a></li>
|
<li role="presentation" class="disabled"><a href="#" title="{{ disabled_message }}">Status</a></li>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<div style="margin-left: -30px">
|
<div style="margin-left: -30px">
|
||||||
<object data="{% url 'dcim-api:rack-elevation' pk=rack.pk %}?face={{face}}&render=svg" class="rack_elevation"></object>
|
<object data="{% url 'dcim-api:rack-elevation' pk=object.pk %}?face={{face}}&render=svg" class="rack_elevation"></object>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center text-small">
|
<div class="text-center text-small">
|
||||||
<a href="{% url 'dcim-api:rack-elevation' pk=rack.pk %}?face={{face}}&render=svg">
|
<a href="{% url 'dcim-api:rack-elevation' pk=object.pk %}?face={{face}}&render=svg">
|
||||||
<i class="mdi mdi-content-save-outline"></i> Save SVG
|
<i class="mdi mdi-content-save-outline"></i> Save SVG
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,25 +13,25 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Device</td>
|
<td>Device</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ instance.device.get_absolute_url }}">{{ instance.device }}</a>
|
<a href="{{ object.device.get_absolute_url }}">{{ object.device }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Name</td>
|
<td>Name</td>
|
||||||
<td>{{ instance.name }}</td>
|
<td>{{ object.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Label</td>
|
<td>Label</td>
|
||||||
<td>{{ instance.label|placeholder }}</td>
|
<td>{{ object.label|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Type</td>
|
<td>Type</td>
|
||||||
<td>{{ instance.get_type_display }}</td>
|
<td>{{ object.get_type_display }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Enabled</td>
|
<td>Enabled</td>
|
||||||
<td>
|
<td>
|
||||||
{% if instance.enabled %}
|
{% if object.enabled %}
|
||||||
<span class="text-success"><i class="mdi mdi-check-bold"></i></span>
|
<span class="text-success"><i class="mdi mdi-check-bold"></i></span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-danger"><i class="mdi mdi-close"></i></span>
|
<span class="text-danger"><i class="mdi mdi-close"></i></span>
|
||||||
@@ -41,8 +41,8 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>LAG</td>
|
<td>LAG</td>
|
||||||
<td>
|
<td>
|
||||||
{% if instance.lag%}
|
{% if object.lag%}
|
||||||
<a href="{{ instance.lag.get_absolute_url }}">{{ instance.lag }}</a>
|
<a href="{{ object.lag.get_absolute_url }}">{{ object.lag }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">None</span>
|
<span class="text-muted">None</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -50,44 +50,44 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Description</td>
|
<td>Description</td>
|
||||||
<td>{{ instance.description|placeholder }} </td>
|
<td>{{ object.description|placeholder }} </td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>MTU</td>
|
<td>MTU</td>
|
||||||
<td>{{ instance.mtu|placeholder }}</td>
|
<td>{{ object.mtu|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>MAC Address</td>
|
<td>MAC Address</td>
|
||||||
<td><span class="text-monospace">{{ instance.mac_address|placeholder }}</span></td>
|
<td><span class="text-monospace">{{ object.mac_address|placeholder }}</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>802.1Q Mode</td>
|
<td>802.1Q Mode</td>
|
||||||
<td>{{ instance.get_mode_display }}</td>
|
<td>{{ object.get_mode_display }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% include 'extras/inc/tags_panel.html' with tags=instance.tags.all %}
|
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all %}
|
||||||
{% plugin_left_page instance %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
{% if instance.is_connectable %}
|
{% if object.is_connectable %}
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Connection</strong>
|
<strong>Connection</strong>
|
||||||
</div>
|
</div>
|
||||||
{% if instance.cable %}
|
{% if object.cable %}
|
||||||
<table class="table table-hover panel-body attr-table">
|
<table class="table table-hover panel-body attr-table">
|
||||||
<tr>
|
<tr>
|
||||||
<td>Cable</td>
|
<td>Cable</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ instance.cable.get_absolute_url }}">{{ instance.cable }}</a>
|
<a href="{{ object.cable.get_absolute_url }}">{{ object.cable }}</a>
|
||||||
<a href="{% url 'dcim:interface_trace' pk=instance.pk %}" class="btn btn-primary btn-xs" title="Trace">
|
<a href="{% url 'dcim:interface_trace' pk=object.pk %}" class="btn btn-primary btn-xs" title="Trace">
|
||||||
<i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
|
<i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if instance.connected_endpoint.device %}
|
{% if object.connected_endpoint.device %}
|
||||||
{% with iface=instance.connected_endpoint %}
|
{% with iface=object.connected_endpoint %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>Device</td>
|
<td>Device</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -141,8 +141,8 @@
|
|||||||
<td>{{ iface.get_mode_display }}</td>
|
<td>{{ iface.get_mode_display }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% elif instance.connected_endpoint.circuit %}
|
{% elif object.connected_endpoint.circuit %}
|
||||||
{% with ct=instance.connected_endpoint %}
|
{% with ct=object.connected_endpoint %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>Provider</td>
|
<td>Provider</td>
|
||||||
<td><a href="{{ ct.circuit.provider.get_absolute_url }}">{{ ct.circuit.provider }}</a></td>
|
<td><a href="{{ ct.circuit.provider.get_absolute_url }}">{{ ct.circuit.provider }}</a></td>
|
||||||
@@ -160,7 +160,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Path Status</td>
|
<td>Path Status</td>
|
||||||
<td>
|
<td>
|
||||||
{% if instance.path.is_active %}
|
{% if object.path.is_active %}
|
||||||
<span class="label label-success">Reachable</span>
|
<span class="label label-success">Reachable</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="label label-danger">Not Reachable</span>
|
<span class="label label-danger">Not Reachable</span>
|
||||||
@@ -177,10 +177,10 @@
|
|||||||
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span> Connect
|
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span> Connect
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-right">
|
<ul class="dropdown-menu dropdown-menu-right">
|
||||||
<li><a href="{% url 'dcim:interface_connect' termination_a_id=instance.pk termination_b_type='interface' %}?return_url={{ instance.get_absolute_url }}">Interface</a></li>
|
<li><a href="{% url 'dcim:interface_connect' termination_a_id=object.pk termination_b_type='interface' %}?return_url={{ object.get_absolute_url }}">Interface</a></li>
|
||||||
<li><a href="{% url 'dcim:interface_connect' termination_a_id=instance.pk termination_b_type='front-port' %}?return_url={{ instance.get_absolute_url }}">Front Port</a></li>
|
<li><a href="{% url 'dcim:interface_connect' termination_a_id=object.pk termination_b_type='front-port' %}?return_url={{ object.get_absolute_url }}">Front Port</a></li>
|
||||||
<li><a href="{% url 'dcim:interface_connect' termination_a_id=instance.pk termination_b_type='rear-port' %}?return_url={{ instance.get_absolute_url }}">Rear Port</a></li>
|
<li><a href="{% url 'dcim:interface_connect' termination_a_id=object.pk termination_b_type='rear-port' %}?return_url={{ object.get_absolute_url }}">Rear Port</a></li>
|
||||||
<li><a href="{% url 'dcim:interface_connect' termination_a_id=instance.pk termination_b_type='circuit-termination' %}?return_url={{ instance.get_absolute_url }}">Circuit Termination</a></li>
|
<li><a href="{% url 'dcim:interface_connect' termination_a_id=object.pk termination_b_type='circuit-termination' %}?return_url={{ object.get_absolute_url }}">Circuit Termination</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if instance.is_lag %}
|
{% if object.is_lag %}
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading"><strong>LAG Members</strong></div>
|
<div class="panel-heading"><strong>LAG Members</strong></div>
|
||||||
<table class="table table-hover table-headings panel-body">
|
<table class="table table-hover table-headings panel-body">
|
||||||
@@ -200,7 +200,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for member in instance.member_interfaces.all %}
|
{% for member in object.member_interfaces.all %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ member.device.get_absolute_url }}">{{ member.device }}</a>
|
<a href="{{ member.device.get_absolute_url }}">{{ member.device }}</a>
|
||||||
@@ -221,7 +221,7 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% plugin_right_page instance %}
|
{% plugin_right_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -236,7 +236,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
{% plugin_full_width_page instance %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -13,14 +13,14 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Device</td>
|
<td>Device</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ instance.device.get_absolute_url }}">{{ instance.device }}</a>
|
<a href="{{ object.device.get_absolute_url }}">{{ object.device }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Parent Item</td>
|
<td>Parent Item</td>
|
||||||
<td>
|
<td>
|
||||||
{% if instance.parent %}
|
{% if object.parent %}
|
||||||
<a href="{{ instance.parent.get_absolute_url }}">{{ instance.parent }}</a>
|
<a href="{{ object.parent.get_absolute_url }}">{{ object.parent }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">—</span>
|
<span class="text-muted">—</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -28,17 +28,17 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Name</td>
|
<td>Name</td>
|
||||||
<td>{{ instance.name }}</td>
|
<td>{{ object.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Label</td>
|
<td>Label</td>
|
||||||
<td>{{ instance.label|placeholder }}</td>
|
<td>{{ object.label|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Manufacturer</td>
|
<td>Manufacturer</td>
|
||||||
<td>
|
<td>
|
||||||
{% if instance.manufacturer %}
|
{% if object.manufacturer %}
|
||||||
<a href="{{ instance.manufacturer.get_absolute_url }}">{{ instance.manufacturer }}</a>
|
<a href="{{ object.manufacturer.get_absolute_url }}">{{ object.manufacturer }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">—</span>
|
<span class="text-muted">—</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -46,32 +46,32 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Part ID</td>
|
<td>Part ID</td>
|
||||||
<td>{{ instance.part_id|placeholder }}</td>
|
<td>{{ object.part_id|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Serial</td>
|
<td>Serial</td>
|
||||||
<td>{{ instance.serial|placeholder }}</td>
|
<td>{{ object.serial|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Asset Tag</td>
|
<td>Asset Tag</td>
|
||||||
<td>{{ instance.asset_tag|placeholder }}</td>
|
<td>{{ object.asset_tag|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Description</td>
|
<td>Description</td>
|
||||||
<td>{{ instance.description|placeholder }}</td>
|
<td>{{ object.description|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% include 'extras/inc/tags_panel.html' with tags=instance.tags.all %}
|
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all %}
|
||||||
{% plugin_left_page instance %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
{% plugin_right_page instance %}
|
{% plugin_right_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
{% plugin_full_width_page instance %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -10,12 +10,12 @@
|
|||||||
<div class="col-sm-8 col-md-9">
|
<div class="col-sm-8 col-md-9">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="{% url 'dcim:powerfeed_list' %}">Power Feeds</a></li>
|
<li><a href="{% url 'dcim:powerfeed_list' %}">Power Feeds</a></li>
|
||||||
<li><a href="{{ powerfeed.power_panel.site.get_absolute_url }}">{{ powerfeed.power_panel.site }}</a></li>
|
<li><a href="{{ object.power_panel.site.get_absolute_url }}">{{ object.power_panel.site }}</a></li>
|
||||||
<li><a href="{{ powerfeed.power_panel.get_absolute_url }}">{{ powerfeed.power_panel }}</a></li>
|
<li><a href="{{ object.power_panel.get_absolute_url }}">{{ object.power_panel }}</a></li>
|
||||||
{% if powerfeed.rack %}
|
{% if object.rack %}
|
||||||
<li><a href="{{ powerfeed.rack.get_absolute_url }}">{{ powerfeed.rack }}</a></li>
|
<li><a href="{{ object.rack.get_absolute_url }}">{{ object.rack }}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li>{{ powerfeed }}</li>
|
<li>{{ object }}</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-4 col-md-3">
|
<div class="col-sm-4 col-md-3">
|
||||||
@@ -32,29 +32,29 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right noprint">
|
<div class="pull-right noprint">
|
||||||
{% plugin_buttons powerfeed %}
|
{% plugin_buttons object %}
|
||||||
{% if perms.dcim.add_powerfeed %}
|
{% if perms.dcim.add_powerfeed %}
|
||||||
{% clone_button powerfeed %}
|
{% clone_button object %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.change_powerfeed %}
|
{% if perms.dcim.change_powerfeed %}
|
||||||
{% edit_button powerfeed %}
|
{% edit_button object %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.delete_powerfeed %}
|
{% if perms.dcim.delete_powerfeed %}
|
||||||
{% delete_button powerfeed %}
|
{% delete_button object %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<h1>{% block title %}{{ powerfeed }}{% endblock %}</h1>
|
<h1>{% block title %}{{ object }}{% endblock %}</h1>
|
||||||
{% include 'inc/created_updated.html' with obj=powerfeed %}
|
{% include 'inc/created_updated.html' %}
|
||||||
<div class="pull-right noprint">
|
<div class="pull-right noprint">
|
||||||
{% custom_links powerfeed %}
|
{% custom_links object %}
|
||||||
</div>
|
</div>
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
||||||
<a href="{{ powerfeed.get_absolute_url }}">Cable</a>
|
<a href="{{ object.get_absolute_url }}">Cable</a>
|
||||||
</li>
|
</li>
|
||||||
{% if perms.extras.view_objectchange %}
|
{% if perms.extras.view_objectchange %}
|
||||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||||
<a href="{% url 'dcim:powerfeed_changelog' pk=powerfeed.pk %}">Change Log</a>
|
<a href="{% url 'dcim:powerfeed_changelog' pk=object.pk %}">Change Log</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -71,14 +71,14 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Power Panel</td>
|
<td>Power Panel</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ powerfeed.power_panel.get_absolute_url }}">{{ powerfeed.power_panel }}</a>
|
<a href="{{ object.power_panel.get_absolute_url }}">{{ object.power_panel }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Rack</td>
|
<td>Rack</td>
|
||||||
<td>
|
<td>
|
||||||
{% if powerfeed.rack %}
|
{% if object.rack %}
|
||||||
<a href="{{ powerfeed.rack.get_absolute_url }}">{{ powerfeed.rack }}</a>
|
<a href="{{ object.rack.get_absolute_url }}">{{ object.rack }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">None</span>
|
<span class="text-muted">None</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -87,20 +87,20 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Type</td>
|
<td>Type</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="label label-{{ powerfeed.get_type_class }}">{{ powerfeed.get_type_display }}</span>
|
<span class="label label-{{ object.get_type_class }}">{{ object.get_type_display }}</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Status</td>
|
<td>Status</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="label label-{{ powerfeed.get_status_class }}">{{ powerfeed.get_status_display }}</span>
|
<span class="label label-{{ object.get_status_class }}">{{ object.get_status_display }}</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Connected Device</td>
|
<td>Connected Device</td>
|
||||||
<td>
|
<td>
|
||||||
{% if powerfeed.connected_endpoint %}
|
{% if object.connected_endpoint %}
|
||||||
<a href="{{ powerfeed.connected_endpoint.device.get_absolute_url }}">{{ powerfeed.connected_endpoint.device }}</a> ({{ powerfeed.connected_endpoint }})
|
<a href="{{ object.connected_endpoint.device.get_absolute_url }}">{{ object.connected_endpoint.device }}</a> ({{ object.connected_endpoint }})
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">None</span>
|
<span class="text-muted">None</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -108,12 +108,12 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Utilization (Allocated)</td>
|
<td>Utilization (Allocated)</td>
|
||||||
{% with utilization=powerfeed.connected_endpoint.get_power_draw %}
|
{% with utilization=object.connected_endpoint.get_power_draw %}
|
||||||
{% if utilization %}
|
{% if utilization %}
|
||||||
<td>
|
<td>
|
||||||
{{ utilization.allocated }}VA / {{ powerfeed.available_power }}VA
|
{{ utilization.allocated }}VA / {{ object.available_power }}VA
|
||||||
{% if powerfeed.available_power > 0 %}
|
{% if object.available_power > 0 %}
|
||||||
{% utilization_graph utilization.allocated|percentage:powerfeed.available_power %}
|
{% utilization_graph utilization.allocated|percentage:object.available_power %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -130,71 +130,71 @@
|
|||||||
<table class="table table-hover panel-body attr-table">
|
<table class="table table-hover panel-body attr-table">
|
||||||
<tr>
|
<tr>
|
||||||
<td>Supply</td>
|
<td>Supply</td>
|
||||||
<td>{{ powerfeed.get_supply_display }}</td>
|
<td>{{ object.get_supply_display }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Voltage</td>
|
<td>Voltage</td>
|
||||||
<td>{{ powerfeed.voltage }}V</td>
|
<td>{{ object.voltage }}V</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Amperage</td>
|
<td>Amperage</td>
|
||||||
<td>{{ powerfeed.amperage }}A</td>
|
<td>{{ object.amperage }}A</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Phase</td>
|
<td>Phase</td>
|
||||||
<td>{{ powerfeed.get_phase_display }}</td>
|
<td>{{ object.get_phase_display }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Max Utilization</td>
|
<td>Max Utilization</td>
|
||||||
<td>{{ powerfeed.max_utilization }}%</td>
|
<td>{{ object.max_utilization }}%</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% include 'inc/custom_fields_panel.html' with obj=powerfeed %}
|
{% include 'inc/custom_fields_panel.html' %}
|
||||||
{% include 'extras/inc/tags_panel.html' with tags=powerfeed.tags.all url='dcim:powerfeed_list' %}
|
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='dcim:powerfeed_list' %}
|
||||||
{% plugin_left_page powerfeed %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Connection</strong>
|
<strong>Connection</strong>
|
||||||
</div>
|
</div>
|
||||||
{% if powerfeed.cable %}
|
{% if object.cable %}
|
||||||
<table class="table table-hover panel-body attr-table">
|
<table class="table table-hover panel-body attr-table">
|
||||||
<tr>
|
<tr>
|
||||||
<td>Cable</td>
|
<td>Cable</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ powerfeed.cable.get_absolute_url }}">{{ powerfeed.cable }}</a>
|
<a href="{{ object.cable.get_absolute_url }}">{{ object.cable }}</a>
|
||||||
<a href="{% url 'dcim:consoleport_trace' pk=powerfeed.pk %}" class="btn btn-primary btn-xs" title="Trace">
|
<a href="{% url 'dcim:consoleport_trace' pk=object.pk %}" class="btn btn-primary btn-xs" title="Trace">
|
||||||
<i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
|
<i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if powerfeed.connected_endpoint %}
|
{% if object.connected_endpoint %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>Device</td>
|
<td>Device</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ powerfeed.connected_endpoint.device.get_absolute_url }}">{{ powerfeed.connected_endpoint.device }}</a>
|
<a href="{{ object.connected_endpoint.device.get_absolute_url }}">{{ object.connected_endpoint.device }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Name</td>
|
<td>Name</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ powerfeed.connected_endpoint.get_absolute_url }}">{{ powerfeed.connected_endpoint.name }}</a>
|
<a href="{{ object.connected_endpoint.get_absolute_url }}">{{ object.connected_endpoint.name }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Type</td>
|
<td>Type</td>
|
||||||
<td>{{ powerfeed.connected_endpoint.get_type_display|placeholder }}</td>
|
<td>{{ object.connected_endpoint.get_type_display|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Description</td>
|
<td>Description</td>
|
||||||
<td>{{ powerfeed.connected_endpoint.description|placeholder }}</td>
|
<td>{{ object.connected_endpoint.description|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Path Status</td>
|
<td>Path Status</td>
|
||||||
<td>
|
<td>
|
||||||
{% if powerfeed.path.is_active %}
|
{% if object.path.is_active %}
|
||||||
<span class="label label-success">Reachable</span>
|
<span class="label label-success">Reachable</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="label label-danger">Not Reachable</span>
|
<span class="label label-danger">Not Reachable</span>
|
||||||
@@ -206,7 +206,7 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<div class="panel-body text-muted">
|
<div class="panel-body text-muted">
|
||||||
{% if perms.dcim.add_cable %}
|
{% if perms.dcim.add_cable %}
|
||||||
<a href="{% url 'dcim:powerfeed_connect' termination_a_id=powerfeed.pk termination_b_type='power-port' %}?return_url={{ powerfeed.get_absolute_url }}" class="btn btn-primary btn-sm pull-right">
|
<a href="{% url 'dcim:powerfeed_connect' termination_a_id=object.pk termination_b_type='power-port' %}?return_url={{ object.get_absolute_url }}" class="btn btn-primary btn-sm pull-right">
|
||||||
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span> Connect
|
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span> Connect
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -219,19 +219,19 @@
|
|||||||
<strong>Comments</strong>
|
<strong>Comments</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body rendered-markdown">
|
<div class="panel-body rendered-markdown">
|
||||||
{% if powerfeed.comments %}
|
{% if object.comments %}
|
||||||
{{ powerfeed.comments|render_markdown }}
|
{{ object.comments|render_markdown }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">None</span>
|
<span class="text-muted">None</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% plugin_right_page powerfeed %}
|
{% plugin_right_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
{% plugin_full_width_page powerfeed %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -13,79 +13,79 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Device</td>
|
<td>Device</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ instance.device.get_absolute_url }}">{{ instance.device }}</a>
|
<a href="{{ object.device.get_absolute_url }}">{{ object.device }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Name</td>
|
<td>Name</td>
|
||||||
<td>{{ instance.name }}</td>
|
<td>{{ object.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Label</td>
|
<td>Label</td>
|
||||||
<td>{{ instance.label|placeholder }}</td>
|
<td>{{ object.label|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Type</td>
|
<td>Type</td>
|
||||||
<td>{{ instance.get_type_display }}</td>
|
<td>{{ object.get_type_display }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Description</td>
|
<td>Description</td>
|
||||||
<td>{{ instance.description|placeholder }}</td>
|
<td>{{ object.description|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Power Port</td>
|
<td>Power Port</td>
|
||||||
<td>{{ instance.power_port }}</td>
|
<td>{{ object.power_port }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Feed Leg</td>
|
<td>Feed Leg</td>
|
||||||
<td>{{ instance.get_feed_leg_display }}</td>
|
<td>{{ object.get_feed_leg_display }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% include 'extras/inc/tags_panel.html' with tags=instance.tags.all %}
|
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all %}
|
||||||
{% plugin_left_page instance %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Connection</strong>
|
<strong>Connection</strong>
|
||||||
</div>
|
</div>
|
||||||
{% if instance.cable %}
|
{% if object.cable %}
|
||||||
<table class="table table-hover panel-body attr-table">
|
<table class="table table-hover panel-body attr-table">
|
||||||
<tr>
|
<tr>
|
||||||
<td>Cable</td>
|
<td>Cable</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ instance.cable.get_absolute_url }}">{{ instance.cable }}</a>
|
<a href="{{ object.cable.get_absolute_url }}">{{ object.cable }}</a>
|
||||||
<a href="{% url 'dcim:poweroutlet_trace' pk=instance.pk %}" class="btn btn-primary btn-xs" title="Trace">
|
<a href="{% url 'dcim:poweroutlet_trace' pk=object.pk %}" class="btn btn-primary btn-xs" title="Trace">
|
||||||
<i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
|
<i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if instance.connected_endpoint %}
|
{% if object.connected_endpoint %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>Device</td>
|
<td>Device</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ instance.connected_endpoint.device.get_absolute_url }}">{{ instance.connected_endpoint.device }}</a>
|
<a href="{{ object.connected_endpoint.device.get_absolute_url }}">{{ object.connected_endpoint.device }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Name</td>
|
<td>Name</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ instance.connected_endpoint.get_absolute_url }}">{{ instance.connected_endpoint.name }}</a>
|
<a href="{{ object.connected_endpoint.get_absolute_url }}">{{ object.connected_endpoint.name }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Type</td>
|
<td>Type</td>
|
||||||
<td>{{ instance.connected_endpoint.get_type_display|placeholder }}</td>
|
<td>{{ object.connected_endpoint.get_type_display|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Description</td>
|
<td>Description</td>
|
||||||
<td>{{ instance.connected_endpoint.description|placeholder }}</td>
|
<td>{{ object.connected_endpoint.description|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Path Status</td>
|
<td>Path Status</td>
|
||||||
<td>
|
<td>
|
||||||
{% if instance.path.is_active %}
|
{% if object.path.is_active %}
|
||||||
<span class="label label-success">Reachable</span>
|
<span class="label label-success">Reachable</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="label label-danger">Not Reachable</span>
|
<span class="label label-danger">Not Reachable</span>
|
||||||
@@ -98,19 +98,19 @@
|
|||||||
<div class="panel-body text-muted">
|
<div class="panel-body text-muted">
|
||||||
Not connected
|
Not connected
|
||||||
{% if perms.dcim.add_cable %}
|
{% if perms.dcim.add_cable %}
|
||||||
<a href="{% url 'dcim:poweroutlet_connect' termination_a_id=instance.pk termination_b_type='power-port' %}?return_url={{ instance.get_absolute_url }}" title="Connect" class="btn btn-primary btn-sm pull-right">
|
<a href="{% url 'dcim:poweroutlet_connect' termination_a_id=object.pk termination_b_type='power-port' %}?return_url={{ object.get_absolute_url }}" title="Connect" class="btn btn-primary btn-sm pull-right">
|
||||||
<i class="mdi mdi-ethernet-cable" aria-hidden="true"></i> Connect
|
<i class="mdi mdi-ethernet-cable" aria-hidden="true"></i> Connect
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% plugin_right_page instance %}
|
{% plugin_right_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
{% plugin_full_width_page instance %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -10,11 +10,11 @@
|
|||||||
<div class="col-sm-8 col-md-9">
|
<div class="col-sm-8 col-md-9">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="{% url 'dcim:powerpanel_list' %}">Power Panels</a></li>
|
<li><a href="{% url 'dcim:powerpanel_list' %}">Power Panels</a></li>
|
||||||
<li><a href="{{ powerpanel.site.get_absolute_url }}">{{ powerpanel.site }}</a></li>
|
<li><a href="{{ object.site.get_absolute_url }}">{{ object.site }}</a></li>
|
||||||
{% if powerpanel.rack_group %}
|
{% if object.rack_group %}
|
||||||
<li><a href="{{ powerpanel.rack_group.get_absolute_url }}">{{ powerpanel.rack_group }}</a></li>
|
<li><a href="{{ object.rack_group.get_absolute_url }}">{{ object.rack_group }}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li>{{ powerpanel }}</li>
|
<li>{{ object }}</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-4 col-md-3">
|
<div class="col-sm-4 col-md-3">
|
||||||
@@ -31,26 +31,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right noprint">
|
<div class="pull-right noprint">
|
||||||
{% plugin_buttons powerpanel %}
|
{% plugin_buttons object %}
|
||||||
{% if perms.dcim.change_powerpanel %}
|
{% if perms.dcim.change_powerpanel %}
|
||||||
{% edit_button powerpanel %}
|
{% edit_button object %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.delete_powerpanel %}
|
{% if perms.dcim.delete_powerpanel %}
|
||||||
{% delete_button powerpanel %}
|
{% delete_button object %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<h1>{% block title %}{{ powerpanel }}{% endblock %}</h1>
|
<h1>{% block title %}{{ object }}{% endblock %}</h1>
|
||||||
{% include 'inc/created_updated.html' with obj=powerpanel %}
|
{% include 'inc/created_updated.html' %}
|
||||||
<div class="pull-right noprint">
|
<div class="pull-right noprint">
|
||||||
{% custom_links powerpanel %}
|
{% custom_links object %}
|
||||||
</div>
|
</div>
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
||||||
<a href="{{ powerpanel.get_absolute_url }}">Cable</a>
|
<a href="{{ object.get_absolute_url }}">Cable</a>
|
||||||
</li>
|
</li>
|
||||||
{% if perms.extras.view_objectchange %}
|
{% if perms.extras.view_objectchange %}
|
||||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||||
<a href="{% url 'dcim:powerpanel_changelog' pk=powerpanel.pk %}">Change Log</a>
|
<a href="{% url 'dcim:powerpanel_changelog' pk=object.pk %}">Change Log</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -67,14 +67,14 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Site</td>
|
<td>Site</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ powerpanel.site.get_absolute_url }}">{{ powerpanel.site }}</a>
|
<a href="{{ object.site.get_absolute_url }}">{{ object.site }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Rack Group</td>
|
<td>Rack Group</td>
|
||||||
<td>
|
<td>
|
||||||
{% if powerpanel.rack_group %}
|
{% if object.rack_group %}
|
||||||
<a href="{{ powerpanel.rack_group.get_absolute_url }}">{{ powerpanel.rack_group }}</a>
|
<a href="{{ object.rack_group.get_absolute_url }}">{{ object.rack_group }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">None</span>
|
<span class="text-muted">None</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -82,18 +82,18 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% plugin_left_page powerpanel %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
{% include 'inc/custom_fields_panel.html' with obj=powerpanel %}
|
{% include 'inc/custom_fields_panel.html' %}
|
||||||
{% include 'extras/inc/tags_panel.html' with tags=powerpanel.tags.all url='dcim:powerpanel_list' %}
|
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='dcim:powerpanel_list' %}
|
||||||
{% plugin_right_page powerpanel %}
|
{% plugin_right_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
{% include 'panel_table.html' with table=powerfeed_table heading='Connected Feeds' %}
|
{% include 'panel_table.html' with table=powerfeed_table heading='Connected Feeds' %}
|
||||||
{% plugin_full_width_page powerpanel %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -13,79 +13,79 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Device</td>
|
<td>Device</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ instance.device.get_absolute_url }}">{{ instance.device }}</a>
|
<a href="{{ object.device.get_absolute_url }}">{{ object.device }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Name</td>
|
<td>Name</td>
|
||||||
<td>{{ instance.name }}</td>
|
<td>{{ object.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Label</td>
|
<td>Label</td>
|
||||||
<td>{{ instance.label|placeholder }}</td>
|
<td>{{ object.label|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Type</td>
|
<td>Type</td>
|
||||||
<td>{{ instance.get_type_display }}</td>
|
<td>{{ object.get_type_display }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Description</td>
|
<td>Description</td>
|
||||||
<td>{{ instance.description|placeholder }}</td>
|
<td>{{ object.description|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Maximum Draw</td>
|
<td>Maximum Draw</td>
|
||||||
<td>{{ instance.maximum_draw|placeholder }}</td>
|
<td>{{ object.maximum_draw|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Allocated Draw</td>
|
<td>Allocated Draw</td>
|
||||||
<td>{{ instance.allocated_draw|placeholder }}</td>
|
<td>{{ object.allocated_draw|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% include 'extras/inc/tags_panel.html' with tags=instance.tags.all %}
|
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all %}
|
||||||
{% plugin_left_page instance %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Connection</strong>
|
<strong>Connection</strong>
|
||||||
</div>
|
</div>
|
||||||
{% if instance.cable %}
|
{% if object.cable %}
|
||||||
<table class="table table-hover panel-body attr-table">
|
<table class="table table-hover panel-body attr-table">
|
||||||
<tr>
|
<tr>
|
||||||
<td>Cable</td>
|
<td>Cable</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ instance.cable.get_absolute_url }}">{{ instance.cable }}</a>
|
<a href="{{ object.cable.get_absolute_url }}">{{ object.cable }}</a>
|
||||||
<a href="{% url 'dcim:powerport_trace' pk=instance.pk %}" class="btn btn-primary btn-xs" title="Trace">
|
<a href="{% url 'dcim:powerport_trace' pk=object.pk %}" class="btn btn-primary btn-xs" title="Trace">
|
||||||
<i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
|
<i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if instance.connected_endpoint %}
|
{% if object.connected_endpoint %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>Device</td>
|
<td>Device</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ instance.connected_endpoint.device.get_absolute_url }}">{{ instance.connected_endpoint.device }}</a>
|
<a href="{{ object.connected_endpoint.device.get_absolute_url }}">{{ object.connected_endpoint.device }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Name</td>
|
<td>Name</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ instance.connected_endpoint.get_absolute_url }}">{{ instance.connected_endpoint.name }}</a>
|
<a href="{{ object.connected_endpoint.get_absolute_url }}">{{ object.connected_endpoint.name }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Type</td>
|
<td>Type</td>
|
||||||
<td>{{ instance.connected_endpoint.get_type_display|placeholder }}</td>
|
<td>{{ object.connected_endpoint.get_type_display|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Description</td>
|
<td>Description</td>
|
||||||
<td>{{ instance.connected_endpoint.description|placeholder }}</td>
|
<td>{{ object.connected_endpoint.description|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Path Status</td>
|
<td>Path Status</td>
|
||||||
<td>
|
<td>
|
||||||
{% if instance.path.is_active %}
|
{% if object.path.is_active %}
|
||||||
<span class="label label-success">Reachable</span>
|
<span class="label label-success">Reachable</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="label label-danger">Not Reachable</span>
|
<span class="label label-danger">Not Reachable</span>
|
||||||
@@ -103,20 +103,20 @@
|
|||||||
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span> Connect
|
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span> Connect
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-right">
|
<ul class="dropdown-menu dropdown-menu-right">
|
||||||
<li><a href="{% url 'dcim:powerport_connect' termination_a_id=instance.pk termination_b_type='power-outlet' %}?return_url={{ instance.get_absolute_url }}">Power Outlet</a></li>
|
<li><a href="{% url 'dcim:powerport_connect' termination_a_id=object.pk termination_b_type='power-outlet' %}?return_url={{ object.get_absolute_url }}">Power Outlet</a></li>
|
||||||
<li><a href="{% url 'dcim:powerport_connect' termination_a_id=instance.pk termination_b_type='power-feed' %}?return_url={{ instance.get_absolute_url }}">Power Feed</a></li>
|
<li><a href="{% url 'dcim:powerport_connect' termination_a_id=object.pk termination_b_type='power-feed' %}?return_url={{ object.get_absolute_url }}">Power Feed</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% plugin_right_page instance %}
|
{% plugin_right_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
{% plugin_full_width_page instance %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -10,14 +10,14 @@
|
|||||||
<div class="col-sm-8 col-md-9">
|
<div class="col-sm-8 col-md-9">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="{% url 'dcim:rack_list' %}">Racks</a></li>
|
<li><a href="{% url 'dcim:rack_list' %}">Racks</a></li>
|
||||||
<li><a href="{% url 'dcim:rack_list' %}?site={{ rack.site.slug }}">{{ rack.site }}</a></li>
|
<li><a href="{% url 'dcim:rack_list' %}?site={{ object.site.slug }}">{{ object.site }}</a></li>
|
||||||
{% if rack.group %}
|
{% if object.group %}
|
||||||
{% for group in rack.group.get_ancestors %}
|
{% for group in object.group.get_ancestors %}
|
||||||
<li><a href="{{ group.get_absolute_url }}">{{ group }}</a></li>
|
<li><a href="{{ group.get_absolute_url }}">{{ group }}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<li><a href="{{ rack.group.get_absolute_url }}">{{ rack.group }}</a></li>
|
<li><a href="{{ object.group.get_absolute_url }}">{{ object.group }}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li>{{ rack }}</li>
|
<li>{{ object }}</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-4 col-md-3">
|
<div class="col-sm-4 col-md-3">
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right noprint">
|
<div class="pull-right noprint">
|
||||||
{% plugin_buttons rack %}
|
{% plugin_buttons object %}
|
||||||
<a {% if prev_rack %}href="{% url 'dcim:rack' pk=prev_rack.pk %}"{% else %}disabled="disabled"{% endif %} class="btn btn-primary">
|
<a {% if prev_rack %}href="{% url 'dcim:rack' pk=prev_rack.pk %}"{% else %}disabled="disabled"{% endif %} class="btn btn-primary">
|
||||||
<span class="mdi mdi-chevron-left" aria-hidden="true"></span> Previous Rack
|
<span class="mdi mdi-chevron-left" aria-hidden="true"></span> Previous Rack
|
||||||
</a>
|
</a>
|
||||||
@@ -42,30 +42,30 @@
|
|||||||
<span class="mdi mdi-chevron-right" aria-hidden="true"></span> Next Rack
|
<span class="mdi mdi-chevron-right" aria-hidden="true"></span> Next Rack
|
||||||
</a>
|
</a>
|
||||||
{% if perms.dcim.add_rack %}
|
{% if perms.dcim.add_rack %}
|
||||||
{% clone_button rack %}
|
{% clone_button object %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.change_rack %}
|
{% if perms.dcim.change_rack %}
|
||||||
{% edit_button rack %}
|
{% edit_button object %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.delete_rack %}
|
{% if perms.dcim.delete_rack %}
|
||||||
{% delete_button rack %}
|
{% delete_button object %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<h1>{% block title %}Rack {{ rack }}{% endblock %}</h1>
|
<h1>{% block title %}Rack {{ object }}{% endblock %}</h1>
|
||||||
{% include 'inc/created_updated.html' with obj=rack %}
|
{% include 'inc/created_updated.html' %}
|
||||||
<div class="pull-right noprint">
|
<div class="pull-right noprint">
|
||||||
<button class="btn btn-sm btn-default toggle-images" selected="selected">
|
<button class="btn btn-sm btn-default toggle-images" selected="selected">
|
||||||
<span class="mdi mdi-checkbox-marked-circle-outline" aria-hidden="true"></span> Show Images
|
<span class="mdi mdi-checkbox-marked-circle-outline" aria-hidden="true"></span> Show Images
|
||||||
</button>
|
</button>
|
||||||
{% custom_links rack %}
|
{% custom_links object %}
|
||||||
</div>
|
</div>
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
||||||
<a href="{{ rack.get_absolute_url }}">Rack</a>
|
<a href="{{ object.get_absolute_url }}">Rack</a>
|
||||||
</li>
|
</li>
|
||||||
{% if perms.extras.view_objectchange %}
|
{% if perms.extras.view_objectchange %}
|
||||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||||
<a href="{% url 'dcim:rack_changelog' pk=rack.pk %}">Change Log</a>
|
<a href="{% url 'dcim:rack_changelog' pk=object.pk %}">Change Log</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -82,20 +82,20 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Site</td>
|
<td>Site</td>
|
||||||
<td>
|
<td>
|
||||||
{% if rack.site.region %}
|
{% if object.site.region %}
|
||||||
<a href="{{ rack.site.region.get_absolute_url }}">{{ rack.site.region }}</a> /
|
<a href="{{ object.site.region.get_absolute_url }}">{{ object.site.region }}</a> /
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{% url 'dcim:site' slug=rack.site.slug %}">{{ rack.site }}</a>
|
<a href="{% url 'dcim:site' slug=object.site.slug %}">{{ object.site }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Group</td>
|
<td>Group</td>
|
||||||
<td>
|
<td>
|
||||||
{% if rack.group %}
|
{% if object.group %}
|
||||||
{% for group in rack.group.get_ancestors %}
|
{% for group in object.group.get_ancestors %}
|
||||||
<a href="{{ group.get_absolute_url }}">{{ group }}</a> /
|
<a href="{{ group.get_absolute_url }}">{{ group }}</a> /
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<a href="{{ rack.group.get_absolute_url }}">{{ rack.group }}</a>
|
<a href="{{ object.group.get_absolute_url }}">{{ object.group }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">None</span>
|
<span class="text-muted">None</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -103,16 +103,16 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Facility ID</td>
|
<td>Facility ID</td>
|
||||||
<td>{{ rack.facility_id|placeholder }}</td>
|
<td>{{ object.facility_id|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Tenant</td>
|
<td>Tenant</td>
|
||||||
<td>
|
<td>
|
||||||
{% if rack.tenant %}
|
{% if object.tenant %}
|
||||||
{% if rack.tenant.group %}
|
{% if object.tenant.group %}
|
||||||
<a href="{{ rack.tenant.group.get_absolute_url }}">{{ rack.tenant.group }}</a> /
|
<a href="{{ object.tenant.group.get_absolute_url }}">{{ object.tenant.group }}</a> /
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{{ rack.tenant.get_absolute_url }}">{{ rack.tenant }}</a>
|
<a href="{{ object.tenant.get_absolute_url }}">{{ object.tenant }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">None</span>
|
<span class="text-muted">None</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -121,14 +121,14 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Status</td>
|
<td>Status</td>
|
||||||
<td>
|
<td>
|
||||||
{{ rack.get_status_display }}
|
{{ object.get_status_display }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Role</td>
|
<td>Role</td>
|
||||||
<td>
|
<td>
|
||||||
{% if rack.role %}
|
{% if object.role %}
|
||||||
<a href="{{ rack.role.get_absolute_url }}">{{ rack.role }}</a>
|
<a href="{{ object.role.get_absolute_url }}">{{ object.role }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">None</span>
|
<span class="text-muted">None</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -136,25 +136,25 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Serial Number</td>
|
<td>Serial Number</td>
|
||||||
<td>{{ rack.serial|placeholder }}</td>
|
<td>{{ object.serial|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Asset Tag</td>
|
<td>Asset Tag</td>
|
||||||
<td>{{ rack.asset_tag|placeholder }}</td>
|
<td>{{ object.asset_tag|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Devices</td>
|
<td>Devices</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'dcim:device_list' %}?rack_id={{ rack.id }}">{{ device_count }}</a>
|
<a href="{% url 'dcim:device_list' %}?rack_id={{ object.id }}">{{ device_count }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Space Utilization</td>
|
<td>Space Utilization</td>
|
||||||
<td>{% utilization_graph rack.get_utilization %}</td>
|
<td>{% utilization_graph object.get_utilization %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Power Utilization</td>
|
<td>Power Utilization</td>
|
||||||
<td>{% utilization_graph rack.get_power_utilization %}</td>
|
<td>{% utilization_graph object.get_power_utilization %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -166,8 +166,8 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Type</td>
|
<td>Type</td>
|
||||||
<td>
|
<td>
|
||||||
{% if rack.type %}
|
{% if object.type %}
|
||||||
{{ rack.get_type_display }}
|
{{ object.get_type_display }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">None</span>
|
<span class="text-muted">None</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -175,17 +175,17 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Width</td>
|
<td>Width</td>
|
||||||
<td>{{ rack.get_width_display }}</td>
|
<td>{{ object.get_width_display }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Height</td>
|
<td>Height</td>
|
||||||
<td>{{ rack.u_height }}U ({% if rack.desc_units %}descending{% else %}ascending{% endif %})</td>
|
<td>{{ object.u_height }}U ({% if object.desc_units %}descending{% else %}ascending{% endif %})</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Outer Width</td>
|
<td>Outer Width</td>
|
||||||
<td>
|
<td>
|
||||||
{% if rack.outer_width %}
|
{% if object.outer_width %}
|
||||||
<span>{{ rack.outer_width }} {{ rack.get_outer_unit_display }}</span>
|
<span>{{ object.outer_width }} {{ object.get_outer_unit_display }}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">—</span>
|
<span class="text-muted">—</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -194,8 +194,8 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Outer Depth</td>
|
<td>Outer Depth</td>
|
||||||
<td>
|
<td>
|
||||||
{% if rack.outer_depth %}
|
{% if object.outer_depth %}
|
||||||
<span>{{ rack.outer_depth }} {{ rack.get_outer_unit_display }}</span>
|
<span>{{ object.outer_depth }} {{ object.get_outer_unit_display }}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">—</span>
|
<span class="text-muted">—</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -203,15 +203,15 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% include 'inc/custom_fields_panel.html' with obj=rack %}
|
{% include 'inc/custom_fields_panel.html' %}
|
||||||
{% include 'extras/inc/tags_panel.html' with tags=rack.tags.all url='dcim:rack_list' %}
|
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='dcim:rack_list' %}
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Comments</strong>
|
<strong>Comments</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body rendered-markdown">
|
<div class="panel-body rendered-markdown">
|
||||||
{% if rack.comments %}
|
{% if object.comments %}
|
||||||
{{ rack.comments|render_markdown }}
|
{{ object.comments|render_markdown }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">None</span>
|
<span class="text-muted">None</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -260,10 +260,10 @@
|
|||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Images</strong>
|
<strong>Images</strong>
|
||||||
</div>
|
</div>
|
||||||
{% include 'inc/image_attachments.html' with images=rack.images.all %}
|
{% include 'inc/image_attachments.html' with images=object.images.all %}
|
||||||
{% if perms.extras.add_imageattachment %}
|
{% if perms.extras.add_imageattachment %}
|
||||||
<div class="panel-footer text-right noprint">
|
<div class="panel-footer text-right noprint">
|
||||||
<a href="{% url 'dcim:rack_add_image' object_id=rack.pk %}" class="btn btn-primary btn-xs">
|
<a href="{% url 'dcim:rack_add_image' object_id=object.pk %}" class="btn btn-primary btn-xs">
|
||||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span>
|
<span class="mdi mdi-plus-thick" aria-hidden="true"></span>
|
||||||
Attach an image
|
Attach an image
|
||||||
</a>
|
</a>
|
||||||
@@ -300,12 +300,12 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="text-right noprint">
|
<td class="text-right noprint">
|
||||||
{% if perms.dcim.change_rackreservation %}
|
{% if perms.dcim.change_rackreservation %}
|
||||||
<a href="{% url 'dcim:rackreservation_edit' pk=resv.pk %}?return_url={{ rack.get_absolute_url }}" class="btn btn-warning btn-xs" title="Edit reservation">
|
<a href="{% url 'dcim:rackreservation_edit' pk=resv.pk %}?return_url={{ object.get_absolute_url }}" class="btn btn-warning btn-xs" title="Edit reservation">
|
||||||
<i class="mdi mdi-pencil" aria-hidden="true"></i>
|
<i class="mdi mdi-pencil" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.delete_rackreservation %}
|
{% if perms.dcim.delete_rackreservation %}
|
||||||
<a href="{% url 'dcim:rackreservation_delete' pk=resv.pk %}?return_url={{ rack.get_absolute_url }}" class="btn btn-danger btn-xs" title="Delete reservation">
|
<a href="{% url 'dcim:rackreservation_delete' pk=resv.pk %}?return_url={{ object.get_absolute_url }}" class="btn btn-danger btn-xs" title="Delete reservation">
|
||||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i>
|
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -318,14 +318,14 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_rackreservation %}
|
{% if perms.dcim.add_rackreservation %}
|
||||||
<div class="panel-footer text-right noprint">
|
<div class="panel-footer text-right noprint">
|
||||||
<a href="{% url 'dcim:rackreservation_add' %}?rack={{ rack.pk }}&return_url={{ rack.get_absolute_url }}" class="btn btn-primary btn-xs">
|
<a href="{% url 'dcim:rackreservation_add' %}?rack={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-primary btn-xs">
|
||||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span>
|
<span class="mdi mdi-plus-thick" aria-hidden="true"></span>
|
||||||
Add a reservation
|
Add a reservation
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% plugin_left_page rack %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="row" style="margin-bottom: 20px">
|
<div class="row" style="margin-bottom: 20px">
|
||||||
@@ -371,19 +371,19 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_device %}
|
{% if perms.dcim.add_device %}
|
||||||
<div class="panel-footer text-right noprint">
|
<div class="panel-footer text-right noprint">
|
||||||
<a href="{% url 'dcim:device_add' %}?site={{ rack.site.pk }}&rack={{ rack.pk }}" class="btn btn-primary btn-xs">
|
<a href="{% url 'dcim:device_add' %}?site={{ object.site.pk }}&rack={{ object.pk }}" class="btn btn-primary btn-xs">
|
||||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span>
|
<span class="mdi mdi-plus-thick" aria-hidden="true"></span>
|
||||||
Add a non-racked device
|
Add a non-racked device
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% plugin_right_page rack %}
|
{% plugin_right_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
{% plugin_full_width_page rack %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
<div class="col-sm-8 col-md-9">
|
<div class="col-sm-8 col-md-9">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="{% url 'dcim:rackreservation_list' %}">Rack Reservations</a></li>
|
<li><a href="{% url 'dcim:rackreservation_list' %}">Rack Reservations</a></li>
|
||||||
<li><a href="{{ rackreservation.rack.get_absolute_url }}">{{ rackreservation.rack }}</a></li>
|
<li><a href="{{ object.rack.get_absolute_url }}">{{ object.rack }}</a></li>
|
||||||
<li>Units {{ rackreservation.unit_list }}</li>
|
<li>Units {{ object.unit_list }}</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-4 col-md-3">
|
<div class="col-sm-4 col-md-3">
|
||||||
@@ -28,26 +28,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right noprint">
|
<div class="pull-right noprint">
|
||||||
{% plugin_buttons rackreservation %}
|
{% plugin_buttons object %}
|
||||||
{% if perms.dcim.change_rackreservation %}
|
{% if perms.dcim.change_rackreservation %}
|
||||||
{% edit_button rackreservation %}
|
{% edit_button object %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.delete_rackreservation %}
|
{% if perms.dcim.delete_rackreservation %}
|
||||||
{% delete_button rackreservation %}
|
{% delete_button object %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<h1>{% block title %}{{ rackreservation }}{% endblock %}</h1>
|
<h1>{% block title %}{{ object }}{% endblock %}</h1>
|
||||||
{% include 'inc/created_updated.html' with obj=rackreservation %}
|
{% include 'inc/created_updated.html' %}
|
||||||
<div class="pull-right noprint">
|
<div class="pull-right noprint">
|
||||||
{% custom_links rackreservation %}
|
{% custom_links object %}
|
||||||
</div>
|
</div>
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
||||||
<a href="{{ rackreservation.get_absolute_url }}">Rack</a>
|
<a href="{{ object.get_absolute_url }}">Rack</a>
|
||||||
</li>
|
</li>
|
||||||
{% if perms.extras.view_objectchange %}
|
{% if perms.extras.view_objectchange %}
|
||||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||||
<a href="{% url 'dcim:rackreservation_changelog' pk=rackreservation.pk %}">Change Log</a>
|
<a href="{% url 'dcim:rackreservation_changelog' pk=object.pk %}">Change Log</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
<strong>Rack</strong>
|
<strong>Rack</strong>
|
||||||
</div>
|
</div>
|
||||||
<table class="table table-hover panel-body attr-table">
|
<table class="table table-hover panel-body attr-table">
|
||||||
{% with rack=rackreservation.rack %}
|
{% with rack=object.rack %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>Site</td>
|
<td>Site</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -97,16 +97,16 @@
|
|||||||
<table class="table table-hover panel-body attr-table">
|
<table class="table table-hover panel-body attr-table">
|
||||||
<tr>
|
<tr>
|
||||||
<td>Units</td>
|
<td>Units</td>
|
||||||
<td>{{ rackreservation.unit_list }}</td>
|
<td>{{ object.unit_list }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Tenant</td>
|
<td>Tenant</td>
|
||||||
<td>
|
<td>
|
||||||
{% if rackreservation.tenant %}
|
{% if object.tenant %}
|
||||||
{% if rackreservation.tenant.group %}
|
{% if object.tenant.group %}
|
||||||
<a href="{{ rackreservation.tenant.group.get_absolute_url }}">{{ rackreservation.tenant.group }}</a> /
|
<a href="{{ object.tenant.group.get_absolute_url }}">{{ object.tenant.group }}</a> /
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{{ rackreservation.tenant.get_absolute_url }}">{{ rackreservation.tenant }}</a>
|
<a href="{{ object.tenant.get_absolute_url }}">{{ object.tenant }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">None</span>
|
<span class="text-muted">None</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -114,20 +114,20 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>User</td>
|
<td>User</td>
|
||||||
<td>{{ rackreservation.user }}</td>
|
<td>{{ object.user }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Description</td>
|
<td>Description</td>
|
||||||
<td>{{ rackreservation.description }}</td>
|
<td>{{ object.description }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% include 'inc/custom_fields_panel.html' with obj=rackreservation %}
|
{% include 'inc/custom_fields_panel.html' %}
|
||||||
{% include 'extras/inc/tags_panel.html' with tags=rackreservation.tags.all url='dcim:rackreservation_list' %}
|
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='dcim:rackreservation_list' %}
|
||||||
{% plugin_left_page rackreservation %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
{% with rack=rackreservation.rack %}
|
{% with rack=object.rack %}
|
||||||
<div class="row" style="margin-bottom: 20px">
|
<div class="row" style="margin-bottom: 20px">
|
||||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||||
<div class="rack_header">
|
<div class="rack_header">
|
||||||
@@ -143,12 +143,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% plugin_right_page rackreservation %}
|
{% plugin_right_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
{% plugin_full_width_page rackreservation %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -13,46 +13,46 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Device</td>
|
<td>Device</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ instance.device.get_absolute_url }}">{{ instance.device }}</a>
|
<a href="{{ object.device.get_absolute_url }}">{{ object.device }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Name</td>
|
<td>Name</td>
|
||||||
<td>{{ instance.name }}</td>
|
<td>{{ object.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Label</td>
|
<td>Label</td>
|
||||||
<td>{{ instance.label|placeholder }}</td>
|
<td>{{ object.label|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Type</td>
|
<td>Type</td>
|
||||||
<td>{{ instance.get_type_display }}</td>
|
<td>{{ object.get_type_display }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Positions</td>
|
<td>Positions</td>
|
||||||
<td>{{ instance.positions }}</td>
|
<td>{{ object.positions }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Description</td>
|
<td>Description</td>
|
||||||
<td>{{ instance.description|placeholder }}</td>
|
<td>{{ object.description|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% include 'extras/inc/tags_panel.html' with tags=instance.tags.all %}
|
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all %}
|
||||||
{% plugin_left_page instance %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Connection</strong>
|
<strong>Connection</strong>
|
||||||
</div>
|
</div>
|
||||||
{% if instance.cable %}
|
{% if object.cable %}
|
||||||
<table class="table table-hover panel-body attr-table">
|
<table class="table table-hover panel-body attr-table">
|
||||||
<tr>
|
<tr>
|
||||||
<td>Cable</td>
|
<td>Cable</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ instance.cable.get_absolute_url }}">{{ instance.cable }}</a>
|
<a href="{{ object.cable.get_absolute_url }}">{{ object.cable }}</a>
|
||||||
<a href="{% url 'dcim:rearport_trace' pk=instance.pk %}" class="btn btn-primary btn-xs" title="Trace">
|
<a href="{% url 'dcim:rearport_trace' pk=object.pk %}" class="btn btn-primary btn-xs" title="Trace">
|
||||||
<i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
|
<i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
@@ -60,10 +60,10 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Connection Status</td>
|
<td>Connection Status</td>
|
||||||
<td>
|
<td>
|
||||||
{% if instance.cable.status %}
|
{% if object.cable.status %}
|
||||||
<span class="label label-success">{{ instance.cable.get_status_display }}</span>
|
<span class="label label-success">{{ object.cable.get_status_display }}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="label label-info">{{ instance.cable.get_status_display }}</span>
|
<span class="label label-info">{{ object.cable.get_status_display }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -77,22 +77,22 @@
|
|||||||
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span> Connect
|
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span> Connect
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-right">
|
<ul class="dropdown-menu dropdown-menu-right">
|
||||||
<li><a href="{% url 'dcim:rearport_connect' termination_a_id=instance.pk termination_b_type='interface' %}?return_url={{ instance.get_absolute_url }}">Interface</a></li>
|
<li><a href="{% url 'dcim:rearport_connect' termination_a_id=object.pk termination_b_type='interface' %}?return_url={{ object.get_absolute_url }}">Interface</a></li>
|
||||||
<li><a href="{% url 'dcim:rearport_connect' termination_a_id=instance.pk termination_b_type='front-port' %}?return_url={{ instance.get_absolute_url }}">Front Port</a></li>
|
<li><a href="{% url 'dcim:rearport_connect' termination_a_id=object.pk termination_b_type='front-port' %}?return_url={{ object.get_absolute_url }}">Front Port</a></li>
|
||||||
<li><a href="{% url 'dcim:rearport_connect' termination_a_id=instance.pk termination_b_type='rear-port' %}?return_url={{ instance.get_absolute_url }}">Rear Port</a></li>
|
<li><a href="{% url 'dcim:rearport_connect' termination_a_id=object.pk termination_b_type='rear-port' %}?return_url={{ object.get_absolute_url }}">Rear Port</a></li>
|
||||||
<li><a href="{% url 'dcim:rearport_connect' termination_a_id=instance.pk termination_b_type='circuit-termination' %}?return_url={{ instance.get_absolute_url }}">Circuit Termination</a></li>
|
<li><a href="{% url 'dcim:rearport_connect' termination_a_id=object.pk termination_b_type='circuit-termination' %}?return_url={{ object.get_absolute_url }}">Circuit Termination</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% plugin_right_page instance %}
|
{% plugin_right_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
{% plugin_full_width_page instance %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -11,13 +11,13 @@
|
|||||||
<div class="col-sm-8 col-md-9">
|
<div class="col-sm-8 col-md-9">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="{% url 'dcim:site_list' %}">Sites</a></li>
|
<li><a href="{% url 'dcim:site_list' %}">Sites</a></li>
|
||||||
{% if site.region %}
|
{% if object.region %}
|
||||||
{% for region in site.region.get_ancestors %}
|
{% for region in object.region.get_ancestors %}
|
||||||
<li><a href="{{ region.get_absolute_url }}">{{ region }}</a></li>
|
<li><a href="{{ region.get_absolute_url }}">{{ region }}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<li><a href="{{ site.region.get_absolute_url }}">{{ site.region }}</a></li>
|
<li><a href="{{ object.region.get_absolute_url }}">{{ object.region }}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li>{{ site }}</li>
|
<li>{{ object }}</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-4 col-md-3">
|
<div class="col-sm-4 col-md-3">
|
||||||
@@ -34,29 +34,29 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right noprint">
|
<div class="pull-right noprint">
|
||||||
{% plugin_buttons site %}
|
{% plugin_buttons object %}
|
||||||
{% if perms.dcim.add_site %}
|
{% if perms.dcim.add_site %}
|
||||||
{% clone_button site %}
|
{% clone_button object %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.change_site %}
|
{% if perms.dcim.change_site %}
|
||||||
{% edit_button site %}
|
{% edit_button object %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.delete_site %}
|
{% if perms.dcim.delete_site %}
|
||||||
{% delete_button site %}
|
{% delete_button object %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<h1>{% block title %}{{ site }}{% endblock %}</h1>
|
<h1>{% block title %}{{ object }}{% endblock %}</h1>
|
||||||
{% include 'inc/created_updated.html' with obj=site %}
|
{% include 'inc/created_updated.html' %}
|
||||||
<div class="pull-right noprint">
|
<div class="pull-right noprint">
|
||||||
{% custom_links site %}
|
{% custom_links object %}
|
||||||
</div>
|
</div>
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
||||||
<a href="{{ site.get_absolute_url }}">Site</a>
|
<a href="{{ object.get_absolute_url }}">Site</a>
|
||||||
</li>
|
</li>
|
||||||
{% if perms.extras.view_objectchange %}
|
{% if perms.extras.view_objectchange %}
|
||||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||||
<a href="{% url 'dcim:site_changelog' slug=site.slug %}">Change Log</a>
|
<a href="{% url 'dcim:site_changelog' slug=object.slug %}">Change Log</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -73,17 +73,17 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Status</td>
|
<td>Status</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="label label-{{ site.get_status_class }}">{{ site.get_status_display }}</span>
|
<span class="label label-{{ object.get_status_class }}">{{ object.get_status_display }}</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Region</td>
|
<td>Region</td>
|
||||||
<td>
|
<td>
|
||||||
{% if site.region %}
|
{% if object.region %}
|
||||||
{% for region in site.region.get_ancestors %}
|
{% for region in object.region.get_ancestors %}
|
||||||
<a href="{{ region.get_absolute_url }}">{{ region }}</a> /
|
<a href="{{ region.get_absolute_url }}">{{ region }}</a> /
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<a href="{{ site.region.get_absolute_url }}">{{ site.region }}</a>
|
<a href="{{ object.region.get_absolute_url }}">{{ object.region }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">None</span>
|
<span class="text-muted">None</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -92,11 +92,11 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Tenant</td>
|
<td>Tenant</td>
|
||||||
<td>
|
<td>
|
||||||
{% if site.tenant %}
|
{% if object.tenant %}
|
||||||
{% if site.tenant.group %}
|
{% if object.tenant.group %}
|
||||||
<a href="{{ site.tenant.group.get_absolute_url }}">{{ site.tenant.group }}</a> /
|
<a href="{{ object.tenant.group.get_absolute_url }}">{{ object.tenant.group }}</a> /
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{{ site.tenant.get_absolute_url }}">{{ site.tenant }}</a>
|
<a href="{{ object.tenant.get_absolute_url }}">{{ object.tenant }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">None</span>
|
<span class="text-muted">None</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -104,18 +104,18 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Facility</td>
|
<td>Facility</td>
|
||||||
<td>{{ site.facility|placeholder }}</td>
|
<td>{{ object.facility|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>AS Number</td>
|
<td>AS Number</td>
|
||||||
<td>{{ site.asn|placeholder }}</td>
|
<td>{{ object.asn|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Time Zone</td>
|
<td>Time Zone</td>
|
||||||
<td>
|
<td>
|
||||||
{% if site.time_zone %}
|
{% if object.time_zone %}
|
||||||
{{ site.time_zone }} (UTC {{ site.time_zone|tzoffset }})<br />
|
{{ object.time_zone }} (UTC {{ object.time_zone|tzoffset }})<br />
|
||||||
<small class="text-muted">Site time: {% timezone site.time_zone %}{% now "SHORT_DATETIME_FORMAT" %}{% endtimezone %}</small>
|
<small class="text-muted">Site time: {% timezone object.time_zone %}{% now "SHORT_DATETIME_FORMAT" %}{% endtimezone %}</small>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">—</span>
|
<span class="text-muted">—</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -123,7 +123,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Description</td>
|
<td>Description</td>
|
||||||
<td>{{ site.description|placeholder }}</td>
|
<td>{{ object.description|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -135,13 +135,13 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Physical Address</td>
|
<td>Physical Address</td>
|
||||||
<td>
|
<td>
|
||||||
{% if site.physical_address %}
|
{% if object.physical_address %}
|
||||||
<div class="pull-right noprint">
|
<div class="pull-right noprint">
|
||||||
<a href="http://maps.google.com/?q={{ site.physical_address|urlencode }}" target="_blank" class="btn btn-primary btn-xs">
|
<a href="http://maps.google.com/?q={{ object.physical_address|urlencode }}" target="_blank" class="btn btn-primary btn-xs">
|
||||||
<i class="mdi mdi-map-marker"></i> Map it
|
<i class="mdi mdi-map-marker"></i> Map it
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<span>{{ site.physical_address|linebreaksbr }}</span>
|
<span>{{ object.physical_address|linebreaksbr }}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">—</span>
|
<span class="text-muted">—</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -149,18 +149,18 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Shipping Address</td>
|
<td>Shipping Address</td>
|
||||||
<td>{{ site.shipping_address|linebreaksbr|placeholder }}</td>
|
<td>{{ object.shipping_address|linebreaksbr|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>GPS Coordinates</td>
|
<td>GPS Coordinates</td>
|
||||||
<td>
|
<td>
|
||||||
{% if site.latitude and site.longitude %}
|
{% if object.latitude and object.longitude %}
|
||||||
<div class="pull-right noprint">
|
<div class="pull-right noprint">
|
||||||
<a href="http://maps.google.com/?q={{ site.latitude }},{{ site.longitude }}" target="_blank" class="btn btn-primary btn-xs">
|
<a href="http://maps.google.com/?q={{ object.latitude }},{{ object.longitude }}" target="_blank" class="btn btn-primary btn-xs">
|
||||||
<i class="mdi mdi-map-marker"></i> Map it
|
<i class="mdi mdi-map-marker"></i> Map it
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<span>{{ site.latitude }}, {{ site.longitude }}</span>
|
<span>{{ object.latitude }}, {{ object.longitude }}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">—</span>
|
<span class="text-muted">—</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -168,13 +168,13 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Contact Name</td>
|
<td>Contact Name</td>
|
||||||
<td>{{ site.contact_name|placeholder }}</td>
|
<td>{{ object.contact_name|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Contact Phone</td>
|
<td>Contact Phone</td>
|
||||||
<td>
|
<td>
|
||||||
{% if site.contact_phone %}
|
{% if object.contact_phone %}
|
||||||
<a href="tel:{{ site.contact_phone }}">{{ site.contact_phone }}</a>
|
<a href="tel:{{ object.contact_phone }}">{{ object.contact_phone }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">—</span>
|
<span class="text-muted">—</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -183,8 +183,8 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Contact E-Mail</td>
|
<td>Contact E-Mail</td>
|
||||||
<td>
|
<td>
|
||||||
{% if site.contact_email %}
|
{% if object.contact_email %}
|
||||||
<a href="mailto:{{ site.contact_email }}">{{ site.contact_email }}</a>
|
<a href="mailto:{{ object.contact_email }}">{{ object.contact_email }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">—</span>
|
<span class="text-muted">—</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -192,21 +192,21 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% include 'inc/custom_fields_panel.html' with obj=site %}
|
{% include 'inc/custom_fields_panel.html' %}
|
||||||
{% include 'extras/inc/tags_panel.html' with tags=site.tags.all url='dcim:site_list' %}
|
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='dcim:site_list' %}
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Comments</strong>
|
<strong>Comments</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body rendered-markdown">
|
<div class="panel-body rendered-markdown">
|
||||||
{% if site.comments %}
|
{% if object.comments %}
|
||||||
{{ site.comments|render_markdown }}
|
{{ object.comments|render_markdown }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">None</span>
|
<span class="text-muted">None</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% plugin_left_page site %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-5">
|
<div class="col-md-5">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
@@ -215,27 +215,27 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row panel-body">
|
<div class="row panel-body">
|
||||||
<div class="col-md-4 text-center">
|
<div class="col-md-4 text-center">
|
||||||
<h2><a href="{% url 'dcim:rack_list' %}?site={{ site.slug }}" class="btn {% if stats.rack_count %}btn-primary{% else %}btn-default{% endif %} btn-lg">{{ stats.rack_count }}</a></h2>
|
<h2><a href="{% url 'dcim:rack_list' %}?site={{ object.slug }}" class="btn {% if stats.rack_count %}btn-primary{% else %}btn-default{% endif %} btn-lg">{{ stats.rack_count }}</a></h2>
|
||||||
<p>Racks</p>
|
<p>Racks</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 text-center">
|
<div class="col-md-4 text-center">
|
||||||
<h2><a href="{% url 'dcim:device_list' %}?site={{ site.slug }}" class="btn {% if stats.device_count %}btn-primary{% else %}btn-default{% endif %} btn-lg">{{ stats.device_count }}</a></h2>
|
<h2><a href="{% url 'dcim:device_list' %}?site={{ object.slug }}" class="btn {% if stats.device_count %}btn-primary{% else %}btn-default{% endif %} btn-lg">{{ stats.device_count }}</a></h2>
|
||||||
<p>Devices</p>
|
<p>Devices</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 text-center">
|
<div class="col-md-4 text-center">
|
||||||
<h2><a href="{% url 'ipam:prefix_list' %}?site={{ site.slug }}" class="btn {% if stats.prefix_count %}btn-primary{% else %}btn-default{% endif %} btn-lg">{{ stats.prefix_count }}</a></h2>
|
<h2><a href="{% url 'ipam:prefix_list' %}?site={{ object.slug }}" class="btn {% if stats.prefix_count %}btn-primary{% else %}btn-default{% endif %} btn-lg">{{ stats.prefix_count }}</a></h2>
|
||||||
<p>Prefixes</p>
|
<p>Prefixes</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 text-center">
|
<div class="col-md-4 text-center">
|
||||||
<h2><a href="{% url 'ipam:vlan_list' %}?site={{ site.slug }}" class="btn {% if stats.vlan_count %}btn-primary{% else %}btn-default{% endif %} btn-lg">{{ stats.vlan_count }}</a></h2>
|
<h2><a href="{% url 'ipam:vlan_list' %}?site={{ object.slug }}" class="btn {% if stats.vlan_count %}btn-primary{% else %}btn-default{% endif %} btn-lg">{{ stats.vlan_count }}</a></h2>
|
||||||
<p>VLANs</p>
|
<p>VLANs</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 text-center">
|
<div class="col-md-4 text-center">
|
||||||
<h2><a href="{% url 'circuits:circuit_list' %}?site={{ site.slug }}" class="btn {% if stats.circuit_count %}btn-primary{% else %}btn-default{% endif %} btn-lg">{{ stats.circuit_count }}</a></h2>
|
<h2><a href="{% url 'circuits:circuit_list' %}?site={{ object.slug }}" class="btn {% if stats.circuit_count %}btn-primary{% else %}btn-default{% endif %} btn-lg">{{ stats.circuit_count }}</a></h2>
|
||||||
<p>Circuits</p>
|
<p>Circuits</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 text-center">
|
<div class="col-md-4 text-center">
|
||||||
<h2><a href="{% url 'virtualization:virtualmachine_list' %}?site={{ site.slug }}" class="btn {% if stats.vm_count %}btn-primary{% else %}btn-default{% endif %} btn-lg">{{ stats.vm_count }}</a></h2>
|
<h2><a href="{% url 'virtualization:virtualmachine_list' %}?site={{ object.slug }}" class="btn {% if stats.vm_count %}btn-primary{% else %}btn-default{% endif %} btn-lg">{{ stats.vm_count }}</a></h2>
|
||||||
<p>Virtual Machines</p>
|
<p>Virtual Machines</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -260,7 +260,7 @@
|
|||||||
<td><i class="mdi mdi-folder-open"></i> All racks</td>
|
<td><i class="mdi mdi-folder-open"></i> All racks</td>
|
||||||
<td>{{ stats.rack_count }}</td>
|
<td>{{ stats.rack_count }}</td>
|
||||||
<td class="text-right noprint">
|
<td class="text-right noprint">
|
||||||
<a href="{% url 'dcim:rack_elevation_list' %}?site={{ site.slug }}" class="btn btn-xs btn-primary" title="View elevations">
|
<a href="{% url 'dcim:rack_elevation_list' %}?site={{ object.slug }}" class="btn btn-xs btn-primary" title="View elevations">
|
||||||
<i class="mdi mdi-server"></i>
|
<i class="mdi mdi-server"></i>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
@@ -271,22 +271,22 @@
|
|||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Images</strong>
|
<strong>Images</strong>
|
||||||
</div>
|
</div>
|
||||||
{% include 'inc/image_attachments.html' with images=site.images.all %}
|
{% include 'inc/image_attachments.html' with images=object.images.all %}
|
||||||
{% if perms.extras.add_imageattachment %}
|
{% if perms.extras.add_imageattachment %}
|
||||||
<div class="panel-footer text-right noprint">
|
<div class="panel-footer text-right noprint">
|
||||||
<a href="{% url 'dcim:site_add_image' object_id=site.pk %}" class="btn btn-primary btn-xs">
|
<a href="{% url 'dcim:site_add_image' object_id=object.pk %}" class="btn btn-primary btn-xs">
|
||||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span>
|
<span class="mdi mdi-plus-thick" aria-hidden="true"></span>
|
||||||
Attach an image
|
Attach an image
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% plugin_right_page site %}
|
{% plugin_right_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
{% plugin_full_width_page site %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user