├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── 01-feature_request.yaml
│ ├── 02-bug_report.yaml
│ ├── 03-housekeeping.yaml
│ └── config.yml
├── configuration.testing.py
├── release-drafter.yml
└── workflows
│ ├── build-test.yml
│ ├── ci.yml
│ ├── pr_approval.yml
│ ├── pypi.yml
│ └── release.yml
├── .gitignore
├── .idea
└── modules.xml
├── LICENSE
├── MANIFEST.in
├── README.md
├── netbox_routing
├── __init__.py
├── api
│ ├── __init__.py
│ ├── _serializers
│ │ ├── __init__.py
│ │ ├── bgp.py
│ │ ├── eigrp.py
│ │ ├── objects.py
│ │ ├── ospf.py
│ │ └── static.py
│ ├── field_serializers
│ │ ├── __init__.py
│ │ └── ip.py
│ ├── serializers.py
│ ├── urls.py
│ └── views
│ │ ├── __init__.py
│ │ ├── bgp.py
│ │ ├── eigrp.py
│ │ ├── objects.py
│ │ ├── ospf.py
│ │ └── static.py
├── choices
│ ├── __init__.py
│ ├── base.py
│ ├── bgp.py
│ ├── eigrp.py
│ └── objects.py
├── constants
│ ├── __init__.py
│ ├── bgp.py
│ └── eigrp.py
├── fields
│ ├── __init__.py
│ └── ip.py
├── filtersets
│ ├── __init__.py
│ ├── bgp.py
│ ├── eigrp.py
│ ├── objects.py
│ ├── ospf.py
│ └── static.py
├── forms
│ ├── __init__.py
│ ├── bgp.py
│ ├── bulk_edit
│ │ ├── __init__.py
│ │ ├── eigrp.py
│ │ ├── objects.py
│ │ ├── ospf.py
│ │ └── static.py
│ ├── bulk_import
│ │ ├── __init__.py
│ │ ├── eigrp.py
│ │ └── ospf.py
│ ├── eigrp.py
│ ├── fields.py
│ ├── filtersets
│ │ ├── __init__.py
│ │ ├── bgp.py
│ │ ├── eigrp.py
│ │ ├── objects.py
│ │ ├── ospf.py
│ │ └── static.py
│ ├── objects.py
│ ├── ospf.py
│ └── static.py
├── graphql
│ ├── __init__.py
│ ├── filter_mixins.py
│ ├── filters.py
│ ├── schema.py
│ └── types.py
├── helpers
│ └── __init__.py
├── migrations
│ ├── 0001_initial.py
│ ├── 0002_netboxmodel_updates.py
│ ├── 0003_model_ordering_and_constraints.py
│ ├── 0004_alter_prefixlistentry_ge_alter_prefixlistentry_le.py
│ ├── 0005_ospf.py
│ ├── 0006_bgp.py
│ ├── 0007_bgpsessiontemplate_asn_bgpsessiontemplate_bfd_and_more.py
│ ├── 0008_convert_to_primarymodel.py
│ ├── 0009_alter_staticroute_metric_alter_staticroute_permanent.py
│ ├── 0010_eigrp.py
│ ├── 0011_osfp_passive_interface.py
│ ├── 0012_osfp_instance_vrf.py
│ └── __init__.py
├── models
│ ├── __init__.py
│ ├── bgp.py
│ ├── eigrp.py
│ ├── mixins.py
│ ├── objects.py
│ ├── ospf.py
│ └── static.py
├── navigation
│ ├── __init__.py
│ ├── bgp.py
│ ├── eigrp.py
│ ├── objects.py
│ ├── ospf.py
│ └── static.py
├── tables
│ ├── __init__.py
│ ├── bgp.py
│ ├── eigrp.py
│ ├── objects.py
│ ├── ospf.py
│ └── static.py
├── templates
│ └── netbox_routing
│ │ ├── bgpaddressfamily.html
│ │ ├── bgprouter.html
│ │ ├── bgpscope.html
│ │ ├── eigrpaddressfamily.html
│ │ ├── eigrpinterface.html
│ │ ├── eigrpnetwork.html
│ │ ├── eigrprouter.html
│ │ ├── inc
│ │ └── settings.html
│ │ ├── object_children.html
│ │ ├── objecttable.html
│ │ ├── ospfarea.html
│ │ ├── ospfinstance.html
│ │ ├── ospfinterface.html
│ │ ├── prefixlist.html
│ │ ├── prefixlistentry.html
│ │ ├── routemap.html
│ │ ├── routemapentry.html
│ │ ├── staticroute.html
│ │ └── staticroute_devices.html
├── tests
│ ├── __init__.py
│ ├── base.py
│ ├── eigrp
│ │ ├── __init__.py
│ │ ├── test_api.py
│ │ ├── test_filtersets.py
│ │ ├── test_forms.py
│ │ ├── test_models.py
│ │ └── test_views.py
│ ├── ospf
│ │ ├── __init__.py
│ │ ├── test_api.py
│ │ ├── test_filtersets.py
│ │ ├── test_forms.py
│ │ ├── test_models.py
│ │ └── test_views.py
│ ├── static
│ │ ├── __init__.py
│ │ ├── test_api.py
│ │ ├── test_filtersets.py
│ │ ├── test_forms.py
│ │ ├── test_models.py
│ │ └── test_views.py
│ ├── test_api.py
│ ├── test_filtersets.py
│ ├── test_forms.py
│ ├── test_models.py
│ └── test_views.py
├── urls.py
└── views
│ ├── __init__.py
│ ├── bgp.py
│ ├── core.py
│ ├── eigrp.py
│ ├── objects.py
│ ├── ospf.py
│ └── static.py
└── pyproject.toml
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | #github: [dansheps]
4 | patreon: dansheps
5 | #open_collective: dansheps
6 | #ko_fi: # Replace with a single Ko-fi username
7 | #tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | #community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | #liberapay: # Replace with a single Liberapay username
10 | #issuehunt: # Replace with a single IssueHunt username
11 | #otechie: # Replace with a single Otechie username
12 | custom: https://paypal.me/dansheps84?country.x=CA&locale.x=en_US
13 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/01-feature_request.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | name: ✨ Feature Request
3 | description: Propose a new Plugin feature or enhancement
4 | labels: ["type: feature"]
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: >
9 | **NOTE:** This form is only for submitting well-formed proposals to extend or modify
10 | the plugin in some way. If you're trying to solve a problem but can't figure out how,
11 | or if you still need time to work on the details of a proposed new feature, please
12 | start a [discussion](https://github.com/netbox-community/netbox/discussions) instead.
13 | - type: input
14 | attributes:
15 | label: Plugin version
16 | description: What version of the plugin are you currently running?
17 | placeholder: v2.1.1
18 | validations:
19 | required: true
20 | - type: input
21 | attributes:
22 | label: NetBox version
23 | description: What version of NetBox are you currently running?
24 | placeholder: v4.1.1
25 | validations:
26 | required: true
27 | - type: dropdown
28 | attributes:
29 | label: Feature type
30 | options:
31 | - Data model extension
32 | - New functionality
33 | - Change to existing functionality
34 | validations:
35 | required: true
36 | - type: textarea
37 | attributes:
38 | label: Proposed functionality
39 | description: >
40 | Describe in detail the new feature or behavior you are proposing. Include any specific changes
41 | to work flows, data models, and/or the user interface. The more detail you provide here, the
42 | greater chance your proposal has of being discussed. Feature requests which don't include an
43 | actionable implementation plan will be rejected.
44 | validations:
45 | required: true
46 | - type: textarea
47 | attributes:
48 | label: Use case
49 | description: >
50 | Explain how adding this functionality would benefit NetBox users. What need does it address?
51 | validations:
52 | required: true
53 | - type: textarea
54 | attributes:
55 | label: Database changes
56 | description: >
57 | Note any changes to the database schema necessary to support the new feature. For example,
58 | does the proposal require adding a new model or field? (Not all new features require database
59 | changes.)
60 | - type: textarea
61 | attributes:
62 | label: External dependencies
63 | description: >
64 | List any new dependencies on external libraries or services that this new feature would
65 | introduce. For example, does the proposal require the installation of a new Python package?
66 | (Not all new features introduce new dependencies.)
67 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/02-bug_report.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | name: 🐛 Bug Report
3 | description: Report a reproducible bug in the current release of the plugin
4 | labels: ["type: bug"]
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: >
9 | **NOTE:** This form is only for reporting _reproducible bugs_ in a current NetBox installation
10 | with the plugin-extensions plugin. If you're having trouble with installation or just looking for
11 | assistance with using NetBox, please visit our
12 | [discussion forum](https://github.com/netbox-community/netbox/discussions) instead.
13 | - type: input
14 | attributes:
15 | label: Plugin version
16 | description: >
17 | What version of the plugin are you running?
18 | placeholder: v2.1.1
19 | validations:
20 | required: true
21 | - type: input
22 | attributes:
23 | label: NetBox version
24 | description: >
25 | What version of NetBox are you currently running? (If you don't have access to the most
26 | recent NetBox release, consider testing on our [demo instance](https://demo.netbox.dev/)
27 | before opening a bug report to see if your issue has already been addressed.)
28 | placeholder: v4.1.1
29 | validations:
30 | required: true
31 | - type: dropdown
32 | attributes:
33 | label: Python version
34 | description: What version of Python are you currently running?
35 | options:
36 | - 3.10
37 | - 3.11
38 | - 3.12
39 | validations:
40 | required: true
41 | - type: textarea
42 | attributes:
43 | label: Steps to Reproduce
44 | description: >
45 | Describe in detail the exact steps that someone else can take to
46 | reproduce this bug using the current stable release of NetBox and the plugin.
47 | Begin with the creation of any necessary database objects and call out every
48 | operation being performed explicitly. If reporting a bug in the REST API, be
49 | sure to reconstruct the raw HTTP request(s) being made: Don't rely on a client
50 | library such as pynetbox. Additionally, **do not rely on the demo instance**
51 | for reproducing suspected bugs, as its data is prone to modification or
52 | deletion at any time.
53 | placeholder: |
54 | 1. Click on "create widget"
55 | 2. Set foo to 12 and bar to G
56 | 3. Click the "create" button
57 | validations:
58 | required: true
59 | - type: textarea
60 | attributes:
61 | label: Expected Behavior
62 | description: What did you expect to happen?
63 | placeholder: A new widget should have been created with the specified attributes
64 | validations:
65 | required: true
66 | - type: textarea
67 | attributes:
68 | label: Observed Behavior
69 | description: What happened instead?
70 | placeholder: A TypeError exception was raised
71 | validations:
72 | required: true
73 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/03-housekeeping.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | name: 🏡 Housekeeping
3 | description: A change pertaining to the codebase itself (developers only)
4 | labels: ["type: housekeeping"]
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: >
9 | **NOTE:** This template is for use by maintainers only. Please do not submit
10 | an issue using this template unless you have been specifically asked to do so.
11 | - type: textarea
12 | attributes:
13 | label: Proposed Changes
14 | description: >
15 | Describe in detail the new feature or behavior you'd like to propose.
16 | Include any specific changes to work flows, data models, or the user interface.
17 | validations:
18 | required: true
19 | - type: textarea
20 | attributes:
21 | label: Justification
22 | description: Please provide justification for the proposed change(s).
23 | validations:
24 | required: true
25 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | # Reference: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser
2 | blank_issues_enabled: false
3 | contact_links:
4 | - name: 💬 Community Slack
5 | url: https://netdev.chat/
6 | about: "Join #netbox on the NetDev Community Slack for assistance with installation issues and other problems"
--------------------------------------------------------------------------------
/.github/configuration.testing.py:
--------------------------------------------------------------------------------
1 | ###################################################################
2 | # This file serves as a base configuration for testing purposes #
3 | # only. It is not intended for production use. #
4 | ###################################################################
5 |
6 | ALLOWED_HOSTS = ['*']
7 |
8 | DATABASE = {
9 | 'NAME': 'netbox',
10 | 'USER': 'netbox',
11 | 'PASSWORD': 'netbox',
12 | 'HOST': 'localhost',
13 | 'PORT': '',
14 | 'CONN_MAX_AGE': 300,
15 | }
16 |
17 | PLUGINS = [
18 | 'netbox_routing',
19 | ]
20 |
21 | REDIS = {
22 | 'tasks': {
23 | 'HOST': 'localhost',
24 | 'PORT': 6379,
25 | 'PASSWORD': '',
26 | 'DATABASE': 0,
27 | 'SSL': False,
28 | },
29 | 'caching': {
30 | 'HOST': 'localhost',
31 | 'PORT': 6379,
32 | 'PASSWORD': '',
33 | 'DATABASE': 1,
34 | 'SSL': False,
35 | }
36 | }
37 |
38 | SECRET_KEY = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
39 |
--------------------------------------------------------------------------------
/.github/release-drafter.yml:
--------------------------------------------------------------------------------
1 | name-template: 'v$RESOLVED_VERSION'
2 | tag-template: 'v$RESOLVED_VERSION'
3 | categories:
4 | - title: '🚀 Features'
5 | labels:
6 | - 'type: feature'
7 | - 'type: enhancement'
8 | - title: '🐛 Bug Fixes'
9 | labels:
10 | - 'type: bug'
11 | - title: '🧰 Maintenance'
12 | labels:
13 | - 'type: housekeeping'
14 | - 'type: documentation'
15 | change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
16 | change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
17 | version-resolver:
18 | minor:
19 | labels:
20 | - 'type: feature'
21 | patch:
22 | labels:
23 | - 'type: enhancement'
24 | - 'type: bug'
25 | - 'type: housekeeping'
26 | - 'type: documentation'
27 | default: patch
28 | template: |
29 | ## Changes
30 |
31 | $CHANGES
--------------------------------------------------------------------------------
/.github/workflows/build-test.yml:
--------------------------------------------------------------------------------
1 | name: Build Test
2 | on: [push, pull_request]
3 | jobs:
4 | build:
5 | name: Build Distribution
6 | runs-on: ubuntu-latest
7 | environment:
8 | name: build
9 | steps:
10 | - name: Checkout repo
11 | uses: actions/checkout@v4
12 | - name: Set up Python 3.12
13 | uses: actions/setup-python@v5
14 | with:
15 | python-version: 3.12
16 | - name: Install dependencies
17 | run: |
18 | python -m pip install --upgrade pip
19 | pip install --upgrade setuptools wheel
20 | python -m pip install build --user
21 | - name: Build a binary wheel and a source tarball
22 | run: python -m build
23 | - name: Store the distribution packages
24 | uses: actions/upload-artifact@v4
25 | with:
26 | name: python-package-distributions
27 | path: dist/
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on: [push, pull_request]
3 |
4 | permissions:
5 | contents: read
6 |
7 | concurrency:
8 | group: ci-${{ github.event_name }}-${{ github.ref }}-${{ github.actor }}
9 | cancel-in-progress: true
10 |
11 | env:
12 | GROUP: ci-${{ github.event_name }}-${{ github.ref }}-${{ github.actor }}
13 |
14 | jobs:
15 | build:
16 | runs-on: ubuntu-latest
17 | env:
18 | NETBOX_CONFIGURATION: netbox.configuration_testing
19 | strategy:
20 | matrix:
21 | python-version: ['3.10', '3.11', '3.12']
22 | services:
23 | redis:
24 | image: redis
25 | ports:
26 | - 6379:6379
27 | postgres:
28 | image: postgres
29 | env:
30 | POSTGRES_USER: netbox
31 | POSTGRES_PASSWORD: netbox
32 | options: >-
33 | --health-cmd pg_isready
34 | --health-interval 10s
35 | --health-timeout 5s
36 | --health-retries 5
37 | ports:
38 | - 5432:5432
39 |
40 | steps:
41 | - name: Echo concurrency group
42 | run: echo "::info ci-${{ github.event_name }}-${{ github.ref }}-${{ github.actor }}"
43 | - name: Set up Python ${{ matrix.python-version }}
44 | uses: actions/setup-python@v5
45 | with:
46 | python-version: ${{ matrix.python-version }}
47 | - name: Check out NetBox
48 | uses: actions/checkout@v4
49 | with:
50 | repository: netbox-community/netbox
51 | ref: main
52 | path: netbox
53 |
54 | - name: Check out repo
55 | uses: actions/checkout@v4
56 | with:
57 | path: netbox-routing
58 |
59 | - name: Install dependencies & set up configuration
60 | run: |
61 | python -m pip install --upgrade pip
62 | pip install -r netbox/requirements.txt
63 | pip install pycodestyle coverage tblib
64 | pip install -e netbox-routing
65 | cp -f netbox-routing/.github/configuration.testing.py netbox/netbox/netbox/configuration_testing.py
66 |
67 | - name: Run tests
68 | run: coverage run --source="netbox-routing/netbox_routing" netbox/netbox/manage.py test netbox-routing/netbox_routing --parallel
69 |
70 | - name: Show coverage report
71 | run: coverage report --skip-covered --omit '*/migrations/*,*/tests/*'
--------------------------------------------------------------------------------
/.github/workflows/pr_approval.yml:
--------------------------------------------------------------------------------
1 | name: Auto approve
2 | on:
3 | pull_request:
4 | types:
5 | - opened
6 | branches:
7 | - "main"
8 |
9 | jobs:
10 | auto-approve:
11 | runs-on: ubuntu-latest
12 | permissions:
13 | pull-requests: write
14 | if: github.actor == 'dansheps'
15 | steps:
16 | - uses: hmarr/auto-approve-action@v4
--------------------------------------------------------------------------------
/.github/workflows/pypi.yml:
--------------------------------------------------------------------------------
1 | name: PyPI Build
2 | on:
3 | release:
4 | types: released
5 |
6 | jobs:
7 |
8 | build:
9 | name: Build Distribution for PyPI
10 | runs-on: ubuntu-latest
11 | environment: release
12 | steps:
13 | - name: Checkout repo
14 | uses: actions/checkout@v4
15 | - name: Set up Python 3.12
16 | uses: actions/setup-python@v5
17 | with:
18 | python-version: 3.12
19 | - name: Install dependencies
20 | run: |
21 | python -m pip install --upgrade pip
22 | pip install --upgrade setuptools wheel
23 | python -m pip install build --user
24 | - name: Build a binary wheel and a source tarball
25 | run: python -m build
26 | - name: Store the distribution packages
27 | uses: actions/upload-artifact@v4
28 | with:
29 | name: python-package-distributions
30 | path: dist/
31 | publish-to-testpypi:
32 | name: Publish Python 🐍 distribution 📦 to PyPI
33 | needs:
34 | - build
35 | runs-on: ubuntu-latest
36 | environment:
37 | name: testpypi
38 | url: https://test.pypi.org/p/netbox-routing
39 | permissions:
40 | id-token: write
41 | steps:
42 | - name: Publish package to TestPyPI
43 | uses: pypa/gh-action-pypi-publish@release/v1
44 | with:
45 | repository_url: https://test.pypi.org/legacy/
46 | skip_existing: true
47 | publish-to-pypi:
48 | name: Publish Python 🐍 distribution 📦 to PyPI
49 | needs:
50 | - build
51 | runs-on: ubuntu-latest
52 | environment:
53 | name: pypi
54 | url: https://pypi.org/p/netbox-routing
55 | permissions:
56 | id-token: write
57 | steps:
58 | - name: Download all the dists
59 | uses: actions/download-artifact@v4
60 | with:
61 | name: python-package-distributions
62 | path: dist/
63 | - name: Publish package
64 | uses: pypa/gh-action-pypi-publish@release/v1
65 | with:
66 | skip_existing: true
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Release Drafter
3 |
4 | on:
5 | push:
6 | branches:
7 | - "main"
8 |
9 | jobs:
10 | update_release_draft:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: release-drafter/release-drafter@v5
14 | env:
15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | .python-version
86 |
87 | # pipenv
88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
91 | # install all needed dependencies.
92 | #Pipfile.lock
93 |
94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95 | __pypackages__/
96 |
97 | # Celery stuff
98 | celerybeat-schedule
99 | celerybeat.pid
100 |
101 | # SageMath parsed files
102 | *.sage.py
103 |
104 | # Environments
105 | .env
106 | .venv
107 | env/
108 | venv/
109 | ENV/
110 | env.bak/
111 | venv.bak/
112 |
113 | # Spyder project settings
114 | .spyderproject
115 | .spyproject
116 |
117 | # Rope project settings
118 | .ropeproject
119 |
120 | # mkdocs documentation
121 | /site
122 |
123 | # mypy
124 | .mypy_cache/
125 | .dmypy.json
126 | dmypy.json
127 |
128 | # Pyre type checker
129 | .pyre/
130 |
131 | .idea
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.md
2 | include LICENSE
3 | recursive-include netbox_routing/templates *
4 | recursive-include netbox_routing/static *
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Netbox Routing
2 | A plugin for tracking all kinds of routing information
3 |
4 | ## Features
5 |
6 | ### Current features
7 |
8 | * Static routing
9 |
10 | ### Under development
11 |
12 | * Dynamic routing
13 | * BGP
14 | * Templates/Group inheritance
15 | * OSPF
16 |
17 | ### Roadmapped
18 |
19 | * Dynamic Routing
20 | * BGP
21 | * IPv4/IPv46 AF VRF support
22 | * VPNv4 support
23 | * EIGRP
24 | * IS-IS
25 |
26 | # Requirements
27 |
28 | * Netbox 4.1+
29 | * Python 3.10+
30 |
31 | ## Compatibility Matrix
32 |
33 | | | Netbox 3.2.x | NetBox 4.1.x |
34 | |-------|----------------|----------------|
35 | | 0.1.x | Compatible | Not Compatible |
36 | | 0.2.x | Not Compatible | Compatible |
37 |
38 | ## Installation
39 |
40 | To install, simply include this plugin in the plugins configuration section of netbox.
41 |
42 | Example:
43 | ```python
44 | PLUGINS = [
45 | 'netbox_routing'
46 | ],
47 | ```
48 |
49 | ## Configuration
50 |
51 | None
52 |
53 | ## Usage
54 |
55 | TBD
56 |
57 | ## Additional Notes
58 |
59 | TBD
60 |
61 | ## Contribute
62 |
63 | Contributions are always welcome! Please open an issue first before contributing as the scope is going to be kept
64 | intentionally narrow
65 |
66 |
67 |
--------------------------------------------------------------------------------
/netbox_routing/__init__.py:
--------------------------------------------------------------------------------
1 | from netbox.plugins import PluginConfig
2 | from importlib.metadata import metadata
3 |
4 |
5 | plugin = metadata('netbox_routing')
6 |
7 |
8 | class NetboxRouting(PluginConfig):
9 | name = plugin.get('Name').replace('-', '_')
10 | verbose_name = plugin.get('Name').replace('-', ' ').title()
11 | description = plugin.get('Summary')
12 | version = plugin.get('Version')
13 | author = plugin.get('Author')
14 | author_email = plugin.get('Author-email')
15 | base_url = 'routing'
16 | min_version = '4.1.0'
17 | required_settings = []
18 | caching_config = {}
19 | default_settings = {}
20 | graphql_schema = 'graphql.schema.schema'
21 |
22 |
23 | config = NetboxRouting
24 |
--------------------------------------------------------------------------------
/netbox_routing/api/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanSheps/netbox-routing/56ec2104ef304157cb27e3ec75d16597031d08c6/netbox_routing/api/__init__.py
--------------------------------------------------------------------------------
/netbox_routing/api/_serializers/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanSheps/netbox-routing/56ec2104ef304157cb27e3ec75d16597031d08c6/netbox_routing/api/_serializers/__init__.py
--------------------------------------------------------------------------------
/netbox_routing/api/_serializers/bgp.py:
--------------------------------------------------------------------------------
1 | from drf_spectacular.utils import extend_schema_field
2 | from rest_framework import serializers
3 |
4 | from dcim.api.serializers_.devices import DeviceSerializer
5 | from ipam.api.serializers_.asns import ASNSerializer
6 | from ipam.api.serializers_.vrfs import VRFSerializer
7 | from netbox.api.serializers import NetBoxModelSerializer
8 | from utilities.api import get_serializer_for_model
9 |
10 | from netbox_routing.models import BGPRouter, BGPSetting, BGPScope, BGPAddressFamily
11 |
12 | __all__ = (
13 | 'BGPRouterSerializer',
14 | 'BGPScopeSerializer',
15 | 'BGPAddressFamilySerializer',
16 | 'BGPSettingSerializer',
17 | )
18 |
19 |
20 |
21 |
22 | class BGPSettingSerializer(NetBoxModelSerializer):
23 | url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_routing-api:bgprouter-detail')
24 |
25 | assigned_object = serializers.SerializerMethodField(read_only=True)
26 |
27 | class Meta:
28 | model = BGPSetting
29 | fields = (
30 | 'url', 'id', 'display', 'assigned_object_type', 'assigned_object_id', 'assigned_object', 'key', 'value',
31 | 'description', 'comments',
32 | )
33 | brief_fields = ('url', 'id', 'display', 'assigned_object', 'key', )
34 |
35 | @extend_schema_field(serializers.JSONField(allow_null=True))
36 | def get_assigned_object(self, obj):
37 | if obj.assigned_object is None:
38 | return None
39 | serializer = get_serializer_for_model(obj.assigned_object)
40 | context = {'request': self.context['request']}
41 | return serializer(obj.assigned_object, context=context, nested=True).data
42 |
43 |
44 | class BGPRouterSerializer(NetBoxModelSerializer):
45 | url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_routing-api:bgprouter-detail')
46 |
47 | device = DeviceSerializer(nested=True)
48 | asn = ASNSerializer(nested=True)
49 |
50 | settings = BGPSettingSerializer(many=True)
51 |
52 | class Meta:
53 | model = BGPRouter
54 | fields = ('url', 'id', 'display', 'device', 'asn', 'settings', 'description', 'comments',)
55 | brief_fields = ('url', 'id', 'display', 'device', 'asn', )
56 |
57 |
58 | class BGPScopeSerializer(NetBoxModelSerializer):
59 | url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_routing-api:bgpscope-detail')
60 |
61 | router = BGPRouterSerializer(nested=True)
62 | vrf = VRFSerializer(nested=True)
63 |
64 | settings = BGPSettingSerializer(many=True)
65 |
66 | class Meta:
67 | model = BGPScope
68 | fields = ('url', 'id', 'display', 'router', 'vrf', 'settings', 'description', 'comments',)
69 | brief_fields = ('url', 'id', 'display', 'router', 'vrf', )
70 |
71 |
72 | class BGPAddressFamilySerializer(NetBoxModelSerializer):
73 | url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_routing-api:bgpaddressfamily-detail')
74 |
75 | scope = BGPScopeSerializer(nested=True)
76 | settings = BGPSettingSerializer(many=True)
77 |
78 | class Meta:
79 | model = BGPAddressFamily
80 | fields = ('url', 'id', 'display', 'scope', 'address_family', 'settings', 'description', 'comments',)
81 | brief_fields = ('url', 'id', 'display', 'scope', 'address_family', )
82 |
--------------------------------------------------------------------------------
/netbox_routing/api/_serializers/eigrp.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 |
3 | from dcim.api.serializers_.device_components import InterfaceSerializer
4 | from dcim.api.serializers_.devices import DeviceSerializer
5 | from ipam.api.serializers_.ip import PrefixSerializer
6 | from netbox.api.serializers import NetBoxModelSerializer
7 | from netbox_routing.models import (
8 | EIGRPRouter, EIGRPAddressFamily, EIGRPNetwork, EIGRPInterface
9 | )
10 |
11 |
12 | __all__ = (
13 | 'EIGRPRouterSerializer',
14 | 'EIGRPAddressFamilySerializer',
15 | 'EIGRPNetworkSerializer',
16 | 'EIGRPInterfaceSerializer',
17 | )
18 |
19 |
20 | class EIGRPRouterSerializer(NetBoxModelSerializer):
21 | url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_routing-api:eigrprouter-detail')
22 | device = DeviceSerializer(nested=True)
23 |
24 | class Meta:
25 | model = EIGRPRouter
26 | fields = ('url', 'id', 'display', 'name', 'pid', 'rid', 'device', 'description', 'comments', )
27 | brief_fields = ('url', 'id', 'display', 'name', 'pid', 'rid', 'device', )
28 |
29 |
30 | class EIGRPAddressFamilySerializer(NetBoxModelSerializer):
31 | url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_routing-api:eigrpaddressfamily-detail')
32 | router = EIGRPRouterSerializer(nested=True)
33 |
34 |
35 | class Meta:
36 | model = EIGRPAddressFamily
37 | fields = (
38 | 'url', 'id', 'display', 'router', 'family', 'description', 'comments',
39 | )
40 | brief_fields = ('url', 'id', 'display', 'router', 'family',)
41 |
42 |
43 | class EIGRPNetworkSerializer(NetBoxModelSerializer):
44 | url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_routing-api:eigrpnetwork-detail')
45 | router = EIGRPRouterSerializer(nested=True)
46 | address_family = EIGRPAddressFamilySerializer(nested=True)
47 | network = PrefixSerializer(nested=True)
48 |
49 | class Meta:
50 | model = EIGRPNetwork
51 | fields = (
52 | 'url', 'id', 'display', 'router', 'address_family', 'network', 'description', 'comments',
53 | )
54 | brief_fields = ('url', 'id', 'display', 'router', 'address_family', 'network',)
55 |
56 |
57 | class EIGRPInterfaceSerializer(NetBoxModelSerializer):
58 | url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_routing-api:eigrpinterface-detail')
59 | router = EIGRPRouterSerializer(nested=True)
60 | address_family = EIGRPAddressFamilySerializer(nested=True)
61 | interface = InterfaceSerializer(nested=True)
62 |
63 | class Meta:
64 | model = EIGRPInterface
65 | fields = (
66 | 'url', 'id', 'display', 'router', 'address_family', 'interface', 'passive', 'bfd',
67 | 'authentication', 'passphrase', 'description', 'comments',
68 | )
69 | brief_fields = (
70 | 'url', 'id', 'display', 'router', 'address_family', 'interface',
71 | )
72 |
--------------------------------------------------------------------------------
/netbox_routing/api/_serializers/objects.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 |
3 | from netbox.api.serializers import NetBoxModelSerializer
4 | from netbox_routing.models import PrefixList, PrefixListEntry, RouteMap, RouteMapEntry
5 |
6 |
7 | __all__ = (
8 | 'StaticRouteSerializer'
9 | )
10 |
11 |
12 | class PrefixListSerializer(NetBoxModelSerializer):
13 | url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_routing-api:prefixlist-detail')
14 |
15 | class Meta:
16 | model = PrefixList
17 | fields = ('url', 'id', 'display', 'name', 'description', 'comments',)
18 | brief_fields = ('url', 'id', 'display', 'name', )
19 |
20 |
21 | class PrefixListEntrySerializer(NetBoxModelSerializer):
22 | url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_routing-api:prefixlistentry-detail')
23 | prefix_list = PrefixListSerializer(nested=True)
24 |
25 | class Meta:
26 | model = PrefixListEntry
27 | fields = (
28 | 'url', 'id', 'display', 'prefix_list', 'sequence', 'type', 'prefix', 'le', 'ge', 'description', 'comments',
29 | )
30 | brief_fields = ('url', 'id', 'display', 'prefix_list', 'sequence', 'type', 'prefix', 'le', 'ge')
31 |
32 |
33 | class RouteMapSerializer(NetBoxModelSerializer):
34 | url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_routing-api:prefixlist-detail')
35 |
36 | class Meta:
37 | model = RouteMap
38 | fields = ('url', 'id', 'display', 'name', 'description', 'comments',)
39 | brief_fields = ('url', 'id', 'display', 'name')
40 |
41 |
42 | class RouteMapEntrySerializer(NetBoxModelSerializer):
43 | url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_routing-api:prefixlistentry-detail')
44 | route_map = RouteMapSerializer(nested=True)
45 |
46 |
47 | class Meta:
48 | model = RouteMapEntry
49 | fields = ('url', 'id', 'display', 'route_map', 'sequence', 'type', 'description', 'comments',)
50 | brief_fields = ('url', 'id', 'display', 'route_map', 'sequence', 'type')
51 |
--------------------------------------------------------------------------------
/netbox_routing/api/_serializers/ospf.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 |
3 | from dcim.api.serializers_.device_components import InterfaceSerializer
4 | from dcim.api.serializers_.devices import DeviceSerializer
5 | from ipam.api.serializers_.vrfs import VRFSerializer
6 | from netbox.api.serializers import NetBoxModelSerializer
7 | from netbox_routing.models import OSPFInstance, OSPFArea, OSPFInterface
8 |
9 |
10 | __all__ = (
11 | 'OSPFInstanceSerializer',
12 | 'OSPFAreaSerializer',
13 | 'OSPFInterfaceSerializer',
14 | )
15 |
16 |
17 | class OSPFInstanceSerializer(NetBoxModelSerializer):
18 | url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_routing-api:ospfinstance-detail')
19 | device = DeviceSerializer(nested=True)
20 | vrf = VRFSerializer(nested=True)
21 |
22 | class Meta:
23 | model = OSPFInstance
24 | fields = (
25 | 'url', 'id', 'display', 'name', 'router_id', 'process_id', 'device', 'vrf', 'description', 'comments',
26 | )
27 | brief_fields = ('url', 'id', 'display', 'name', 'router_id', 'process_id', 'device', 'vrf', )
28 |
29 |
30 | class OSPFAreaSerializer(NetBoxModelSerializer):
31 | url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_routing-api:ospfarea-detail')
32 |
33 | class Meta:
34 | model = OSPFArea
35 | fields = ('url', 'id', 'display', 'area_id', 'description', 'comments',)
36 | brief_fields = ('url', 'id', 'display', 'area_id',)
37 |
38 |
39 | class OSPFInterfaceSerializer(NetBoxModelSerializer):
40 | url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_routing-api:ospfarea-detail')
41 | instance = OSPFInstanceSerializer(nested=True)
42 | area = OSPFAreaSerializer(nested=True)
43 | interface = InterfaceSerializer(nested=True)
44 |
45 | class Meta:
46 | model = OSPFInterface
47 | fields = (
48 | 'url', 'id', 'display', 'instance', 'area', 'interface', 'passive', 'priority', 'bfd', 'authentication',
49 | 'passphrase', 'description', 'comments',
50 | )
51 | brief_fields = (
52 | 'url', 'id', 'display', 'instance', 'area', 'interface', 'passive',
53 | )
54 |
--------------------------------------------------------------------------------
/netbox_routing/api/_serializers/static.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 |
3 | from dcim.api.serializers_.devices import DeviceSerializer
4 | from ipam.api.serializers_.vrfs import VRFSerializer
5 | from netbox.api.serializers import NetBoxModelSerializer
6 |
7 | from netbox_routing.api.field_serializers import IPAddressField
8 | from netbox_routing.models import StaticRoute
9 |
10 |
11 | __all__ = (
12 | 'StaticRouteSerializer'
13 | )
14 |
15 |
16 | class StaticRouteSerializer(NetBoxModelSerializer):
17 | url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_routing-api:staticroute-detail')
18 | devices = DeviceSerializer(many=True, nested=True, required=False, allow_null=True)
19 | vrf = VRFSerializer(nested=True, required=False, allow_null=True)
20 | next_hop = IPAddressField()
21 |
22 | class Meta:
23 | model = StaticRoute
24 | fields = (
25 | 'url', 'id', 'display', 'devices', 'vrf', 'prefix', 'next_hop', 'name', 'metric', 'permanent',
26 | 'description', 'comments'
27 | )
28 | brief_fields = ('url', 'id', 'display', 'name', 'prefix', 'next_hop', 'description')
29 |
30 | def create(self, validated_data):
31 | devices = validated_data.pop('devices', None)
32 | instance = super(StaticRouteSerializer, self).create(validated_data)
33 |
34 | return self._update_devices(instance, devices)
35 |
36 | def update(self, instance, validated_data):
37 | devices = validated_data.pop('devices', None)
38 | instance = super(StaticRouteSerializer, self).update(instance, validated_data)
39 |
40 | return self._update_devices(instance, devices)
41 |
42 | def _update_devices(self, instance: StaticRoute, devices: object) -> StaticRoute:
43 | if devices:
44 | instance.devices.set(devices)
45 | elif devices is not None:
46 | instance.devices.clear()
47 |
48 | return instance
49 |
50 |
--------------------------------------------------------------------------------
/netbox_routing/api/field_serializers/__init__.py:
--------------------------------------------------------------------------------
1 | from .ip import IPAddressField
2 |
3 | __all__ = (
4 | 'IPAddressField',
5 | )
6 |
--------------------------------------------------------------------------------
/netbox_routing/api/field_serializers/ip.py:
--------------------------------------------------------------------------------
1 | import netaddr
2 | from django.utils.translation import gettext_lazy as _
3 | from rest_framework import serializers
4 |
5 | __all__ = (
6 | 'IPAddressField',
7 | )
8 |
9 |
10 | class IPAddressField(serializers.CharField):
11 | """
12 | An IPv4 or IPv6 address with optional mask
13 | """
14 | default_error_messages = {
15 | 'invalid': _('Enter a valid IPv4 or IPv6 address with optional mask.'),
16 | }
17 |
18 | def to_internal_value(self, data):
19 | try:
20 | return netaddr.IPAddress(data)
21 | except netaddr.AddrFormatError:
22 | raise serializers.ValidationError(_("Invalid IP address format: {data}").format(data))
23 | except (TypeError, ValueError) as e:
24 | raise serializers.ValidationError(e)
25 |
26 | def to_representation(self, value):
27 | return str(value)
28 |
--------------------------------------------------------------------------------
/netbox_routing/api/serializers.py:
--------------------------------------------------------------------------------
1 | from netbox_routing.api._serializers.objects import (
2 | PrefixListSerializer, PrefixListEntrySerializer, RouteMapSerializer, RouteMapEntrySerializer
3 | )
4 | from netbox_routing.api._serializers.static import StaticRouteSerializer
5 | from netbox_routing.api._serializers.bgp import (
6 | BGPRouterSerializer, BGPScopeSerializer, BGPAddressFamilySerializer, BGPSettingSerializer
7 | )
8 | from netbox_routing.api._serializers.ospf import *
9 | from netbox_routing.api._serializers.eigrp import *
10 |
11 | __all__ = (
12 | 'StaticRouteSerializer',
13 |
14 | 'OSPFInstanceSerializer',
15 | 'OSPFAreaSerializer',
16 | 'OSPFInterfaceSerializer',
17 |
18 | 'EIGRPRouterSerializer',
19 | 'EIGRPAddressFamilySerializer',
20 | 'EIGRPNetworkSerializer',
21 | 'EIGRPInterfaceSerializer',
22 |
23 | 'PrefixListSerializer',
24 | 'PrefixListEntrySerializer',
25 | 'RouteMapSerializer',
26 | 'RouteMapEntrySerializer',
27 |
28 | 'BGPRouterSerializer',
29 | 'BGPScopeSerializer',
30 | 'BGPAddressFamilySerializer',
31 | 'BGPSettingSerializer',
32 | )
--------------------------------------------------------------------------------
/netbox_routing/api/urls.py:
--------------------------------------------------------------------------------
1 | from netbox.api.routers import NetBoxRouter
2 | from .views import StaticRouteViewSet, PrefixListViewSet, RouteMapViewSet, PrefixListEntryViewSet, \
3 | RouteMapEntryViewSet, OSPFInstanceViewSet, OSPFAreaViewSet, OSPFInterfaceViewSet, BGPRouterViewSet, \
4 | BGPScopeViewSet, BGPAddressFamilyViewSet, BGPSettingViewSet, EIGRPRouterViewSet, EIGRPAddressFamilyViewSet, \
5 | EIGRPNetworkViewSet, EIGRPInterfaceViewSet
6 |
7 | router = NetBoxRouter()
8 | router.register('staticroute', StaticRouteViewSet)
9 | router.register('bgp-router', BGPRouterViewSet)
10 | router.register('bgp-scope', BGPScopeViewSet)
11 | router.register('bgp-addressfamily', BGPAddressFamilyViewSet)
12 | router.register('bgp-setting', BGPSettingViewSet)
13 | router.register('ospf-instance', OSPFInstanceViewSet)
14 | router.register('ospf-area', OSPFAreaViewSet)
15 | router.register('ospf-interface', OSPFInterfaceViewSet)
16 | router.register('eigrp-router', EIGRPRouterViewSet)
17 | router.register('eigrp-address-family', EIGRPAddressFamilyViewSet)
18 | router.register('eigrp-network', EIGRPNetworkViewSet)
19 | router.register('eigrp-interface', EIGRPInterfaceViewSet)
20 | router.register('prefix-list', PrefixListViewSet)
21 | router.register('prefix-list-entry', PrefixListEntryViewSet)
22 | router.register('route-map', RouteMapViewSet)
23 | router.register('route-map-entry', RouteMapEntryViewSet)
24 | urlpatterns = router.urls
25 |
--------------------------------------------------------------------------------
/netbox_routing/api/views/__init__.py:
--------------------------------------------------------------------------------
1 | from .static import StaticRouteViewSet
2 | from .ospf import OSPFInstanceViewSet, OSPFAreaViewSet, OSPFInterfaceViewSet
3 | from .bgp import BGPRouterViewSet, BGPScopeViewSet, BGPAddressFamilyViewSet, BGPSettingViewSet
4 | from .objects import PrefixListViewSet, PrefixListEntryViewSet, RouteMapViewSet, RouteMapEntryViewSet
5 | from .eigrp import (
6 | EIGRPRouterViewSet, EIGRPAddressFamilyViewSet,
7 | EIGRPNetworkViewSet, EIGRPInterfaceViewSet
8 | )
9 |
10 | __all__ = (
11 | 'StaticRouteViewSet',
12 |
13 | 'BGPRouterViewSet',
14 | 'BGPScopeViewSet',
15 | 'BGPAddressFamilyViewSet',
16 | 'BGPSettingViewSet',
17 |
18 | 'EIGRPRouterViewSet',
19 | 'EIGRPAddressFamilyViewSet',
20 | 'EIGRPNetworkViewSet',
21 | 'EIGRPInterfaceViewSet',
22 |
23 | 'OSPFInstanceViewSet',
24 | 'OSPFAreaViewSet',
25 | 'OSPFInterfaceViewSet',
26 |
27 | 'PrefixListViewSet',
28 | 'PrefixListEntryViewSet',
29 | 'RouteMapViewSet',
30 | 'RouteMapEntryViewSet',
31 | )
32 |
--------------------------------------------------------------------------------
/netbox_routing/api/views/bgp.py:
--------------------------------------------------------------------------------
1 | from netbox.api.viewsets import NetBoxModelViewSet
2 | from netbox_routing import filtersets
3 | from netbox_routing.api.serializers import BGPRouterSerializer, BGPSettingSerializer, BGPScopeSerializer, \
4 | BGPAddressFamilySerializer
5 | from netbox_routing.models import BGPRouter, BGPSetting, BGPScope, BGPAddressFamily
6 |
7 | __all__ = (
8 | 'BGPRouterViewSet',
9 | 'BGPScopeViewSet',
10 | 'BGPAddressFamilyViewSet',
11 | 'BGPSettingViewSet',
12 | )
13 |
14 |
15 | class BGPRouterViewSet(NetBoxModelViewSet):
16 | queryset = BGPRouter.objects.all()
17 | serializer_class = BGPRouterSerializer
18 | filterset_class = filtersets.BGPRouterFilterSet
19 |
20 |
21 | class BGPScopeViewSet(NetBoxModelViewSet):
22 | queryset = BGPScope.objects.all()
23 | serializer_class = BGPScopeSerializer
24 | filterset_class = filtersets.BGPScopeFilterSet
25 |
26 |
27 | class BGPAddressFamilyViewSet(NetBoxModelViewSet):
28 | queryset = BGPAddressFamily.objects.all()
29 | serializer_class = BGPAddressFamilySerializer
30 | filterset_class = filtersets.BGPAddressFamilyFilterSet
31 |
32 |
33 | class BGPSettingViewSet(NetBoxModelViewSet):
34 | queryset = BGPSetting.objects.all()
35 | serializer_class = BGPSettingSerializer
36 | filterset_class = filtersets.BGPSettingFilterSet
37 |
--------------------------------------------------------------------------------
/netbox_routing/api/views/eigrp.py:
--------------------------------------------------------------------------------
1 | from netbox.api.viewsets import NetBoxModelViewSet
2 | from netbox_routing import filtersets
3 | from netbox_routing.api.serializers import (
4 | EIGRPRouterSerializer, EIGRPRouterSerializer, EIGRPAddressFamilySerializer,
5 | EIGRPInterfaceSerializer, EIGRPNetworkSerializer
6 | )
7 | from netbox_routing.models import (
8 | EIGRPRouter, EIGRPAddressFamily, EIGRPNetwork, EIGRPInterface
9 | )
10 |
11 |
12 | __all__ = (
13 | 'EIGRPRouterViewSet',
14 | 'EIGRPAddressFamilyViewSet',
15 | 'EIGRPNetworkViewSet',
16 | 'EIGRPInterfaceViewSet',
17 | )
18 |
19 |
20 | class EIGRPRouterViewSet(NetBoxModelViewSet):
21 | queryset = EIGRPRouter.objects.all()
22 | serializer_class = EIGRPRouterSerializer
23 | filterset_class = filtersets.EIGRPRouterFilterSet
24 |
25 |
26 | class EIGRPAddressFamilyViewSet(NetBoxModelViewSet):
27 | queryset = EIGRPAddressFamily.objects.all()
28 | serializer_class = EIGRPAddressFamilySerializer
29 | filterset_class = filtersets.EIGRPAddressFamilyFilterSet
30 |
31 |
32 | class EIGRPNetworkViewSet(NetBoxModelViewSet):
33 | queryset = EIGRPNetwork.objects.all()
34 | serializer_class = EIGRPNetworkSerializer
35 | filterset_class = filtersets.EIGRPNetworkFilterSet
36 |
37 |
38 | class EIGRPInterfaceViewSet(NetBoxModelViewSet):
39 | queryset = EIGRPInterface.objects.all()
40 | serializer_class = EIGRPInterfaceSerializer
41 | filterset_class = filtersets.EIGRPInterfaceFilterSet
42 |
--------------------------------------------------------------------------------
/netbox_routing/api/views/objects.py:
--------------------------------------------------------------------------------
1 | from netbox.api.viewsets import NetBoxModelViewSet
2 | from netbox_routing import filtersets
3 | from netbox_routing.api.serializers import PrefixListSerializer, PrefixListEntrySerializer, RouteMapSerializer, \
4 | RouteMapEntrySerializer
5 | from netbox_routing.models import PrefixList, PrefixListEntry, RouteMap, RouteMapEntry
6 |
7 |
8 | class PrefixListViewSet(NetBoxModelViewSet):
9 | queryset = PrefixList.objects.all()
10 | serializer_class = PrefixListSerializer
11 | filterset_class = filtersets.PrefixListFilterSet
12 |
13 |
14 | class PrefixListEntryViewSet(NetBoxModelViewSet):
15 | queryset = PrefixListEntry.objects.all()
16 | serializer_class = PrefixListEntrySerializer
17 | filterset_class = filtersets.PrefixListEntryFilterSet
18 |
19 |
20 | class RouteMapViewSet(NetBoxModelViewSet):
21 | queryset = RouteMap.objects.all()
22 | serializer_class = RouteMapSerializer
23 | filterset_class = filtersets.RouteMapFilterSet
24 |
25 |
26 | class RouteMapEntryViewSet(NetBoxModelViewSet):
27 | queryset = RouteMapEntry.objects.all()
28 | serializer_class = RouteMapEntrySerializer
29 | filterset_class = filtersets.RouteMapEntryFilterSet
30 |
--------------------------------------------------------------------------------
/netbox_routing/api/views/ospf.py:
--------------------------------------------------------------------------------
1 | from netbox.api.viewsets import NetBoxModelViewSet
2 | from netbox_routing import filtersets
3 | from netbox_routing.api.serializers import OSPFInstanceSerializer, OSPFAreaSerializer, OSPFInterfaceSerializer
4 | from netbox_routing.models import OSPFInstance, OSPFArea, OSPFInterface
5 |
6 |
7 | __all__ = (
8 | 'OSPFInstanceViewSet',
9 | 'OSPFAreaViewSet',
10 | 'OSPFInterfaceViewSet',
11 | )
12 |
13 |
14 | class OSPFInstanceViewSet(NetBoxModelViewSet):
15 | queryset = OSPFInstance.objects.all()
16 | serializer_class = OSPFInstanceSerializer
17 | filterset_class = filtersets.OSPFInstanceFilterSet
18 |
19 |
20 | class OSPFAreaViewSet(NetBoxModelViewSet):
21 | queryset = OSPFArea.objects.all()
22 | serializer_class = OSPFAreaSerializer
23 | filterset_class = filtersets.OSPFAreaFilterSet
24 |
25 |
26 | class OSPFInterfaceViewSet(NetBoxModelViewSet):
27 | queryset = OSPFInterface.objects.all()
28 | serializer_class = OSPFInterfaceSerializer
29 | filterset_class = filtersets.OSPFInterfaceFilterSet
30 |
--------------------------------------------------------------------------------
/netbox_routing/api/views/static.py:
--------------------------------------------------------------------------------
1 | from netbox.api.viewsets import NetBoxModelViewSet
2 | from netbox_routing import filtersets
3 | from netbox_routing.api.serializers import StaticRouteSerializer
4 | from netbox_routing.models import StaticRoute
5 |
6 |
7 | class StaticRouteViewSet(NetBoxModelViewSet):
8 | queryset = StaticRoute.objects.all()
9 | serializer_class = StaticRouteSerializer
10 | filterset_class = filtersets.StaticRouteFilterSet
11 |
--------------------------------------------------------------------------------
/netbox_routing/choices/__init__.py:
--------------------------------------------------------------------------------
1 | from .base import *
2 | from .bgp import *
3 |
--------------------------------------------------------------------------------
/netbox_routing/choices/base.py:
--------------------------------------------------------------------------------
1 | from utilities.choices import ChoiceSet
2 |
3 |
4 | __all__ = (
5 | 'AuthenticationChoices',
6 | )
7 |
8 |
9 | class AuthenticationChoices(ChoiceSet):
10 | KEYCHAIN = 'key-chain'
11 | MESSAGE_DIGEST = 'message-digest'
12 | NULL = 'null'
13 |
14 | CHOICES = [
15 | (KEYCHAIN, 'Key Chain'),
16 | (MESSAGE_DIGEST, 'Message Digest'),
17 | (NULL, 'Null Authentication')
18 | ]
--------------------------------------------------------------------------------
/netbox_routing/choices/bgp.py:
--------------------------------------------------------------------------------
1 | from utilities.choices import ChoiceSet
2 |
3 |
4 | __all__ = (
5 | 'BGPAdditionalPathSelectChoices',
6 | 'BGPAddressFamilyChoices',
7 | 'BGPBestPathASPathChoices',
8 | 'BGPSettingChoices',
9 | 'BFDChoices',
10 | )
11 |
12 |
13 | class BGPSettingChoices(ChoiceSet):
14 | SUMMARY = 'auto_summary'
15 |
16 | RID = 'router_id'
17 | ADDPATH_INSTALL = 'additional_paths_install'
18 | ADDPATH_RECEIVE = 'additional_paths_receive'
19 | ADDPATH_SEND = 'additional_paths_send'
20 | ASDOT = 'asdot'
21 | GR = 'graceful_restart'
22 | DEFAULT_ORIGINATE = 'default_information_originate'
23 | DEFAULT_METRIC = 'default_metric'
24 | DISTANCE_EBGP = 'distance_ebgp'
25 | DISTANCE_IBGP = 'distance_ibgp'
26 | DISTANCE_EMBGP = 'distance_embgp'
27 | DISTANCE_IMBGP = 'distance_imbgp'
28 | MAX_PATHS = 'paths_maximum'
29 | MAX_PATHS_SECONDARY = 'paths_maximum_secondary'
30 | KEEPALIVE = 'timers_keepalive'
31 | HOLD = 'timers_hold'
32 |
33 | CHOICES = [
34 | (RID, 'Router ID'),
35 | (SUMMARY, 'Auto summary'),
36 | (ADDPATH_INSTALL, 'Additional Pathss (install)'),
37 | (ADDPATH_RECEIVE, 'Additional Paths (receive)'),
38 | (ADDPATH_SEND, 'Additional Paths (send)'),
39 | (ASDOT, 'AS Dot Notation'),
40 | (GR, 'Graceful Restart'),
41 | (DEFAULT_ORIGINATE, 'Default Originate'),
42 | (DEFAULT_METRIC, 'Default Metric'),
43 | (DISTANCE_EBGP, 'eBGP Distance'),
44 | (DISTANCE_IBGP, 'iBGP Distance'),
45 | (DISTANCE_EMBGP, 'MP eBGP Distance'),
46 | (DISTANCE_IMBGP, 'MP iBGP Distance'),
47 | (MAX_PATHS, 'Maximum Paths'),
48 | (MAX_PATHS_SECONDARY, 'Maximum Paths (secondary)'),
49 | (KEEPALIVE, 'Keep Alive'),
50 | (HOLD, 'Hold Time'),
51 | ]
52 |
53 | FIELD_TYPES = {
54 | RID: 'ipaddr',
55 | SUMMARY: 'boolean',
56 | ADDPATH_INSTALL: 'integer',
57 | ADDPATH_RECEIVE: 'integer',
58 | ADDPATH_SEND: 'integer',
59 | ASDOT: 'boolean',
60 | GR: 'boolean',
61 | DEFAULT_ORIGINATE: 'boolean',
62 | DEFAULT_METRIC: 'integer',
63 | DISTANCE_EBGP: 'integer',
64 | DISTANCE_IBGP: 'integer',
65 | DISTANCE_EMBGP: 'integer',
66 | DISTANCE_IMBGP: 'integer',
67 | MAX_PATHS: 'integer',
68 | MAX_PATHS_SECONDARY: 'integer',
69 | KEEPALIVE: 'integer',
70 | HOLD: 'integer',
71 | }
72 |
73 |
74 | class BGPAdditionalPathSelectChoices(ChoiceSet):
75 | ALL = 'all'
76 | BACKUP = 'backup'
77 | BEST_EXTERNAL = 'best-external'
78 | GROUP_BEST = 'group-best'
79 |
80 | CHOICES = [
81 | (ALL, 'All'),
82 | (BACKUP, 'Backup'),
83 | (BEST_EXTERNAL, 'Best External'),
84 | (GROUP_BEST, 'Group Best')
85 | ]
86 |
87 |
88 | class BFDChoices(ChoiceSet):
89 | SINGLEHOP = 'singlehop'
90 | MULTIHOP = 'multihop'
91 |
92 | CHOICES = [
93 | (SINGLEHOP, 'Single-Hop'),
94 | (MULTIHOP, 'Multi-Hop')
95 | ]
96 |
97 |
98 | class BGPBestPathASPathChoices(ChoiceSet):
99 | IGNORE = 'ignore'
100 | MULTIPATH = 'multipath-relax'
101 |
102 | CHOICES = [
103 | (IGNORE, 'Ignore'),
104 | (MULTIPATH, 'Multipath Relax Comparison')
105 | ]
106 |
107 |
108 | class BGPAddressFamilyChoices(ChoiceSet):
109 | IPV4_UNICAST = 'ipv4-unicast'
110 | IPV6_UNICAST = 'ipv6-unicast'
111 | VPNV4_UNICAST = 'vpnv4-unicast'
112 | VPNV6_UNICAST = 'vpnv6-unicast'
113 | IPV4_MULTICAST = 'ipv4-multicast'
114 | IPV6_MULTICAST = 'ipv6-multicast'
115 | VPNV4_MULTICAST = 'vpnv4-multicast'
116 | VPNV6_MULTICAST = 'vpnv6-multicast'
117 | IPV4_FLOWSPEC = 'ipv4-flowspec'
118 | IPV6_FLOWSPEC = 'ipv6-flowspec'
119 | VPNV4_FLOWSPEC = 'vpnv4-flowspec'
120 | VPNV6_FLOWSPEC = 'vpnv6-flowspec'
121 | NSAP = 'nsap'
122 | L2VPNVPLS = 'l2vpn-vpls'
123 | L2VPSEVPN = 'l2vpn-evpn'
124 | LINKSTATE = 'link-state'
125 | RTFILTER_UNICAST = 'rtfilter-unicast'
126 |
127 | CHOICES = [
128 | (IPV4_UNICAST, 'IPv4 Unicast'),
129 | (IPV6_UNICAST, 'IPv6 Unicast'),
130 | (VPNV4_UNICAST, 'VPNv4 Unicast'),
131 | (VPNV6_UNICAST, 'VPNv6 Unicast'),
132 | (IPV4_MULTICAST, 'IPv4 Multicast'),
133 | (IPV6_MULTICAST, 'IPv6 Multicast'),
134 | (VPNV4_UNICAST, 'VPNv4 Multicast'),
135 | (VPNV6_MULTICAST, 'VPNv6 Multicast'),
136 | (IPV4_FLOWSPEC, 'IPv4 Flowspec'),
137 | (IPV6_FLOWSPEC, 'IPv6 Flowspec'),
138 | (VPNV4_FLOWSPEC, 'VPNv4 Flowspec'),
139 | (VPNV6_FLOWSPEC, 'VPNv6 Flowspec'),
140 | (NSAP, 'NSAP'),
141 | (L2VPNVPLS, 'L2VPN VPLS'),
142 | (L2VPSEVPN, 'L2VPN EVPN'),
143 | (LINKSTATE, 'LINK-STATE'),
144 | (RTFILTER_UNICAST, 'RTFILTER')
145 | ]
146 |
--------------------------------------------------------------------------------
/netbox_routing/choices/eigrp.py:
--------------------------------------------------------------------------------
1 | from utilities.choices import ChoiceSet
2 |
3 |
4 | class EIGRPRouterChoices(ChoiceSet):
5 | CLASSIC = 'classic'
6 | NAMED = 'named'
7 |
8 | CHOICES = [
9 | (CLASSIC, 'Classic Router'),
10 | (NAMED, 'Named Router')
11 | ]
--------------------------------------------------------------------------------
/netbox_routing/choices/objects.py:
--------------------------------------------------------------------------------
1 | from utilities.choices import ChoiceSet
2 |
3 |
4 | class PermitDenyChoices(ChoiceSet):
5 | PERMIT = 'permit'
6 | DENY = 'deny'
7 |
8 | CHOICES = [
9 | (PERMIT, 'Permit', 'blue'),
10 | (DENY, 'Deny', 'red')
11 | ]
12 |
--------------------------------------------------------------------------------
/netbox_routing/constants/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanSheps/netbox-routing/56ec2104ef304157cb27e3ec75d16597031d08c6/netbox_routing/constants/__init__.py
--------------------------------------------------------------------------------
/netbox_routing/constants/bgp.py:
--------------------------------------------------------------------------------
1 | from django.db.models import Q
2 |
3 | BGPSETTING_ASSIGNMENT_MODELS = Q(
4 | Q(app_label='netbox_routing', model='bgprouter') |
5 | Q(app_label='netbox_routing', model='bgpscope')
6 | )
7 |
8 | BGPAF_ASSIGNMENT_MODELS = Q(
9 | Q(app_label='netbox_routing', model='bgprouter') |
10 | Q(app_label='netbox_routing', model='bgpscope')
11 | )
12 |
13 | BGPPEER_ASSIGNMENT_MODELS = Q(
14 | Q(app_label='netbox_routing', model='bgprouter') |
15 | Q(app_label='netbox_routing', model='bgpscope')
16 | )
17 |
18 | BGPPEERAF_ASSIGNMENT_MODELS = Q(
19 | Q(app_label='netbox_routing', model='bgppeer') |
20 | Q(app_label='netbox_routing', model='bgppeergroup') |
21 | Q(app_label='netbox_routing', model='bgptemplatepeer') |
22 | Q(app_label='netbox_routing', model='bgptemplatepeerpolicy')
23 | )
--------------------------------------------------------------------------------
/netbox_routing/constants/eigrp.py:
--------------------------------------------------------------------------------
1 | from django.db.models import Q
2 |
3 | EIGRP_ROUTER_MODELS = Q(
4 | app_label='netbox_routing',
5 | model__in=('eigrpnamedrouter', 'eigrpclassicrouter', )
6 | )
7 |
8 | EIGRP_ASSIGNABLE_MODELS = Q(
9 | app_label='netbox_routing',
10 | model__in=('eigrpnamedrouter', 'eigrpclassicrouter', 'eigrpaddressfamily', )
11 | )
--------------------------------------------------------------------------------
/netbox_routing/fields/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanSheps/netbox-routing/56ec2104ef304157cb27e3ec75d16597031d08c6/netbox_routing/fields/__init__.py
--------------------------------------------------------------------------------
/netbox_routing/fields/ip.py:
--------------------------------------------------------------------------------
1 | from django.core.exceptions import ValidationError
2 | from django.db import models
3 | from netaddr import AddrFormatError, IPAddress
4 |
5 | from ipam import lookups
6 | from ipam.formfields import IPAddressFormField
7 |
8 |
9 | class IPAddressField(models.Field):
10 |
11 | def python_type(self):
12 | return IPAddress
13 |
14 | def from_db_value(self, value, expression, connection):
15 | return self.to_python(value)
16 |
17 | def to_python(self, value):
18 | if value is None:
19 | return None
20 | elif not value:
21 | return value
22 | try:
23 | # Always return a netaddr.IPNetwork object. (netaddr.IPAddress does not provide a mask.)
24 | return IPAddress(value)
25 | except AddrFormatError:
26 | raise ValidationError("Invalid IP address format: {}".format(value))
27 | except (TypeError, ValueError) as e:
28 | raise ValidationError(e)
29 |
30 | def get_prep_value(self, value):
31 | if value is None:
32 | return None
33 | if isinstance(value, list):
34 | return [str(self.to_python(v)) for v in value]
35 | return str(self.to_python(value))
36 |
37 | def form_class(self):
38 | return IPAddressFormField
39 |
40 | def formfield(self, **kwargs):
41 | defaults = {'form_class': self.form_class()}
42 | defaults.update(kwargs)
43 | return super().formfield(**defaults)
44 |
45 |
46 | IPAddressField.register_lookup(lookups.IExact)
47 | IPAddressField.register_lookup(lookups.EndsWith)
48 | IPAddressField.register_lookup(lookups.IEndsWith)
49 | IPAddressField.register_lookup(lookups.StartsWith)
50 | IPAddressField.register_lookup(lookups.IStartsWith)
51 | IPAddressField.register_lookup(lookups.Regex)
52 | IPAddressField.register_lookup(lookups.IRegex)
53 | IPAddressField.register_lookup(lookups.NetContained)
54 | IPAddressField.register_lookup(lookups.NetContainedOrEqual)
55 | IPAddressField.register_lookup(lookups.NetFamily)
--------------------------------------------------------------------------------
/netbox_routing/filtersets/__init__.py:
--------------------------------------------------------------------------------
1 | from .static import StaticRouteFilterSet
2 | from .objects import PrefixListFilterSet, PrefixListEntryFilterSet, RouteMapFilterSet, RouteMapEntryFilterSet
3 | from .ospf import *
4 | from .bgp import *
5 | from .eigrp import *
6 |
7 |
8 | __all__ = (
9 | 'StaticRouteFilterSet',
10 |
11 | 'BGPSettingFilterSet',
12 | 'BGPRouterFilterSet',
13 |
14 | 'OSPFInstanceFilterSet',
15 | 'OSPFAreaFilterSet',
16 | 'OSPFInterfaceFilterSet',
17 |
18 | 'EIGRPRouterFilterSet',
19 | 'EIGRPAddressFamilyFilterSet',
20 | 'EIGRPNetworkFilterSet',
21 | 'EIGRPInterfaceFilterSet',
22 |
23 | 'PrefixListFilterSet',
24 | 'PrefixListEntryFilterSet',
25 | 'RouteMapFilterSet',
26 | 'RouteMapEntryFilterSet',
27 | )
28 |
--------------------------------------------------------------------------------
/netbox_routing/filtersets/bgp.py:
--------------------------------------------------------------------------------
1 | import django_filters
2 | from django.db.models import Q
3 | from django.utils.translation import gettext as _
4 |
5 | from netbox.filtersets import NetBoxModelFilterSet
6 | from dcim.models import Device
7 | from ipam.models import ASN, VRF
8 | from netbox_routing.choices.bgp import BGPSettingChoices, BGPAddressFamilyChoices
9 | from netbox_routing.models import BGPRouter, BGPSetting, BGPAddressFamily, BGPScope
10 |
11 |
12 | __all__ = (
13 | 'BGPRouterFilterSet',
14 | 'BGPScopeFilterSet',
15 | 'BGPAddressFamilyFilterSet',
16 | 'BGPSettingFilterSet',
17 | )
18 |
19 |
20 |
21 | class BGPRouterFilterSet(NetBoxModelFilterSet):
22 | device_id = django_filters.ModelMultipleChoiceFilter(
23 | field_name='device',
24 | queryset=Device.objects.all(),
25 | label=_('Device (ID)'),
26 | )
27 | device = django_filters.ModelMultipleChoiceFilter(
28 | field_name='device__name',
29 | queryset=Device.objects.all(),
30 | to_field_name='name',
31 | label=_('Device'),
32 | )
33 | asn_id = django_filters.ModelMultipleChoiceFilter(
34 | field_name='asn',
35 | queryset=ASN.objects.all(),
36 | label=_('AS Number (ID)'),
37 | )
38 | asn = django_filters.ModelMultipleChoiceFilter(
39 | field_name='asn__asn',
40 | queryset=ASN.objects.all(),
41 | to_field_name='asn',
42 | label=_('AS Number'),
43 | )
44 |
45 | class Meta:
46 | model = BGPRouter
47 | fields = ('device_id', 'device', 'asn_id', 'asn')
48 |
49 | def search(self, queryset, name, value):
50 | if not value.strip():
51 | return queryset
52 | qs_filter = (
53 | Q(device__name__icontains=value) |
54 | Q(asn__asn__icontains=value)
55 | )
56 | return queryset.filter(qs_filter).distinct()
57 |
58 |
59 |
60 | class BGPScopeFilterSet(NetBoxModelFilterSet):
61 | router_id = django_filters.ModelMultipleChoiceFilter(
62 | field_name='router',
63 | queryset=BGPRouter.objects.all(),
64 | label=_('Router (ID)'),
65 | )
66 | vrf_id = django_filters.ModelMultipleChoiceFilter(
67 | field_name='vrf',
68 | queryset=VRF.objects.all(),
69 | label=_('VRF (ID)'),
70 | )
71 | vrf = django_filters.ModelMultipleChoiceFilter(
72 | field_name='vrf__name',
73 | queryset=VRF.objects.all(),
74 | to_field_name='vrf',
75 | label=_('VRF'),
76 | )
77 |
78 | class Meta:
79 | model = BGPScope
80 | fields = ('router_id', 'vrf_id', 'vrf')
81 |
82 | def search(self, queryset, name, value):
83 | if not value.strip():
84 | return queryset
85 | qs_filter = (
86 | Q(router__device__name__icontains=value) |
87 | Q(router__asn__asn__icontains=value) |
88 | Q(vrf__name__icontains=value)
89 | )
90 | return queryset.filter(qs_filter).distinct()
91 |
92 |
93 |
94 | class BGPAddressFamilyFilterSet(NetBoxModelFilterSet):
95 | scope_id = django_filters.ModelMultipleChoiceFilter(
96 | field_name='scope',
97 | queryset=Device.objects.all(),
98 | label=_('Router (ID)'),
99 | )
100 | address_family = django_filters.MultipleChoiceFilter(
101 | choices=BGPAddressFamilyChoices,
102 | null_value=None,
103 | label=_('Address Family')
104 | )
105 |
106 | class Meta:
107 | model = BGPAddressFamily
108 | fields = ('scope_id', 'address_family')
109 |
110 | def search(self, queryset, name, value):
111 | if not value.strip():
112 | return queryset
113 | qs_filter = (
114 | Q(address_family__icontains=value)
115 | )
116 | return queryset.filter(qs_filter).distinct()
117 |
118 |
119 | class BGPSettingFilterSet(NetBoxModelFilterSet):
120 | key = django_filters.MultipleChoiceFilter(
121 | choices=BGPSettingChoices,
122 | null_value=None,
123 | label=_('Setting Name')
124 | )
125 |
126 | class Meta:
127 | model = BGPSetting
128 | fields = ('key', )
129 |
130 | def search(self, queryset, name, value):
131 | if not value.strip():
132 | return queryset
133 | qs_filter = (
134 | Q(key__icontains=value)
135 | )
136 | return queryset.filter(qs_filter).distinct()
137 |
--------------------------------------------------------------------------------
/netbox_routing/filtersets/objects.py:
--------------------------------------------------------------------------------
1 | import django_filters
2 | import netaddr
3 | from django.db.models import Q
4 |
5 | from netbox.filtersets import NetBoxModelFilterSet
6 | from netbox_routing.models import PrefixList, PrefixListEntry, RouteMapEntry, RouteMap
7 |
8 |
9 | class PrefixListFilterSet(NetBoxModelFilterSet):
10 | class Meta:
11 | model = PrefixList
12 | fields = ()
13 |
14 | def search(self, queryset, name, value):
15 | if not value.strip():
16 | return queryset
17 | qs_filter = (
18 | Q(name=value)
19 | )
20 | return queryset.filter(qs_filter).distinct()
21 |
22 |
23 | class PrefixListEntryFilterSet(NetBoxModelFilterSet):
24 |
25 | prefix = django_filters.CharFilter(
26 | method='filter_prefix',
27 | label='Prefix',
28 | )
29 |
30 | class Meta:
31 | model = PrefixListEntry
32 | fields = ('prefix_list', 'prefix', 'sequence', 'type', 'le', 'ge')
33 |
34 | def search(self, queryset, name, value):
35 | if not value.strip():
36 | return queryset
37 | qs_filter = (
38 | Q(prefix_list__name__icontains=value) |
39 | Q(prefix__icontains=value) |
40 | Q(type=value)
41 | )
42 | return queryset.filter(qs_filter).distinct()
43 |
44 | def filter_prefix(self, queryset, name, value):
45 | if not value.strip():
46 | return queryset
47 | try:
48 | query = str(netaddr.IPNetwork(value).cidr)
49 | return queryset.filter(prefix=query)
50 | except (netaddr.AddrFormatError, ValueError):
51 | return queryset.none()
52 |
53 |
54 | class RouteMapFilterSet(NetBoxModelFilterSet):
55 |
56 | class Meta:
57 | model = RouteMap
58 | fields = ()
59 |
60 | def search(self, queryset, name, value):
61 | if not value.strip():
62 | return queryset
63 | qs_filter = (
64 | Q(name__icontains=value)
65 | )
66 | return queryset.filter(qs_filter).distinct()
67 |
68 |
69 | class RouteMapEntryFilterSet(NetBoxModelFilterSet):
70 |
71 | class Meta:
72 | model = RouteMapEntry
73 | fields = ('route_map', 'sequence', 'type')
74 |
75 | def search(self, queryset, name, value):
76 | if not value.strip():
77 | return queryset
78 | qs_filter = (
79 | Q(route_map__name__icontains=value) |
80 | Q(type=value)
81 | )
82 | return queryset.filter(qs_filter).distinct()
83 |
--------------------------------------------------------------------------------
/netbox_routing/filtersets/ospf.py:
--------------------------------------------------------------------------------
1 | import django_filters
2 | from django.core.exceptions import ValidationError
3 | from django.db.models import Q
4 | from django.utils.translation import gettext as _
5 |
6 | from dcim.models import Device, Interface
7 | from ipam.models import VRF
8 | from utilities.filters import MultiValueCharFilter
9 |
10 | from netbox.filtersets import NetBoxModelFilterSet
11 | from netbox_routing.models import OSPFArea, OSPFInstance, OSPFInterface
12 |
13 |
14 | __all__ = (
15 | 'OSPFAreaFilterSet',
16 | 'OSPFInstanceFilterSet',
17 | 'OSPFInterfaceFilterSet'
18 | )
19 |
20 |
21 |
22 | class OSPFInstanceFilterSet(NetBoxModelFilterSet):
23 | device_id = django_filters.ModelMultipleChoiceFilter(
24 | field_name='device',
25 | queryset=Device.objects.all(),
26 | label='Device (ID)',
27 | )
28 | device = django_filters.ModelMultipleChoiceFilter(
29 | field_name='device__name',
30 | queryset=Device.objects.all(),
31 | to_field_name='name',
32 | label='Device',
33 | )
34 | vrf_id = django_filters.ModelMultipleChoiceFilter(
35 | field_name='vrf',
36 | queryset=VRF.objects.all(),
37 | label='VRF (ID)',
38 | )
39 | vrf = django_filters.ModelMultipleChoiceFilter(
40 | field_name='vrf__name',
41 | queryset=VRF.objects.all(),
42 | to_field_name='name',
43 | label='VRF',
44 | )
45 |
46 | router_id = MultiValueCharFilter(
47 | method='filter_rid',
48 | label=_('Router ID'),
49 | )
50 |
51 | class Meta:
52 | model = OSPFInstance
53 | fields = ('device_id', 'device', 'name', 'vrf_id', 'vrf', 'router_id', 'process_id')
54 |
55 | def search(self, queryset, name, value):
56 | if not value.strip():
57 | return queryset
58 | qs_filter = (
59 | Q(name__icontains=value) |
60 | Q(device__name__icontains=value) |
61 | Q(router_id__icontains=value)
62 | )
63 | return queryset.filter(qs_filter).distinct()
64 |
65 | def filter_rid(self, queryset, name, value):
66 | try:
67 | return queryset.filter(**{f'{name}__in': value})
68 | except ValidationError:
69 | return queryset.none()
70 |
71 |
72 | class OSPFAreaFilterSet(NetBoxModelFilterSet):
73 | area_id = MultiValueCharFilter(
74 | method='filter_aid',
75 | label=_('Area ID'),
76 | )
77 |
78 | class Meta:
79 | model = OSPFArea
80 | fields = ('area_id', )
81 |
82 | def search(self, queryset, name, value):
83 | if not value.strip():
84 | return queryset
85 | qs_filter = (
86 | Q(area_id__icontains=value)
87 | )
88 | return queryset.filter(qs_filter).distinct()
89 |
90 | def filter_aid(self, queryset, name, value):
91 | try:
92 | return queryset.filter(**{f'{name}__in': value})
93 | except ValidationError:
94 | return queryset.none()
95 |
96 |
97 | class OSPFInterfaceFilterSet(NetBoxModelFilterSet):
98 | instance_id = django_filters.ModelMultipleChoiceFilter(
99 | field_name='instance',
100 | queryset=OSPFInstance.objects.all(),
101 | label='Instance (ID)',
102 | )
103 | instance = django_filters.ModelMultipleChoiceFilter(
104 | field_name='instance__name',
105 | queryset=OSPFInstance.objects.all(),
106 | to_field_name='name',
107 | label='Instance',
108 | )
109 | vrf_id = django_filters.ModelMultipleChoiceFilter(
110 | field_name='instance__vrf',
111 | queryset=VRF.objects.all(),
112 | label='VRF (ID)',
113 | )
114 | vrf = django_filters.ModelMultipleChoiceFilter(
115 | field_name='instance__vrf__name',
116 | queryset=VRF.objects.all(),
117 | to_field_name='name',
118 | label='VRF',
119 | )
120 | area_id = django_filters.ModelMultipleChoiceFilter(
121 | field_name='area',
122 | queryset=OSPFArea.objects.all(),
123 | label='Area (ID)',
124 | )
125 | area = django_filters.ModelMultipleChoiceFilter(
126 | field_name='area__area_id',
127 | queryset=OSPFArea.objects.all(),
128 | to_field_name='area_id',
129 | label='Area',
130 | )
131 | device_id = django_filters.ModelMultipleChoiceFilter(
132 | field_name='interface__device',
133 | queryset=Device.objects.all(),
134 | label='Device (ID)',
135 | )
136 | device = django_filters.ModelMultipleChoiceFilter(
137 | field_name='interface__device__name',
138 | queryset=Device.objects.all(),
139 | to_field_name='name',
140 | label='Device',
141 | )
142 | interface_id = django_filters.ModelMultipleChoiceFilter(
143 | field_name='interface',
144 | queryset=Interface.objects.all(),
145 | label='Area (ID)',
146 | )
147 | interface = django_filters.ModelMultipleChoiceFilter(
148 | field_name='interface__name',
149 | queryset=Interface.objects.all(),
150 | to_field_name='name',
151 | label='Area',
152 | )
153 |
154 | class Meta:
155 | model = OSPFInterface
156 | fields = ('instance', 'area', 'interface', 'passive', 'bfd', 'priority', 'authentication', 'passphrase')
157 |
158 | def search(self, queryset, name, value):
159 | if not value.strip():
160 | return queryset
161 | qs_filter = (
162 | Q(instance__name__icontains=value) |
163 | Q(area__area_id__icontains=value) |
164 | Q(interface__name__icontains=value) |
165 | Q(interface__label__icontains=value) |
166 | Q(interface__device__name__icontains=value)
167 | )
168 | return queryset.filter(qs_filter).distinct()
169 |
170 |
--------------------------------------------------------------------------------
/netbox_routing/filtersets/static.py:
--------------------------------------------------------------------------------
1 | import django_filters
2 | import netaddr
3 | from django.db.models import Q
4 |
5 | from dcim.models import Device
6 | from ipam.models import VRF
7 | from netbox.filtersets import NetBoxModelFilterSet
8 | from netbox_routing.models import StaticRoute
9 |
10 |
11 | class StaticRouteFilterSet(NetBoxModelFilterSet):
12 |
13 | device = django_filters.ModelMultipleChoiceFilter(
14 | field_name='devices__name',
15 | queryset=Device.objects.all(),
16 | to_field_name='name',
17 | label='Device (name)',
18 | )
19 | device_id = django_filters.ModelMultipleChoiceFilter(
20 | field_name='devices',
21 | queryset=Device.objects.all(),
22 | label='Device (ID)',
23 | )
24 | vrf = django_filters.ModelMultipleChoiceFilter(
25 | field_name='vrf__name',
26 | queryset=VRF.objects.all(),
27 | to_field_name='name',
28 | label='VRF (name)',
29 | )
30 | vrf_id = django_filters.ModelMultipleChoiceFilter(
31 | field_name='vrf',
32 | queryset=VRF.objects.all(),
33 | label='VRF (ID)',
34 | )
35 |
36 | prefix = django_filters.CharFilter(
37 | method='filter_prefix',
38 | label='Prefix',
39 | )
40 |
41 | next_hop = django_filters.CharFilter(
42 | method='filter_address',
43 | label='Prefix',
44 | )
45 |
46 | class Meta:
47 | model = StaticRoute
48 | fields = ('name', 'devices', 'device', 'device_id', 'vrf', 'vrf_id', 'prefix', 'metric', 'next_hop')
49 |
50 | def search(self, queryset, name, value):
51 | if not value.strip():
52 | return queryset
53 | qs_filter = (
54 | Q(devices__name__icontains=value) |
55 | Q(vrf__name__icontains=value) |
56 | Q(vrf__rd__icontains=value) |
57 | Q(prefix__icontains=value) |
58 | Q(next_hop__icontains=value) |
59 | Q(name__icontains=value)
60 | )
61 | return queryset.filter(qs_filter).distinct()
62 |
63 | def filter_prefix(self, queryset, name, value):
64 | if not value.strip():
65 | return queryset
66 | try:
67 | query = str(netaddr.IPNetwork(value).cidr)
68 | return queryset.filter(**{f'{name}': query})
69 | except (netaddr.AddrFormatError, ValueError):
70 | return queryset.none()
71 |
72 | def filter_address(self, queryset, name, value):
73 | if not value.strip():
74 | return queryset
75 | try:
76 | query = netaddr.IPAddress(value)
77 | return queryset.filter(**{f'{name}': query})
78 | except (netaddr.AddrFormatError, ValueError) as e:
79 | return queryset.none()
80 |
--------------------------------------------------------------------------------
/netbox_routing/forms/__init__.py:
--------------------------------------------------------------------------------
1 | from .filtersets import *
2 | from .bulk_edit import *
3 | from .bulk_import import *
4 | from .objects import PrefixListForm, PrefixListEntryForm, RouteMapForm, RouteMapEntryForm
5 | from .ospf import OSPFAreaForm, OSPFInstanceForm, OSPFInterfaceForm
6 | from .bgp import BGPRouterForm, BGPScopeForm, BGPAddressFamilyForm
7 | from .static import StaticRouteForm
8 | from .eigrp import *
9 |
10 | __all__ = (
11 |
12 | # Static Routes
13 | 'StaticRouteForm',
14 | 'StaticRouteFilterForm',
15 |
16 | # OSPF
17 | 'OSPFAreaForm',
18 | 'OSPFAreaBulkEditForm',
19 | 'OSPFAreaImportForm',
20 | 'OSPFAreaFilterForm',
21 |
22 | 'OSPFInstanceForm',
23 | 'OSPFInstanceBulkEditForm',
24 | 'OSPFInstanceFilterForm',
25 | 'OSPFInstanceImportForm',
26 |
27 | 'OSPFInterfaceForm',
28 | 'OSPFInterfaceFilterForm',
29 | 'OSPFInterfaceBulkEditForm',
30 | 'OSPFInterfaceImportForm',
31 |
32 | # EIGRP
33 | 'EIGRPRouterForm',
34 | 'EIGRPRouterBulkEditForm',
35 | 'EIGRPRouterFilterForm',
36 | 'EIGRPRouterImportForm',
37 |
38 | 'EIGRPAddressFamilyForm',
39 | 'EIGRPAddressFamilyBulkEditForm',
40 | 'EIGRPAddressFamilyFilterForm',
41 | 'EIGRPAddressFamilyImportForm',
42 |
43 | 'EIGRPNetworkForm',
44 | 'EIGRPNetworkBulkEditForm',
45 | 'EIGRPNetworkFilterForm',
46 | 'EIGRPNetworkImportForm',
47 |
48 | 'EIGRPInterfaceForm',
49 | 'EIGRPInterfaceBulkEditForm',
50 | 'EIGRPInterfaceFilterForm',
51 | 'EIGRPInterfaceImportForm',
52 |
53 | # BGP
54 | 'BGPRouterForm',
55 | 'BGPScopeForm',
56 | 'BGPAddressFamilyForm',
57 | 'BGPRouterFilterForm',
58 | 'BGPScopeFilterForm',
59 | 'BGPAddressFamilyFilterForm',
60 | 'BGPSettingFilterForm',
61 |
62 | # Objects
63 | 'PrefixListForm',
64 | 'PrefixListEntryForm',
65 | 'RouteMapForm',
66 | 'RouteMapEntryForm',
67 | 'PrefixListFilterForm',
68 | 'PrefixListEntryFilterForm',
69 | 'RouteMapFilterForm',
70 | 'RouteMapEntryFilterForm'
71 | )
72 |
--------------------------------------------------------------------------------
/netbox_routing/forms/bulk_edit/__init__.py:
--------------------------------------------------------------------------------
1 | from .static import *
2 | from .objects import *
3 | from .ospf import *
4 | from .eigrp import *
5 |
6 |
7 | __all__ = (
8 | # Staticroute
9 | 'StaticRouteBulkEditForm',
10 |
11 | # OSPF
12 | 'OSPFInstanceBulkEditForm',
13 | 'OSPFInterfaceBulkEditForm',
14 | 'OSPFAreaBulkEditForm',
15 |
16 | # EIGRP
17 | 'EIGRPRouterBulkEditForm',
18 | 'EIGRPAddressFamilyBulkEditForm',
19 | 'EIGRPNetworkBulkEditForm',
20 | 'EIGRPInterfaceBulkEditForm',
21 |
22 | # Route Objects
23 | 'PrefixListEntryBulkEditForm',
24 | 'RouteMapEntryBulkEditForm'
25 | )
26 |
27 |
--------------------------------------------------------------------------------
/netbox_routing/forms/bulk_edit/eigrp.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 | from django.forms import CharField
3 | from django.utils.translation import gettext as _
4 |
5 | from dcim.models import Device
6 | from ipam.models import VRF
7 | from netbox.forms import NetBoxModelBulkEditForm
8 | from netbox_routing.models import EIGRPRouter, EIGRPAddressFamily, EIGRPNetwork, EIGRPInterface
9 | from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, add_blank_choice
10 | from utilities.forms.fields import DynamicModelChoiceField, CommentField
11 | from utilities.forms.rendering import FieldSet
12 |
13 | from netbox_routing import choices
14 |
15 | __all__ = (
16 | 'EIGRPRouterBulkEditForm',
17 | 'EIGRPAddressFamilyBulkEditForm',
18 | 'EIGRPNetworkBulkEditForm',
19 | 'EIGRPInterfaceBulkEditForm',
20 | )
21 |
22 |
23 | class EIGRPRouterBulkEditForm(NetBoxModelBulkEditForm):
24 | device = DynamicModelChoiceField(
25 | queryset=Device.objects.all(),
26 | label=_('Device'),
27 | required=False,
28 | selector=True
29 | )
30 |
31 | description = forms.CharField(
32 | label=_('Description'),
33 | max_length=200,
34 | required=False
35 | )
36 | comments = CommentField()
37 |
38 | model = EIGRPRouter
39 | fieldsets = (
40 | FieldSet('device', 'mode', name='EIGRP'),
41 | FieldSet('description', ),
42 | )
43 | nullable_fields = ()
44 |
45 |
46 | class EIGRPAddressFamilyBulkEditForm(NetBoxModelBulkEditForm):
47 | vrf = DynamicModelChoiceField(
48 | queryset=VRF.objects.all(),
49 | label=_('VRF'),
50 | required=False,
51 | selector=True
52 | )
53 | family = CharField(max_length=4)
54 |
55 | description = forms.CharField(
56 | label=_('Description'),
57 | max_length=200,
58 | required=False
59 | )
60 | comments = CommentField()
61 |
62 | model = EIGRPAddressFamily
63 | fieldsets = (
64 | FieldSet('description'),
65 | )
66 | nullable_fields = ()
67 |
68 |
69 | class EIGRPNetworkBulkEditForm(NetBoxModelBulkEditForm):
70 | router = DynamicModelChoiceField(
71 | queryset=EIGRPRouter.objects.all(),
72 | label=_('Router'),
73 | required=False,
74 | selector=True
75 | )
76 | address_family = DynamicModelChoiceField(
77 | queryset=EIGRPAddressFamily.objects.all(),
78 | label=_('Router'),
79 | required=False,
80 | selector=True
81 | )
82 |
83 | description = forms.CharField(
84 | label=_('Description'),
85 | max_length=200,
86 | required=False
87 | )
88 | comments = CommentField()
89 |
90 | model = EIGRPNetwork
91 | fieldsets = (
92 | FieldSet('description'),
93 | )
94 | nullable_fields = ()
95 |
96 |
97 | class EIGRPInterfaceBulkEditForm(NetBoxModelBulkEditForm):
98 | router = DynamicModelChoiceField(
99 | queryset=EIGRPRouter.objects.all(),
100 | label=_('EIGRP Router'),
101 | required=False,
102 | selector=True
103 | )
104 | address_family = DynamicModelChoiceField(
105 | queryset=EIGRPAddressFamily.objects.all(),
106 | label=_('EIGRP Address Family'),
107 | required=False,
108 | selector=True
109 | )
110 | passive = forms.ChoiceField(label=_('Passive'), choices=BOOLEAN_WITH_BLANK_CHOICES, required=False)
111 | bfd = forms.ChoiceField(label=_('BFD'), choices=BOOLEAN_WITH_BLANK_CHOICES, required=False)
112 | authentication = forms.ChoiceField(
113 | label=_('Authentication'),
114 | choices=add_blank_choice(choices.AuthenticationChoices),
115 | required=False
116 | )
117 | passphrase = forms.CharField(label=_('Passphrase'), required=False)
118 |
119 | description = forms.CharField(
120 | label=_('Description'),
121 | max_length=200,
122 | required=False
123 | )
124 | comments = CommentField()
125 |
126 | model = EIGRPInterface
127 | fieldsets = (
128 | FieldSet('router', 'address_family', name='EIGRP'),
129 | FieldSet('priority', 'bfd', 'authentication', 'passphrase', name='Attributes'),
130 | FieldSet('description'),
131 | )
132 | nullable_fields = ()
133 |
--------------------------------------------------------------------------------
/netbox_routing/forms/bulk_edit/objects.py:
--------------------------------------------------------------------------------
1 |
2 | from django.utils.translation import gettext as _
3 |
4 | from netbox.forms import NetBoxModelBulkEditForm
5 | from netbox_routing.models import PrefixList, PrefixListEntry, RouteMapEntry, RouteMap
6 | from utilities.forms.fields import DynamicModelChoiceField
7 |
8 |
9 | __all__ = (
10 | 'PrefixListEntryBulkEditForm',
11 | 'RouteMapEntryBulkEditForm'
12 | )
13 |
14 | from utilities.forms.rendering import FieldSet
15 |
16 |
17 | class PrefixListEntryBulkEditForm(NetBoxModelBulkEditForm):
18 | prefix_list = DynamicModelChoiceField(
19 | queryset=PrefixList.objects.all(),
20 | label=_('Prefix List'),
21 | required=False,
22 | selector=True
23 | )
24 |
25 | model = PrefixListEntry
26 | fieldsets = (
27 | FieldSet('prefix_list'),
28 | )
29 | nullable_fields = ()
30 |
31 |
32 | class RouteMapEntryBulkEditForm(NetBoxModelBulkEditForm):
33 | route_map = DynamicModelChoiceField(
34 | queryset=RouteMap.objects.all(),
35 | label=_('Route Map'),
36 | required=False,
37 | selector=True
38 | )
39 |
40 | model = RouteMapEntry
41 | fieldsets = (
42 | FieldSet('route_map'),
43 | )
44 | nullable_fields = ()
45 |
--------------------------------------------------------------------------------
/netbox_routing/forms/bulk_edit/ospf.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 | from django.utils.translation import gettext as _
3 |
4 | from dcim.models import Device
5 | from ipam.models import VRF
6 | from netbox.forms import NetBoxModelBulkEditForm
7 | from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, add_blank_choice
8 | from utilities.forms.fields import DynamicModelChoiceField, CommentField
9 |
10 | from netbox_routing import choices
11 | from netbox_routing.models import OSPFArea, OSPFInstance, OSPFInterface
12 |
13 | __all__ = (
14 | 'OSPFInterfaceBulkEditForm',
15 | 'OSPFInstanceBulkEditForm',
16 | 'OSPFAreaBulkEditForm',
17 | )
18 |
19 | from utilities.forms.rendering import FieldSet
20 |
21 |
22 | class OSPFInstanceBulkEditForm(NetBoxModelBulkEditForm):
23 | device = DynamicModelChoiceField(
24 | queryset=Device.objects.all(),
25 | label=_('Device'),
26 | required=False,
27 | selector=True
28 | )
29 | vrf = DynamicModelChoiceField(
30 | queryset=VRF.objects.all(),
31 | label=_('VRF'),
32 | required=False,
33 | selector=True
34 | )
35 |
36 | description = forms.CharField(
37 | label=_('Description'),
38 | max_length=200,
39 | required=False
40 | )
41 | comments = CommentField()
42 |
43 | model = OSPFInstance
44 | fieldsets = (
45 | FieldSet('device', 'vrf', name='OSPF'),
46 | FieldSet('description', ),
47 | )
48 | nullable_fields = ('vrf', 'description', )
49 |
50 |
51 | class OSPFAreaBulkEditForm(NetBoxModelBulkEditForm):
52 |
53 | description = forms.CharField(
54 | label=_('Description'),
55 | max_length=200,
56 | required=False
57 | )
58 | comments = CommentField()
59 |
60 | model = OSPFArea
61 | fieldsets = (
62 | FieldSet('description'),
63 | )
64 | nullable_fields = ()
65 |
66 |
67 | class OSPFInterfaceBulkEditForm(NetBoxModelBulkEditForm):
68 | instance = DynamicModelChoiceField(
69 | queryset=OSPFInstance.objects.all(),
70 | label=_('OSPF Instance'),
71 | required=False,
72 | selector=True
73 | )
74 | area = DynamicModelChoiceField(
75 | queryset=OSPFArea.objects.all(),
76 | label=_('OSPF Area'),
77 | required=False,
78 | selector=True
79 | )
80 | passive = forms.ChoiceField(label=_('Passive'), choices=BOOLEAN_WITH_BLANK_CHOICES, required=False)
81 | priority = forms.IntegerField(label=_('Priority'), required=False)
82 | bfd = forms.ChoiceField(label=_('BFD'), choices=BOOLEAN_WITH_BLANK_CHOICES, required=False)
83 | authentication = forms.ChoiceField(
84 | label=_('Authentication'),
85 | choices=add_blank_choice(choices.AuthenticationChoices),
86 | required=False
87 | )
88 | passphrase = forms.CharField(label=_('Passphrase'), required=False)
89 |
90 | description = forms.CharField(
91 | label=_('Description'),
92 | max_length=200,
93 | required=False
94 | )
95 | comments = CommentField()
96 |
97 | model = OSPFInterface
98 | fieldsets = (
99 | FieldSet('instance', 'area', name='OSPF'),
100 | FieldSet('passive', 'priority', 'bfd', 'authentication', 'passphrase', name='Attributes'),
101 | FieldSet('description'),
102 | )
103 | nullable_fields = ()
104 |
--------------------------------------------------------------------------------
/netbox_routing/forms/bulk_edit/static.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 | from django.utils.translation import gettext as _
3 |
4 | from dcim.models import Device
5 | from ipam.models import VRF
6 | from netbox.forms import NetBoxModelBulkEditForm
7 | from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES
8 | from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField, CommentField
9 | from utilities.forms.rendering import FieldSet
10 |
11 | from netbox_routing.models import StaticRoute
12 |
13 |
14 | __all__ = (
15 | 'StaticRouteBulkEditForm',
16 | )
17 |
18 |
19 | class StaticRouteBulkEditForm(NetBoxModelBulkEditForm):
20 | devices = DynamicModelMultipleChoiceField(
21 | label='Device',
22 | queryset=Device.objects.all(),
23 | required=False,
24 | selector=True,
25 | )
26 | vrf = DynamicModelChoiceField(
27 | label='VRF',
28 | queryset=VRF.objects.all(),
29 | required=False,
30 | selector=True,
31 | )
32 | metric = forms.IntegerField(label=_('Metric'), required=False)
33 | permanent = forms.ChoiceField(label=_('Permanent'), choices=BOOLEAN_WITH_BLANK_CHOICES, required=False)
34 |
35 | description = forms.CharField(
36 | label=_('Description'),
37 | max_length=200,
38 | required=False
39 | )
40 | comments = CommentField()
41 |
42 | model = StaticRoute
43 | fieldsets = (
44 | FieldSet('devices', 'vrf', 'prefix', 'next_hop', name='Route'),
45 | FieldSet('metric', 'permanent', name='Attributes'),
46 | FieldSet('description', )
47 | )
48 | nullable_fields = ('devices', 'vrf', 'metric', 'permanent', 'description', 'comments')
49 |
--------------------------------------------------------------------------------
/netbox_routing/forms/bulk_import/__init__.py:
--------------------------------------------------------------------------------
1 | from .ospf import *
2 | from .eigrp import *
3 |
4 |
5 | __all__ = (
6 | # OSPF
7 | 'OSPFInstanceImportForm',
8 | 'OSPFAreaImportForm',
9 | 'OSPFInterfaceImportForm',
10 |
11 | # EIGRP
12 | 'EIGRPRouterImportForm',
13 | 'EIGRPAddressFamilyImportForm',
14 | 'EIGRPNetworkImportForm',
15 | 'EIGRPInterfaceImportForm',
16 | )
17 |
18 |
--------------------------------------------------------------------------------
/netbox_routing/forms/bulk_import/eigrp.py:
--------------------------------------------------------------------------------
1 | from django.utils.translation import gettext as _
2 |
3 | from dcim.models import Interface
4 | from ipam.models import Prefix
5 | from netbox.forms import NetBoxModelImportForm
6 | from utilities.forms.fields import CSVModelChoiceField
7 |
8 | from netbox_routing.models import *
9 |
10 |
11 | __all__ = (
12 | 'EIGRPRouterImportForm',
13 | 'EIGRPAddressFamilyImportForm',
14 | 'EIGRPNetworkImportForm',
15 | 'EIGRPInterfaceImportForm',
16 | )
17 |
18 |
19 | class EIGRPRouterImportForm(NetBoxModelImportForm):
20 | device = CSVModelChoiceField(
21 | queryset=Interface.objects.all(),
22 | required=False,
23 | to_field_name='name',
24 | help_text=_('Name of device')
25 | )
26 |
27 | class Meta:
28 | model = EIGRPRouter
29 | fields = ('device', 'rid', 'mode', 'name', 'pid', 'description', 'comments', 'tags',)
30 |
31 |
32 | class EIGRPAddressFamilyImportForm(NetBoxModelImportForm):
33 |
34 | class Meta:
35 | model = EIGRPAddressFamily
36 | fields = ('router', 'family', 'rid', 'description', 'comments', 'tags',)
37 |
38 |
39 | class EIGRPNetworkImportForm(NetBoxModelImportForm):
40 | router = CSVModelChoiceField(
41 | queryset=EIGRPRouter.objects.all(),
42 | required=True,
43 | help_text=_('PK of Router Instance')
44 | )
45 | address_family = CSVModelChoiceField(
46 | queryset=EIGRPAddressFamily.objects.all(),
47 | required=False,
48 | help_text=_('PK of Address Family')
49 | )
50 | network = CSVModelChoiceField(
51 | queryset=Prefix.objects.all(),
52 | required=True,
53 | to_field_name='prefix',
54 | help_text=_('Prefix of Network')
55 | )
56 |
57 | class Meta:
58 | model = EIGRPNetwork
59 | fields = ('router', 'address_family', 'network', 'description', 'comments', 'tags',)
60 |
61 |
62 | class EIGRPInterfaceImportForm(NetBoxModelImportForm):
63 | router = CSVModelChoiceField(
64 | queryset=EIGRPRouter.objects.all(),
65 | required=True,
66 | help_text=_('PK of Router Instance')
67 | )
68 | address_family = CSVModelChoiceField(
69 | queryset=EIGRPAddressFamily.objects.all(),
70 | required=False,
71 | help_text=_('PK of Address Family')
72 | )
73 | interface = CSVModelChoiceField(
74 | queryset=Interface.objects.all(),
75 | required=False,
76 | to_field_name='name',
77 | help_text=_('Name of interface')
78 | )
79 |
80 | class Meta:
81 | model = EIGRPInterface
82 | fields = ('router', 'address_family', 'interface', 'description', 'comments', 'tags',)
83 |
--------------------------------------------------------------------------------
/netbox_routing/forms/bulk_import/ospf.py:
--------------------------------------------------------------------------------
1 | from django.utils.translation import gettext as _
2 |
3 | from dcim.models import Interface, Device
4 | from ipam.models import VRF
5 | from netbox.forms import NetBoxModelImportForm
6 | from utilities.forms.fields import CSVModelChoiceField
7 |
8 | from netbox_routing.models import OSPFInstance, OSPFArea, OSPFInterface
9 |
10 |
11 | __all__ = (
12 | 'OSPFInstanceImportForm',
13 | 'OSPFAreaImportForm',
14 | 'OSPFInterfaceImportForm',
15 | )
16 |
17 |
18 | class OSPFInstanceImportForm(NetBoxModelImportForm):
19 | device = CSVModelChoiceField(
20 | queryset=Device.objects.all(),
21 | required=True,
22 | to_field_name='name',
23 | help_text=_('Name of device')
24 | )
25 | vrf = CSVModelChoiceField(
26 | queryset=VRF.objects.all(),
27 | required=False,
28 | to_field_name='name',
29 | help_text=_('Name of VRF')
30 | )
31 |
32 | class Meta:
33 | model = OSPFInstance
34 | fields = ('name', 'router_id', 'process_id', 'device', 'vrf', 'description', 'comments', 'tags',)
35 |
36 |
37 | class OSPFAreaImportForm(NetBoxModelImportForm):
38 |
39 | class Meta:
40 | model = OSPFArea
41 | fields = ('area_id', 'description', 'comments', 'tags',)
42 |
43 |
44 | class OSPFInterfaceImportForm(NetBoxModelImportForm):
45 | instance = CSVModelChoiceField(
46 | queryset=OSPFInstance.objects.all(),
47 | required=False,
48 | to_field_name='name',
49 | help_text=_('Name of OSPF Instance')
50 | )
51 | area = CSVModelChoiceField(
52 | queryset=OSPFArea.objects.all(),
53 | required=False,
54 | to_field_name='name',
55 | help_text=_('Area ID')
56 | )
57 | interface = CSVModelChoiceField(
58 | queryset=Interface.objects.all(),
59 | required=False,
60 | to_field_name='name',
61 | help_text=_('Name of interface')
62 | )
63 |
64 | class Meta:
65 | model = OSPFInterface
66 | fields = ('instance', 'area', 'interface', 'passive', 'description', 'comments', 'tags',)
67 |
--------------------------------------------------------------------------------
/netbox_routing/forms/fields.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanSheps/netbox-routing/56ec2104ef304157cb27e3ec75d16597031d08c6/netbox_routing/forms/fields.py
--------------------------------------------------------------------------------
/netbox_routing/forms/filtersets/__init__.py:
--------------------------------------------------------------------------------
1 | from .static import StaticRouteFilterForm
2 | from .bgp import BGPRouterFilterForm, BGPScopeFilterForm, BGPAddressFamilyFilterForm, BGPSettingFilterForm
3 | from .ospf import OSPFAreaFilterForm, OSPFInstanceFilterForm, OSPFInterfaceFilterForm
4 | from .objects import PrefixListFilterForm, PrefixListEntryFilterForm, RouteMapFilterForm,\
5 | RouteMapEntryFilterForm
6 | from .eigrp import *
7 |
8 | __all__ = (
9 | # Static
10 | 'StaticRouteFilterForm',
11 |
12 | # BGP
13 | 'BGPRouterFilterForm',
14 | 'BGPScopeFilterForm',
15 | 'BGPAddressFamilyFilterForm',
16 | 'BGPSettingFilterForm',
17 |
18 | # EIGRP
19 | 'EIGRPRouterFilterForm',
20 | 'EIGRPAddressFamilyFilterForm',
21 | 'EIGRPNetworkFilterForm',
22 | 'EIGRPInterfaceFilterForm',
23 |
24 | # OSPF
25 | 'OSPFAreaFilterForm',
26 | 'OSPFInstanceFilterForm',
27 | 'OSPFInterfaceFilterForm',
28 |
29 | # Routing Objects
30 | 'PrefixListFilterForm',
31 | 'PrefixListEntryFilterForm',
32 | 'RouteMapFilterForm',
33 | 'RouteMapEntryFilterForm'
34 | )
35 |
--------------------------------------------------------------------------------
/netbox_routing/forms/filtersets/bgp.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 | from django.utils.translation import gettext as _
3 |
4 | from dcim.models import Device
5 | from ipam.models import ASN, VRF
6 | from netbox.forms import NetBoxModelFilterSetForm
7 | from netbox_routing.choices import BGPAddressFamilyChoices
8 | from netbox_routing.models import BGPRouter, BGPSetting, BGPAddressFamily, BGPScope
9 | from utilities.forms.fields import TagFilterField, DynamicModelMultipleChoiceField
10 |
11 |
12 | __all__ = (
13 | 'BGPRouterFilterForm',
14 | 'BGPScopeFilterForm',
15 | 'BGPAddressFamilyFilterForm',
16 | 'BGPSettingFilterForm',
17 | )
18 |
19 | from utilities.forms.rendering import FieldSet
20 |
21 |
22 | class BGPRouterFilterForm(NetBoxModelFilterSetForm):
23 | device_id = DynamicModelMultipleChoiceField(
24 | queryset=Device.objects.all(),
25 | required=False,
26 | selector=True,
27 | label=_('Device'),
28 | )
29 | asn_id = DynamicModelMultipleChoiceField(
30 | queryset=ASN.objects.all(),
31 | required=False,
32 | selector=True,
33 | label=_('ASN'),
34 | )
35 | model = BGPRouter
36 | fieldsets = (
37 | FieldSet('q', 'filter_id', 'tag', 'device_id', 'asn_id'),
38 | )
39 | tag = TagFilterField(model)
40 |
41 |
42 | class BGPScopeFilterForm(NetBoxModelFilterSetForm):
43 | router_id = DynamicModelMultipleChoiceField(
44 | queryset=BGPRouter.objects.all(),
45 | required=False,
46 | selector=True,
47 | label=_('Router'),
48 | )
49 | vrf_id = DynamicModelMultipleChoiceField(
50 | queryset=VRF.objects.all(),
51 | required=False,
52 | selector=True,
53 | label=_('VRF'),
54 | )
55 | model = BGPScope
56 | fieldsets = (
57 | FieldSet('q', 'filter_id', 'tag', 'router_id', 'vrf_id'),
58 | )
59 | tag = TagFilterField(model)
60 |
61 |
62 | class BGPAddressFamilyFilterForm(NetBoxModelFilterSetForm):
63 | scope_id = DynamicModelMultipleChoiceField(
64 | queryset=BGPRouter.objects.all(),
65 | required=False,
66 | selector=True,
67 | label=_('Router'),
68 | )
69 | address_family = forms.MultipleChoiceField(
70 | choices=BGPAddressFamilyChoices,
71 | required=False,
72 | label=_('Address Family'),
73 | )
74 | model = BGPAddressFamily
75 | fieldsets = (
76 | FieldSet('q', 'filter_id', 'tag', 'scope_id', 'address_family'),
77 | )
78 | tag = TagFilterField(model)
79 |
80 |
81 | class BGPSettingFilterForm(NetBoxModelFilterSetForm):
82 | model = BGPSetting
83 | fieldsets = (
84 | FieldSet('q', 'filter_id', 'tag'),
85 | )
86 | tag = TagFilterField(model)
87 |
--------------------------------------------------------------------------------
/netbox_routing/forms/filtersets/objects.py:
--------------------------------------------------------------------------------
1 | from netbox.forms import NetBoxModelFilterSetForm
2 | from netbox_routing.models import PrefixList, PrefixListEntry, RouteMap, RouteMapEntry
3 |
4 |
5 | class PrefixListFilterForm(NetBoxModelFilterSetForm):
6 | model = PrefixList
7 |
8 |
9 | class PrefixListEntryFilterForm(NetBoxModelFilterSetForm):
10 | model = PrefixListEntry
11 |
12 |
13 | class RouteMapFilterForm(NetBoxModelFilterSetForm):
14 | model = RouteMap
15 |
16 |
17 | class RouteMapEntryFilterForm(NetBoxModelFilterSetForm):
18 | model = RouteMapEntry
--------------------------------------------------------------------------------
/netbox_routing/forms/filtersets/ospf.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 | from django.utils.translation import gettext as _
3 |
4 | from dcim.models import Interface, Device
5 | from ipam.models import VRF
6 | from netbox.forms import NetBoxModelFilterSetForm
7 | from netbox_routing.choices import AuthenticationChoices
8 | from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, add_blank_choice
9 | from utilities.forms.fields import TagFilterField, DynamicModelMultipleChoiceField
10 |
11 | from netbox_routing.models import OSPFInstance, OSPFArea, OSPFInterface
12 |
13 |
14 | __all__ = (
15 | 'OSPFAreaFilterForm',
16 | 'OSPFInstanceFilterForm',
17 | 'OSPFInterfaceFilterForm',
18 | )
19 |
20 | from utilities.forms.rendering import FieldSet
21 |
22 |
23 | class OSPFInstanceFilterForm(NetBoxModelFilterSetForm):
24 | device = DynamicModelMultipleChoiceField(
25 | queryset=Device.objects.all(),
26 | required=False,
27 | selector=True,
28 | label=_('Device'),
29 | )
30 | vrf = DynamicModelMultipleChoiceField(
31 | queryset=VRF.objects.all(),
32 | required=False,
33 | selector=True,
34 | label=_('VRF'),
35 | )
36 | model = OSPFInstance
37 | fieldsets = (
38 | FieldSet('q', 'filter_id', 'tag', 'device'),
39 | )
40 | tag = TagFilterField(model)
41 |
42 |
43 | class OSPFAreaFilterForm(NetBoxModelFilterSetForm):
44 | model = OSPFArea
45 | fieldsets = (
46 | FieldSet('q', 'filter_id', 'tag'),
47 | )
48 | tag = TagFilterField(model)
49 |
50 |
51 | class OSPFInterfaceFilterForm(NetBoxModelFilterSetForm):
52 | model = OSPFInterface
53 | fieldsets = (
54 | FieldSet('q', 'filter_id', 'tag'),
55 | FieldSet('instance', 'area', name=_('OSPF')),
56 | FieldSet('interface', name=_('Device')),
57 | FieldSet('priority', 'bfd', 'authentication', name=_('Attributes'))
58 | )
59 | device_id = DynamicModelMultipleChoiceField(
60 | queryset=Device.objects.all(),
61 | required=False,
62 | selector=True,
63 | label=_('Device'),
64 | )
65 | vrf_id = DynamicModelMultipleChoiceField(
66 | queryset=VRF.objects.all(),
67 | required=False,
68 | selector=True,
69 | label=_('VRF'),
70 | )
71 | instance_id = DynamicModelMultipleChoiceField(
72 | queryset=OSPFInstance.objects.all(),
73 | required=False,
74 | selector=True,
75 | label=_('Instance'),
76 | )
77 | area_id = DynamicModelMultipleChoiceField(
78 | queryset=OSPFArea.objects.all(),
79 | required=False,
80 | selector=True,
81 | label=_('Area'),
82 | )
83 | interface_id = DynamicModelMultipleChoiceField(
84 | queryset=Interface.objects.all(),
85 | required=False,
86 | selector=True,
87 | label=_('Interface'),
88 | )
89 | passive = forms.NullBooleanField(
90 | required=False,
91 | label='Passive Interface',
92 | widget=forms.Select(
93 | choices=BOOLEAN_WITH_BLANK_CHOICES
94 | )
95 | )
96 | bfd = forms.NullBooleanField(
97 | required=False,
98 | label='BFD Enabled',
99 | widget=forms.Select(
100 | choices=BOOLEAN_WITH_BLANK_CHOICES
101 | )
102 | )
103 | priority = forms.IntegerField(
104 | required=False
105 | )
106 | authentication = forms.ChoiceField(
107 | choices=add_blank_choice(AuthenticationChoices),
108 | required=False
109 | )
110 | tag = TagFilterField(model)
111 |
--------------------------------------------------------------------------------
/netbox_routing/forms/filtersets/static.py:
--------------------------------------------------------------------------------
1 | from ipam.models import VRF
2 | from netbox.forms import NetBoxModelFilterSetForm
3 | from netbox_routing.models import StaticRoute
4 | from utilities.forms.fields import DynamicModelMultipleChoiceField, TagFilterField
5 | from django.utils.translation import gettext as _
6 |
7 | from utilities.forms.rendering import FieldSet
8 |
9 |
10 | class StaticRouteFilterForm(NetBoxModelFilterSetForm):
11 | model = StaticRoute
12 | fieldsets = (
13 | FieldSet('q', 'filter_id', 'tag', 'vrf'),
14 | )
15 | vrf = DynamicModelMultipleChoiceField(
16 | queryset=VRF.objects.all(),
17 | required=False,
18 | selector=True,
19 | label=_('VRF'),
20 | )
21 | tag = TagFilterField(model)
--------------------------------------------------------------------------------
/netbox_routing/forms/objects.py:
--------------------------------------------------------------------------------
1 | from netbox.forms import NetBoxModelForm
2 | from netbox_routing.models import PrefixList, PrefixListEntry, RouteMap, RouteMapEntry
3 |
4 |
5 | class PrefixListForm(NetBoxModelForm):
6 |
7 | class Meta:
8 | model = PrefixList
9 | fields = ('name', 'description', 'comments', )
10 |
11 |
12 | class PrefixListEntryForm(NetBoxModelForm):
13 |
14 | class Meta:
15 | model = PrefixListEntry
16 | fields = ('prefix_list', 'sequence', 'type', 'prefix', 'le', 'ge', 'description', 'comments', )
17 |
18 |
19 | class RouteMapForm(NetBoxModelForm):
20 |
21 | class Meta:
22 | model = RouteMap
23 | fields = ('name', 'description', 'comments', )
24 |
25 |
26 | class RouteMapEntryForm(NetBoxModelForm):
27 |
28 | class Meta:
29 | model = RouteMapEntry
30 | fields = ('route_map', 'sequence', 'type', 'description', 'comments', )
31 |
--------------------------------------------------------------------------------
/netbox_routing/forms/ospf.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 | from django.core.exceptions import ValidationError
3 | from django.utils.translation import gettext as _
4 |
5 | from dcim.models import Interface, Device
6 | from ipam.models import VRF
7 | from netbox.forms import NetBoxModelForm
8 | from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES
9 | from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField, CommentField
10 |
11 | from netbox_routing.models import OSPFArea, OSPFInstance, OSPFInterface
12 |
13 |
14 | __all__ = (
15 | 'OSPFAreaForm',
16 | 'OSPFInstanceForm',
17 | 'OSPFInterfaceForm',
18 | )
19 |
20 |
21 | class OSPFInstanceForm(NetBoxModelForm):
22 | device = DynamicModelChoiceField(
23 | queryset=Device.objects.all(),
24 | required=True,
25 | selector=True,
26 | label=_('Device'),
27 | )
28 | vrf = DynamicModelChoiceField(
29 | queryset=VRF.objects.all(),
30 | required=False,
31 | selector=True,
32 | label=_('VRF'),
33 | )
34 | comments = CommentField()
35 |
36 | class Meta:
37 | model = OSPFInstance
38 | fields = ('name', 'router_id', 'process_id', 'device', 'vrf', 'description', 'comments', )
39 |
40 |
41 | class OSPFAreaForm(NetBoxModelForm):
42 | comments = CommentField()
43 |
44 | class Meta:
45 | model = OSPFArea
46 | fields = ('area_id', 'description', 'comments', )
47 |
48 |
49 | class OSPFInterfaceForm(NetBoxModelForm):
50 | device = DynamicModelChoiceField(
51 | queryset=Device.objects.all(),
52 | required=False,
53 | selector=True,
54 | label=_('Device'),
55 | )
56 | instance = DynamicModelChoiceField(
57 | queryset=OSPFInstance.objects.all(),
58 | required=True,
59 | selector=True,
60 | label=_('Instance'),
61 | query_params={
62 | 'device_id': '$device',
63 | }
64 | )
65 | area = DynamicModelChoiceField(
66 | queryset=OSPFArea.objects.all(),
67 | required=True,
68 | selector=True,
69 | label=_('Area'),
70 | )
71 | interface = DynamicModelChoiceField(
72 | queryset=Interface.objects.all(),
73 | required=True,
74 | selector=True,
75 | label=_('Interface'),
76 | query_params={
77 | 'device_id': '$device',
78 | }
79 | )
80 | comments = CommentField()
81 |
82 |
83 | class Meta:
84 | model = OSPFInterface
85 | fields = (
86 | 'device', 'instance', 'area', 'interface', 'passive', 'priority', 'bfd', 'authentication', 'passphrase',
87 | 'description', 'comments',
88 | )
89 |
90 | widgets = {
91 | 'bfd': forms.Select(choices=BOOLEAN_WITH_BLANK_CHOICES),
92 | 'passive': forms.Select(choices=BOOLEAN_WITH_BLANK_CHOICES),
93 | }
94 |
95 | def __init__(self, *args, **kwargs):
96 | super().__init__(*args, **kwargs)
97 | if self.instance.pk:
98 | self.initial['device'] = self.instance.interface.device.pk
99 |
100 | def clean(self):
101 | super().clean()
102 | if self.cleaned_data.get('instance') and self.cleaned_data.get('interface'):
103 | if self.cleaned_data.get('instance').device != self.cleaned_data.get('interface').device:
104 | raise ValidationError(
105 | {
106 | 'instance': _('OSPF Instance Device and Interface Device must match'),
107 | 'interface': _('OSPF Instance Device and Interface Device must match')
108 | }
109 | )
110 |
--------------------------------------------------------------------------------
/netbox_routing/forms/static.py:
--------------------------------------------------------------------------------
1 | from dcim.models import Device
2 | from ipam.models import VRF
3 | from netbox.forms import NetBoxModelForm
4 | from netbox_routing.models import StaticRoute
5 | from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField, CommentField
6 |
7 |
8 | class StaticRouteForm(NetBoxModelForm):
9 | devices = DynamicModelMultipleChoiceField(
10 | queryset=Device.objects.all()
11 | )
12 | vrf = DynamicModelChoiceField(
13 | queryset=VRF.objects.all(),
14 | required=False,
15 | label='VRF'
16 | )
17 | comments = CommentField()
18 |
19 | class Meta:
20 | model = StaticRoute
21 | fields = ('devices', 'vrf', 'prefix', 'next_hop', 'name', 'metric', 'permanent', 'description', 'comments', )
22 |
23 | def __init__(self, data=None, instance=None, *args, **kwargs):
24 | super().__init__(data=data, instance=instance, *args, **kwargs)
25 |
26 | if self.instance and self.instance.pk is not None:
27 | self.fields['devices'].initial = self.instance.devices.all().values_list('id', flat=True)
28 |
29 | def save(self, *args, **kwargs):
30 | instance = super().save(*args, **kwargs)
31 | instance.devices.set(self.cleaned_data['devices'])
32 | return instance
33 |
--------------------------------------------------------------------------------
/netbox_routing/graphql/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanSheps/netbox-routing/56ec2104ef304157cb27e3ec75d16597031d08c6/netbox_routing/graphql/__init__.py
--------------------------------------------------------------------------------
/netbox_routing/graphql/filter_mixins.py:
--------------------------------------------------------------------------------
1 | from typing import Annotated
2 |
3 | import strawberry
4 | import strawberry_django
5 | from strawberry import ID
6 |
7 | from core.graphql.filter_mixins import BaseObjectTypeFilterMixin
8 |
9 |
10 | class DeviceMixin(BaseObjectTypeFilterMixin):
11 | device: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
12 | device_id: ID | None = strawberry_django.filter_field()
13 |
14 |
15 | class InterfaceMixin(DeviceMixin, BaseObjectTypeFilterMixin):
16 | device: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
17 | device_id: ID | None = strawberry_django.filter_field()
18 | interface: Annotated['InterfaceFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
19 | interface_id: ID | None = strawberry_django.filter_field()
20 |
21 |
22 | class VRFMixin:
23 | vrf: Annotated['VRFFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field()
24 |
25 |
26 | class NetworkPrefixMixin(BaseObjectTypeFilterMixin):
27 | network: Annotated['PrefixFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field()
28 | network_id: ID | None = strawberry_django.filter_field()
29 |
--------------------------------------------------------------------------------
/netbox_routing/graphql/filters.py:
--------------------------------------------------------------------------------
1 | from typing import Annotated
2 |
3 | import strawberry
4 | import strawberry_django
5 | from strawberry import ID
6 | from strawberry_django import FilterLookup
7 |
8 | from netbox.graphql.filter_mixins import BaseObjectTypeFilterMixin
9 |
10 | from netbox_routing import filtersets, models
11 | from netbox_routing.graphql.filter_mixins import DeviceMixin, InterfaceMixin, VRFMixin, NetworkPrefixMixin
12 |
13 | __all__ = (
14 | 'StaticRouteFilter',
15 | 'OSPFInstanceFilter',
16 | 'OSPFAreaFilter',
17 | 'OSPFInterfaceFilter',
18 | 'EIGRPRouterFilter',
19 | 'EIGRPAddressFamilyFilter',
20 | 'EIGRPNetworkFilter',
21 | 'EIGRPInterfaceFilter',
22 | )
23 |
24 |
25 | @strawberry_django.filter(models.StaticRoute, lookups=True)
26 | class StaticRouteFilter(VRFMixin, DeviceMixin, BaseObjectTypeFilterMixin):
27 | prefix: FilterLookup[str] | None = strawberry_django.filter_field()
28 | next_hop: FilterLookup[str] | None = strawberry_django.filter_field()
29 |
30 |
31 | @strawberry_django.filter(models.OSPFInstance, lookups=True)
32 | class OSPFInstanceFilter(VRFMixin, DeviceMixin, BaseObjectTypeFilterMixin):
33 | router_id: FilterLookup[str] | None = strawberry_django.filter_field()
34 |
35 |
36 | @strawberry_django.filter(models.OSPFArea, lookups=True)
37 | class OSPFAreaFilter(BaseObjectTypeFilterMixin):
38 | area_id: FilterLookup[str] | None = strawberry_django.filter_field()
39 |
40 |
41 | @strawberry_django.filter(models.OSPFInterface, lookups=True)
42 | class OSPFInterfaceFilter(InterfaceMixin, VRFMixin, BaseObjectTypeFilterMixin):
43 | instance: Annotated['OSPFInstanceFilter', strawberry.lazy('netbox_routing.graphql.filters')] | None = strawberry_django.filter_field()
44 | instance_id: ID | None = strawberry_django.filter_field()
45 | area: Annotated['OSPFAreaFilter', strawberry.lazy('netbox_routing.graphql.filters')] | None = strawberry_django.filter_field()
46 | area_id: ID | None = strawberry_django.filter_field()
47 |
48 |
49 | @strawberry_django.filter(models.EIGRPRouter, lookups=True)
50 | class EIGRPRouterFilter(DeviceMixin, BaseObjectTypeFilterMixin):
51 | rid: FilterLookup[str] | None = strawberry_django.filter_field()
52 |
53 |
54 | @strawberry_django.filter(models.EIGRPAddressFamily, lookups=True)
55 | class EIGRPAddressFamilyFilter(BaseObjectTypeFilterMixin):
56 | router: Annotated['EIGRPRouterFilter', strawberry.lazy('netbox_routing.graphql.filters')] | None = strawberry_django.filter_field()
57 | router_id: ID | None = strawberry_django.filter_field()
58 | rid: FilterLookup[str] | None = strawberry_django.filter_field()
59 |
60 |
61 | @strawberry_django.filter(models.EIGRPNetwork, lookups=True)
62 | class EIGRPNetworkFilter(NetworkPrefixMixin, BaseObjectTypeFilterMixin):
63 | router: Annotated['EIGRPRouterFilter', strawberry.lazy('netbox_routing.graphql.filters')] | None = strawberry_django.filter_field()
64 | router_id: ID | None = strawberry_django.filter_field()
65 | address_family: Annotated['EIGRPAddressFamilyFilter', strawberry.lazy('netbox_routing.graphql.filters')] | None = strawberry_django.filter_field()
66 | address_family_id: ID | None = strawberry_django.filter_field()
67 |
68 |
69 | @strawberry_django.filter(models.EIGRPInterface, lookups=True)
70 | class EIGRPInterfaceFilter(InterfaceMixin, BaseObjectTypeFilterMixin):
71 | router: Annotated['EIGRPRouterFilter', strawberry.lazy('netbox_routing.graphql.filters')] | None = strawberry_django.filter_field()
72 | router_id: ID | None = strawberry_django.filter_field()
73 | address_family: Annotated['EIGRPAddressFamilyFilter', strawberry.lazy('netbox_routing.graphql.filters')] | None = strawberry_django.filter_field()
74 | address_family_id: ID | None = strawberry_django.filter_field()
75 |
76 |
--------------------------------------------------------------------------------
/netbox_routing/graphql/schema.py:
--------------------------------------------------------------------------------
1 | from typing import List
2 |
3 | import strawberry
4 | import strawberry_django
5 |
6 | from .types import *
7 |
8 |
9 | @strawberry.type(name="Query")
10 | class StaticRouteQuery:
11 | static_route: StaticRouteType = strawberry_django.field()
12 | static_route_list: List[StaticRouteType] = strawberry_django.field()
13 |
14 |
15 | @strawberry.type(name="Query")
16 | class OSPFQuery:
17 | ospf_instance: OSPFInstanceType = strawberry_django.field()
18 | ospf_instance_list: List[OSPFInstanceType] = strawberry_django.field()
19 |
20 | ospf_area: OSPFAreaType = strawberry_django.field()
21 | ospf_area_list: List[OSPFAreaType] = strawberry_django.field()
22 |
23 | ospf_interface: OSPFInterfaceType = strawberry_django.field()
24 | ospf_interface_list: List[OSPFInterfaceType] = strawberry_django.field()
25 |
26 |
27 | @strawberry.type(name="Query")
28 | class EIGRPQuery:
29 | eigrp_router: EIGRPRouterType = strawberry_django.field()
30 | eigrp_router_list: List[EIGRPRouterType] = strawberry_django.field()
31 |
32 | eigrp_address_family: EIGRPAddressFamilyType = strawberry_django.field()
33 | eigrp_address_family_list: List[EIGRPAddressFamilyType] = strawberry_django.field()
34 |
35 | eigrp_network: EIGRPNetworkType = strawberry_django.field()
36 | eigrp_network_list: List[EIGRPNetworkType] = strawberry_django.field()
37 |
38 | eigrp_interface: EIGRPInterfaceType = strawberry_django.field()
39 | eigrp_interface_list: List[EIGRPInterfaceType] = strawberry_django.field()
40 |
41 |
42 | schema = [
43 | StaticRouteQuery,
44 | OSPFQuery,
45 | EIGRPQuery,
46 | ]
47 |
--------------------------------------------------------------------------------
/netbox_routing/graphql/types.py:
--------------------------------------------------------------------------------
1 | from typing import Annotated, List, Union
2 |
3 | import strawberry
4 | import strawberry_django
5 |
6 | from netbox.graphql.types import NetBoxObjectType
7 | from .filters import *
8 |
9 | from netbox_routing import models
10 |
11 | __all__ = (
12 | 'StaticRouteType',
13 |
14 | 'OSPFInstanceType',
15 | 'OSPFAreaType',
16 | 'OSPFInterfaceType',
17 |
18 | 'EIGRPRouterType',
19 | 'EIGRPAddressFamilyType',
20 | 'EIGRPNetworkType',
21 | 'EIGRPInterfaceType',
22 | )
23 |
24 |
25 | @strawberry_django.type(
26 | models.StaticRoute,
27 | fields='__all__',
28 | filters=StaticRouteFilter
29 | )
30 | class StaticRouteType(NetBoxObjectType):
31 |
32 | name: str
33 | devices: List[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]] | None
34 | vrf: Annotated["VRFType", strawberry.lazy('ipam.graphql.types')] | None
35 | prefix: str | None
36 | next_hop: str | None
37 | metric: int | None
38 | permanent: bool | None
39 |
40 |
41 | @strawberry_django.type(
42 | models.OSPFInstance,
43 | fields='__all__',
44 | filters=OSPFInstanceFilter
45 | )
46 | class OSPFInstanceType(NetBoxObjectType):
47 |
48 | name: str
49 | device: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]
50 | vrf: Annotated["VRFType", strawberry.lazy('ipam.graphql.types')] | None
51 | router_id: str
52 | process_id: str
53 |
54 |
55 | @strawberry_django.type(
56 | models.OSPFArea,
57 | fields='__all__',
58 | filters=OSPFAreaFilter
59 | )
60 | class OSPFAreaType(NetBoxObjectType):
61 |
62 | area_id: str
63 |
64 |
65 | @strawberry_django.type(
66 | models.OSPFInterface,
67 | fields='__all__',
68 | filters=OSPFInterfaceFilter
69 | )
70 | class OSPFInterfaceType(NetBoxObjectType):
71 |
72 | instance: Annotated["OSPFInstanceType", strawberry.lazy('netbox_routing.graphql.types')]
73 | area: Annotated["OSPFAreaType", strawberry.lazy('netbox_routing.graphql.types')]
74 | interface: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]
75 | passive: bool | None
76 | priority: str | None
77 | bfd: bool | None
78 | authentication: str | None
79 | passphrase: str | None
80 |
81 |
82 | @strawberry_django.type(
83 | models.EIGRPRouter,
84 | fields='__all__',
85 | filters=EIGRPRouterFilter
86 | )
87 | class EIGRPRouterType(NetBoxObjectType):
88 |
89 | device: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]
90 | rid: str
91 | type: str
92 | name: str
93 | pid: str
94 |
95 |
96 | @strawberry_django.type(
97 | models.EIGRPAddressFamily,
98 | fields='__all__',
99 | filters=EIGRPAddressFamilyFilter
100 | )
101 | class EIGRPAddressFamilyType(NetBoxObjectType):
102 |
103 | router: Annotated["EIGRPRouterType", strawberry.lazy('netbox_routing.graphql.types')]
104 | rid: str
105 |
106 |
107 | @strawberry_django.type(
108 | models.EIGRPNetwork,
109 | fields='__all__',
110 | filters=EIGRPNetworkFilter
111 | )
112 | class EIGRPNetworkType(NetBoxObjectType):
113 |
114 | router: Annotated["EIGRPRouterType", strawberry.lazy('netbox_routing.graphql.types')]
115 | address_family: Annotated["EIGRPAddressFamilyType", strawberry.lazy('netbox_routing.graphql.types')] | None
116 | network: Annotated["PrefixType", strawberry.lazy('ipam.graphql.types')]
117 |
118 |
119 | @strawberry_django.type(
120 | models.EIGRPInterface,
121 | fields='__all__',
122 | filters=EIGRPInterfaceFilter
123 | )
124 | class EIGRPInterfaceType(NetBoxObjectType):
125 |
126 | router: Annotated["EIGRPRouterType", strawberry.lazy('netbox_routing.graphql.types')]
127 | address_family: Annotated["EIGRPAddressFamilyType", strawberry.lazy('netbox_routing.graphql.types')] | None
128 | interface: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]
129 | passive: str | None
130 | bfd: bool | None
131 | authentication: str | None
132 | passphrase: str | None
133 |
134 |
--------------------------------------------------------------------------------
/netbox_routing/helpers/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanSheps/netbox-routing/56ec2104ef304157cb27e3ec75d16597031d08c6/netbox_routing/helpers/__init__.py
--------------------------------------------------------------------------------
/netbox_routing/migrations/0002_netboxmodel_updates.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.7 on 2023-05-18 13:43
2 |
3 | from django.db import migrations, models
4 | import utilities.json
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('netbox_routing', '0001_initial'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='prefixlist',
16 | name='custom_field_data',
17 | field=models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder),
18 | ),
19 | migrations.AlterField(
20 | model_name='prefixlistentry',
21 | name='custom_field_data',
22 | field=models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder),
23 | ),
24 | migrations.AlterField(
25 | model_name='routemap',
26 | name='custom_field_data',
27 | field=models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder),
28 | ),
29 | migrations.AlterField(
30 | model_name='routemapentry',
31 | name='custom_field_data',
32 | field=models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder),
33 | ),
34 | migrations.AlterField(
35 | model_name='staticroute',
36 | name='custom_field_data',
37 | field=models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder),
38 | ),
39 | ]
40 |
--------------------------------------------------------------------------------
/netbox_routing/migrations/0003_model_ordering_and_constraints.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.7 on 2023-05-18 13:45
2 |
3 | from django.db import migrations, models
4 | import django.db.models.functions.text
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('netbox_routing', '0002_netboxmodel_updates'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterModelOptions(
15 | name='prefixlist',
16 | options={'ordering': ['name']},
17 | ),
18 | migrations.AlterModelOptions(
19 | name='prefixlistentry',
20 | options={'ordering': ['prefix_list', 'sequence']},
21 | ),
22 | migrations.AlterModelOptions(
23 | name='routemap',
24 | options={'ordering': ['name']},
25 | ),
26 | migrations.AlterModelOptions(
27 | name='routemapentry',
28 | options={'ordering': ['route_map', 'sequence']},
29 | ),
30 | migrations.AlterModelOptions(
31 | name='staticroute',
32 | options={'ordering': ['vrf', 'prefix', 'metric']},
33 | ),
34 | migrations.AddConstraint(
35 | model_name='prefixlist',
36 | constraint=models.UniqueConstraint(django.db.models.functions.text.Lower('name'), name='netbox_routing_prefixlist_unique_name', violation_error_message='Name must be unique.'),
37 | ),
38 | migrations.AddConstraint(
39 | model_name='prefixlistentry',
40 | constraint=models.UniqueConstraint(models.F('prefix_list'), models.F('sequence'), name='netbox_routing_prefixlistentry_unique_prefixlist_sequence', violation_error_message='Prefix List sequence must be unique.'),
41 | ),
42 | migrations.AddConstraint(
43 | model_name='routemap',
44 | constraint=models.UniqueConstraint(django.db.models.functions.text.Lower('name'), name='netbox_routing_routemap_unique_name', violation_error_message='Name must be unique.'),
45 | ),
46 | migrations.AddConstraint(
47 | model_name='routemapentry',
48 | constraint=models.UniqueConstraint(models.F('route_map'), models.F('sequence'), name='netbox_routing_routemapentry_unique_routemap_sequence', violation_error_message='Route Map sequence must be unique.'),
49 | ),
50 | migrations.AddConstraint(
51 | model_name='staticroute',
52 | constraint=models.UniqueConstraint(models.F('vrf'), models.F('prefix'), models.F('next_hop'), name='netbox_routing_staticroute_unique_vrf_prefix_nexthop', violation_error_message='VRF, Prefix and Next Hop must be unique.'),
53 | ),
54 | ]
55 |
--------------------------------------------------------------------------------
/netbox_routing/migrations/0004_alter_prefixlistentry_ge_alter_prefixlistentry_le.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.7 on 2023-05-18 13:50
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('netbox_routing', '0003_model_ordering_and_constraints'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='prefixlistentry',
15 | name='ge',
16 | field=models.PositiveSmallIntegerField(blank=True, null=True),
17 | ),
18 | migrations.AlterField(
19 | model_name='prefixlistentry',
20 | name='le',
21 | field=models.PositiveSmallIntegerField(blank=True, null=True),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/netbox_routing/migrations/0005_ospf.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.10 on 2023-08-09 19:07
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 | import netbox_routing.fields.ip
6 | import taggit.managers
7 | import utilities.json
8 |
9 |
10 | class Migration(migrations.Migration):
11 |
12 | dependencies = [
13 | ('dcim', '0172_larger_power_draw_values'),
14 | ('extras', '0092_delete_jobresult'),
15 | ('netbox_routing', '0004_alter_prefixlistentry_ge_alter_prefixlistentry_le'),
16 | ]
17 |
18 | operations = [
19 | migrations.CreateModel(
20 | name='OSPFArea',
21 | fields=[
22 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
23 | ('created', models.DateTimeField(auto_now_add=True, null=True)),
24 | ('last_updated', models.DateTimeField(auto_now=True, null=True)),
25 | ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
26 | ('area_id', models.CharField(max_length=100)),
27 | ('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
28 | ],
29 | options={
30 | 'verbose_name': 'OSPF Area',
31 | },
32 | ),
33 | migrations.CreateModel(
34 | name='OSPFInstance',
35 | fields=[
36 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
37 | ('created', models.DateTimeField(auto_now_add=True, null=True)),
38 | ('last_updated', models.DateTimeField(auto_now=True, null=True)),
39 | ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
40 | ('name', models.CharField(max_length=100)),
41 | ('router_id', netbox_routing.fields.ip.IPAddressField()),
42 | ('process_id', models.IntegerField()),
43 | ('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ospf_instances', to='dcim.device')),
44 | ('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
45 | ],
46 | options={
47 | 'verbose_name': 'OSPF Instance',
48 | },
49 | ),
50 | migrations.CreateModel(
51 | name='OSPFInterface',
52 | fields=[
53 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
54 | ('created', models.DateTimeField(auto_now_add=True, null=True)),
55 | ('last_updated', models.DateTimeField(auto_now=True, null=True)),
56 | ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
57 | ('priority', models.IntegerField(blank=True, null=True)),
58 | ('bfd', models.BooleanField(blank=True, null=True)),
59 | ('authentication', models.CharField(blank=True, max_length=50, null=True)),
60 | ('passphrase', models.CharField(blank=True, max_length=200, null=True)),
61 | ('area', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='interfaces', to='netbox_routing.ospfarea')),
62 | ('instance', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='interfaces', to='netbox_routing.ospfinstance')),
63 | ('interface', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ospf_interfaces', to='dcim.interface')),
64 | ('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
65 | ],
66 | options={
67 | 'verbose_name': 'OSPF Interface',
68 | 'ordering': ('instance', 'area', 'interface'),
69 | },
70 | ),
71 | migrations.AddConstraint(
72 | model_name='ospfinterface',
73 | constraint=models.UniqueConstraint(fields=('interface',), name='netbox_routing_ospfinterface_unique_interface'),
74 | ),
75 | ]
76 |
--------------------------------------------------------------------------------
/netbox_routing/migrations/0007_bgpsessiontemplate_asn_bgpsessiontemplate_bfd_and_more.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.2.4 on 2024-05-07 15:07
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('ipam', '0069_gfk_indexes'),
11 | ('netbox_routing', '0006_bgp'),
12 | ]
13 |
14 | operations = [
15 | migrations.AddField(
16 | model_name='bgpsessiontemplate',
17 | name='asn',
18 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='session_templates', to='ipam.asn'),
19 | ),
20 | migrations.AddField(
21 | model_name='bgpsessiontemplate',
22 | name='bfd',
23 | field=models.CharField(blank=True, max_length=50, null=True),
24 | ),
25 | migrations.AddField(
26 | model_name='bgpsessiontemplate',
27 | name='parent',
28 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='children', to='netbox_routing.bgpsessiontemplate'),
29 | ),
30 | migrations.AddField(
31 | model_name='bgpsessiontemplate',
32 | name='password',
33 | field=models.CharField(blank=True, max_length=255, null=True),
34 | ),
35 | ]
36 |
--------------------------------------------------------------------------------
/netbox_routing/migrations/0009_alter_staticroute_metric_alter_staticroute_permanent.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 5.0.8 on 2024-09-16 13:27
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('netbox_routing', '0008_convert_to_primarymodel'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='staticroute',
15 | name='metric',
16 | field=models.PositiveSmallIntegerField(blank=True, default=1),
17 | ),
18 | migrations.AlterField(
19 | model_name='staticroute',
20 | name='permanent',
21 | field=models.BooleanField(blank=True, default=False, null=True),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/netbox_routing/migrations/0011_osfp_passive_interface.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 5.0.8 on 2024-09-28 03:35
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('netbox_routing', '0010_eigrp'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='ospfinterface',
15 | name='passive',
16 | field=models.BooleanField(blank=True, null=True),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/netbox_routing/migrations/0012_osfp_instance_vrf.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 5.0.8 on 2024-09-28 04:42
2 |
3 | import django.db.models.deletion
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('ipam', '0070_vlangroup_vlan_id_ranges'),
11 | ('netbox_routing', '0011_osfp_passive_interface'),
12 | ]
13 |
14 | operations = [
15 | migrations.AddField(
16 | model_name='ospfinstance',
17 | name='vrf',
18 | field=models.ForeignKey(
19 | blank=True,
20 | null=True,
21 | on_delete=django.db.models.deletion.CASCADE,
22 | related_name='ospf_instances',
23 | to='ipam.vrf',
24 | ),
25 | ),
26 | ]
27 |
--------------------------------------------------------------------------------
/netbox_routing/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanSheps/netbox-routing/56ec2104ef304157cb27e3ec75d16597031d08c6/netbox_routing/migrations/__init__.py
--------------------------------------------------------------------------------
/netbox_routing/models/__init__.py:
--------------------------------------------------------------------------------
1 | from .static import StaticRoute
2 | from .ospf import OSPFArea, OSPFInstance, OSPFInterface
3 | from .objects import PrefixList, PrefixListEntry, RouteMap, RouteMapEntry
4 | from .bgp import BGPRouter, BGPScope, BGPAddressFamily, BGPSetting
5 | from .eigrp import *
6 |
7 | __all__ = (
8 | 'StaticRoute',
9 |
10 | 'OSPFArea',
11 | 'OSPFInstance',
12 | 'OSPFInterface',
13 |
14 | 'EIGRPRouter',
15 | 'EIGRPAddressFamily',
16 | 'EIGRPNetwork',
17 | 'EIGRPInterface',
18 |
19 | 'PrefixList',
20 | 'PrefixListEntry',
21 | 'RouteMap',
22 | 'RouteMapEntry',
23 |
24 | # Not fully implemented
25 | 'BGPRouter',
26 | 'BGPScope',
27 | 'BGPAddressFamily',
28 | 'BGPSetting'
29 | )
30 |
--------------------------------------------------------------------------------
/netbox_routing/models/mixins.py:
--------------------------------------------------------------------------------
1 | class RoutingInstanceMixin:
2 | pass
3 |
--------------------------------------------------------------------------------
/netbox_routing/models/ospf.py:
--------------------------------------------------------------------------------
1 | import netaddr
2 | from django.core.exceptions import ValidationError
3 | from django.db import models
4 | from django.urls import reverse
5 | from django.utils.translation import gettext as _
6 |
7 | from netbox.models import PrimaryModel
8 |
9 | from netbox_routing import choices
10 | from netbox_routing.fields.ip import IPAddressField
11 |
12 |
13 | __all__ = (
14 | 'OSPFInstance',
15 | 'OSPFArea',
16 | 'OSPFInterface',
17 | )
18 |
19 |
20 | class OSPFInstance(PrimaryModel):
21 | name = models.CharField(max_length=100)
22 | router_id = IPAddressField(verbose_name=_('Router ID'))
23 | process_id = models.IntegerField(verbose_name=_('Process ID'))
24 | device = models.ForeignKey(
25 | to='dcim.Device',
26 | related_name='ospf_instances',
27 | on_delete=models.CASCADE,
28 | blank=False,
29 | null=False
30 | )
31 | vrf = models.ForeignKey(
32 | verbose_name=_('VRF'),
33 | to='ipam.VRF',
34 | related_name='ospf_instances',
35 | on_delete=models.CASCADE,
36 | blank=True,
37 | null=True
38 | )
39 |
40 | clone_fields = ('device',)
41 | prerequisite_models = (
42 | 'dcim.Device',
43 | )
44 |
45 | class Meta:
46 | verbose_name = 'OSPF Instance'
47 |
48 | def __str__(self):
49 | return f'{self.name} ({self.router_id})'
50 |
51 | def get_absolute_url(self):
52 | return reverse('plugins:netbox_routing:ospfinstance', args=[self.pk])
53 |
54 |
55 | class OSPFArea(PrimaryModel):
56 | area_id = models.CharField(max_length=100, verbose_name='Area ID')
57 |
58 | prerequisite_models = ()
59 | clone_fields = ()
60 | class Meta:
61 | verbose_name = 'OSPF Area'
62 |
63 | def __str__(self):
64 | return f'{self.area_id}'
65 |
66 | def get_absolute_url(self):
67 | return reverse('plugins:netbox_routing:ospfarea', args=[self.pk])
68 |
69 | def clean(self):
70 | super().clean()
71 | area_id = self.area_id
72 | try:
73 | int(area_id)
74 | except ValueError:
75 | try:
76 | str(netaddr.IPAddress(area_id))
77 | except netaddr.core.AddrFormatError:
78 | raise ValidationError({'area_id': ['This field must be an integer or a valid net address']})
79 |
80 |
81 | class OSPFInterface(PrimaryModel):
82 | instance = models.ForeignKey(
83 | to='netbox_routing.OSPFInstance',
84 | related_name='interfaces',
85 | on_delete=models.PROTECT,
86 | blank=False,
87 | null=False,
88 | )
89 | area = models.ForeignKey(
90 | to='netbox_routing.OSPFArea',
91 | on_delete=models.CASCADE,
92 | related_name='interfaces',
93 | blank=False,
94 | null=False
95 | )
96 | interface = models.ForeignKey(
97 | to='dcim.Interface',
98 | related_name='ospf_interfaces',
99 | on_delete=models.CASCADE,
100 | blank=False,
101 | null=False
102 | )
103 | passive = models.BooleanField(verbose_name='Passive', blank=True, null=True)
104 | priority = models.IntegerField(blank=True, null=True)
105 | bfd = models.BooleanField(blank=True, null=True, verbose_name='BFD')
106 | authentication = models.CharField(
107 | max_length=50,
108 | choices=choices.AuthenticationChoices,
109 | blank=True,
110 | null=True
111 | )
112 | passphrase = models.CharField(
113 | max_length=200,
114 | blank=True,
115 | null=True
116 | )
117 |
118 | clone_fields = ('instance', 'area', 'priority', 'bfd', 'authentication', 'passphrase')
119 | prerequisite_models = (
120 | 'netbox_routing.OSPFInstance', 'netbox_routing.OSPFArea', 'dcim.Interface',
121 | )
122 |
123 | class Meta:
124 | verbose_name = 'OSPF Interface'
125 | ordering = ('instance', 'area', 'interface') # Name may be non-unique
126 | constraints = (
127 | models.UniqueConstraint(
128 | fields=('interface', ),
129 | name='%(app_label)s_%(class)s_unique_interface'
130 | ),
131 | )
132 |
133 | def __str__(self):
134 | return f'{self.interface.name}'
135 |
136 | def get_absolute_url(self):
137 | return reverse('plugins:netbox_routing:ospfinterface', args=[self.pk])
138 |
139 |
--------------------------------------------------------------------------------
/netbox_routing/models/static.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django.db.models import CheckConstraint, Q
3 | from django.urls import reverse
4 |
5 | from ipam.fields import IPNetworkField
6 | from netbox.models import PrimaryModel
7 | from netbox_routing.fields.ip import IPAddressField
8 |
9 |
10 | __all__ = (
11 | 'StaticRoute'
12 | )
13 |
14 |
15 | class StaticRoute(PrimaryModel):
16 | devices = models.ManyToManyField(
17 | to='dcim.Device',
18 | related_name='static_routes'
19 | )
20 | vrf = models.ForeignKey(
21 | to='ipam.VRF',
22 | on_delete=models.PROTECT,
23 | related_name='staticroutes',
24 | blank=True,
25 | null=True,
26 | verbose_name='VRF'
27 | )
28 | prefix = IPNetworkField(help_text='IPv4 or IPv6 network with mask')
29 | next_hop = IPAddressField()
30 | name = models.CharField(
31 | max_length=50,
32 | verbose_name='Name',
33 | blank=True,
34 | null=True,
35 | help_text='Optional name for this static route'
36 | )
37 | metric = models.PositiveSmallIntegerField(
38 | verbose_name='Metric',
39 | blank=True,
40 | default=1,
41 | )
42 | permanent = models.BooleanField(default=False, blank=True, null=True,)
43 |
44 | clone_fields = (
45 | 'vrf', 'metric', 'permanent'
46 | )
47 | prerequisite_models = (
48 | 'dcim.Device',
49 | 'ipam.VRF',
50 | )
51 |
52 | class Meta:
53 | ordering = ['vrf', 'prefix', 'metric']
54 | constraints = (
55 | CheckConstraint(check=Q(Q(metric__lte=255) & Q(metric__gte=0)), name='metric_gte_lte'),
56 | models.UniqueConstraint(
57 | 'vrf', 'prefix', 'next_hop',
58 | name='%(app_label)s_%(class)s_unique_vrf_prefix_nexthop',
59 | violation_error_message="VRF, Prefix and Next Hop must be unique."
60 | ),
61 | )
62 |
63 | def __str__(self):
64 | if self.vrf is None:
65 | return f'{self.prefix} NH {self.next_hop}'
66 | return f'{self.prefix} VRF {self.vrf} NH {self.next_hop}'
67 |
68 | def get_absolute_url(self):
69 | return reverse('plugins:netbox_routing:staticroute', args=[self.pk])
70 |
--------------------------------------------------------------------------------
/netbox_routing/navigation/__init__.py:
--------------------------------------------------------------------------------
1 | from netbox.plugins import PluginMenu
2 |
3 | from .bgp import MENUITEMS as BGP_MENU
4 | from .objects import MENUITEMS as OBJECT_MENU
5 | from .ospf import MENUITEMS as OSPF_MENU
6 | from .eigrp import eigrp
7 | from .static import MENUITEMS as STATIC_MENU
8 |
9 |
10 | __all__ = (
11 | 'menu',
12 | )
13 |
14 | menu = PluginMenu(
15 | label='Netbox Routing',
16 | groups=(
17 | # ('Routing Objects', OBJECT_MENU),
18 | ('Static', STATIC_MENU),
19 | # ('BGP', BGP_MENU),
20 | ('OSPF', OSPF_MENU),
21 | ('EIGRP', eigrp),
22 | ),
23 | icon_class='mdi mdi-router'
24 | )
--------------------------------------------------------------------------------
/netbox_routing/navigation/bgp.py:
--------------------------------------------------------------------------------
1 | from netbox.choices import ButtonColorChoices
2 | from netbox.plugins import PluginMenuItem, PluginMenuButton
3 |
4 |
5 | __all__ = (
6 | 'MENUITEMS',
7 | )
8 |
9 |
10 | router = PluginMenuItem(
11 | link='plugins:netbox_routing:bgprouter_list',
12 | link_text='BGP Router',
13 | permissions=['netbox_routing.view_bgprouter'],
14 | buttons=(
15 | PluginMenuButton('plugins:netbox_routing:bgprouter_add', 'Add', 'mdi mdi-plus', ButtonColorChoices.GREEN),
16 | #PluginMenuButton('plugins:netbox_routing:bgprouter_import', 'Import', 'mdi mdi-upload', ButtonColorChoices.CYAN),
17 | )
18 | )
19 |
20 |
21 | scope = PluginMenuItem(
22 | link='plugins:netbox_routing:bgpscope_list',
23 | link_text='BGP Scope',
24 | permissions=['netbox_routing.view_bgpscope'],
25 | buttons=(
26 | PluginMenuButton('plugins:netbox_routing:bgpscope_add', 'Add', 'mdi mdi-plus', ButtonColorChoices.GREEN),
27 | #PluginMenuButton('plugins:netbox_routing:bgpscope_import', 'Import', 'mdi mdi-upload', ButtonColorChoices.CYAN),
28 | )
29 | )
30 |
31 |
32 | address_family = PluginMenuItem(
33 | link='plugins:netbox_routing:bgpaddressfamily_list',
34 | link_text='BGP Address Family',
35 | permissions=['netbox_routing.view_bgpaddressfamily'],
36 | buttons=(
37 | PluginMenuButton('plugins:netbox_routing:bgpaddressfamily_add', 'Add', 'mdi mdi-plus', ButtonColorChoices.GREEN),
38 | #PluginMenuButton('plugins:netbox_routing:bgpaddressfamily_import', 'Import', 'mdi mdi-upload', ButtonColorChoices.CYAN),
39 | )
40 | )
41 |
42 | MENUITEMS = (router, scope, address_family, )
--------------------------------------------------------------------------------
/netbox_routing/navigation/eigrp.py:
--------------------------------------------------------------------------------
1 | from netbox.choices import ButtonColorChoices
2 | from netbox.plugins import PluginMenuItem, PluginMenuButton
3 |
4 |
5 | __all__ = (
6 | 'eigrp',
7 | )
8 |
9 |
10 | routers = PluginMenuItem(
11 | link='plugins:netbox_routing:eigrprouter_list',
12 | link_text='Routers',
13 | permissions=['netbox_routing.view_eigrprouter'],
14 | buttons=(
15 | PluginMenuButton('plugins:netbox_routing:eigrprouter_add', 'Add', 'mdi mdi-plus', ButtonColorChoices.GREEN),
16 | PluginMenuButton(
17 | 'plugins:netbox_routing:eigrprouter_import',
18 | 'Import',
19 | 'mdi mdi-upload',
20 | ButtonColorChoices.CYAN
21 | ),
22 | )
23 | )
24 | address_families = PluginMenuItem(
25 | link='plugins:netbox_routing:eigrpaddressfamily_list',
26 | link_text='Address Families',
27 | permissions=['netbox_routing.view_eigrpaddressfamily'],
28 | buttons=(
29 | PluginMenuButton('plugins:netbox_routing:eigrpaddressfamily_add', 'Add', 'mdi mdi-plus', ButtonColorChoices.GREEN),
30 | )
31 | )
32 | networks = PluginMenuItem(
33 | link='plugins:netbox_routing:eigrpnetwork_list',
34 | link_text='Networks',
35 | permissions=['netbox_routing.view_eigrpnetwork'],
36 | buttons=(
37 | PluginMenuButton('plugins:netbox_routing:eigrpnetwork_add', 'Add', 'mdi mdi-plus', ButtonColorChoices.GREEN),
38 | )
39 | )
40 | interfaces = PluginMenuItem(
41 | link='plugins:netbox_routing:eigrpinterface_list',
42 | link_text='Interfaces',
43 | permissions=['netbox_routing.view_eigrpinterface'],
44 | buttons=(
45 | PluginMenuButton('plugins:netbox_routing:eigrpinterface_add', 'Add', 'mdi mdi-plus', ButtonColorChoices.GREEN),
46 | )
47 | )
48 |
49 | eigrp = (routers, address_families, networks, interfaces)
50 |
--------------------------------------------------------------------------------
/netbox_routing/navigation/objects.py:
--------------------------------------------------------------------------------
1 | from netbox.choices import ButtonColorChoices
2 | from netbox.plugins import PluginMenuButton, PluginMenuItem
3 |
4 |
5 | __all__ = (
6 | 'MENUITEMS',
7 | )
8 |
9 |
10 | prefixlist = PluginMenuItem(
11 | link='plugins:netbox_routing:prefixlist_list',
12 | link_text='Prefix Lists',
13 | permissions=['netbox_routing.view_prefixlist'],
14 | buttons=(
15 | PluginMenuButton('plugins:netbox_routing:prefixlist_add', 'Add', 'mdi mdi-plus', ButtonColorChoices.GREEN),
16 | PluginMenuButton('plugins:netbox_routing:prefixlist_import', 'Import', 'mdi mdi-upload', ButtonColorChoices.CYAN),
17 | )
18 | )
19 | routemap = PluginMenuItem(
20 | link='plugins:netbox_routing:routemap_list',
21 | link_text='Route Maps',
22 | permissions=['netbox_routing.view_routemap'],
23 | buttons=(
24 | PluginMenuButton('plugins:netbox_routing:routemap_add', 'Add', 'mdi mdi-plus', ButtonColorChoices.GREEN),
25 | PluginMenuButton('plugins:netbox_routing:routemap_import', 'Import', 'mdi mdi-upload', ButtonColorChoices.CYAN),
26 | )
27 | )
28 |
29 | MENUITEMS = (prefixlist, routemap)
30 |
--------------------------------------------------------------------------------
/netbox_routing/navigation/ospf.py:
--------------------------------------------------------------------------------
1 | from netbox.choices import ButtonColorChoices
2 | from netbox.plugins import PluginMenuItem, PluginMenuButton
3 |
4 |
5 | __all__ = (
6 | 'MENUITEMS',
7 | )
8 |
9 |
10 | ospf_instance = PluginMenuItem(
11 | link='plugins:netbox_routing:ospfinstance_list',
12 | link_text='Instances',
13 | permissions=['netbox_routing.view_ospfinstance'],
14 | buttons=(
15 | PluginMenuButton('plugins:netbox_routing:ospfinstance_add', 'Add', 'mdi mdi-plus', ButtonColorChoices.GREEN),
16 | PluginMenuButton(
17 | 'plugins:netbox_routing:ospfinstance_import',
18 | 'Import',
19 | 'mdi mdi-upload',
20 | ButtonColorChoices.CYAN
21 | ),
22 | )
23 | )
24 | ospf_area = PluginMenuItem(
25 | link='plugins:netbox_routing:ospfarea_list',
26 | link_text='Areas',
27 | permissions=['netbox_routing.view_ospfarea'],
28 | buttons=(
29 | PluginMenuButton('plugins:netbox_routing:ospfarea_add', 'Add', 'mdi mdi-plus', ButtonColorChoices.GREEN),
30 | PluginMenuButton(
31 | 'plugins:netbox_routing:ospfarea_import',
32 | 'Import',
33 | 'mdi mdi-upload',
34 | ButtonColorChoices.CYAN
35 | ),
36 | )
37 | )
38 | ospf_interfaces = PluginMenuItem(
39 | link='plugins:netbox_routing:ospfinterface_list',
40 | link_text='Interfaces',
41 | permissions=['netbox_routing.view_ospfinterface'],
42 | buttons=(
43 | PluginMenuButton('plugins:netbox_routing:ospfinterface_add', 'Add', 'mdi mdi-plus', ButtonColorChoices.GREEN),
44 | PluginMenuButton(
45 | 'plugins:netbox_routing:ospfinterface_import',
46 | 'Import',
47 | 'mdi mdi-upload',
48 | ButtonColorChoices.CYAN
49 | ),
50 | )
51 | )
52 |
53 | MENUITEMS = (ospf_instance, ospf_area, ospf_interfaces)
54 |
--------------------------------------------------------------------------------
/netbox_routing/navigation/static.py:
--------------------------------------------------------------------------------
1 | from netbox.choices import ButtonColorChoices
2 | from netbox.plugins import PluginMenuItem, PluginMenuButton
3 |
4 | __all__ = (
5 | 'MENUITEMS',
6 | )
7 |
8 | static = PluginMenuItem(
9 | link='plugins:netbox_routing:staticroute_list',
10 | link_text='Static Route',
11 | permissions=['netbox_routing.view_staticroute'],
12 | buttons=(
13 | PluginMenuButton('plugins:netbox_routing:staticroute_add', 'Add', 'mdi mdi-plus', ButtonColorChoices.GREEN),
14 | PluginMenuButton(
15 | 'plugins:netbox_routing:staticroute_import',
16 | 'Import',
17 | 'mdi mdi-upload',
18 | ButtonColorChoices.CYAN
19 | ),
20 | )
21 | )
22 |
23 | MENUITEMS = (static,)
24 |
--------------------------------------------------------------------------------
/netbox_routing/tables/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanSheps/netbox-routing/56ec2104ef304157cb27e3ec75d16597031d08c6/netbox_routing/tables/__init__.py
--------------------------------------------------------------------------------
/netbox_routing/tables/bgp.py:
--------------------------------------------------------------------------------
1 | import django_tables2 as tables
2 | from django.utils.translation import gettext_lazy as _
3 |
4 | from netbox.tables import NetBoxTable
5 | from netbox_routing.models import BGPRouter, BGPSetting, BGPScope, BGPAddressFamily
6 |
7 |
8 | __all__ = (
9 | 'BGPRouterTable',
10 | 'BGPScopeTable',
11 | 'BGPAddressFamilyTable',
12 | 'BGPSettingTable',
13 | )
14 |
15 |
16 |
17 | class BGPRouterTable(NetBoxTable):
18 | class Meta(NetBoxTable.Meta):
19 | model = BGPRouter
20 | fields = ('pk', 'id', 'device', 'asn')
21 | default_columns = ('pk', 'id', 'device', 'asn')
22 |
23 |
24 | class BGPScopeTable(NetBoxTable):
25 | class Meta(NetBoxTable.Meta):
26 | model = BGPScope
27 | fields = ('pk', 'id', 'router', 'vrf')
28 | default_columns = ('pk', 'id', 'router', 'vrf')
29 |
30 |
31 | class BGPAddressFamilyTable(NetBoxTable):
32 | class Meta(NetBoxTable.Meta):
33 | model = BGPAddressFamily
34 | fields = ('pk', 'id', 'scope', 'address_family')
35 | default_columns = ('pk', 'id', 'scope', 'address_family')
36 |
37 |
38 | class BGPSettingTable(NetBoxTable):
39 | class Meta(NetBoxTable.Meta):
40 | model = BGPSetting
41 | fields = ('pk', 'id', 'assigned_object', 'key', 'value')
42 | default_columns = ('pk', 'id', 'assigned_object', 'key', 'value')
43 |
--------------------------------------------------------------------------------
/netbox_routing/tables/eigrp.py:
--------------------------------------------------------------------------------
1 | import django_tables2 as tables
2 | from django.utils.translation import gettext_lazy as _
3 |
4 | from netbox.tables import NetBoxTable
5 | from netbox_routing.models import EIGRPAddressFamily, EIGRPRouter, EIGRPInterface
6 |
7 |
8 | __all__ = (
9 | 'EIGRPAddressFamilyTable',
10 | 'EIGRPRouterTable',
11 | 'EIGRPNetworkTable',
12 | 'EIGRPInterfaceTable',
13 | )
14 |
15 |
16 | class EIGRPRouterTable(NetBoxTable):
17 | class Meta(NetBoxTable.Meta):
18 | model = EIGRPRouter
19 | fields = ('pk', 'id', 'name', 'mode', 'pid', 'rid', 'device')
20 | default_columns = ('pk', 'id', 'name', 'device')
21 |
22 |
23 | class EIGRPAddressFamilyTable(NetBoxTable):
24 | router = tables.Column(
25 | verbose_name=_('Router'),
26 | linkify=True
27 | )
28 | class Meta(NetBoxTable.Meta):
29 | model = EIGRPAddressFamily
30 | fields = ('pk', 'id', 'family', 'router')
31 | default_columns = ('pk', 'id', 'family', 'router')
32 |
33 |
34 | class EIGRPNetworkTable(NetBoxTable):
35 | router = tables.Column(
36 | verbose_name=_('Router'),
37 | linkify=True
38 | )
39 | address_family = tables.Column(
40 | verbose_name=_('Address Family'),
41 | linkify=True
42 | )
43 | network = tables.Column(
44 | verbose_name=_('Network'),
45 | linkify=True
46 | )
47 |
48 | class Meta(NetBoxTable.Meta):
49 | model = EIGRPInterface
50 | fields = ('pk', 'id', 'router', 'address_family', 'network')
51 | default_columns = ('pk', 'id', 'router', 'address_family', 'network')
52 |
53 |
54 | class EIGRPInterfaceTable(NetBoxTable):
55 | router = tables.Column(
56 | verbose_name=_('Router'),
57 | linkify=True
58 | )
59 | address_family = tables.Column(
60 | verbose_name=_('Address Family'),
61 | linkify=True
62 | )
63 | interface = tables.Column(
64 | verbose_name=_('Interface'),
65 | linkify=True
66 | )
67 |
68 | class Meta(NetBoxTable.Meta):
69 | model = EIGRPInterface
70 | fields = ('pk', 'id', 'router', 'address_family', 'interface', 'passive', 'bfd', 'authentication', 'passphrase')
71 | default_columns = ('pk', 'id', 'router', 'address_family', 'interface')
72 |
--------------------------------------------------------------------------------
/netbox_routing/tables/objects.py:
--------------------------------------------------------------------------------
1 | import django_tables2 as tables
2 | from django.utils.translation import gettext_lazy as _
3 |
4 | from netbox.tables import NetBoxTable, columns
5 | from netbox_routing.models import PrefixList, PrefixListEntry, RouteMap, RouteMapEntry
6 |
7 |
8 | class PrefixListTable(NetBoxTable):
9 | class Meta(NetBoxTable.Meta):
10 | model = PrefixList
11 | fields = ('pk', 'id', 'name')
12 | default_columns = ('pk', 'id', 'name')
13 |
14 |
15 | class PrefixListEntryTable(NetBoxTable):
16 | prefix_list = tables.Column(
17 | verbose_name=_('Prefix List'),
18 | linkify=True
19 | )
20 | type = columns.ChoiceFieldColumn()
21 | class Meta(NetBoxTable.Meta):
22 | model = PrefixListEntry
23 | fields = ('pk', 'id', 'prefix_list', 'sequence', 'type', 'prefix', 'le', 'ge')
24 | default_columns = ('pk', 'id', 'prefix_list', 'sequence', 'type', 'prefix', 'le', 'ge')
25 |
26 |
27 | class RouteMapTable(NetBoxTable):
28 | class Meta(NetBoxTable.Meta):
29 | model = RouteMap
30 | fields = ('pk', 'id', 'name')
31 | default_columns = ('pk', 'id', 'name')
32 |
33 |
34 | class RouteMapEntryTable(NetBoxTable):
35 | route_map = tables.Column(
36 | verbose_name=_('Route Map'),
37 | linkify=True
38 | )
39 | type = columns.ChoiceFieldColumn()
40 | class Meta(NetBoxTable.Meta):
41 | model = RouteMapEntry
42 | fields = ('pk', 'id', 'route_map', 'sequence', 'type')
43 | default_columns = ('pk', 'id', 'route_map', 'sequence', 'type')
44 |
--------------------------------------------------------------------------------
/netbox_routing/tables/ospf.py:
--------------------------------------------------------------------------------
1 | import django_tables2 as tables
2 | from django.utils.translation import gettext_lazy as _
3 |
4 | from netbox.tables import NetBoxTable
5 | from netbox_routing.models import OSPFArea, OSPFInstance, OSPFInterface
6 |
7 |
8 | __all__ = (
9 | 'OSPFAreaTable',
10 | 'OSPFInstanceTable',
11 | 'OSPFInterfaceTable',
12 | )
13 |
14 |
15 | class OSPFInstanceTable(NetBoxTable):
16 | class Meta(NetBoxTable.Meta):
17 | model = OSPFInstance
18 | fields = ('pk', 'id', 'name', 'router_id', 'process_id', 'device', 'vrf', )
19 | default_columns = ('pk', 'id', 'name', 'router_id', 'process_id', 'device', )
20 |
21 |
22 | class OSPFAreaTable(NetBoxTable):
23 | class Meta(NetBoxTable.Meta):
24 | model = OSPFArea
25 | fields = ('pk', 'id', 'area_id')
26 | default_columns = ('pk', 'id', 'area_id')
27 |
28 |
29 | class OSPFInterfaceTable(NetBoxTable):
30 | instance = tables.Column(
31 | verbose_name=_('Instance'),
32 | linkify=True
33 | )
34 | area = tables.Column(
35 | verbose_name=_('Area'),
36 | linkify=True
37 | )
38 | interface = tables.Column(
39 | verbose_name=_('Interface'),
40 | linkify=True
41 | )
42 |
43 | class Meta(NetBoxTable.Meta):
44 | model = OSPFInterface
45 | fields = (
46 | 'pk', 'id', 'instance', 'area', 'interface', 'passive', 'priority', 'bfd', 'authentication', 'passphrase'
47 | )
48 | default_columns = ('pk', 'id', 'instance', 'area', 'interface', 'passive')
49 |
--------------------------------------------------------------------------------
/netbox_routing/tables/static.py:
--------------------------------------------------------------------------------
1 | import django_tables2 as tables
2 | from django.utils.translation import gettext_lazy as _
3 |
4 | from netbox.tables import NetBoxTable
5 | from netbox_routing.models import StaticRoute
6 |
7 |
8 | class StaticRouteTable(NetBoxTable):
9 | devices = tables.ManyToManyColumn(
10 | verbose_name=_('Devices'),
11 | linkify_item=True,
12 | )
13 | vrf = tables.Column(
14 | verbose_name=_('VRF'),
15 | linkify=True,
16 | )
17 |
18 | class Meta(NetBoxTable.Meta):
19 | model = StaticRoute
20 | fields = (
21 | 'pk', 'id', 'devices', 'vrf', 'prefix', 'next_hop', 'name', 'metric', 'permanent', 'description',
22 | 'comments',
23 | )
24 | default_columns = ('pk', 'id', 'devices', 'vrf', 'prefix', 'next_hop', 'name')
25 |
--------------------------------------------------------------------------------
/netbox_routing/templates/netbox_routing/bgpaddressfamily.html:
--------------------------------------------------------------------------------
1 | {% extends 'generic/object.html' %}
2 | {% load humanize %}
3 | {% load helpers %}
4 | {% load plugins %}
5 |
6 | {% block content %}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Scope |
15 |
16 | {{ object.scope|linkify }}
17 | |
18 |
19 |
20 | Address Family |
21 |
22 | {{ object.address_family }}
23 | |
24 |
25 |
26 |
27 |
28 | {% include 'netbox_routing/inc/settings.html' %}
29 | {% plugin_left_page object %}
30 |
31 |
32 | {% include 'inc/panels/related_objects.html' %}
33 | {% include 'inc/panels/custom_fields.html' %}
34 | {% include 'inc/panels/comments.html' %}
35 | {% include 'inc/panels/tags.html' %}
36 | {% plugin_right_page object %}
37 |
38 |
39 |
40 |
41 | {% plugin_full_width_page object %}
42 |
43 |
44 | {% endblock %}
--------------------------------------------------------------------------------
/netbox_routing/templates/netbox_routing/bgprouter.html:
--------------------------------------------------------------------------------
1 | {% extends 'generic/object.html' %}
2 | {% load humanize %}
3 | {% load helpers %}
4 | {% load plugins %}
5 |
6 | {% block content %}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Device |
15 |
16 | {{ object.device|linkify }}
17 | |
18 |
19 |
20 | AS Number |
21 |
22 | {{ object.asn|linkify }}
23 | |
24 |
25 |
26 |
27 |
28 | {% include 'netbox_routing/inc/settings.html' %}
29 | {% plugin_left_page object %}
30 |
31 |
32 | {% include 'inc/panels/related_objects.html' %}
33 | {% include 'inc/panels/custom_fields.html' %}
34 | {% include 'inc/panels/comments.html' %}
35 | {% include 'inc/panels/tags.html' %}
36 | {% plugin_right_page object %}
37 |
38 |
39 |
40 |
41 | {% plugin_full_width_page object %}
42 |
43 |
44 | {% endblock %}
--------------------------------------------------------------------------------
/netbox_routing/templates/netbox_routing/bgpscope.html:
--------------------------------------------------------------------------------
1 | {% extends 'generic/object.html' %}
2 | {% load humanize %}
3 | {% load helpers %}
4 | {% load plugins %}
5 |
6 | {% block content %}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Router |
15 |
16 | {{ object.router|linkify }}
17 | |
18 |
19 |
20 | AS Number |
21 |
22 | {{ object.vrf|linkify }}
23 | |
24 |
25 |
26 |
27 |
28 | {% include 'netbox_routing/inc/settings.html' %}
29 | {% plugin_left_page object %}
30 |
31 |
32 | {% include 'inc/panels/related_objects.html' %}
33 | {% include 'inc/panels/custom_fields.html' %}
34 | {% include 'inc/panels/comments.html' %}
35 | {% include 'inc/panels/tags.html' %}
36 | {% plugin_right_page object %}
37 |
38 |
39 |
40 |
41 | {% plugin_full_width_page object %}
42 |
43 |
44 | {% endblock %}
--------------------------------------------------------------------------------
/netbox_routing/templates/netbox_routing/eigrpaddressfamily.html:
--------------------------------------------------------------------------------
1 | {% extends 'generic/object.html' %}
2 | {% load humanize %}
3 | {% load helpers %}
4 | {% load plugins %}
5 |
6 | {% block content %}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Router |
15 |
16 | {{ object.router }}
17 | |
18 |
19 |
20 | VRF |
21 |
22 | {{ object.vrf }}
23 | |
24 |
25 |
26 | Family |
27 |
28 | {{ object.family }}
29 | |
30 |
31 |
32 | Router ID |
33 |
34 | {% if object.rid %}{{ object.rid }}{% else %}{{ object.router.rid}}{% endif %}
35 | |
36 |
37 |
38 |
39 |
40 | {% plugin_left_page object %}
41 |
42 |
43 | {% include 'inc/panels/related_objects.html' %}
44 | {% include 'inc/panels/custom_fields.html' %}
45 | {% include 'inc/panels/comments.html' %}
46 | {% include 'inc/panels/tags.html' %}
47 | {% plugin_right_page object %}
48 |
49 |
50 |
51 |
52 | {% plugin_full_width_page object %}
53 |
54 |
55 | {% endblock %}
--------------------------------------------------------------------------------
/netbox_routing/templates/netbox_routing/eigrpinterface.html:
--------------------------------------------------------------------------------
1 | {% extends 'generic/object.html' %}
2 | {% load humanize %}
3 | {% load helpers %}
4 | {% load plugins %}
5 |
6 | {% block content %}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Router |
15 |
16 | {{ object.router|linkify }}
17 | |
18 |
19 |
20 | Address Family |
21 |
22 | {{ object.address_family|linkify }}
23 | |
24 |
25 |
26 | Interface |
27 |
28 | {{ object.interface|linkify }}
29 | |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | Passive |
40 |
41 | {{ object.passive|placeholder }}
42 | |
43 |
44 |
45 | BFD |
46 |
47 | {{ object.bfd|placeholder }}
48 | |
49 |
50 |
51 | Authentication Type |
52 |
53 | {{ object.authentication|placeholder }}
54 | |
55 |
56 |
57 | Passphrase (or keyring) |
58 |
59 | {{ object.passphrase|placeholder }}
60 | |
61 |
62 |
63 |
64 |
65 | {% plugin_left_page object %}
66 |
67 |
68 | {% include 'inc/panels/related_objects.html' %}
69 | {% include 'inc/panels/custom_fields.html' %}
70 | {% include 'inc/panels/comments.html' %}
71 | {% include 'inc/panels/tags.html' %}
72 | {% plugin_right_page object %}
73 |
74 |
75 |
76 |
77 | {% plugin_full_width_page object %}
78 |
79 |
80 | {% endblock %}
--------------------------------------------------------------------------------
/netbox_routing/templates/netbox_routing/eigrpnetwork.html:
--------------------------------------------------------------------------------
1 | {% extends 'generic/object.html' %}
2 | {% load humanize %}
3 | {% load helpers %}
4 | {% load plugins %}
5 |
6 | {% block content %}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Router |
15 |
16 | {{ object.router|linkify }}
17 | |
18 |
19 |
20 | Address Family |
21 |
22 | {{ object.address_family|linkify }}
23 | |
24 |
25 |
26 | Network |
27 |
28 | {{ object.network|linkify }}
29 | |
30 |
31 |
32 |
33 |
34 | {% plugin_left_page object %}
35 |
36 |
37 | {% include 'inc/panels/related_objects.html' %}
38 | {% include 'inc/panels/custom_fields.html' %}
39 | {% include 'inc/panels/comments.html' %}
40 | {% include 'inc/panels/tags.html' %}
41 | {% plugin_right_page object %}
42 |
43 |
44 |
45 |
46 | {% plugin_full_width_page object %}
47 |
48 |
49 | {% endblock %}
--------------------------------------------------------------------------------
/netbox_routing/templates/netbox_routing/eigrprouter.html:
--------------------------------------------------------------------------------
1 | {% extends 'generic/object.html' %}
2 | {% load humanize %}
3 | {% load helpers %}
4 | {% load plugins %}
5 |
6 | {% block content %}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Device |
15 |
16 | {{ object.device|linkify }}
17 | |
18 |
19 |
20 | Router ID |
21 |
22 | {{ object.rid }}
23 | |
24 |
25 |
26 | Name |
27 |
28 | {{ object.name }}
29 | |
30 |
31 |
32 | Process ID |
33 |
34 | {{ object.process_id }}
35 | |
36 |
37 |
38 |
39 |
40 | {% plugin_left_page object %}
41 |
42 |
43 | {% include 'inc/panels/related_objects.html' %}
44 | {% include 'inc/panels/custom_fields.html' %}
45 | {% include 'inc/panels/comments.html' %}
46 | {% include 'inc/panels/tags.html' %}
47 | {% plugin_right_page object %}
48 |
49 |
50 |
51 |
52 | {% plugin_full_width_page object %}
53 |
54 |
55 | {% endblock %}
--------------------------------------------------------------------------------
/netbox_routing/templates/netbox_routing/inc/settings.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {% for setting in object.settings.all %}
6 |
7 | {{ setting.get_key_display }} |
8 |
9 | {{ setting.value }}
10 | |
11 |
12 | {% endfor %}
13 |
14 |
15 |
--------------------------------------------------------------------------------
/netbox_routing/templates/netbox_routing/object_children.html:
--------------------------------------------------------------------------------
1 | {% extends 'generic/object_children.html' %}
2 | {% load helpers %}
3 |
4 | {% block bulk_extra_controls %}
5 | {{ block.super }}
6 | {% if perms.netbox_routing.add_ospfinterface %}
7 |
13 | {% endif %}
14 | {% endblock bulk_extra_controls %}
--------------------------------------------------------------------------------
/netbox_routing/templates/netbox_routing/objecttable.html:
--------------------------------------------------------------------------------
1 | {% extends 'generic/object.html' %}
2 | {% load render_table from django_tables2 %}
3 | {% load helpers %}
4 | {% load static %}
5 |
6 | {% block content %}
7 | {% include 'inc/table_controls_htmx.html' with table_modal="ObjectTable_config" %}
8 |
9 |
44 | {% endblock %}
45 |
46 | {% block modals %}
47 | {{ block.super }}
48 | {% table_config_form table table_name="ObjectTable"%}
49 | {% endblock modals %}
--------------------------------------------------------------------------------
/netbox_routing/templates/netbox_routing/ospfarea.html:
--------------------------------------------------------------------------------
1 | {% extends 'generic/object.html' %}
2 | {% load humanize %}
3 | {% load helpers %}
4 | {% load plugins %}
5 |
6 | {% block content %}
7 |
8 |
9 | {% plugin_left_page object %}
10 |
11 |
12 | {% include 'inc/panels/related_objects.html' %}
13 | {% include 'inc/panels/custom_fields.html' %}
14 | {% include 'inc/panels/comments.html' %}
15 | {% include 'inc/panels/tags.html' %}
16 | {% plugin_right_page object %}
17 |
18 |
19 |
20 |
21 | {% plugin_full_width_page object %}
22 |
23 |
24 | {% endblock %}
--------------------------------------------------------------------------------
/netbox_routing/templates/netbox_routing/ospfinstance.html:
--------------------------------------------------------------------------------
1 | {% extends 'generic/object.html' %}
2 | {% load humanize %}
3 | {% load helpers %}
4 | {% load plugins %}
5 |
6 | {% block content %}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Name |
15 |
16 | {{ object.name }}
17 | |
18 |
19 |
20 | Router ID |
21 |
22 | {{ object.router_id }}
23 | |
24 |
25 |
26 | Process ID |
27 |
28 | {{ object.process_id }}
29 | |
30 |
31 |
32 | Device |
33 |
34 | {{ object.device|linkify }}
35 | |
36 |
37 |
38 | VRF |
39 |
40 | {{ object.vrf|linkify }}
41 | |
42 |
43 |
44 |
45 |
46 | {% plugin_left_page object %}
47 |
48 |
49 | {% include 'inc/panels/related_objects.html' %}
50 | {% include 'inc/panels/custom_fields.html' %}
51 | {% include 'inc/panels/comments.html' %}
52 | {% include 'inc/panels/tags.html' %}
53 | {% plugin_right_page object %}
54 |
55 |
56 |
57 |
58 | {% plugin_full_width_page object %}
59 |
60 |
61 | {% endblock %}
--------------------------------------------------------------------------------
/netbox_routing/templates/netbox_routing/ospfinterface.html:
--------------------------------------------------------------------------------
1 | {% extends 'generic/object.html' %}
2 | {% load humanize %}
3 | {% load helpers %}
4 | {% load plugins %}
5 |
6 | {% block content %}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Instance |
15 |
16 | {{ object.instance|linkify }}
17 | |
18 |
19 |
20 | Area |
21 |
22 | {{ object.area|linkify }}
23 | |
24 |
25 |
26 | Interface |
27 |
28 | {{ object.interface|linkify }}
29 | |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | Passive |
40 |
41 | {{ object.passive|placeholder }}
42 | |
43 |
44 |
45 | BFD |
46 |
47 | {{ object.bfd|placeholder }}
48 | |
49 |
50 |
51 | Priority |
52 |
53 | {{ object.priority|placeholder }}
54 | |
55 |
56 |
57 | Authentication Type |
58 |
59 | {{ object.authentication|placeholder }}
60 | |
61 |
62 |
63 | Passphrase (or keyring) |
64 |
65 | {{ object.passphrase|placeholder }}
66 | |
67 |
68 |
69 |
70 |
71 | {% plugin_left_page object %}
72 |
73 |
74 | {% include 'inc/panels/related_objects.html' %}
75 | {% include 'inc/panels/custom_fields.html' %}
76 | {% include 'inc/panels/comments.html' %}
77 | {% include 'inc/panels/tags.html' %}
78 | {% plugin_right_page object %}
79 |
80 |
81 |
82 |
83 | {% plugin_full_width_page object %}
84 |
85 |
86 | {% endblock %}
--------------------------------------------------------------------------------
/netbox_routing/templates/netbox_routing/prefixlist.html:
--------------------------------------------------------------------------------
1 | {% extends 'generic/object.html' %}
2 | {% load humanize %}
3 | {% load helpers %}
4 | {% load plugins %}
5 |
6 | {% block content %}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Name |
15 |
16 | {{ object.name }}
17 | |
18 |
19 |
20 |
21 |
22 | {% plugin_left_page object %}
23 |
24 |
25 | {% include 'inc/panels/related_objects.html' %}
26 | {% include 'inc/panels/custom_fields.html' %}
27 | {% include 'inc/panels/comments.html' %}
28 | {% include 'inc/panels/tags.html' %}
29 | {% plugin_right_page object %}
30 |
31 |
32 |
33 |
34 | {% plugin_full_width_page object %}
35 |
36 |
37 | {% endblock %}
--------------------------------------------------------------------------------
/netbox_routing/templates/netbox_routing/prefixlistentry.html:
--------------------------------------------------------------------------------
1 | {% extends 'generic/object.html' %}
2 | {% load humanize %}
3 | {% load helpers %}
4 | {% load plugins %}
5 |
6 | {% block content %}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Prefix List |
15 |
16 | {{ object.prefix_list|linkify }}
17 | |
18 |
19 |
20 | Sequence |
21 |
22 | {{ object.sequence }}
23 | |
24 |
25 |
26 | Type |
27 |
28 | {% badge object.get_type_display bg_color=object.get_type_color %}
29 | |
30 |
31 |
32 | Prefix |
33 |
34 | {{ object.prefix|placeholder }}
35 | |
36 |
37 |
38 | GE |
39 |
40 | {{ object.ge }}
41 | |
42 |
43 |
44 | LE |
45 |
46 | {{ object.le }}
47 | |
48 |
49 |
50 |
51 |
52 | {% plugin_left_page object %}
53 |
54 |
55 | {% include 'inc/panels/related_objects.html' %}
56 | {% include 'inc/panels/custom_fields.html' %}
57 | {% include 'inc/panels/comments.html' %}
58 | {% include 'inc/panels/tags.html' %}
59 | {% plugin_right_page object %}
60 |
61 |
62 |
63 |
64 | {% plugin_full_width_page object %}
65 |
66 |
67 | {% endblock %}
--------------------------------------------------------------------------------
/netbox_routing/templates/netbox_routing/routemap.html:
--------------------------------------------------------------------------------
1 | {% extends 'generic/object.html' %}
2 | {% load humanize %}
3 | {% load helpers %}
4 | {% load plugins %}
5 |
6 | {% block content %}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Name |
15 |
16 | {{ object.name }}
17 | |
18 |
19 |
20 |
21 |
22 | {% plugin_left_page object %}
23 |
24 |
25 | {% include 'inc/panels/related_objects.html' %}
26 | {% include 'inc/panels/custom_fields.html' %}
27 | {% include 'inc/panels/comments.html' %}
28 | {% include 'inc/panels/tags.html' %}
29 | {% plugin_right_page object %}
30 |
31 |
32 |
33 |
34 | {% plugin_full_width_page object %}
35 |
36 |
37 | {% endblock %}
--------------------------------------------------------------------------------
/netbox_routing/templates/netbox_routing/routemapentry.html:
--------------------------------------------------------------------------------
1 | {% extends 'generic/object.html' %}
2 | {% load humanize %}
3 | {% load helpers %}
4 | {% load plugins %}
5 |
6 | {% block content %}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Route Map |
15 |
16 | {{ object.route_map|linkify }}
17 | |
18 |
19 |
20 | Type |
21 |
22 | {% badge object.get_type_display bg_color=object.get_type_color %}
23 | |
24 |
25 |
26 | Sequence |
27 |
28 | {{ object.sequence }}
29 | |
30 |
31 |
32 |
33 |
34 | {% plugin_left_page object %}
35 |
36 |
37 | {% include 'inc/panels/related_objects.html' %}
38 | {% include 'inc/panels/custom_fields.html' %}
39 | {% include 'inc/panels/comments.html' %}
40 | {% include 'inc/panels/tags.html' %}
41 | {% plugin_right_page object %}
42 |
43 |
44 |
45 |
46 | {% plugin_full_width_page object %}
47 |
48 |
49 | {% endblock %}
--------------------------------------------------------------------------------
/netbox_routing/templates/netbox_routing/staticroute.html:
--------------------------------------------------------------------------------
1 | {% extends 'generic/object.html' %}
2 | {% load humanize %}
3 | {% load helpers %}
4 | {% load plugins %}
5 |
6 | {% block content %}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Name |
15 |
16 | {{object.name}}
17 | |
18 |
19 |
20 | Description |
21 |
22 | {{object.description}}
23 | |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | VRF |
34 |
35 | {% if object.vrf %}
36 | {{ object.vrf }}
37 | {% else %}
38 | Global
39 | {% endif %}
40 | |
41 |
42 |
43 | Prefix |
44 |
45 | {{ object.prefix }}
46 | |
47 |
48 |
49 | Next Hop |
50 |
51 | {{ object.next_hop }}
52 | |
53 |
54 |
55 | Name |
56 |
57 | {{ object.name|placeholder }}
58 | |
59 |
60 |
61 | Metric |
62 |
63 | {{ object.metric }}
64 | |
65 |
66 |
67 | Permanent |
68 |
69 | {{ object.permanent }}
70 | |
71 |
72 |
73 |
74 |
75 | {% plugin_left_page object %}
76 |
77 |
78 | {% include 'inc/panels/related_objects.html' %}
79 | {% include 'inc/panels/custom_fields.html' %}
80 | {% include 'inc/panels/comments.html' %}
81 | {% include 'inc/panels/tags.html' %}
82 | {% plugin_right_page object %}
83 |
84 |
85 |
86 |
87 | {% plugin_full_width_page object %}
88 |
89 |
90 | {% endblock %}
--------------------------------------------------------------------------------
/netbox_routing/templates/netbox_routing/staticroute_devices.html:
--------------------------------------------------------------------------------
1 | {% extends 'generic/object.html' %}
2 | {% load render_table from django_tables2 %}
3 | {% load helpers %}
4 | {% load static %}
5 |
6 | {% block content %}
7 | {% include 'inc/table_controls_htmx.html' with table_modal="StaticRouteDevices_config" %}
8 |
9 |
18 | {% endblock %}
19 |
20 | {% block modals %}
21 | {{ block.super }}
22 | {% table_config_form table table_name="StaticRouteDevices"%}
23 | {% endblock modals %}
--------------------------------------------------------------------------------
/netbox_routing/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanSheps/netbox-routing/56ec2104ef304157cb27e3ec75d16597031d08c6/netbox_routing/tests/__init__.py
--------------------------------------------------------------------------------
/netbox_routing/tests/base.py:
--------------------------------------------------------------------------------
1 | from netaddr.ip import IPAddress
2 |
3 |
4 | class IPAddressFieldMixin:
5 | def model_to_dict(self, instance, fields, api=False):
6 | model_dict = super().model_to_dict(instance, fields, api)
7 | for key, value in list(model_dict.items()):
8 | if api:
9 | if type(value) is IPAddress:
10 | model_dict[key] = str(value)
11 | elif not api and key in ['router_id', ]:
12 | if type(value) is IPAddress:
13 | model_dict[key] = str(value)
14 | return model_dict
--------------------------------------------------------------------------------
/netbox_routing/tests/eigrp/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanSheps/netbox-routing/56ec2104ef304157cb27e3ec75d16597031d08c6/netbox_routing/tests/eigrp/__init__.py
--------------------------------------------------------------------------------
/netbox_routing/tests/eigrp/test_api.py:
--------------------------------------------------------------------------------
1 | from django.urls import reverse
2 | from netaddr.ip import IPAddress
3 | from rest_framework import status
4 |
5 | from dcim.models import Interface
6 | from ipam.models import VRF
7 | from utilities.testing import APIViewTestCases, APITestCase, create_test_device
8 |
9 | from netbox_routing.models import EIGRPRouter, EIGRPAddressFamily, EIGRPNetwork, EIGRPInterface
10 | from netbox_routing.tests.base import IPAddressFieldMixin
11 |
12 | __all__ = (
13 | 'EIGRPRouterTestCase',
14 | 'EIGRPAddressFamilyTestCase',
15 | 'EIGRPNetworkTestCase',
16 | 'EIGRPInterfaceTestCase',
17 | )
18 |
19 | class EIGRPRouterTestCase(APIViewTestCases):
20 | pass
21 |
22 | class EIGRPAddressFamilyTestCase(APIViewTestCases):
23 | pass
24 |
25 | class EIGRPNetworkTestCase(APIViewTestCases):
26 | pass
27 |
28 | class EIGRPInterfaceTestCase(APIViewTestCases):
29 | pass
30 |
31 |
--------------------------------------------------------------------------------
/netbox_routing/tests/eigrp/test_filtersets.py:
--------------------------------------------------------------------------------
1 | import netaddr
2 | from django.test import TestCase
3 |
4 | from dcim.models import Device, Interface
5 | from ipam.models import VRF
6 | from utilities.testing import create_test_device
7 |
8 | from netbox_routing.filtersets import *
9 | from netbox_routing.models import *
10 |
11 | __all__ = (
12 | 'EIGRPRouterTestCase',
13 | 'EIGRPAddressFamilyTestCase',
14 | 'EIGRPNetworkTestCase',
15 | 'EIGRPInterfaceTestCase',
16 | )
17 |
18 |
19 | class EIGRPRouterTestCase(TestCase):
20 | pass
21 |
22 |
23 | class EIGRPAddressFamilyTestCase(TestCase):
24 | pass
25 |
26 |
27 | class EIGRPNetworkTestCase(TestCase):
28 | pass
29 |
30 |
31 | class EIGRPInterfaceTestCase(TestCase):
32 | pass
33 |
--------------------------------------------------------------------------------
/netbox_routing/tests/eigrp/test_forms.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | from dcim.models import Device, Interface
4 | from utilities.testing import create_test_device
5 |
6 | from netbox_routing.forms import *
7 | from netbox_routing.models import *
8 |
9 | __all__ = (
10 | 'EIGRPRouterTestCase',
11 | 'EIGRPAddressFamilyTestCase',
12 | 'EIGRPNetworkTestCase',
13 | 'EIGRPInterfaceTestCase',
14 | )
15 |
16 |
17 | class EIGRPRouterTestCase(TestCase):
18 | pass
19 |
20 |
21 | class EIGRPAddressFamilyTestCase(TestCase):
22 | pass
23 |
24 |
25 | class EIGRPNetworkTestCase(TestCase):
26 | pass
27 |
28 |
29 | class EIGRPInterfaceTestCase(TestCase):
30 | pass
31 |
--------------------------------------------------------------------------------
/netbox_routing/tests/eigrp/test_models.py:
--------------------------------------------------------------------------------
1 | from django.core.exceptions import ValidationError
2 | from django.test import TestCase
3 |
4 | from utilities.testing import create_test_device
5 |
6 | from netbox_routing.models import *
7 |
8 | __all__ = (
9 | 'EIGRPRouterTestCase',
10 | 'EIGRPAddressFamilyTestCase',
11 | 'EIGRPNetworkTestCase',
12 | 'EIGRPInterfaceTestCase',
13 | )
14 |
15 |
16 | class EIGRPRouterTestCase(TestCase):
17 | pass
18 |
19 |
20 | class EIGRPAddressFamilyTestCase(TestCase):
21 | pass
22 |
23 |
24 | class EIGRPNetworkTestCase(TestCase):
25 | pass
26 |
27 |
28 | class EIGRPInterfaceTestCase(TestCase):
29 | pass
30 |
--------------------------------------------------------------------------------
/netbox_routing/tests/eigrp/test_views.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanSheps/netbox-routing/56ec2104ef304157cb27e3ec75d16597031d08c6/netbox_routing/tests/eigrp/test_views.py
--------------------------------------------------------------------------------
/netbox_routing/tests/ospf/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanSheps/netbox-routing/56ec2104ef304157cb27e3ec75d16597031d08c6/netbox_routing/tests/ospf/__init__.py
--------------------------------------------------------------------------------
/netbox_routing/tests/ospf/test_api.py:
--------------------------------------------------------------------------------
1 | from dcim.models import Interface
2 | from ipam.models import VRF
3 | from utilities.testing import APIViewTestCases, create_test_device
4 |
5 | from netbox_routing.models import OSPFInstance, OSPFArea, OSPFInterface
6 | from netbox_routing.tests.base import IPAddressFieldMixin
7 |
8 | __all__ = (
9 | 'OSPFInstanceTestCase',
10 | 'OSPFAreaTestCase',
11 | 'OSPFInterfaceTestCase',
12 | )
13 |
14 |
15 | class OSPFInstanceTestCase(IPAddressFieldMixin, APIViewTestCases.APIViewTestCase):
16 | model = OSPFInstance
17 | view_namespace = 'plugins-api:netbox_routing'
18 | brief_fields = ['device', 'display', 'id', 'name', 'process_id', 'router_id', 'url', 'vrf', ]
19 |
20 | user_permissions = ('dcim.view_device', )
21 |
22 | bulk_update_data = {
23 | 'description': "A test description"
24 | }
25 |
26 | @classmethod
27 | def setUpTestData(cls):
28 | vrf = VRF.objects.create(name='Test VRF')
29 | device = create_test_device(name='Test Device')
30 |
31 | data = (
32 | cls.model(name='Instance 1', device=device, router_id='1.1.1.1', process_id=1, vrf=None),
33 | cls.model(name='Instance 2', device=device, router_id='2.2.2.2', process_id=2, vrf=vrf),
34 | cls.model(name='Instance 3', device=device, router_id='3.3.3.3', process_id=3, vrf=None),
35 | )
36 | cls.model.objects.bulk_create(data)
37 |
38 | cls.create_data = [
39 | {
40 | 'name': 'Instance X',
41 | 'device': device.pk,
42 | 'router_id': '4.4.4.4',
43 | 'process_id': 4,
44 | 'vrf': vrf.pk,
45 | },
46 | ]
47 |
48 |
49 | class OSPFAreaTestCase(IPAddressFieldMixin , APIViewTestCases.APIViewTestCase):
50 | model = OSPFArea
51 | view_namespace = 'plugins-api:netbox_routing'
52 | brief_fields = ['area_id', 'display', 'id', 'url', ]
53 |
54 | bulk_update_data = {
55 | 'description': "A test description"
56 | }
57 |
58 | @classmethod
59 | def setUpTestData(cls):
60 |
61 | data = (
62 | cls.model(area_id='1'),
63 | cls.model(area_id='2'),
64 | cls.model(area_id='3'),
65 | )
66 | cls.model.objects.bulk_create(data)
67 |
68 | cls.create_data = [
69 | {
70 | 'area_id': '4',
71 | },
72 | ]
73 |
74 |
75 | class OSPFInterfaceTestCase(IPAddressFieldMixin, APIViewTestCases.APIViewTestCase):
76 | model = OSPFInterface
77 | view_namespace = 'plugins-api:netbox_routing'
78 | brief_fields = ['area', 'display', 'id', 'instance', 'interface', 'passive', 'url', ]
79 |
80 | user_permissions = (
81 | 'netbox_routing.view_ospfinstance', 'netbox_routing.view_ospfarea', 'dcim.view_device', 'dcim.view_interface',
82 | )
83 |
84 | bulk_update_data = {
85 | 'description': "A test description"
86 | }
87 |
88 | @classmethod
89 | def setUpTestData(cls):
90 |
91 | device = create_test_device(name='Test Device')
92 | instance = OSPFInstance.objects.create(name='Instance 1', device=device, router_id='1.1.1.1', process_id=1)
93 | area = OSPFArea.objects.create(area_id='0')
94 |
95 | interfaces = (
96 | Interface(device=device, name='Interface 1', type='virtual'),
97 | Interface(device=device, name='Interface 2', type='virtual'),
98 | Interface(device=device, name='Interface 3', type='virtual'),
99 | Interface(device=device, name='Interface 4', type='virtual'),
100 | )
101 | Interface.objects.bulk_create(interfaces)
102 |
103 | data = (
104 | cls.model(instance=instance, area=area, interface=interfaces[0], passive=True),
105 | cls.model(instance=instance, area=area, interface=interfaces[1], passive=False),
106 | cls.model(instance=instance, area=area, interface=interfaces[2], ),
107 | )
108 | cls.model.objects.bulk_create(data)
109 |
110 | cls.create_data = [
111 | {
112 | 'instance': instance.pk,
113 | 'area': area.pk,
114 | 'interface': interfaces[3].pk,
115 | 'passive': True,
116 | 'priority': 2,
117 | 'authentication': 'message-digest',
118 | 'passphrase': 'test-password',
119 | 'bfd': True,
120 | },
121 | ]
122 |
--------------------------------------------------------------------------------
/netbox_routing/tests/ospf/test_forms.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | from dcim.models import Device, Interface
4 | from ipam.models import VRF
5 | from utilities.testing import create_test_device
6 |
7 | from netbox_routing.forms import *
8 | from netbox_routing.models import *
9 |
10 | __all__ = (
11 | 'OSPFInstanceTestCase',
12 | 'OSPFAreaTestCase',
13 | 'OSPFInterfaceTestCase',
14 | )
15 |
16 |
17 | class OSPFInstanceTestCase(TestCase):
18 |
19 | @classmethod
20 | def setUpTestData(cls):
21 | vrf = VRF.objects.create(name='Test VRF')
22 | device = create_test_device(name='Device 1')
23 |
24 | def test_instance(self):
25 | form = OSPFInstanceForm(data={
26 | 'name': 'Instance 1',
27 | 'process_id': '0',
28 | 'router_id': '10.10.10.1',
29 | 'device': Device.objects.first().pk,
30 | })
31 | if not form.is_valid():
32 | print(form.errors)
33 | self.assertTrue(form.is_valid())
34 | self.assertTrue(form.save())
35 |
36 | def test_instance_with_vrf(self):
37 | form = OSPFInstanceForm(data={
38 | 'name': 'Instance 2',
39 | 'process_id': '1',
40 | 'router_id': '20.20.20.1',
41 | 'device': Device.objects.first().pk,
42 | 'vrf': VRF.objects.first().pk,
43 | })
44 | if not form.is_valid():
45 | print(form.errors)
46 | self.assertTrue(form.is_valid())
47 | self.assertTrue(form.save())
48 |
49 |
50 | class OSPFAreaTestCase(TestCase):
51 |
52 | @classmethod
53 | def setUpTestData(cls):
54 | pass
55 |
56 | def test_valid_area_id_with_ip(self):
57 | form = OSPFAreaForm(data={
58 | 'area_id': '0.0.0.0',
59 | })
60 | self.assertTrue(form.is_valid())
61 | self.assertTrue(form.save())
62 |
63 | def test_valid_area_id_with_integer(self):
64 | form = OSPFAreaForm(data={
65 | 'area_id': '0',
66 | })
67 | self.assertTrue(form.is_valid())
68 | self.assertTrue(form.save())
69 |
70 | def test_invalid_area(self):
71 | form = OSPFAreaForm(data={
72 | 'area_id': 'a.a.a.a',
73 | })
74 | self.assertFalse(form.is_valid())
75 | with self.assertRaises(ValueError):
76 | form.save()
77 |
78 |
79 | class OSPFInterfaceTestCase(TestCase):
80 |
81 | @classmethod
82 | def setUpTestData(cls):
83 | device = create_test_device(name='Device 1')
84 | interface = Interface.objects.create(name='Interface 1', device=device, type='virtual')
85 | instance = OSPFInstance.objects.create(
86 | name='Instance 1',
87 | router_id='10.10.10.1',
88 | process_id=0,
89 | device_id=device.pk
90 | )
91 | area = OSPFArea.objects.create(area_id='0.0.0.0')
92 |
93 | def test_interface_with_correct_device(self):
94 | form = OSPFInterfaceForm(data={
95 | 'device': Device.objects.first().pk,
96 | 'interface': Interface.objects.first().pk,
97 | 'instance': OSPFInstance.objects.first().pk,
98 | 'area': OSPFArea.objects.first().pk,
99 | 'passive': True,
100 | })
101 | self.assertTrue(form.is_valid())
102 | self.assertTrue(form.save())
103 |
104 | def test_interface_with_incorrect_device(self):
105 | device = create_test_device(name='Device 2')
106 | interface = Interface.objects.create(name='Interface 1', device=device, type='virtual')
107 |
108 | form = OSPFInterfaceForm(data={
109 | 'device': device.pk,
110 | 'interface': interface.pk,
111 | 'instance': OSPFInstance.objects.first().pk,
112 | 'area': OSPFArea.objects.first().pk,
113 | })
114 | self.assertFalse(form.is_valid())
115 | with self.assertRaises(ValueError):
116 | form.save()
117 |
--------------------------------------------------------------------------------
/netbox_routing/tests/ospf/test_models.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | from netbox_routing.models import *
4 |
5 | __all__ = (
6 | 'OSPFInstanceTestCase',
7 | 'OSPFAreaTestCase',
8 | 'OSPFInterfaceTestCase',
9 | )
10 |
11 |
12 | class OSPFInstanceTestCase(TestCase):
13 | pass
14 |
15 |
16 | class OSPFAreaTestCase(TestCase):
17 | pass
18 |
19 |
20 | class OSPFInterfaceTestCase(TestCase):
21 | pass
22 |
--------------------------------------------------------------------------------
/netbox_routing/tests/static/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanSheps/netbox-routing/56ec2104ef304157cb27e3ec75d16597031d08c6/netbox_routing/tests/static/__init__.py
--------------------------------------------------------------------------------
/netbox_routing/tests/static/test_api.py:
--------------------------------------------------------------------------------
1 | from netaddr.ip import IPAddress
2 | from ipam.models import VRF
3 | from utilities.testing import APIViewTestCases, create_test_device
4 |
5 | from netbox_routing.models import StaticRoute
6 | from netbox_routing.tests.base import IPAddressFieldMixin
7 |
8 | __all__ = (
9 | 'StaticRouteTestCase',
10 | )
11 |
12 | class StaticRouteTestCase(IPAddressFieldMixin, APIViewTestCases.APIViewTestCase):
13 | model = StaticRoute
14 | view_namespace = "plugins-api:netbox_routing"
15 | brief_fields = ['description', 'display', 'id', 'name', 'next_hop', 'prefix', 'url', ]
16 |
17 |
18 | bulk_update_data = {
19 | 'metric': 5
20 | }
21 |
22 | @classmethod
23 | def setUpTestData(cls):
24 |
25 | device = create_test_device(name='Test Device')
26 | vrf = VRF.objects.create(name='Test VRF')
27 |
28 | nh = IPAddress('10.10.10.1')
29 |
30 | routes = (
31 | StaticRoute(name='Test Route 1', vrf=vrf, prefix='0.0.0.0/0', next_hop=nh),
32 | StaticRoute(name='Test Route 2', vrf=None, prefix='1.1.1.1/32', next_hop=nh),
33 | StaticRoute(name='Test Route 3', vrf=vrf, prefix='2.2.2.2/32', next_hop=nh),
34 | )
35 | StaticRoute.objects.bulk_create(routes)
36 |
37 | routes[0].devices.set([device])
38 | routes[1].devices.set([device])
39 | routes[2].devices.set([device])
40 |
41 | cls.create_data = [
42 | {
43 | 'name': 'Default Route',
44 | 'devices': [device.pk],
45 | 'vrf': vrf.pk,
46 | 'prefix': '0.0.0.0/0',
47 | 'next_hop': '10.10.10.2',
48 | 'metric': 1,
49 | 'permanent': True
50 | },
51 | {
52 | 'name': 'Google DNS',
53 | 'devices': [device.pk],
54 | 'vrf': None,
55 | 'prefix': '4.4.4.4/32',
56 | 'next_hop': '10.10.10.1',
57 | 'metric': 1,
58 | 'permanent': True
59 | },
60 | {
61 | 'name': 'One dot one dot one dot one',
62 | 'devices': [device.pk],
63 | 'vrf': None,
64 | 'prefix': '1.1.1.0/24',
65 | 'next_hop': '10.10.10.1',
66 | 'metric': 1,
67 | 'permanent': True
68 | },
69 | ]
70 |
--------------------------------------------------------------------------------
/netbox_routing/tests/static/test_filtersets.py:
--------------------------------------------------------------------------------
1 | import netaddr
2 | from django.test import TestCase
3 |
4 | from ipam.models import VRF
5 | from utilities.testing import create_test_device
6 |
7 | from netbox_routing.filtersets import *
8 | from netbox_routing.models import *
9 |
10 | __all__ = (
11 | 'StaticRouteTestCase',
12 | )
13 |
14 |
15 | class StaticRouteTestCase(TestCase):
16 | queryset = StaticRoute.objects.all()
17 | filterset = StaticRouteFilterSet
18 |
19 | @classmethod
20 | def setUpTestData(cls):
21 | devices = [create_test_device(name='Device 1'), create_test_device(name='Device 2')]
22 | vrf = VRF.objects.create(name='Test VRF')
23 |
24 | nh = netaddr.IPAddress('10.10.10.1')
25 |
26 | routes = (
27 | StaticRoute(name="Route 1", vrf=vrf, prefix='0.0.0.0/0', next_hop=nh),
28 | StaticRoute(name="Route 2", vrf=vrf, prefix='1.1.1.0/24', next_hop=netaddr.IPAddress('10.10.10.2')),
29 | StaticRoute(name="Route 3", prefix='0.0.0.0/0', next_hop=nh, metric=100)
30 | )
31 |
32 | StaticRoute.objects.bulk_create(routes)
33 |
34 | routes[0].devices.set([devices[0]])
35 | routes[1].devices.set([devices[0]])
36 | routes[2].devices.set([devices[1]])
37 |
38 | def test_q(self):
39 | params = {'q': 'Route 1'}
40 | self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
41 |
42 | def test_name(self):
43 | params = {'name': ['Route 1', 'Route 2']}
44 | self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
45 |
46 | def test_device(self):
47 | params = {'device': ['Device 1']}
48 | self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
49 |
50 | def test_vrf(self):
51 | params = {'vrf': ['Test VRF']}
52 | self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
53 |
54 | def test_prefix(self):
55 | params = {'prefix': '0.0.0.0/0'}
56 | self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
57 |
58 | def test_next_hop(self):
59 | params = {'next_hop': '10.10.10.1'}
60 | self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
61 |
62 | def test_metric(self):
63 | params = {'metric': [1]}
64 | self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
65 |
--------------------------------------------------------------------------------
/netbox_routing/tests/static/test_forms.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | from dcim.models import Device
4 | from utilities.testing import create_test_device
5 |
6 | from netbox_routing.forms import *
7 |
8 | __all__ = (
9 | 'StaticRouteTestCase',
10 | )
11 |
12 |
13 | class StaticRouteTestCase(TestCase):
14 |
15 | @classmethod
16 | def setUpTestData(cls):
17 | device = create_test_device(name='Device 1')
18 |
19 | def test_staticroute(self):
20 | form = StaticRouteForm(data={
21 | 'name': 'Route 1',
22 | 'devices': [Device.objects.first().pk],
23 | 'vrf': None,
24 | 'prefix': '0.0.0.0/0',
25 | 'next_hop': '10.10.10.1',
26 | 'metric': 1,
27 | })
28 | self.assertTrue(form.is_valid())
29 | self.assertTrue(form.save())
30 |
--------------------------------------------------------------------------------
/netbox_routing/tests/static/test_models.py:
--------------------------------------------------------------------------------
1 | from django.core.exceptions import ValidationError
2 | from django.test import TestCase
3 |
4 | from utilities.testing import create_test_device
5 |
6 | from netbox_routing.models import *
7 |
8 | __all__ = (
9 | 'StaticRouteTestCase',
10 | )
11 |
12 |
13 | class StaticRouteTestCase(TestCase):
14 | pass
15 |
--------------------------------------------------------------------------------
/netbox_routing/tests/static/test_views.py:
--------------------------------------------------------------------------------
1 | import netaddr
2 |
3 | from ipam.models import VRF
4 | from utilities.testing import ViewTestCases, create_test_device
5 |
6 | from netbox_routing.models import StaticRoute
7 |
8 | __all__ = (
9 | 'StaticRouteTestCase',
10 | )
11 |
12 |
13 | class StaticRouteTestCase(
14 | ViewTestCases.GetObjectViewTestCase,
15 | ViewTestCases.GetObjectChangelogViewTestCase,
16 | ViewTestCases.CreateObjectViewTestCase,
17 | ViewTestCases.EditObjectViewTestCase,
18 | ViewTestCases.DeleteObjectViewTestCase,
19 | ViewTestCases.ListObjectsViewTestCase,
20 | ViewTestCases.BulkEditObjectsViewTestCase,
21 | ViewTestCases.BulkDeleteObjectsViewTestCase
22 | ):
23 | # ViewTestCases.BulkImportObjectsViewTestCase,
24 | model = StaticRoute
25 |
26 | @classmethod
27 | def setUpTestData(cls):
28 | devices = [create_test_device(name='Device 1'), create_test_device(name='Device 2')]
29 | vrf = VRF.objects.create(name='Test VRF')
30 |
31 | nh = netaddr.IPAddress('10.10.10.1')
32 |
33 | routes = (
34 | StaticRoute(name="Route 1", vrf=vrf, prefix='0.0.0.0/0', next_hop=nh),
35 | StaticRoute(name="Route 2", vrf=vrf, prefix='1.1.1.0/24', next_hop=netaddr.IPAddress('10.10.10.2')),
36 | StaticRoute(name="Route 3", prefix='0.0.0.0/0', next_hop=nh, metric=100)
37 | )
38 |
39 | StaticRoute.objects.bulk_create(routes)
40 |
41 | routes[0].devices.set([devices[0]])
42 | routes[1].devices.set([devices[0]])
43 | routes[2].devices.set([devices[0]])
44 |
45 | cls.form_data = {
46 | 'name': 'Route X',
47 | 'devices': [devices[1].pk],
48 | 'vrf': vrf.pk,
49 | 'prefix': netaddr.IPNetwork('1.1.1.1/32'),
50 | 'next_hop': nh,
51 | }
52 |
53 | cls.bulk_edit_data = {
54 | 'metric': 10,
55 | }
56 |
57 | def _get_base_url(self):
58 | return 'plugins:netbox_routing:staticroute_{}'
59 |
--------------------------------------------------------------------------------
/netbox_routing/tests/test_api.py:
--------------------------------------------------------------------------------
1 | from django.urls import reverse
2 | from rest_framework import status
3 |
4 | from utilities.testing import APITestCase
5 |
6 | from netbox_routing.tests.eigrp.test_api import *
7 | from netbox_routing.tests.ospf.test_api import *
8 | from netbox_routing.tests.static.test_api import *
9 |
10 | __all__ = (
11 | 'AppTest',
12 |
13 | 'StaticRouteTestCase',
14 |
15 | 'OSPFInstanceTestCase',
16 | 'OSPFAreaTestCase',
17 | 'OSPFInterfaceTestCase',
18 |
19 | 'EIGRPRouterTestCase',
20 | 'EIGRPAddressFamilyTestCase',
21 | 'EIGRPNetworkTestCase',
22 | 'EIGRPInterfaceTestCase',
23 | )
24 |
25 | class AppTest(APITestCase):
26 | def test_root(self):
27 | url = reverse("plugins-api:netbox_routing-api:api-root")
28 | response = self.client.get(f"{url}?format=api", **self.header)
29 |
30 | self.assertEqual(response.status_code, status.HTTP_200_OK)
31 |
--------------------------------------------------------------------------------
/netbox_routing/tests/test_filtersets.py:
--------------------------------------------------------------------------------
1 | from netbox_routing.tests.eigrp.test_filtersets import *
2 | from netbox_routing.tests.ospf.test_filtersets import *
3 | from netbox_routing.tests.static.test_filtersets import *
4 |
5 | __all__ = (
6 | 'StaticRouteTestCase',
7 |
8 | 'OSPFInstanceTestCase',
9 | 'OSPFAreaTestCase',
10 | 'OSPFInterfaceTestCase',
11 |
12 | 'EIGRPRouterTestCase',
13 | 'EIGRPAddressFamilyTestCase',
14 | 'EIGRPNetworkTestCase',
15 | 'EIGRPInterfaceTestCase',
16 | )
17 |
--------------------------------------------------------------------------------
/netbox_routing/tests/test_forms.py:
--------------------------------------------------------------------------------
1 | from netbox_routing.tests.eigrp.test_forms import *
2 | from netbox_routing.tests.ospf.test_forms import *
3 | from netbox_routing.tests.static.test_forms import *
4 |
5 | __all__ = (
6 | 'StaticRouteTestCase',
7 |
8 | 'OSPFInstanceTestCase',
9 | 'OSPFAreaTestCase',
10 | 'OSPFInterfaceTestCase',
11 |
12 | 'EIGRPRouterTestCase',
13 | 'EIGRPAddressFamilyTestCase',
14 | 'EIGRPNetworkTestCase',
15 | 'EIGRPInterfaceTestCase',
16 | )
17 |
--------------------------------------------------------------------------------
/netbox_routing/tests/test_models.py:
--------------------------------------------------------------------------------
1 | from netbox_routing.tests.eigrp.test_models import *
2 | from netbox_routing.tests.ospf.test_models import *
3 | from netbox_routing.tests.static.test_models import *
4 |
5 | __all__ = (
6 | 'StaticRouteTestCase',
7 |
8 | 'OSPFInstanceTestCase',
9 | 'OSPFAreaTestCase',
10 | 'OSPFInterfaceTestCase',
11 |
12 | 'EIGRPRouterTestCase',
13 | 'EIGRPAddressFamilyTestCase',
14 | 'EIGRPNetworkTestCase',
15 | 'EIGRPInterfaceTestCase',
16 | )
17 |
--------------------------------------------------------------------------------
/netbox_routing/tests/test_views.py:
--------------------------------------------------------------------------------
1 | from netbox_routing.tests.eigrp.test_views import *
2 | from netbox_routing.tests.ospf.test_views import *
3 | from netbox_routing.tests.static.test_views import *
4 |
5 | __all__ = (
6 | 'StaticRouteTestCase',
7 |
8 | 'OSPFInstanceTestCase',
9 | 'OSPFAreaTestCase',
10 | 'OSPFInterfaceTestCase',
11 |
12 | #'EIGRPRouterTestCase',
13 | #'EIGRPAddressFamilyTestCase',
14 | #'EIGRPNetworkTestCase',
15 | #'EIGRPInterfaceTestCase',
16 |
17 | )
18 |
--------------------------------------------------------------------------------
/netbox_routing/views/__init__.py:
--------------------------------------------------------------------------------
1 | from .static import *
2 |
3 | from .objects import PrefixListView, PrefixListEditView, PrefixListListView, PrefixListDeleteView, RouteMapListView, \
4 | RouteMapView, RouteMapEditView, RouteMapDeleteView, PrefixListEntryListView, PrefixListEntryEditView, \
5 | PrefixListEntryDeleteView, PrefixListEntryView, RouteMapEntryListView, RouteMapEntryView, RouteMapEntryEditView, \
6 | RouteMapEntryDeleteView, PrefixListEntriesView, RouteMapEntriesView, RouteMapEntryBulkEditView, \
7 | RouteMapEntryBulkDeleteView, PrefixListEntryBulkDeleteView, PrefixListEntryBulkEditView
8 |
9 | from .ospf import *
10 | from .eigrp import *
11 | from .bgp import *
12 | from .core import *
13 |
14 | __all__ = (
15 | # Core View Extensions
16 | 'DeviceStaticRoutesView',
17 |
18 | # Static
19 | 'StaticRouteListView',
20 | 'StaticRouteView',
21 | 'StaticRouteDevicesView',
22 | 'StaticRouteEditView',
23 | 'StaticRouteBulkEditView',
24 | 'StaticRouteDeleteView',
25 | 'StaticRouteBulkDeleteView',
26 |
27 | # OSPF
28 | 'OSPFInstanceListView',
29 | 'OSPFInstanceView',
30 | 'OSPFInstanceEditView',
31 | 'OSPFInstanceDeleteView',
32 | 'OSPFInstanceInterfacesView',
33 |
34 | 'OSPFAreaListView',
35 | 'OSPFAreaView',
36 | 'OSPFAreaInterfacesView',
37 | 'OSPFAreaEditView',
38 | 'OSPFAreaDeleteView',
39 |
40 | 'OSPFInterfaceListView',
41 | 'OSPFInterfaceView',
42 | 'OSPFInterfaceEditView',
43 | 'OSPFInterfaceDeleteView',
44 |
45 | # EIGRP
46 | 'EIGRPRouterListView',
47 | 'EIGRPRouterView',
48 | 'EIGRPRouterInterfacesView',
49 | 'EIGRPRouterEditView',
50 | 'EIGRPRouterImportView',
51 | 'EIGRPRouterBulkEditView',
52 | 'EIGRPRouterDeleteView',
53 | 'EIGRPRouterBulkDeleteView',
54 |
55 | 'EIGRPAddressFamilyListView',
56 | 'EIGRPAddressFamilyView',
57 | 'EIGRPAddressFamilyInterfacesView',
58 | 'EIGRPAddressFamilyEditView',
59 | 'EIGRPAddressFamilyBulkEditView',
60 | 'EIGRPAddressFamilyDeleteView',
61 | 'EIGRPAddressFamilyBulkDeleteView',
62 |
63 | 'EIGRPNetworkListView',
64 | 'EIGRPNetworkView',
65 | 'EIGRPNetworkEditView',
66 | 'EIGRPNetworkBulkEditView',
67 | 'EIGRPNetworkDeleteView',
68 | 'EIGRPNetworkBulkDeleteView',
69 |
70 | 'EIGRPInterfaceListView',
71 | 'EIGRPInterfaceView',
72 | 'EIGRPInterfaceEditView',
73 | 'EIGRPInterfaceBulkEditView',
74 | 'EIGRPInterfaceDeleteView',
75 | 'EIGRPInterfaceBulkDeleteView',
76 |
77 | 'BGPRouterView',
78 | 'BGPRouterEditView',
79 |
80 | # Routing Objects
81 | 'PrefixListListView',
82 | 'PrefixListView',
83 | 'PrefixListEntriesView',
84 | 'PrefixListEditView',
85 | 'PrefixListDeleteView',
86 | 'PrefixListEntryListView',
87 | 'PrefixListEntryView',
88 | 'PrefixListEntryEditView',
89 | 'PrefixListEntryDeleteView',
90 | 'PrefixListEntryBulkEditView',
91 | 'PrefixListEntryBulkDeleteView',
92 |
93 | 'RouteMapListView',
94 | 'RouteMapView',
95 | 'RouteMapEntriesView',
96 | 'RouteMapEditView',
97 | 'RouteMapDeleteView',
98 | 'RouteMapEntryListView',
99 | 'RouteMapEntryView',
100 | 'RouteMapEntryEditView',
101 | 'RouteMapEntryDeleteView',
102 | 'RouteMapEntryBulkEditView',
103 | 'RouteMapEntryBulkDeleteView',
104 |
105 | )
106 |
--------------------------------------------------------------------------------
/netbox_routing/views/bgp.py:
--------------------------------------------------------------------------------
1 | from netbox.views.generic import ObjectView, ObjectEditView, ObjectListView, ObjectDeleteView
2 | from netbox_routing.filtersets import BGPRouterFilterSet, BGPScopeFilterSet, BGPAddressFamilyFilterSet
3 | from netbox_routing.forms import BGPRouterForm, BGPScopeForm, BGPAddressFamilyForm, BGPRouterFilterForm, \
4 | BGPSettingFilterForm, BGPAddressFamilyForm, BGPScopeFilterForm, BGPAddressFamilyFilterForm
5 | from netbox_routing.models import BGPRouter, BGPScope, BGPAddressFamily
6 | from netbox_routing.tables.bgp import BGPRouterTable, BGPScopeTable, BGPAddressFamilyTable
7 | from utilities.views import register_model_view
8 |
9 |
10 | #
11 | # BGP Router Views
12 | #
13 | @register_model_view(BGPRouter, name='list')
14 | class BGPRouterListView(ObjectListView):
15 | queryset = BGPRouter.objects.all()
16 | table = BGPRouterTable
17 | filterset = BGPRouterFilterSet
18 | filterset_form = BGPRouterFilterForm
19 |
20 |
21 | @register_model_view(BGPRouter)
22 | class BGPRouterView(ObjectView):
23 | queryset = BGPRouter.objects.all()
24 | template_name = 'netbox_routing/bgprouter.html'
25 |
26 |
27 | @register_model_view(BGPRouter, name='edit')
28 | class BGPRouterEditView(ObjectEditView):
29 | queryset = BGPRouter.objects.all()
30 | form = BGPRouterForm
31 |
32 |
33 | @register_model_view(BGPRouter, name='delete')
34 | class BGPRouterDeleteView(ObjectDeleteView):
35 | queryset = BGPRouter.objects.all()
36 |
37 |
38 | #
39 | # BGP Scope Views
40 | #
41 | @register_model_view(BGPScope, name='list')
42 | class BGPScopeListView(ObjectListView):
43 | queryset = BGPScope.objects.all()
44 | table = BGPScopeTable
45 | filterset = BGPScopeFilterSet
46 | filterset_form = BGPScopeFilterForm
47 |
48 |
49 | @register_model_view(BGPScope)
50 | class BGPScopeView(ObjectView):
51 | queryset = BGPScope.objects.all()
52 | template_name = 'netbox_routing/bgpscope.html'
53 |
54 |
55 | @register_model_view(BGPScope, name='edit')
56 | class BGPScopeEditView(ObjectEditView):
57 | queryset = BGPScope.objects.all()
58 | form = BGPScopeForm
59 |
60 |
61 | @register_model_view(BGPScope, name='delete')
62 | class BGPScopeDeleteView(ObjectDeleteView):
63 | queryset = BGPScope.objects.all()
64 |
65 |
66 | #
67 | # BGP Scope Views
68 | #
69 | @register_model_view(BGPAddressFamily, name='list')
70 | class BGPAddressFamilyListView(ObjectListView):
71 | queryset = BGPAddressFamily.objects.all()
72 | table = BGPAddressFamilyTable
73 | filterset = BGPAddressFamilyFilterSet
74 | filterset_form = BGPAddressFamilyFilterForm
75 |
76 |
77 | @register_model_view(BGPAddressFamily)
78 | class BGPAddressFamilyView(ObjectView):
79 | queryset = BGPAddressFamily.objects.all()
80 | template_name = 'netbox_routing/bgpaddressfamily.html'
81 |
82 |
83 | @register_model_view(BGPAddressFamily, name='edit')
84 | class BGPAddressFamilyEditView(ObjectEditView):
85 | queryset = BGPAddressFamily.objects.all()
86 | form = BGPAddressFamilyForm
87 |
88 |
89 | @register_model_view(BGPAddressFamily, name='delete')
90 | class BGPAddressFamilyDeleteView(ObjectDeleteView):
91 | queryset = BGPAddressFamily.objects.all()
92 |
--------------------------------------------------------------------------------
/netbox_routing/views/core.py:
--------------------------------------------------------------------------------
1 | from dcim.models import Device
2 | from netbox.views import generic
3 | from utilities.views import register_model_view, ViewTab
4 |
5 | from netbox_routing.filtersets import StaticRouteFilterSet
6 | from netbox_routing.models import StaticRoute
7 | from netbox_routing.tables.static import StaticRouteTable
8 |
9 |
10 | @register_model_view(Device, name='staticroutes', path='static_routes')
11 | class DeviceStaticRoutesView(generic.ObjectChildrenView):
12 | template_name = 'generic/object_children.html'
13 | queryset = Device.objects.all()
14 | child_model = StaticRoute
15 | table = StaticRouteTable
16 | filterset = StaticRouteFilterSet
17 | tab = ViewTab(
18 | label='Static Routes',
19 | badge=lambda obj: StaticRoute.objects.filter(devices=obj).count(),
20 | permission='netbox_routing.view_staticroute'
21 | )
22 |
23 | def get_children(self, request, device):
24 | return self.child_model.objects.filter(devices=device)
25 |
--------------------------------------------------------------------------------
/netbox_routing/views/static.py:
--------------------------------------------------------------------------------
1 | from dcim.filtersets import DeviceFilterSet
2 | from dcim.models import Device
3 | from dcim.tables import DeviceTable
4 | from netbox.views.generic import ObjectListView, ObjectEditView, ObjectView, ObjectDeleteView, ObjectChildrenView, \
5 | BulkDeleteView, BulkEditView
6 | from netbox_routing.filtersets.static import StaticRouteFilterSet
7 | from netbox_routing.forms import StaticRouteForm
8 | from netbox_routing.forms.bulk_edit import StaticRouteBulkEditForm
9 | from netbox_routing.forms.filtersets.static import StaticRouteFilterForm
10 | from netbox_routing.models import StaticRoute
11 | from netbox_routing.tables.static import StaticRouteTable
12 |
13 |
14 | __all__ = (
15 | 'StaticRouteListView',
16 | 'StaticRouteView',
17 | 'StaticRouteDevicesView',
18 | 'StaticRouteEditView',
19 | 'StaticRouteBulkEditView',
20 | 'StaticRouteDeleteView',
21 | 'StaticRouteBulkDeleteView',
22 | )
23 |
24 | from utilities.views import register_model_view, ViewTab
25 |
26 |
27 | @register_model_view(StaticRoute, name='list')
28 | class StaticRouteListView(ObjectListView):
29 | queryset = StaticRoute.objects.all()
30 | table = StaticRouteTable
31 | filterset = StaticRouteFilterSet
32 | filterset_form = StaticRouteFilterForm
33 |
34 |
35 | @register_model_view(StaticRoute)
36 | class StaticRouteView(ObjectView):
37 | queryset = StaticRoute.objects.all()
38 | template_name = 'netbox_routing/staticroute.html'
39 |
40 |
41 | @register_model_view(StaticRoute, name='devices')
42 | class StaticRouteDevicesView(ObjectChildrenView):
43 | template_name = 'netbox_routing/staticroute_devices.html'
44 | queryset = StaticRoute.objects.all()
45 | child_model = Device
46 | table = DeviceTable
47 | filterset = DeviceFilterSet
48 | tab = ViewTab(
49 | label='Assigned Devices',
50 | badge=lambda obj: Device.objects.filter(static_routes=obj).count(),
51 | )
52 |
53 | def get_children(self, request, parent):
54 | return self.child_model.objects.filter(static_routes=parent)
55 |
56 |
57 | @register_model_view(StaticRoute, name='edit')
58 | class StaticRouteEditView(ObjectEditView):
59 | queryset = StaticRoute.objects.all()
60 | form = StaticRouteForm
61 |
62 |
63 | @register_model_view(StaticRoute, name='bulk_edit')
64 | class StaticRouteBulkEditView(BulkEditView):
65 | queryset = StaticRoute.objects.all()
66 | filterset = StaticRouteFilterSet
67 | table = StaticRouteTable
68 | form = StaticRouteBulkEditForm
69 |
70 |
71 | @register_model_view(StaticRoute, name='delete')
72 | class StaticRouteDeleteView(ObjectDeleteView):
73 | queryset = StaticRoute.objects.all()
74 |
75 |
76 | @register_model_view(StaticRoute, name='bulk_delete')
77 | class StaticRouteBulkDeleteView(BulkDeleteView):
78 | queryset = StaticRoute.objects.all()
79 | filterset = StaticRouteFilterSet
80 | table = StaticRouteTable
81 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = [
3 | "setuptools",
4 | "wheel"
5 | ]
6 | build-backend = "setuptools.build_meta"
7 |
8 | [project]
9 | name = "netbox-routing"
10 | authors = [
11 | {name = "Daniel Sheppard", email = "dans@dansheps.com"}
12 | ]
13 | maintainers = [
14 | {name = "Daniel Sheppard", email = "dans@dansheps.com"},
15 | ]
16 | description = "A NetBox Routing Plugin"
17 | readme = "README.md"
18 | requires-python = ">=3.10"
19 | keywords = ["netbox-plugin", ]
20 | version = "0.3.1"
21 | license = {file = "LICENSE"}
22 | classifiers = [
23 | "Programming Language :: Python :: 3",
24 | ]
25 | dependencies = [
26 | 'django-polymorphic',
27 | ]
28 |
29 | [project.urls]
30 | Documentation = "https://github.com/dansheps/netbox-routing/blob/main/README.md"
31 | Source = "https://github.com/dansheps/netbox-routing"
32 | Tracker = "https://github.com/dansheps/netbox-routing/issues"
33 |
34 | [tool.setuptools.packages.find]
35 | exclude=["netbox_routing.tests"]
36 |
37 | [tool.black]
38 | skip-string-normalization = 1
39 |
--------------------------------------------------------------------------------