├── .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 |
BGP Address Family
11 |
12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 24 | 25 |
Scope 16 | {{ object.scope|linkify }} 17 |
Address Family 22 | {{ object.address_family }} 23 |
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 |
BGP Router
11 |
12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 24 | 25 |
Device 16 | {{ object.device|linkify }} 17 |
AS Number 22 | {{ object.asn|linkify }} 23 |
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 |
BGP Scope
11 |
12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 24 | 25 |
Router 16 | {{ object.router|linkify }} 17 |
AS Number 22 | {{ object.vrf|linkify }} 23 |
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 |
EIGRP Details
11 |
12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | 36 | 37 |
Router 16 | {{ object.router }} 17 |
VRF 22 | {{ object.vrf }} 23 |
Family 28 | {{ object.family }} 29 |
Router ID 34 | {% if object.rid %}{{ object.rid }}{% else %}{{ object.router.rid}}{% endif %} 35 |
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 | 15 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 30 | 31 |
Router 16 | {{ object.router|linkify }} 17 |
Address Family 22 | {{ object.address_family|linkify }} 23 |
Interface 28 | {{ object.interface|linkify }} 29 |
32 |
33 |
34 |
35 |
Attributes
36 |
37 | 38 | 39 | 40 | 43 | 44 | 45 | 46 | 49 | 50 | 51 | 52 | 55 | 56 | 57 | 58 | 61 | 62 |
Passive 41 | {{ object.passive|placeholder }} 42 |
BFD 47 | {{ object.bfd|placeholder }} 48 |
Authentication Type 53 | {{ object.authentication|placeholder }} 54 |
Passphrase (or keyring) 59 | {{ object.passphrase|placeholder }} 60 |
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 | 15 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 30 | 31 |
Router 16 | {{ object.router|linkify }} 17 |
Address Family 22 | {{ object.address_family|linkify }} 23 |
Network 28 | {{ object.network|linkify }} 29 |
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 |
EIGRP Details
11 |
12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | 36 | 37 |
Device 16 | {{ object.device|linkify }} 17 |
Router ID 22 | {{ object.rid }} 23 |
Name 28 | {{ object.name }} 29 |
Process ID 34 | {{ object.process_id }} 35 |
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 |
Settings
3 |
4 | 5 | {% for setting in object.settings.all %} 6 | 7 | 8 | 11 | 12 | {% endfor %} 13 |
{{ setting.get_key_display }} 9 | {{ setting.value }} 10 |
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 |
8 | 10 | Add OSPF Interface 11 | 12 |
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 |
10 | {% csrf_token %} 11 | 12 |
13 |
14 | {% include 'htmx/table.html' %} 15 |
16 |
17 | 18 |
19 |
20 | {% if 'bulk_edit' in actions %} 21 |
22 | 25 |
26 | {% endif %} 27 |
28 | {% if 'bulk_delete' in actions %} 29 | 32 | {% endif %} 33 |
34 |
35 | {% if 'add' in actions %} 36 |
37 | 38 | Add 39 | 40 |
41 | {% endif %} 42 |
43 |
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 |
OSPF Details
11 |
12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | 36 | 37 | 38 | 39 | 42 | 43 |
Name 16 | {{ object.name }} 17 |
Router ID 22 | {{ object.router_id }} 23 |
Process ID 28 | {{ object.process_id }} 29 |
Device 34 | {{ object.device|linkify }} 35 |
VRF 40 | {{ object.vrf|linkify }} 41 |
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 | 15 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 30 | 31 |
Instance 16 | {{ object.instance|linkify }} 17 |
Area 22 | {{ object.area|linkify }} 23 |
Interface 28 | {{ object.interface|linkify }} 29 |
32 |
33 |
34 |
35 |
Attributes
36 |
37 | 38 | 39 | 40 | 43 | 44 | 45 | 46 | 49 | 50 | 51 | 52 | 55 | 56 | 57 | 58 | 61 | 62 | 63 | 64 | 67 | 68 |
Passive 41 | {{ object.passive|placeholder }} 42 |
BFD 47 | {{ object.bfd|placeholder }} 48 |
Priority 53 | {{ object.priority|placeholder }} 54 |
Authentication Type 59 | {{ object.authentication|placeholder }} 60 |
Passphrase (or keyring) 65 | {{ object.passphrase|placeholder }} 66 |
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 |
Static Route
11 |
12 | 13 | 14 | 15 | 18 | 19 |
Name 16 | {{ object.name }} 17 |
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 |
Prefix List Entry
11 |
12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | 36 | 37 | 38 | 39 | 42 | 43 | 44 | 45 | 48 | 49 |
Prefix List 16 | {{ object.prefix_list|linkify }} 17 |
Sequence 22 | {{ object.sequence }} 23 |
Type 28 | {% badge object.get_type_display bg_color=object.get_type_color %} 29 |
Prefix 34 | {{ object.prefix|placeholder }} 35 |
GE 40 | {{ object.ge }} 41 |
LE 46 | {{ object.le }} 47 |
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 |
Static Route
11 |
12 | 13 | 14 | 15 | 18 | 19 |
Name 16 | {{ object.name }} 17 |
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 |
Route Map Entry
11 |
12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 30 | 31 |
Route Map 16 | {{ object.route_map|linkify }} 17 |
Type 22 | {% badge object.get_type_display bg_color=object.get_type_color %} 23 |
Sequence 28 | {{ object.sequence }} 29 |
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 |
Details
11 |
12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 24 | 25 |
Name 16 | {{object.name}} 17 |
Description 22 | {{object.description}} 23 |
26 |
27 |
28 |
29 |
Static Route
30 |
31 | 32 | 33 | 34 | 41 | 42 | 43 | 44 | 47 | 48 | 49 | 50 | 53 | 54 | 55 | 56 | 59 | 60 | 61 | 62 | 65 | 66 | 67 | 68 | 71 | 72 |
VRF 35 | {% if object.vrf %} 36 | {{ object.vrf }} 37 | {% else %} 38 | Global 39 | {% endif %} 40 |
Prefix 45 | {{ object.prefix }} 46 |
Next Hop 51 | {{ object.next_hop }} 52 |
Name 57 | {{ object.name|placeholder }} 58 |
Metric 63 | {{ object.metric }} 64 |
Permanent 69 | {{ object.permanent }} 70 |
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 |
10 | {% csrf_token %} 11 | 12 |
13 |
14 | {% include 'htmx/table.html' %} 15 |
16 |
17 |
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 | --------------------------------------------------------------------------------