├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yaml │ ├── config.yml │ └── feature_request.yaml ├── pull_request_template.md └── workflows │ ├── codeql-analysis.yml │ ├── manifest-modified.yaml │ └── pythonpublish.yml ├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── SECURITY.md ├── dev_setup ├── README.md ├── configuration.py └── docker-compose.yml ├── doc └── img │ ├── topology_dark.png │ ├── topology_filter_options.png │ ├── topology_images.png │ ├── topology_individual_options.png │ └── topology_light.png ├── netbox-plugin.yaml ├── netbox_topology_views ├── __init__.py ├── api │ ├── __init__.py │ ├── serializers.py │ ├── urls.py │ └── views.py ├── choices.py ├── filters.py ├── forms.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_individualoptions.py │ ├── 0003_individualoptions_show_neighbors.py │ ├── 0004_coordinategroup_coordinate.py │ ├── 0005_individualoptions_save_coords.py │ ├── 0006_powerpanelcoordinate_powerfeedcoordinate_and_more.py │ ├── 0007_individualoptions_group_locations_and_more.py │ ├── 0008_straight_cables.py │ ├── 0009_individualoptions_group_virtualchassis.py │ ├── 0010_individualoptions_grid_size.py │ ├── 0011_individualoptions_node_label_items.py │ └── __init__.py ├── models.py ├── navigation.py ├── search.py ├── signals.py ├── static │ └── netbox_topology_views │ │ ├── css │ │ ├── app.css │ │ └── vendor.css │ │ ├── img │ │ ├── LICENSE │ │ ├── access-switch.svg │ │ ├── backhaul.svg │ │ ├── backup.svg │ │ ├── cable-doubler.svg │ │ ├── camera-server.svg │ │ ├── camera.svg │ │ ├── circuit.svg │ │ ├── console-server.svg │ │ ├── core-router.svg │ │ ├── core-switch.svg │ │ ├── database-server.svg │ │ ├── database.svg │ │ ├── dect-station.svg │ │ ├── desktop.svg │ │ ├── distribution-switch.svg │ │ ├── docking-station.svg │ │ ├── environment-monitor.svg │ │ ├── fire-alarm-control-panel.svg │ │ ├── firewall.svg │ │ ├── fo-patch-panel.svg │ │ ├── intrusion-alarm-system.svg │ │ ├── kvm-over-ip.svg │ │ ├── kvm.svg │ │ ├── load-balancer.svg │ │ ├── mobile-phone.svg │ │ ├── modem.svg │ │ ├── network-socket.svg │ │ ├── notebook.svg │ │ ├── pabx.svg │ │ ├── patch-panel.svg │ │ ├── pdu.svg │ │ ├── phone.svg │ │ ├── power-panel.svg │ │ ├── power-units.svg │ │ ├── printer.svg │ │ ├── provider-networks.svg │ │ ├── proxy.svg │ │ ├── role-unknown.svg │ │ ├── router.svg │ │ ├── server.svg │ │ ├── siem.svg │ │ ├── storage.svg │ │ ├── switch.svg │ │ ├── time-recording-terminal.svg │ │ ├── ups.svg │ │ ├── usb-lan-adapter.svg │ │ ├── wan-network.svg │ │ └── wireless-ap.svg │ │ └── js │ │ ├── app.js │ │ ├── images.js │ │ └── images.js.map ├── static_dev │ ├── bundle.js │ ├── css │ │ ├── _external.scss │ │ └── app.scss │ ├── js │ │ ├── csrftoken.js │ │ ├── home.js │ │ ├── images.js │ │ └── toast.js │ ├── package-lock.json │ └── package.json ├── tables.py ├── template_content.py ├── templates │ └── netbox_topology_views │ │ ├── circuitcoordinate.html │ │ ├── circuitcoordinate_add.html │ │ ├── circuitcoordinate_edit.html │ │ ├── circuitcoordinate_list.html │ │ ├── coordinate.html │ │ ├── coordinate_add.html │ │ ├── coordinate_edit.html │ │ ├── coordinate_list.html │ │ ├── coordinategroup.html │ │ ├── coordinategroup_add.html │ │ ├── coordinategroup_edit.html │ │ ├── coordinategroup_list.html │ │ ├── htmx_topology.html │ │ ├── images.html │ │ ├── index.html │ │ ├── individual_options.html │ │ ├── location_button.html │ │ ├── powerfeedcoordinate.html │ │ ├── powerfeedcoordinate_add.html │ │ ├── powerfeedcoordinate_edit.html │ │ ├── powerfeedcoordinate_list.html │ │ ├── powerpanelcoordinate.html │ │ ├── powerpanelcoordinate_add.html │ │ ├── powerpanelcoordinate_edit.html │ │ ├── powerpanelcoordinate_list.html │ │ ├── site_button.html │ │ └── toasts.html ├── urls.py ├── utils.py └── views.py ├── requirements.txt └── setup.py /.github/ISSUE_TEMPLATE/bug_report.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug Report 3 | description: Report a reproducible bug in the current release of NetBox 4 | labels: ["type: bug"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: > 9 | **NOTE:** This form is only for reporting _reproducible bugs_. If you're having 10 | trouble with installation or just looking for assistance with using NetBox 11 | Topology Views, please visit our 12 | [discussion forum](https://github.com/mattieserver/netbox-topology-views/discussions) instead. 13 | - type: input 14 | attributes: 15 | label: NetBox version 16 | description: What version of NetBox are you currently running? 17 | placeholder: v3.3.9 18 | validations: 19 | required: true 20 | - type: input 21 | attributes: 22 | label: Topology Views version 23 | description: What version of NetBox Topology Views are you currently running? 24 | placeholder: v3.1.0 25 | validations: 26 | required: true 27 | - type: textarea 28 | attributes: 29 | label: Steps to Reproduce 30 | description: > 31 | Describe in detail the exact steps that someone else can take to 32 | reproduce this bug using the current stable release of NetBox Topology Views. 33 | placeholder: | 34 | 1. Click on "create widget" 35 | 2. Set foo to 12 and bar to G 36 | 3. Click the "create" button 37 | validations: 38 | required: true 39 | - type: textarea 40 | attributes: 41 | label: Expected Behavior 42 | description: What did you expect to happen? 43 | placeholder: A new widget should have been created with the specified attributes 44 | validations: 45 | required: true 46 | - type: textarea 47 | attributes: 48 | label: Observed Behavior 49 | description: What happened instead? 50 | placeholder: A TypeError exception was raised 51 | validations: 52 | required: true 53 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: ❓ Discussion 4 | url: https://github.com/mattieserver/netbox-topology-views/discussions 5 | about: "If you're just looking for help, try starting a discussion instead" 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: ✨ Feature Request 3 | description: Propose a new NetBox Topology Views 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 | NetBox Topology Views in some way. If you're trying to solve a problem but can't figure 11 | out how, or if you still need time to work on the details of a proposed new feature, 12 | please start a [discussion](https://github.com/mattieserver/netbox-topology-views/discussions) instead. 13 | - type: input 14 | attributes: 15 | label: NetBox version 16 | description: What version of NetBox are you currently running? 17 | placeholder: v3.3.9 18 | validations: 19 | required: true 20 | - type: input 21 | attributes: 22 | label: Topology Views version 23 | description: What version of NetBox Topology Views are you currently running? 24 | placeholder: v3.1.0 25 | validations: 26 | required: true 27 | - type: dropdown 28 | attributes: 29 | label: Feature type 30 | options: 31 | - New functionality 32 | - Change to existing functionality 33 | validations: 34 | required: true 35 | - type: textarea 36 | attributes: 37 | label: Proposed functionality 38 | description: > 39 | Describe in detail the new feature or behavior you are proposing. Include any specific changes 40 | to work flows and/or the user interface. The more detail you provide here, the 41 | greater chance your proposal has of being discussed. Feature requests which don't include an 42 | actionable implementation plan will be rejected. 43 | validations: 44 | required: true 45 | - type: textarea 46 | attributes: 47 | label: Use case 48 | description: > 49 | Explain how adding this functionality would benefit NetBox Topology Views users. What need does it address? 50 | validations: 51 | required: true 52 | - type: textarea 53 | attributes: 54 | label: External dependencies 55 | description: > 56 | List any new dependencies on external libraries or services that this new feature would 57 | introduce. For example, does the proposal require the installation of a new Python package? 58 | (Not all new features introduce new dependencies.) 59 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 25 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '16 22 * * 6' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript', 'python' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v2 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /.github/workflows/manifest-modified.yaml: -------------------------------------------------------------------------------- 1 | name: NetBox plugin manifest modified 2 | 3 | on: 4 | push: 5 | branches: [ develop ] 6 | paths: 7 | - netbox-plugin.yaml 8 | 9 | concurrency: 10 | group: ${{ github.workflow }} 11 | cancel-in-progress: false 12 | 13 | jobs: 14 | manifest-modified: 15 | uses: netboxlabs/public-workflows/.github/workflows/reusable-plugin-manifest-modified.yml@release 16 | -------------------------------------------------------------------------------- /.github/workflows/pythonpublish.yml: -------------------------------------------------------------------------------- 1 | # This workflows will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: Upload Python Package 5 | 6 | on: 7 | release: 8 | types: [published] 9 | 10 | jobs: 11 | deploy: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Set up Python 18 | uses: actions/setup-python@v3 19 | with: 20 | python-version: '3.x' 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install build 25 | - name: Build package 26 | run: python -m build 27 | - name: Publish package 28 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 29 | with: 30 | user: __token__ 31 | password: ${{ secrets.PYPI_API_TOKEN }} 32 | -------------------------------------------------------------------------------- /.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 | node_modules 132 | app.js.map -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include netbox_topology_views/static * 2 | recursive-include netbox_topology_views/templates * 3 | recursive-include netbox_topology_views/api * 4 | prune netbox_topology_views/api/__pycache__ -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | 6 | | Version | Supported | 7 | | ------- | ------------------ | 8 | | > 2.0 | :white_check_mark: | 9 | | < 2.0 | :x: | 10 | 11 | ## Reporting a Vulnerability 12 | 13 | Please create an issue when you encounter a Security issue. 14 | -------------------------------------------------------------------------------- /dev_setup/README.md: -------------------------------------------------------------------------------- 1 | # Setup Dev 2 | 3 | We will assume that you work in the folder `~/code/netbox_plugin/`, change this if you use another folder for your projects/code. 4 | 5 | ## Setup native prerequisites 6 | **_NOTE:_** these requirements are needed if you want install Postgresql & Redis native. Check the steps for docker & docker-compose if you don't want this. 7 | 8 | Make sure that you have already installed Postgresql and Redis. Also make sure that the database has already been created and privileges has been granted as described in the NetBox installation instructions at https://docs.netbox.dev/en/stable/installation/. 9 | 10 | In short: 11 | ``` 12 | sudo apt update 13 | sudo apt install -y postgresql 14 | sudo systemctl start postgresql 15 | sudo systemctl enable postgresql 16 | 17 | sudo -u postgres psql 18 | CREATE DATABASE netbox; 19 | CREATE USER netbox WITH PASSWORD 'J5brHrAXFLQSif0K'; 20 | GRANT ALL PRIVILEGES ON DATABASE netbox TO netbox; 21 | 22 | sudo apt install -y redis-server 23 | ``` 24 | 25 | ## Setup docker prerequisites 26 | 27 | **_NOTE:_** if you used the 'native' prerequisites you can skip these steps below 28 | 29 | ### Docker 30 | This depends on your OS or how you want to install docker. 31 | See https://docs.docker.com/engine/install/ to find the install steps. 32 | 33 | ### Docker compose 34 | This also depends on your OS, see the link below: 35 | https://docs.docker.com/compose/install/linux/ 36 | 37 | ## Create the base folder 38 | ``` 39 | mkdir ~/code/netbox_plugin/ 40 | cd ~/code/netbox_plugin/ 41 | ``` 42 | 43 | ## Install requirements 44 | ### Python + deps 45 | You will need the following packages installed: 46 | + python3-venv 47 | + python3 48 | 49 | Install them with your OS package manager (apt, yum, ...) 50 | 51 | 52 | ### NPM (with nvm) 53 | Follow the steps on the repo below to install npm: 54 | https://github.com/nvm-sh/nvm#installing-and-updating 55 | 56 | 57 | ## Create venv 58 | ``` 59 | python3 -m venv ~/code/netbox_plugin/.venvs/netbox 60 | source ~/code/netbox_plugin/.venvs/netbox/bin/activate 61 | ``` 62 | 63 | ## Setup netbox 64 | ### Clone netbox repo 65 | ``` 66 | cd ~/code/netbox_plugin/ 67 | git clone git@github.com:netbox-community/netbox.git netbox 68 | ``` 69 | ### Install deps 70 | ``` 71 | cd netbox 72 | python3 -m pip install -r requirements.txt 73 | ``` 74 | 75 | 76 | ## Setup plugin 77 | ### Clone netbox-topology-views repo 78 | ``` 79 | cd ~/code/netbox_plugin/ 80 | git clone git@github.com:mattieserver/netbox-topology-views.git plugin 81 | ``` 82 | ### Install deps 83 | ``` 84 | python3 -m pip install --upgrade build setuptools wheel 85 | ``` 86 | ### Copy netbox config file 87 | ``` 88 | cd ~/code/netbox_plugin/ 89 | cp plugin/dev_setup/configuration.py netbox/netbox/netbox/configuration.py 90 | ``` 91 | 92 | ## Start netbox 93 | ### Start docker compose 94 | **_NOTE:_** if you used the 'native' prerequisites you can skip to 'Start netbox' 95 | ``` 96 | cd ~/code/netbox_plugin/ 97 | cd plugin/dev_setup 98 | docker-compose up -d 99 | ``` 100 | ### Start netbox 101 | ``` 102 | cd ~/code/netbox_plugin/ 103 | source ~/code/netbox_plugin/.venvs/netbox/bin/activate 104 | cd netbox/netbox/ 105 | python3 manage.py migrate 106 | python3 manage.py runserver 107 | ``` 108 | You might need to create a superuser if you want to start with an empty database: `python3 manage.py createsuperuser` 109 | 110 | # Build 111 | 112 | ## Build the plugin 113 | 114 | ``` 115 | cd ~/code/netbox_plugin/ 116 | source ~/code/netbox_plugin/.venvs/netbox/bin/activate 117 | cd plugin/ 118 | python3 setup.py develop 119 | ``` 120 | 121 | ## Build javascript 122 | ``` 123 | cd plugin/netbox_topology_views/static_dev 124 | npm install 125 | npm run bundle 126 | ``` 127 | 128 | ### Test 129 | You can now browse to http://localhost:8000. 130 | The plugin should be available in the menu. 131 | 132 | # Import demo data 133 | If you want to add demo data instead of your own data, there's an official Repository you can use. The following example shows how to add demo data for NetBox v3.4. You have to use the json file according to the version you are working on. 134 | 135 | ``` 136 | ~/code/netbox_plugin/ 137 | git clone git@github.com:netbox-community/netbox-demo-data.git demodata 138 | source ~/code/netbox_plugin/.venvs/netbox/bin/activate 139 | cd netbox/netbox/ 140 | python3 manage.py loaddata -v 3 ~/src/netbox-demo-data/netbox-demo-v3.4.json 141 | ``` 142 | 143 | Please note that the demo data adds a superuser with credentials admin/admin. -------------------------------------------------------------------------------- /dev_setup/configuration.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': 'J5brHrAXFLQSif0K', 12 | 'HOST': 'localhost', 13 | 'PORT': '', 14 | 'CONN_MAX_AGE': 300, 15 | } 16 | 17 | DEBUG = True 18 | 19 | PLUGINS = [ 20 | 'netbox_topology_views', 21 | ] 22 | 23 | REDIS = { 24 | 'tasks': { 25 | 'HOST': 'localhost', 26 | 'PORT': 6379, 27 | 'PASSWORD': 't4Ph722qJ5QHeQ1qfu36', 28 | 'DATABASE': 0, 29 | 'SSL': False, 30 | }, 31 | 'caching': { 32 | 'HOST': 'localhost', 33 | 'PORT': 6378, 34 | 'PASSWORD': 'H733Kdjndks81', 35 | 'DATABASE': 1, 36 | 'SSL': False, 37 | } 38 | } 39 | 40 | SECRET_KEY = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' -------------------------------------------------------------------------------- /dev_setup/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.4' 2 | services: 3 | postgres: 4 | image: postgres:16-alpine 5 | environment: 6 | POSTGRES_PASSWORD: J5brHrAXFLQSif0K #just for local dev 7 | POSTGRES_DB: netbox 8 | POSTGRES_USER: netbox 9 | ports: 10 | - "5432:5432" 11 | volumes: 12 | - netbox-postgres-data-16:/var/lib/postgresql/data 13 | 14 | redis: 15 | image: redis:6-alpine 16 | command: 17 | - sh 18 | - -c # this is to evaluate the $REDIS_PASSWORD from the env 19 | - redis-server --appendonly yes --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose 20 | environment: 21 | REDIS_PASSWORD: t4Ph722qJ5QHeQ1qfu36 #just for local dev 22 | volumes: 23 | - netbox-redis-data:/data 24 | ports: 25 | - "6379:6379" 26 | redis-cache: 27 | image: redis:6-alpine 28 | command: 29 | - sh 30 | - -c # this is to evaluate the $REDIS_PASSWORD from the env 31 | - redis-server --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose 32 | environment: 33 | REDIS_PASSWORD: H733Kdjndks81 #just for local dev 34 | ports: 35 | - "6378:6379" 36 | 37 | volumes: 38 | netbox-postgres-data-16: 39 | driver: local 40 | netbox-redis-data: 41 | driver: local 42 | -------------------------------------------------------------------------------- /doc/img/topology_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbox-community/netbox-topology-views/79a234fcc97f64e474f04bb9b03b79179b38aeb1/doc/img/topology_dark.png -------------------------------------------------------------------------------- /doc/img/topology_filter_options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbox-community/netbox-topology-views/79a234fcc97f64e474f04bb9b03b79179b38aeb1/doc/img/topology_filter_options.png -------------------------------------------------------------------------------- /doc/img/topology_images.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbox-community/netbox-topology-views/79a234fcc97f64e474f04bb9b03b79179b38aeb1/doc/img/topology_images.png -------------------------------------------------------------------------------- /doc/img/topology_individual_options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbox-community/netbox-topology-views/79a234fcc97f64e474f04bb9b03b79179b38aeb1/doc/img/topology_individual_options.png -------------------------------------------------------------------------------- /doc/img/topology_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbox-community/netbox-topology-views/79a234fcc97f64e474f04bb9b03b79179b38aeb1/doc/img/topology_light.png -------------------------------------------------------------------------------- /netbox-plugin.yaml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | package_name: netbox-topology-views 3 | compatibility: 4 | - release: 4.3.0 5 | netbox_min: 4.3.0 6 | netbox_max: 4.3.1 7 | - release: 4.2.0 8 | netbox_min: 4.2.0 9 | netbox_max: 4.2.1 10 | - release: 4.1.0 11 | netbox_min: 4.1.0 12 | netbox_max: 4.1.11 13 | - release: 4.0.1 14 | netbox_min: 4.0.9 15 | netbox_max: 4.0.11 16 | - release: 4.0.0 17 | netbox_min: 4.0.0 18 | netbox_max: 4.0.9 19 | - release: 3.9.1 20 | netbox_min: 3.7.1 21 | netbox_max: 3.7.8 22 | - release: 3.9.0 23 | netbox_min: 3.7.1 24 | netbox_max: 3.7.8 25 | - release: 3.8.1 26 | netbox_min: 3.6.9 27 | netbox_max: 3.6.9 28 | - release: 3.6.2 29 | netbox_min: 3.5.1 30 | netbox_max: 3.5.9 31 | -------------------------------------------------------------------------------- /netbox_topology_views/__init__.py: -------------------------------------------------------------------------------- 1 | from netbox.plugins import PluginConfig 2 | 3 | 4 | class TopologyViewsConfig(PluginConfig): 5 | name = "netbox_topology_views" 6 | verbose_name = "Topology views" 7 | description = "An plugin to render topology maps" 8 | version = "4.3.0" 9 | author = "Mattijs Vanhaverbeke" 10 | author_email = "author@example.com" 11 | base_url = "netbox_topology_views" 12 | required_settings = [] 13 | default_settings = { 14 | "static_image_directory": "netbox_topology_views/img", 15 | "allow_coordinates_saving": False, 16 | "always_save_coordinates": False, 17 | } 18 | 19 | def ready(self): 20 | from . import signals 21 | 22 | super().ready() 23 | 24 | 25 | config = TopologyViewsConfig 26 | -------------------------------------------------------------------------------- /netbox_topology_views/api/__init__.py: -------------------------------------------------------------------------------- 1 | """REST API""" 2 | -------------------------------------------------------------------------------- /netbox_topology_views/api/serializers.py: -------------------------------------------------------------------------------- 1 | from dcim.models import Device, DeviceRole 2 | from rest_framework.serializers import ModelSerializer 3 | from netbox.api.serializers import NetBoxModelSerializer 4 | 5 | from netbox_topology_views.models import RoleImage, IndividualOptions, CoordinateGroup, Coordinate, CircuitCoordinate, PowerPanelCoordinate, PowerFeedCoordinate 6 | 7 | 8 | class TopologyDummySerializer(ModelSerializer): 9 | class Meta: 10 | model = Device 11 | fields = ("id", "name") 12 | 13 | 14 | class RoleImageSerializer(ModelSerializer): 15 | class Meta: 16 | model = RoleImage 17 | fields = ("role", "image") 18 | 19 | 20 | class DeviceRoleSerializer(ModelSerializer): 21 | class Meta: 22 | model = DeviceRole 23 | fields = ("name", "slug", "color", "vm_role", "description") 24 | 25 | class CoordinateGroupSerializer(NetBoxModelSerializer): 26 | class Meta: 27 | model = CoordinateGroup 28 | fields = ("name", "description") 29 | 30 | class CoordinateSerializer(NetBoxModelSerializer): 31 | class Meta: 32 | model = Coordinate 33 | fields = ("x", "y") 34 | 35 | class CircuitCoordinateSerializer(NetBoxModelSerializer): 36 | class Meta: 37 | model = CircuitCoordinate 38 | fields = ("x", "y") 39 | 40 | class PowerPanelCoordinateSerializer(NetBoxModelSerializer): 41 | class Meta: 42 | model = PowerPanelCoordinate 43 | fields = ("x", "y") 44 | 45 | class PowerFeedCoordinateSerializer(NetBoxModelSerializer): 46 | class Meta: 47 | model = PowerFeedCoordinate 48 | fields = ("x", "y") 49 | 50 | class IndividualOptionsSerializer(NetBoxModelSerializer): 51 | class Meta: 52 | model = IndividualOptions 53 | fields = ("ignore_cable_type", "save_coords", "show_unconnected", "show_cables", "show_logical_connections", "show_single_cable_logical_conns", "show_neighbors", "show_circuit", "show_power", "show_wireless", "group_sites", "group_locations", "group_racks", "group_virtualchassis", "draw_default_layout", "straight_cables", "grid_size", "node_label_items") 54 | -------------------------------------------------------------------------------- /netbox_topology_views/api/urls.py: -------------------------------------------------------------------------------- 1 | from netbox.api.routers import NetBoxRouter 2 | 3 | from netbox_topology_views.api import views 4 | 5 | router = NetBoxRouter() 6 | 7 | router.register("save-coords", views.SaveCoordsViewSet) 8 | router.register("images", views.SaveRoleImageViewSet) 9 | router.register("xml-export", views.ExportTopoToXML, "xml-export") 10 | router.register("coordinate-groups", views.CoordinateGroupViewSet) 11 | router.register("coordinate", views.CoordinateViewSet) 12 | router.register("circuitcoordinate", views.CircuitCoordinateViewSet) 13 | router.register("powerpanelcoordinate", views.PowerPanelCoordinateViewSet) 14 | router.register("powerfeedcoordinate", views.PowerFeedCoordinateViewSet) 15 | 16 | urlpatterns = router.urls 17 | -------------------------------------------------------------------------------- /netbox_topology_views/choices.py: -------------------------------------------------------------------------------- 1 | from utilities.choices import ChoiceSet 2 | 3 | class NodeLabelItems(ChoiceSet): 4 | DEVICE_NAME = 'devicename' 5 | PRIMARY_IPV4 = 'primaryipv4' 6 | PRIMARY_IPV6 = 'primaryipv6' 7 | OUT_OF_BAND_IP = 'outofbandip' 8 | 9 | CHOICES = [ 10 | (DEVICE_NAME, 'Device Name'), 11 | (PRIMARY_IPV4, 'Primary IPv4'), 12 | (PRIMARY_IPV6, 'Primary IPv6'), 13 | (OUT_OF_BAND_IP, 'Out-of-band IP'), 14 | ] 15 | -------------------------------------------------------------------------------- /netbox_topology_views/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.8 on 2022-12-06 13:07 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ('contenttypes', '0002_remove_content_type_name'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='RoleImage', 18 | fields=[ 19 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)), 20 | ('created', models.DateTimeField(auto_now_add=True, null=True)), 21 | ('last_updated', models.DateTimeField(auto_now=True, null=True)), 22 | ('image', models.CharField(max_length=255)), 23 | ('object_id', models.PositiveIntegerField(blank=True, null=True)), 24 | ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')), 25 | ], 26 | ), 27 | migrations.AddIndex( 28 | model_name='roleimage', 29 | index=models.Index(fields=['content_type', 'object_id'], name='netbox_topo_content_9d87d4_idx'), 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /netbox_topology_views/migrations/0002_individualoptions.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.4 on 2023-03-19 14:13 2 | 3 | from django.db import migrations, models 4 | import taggit.managers 5 | import utilities.json 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('dcim', '0167_module_status'), 12 | ('extras', '0084_staging'), 13 | ('netbox_topology_views', '0001_initial'), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='IndividualOptions', 19 | fields=[ 20 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)), 21 | ('created', models.DateTimeField(auto_now_add=True, null=True)), 22 | ('last_updated', models.DateTimeField(auto_now=True, null=True)), 23 | ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)), 24 | ('user_id', models.IntegerField(null=True, unique=True)), 25 | ('ignore_cable_type', models.CharField(blank=True, max_length=255)), 26 | ('show_unconnected', models.BooleanField(default=False)), 27 | ('show_cables', models.BooleanField(default=False)), 28 | ('show_logical_connections', models.BooleanField(default=False)), 29 | ('show_single_cable_logical_conns', models.BooleanField(default=False)), 30 | ('show_circuit', models.BooleanField(default=False)), 31 | ('show_power', models.BooleanField(default=False)), 32 | ('show_wireless', models.BooleanField(default=False)), 33 | ('draw_default_layout', models.BooleanField(default=False)), 34 | ('preselected_device_roles', models.ManyToManyField(blank=True, db_table='netbox_topology_views_individualoptions_preselected_device', related_name='+', to='dcim.devicerole')), 35 | ('preselected_tags', models.ManyToManyField(blank=True, db_table='netbox_topology_views_individualoptions_preselected_tag', related_name='+', to='extras.tag')), 36 | ('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')), 37 | ], 38 | options={ 39 | 'abstract': False, 40 | }, 41 | ), 42 | ] 43 | -------------------------------------------------------------------------------- /netbox_topology_views/migrations/0003_individualoptions_show_neighbors.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.8 on 2023-04-29 09:17 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('netbox_topology_views', '0002_individualoptions'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='individualoptions', 15 | name='show_neighbors', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /netbox_topology_views/migrations/0004_coordinategroup_coordinate.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.8 on 2023-05-01 16:13 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | import taggit.managers 6 | import utilities.json 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('netbox_topology_views', '0003_individualoptions_show_neighbors'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='CoordinateGroup', 18 | fields=[ 19 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)), 20 | ('created', models.DateTimeField(auto_now_add=True, null=True)), 21 | ('last_updated', models.DateTimeField(auto_now=True, null=True)), 22 | ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)), 23 | ('name', models.CharField(max_length=100, unique=True)), 24 | ('description', models.CharField(blank=True, max_length=255)), 25 | ('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')), 26 | ], 27 | options={ 28 | 'ordering': ['name'], 29 | }, 30 | ), 31 | migrations.CreateModel( 32 | name='Coordinate', 33 | fields=[ 34 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)), 35 | ('created', models.DateTimeField(auto_now_add=True, null=True)), 36 | ('last_updated', models.DateTimeField(auto_now=True, null=True)), 37 | ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)), 38 | ('x', models.IntegerField()), 39 | ('y', models.IntegerField()), 40 | ('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dcim.device')), 41 | ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='netbox_topology_views.coordinategroup')), 42 | ('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')), 43 | ], 44 | options={ 45 | 'ordering': ['group', 'device'], 46 | 'unique_together': {('device', 'group')}, 47 | }, 48 | ), 49 | ] 50 | -------------------------------------------------------------------------------- /netbox_topology_views/migrations/0005_individualoptions_save_coords.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.8 on 2023-05-23 20:13 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('netbox_topology_views', '0004_coordinategroup_coordinate'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='individualoptions', 15 | name='save_coords', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /netbox_topology_views/migrations/0006_powerpanelcoordinate_powerfeedcoordinate_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.8 on 2023-09-25 21:18 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | import taggit.managers 6 | import utilities.json 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('netbox_topology_views', '0005_individualoptions_save_coords'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='PowerPanelCoordinate', 18 | fields=[ 19 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)), 20 | ('created', models.DateTimeField(auto_now_add=True, null=True)), 21 | ('last_updated', models.DateTimeField(auto_now=True, null=True)), 22 | ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)), 23 | ('x', models.IntegerField()), 24 | ('y', models.IntegerField()), 25 | ('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dcim.powerpanel')), 26 | ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='netbox_topology_views.coordinategroup')), 27 | ('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')), 28 | ], 29 | options={ 30 | 'ordering': ['group', 'device'], 31 | 'unique_together': {('device', 'group')}, 32 | }, 33 | ), 34 | migrations.CreateModel( 35 | name='PowerFeedCoordinate', 36 | fields=[ 37 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)), 38 | ('created', models.DateTimeField(auto_now_add=True, null=True)), 39 | ('last_updated', models.DateTimeField(auto_now=True, null=True)), 40 | ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)), 41 | ('x', models.IntegerField()), 42 | ('y', models.IntegerField()), 43 | ('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dcim.powerfeed')), 44 | ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='netbox_topology_views.coordinategroup')), 45 | ('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')), 46 | ], 47 | options={ 48 | 'ordering': ['group', 'device'], 49 | 'unique_together': {('device', 'group')}, 50 | }, 51 | ), 52 | migrations.CreateModel( 53 | name='CircuitCoordinate', 54 | fields=[ 55 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)), 56 | ('created', models.DateTimeField(auto_now_add=True, null=True)), 57 | ('last_updated', models.DateTimeField(auto_now=True, null=True)), 58 | ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)), 59 | ('x', models.IntegerField()), 60 | ('y', models.IntegerField()), 61 | ('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='circuits.circuit')), 62 | ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='netbox_topology_views.coordinategroup')), 63 | ('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')), 64 | ], 65 | options={ 66 | 'ordering': ['group', 'device'], 67 | 'unique_together': {('device', 'group')}, 68 | }, 69 | ), 70 | ] 71 | -------------------------------------------------------------------------------- /netbox_topology_views/migrations/0007_individualoptions_group_locations_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.10 on 2024-03-02 16:26 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('netbox_topology_views', '0006_powerpanelcoordinate_powerfeedcoordinate_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='individualoptions', 15 | name='group_locations', 16 | field=models.BooleanField(default=False), 17 | ), 18 | migrations.AddField( 19 | model_name='individualoptions', 20 | name='group_racks', 21 | field=models.BooleanField(default=False), 22 | ), 23 | migrations.AddField( 24 | model_name='individualoptions', 25 | name='group_sites', 26 | field=models.BooleanField(default=False), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /netbox_topology_views/migrations/0008_straight_cables.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.10 on 2024-04-29 14:37 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('netbox_topology_views', '0007_individualoptions_group_locations_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='individualoptions', 15 | name='straight_cables', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] -------------------------------------------------------------------------------- /netbox_topology_views/migrations/0009_individualoptions_group_virtualchassis.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0.6 on 2024-07-29 08:54 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('netbox_topology_views', '0008_straight_cables'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='individualoptions', 15 | name='group_virtualchassis', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /netbox_topology_views/migrations/0010_individualoptions_grid_size.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0.6 on 2024-08-14 09:46 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('netbox_topology_views', '0009_individualoptions_group_virtualchassis'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='individualoptions', 15 | name='grid_size', 16 | field=models.PositiveSmallIntegerField(default=0), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /netbox_topology_views/migrations/0011_individualoptions_node_label_items.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0.9 on 2024-09-27 18:19 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('netbox_topology_views', '0010_individualoptions_grid_size'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='individualoptions', 15 | name='node_label_items', 16 | field=models.CharField(blank=True, max_length=255), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /netbox_topology_views/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netbox-community/netbox-topology-views/79a234fcc97f64e474f04bb9b03b79179b38aeb1/netbox_topology_views/migrations/__init__.py -------------------------------------------------------------------------------- /netbox_topology_views/navigation.py: -------------------------------------------------------------------------------- 1 | from netbox.plugins import PluginMenu, PluginMenuItem, PluginMenuButton 2 | 3 | coordinategroup_buttons = ( 4 | PluginMenuButton( 5 | link='plugins:netbox_topology_views:coordinategroup_add', 6 | title='Add', 7 | icon_class='mdi mdi-plus-thick', 8 | permissions=['netbox_topology_views.add_coordinategroup'] 9 | ), 10 | PluginMenuButton( 11 | link='plugins:netbox_topology_views:coordinategroup_import', 12 | title='Import', 13 | icon_class='mdi mdi-upload', 14 | permissions=['netbox_topology_views.add_coordinategroup'] 15 | ) 16 | ) 17 | 18 | circuitcoordinate_buttons = ( 19 | PluginMenuButton( 20 | link='plugins:netbox_topology_views:circuitcoordinate_add', 21 | title='Add', 22 | icon_class='mdi mdi-plus-thick', 23 | permissions=['netbox_topology_views.add_coordinate'] 24 | ), 25 | PluginMenuButton( 26 | link='plugins:netbox_topology_views:circuitcoordinate_import', 27 | title='Import', 28 | icon_class='mdi mdi-upload', 29 | permissions=['netbox_topology_views.add_coordinate'] 30 | ) 31 | ) 32 | 33 | powerpanelcoordinate_buttons = ( 34 | PluginMenuButton( 35 | link='plugins:netbox_topology_views:powerpanelcoordinate_add', 36 | title='Add', 37 | icon_class='mdi mdi-plus-thick', 38 | permissions=['netbox_topology_views.add_coordinate'] 39 | ), 40 | PluginMenuButton( 41 | link='plugins:netbox_topology_views:powerpanelcoordinate_import', 42 | title='Import', 43 | icon_class='mdi mdi-upload', 44 | permissions=['netbox_topology_views.add_coordinate'] 45 | ) 46 | ) 47 | 48 | powerfeedcoordinate_buttons = ( 49 | PluginMenuButton( 50 | link='plugins:netbox_topology_views:powerfeedcoordinate_add', 51 | title='Add', 52 | icon_class='mdi mdi-plus-thick', 53 | permissions=['netbox_topology_views.add_coordinate'] 54 | ), 55 | PluginMenuButton( 56 | link='plugins:netbox_topology_views:powerfeedcoordinate_import', 57 | title='Import', 58 | icon_class='mdi mdi-upload', 59 | permissions=['netbox_topology_views.add_coordinate'] 60 | ) 61 | ) 62 | 63 | coordinate_buttons = ( 64 | PluginMenuButton( 65 | link='plugins:netbox_topology_views:coordinate_add', 66 | title='Add', 67 | icon_class='mdi mdi-plus-thick', 68 | permissions=['netbox_topology_views.add_coordinate'] 69 | ), 70 | PluginMenuButton( 71 | link='plugins:netbox_topology_views:coordinate_import', 72 | title='Import', 73 | icon_class='mdi mdi-upload', 74 | permissions=['netbox_topology_views.add_coordinate'] 75 | ) 76 | ) 77 | 78 | menu = PluginMenu( 79 | label='Topology Views', 80 | icon_class="mdi mdi-sitemap", 81 | groups=( 82 | ('TOPOLOGY', 83 | ( 84 | PluginMenuItem(link="plugins:netbox_topology_views:home", link_text="Topology", permissions=["dcim.view_site", "dcim.view_device"]), 85 | ), 86 | ), 87 | ('COORDINATES', 88 | ( 89 | PluginMenuItem(link="plugins:netbox_topology_views:coordinategroup_list", link_text="Coordinate Groups", buttons=coordinategroup_buttons, permissions=['netbox_topology_views.view_coordinategroup']), 90 | PluginMenuItem(link="plugins:netbox_topology_views:coordinate_list", link_text="Device Coordinates", buttons=coordinate_buttons, permissions=['netbox_topology_views.view_coordinate']), 91 | PluginMenuItem(link="plugins:netbox_topology_views:powerfeedcoordinate_list", link_text="Power Feed Coords", buttons=powerfeedcoordinate_buttons, permissions=['netbox_topology_views.view_coordinate']), 92 | PluginMenuItem(link="plugins:netbox_topology_views:powerpanelcoordinate_list", link_text="Power Panel Coords", buttons=powerpanelcoordinate_buttons, permissions=['netbox_topology_views.view_coordinate']), 93 | PluginMenuItem(link="plugins:netbox_topology_views:circuitcoordinate_list", link_text="Circuit Coordinates", buttons=circuitcoordinate_buttons, permissions=['netbox_topology_views.view_coordinate']), 94 | ), 95 | ), 96 | ('PREFERENCES', 97 | ( 98 | PluginMenuItem(link="plugins:netbox_topology_views:images", link_text="Images", permissions=[ "dcim.view_site","dcim.view_devicerole"]), 99 | PluginMenuItem(link="plugins:netbox_topology_views:individualoptions", link_text="Individual Options", permissions=['netbox_topology_views.change_individualoptions']), 100 | ), 101 | ), 102 | ), 103 | ) 104 | -------------------------------------------------------------------------------- /netbox_topology_views/search.py: -------------------------------------------------------------------------------- 1 | from netbox.search import SearchIndex, register_search 2 | from .models import CoordinateGroup, Coordinate 3 | 4 | @register_search 5 | class CoordinateGroupsIndex(SearchIndex): 6 | model = CoordinateGroup 7 | fields = ( 8 | ('name', 100), 9 | ('description', 2000), 10 | ) 11 | 12 | @register_search 13 | class CoordinatesIndex(SearchIndex): 14 | model = Coordinate 15 | fields = ( 16 | ('group', 100), 17 | ('device', 200), 18 | ) 19 | -------------------------------------------------------------------------------- /netbox_topology_views/signals.py: -------------------------------------------------------------------------------- 1 | from typing import Type 2 | 3 | from dcim.models import DeviceRole 4 | from django.contrib.contenttypes.models import ContentType 5 | from django.db.models.signals import pre_delete 6 | from django.dispatch import receiver 7 | 8 | from netbox_topology_views.models import RoleImage 9 | 10 | 11 | @receiver(pre_delete, sender=DeviceRole, dispatch_uid="delete_hanging_role_image") 12 | def delete_hanging_role_image(sender: Type[DeviceRole], instance: DeviceRole, **kwargs): 13 | ct = ContentType.objects.get_for_model(sender) 14 | RoleImage.objects.filter(content_type=ct, object_id=instance.id).delete() 15 | -------------------------------------------------------------------------------- /netbox_topology_views/static/netbox_topology_views/css/app.css: -------------------------------------------------------------------------------- 1 | #visgraph{height:64vh;border:1px solid #ced4da}html[data-netbox-color-mode=dark] #visgraph{background-color:#212529;border:1px solid #495057}.image-dropdown img{width:64px;height:64px}.image-dropdown-content{display:flex;flex-wrap:wrap;gap:.5rem;padding-inline:.5rem;width:50vw;max-width:32rem}.image-dropdown-content>img{cursor:pointer} 2 | -------------------------------------------------------------------------------- /netbox_topology_views/static/netbox_topology_views/img/backhaul.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 44 | 49 | 55 | 58 | 62 | 66 | 73 | 77 | 81 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /netbox_topology_views/static/netbox_topology_views/img/backup.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 44 | 49 | 55 | 59 | 63 | 68 | 69 | 73 | 78 | 79 | 83 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /netbox_topology_views/static/netbox_topology_views/img/cable-doubler.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 44 | 49 | 55 | 57 | 66 | 72 | 78 | 84 | 88 | 92 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /netbox_topology_views/static/netbox_topology_views/img/camera.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 44 | 49 | 55 | 57 | 62 | 68 | 72 | 76 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /netbox_topology_views/static/netbox_topology_views/img/core-router.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 44 | 49 | 55 | 57 | 60 | 78 | 96 | 101 | 102 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /netbox_topology_views/static/netbox_topology_views/img/database-server.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 44 | 49 | 55 | 59 | 64 | 68 | 72 | 76 | 82 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /netbox_topology_views/static/netbox_topology_views/img/database.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 44 | 49 | 55 | 59 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /netbox_topology_views/static/netbox_topology_views/img/desktop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 44 | 49 | 52 | 59 | 63 | 72 | 76 | 82 | 83 | 92 | 101 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /netbox_topology_views/static/netbox_topology_views/img/docking-station.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 56 | 57 | 63 | 68 | 74 | 77 | 84 | 93 | 102 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /netbox_topology_views/static/netbox_topology_views/img/environment-monitor.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 44 | 49 | 55 | 59 | 64 | 68 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /netbox_topology_views/static/netbox_topology_views/img/firewall.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 44 | 49 | 53 | 59 | 60 | 69 | 78 | 87 | 96 | 100 | 105 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /netbox_topology_views/static/netbox_topology_views/img/fo-patch-panel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 44 | 52 | 58 | 61 | 70 | 79 | 88 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /netbox_topology_views/static/netbox_topology_views/img/load-balancer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 44 | 49 | 55 | 58 | 67 | 76 | 80 | 86 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /netbox_topology_views/static/netbox_topology_views/img/network-socket.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 44 | 49 | 55 | 59 | 68 | 73 | 77 | 81 | 85 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /netbox_topology_views/static/netbox_topology_views/img/notebook.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 44 | 49 | 55 | 58 | 67 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /netbox_topology_views/static/netbox_topology_views/img/patch-panel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 44 | 49 | 55 | 57 | 66 | 75 | 84 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /netbox_topology_views/static/netbox_topology_views/img/phone.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 44 | 49 | 53 | 59 | 60 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /netbox_topology_views/static/netbox_topology_views/img/power-units.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 56 | 57 | 63 | 68 | 74 | 78 | 85 | 89 | 93 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /netbox_topology_views/static/netbox_topology_views/img/printer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 44 | 49 | 55 | 58 | 67 | 76 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /netbox_topology_views/static/netbox_topology_views/img/provider-networks.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 56 | 57 | 63 | 68 | 74 | 76 | 80 | 84 | 90 | 97 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /netbox_topology_views/static/netbox_topology_views/img/role-unknown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 44 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /netbox_topology_views/static/netbox_topology_views/img/router.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 44 | 49 | 55 | 57 | 60 | 78 | 96 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /netbox_topology_views/static/netbox_topology_views/img/server.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 44 | 49 | 55 | 58 | 67 | 71 | 75 | 79 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /netbox_topology_views/static/netbox_topology_views/img/siem.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 44 | 49 | 55 | 59 | 64 | 70 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /netbox_topology_views/static/netbox_topology_views/img/storage.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 44 | 49 | 55 | 58 | 62 | 66 | 72 | 75 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /netbox_topology_views/static/netbox_topology_views/img/ups.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 44 | 49 | 55 | 58 | 67 | 71 | 75 | 79 | 83 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /netbox_topology_views/static/netbox_topology_views/img/wan-network.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 44 | 49 | 55 | 57 | 61 | 66 | 70 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /netbox_topology_views/static/netbox_topology_views/img/wireless-ap.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 44 | 49 | 55 | 58 | 62 | 66 | 70 | 74 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /netbox_topology_views/static/netbox_topology_views/js/images.js: -------------------------------------------------------------------------------- 1 | (()=>{var l=(o,t)=>()=>(o&&(t=o(o=0)),t);var k=(o,t)=>()=>(t||o((t={exports:{}}).exports,t),t.exports);var d=(o,t,e)=>new Promise((n,r)=>{var c=s=>{try{i(e.next(s))}catch(a){r(a)}},y=s=>{try{i(e.throw(s))}catch(a){r(a)}},i=s=>s.done?n(s.value):Promise.resolve(s.value).then(c,y);i((e=e.apply(o,t)).next())});var g,m=l(()=>{g=o=>{if(!document.cookie)return;let t=null,e=document.cookie.split(";");for(let n=0;n{u={success:o=>{let t=document.querySelector("#topology-plugin-success-toast");if(!t)return console.error("Could not find toast component!");let e=t.querySelector("span");e.textContent=o,new window.Toast(t).show()},error:o=>{let t=document.querySelector("#topology-plugin-error-toast");if(!t)return console.error("Could not find toast component!");let e=t.querySelector("span");e.textContent=o,new window.Toast(t).show()}}});var S=k(w=>{m();p();var f={},h=g("csrftoken");document.querySelector("form#images").addEventListener("submit",o=>d(null,null,function*(){o.preventDefault();try{let t=yield fetch("/"+basePath+"api/plugins/netbox_topology_views/images/save/",{method:"POST",body:JSON.stringify(f),headers:{"X-CSRFToken":h,"Content-Type":"application/json"}});if(!t.ok)throw new Error(yield t.text());u.success("Saved settings")}catch(t){console.dir(t),u.error(t.message)}}));document.querySelectorAll("form#images .dropdown-menu img").forEach(o=>{o.addEventListener("click",t=>{var c;if(!(t.currentTarget instanceof HTMLElement))return;let{dataset:{role:e,image:n}}=t.currentTarget;f[e]=n;let r=(c=t.currentTarget.closest(".dropdown"))==null?void 0:c.querySelector(`#dropdownMenuButton${e}`);r&&(r.innerHTML=``)})})});S();})(); 2 | -------------------------------------------------------------------------------- /netbox_topology_views/static/netbox_topology_views/js/images.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["../../../static_dev/js/csrftoken.js", "../../../static_dev/js/toast.js", "../../../static_dev/js/images.js"], 4 | "mappings": "oTAAA,IAAaA,EAAbC,EAAAC,EAAA,KAAaF,EAAaG,GAAS,CAC/B,GAAI,CAAC,SAAS,OAAQ,OAEtB,IAAIC,EAAc,KACZC,EAAU,SAAS,OAAO,MAAM,GAAG,EAEzC,QAASC,EAAI,EAAGA,EAAID,EAAQ,OAAQC,IAAK,CACrC,IAAMC,EAASF,EAAQC,CAAC,EAAE,KAAK,EAE/B,GAAIC,EAAO,UAAU,EAAGJ,EAAK,OAAS,CAAC,IAAMA,EAAO,IAAK,CACrDC,EAAc,mBAAmBG,EAAO,UAAUJ,EAAK,OAAS,CAAC,CAAC,EAClE,KACJ,CACJ,CAEA,OAAOC,CACX,IChBA,IAAaI,EAAbC,EAAAC,EAAA,KAAaF,EAAQ,CACjB,QAAUG,GAAY,CAClB,IAAMC,EAAK,SAAS,cAAc,gCAAgC,EAClE,GAAI,CAACA,EAAI,OAAO,QAAQ,MAAM,iCAAiC,EAC/D,IAAMC,EAAUD,EAAG,cAAc,MAAM,EACvCC,EAAQ,YAAcF,EACR,IAAI,OAAO,MAAMC,CAAE,EAC3B,KAAK,CACf,EACA,MAAQD,GAAY,CAChB,IAAMC,EAAK,SAAS,cAAc,8BAA8B,EAChE,GAAI,CAACA,EAAI,OAAO,QAAQ,MAAM,iCAAiC,EAC/D,IAAMC,EAAUD,EAAG,cAAc,MAAM,EACvCC,EAAQ,YAAcF,EACR,IAAI,OAAO,MAAMC,CAAE,EAC3B,KAAK,CACf,CACJ,ICjBA,IAAAE,EAAAC,EAAAC,GAAA,CAAAC,IACAC,IAEA,IAAMC,EAAU,CAAC,EACXC,EAAYC,EAAU,WAAW,EAEvC,SAAS,cAAc,aAAa,EAAE,iBAAiB,SAAiBC,GAAMC,EAAA,sBAC1ED,EAAE,eAAe,EACjB,GAAI,CACA,IAAME,EAAM,MAAM,MAAM,IAAM,SAAW,iDAAkD,CACvF,OAAQ,OACR,KAAM,KAAK,UAAUL,CAAO,EAC5B,QAAS,CACL,cAAeC,EACf,eAAgB,kBACpB,CACJ,CAAC,EAED,GAAI,CAACI,EAAI,GAAI,MAAM,IAAI,MAAM,MAAMA,EAAI,KAAK,CAAC,EAC7CC,EAAM,QAAQ,gBAAgB,CAClC,OAASC,EAAK,CACV,QAAQ,IAAIA,CAAG,EACfD,EAAM,MAAMC,EAAI,OAAO,CAC3B,CACJ,EAAC,EAED,SAAS,iBAAiB,gCAAgC,EAAE,QAASC,GAAO,CACxEA,EAAG,iBAAiB,QAAUL,GAAM,CA3BxC,IAAAM,EA4BQ,GAAI,EAAEN,EAAE,yBAAyB,aAAc,OAC/C,GAAM,CACF,QAAS,CAAE,KAAAO,EAAM,MAAAC,CAAM,CAC3B,EAAIR,EAAE,cAENH,EAAQU,CAAI,EAAIC,EAEhB,IAAMC,GAASH,EAAAN,EAAE,cACZ,QAAQ,WAAW,IADT,YAAAM,EAET,cAAc,sBAAsBC,CAAI,IAC1CE,IAAQA,EAAO,UAAY,aAAaD,CAAK,OACrD,CAAC,CACL,CAAC", 5 | "names": ["getCookie", "init_csrftoken", "__esmMin", "name", "cookieValue", "cookies", "i", "cookie", "toast", "init_toast", "__esmMin", "message", "el", "content", "require_images", "__commonJSMin", "exports", "init_csrftoken", "init_toast", "mapping", "csrftoken", "getCookie", "e", "__async", "res", "toast", "err", "el", "_a", "role", "image", "button"] 6 | } 7 | -------------------------------------------------------------------------------- /netbox_topology_views/static_dev/bundle.js: -------------------------------------------------------------------------------- 1 | const esbuild = require('esbuild') 2 | const { sassPlugin } = require('esbuild-sass-plugin') 3 | 4 | const options = { 5 | bundle: true, 6 | minify: true, 7 | sourcemap: 'external', 8 | sourcesContent: false, 9 | logLevel: 'error' 10 | } 11 | 12 | const ARGS = process.argv.slice(2) 13 | const noCache = ARGS.includes('--no-cache') 14 | 15 | async function bundleScripts() { 16 | const entryPoints = { 17 | app: 'js/home.js', 18 | images: 'js/images.js' 19 | } 20 | 21 | try { 22 | const result = await esbuild.build({ 23 | ...options, 24 | outdir: '../static/netbox_topology_views/js/', 25 | entryPoints, 26 | target: 'es2016' 27 | }) 28 | if (result.errors.length !== 0) return 29 | 30 | for (const [targetName, sourceName] of Object.entries(entryPoints)) { 31 | const source = sourceName.split('/').pop() // take last element 32 | console.log( 33 | `✅ Bundled source file '${source}' to '${targetName}.js'` 34 | ) 35 | } 36 | } catch (err) { 37 | console.error(err) 38 | } 39 | } 40 | 41 | async function bundleStyles() { 42 | try { 43 | const entryPoints = { 44 | vendor: 'css/_external.scss', 45 | app: 'css/app.scss' 46 | } 47 | const pluginOptions = { outputStyle: 'compressed' } 48 | // Allow cache disabling. 49 | if (noCache) { 50 | pluginOptions.cache = false 51 | } 52 | 53 | const result = await esbuild.build({ 54 | ...options, 55 | outdir: '../static/netbox_topology_views/css/', 56 | // Disable sourcemaps for CSS/SCSS files, see #7068 57 | sourcemap: false, 58 | entryPoints, 59 | plugins: [sassPlugin(pluginOptions)], 60 | loader: { 61 | '.eot': 'file', 62 | '.woff': 'file', 63 | '.woff2': 'file', 64 | '.svg': 'file', 65 | '.ttf': 'file' 66 | } 67 | }) 68 | if (result.errors.length === 0) { 69 | for (const [targetName, sourceName] of Object.entries( 70 | entryPoints 71 | )) { 72 | const source = sourceName.split('/')[1] 73 | console.log( 74 | `✅ Bundled source file '${source}' to '${targetName}.css'` 75 | ) 76 | } 77 | } 78 | } catch (err) { 79 | console.error(err) 80 | } 81 | } 82 | 83 | async function bundleAll() { 84 | if (ARGS.includes('--styles')) { 85 | // Only run style jobs. 86 | return await bundleStyles() 87 | } 88 | if (ARGS.includes('--scripts')) { 89 | // Only run script jobs. 90 | return await bundleScripts() 91 | } 92 | await bundleStyles() 93 | await bundleScripts() 94 | } 95 | 96 | bundleAll() 97 | -------------------------------------------------------------------------------- /netbox_topology_views/static_dev/css/_external.scss: -------------------------------------------------------------------------------- 1 | @import '../node_modules/vis-network/dist/dist/vis-network.min.css'; -------------------------------------------------------------------------------- /netbox_topology_views/static_dev/css/app.scss: -------------------------------------------------------------------------------- 1 | #visgraph { 2 | height: 64vh; 3 | border: 1px solid #ced4da 4 | } 5 | 6 | html[data-netbox-color-mode=dark] #visgraph { 7 | background-color: #212529; 8 | border: 1px solid #495057 9 | } 10 | 11 | .image-dropdown img { 12 | width: 64px; 13 | height: 64px; 14 | } 15 | 16 | .image-dropdown-content { 17 | display: flex; 18 | flex-wrap: wrap; 19 | gap: 0.5rem; 20 | 21 | padding-inline: 0.5rem; 22 | width: 50vw; 23 | max-width: 32rem; 24 | 25 | > img { 26 | cursor: pointer; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /netbox_topology_views/static_dev/js/csrftoken.js: -------------------------------------------------------------------------------- 1 | export const getCookie = (name) => { 2 | if (!document.cookie) return 3 | 4 | let cookieValue = null 5 | const cookies = document.cookie.split(';') 6 | 7 | for (let i = 0; i < cookies.length; i++) { 8 | const cookie = cookies[i].trim() 9 | // Does this cookie string begin with the name we want? 10 | if (cookie.substring(0, name.length + 1) === name + '=') { 11 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1)) 12 | break 13 | } 14 | } 15 | 16 | return cookieValue 17 | } 18 | -------------------------------------------------------------------------------- /netbox_topology_views/static_dev/js/images.js: -------------------------------------------------------------------------------- 1 | import { getCookie } from './csrftoken.js' 2 | import { toast } from './toast.js' 3 | 4 | const mapping = {} 5 | const csrftoken = getCookie('csrftoken') 6 | 7 | document.querySelector('form#images').addEventListener('submit', async (e) => { 8 | e.preventDefault() 9 | try { 10 | const res = await fetch('/' + basePath + 'api/plugins/netbox_topology_views/images/save/', { 11 | method: 'POST', 12 | body: JSON.stringify(mapping), 13 | headers: { 14 | 'X-CSRFToken': csrftoken, 15 | 'Content-Type': 'application/json' 16 | } 17 | }) 18 | 19 | if (!res.ok) throw new Error(await res.text()) 20 | toast.success('Saved settings') 21 | } catch (err) { 22 | console.dir(err) 23 | toast.error(err.message) 24 | } 25 | }) 26 | 27 | document.querySelectorAll('form#images .dropdown-menu img').forEach((el) => { 28 | el.addEventListener('click', (e) => { 29 | if (!(e.currentTarget instanceof HTMLElement)) return 30 | const { 31 | dataset: { role, image } 32 | } = e.currentTarget 33 | 34 | mapping[role] = image 35 | 36 | const button = e.currentTarget 37 | .closest('.dropdown') 38 | ?.querySelector(`#dropdownMenuButton${role}`) 39 | if (button) button.innerHTML = `` 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /netbox_topology_views/static_dev/js/toast.js: -------------------------------------------------------------------------------- 1 | export const toast = { 2 | success: (message) => { 3 | const el = document.querySelector('#topology-plugin-success-toast') 4 | if (!el) return console.error('Could not find toast component!') 5 | const content = el.querySelector('span') 6 | content.textContent = message 7 | const toast = new window.Toast(el) 8 | toast.show() 9 | }, 10 | error: (message) => { 11 | const el = document.querySelector('#topology-plugin-error-toast') 12 | if (!el) return console.error('Could not find toast component!') 13 | const content = el.querySelector('span') 14 | content.textContent = message 15 | const toast = new window.Toast(el) 16 | toast.show() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /netbox_topology_views/static_dev/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "netbox_topology_views", 4 | "version": "4.3.0", 5 | "scripts": { 6 | "bundle": "node bundle.js", 7 | "bundle:styles": "node bundle.js --styles", 8 | "bundle:scripts": "node bundle.js --scripts" 9 | }, 10 | "dependencies": { 11 | "vis-data": "^7.1.9", 12 | "vis-network": "^9.1.9", 13 | "vis-util": "^5.0.7" 14 | }, 15 | "devDependencies": { 16 | "@egjs/hammerjs": "^2.0.0", 17 | "component-emitter": "^1.3.0", 18 | "esbuild": "^0.25.0", 19 | "esbuild-sass-plugin": "^3.3.1", 20 | "keycharm": "^0.4.0", 21 | "timsort": "^0.3.0", 22 | "uuid": "^8.0.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /netbox_topology_views/tables.py: -------------------------------------------------------------------------------- 1 | import django_tables2 as tables 2 | 3 | from netbox.tables import NetBoxTable, ChoiceFieldColumn 4 | from netbox_topology_views.models import CoordinateGroup, Coordinate, CircuitCoordinate, PowerPanelCoordinate, PowerFeedCoordinate 5 | 6 | class CoordinateGroupListTable(NetBoxTable): 7 | name = tables.Column( 8 | linkify=True 9 | ) 10 | devices = tables.Column() 11 | 12 | class Meta(NetBoxTable.Meta): 13 | model = CoordinateGroup 14 | fields = ('pk', 'id', 'name', 'description', 'devices') 15 | default_columns = ('name', 'description', 'devices') 16 | 17 | class CircuitCoordinateListTable(NetBoxTable): 18 | group = tables.Column( 19 | linkify=True 20 | ) 21 | 22 | device = tables.Column( 23 | linkify=True 24 | ) 25 | 26 | class Meta(NetBoxTable.Meta): 27 | model = CircuitCoordinate 28 | fields = ('pk', 'id', 'group', 'device', 'x', 'y') 29 | default_columns = ('id', 'group', 'device', 'x', 'y') 30 | 31 | class PowerPanelCoordinateListTable(NetBoxTable): 32 | group = tables.Column( 33 | linkify=True 34 | ) 35 | 36 | device = tables.Column( 37 | linkify=True 38 | ) 39 | 40 | class Meta(NetBoxTable.Meta): 41 | model = PowerPanelCoordinate 42 | fields = ('pk', 'id', 'group', 'device', 'x', 'y') 43 | default_columns = ('id', 'group', 'device', 'x', 'y') 44 | 45 | class PowerFeedCoordinateListTable(NetBoxTable): 46 | group = tables.Column( 47 | linkify=True 48 | ) 49 | 50 | device = tables.Column( 51 | linkify=True 52 | ) 53 | 54 | class Meta(NetBoxTable.Meta): 55 | model = PowerFeedCoordinate 56 | fields = ('pk', 'id', 'group', 'device', 'x', 'y') 57 | default_columns = ('id', 'group', 'device', 'x', 'y') 58 | 59 | class CoordinateListTable(NetBoxTable): 60 | group = tables.Column( 61 | linkify=True 62 | ) 63 | 64 | device = tables.Column( 65 | linkify=True 66 | ) 67 | 68 | class Meta(NetBoxTable.Meta): 69 | model = Coordinate 70 | fields = ('pk', 'id', 'group', 'device', 'x', 'y') 71 | default_columns = ('id', 'group', 'device', 'x', 'y') 72 | -------------------------------------------------------------------------------- /netbox_topology_views/template_content.py: -------------------------------------------------------------------------------- 1 | 2 | from netbox.plugins import PluginTemplateExtension 3 | from django.conf import settings 4 | from packaging import version 5 | 6 | NETBOX_CURRENT_VERSION = version.parse(settings.RELEASE.version) 7 | 8 | 9 | class SiteButtons(PluginTemplateExtension): 10 | models = ('dcim.site', ) 11 | def buttons(self): 12 | return self.render('netbox_topology_views/site_button.html') 13 | 14 | class LocationButtons(PluginTemplateExtension): 15 | models = ('dcim.location', ) 16 | def buttons(self): 17 | return self.render('netbox_topology_views/location_button.html') 18 | 19 | 20 | template_extensions = [SiteButtons, LocationButtons] 21 | -------------------------------------------------------------------------------- /netbox_topology_views/templates/netbox_topology_views/circuitcoordinate.html: -------------------------------------------------------------------------------- 1 | {% extends 'generic/object.html' %} 2 | {% load helpers %} 3 | {% load plugins %} 4 | 5 | {% block title %}Topology Views Coordinates{% endblock title %} 6 | 7 | {% block content %} 8 |
9 |
10 |
11 |
12 | Coordinates 13 |
14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
Group{{ object.group|linkify }}
Circuit{{ object.device|linkify }}
X-Coordinate{{ object.x }}
Y-Coordinate{{ object.y }}
33 |
34 |
35 | {% plugin_left_page object %} 36 |
37 |
38 | {% include 'inc/panels/custom_fields.html' %} 39 | {% plugin_right_page object %} 40 |
41 |
42 | {% endblock content %} 43 | -------------------------------------------------------------------------------- /netbox_topology_views/templates/netbox_topology_views/circuitcoordinate_add.html: -------------------------------------------------------------------------------- 1 | {% extends 'generic/object_edit.html' %} 2 | 3 | {% block title %}Add new Circuit Coordinates{% endblock title %} 4 | -------------------------------------------------------------------------------- /netbox_topology_views/templates/netbox_topology_views/circuitcoordinate_edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'generic/object_edit.html' %} 2 | 3 | {% block title %}Edit Circuit Coordinates{% endblock title %} 4 | -------------------------------------------------------------------------------- /netbox_topology_views/templates/netbox_topology_views/circuitcoordinate_list.html: -------------------------------------------------------------------------------- 1 | {% extends 'generic/object_list.html' %} 2 | 3 | {% block title %}Circuit Coordinates{% endblock title %} 4 | -------------------------------------------------------------------------------- /netbox_topology_views/templates/netbox_topology_views/coordinate.html: -------------------------------------------------------------------------------- 1 | {% extends 'generic/object.html' %} 2 | {% load helpers %} 3 | {% load plugins %} 4 | 5 | {% block title %}Topology Views Coordinates{% endblock title %} 6 | 7 | {% block content %} 8 |
9 |
10 |
11 |
12 | Coordinates 13 |
14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
Group{{ object.group|linkify }}
Device{{ object.device|linkify }}
X-Coordinate{{ object.x }}
Y-Coordinate{{ object.y }}
33 |
34 |
35 | {% plugin_left_page object %} 36 |
37 |
38 | {% include 'inc/panels/custom_fields.html' %} 39 | {% plugin_right_page object %} 40 |
41 |
42 | {% endblock content %} 43 | -------------------------------------------------------------------------------- /netbox_topology_views/templates/netbox_topology_views/coordinate_add.html: -------------------------------------------------------------------------------- 1 | {% extends 'generic/object_edit.html' %} 2 | 3 | {% block title %}Add new Device Coordinates{% endblock title %} 4 | -------------------------------------------------------------------------------- /netbox_topology_views/templates/netbox_topology_views/coordinate_edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'generic/object_edit.html' %} 2 | 3 | {% block title %}Edit Device Coordinates{% endblock title %} 4 | -------------------------------------------------------------------------------- /netbox_topology_views/templates/netbox_topology_views/coordinate_list.html: -------------------------------------------------------------------------------- 1 | {% extends 'generic/object_list.html' %} 2 | 3 | {% block title %}Device Coordinates{% endblock title %} 4 | -------------------------------------------------------------------------------- /netbox_topology_views/templates/netbox_topology_views/coordinategroup.html: -------------------------------------------------------------------------------- 1 | {% extends 'generic/object.html' %} 2 | {% load helpers %} 3 | {% load plugins %} 4 | {% load render_table from django_tables2 %} 5 | 6 | {% block title %}Topology Views Coordinate Group{% endblock title %} 7 | 8 | {% block content %} 9 |
10 |
11 |
12 |
13 | Coordinate Group 14 |
15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
Name{{ object.name }}
Description{{ object.description|placeholder }}
26 |
27 |
28 | {% plugin_left_page object %} 29 |
30 |
31 | {% include 'inc/panels/custom_fields.html' %} 32 | {% plugin_right_page object %} 33 |
34 |
35 |
36 |
37 |
38 |
Circuit Coordinates
39 |
40 | {% render_table circuitcoordinates_table %} 41 |
42 |
43 |
44 |
45 |
46 |
Power Panel Coordinates
47 |
48 | {% render_table powerpanelcoordinates_table %} 49 |
50 |
51 |
52 |
53 |
54 |
Power Feed Coordinates
55 |
56 | {% render_table powerfeedcoordinates_table %} 57 |
58 |
59 |
60 |
61 |
62 |
Device Coordinates
63 |
64 | {% render_table coordinates_table %} 65 |
66 |
67 |
68 |
69 | {% endblock content %} 70 | -------------------------------------------------------------------------------- /netbox_topology_views/templates/netbox_topology_views/coordinategroup_add.html: -------------------------------------------------------------------------------- 1 | {% extends 'generic/object_edit.html' %} 2 | 3 | {% block title %}Add a new Coordinate Group{% endblock title %} 4 | -------------------------------------------------------------------------------- /netbox_topology_views/templates/netbox_topology_views/coordinategroup_edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'generic/object_edit.html' %} 2 | 3 | {% block title %}Edit Coordinate Group{% endblock title %} 4 | -------------------------------------------------------------------------------- /netbox_topology_views/templates/netbox_topology_views/coordinategroup_list.html: -------------------------------------------------------------------------------- 1 | {% extends 'generic/object_list.html' %} 2 | 3 | {% block title %}Coordinate Groups{% endblock title %} 4 | -------------------------------------------------------------------------------- /netbox_topology_views/templates/netbox_topology_views/htmx_topology.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 26 |
27 |
28 |
29 |
30 |
31 |
32 | 33 | 38 | 39 | 45 | 46 | -------------------------------------------------------------------------------- /netbox_topology_views/templates/netbox_topology_views/images.html: -------------------------------------------------------------------------------- 1 | {% extends 'base/layout.html' %} 2 | {% load buttons %} 3 | {% load render_table from django_tables2 %} 4 | {% load helpers %} 5 | {% load static %} 6 | 7 | {% block title %}Topology Views Images{% endblock %} 8 | 9 | {% block head %} 10 | 11 | {% endblock %} 12 | 13 | {% block content %} 14 |
15 |
16 | {% for role in roles %} 17 |
18 | 34 | {{ role.name }} 35 |
36 | {% endfor %} 37 |
38 | 39 | 40 |
41 | {% include 'netbox_topology_views/toasts.html' %} 42 | {% endblock content %} 43 | 44 | {% block javascript %} 45 | 48 | 49 | {% endblock javascript %} 50 | -------------------------------------------------------------------------------- /netbox_topology_views/templates/netbox_topology_views/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'generic/_base.html' %} 2 | {% load buttons %} 3 | {% load render_table from django_tables2 %} 4 | {% load static %} 5 | {% load perms %} 6 | {% load helpers %} 7 | 8 | {% block title %}Topology Views{% endblock %} 9 | 10 | {% block head %} 11 | 12 | 13 | {% endblock %} 14 | 15 | {% block controls %} 16 |
17 |
18 | {% block extra_controls %}{% endblock %} 19 | 20 | 21 | Download XML 22 | 23 | 24 | 25 | Download PNG 26 | 27 |
28 |
29 | {% endblock controls %} 30 | 31 | {% block tabs %} 32 | 49 | {% endblock tabs %} 50 | 51 | {% block content %} 52 | {% with config=settings.PLUGINS_CONFIG.netbox_topology_views %} 53 |
54 | 55 | {# Applied filters #} 56 | {% if filter_form %} 57 |
58 | {% applied_filters model filter_form request.GET %} 59 |
60 | {% endif %} 61 | 62 |
63 |
64 |
65 |
66 |
67 | 68 | {% if filter_form %} 69 |
70 | {% include 'inc/filter_list.html' %} 71 |
72 | {% endif %} 73 | 74 |
75 | {% endwith %} 76 | {% endblock content %} 77 | 78 | {% block javascript %} 79 | 113 | 114 | {% endblock javascript %} 115 | -------------------------------------------------------------------------------- /netbox_topology_views/templates/netbox_topology_views/individual_options.html: -------------------------------------------------------------------------------- 1 | {% extends 'generic/object_edit.html' %} 2 | 3 | {% block title %}Topology Views Individual Options{% endblock %} 4 | 5 | {% block tabs %} {% endblock tabs %} -------------------------------------------------------------------------------- /netbox_topology_views/templates/netbox_topology_views/location_button.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Topology 4 | 5 | -------------------------------------------------------------------------------- /netbox_topology_views/templates/netbox_topology_views/powerfeedcoordinate.html: -------------------------------------------------------------------------------- 1 | {% extends 'generic/object.html' %} 2 | {% load helpers %} 3 | {% load plugins %} 4 | 5 | {% block title %}Topology Views Coordinates{% endblock title %} 6 | 7 | {% block content %} 8 |
9 |
10 |
11 |
12 | Coordinates 13 |
14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
Group{{ object.group|linkify }}
Power Feed{{ object.device|linkify }}
X-Coordinate{{ object.x }}
Y-Coordinate{{ object.y }}
33 |
34 |
35 | {% plugin_left_page object %} 36 |
37 |
38 | {% include 'inc/panels/custom_fields.html' %} 39 | {% plugin_right_page object %} 40 |
41 |
42 | {% endblock content %} 43 | -------------------------------------------------------------------------------- /netbox_topology_views/templates/netbox_topology_views/powerfeedcoordinate_add.html: -------------------------------------------------------------------------------- 1 | {% extends 'generic/object_edit.html' %} 2 | 3 | {% block title %}Add new Power Feed Coordinates{% endblock title %} 4 | -------------------------------------------------------------------------------- /netbox_topology_views/templates/netbox_topology_views/powerfeedcoordinate_edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'generic/object_edit.html' %} 2 | 3 | {% block title %}Edit Power Feed Coordinates{% endblock title %} 4 | -------------------------------------------------------------------------------- /netbox_topology_views/templates/netbox_topology_views/powerfeedcoordinate_list.html: -------------------------------------------------------------------------------- 1 | {% extends 'generic/object_list.html' %} 2 | 3 | {% block title %}Power Feed Coordinates{% endblock title %} 4 | -------------------------------------------------------------------------------- /netbox_topology_views/templates/netbox_topology_views/powerpanelcoordinate.html: -------------------------------------------------------------------------------- 1 | {% extends 'generic/object.html' %} 2 | {% load helpers %} 3 | {% load plugins %} 4 | 5 | {% block title %}Topology Views Coordinates{% endblock title %} 6 | 7 | {% block content %} 8 |
9 |
10 |
11 |
12 | Coordinates 13 |
14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
Group{{ object.group|linkify }}
Power Panel{{ object.device|linkify }}
X-Coordinate{{ object.x }}
Y-Coordinate{{ object.y }}
33 |
34 |
35 | {% plugin_left_page object %} 36 |
37 |
38 | {% include 'inc/panels/custom_fields.html' %} 39 | {% plugin_right_page object %} 40 |
41 |
42 | {% endblock content %} 43 | -------------------------------------------------------------------------------- /netbox_topology_views/templates/netbox_topology_views/powerpanelcoordinate_add.html: -------------------------------------------------------------------------------- 1 | {% extends 'generic/object_edit.html' %} 2 | 3 | {% block title %}Add new Power Panel Coordinates{% endblock title %} 4 | -------------------------------------------------------------------------------- /netbox_topology_views/templates/netbox_topology_views/powerpanelcoordinate_edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'generic/object_edit.html' %} 2 | 3 | {% block title %}Edit Power Panel Coordinates{% endblock title %} 4 | -------------------------------------------------------------------------------- /netbox_topology_views/templates/netbox_topology_views/powerpanelcoordinate_list.html: -------------------------------------------------------------------------------- 1 | {% extends 'generic/object_list.html' %} 2 | 3 | {% block title %}Power Panel Coordinates{% endblock title %} 4 | -------------------------------------------------------------------------------- /netbox_topology_views/templates/netbox_topology_views/site_button.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Topology 4 | 5 | -------------------------------------------------------------------------------- /netbox_topology_views/templates/netbox_topology_views/toasts.html: -------------------------------------------------------------------------------- 1 |
2 | 23 | 44 |
-------------------------------------------------------------------------------- /netbox_topology_views/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from django.views.generic.base import RedirectView 3 | from netbox.views.generic import ObjectChangeLogView 4 | from . import models, views 5 | 6 | # Define a list of URL patterns to be imported by NetBox. Each pattern maps a URL to 7 | # a specific view so that it can be accessed by users. 8 | urlpatterns = ( 9 | path("", RedirectView.as_view(url="topology/", permanent=True)), 10 | path("topology/", views.TopologyHomeView.as_view(), name="home"), 11 | path("images/", views.TopologyImagesView.as_view(), name="images"), 12 | path("individualoptions/", views.TopologyIndividualOptionsView.as_view(), name="individualoptions"), 13 | 14 | # Coordinate Group 15 | path("coordinate-groups/", views.CoordinateGroupListView.as_view(), name="coordinategroup_list"), 16 | path("coordinate-groups/add/", views.CoordinateGroupAddView.as_view(), name="coordinategroup_add"), 17 | path('coordinate-groups/import/', views.CoordinateGroupBulkImportView.as_view(), name='coordinategroup_import'), 18 | path("coordinate-groups//", views.CoordinateGroupView.as_view(), name="coordinategroup"), 19 | path("coordinate-groups//edit/", views.CoordinateGroupEditView.as_view(), name="coordinategroup_edit"), 20 | path("coordinate-groups//delete/", views.CoordinateGroupDeleteView.as_view(), name="coordinategroup_delete"), 21 | path("coordinate-groups//changelog/", ObjectChangeLogView.as_view(), name="coordinategroup_changelog", kwargs={'model': models.CoordinateGroup}), 22 | 23 | # Circuit Coordinate 24 | path("circuitcoordinate/", views.CircuitCoordinateListView.as_view(), name="circuitcoordinate_list"), 25 | path("circuitcoordinate/add/", views.CircuitCoordinateAddView.as_view(), name="circuitcoordinate_add"), 26 | path('circuitcoordinate/import/', views.CircuitCoordinateBulkImportView.as_view(), name='circuitcoordinate_import'), 27 | path("circuitcoordinate//", views.CircuitCoordinateView.as_view(), name="circuitcoordinate"), 28 | path("circuitcoordinate//edit/", views.CircuitCoordinateEditView.as_view(), name="circuitcoordinate_edit"), 29 | path("circuitcoordinate//delete/", views.CircuitCoordinateDeleteView.as_view(), name="circuitcoordinate_delete"), 30 | path("circuitcoordinate//changelog/", ObjectChangeLogView.as_view(), name="circuitcoordinate_changelog", kwargs={'model': models.CircuitCoordinate}), 31 | 32 | # Power Panel Coordinate 33 | path("powerpanelcoordinate/", views.PowerPanelCoordinateListView.as_view(), name="powerpanelcoordinate_list"), 34 | path("powerpanelcoordinate/add/", views.PowerPanelCoordinateAddView.as_view(), name="powerpanelcoordinate_add"), 35 | path('powerpanelcoordinate/import/', views.PowerPanelCoordinateBulkImportView.as_view(), name='powerpanelcoordinate_import'), 36 | path("powerpanelcoordinate//", views.PowerPanelCoordinateView.as_view(), name="powerpanelcoordinate"), 37 | path("powerpanelcoordinate//edit/", views.PowerPanelCoordinateEditView.as_view(), name="powerpanelcoordinate_edit"), 38 | path("powerpanelcoordinate//delete/", views.PowerPanelCoordinateDeleteView.as_view(), name="powerpanelcoordinate_delete"), 39 | path("powerpanelcoordinate//changelog/", ObjectChangeLogView.as_view(), name="powerpanelcoordinate_changelog", kwargs={'model': models.PowerPanelCoordinate}), 40 | 41 | # Power Feed Coordinate 42 | path("powerfeedcoordinate/", views.PowerFeedCoordinateListView.as_view(), name="powerfeedcoordinate_list"), 43 | path("powerfeedcoordinate/add/", views.PowerFeedCoordinateAddView.as_view(), name="powerfeedcoordinate_add"), 44 | path('powerfeedcoordinate/import/', views.PowerFeedCoordinateBulkImportView.as_view(), name='powerfeedcoordinate_import'), 45 | path("powerfeedcoordinate//", views.PowerFeedCoordinateView.as_view(), name="powerfeedcoordinate"), 46 | path("powerfeedcoordinate//edit/", views.PowerFeedCoordinateEditView.as_view(), name="powerfeedcoordinate_edit"), 47 | path("powerfeedcoordinate//delete/", views.PowerFeedCoordinateDeleteView.as_view(), name="powerfeedcoordinate_delete"), 48 | path("powerfeedcoordinate//changelog/", ObjectChangeLogView.as_view(), name="powerfeedcoordinate_changelog", kwargs={'model': models.PowerFeedCoordinate}), 49 | 50 | # Coordinate 51 | path("coordinate/", views.CoordinateListView.as_view(), name="coordinate_list"), 52 | path("coordinate/add/", views.CoordinateAddView.as_view(), name="coordinate_add"), 53 | path('coordinate/import/', views.CoordinateBulkImportView.as_view(), name='coordinate_import'), 54 | path("coordinate//", views.CoordinateView.as_view(), name="coordinate"), 55 | path("coordinate//edit/", views.CoordinateEditView.as_view(), name="coordinate_edit"), 56 | path("coordinate//delete/", views.CoordinateDeleteView.as_view(), name="coordinate_delete"), 57 | path("coordinate//changelog/", ObjectChangeLogView.as_view(), name="coordinate_changelog", kwargs={'model': models.Coordinate}), 58 | ) 59 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | djangorestframework 2 | django-filter 3 | black -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from setuptools import find_packages, setup 4 | 5 | readme = Path(__file__).parent / "README.md" 6 | long_description = readme.read_text() 7 | 8 | setup( 9 | name="netbox-topology-views", 10 | version="4.3.0", 11 | description="An NetBox plugin to create Topology maps", 12 | long_description=long_description, 13 | long_description_content_type="text/markdown", 14 | url="https://github.com/mattieserver/netbox-topology-views", 15 | author="Mattijs Vanhaverbeke", 16 | license="Apache 2.0", 17 | install_requires=[], 18 | packages=find_packages(), 19 | include_package_data=True, 20 | keywords=["netbox-plugin"], 21 | classifiers=[ 22 | "Programming Language :: Python", 23 | "Programming Language :: Python :: 3", 24 | "Programming Language :: Python :: 3 :: Only", 25 | ], 26 | project_urls={ 27 | "Source": "https://github.com/mattieserver/netbox-topology-views", 28 | }, 29 | ) 30 | --------------------------------------------------------------------------------