Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d773a407a2 | ||
|
|
175a01b257 | ||
|
|
334a76e640 | ||
|
|
ef45789b30 | ||
|
|
4bd02ab6f9 | ||
|
|
410b8f6617 | ||
|
|
306d853a16 | ||
|
|
193f04a208 | ||
|
|
c3ed952b5f | ||
|
|
87fdeb1c24 | ||
|
|
a0a98bb407 | ||
|
|
3539539c4e | ||
|
|
72646a6287 | ||
|
|
86215f43a6 |
@@ -1,3 +1,4 @@
|
|||||||
|
---
|
||||||
name: Docker Image CI
|
name: Docker Image CI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
@@ -12,6 +13,8 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Build the Docker image
|
- name: Build the Docker image
|
||||||
run: docker compose build --no-cache
|
run: docker compose build --no-cache
|
||||||
@@ -23,14 +26,16 @@ jobs:
|
|||||||
- name: Run Django tests with coverage for netbox_slm
|
- name: Run Django tests with coverage for netbox_slm
|
||||||
run: |
|
run: |
|
||||||
chmod go+w ci/reports
|
chmod go+w ci/reports
|
||||||
docker compose run netbox sh -c " \
|
docker compose run netbox sh -c "\
|
||||||
/opt/netbox/venv/bin/coverage run --source='netbox_slm' manage.py test netbox_slm && \
|
/opt/netbox/venv/bin/coverage run --source='netbox_slm' manage.py test netbox_slm &&\
|
||||||
/opt/netbox/venv/bin/coverage report --fail-under=0 && \
|
/opt/netbox/venv/bin/coverage report --fail-under=0 &&\
|
||||||
/opt/netbox/venv/bin/coverage xml -o /ci/reports/coverage.xml"
|
/opt/netbox/venv/bin/coverage xml -o /ci/reports/coverage.xml"
|
||||||
|
sed -i "s/<source>\/opt\/netbox\/netbox<\/source>/<source><\/source>/" ci/reports/coverage.xml
|
||||||
|
timeout-minutes: 6
|
||||||
|
|
||||||
- name: Sonarcloud scan
|
- name: Sonar scan
|
||||||
if: env.SONAR_TOKEN != null
|
if: env.SONAR_TOKEN != null
|
||||||
uses: sonarsource/sonarcloud-github-action@master
|
uses: SonarSource/sonarqube-scan-action@v4
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
name: Markdown lint
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize, reopened]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
vale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: errata-ai/vale-action@v2.1.1
|
||||||
|
with:
|
||||||
|
fail_on_error: true
|
||||||
|
filter_mode: nofilter
|
||||||
|
vale_flags: "--glob=*.md --config=ci/.vale.ini"
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
---
|
||||||
name: Python
|
name: Python
|
||||||
|
|
||||||
on:
|
on:
|
||||||
@@ -10,19 +11,25 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- python-version: 3.11
|
||||||
|
- python-version: 3.12
|
||||||
|
- python-version: 3.13
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.9"
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
- name: Install tools
|
- name: Install ci tools
|
||||||
run: pip install .[tools]
|
run: pip install .[ci]
|
||||||
|
|
||||||
- name: Run ruff
|
- name: Run ruff lint
|
||||||
run: ruff netbox_slm
|
run: ruff check netbox_slm
|
||||||
|
|
||||||
- name: Run black
|
- name: Run ruff format
|
||||||
run: black netbox_slm
|
run: ruff format --check netbox_slm
|
||||||
|
|||||||
+104
@@ -0,0 +1,104 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [1.8.1](https://github.com/ICTU/netbox_slm/releases/tag/1.8.1) - 2025-04-11
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Fix filtering on foreign objects (#65)
|
||||||
|
|
||||||
|
## [1.8.0](https://github.com/ICTU/netbox_slm/releases/tag/1.8.0) - 2025-02-25
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* Toggle to show plugin as top level menu item (#42)
|
||||||
|
* Bulk import and bulk edit (#4)
|
||||||
|
* Changelog with backdated changes (#51)
|
||||||
|
* List filtering (#54)
|
||||||
|
* Show installations on native objects (#49)
|
||||||
|
* SPDX license expressions (#47)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
* Support NetBox v4.2.3 (#51)
|
||||||
|
* Render objects consistently (#58)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Model detail view listing of linked objects (#55)
|
||||||
|
* Search link of installations table cells (#48)
|
||||||
|
|
||||||
|
## [1.7.0](https://github.com/ICTU/netbox_slm/releases/tag/1.7.0) - 2024-07-09
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
* Support NetBox v4.0.6 (#38)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Hidden plugin data when not logged in (#37)
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
* Broken bulk import and bulk edit (#4)
|
||||||
|
|
||||||
|
## [1.6.0](https://github.com/ICTU/netbox_slm/releases/tag/1.6.0) - 2024-02-13
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* Additional `SoftwareProductVersion` fields (#10)
|
||||||
|
* Additional `SoftwareLicense` fields (#26)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
* Support NetBox v3.7.2 (#35)
|
||||||
|
|
||||||
|
## [1.5](https://github.com/ICTU/netbox_slm/releases/tag/1.5) - 2023-10-04
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* Hyperlink option for `SoftwareLicense.stored_location` (#27)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
* Update docs and imports for newer NetBox versions (#23)
|
||||||
|
* Support NetBox v3.6.1 (#21)
|
||||||
|
* Allow linking `SoftwareProductInstallation` to `Cluster` (#17)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Date picker for start and expiration dates (#22)
|
||||||
|
|
||||||
|
## [1.4](https://github.com/ICTU/netbox_slm/releases/tag/1.4) - 2023-06-30
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* License information for software installations (#8)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
* Replace `NetBoxModelCSVForm` with `NetBoxModelImportForm` (#18)
|
||||||
|
|
||||||
|
## [1.3](https://github.com/ICTU/netbox_slm/releases/tag/1.3) - 2023-04-19
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* Set up continuous integration (#3)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
* Support NetBox v3.4.7 (#14)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Unify API serialization (#12)
|
||||||
|
|
||||||
|
## [1.2](https://github.com/ICTU/netbox_slm/releases/tag/1.2) - 2022-05-19
|
||||||
|
|
||||||
|
* Fixed filtering of relevant versions when adding a new installation
|
||||||
|
* Manufacturer is now required for the add software product form
|
||||||
|
|
||||||
|
## [1.1](https://github.com/ICTU/netbox_slm/releases/tag/1.1) - 2022-05-03
|
||||||
|
|
||||||
|
* Fixed search filter
|
||||||
@@ -1,13 +1,201 @@
|
|||||||
Copyright 2022 ICTU
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
1. Definitions.
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
limitations under the License.
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|||||||
+151
@@ -0,0 +1,151 @@
|
|||||||
|
# Maintenance related tasks
|
||||||
|
|
||||||
|
|
||||||
|
## Version upgrade workflow
|
||||||
|
|
||||||
|
1. Update version spec in `netbox_slm/__init__.py` and `sonar-project.properties`
|
||||||
|
1. Check for any runtime errors and warnings in the `netbox-*` container logs
|
||||||
|
1. Create new version tag on GitHub, following semantic versioning as: `MAJOR.MINOR.PATCH`
|
||||||
|
1. Update the `CHANGELOG.md` with new version information and move `[Unreleased]` items to new version section
|
||||||
|
1. Build the package: `python -m build`
|
||||||
|
1. Upload the distributions to PyPI: `twine upload --skip-existing dist/*`
|
||||||
|
|
||||||
|
|
||||||
|
## Developer Guide (local installation)
|
||||||
|
|
||||||
|
*Follow the steps below on your local system to run NetBox and the
|
||||||
|
`netbox_slm` plugin in developer mode*
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
The goal below is to run all NetBox components in Docker and run a local
|
||||||
|
NetBox Django copy with auto-reload to develop the plugin pointing to
|
||||||
|
the PostgreSQL and Redis container instances, basically ignoring the
|
||||||
|
NetBox docker runtime server.
|
||||||
|
|
||||||
|
### Steps
|
||||||
|
|
||||||
|
from your projects directory clone the NetBox repository
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ git clone https://github.com/netbox-community/netbox
|
||||||
|
$ cd netbox
|
||||||
|
```
|
||||||
|
|
||||||
|
install the virtual environment
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ pipenv shell
|
||||||
|
$ pipenv install
|
||||||
|
```
|
||||||
|
|
||||||
|
create and edit `netbox/configuration.py` (based on the template file) add these lines at the end of the file;
|
||||||
|
|
||||||
|
```python
|
||||||
|
DEBUG = True
|
||||||
|
SECRET_KEY = 'dummy'
|
||||||
|
PLUGINS = [
|
||||||
|
'netbox_slm',
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
The NetBox installation above will be used to run Django management
|
||||||
|
commands like `runserver`, `makemigrations` and `migrate`, which will be
|
||||||
|
explained in the next steps below;
|
||||||
|
|
||||||
|
from your projects directory clone the `netbox_slm` repository
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ git clone https://github.com/ICTU/netbox_slm
|
||||||
|
$ cd netbox_slm
|
||||||
|
$ ./start-netbox.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This will start NetBox locally (requires Docker) and forward the Redis
|
||||||
|
and PostgreSQL ports to the localhost (make sure there’s no processes
|
||||||
|
using these ports or change the Dockerfile(s) accordingly)
|
||||||
|
|
||||||
|
Note, you can also start and stop NetBox by hand:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ cd netbox-docker
|
||||||
|
$ docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
or stop the stack with
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ docker compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
#### to start fresh:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ docker compose down
|
||||||
|
$ docker volume rm netbox-docker_netbox-postgres-data # et cetera
|
||||||
|
$ docker compose up -d --force-recreate
|
||||||
|
```
|
||||||
|
|
||||||
|
this will require you to re-run the migrate commando's for `netbox_slm`, see further down below
|
||||||
|
|
||||||
|
Go back to the NetBox `configuration.py` file and update the PostgreSQL and
|
||||||
|
Redis connection strings (username, password) to the ones the NetBox
|
||||||
|
Docker backend is using, for example (using default user and passwords
|
||||||
|
from the NetBox Docker example):
|
||||||
|
|
||||||
|
```python
|
||||||
|
# PostgreSQL database configuration. See the Django documentation for a complete list of available parameters:
|
||||||
|
# https://docs.djangoproject.com/en/stable/ref/settings/#databases
|
||||||
|
DATABASE = {
|
||||||
|
'NAME': 'netbox', # Database name
|
||||||
|
'USER': 'netbox', # PostgreSQL username
|
||||||
|
'PASSWORD': 'J5brHrAXFLQSif0K', # PostgreSQL password
|
||||||
|
'HOST': 'localhost', # Database server
|
||||||
|
'PORT': '', # Database port (leave blank for default)
|
||||||
|
'CONN_MAX_AGE': 300, # Max database connection age
|
||||||
|
}
|
||||||
|
|
||||||
|
# Redis database settings. Redis is used for caching and for queuing background tasks such as webhook events. A separate
|
||||||
|
# configuration exists for each. Full connection details are required in both sections, and it is strongly recommended
|
||||||
|
# to use two separate database IDs.
|
||||||
|
REDIS = {
|
||||||
|
'tasks': {
|
||||||
|
'HOST': 'localhost',
|
||||||
|
'PORT': 6379,
|
||||||
|
# Comment out `HOST` and `PORT` lines and uncomment the following if using Redis Sentinel
|
||||||
|
# 'SENTINELS': [('mysentinel.redis.example.com', 6379)],
|
||||||
|
# 'SENTINEL_SERVICE': 'netbox',
|
||||||
|
'PASSWORD': 'H733Kdjndks81',
|
||||||
|
'DATABASE': 0,
|
||||||
|
'SSL': False,
|
||||||
|
# Set this to True to skip TLS certificate verification
|
||||||
|
# This can expose the connection to attacks, be careful
|
||||||
|
# 'INSECURE_SKIP_TLS_VERIFY': False,
|
||||||
|
},
|
||||||
|
'caching': {
|
||||||
|
'HOST': 'localhost',
|
||||||
|
'PORT': 6379,
|
||||||
|
# Comment out `HOST` and `PORT` lines and uncomment the following if using Redis Sentinel
|
||||||
|
# 'SENTINELS': [('mysentinel.redis.example.com', 6379)],
|
||||||
|
# 'SENTINEL_SERVICE': 'netbox',
|
||||||
|
'PASSWORD': 'H733Kdjndks81',
|
||||||
|
'DATABASE': 1,
|
||||||
|
'SSL': False,
|
||||||
|
# Set this to True to skip TLS certificate verification
|
||||||
|
# This can expose the connection to attacks, be careful
|
||||||
|
# 'INSECURE_SKIP_TLS_VERIFY': False,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you can run commands from the NetBox repository like this;
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ cd netbox/netbox
|
||||||
|
$ export PYTHONPATH=../../netbox_slm/netbox_slm/ # or with the pipenv activated run `python3 setup.py develop` from the netbox_slm directory
|
||||||
|
$ python3 manage.py migrate netbox_slm
|
||||||
|
$ python3 manage.py runserver 8001
|
||||||
|
```
|
||||||
|
|
||||||
|
Visit http://127.0.0.1:8001 in the browser to see the auto reloading version of the NetBox UI.
|
||||||
|
Port 8000 is taken by the Docker ran variant.
|
||||||
@@ -13,18 +13,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
## Known Issues
|
|
||||||
|
|
||||||
- WARNING: This plugin is only tested with a single NetBox version at this time.
|
|
||||||
- CSV/Bulk imports for SoftwareProduct, Version and Installation are currently broken (WIP)
|
|
||||||
|
|
||||||
## Installation Guide
|
## Installation Guide
|
||||||
|
|
||||||
When using the Docker version of NetBox, first follow the netbox-docker [quickstart](https://github.com/netbox-community/netbox-docker#quickstart) instructions to clone the netbox-docker repo and set up the ``docker-compose.override.yml``.
|
When using the Docker version of NetBox, first follow the `netbox-docker` [quickstart](https://github.com/netbox-community/netbox-docker#quickstart) instructions to clone the `netbox-docker` repository and set up the ``docker-compose.override.yml``.
|
||||||
|
|
||||||
Next, follow these instructions (based on the NetBox docker variant
|
Note that this plugin is only tested against a single NetBox version at this time, see [Dockerfile-CI](https://github.com/ICTU/netbox_slm/blob/master/ci/Dockerfile-CI).
|
||||||
[instructions](https://github.com/netbox-community/netbox-docker/wiki/Configuration#custom-configuration-files))
|
|
||||||
to install the NetBox SLM plugin:
|
Next, follow these instructions (based on the NetBox docker variant [instructions](https://github.com/netbox-community/netbox-docker/wiki/Configuration#custom-configuration-files)) to install the NetBox SLM plugin:
|
||||||
|
|
||||||
1. Add ``netbox_slm`` to the ``PLUGINS`` list in
|
1. Add ``netbox_slm`` to the ``PLUGINS`` list in
|
||||||
``configuration/plugins.py``.
|
``configuration/plugins.py``.
|
||||||
@@ -32,195 +27,35 @@ to install the NetBox SLM plugin:
|
|||||||
contents.
|
contents.
|
||||||
3. Create a ``Dockerfile-SLM`` with contents:
|
3. Create a ``Dockerfile-SLM`` with contents:
|
||||||
|
|
||||||
```
|
```dockerfile
|
||||||
FROM netboxcommunity/netbox:vX.Y.Z
|
FROM netboxcommunity/netbox:vX.Y.Z
|
||||||
|
|
||||||
COPY ./plugin_requirements.txt /
|
COPY ../pyproject.toml /tmp/
|
||||||
RUN /opt/netbox/venv/bin/pip install --no-warn-script-location -r /plugin_requirements.txt
|
RUN uv pip install -r /tmp/pyproject.toml
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Create a ``docker-compose.override.yml`` with contents:
|
4. Create a ``docker-compose.override.yml`` with contents:
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
version: '3.7'
|
version: "3.7"
|
||||||
services:
|
services:
|
||||||
netbox:
|
netbox:
|
||||||
ports:
|
ports:
|
||||||
- 8000:8080
|
- "8000:8080"
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile-SLM
|
dockerfile: Dockerfile-SLM
|
||||||
image: netbox:slm
|
image: netbox:slm
|
||||||
netbox-worker:
|
netbox-worker:
|
||||||
image: netbox:slm
|
image: netbox:slm
|
||||||
netbox-housekeeping:
|
netbox-housekeeping:
|
||||||
image: netbox:slm
|
image: netbox:slm
|
||||||
```
|
```
|
||||||
|
|
||||||
Now, build the image: ``docker compose build --no-cache``
|
Now, build the image: ``docker compose build --no-cache``
|
||||||
|
|
||||||
And finally, run NetBox with the SLM plugin: ``docker compose up``
|
And finally, run NetBox with the SLM plugin: ``docker compose up``
|
||||||
|
|
||||||
## Releasing Guide
|
|
||||||
|
|
||||||
To draft a release;
|
|
||||||
|
|
||||||
update the `netbox_slm/__init__.py` file to reflect the new version, then from the *src*
|
|
||||||
directory run
|
|
||||||
|
|
||||||
```
|
|
||||||
$ python -m build
|
|
||||||
$ twine upload dist/*
|
|
||||||
```
|
|
||||||
|
|
||||||
On Github.com create a similar tag and version. These steps could be
|
|
||||||
automated with a github workflow.
|
|
||||||
|
|
||||||
|
|
||||||
## Developer Guide (local installation)
|
|
||||||
|
|
||||||
*Follow the steps below on your local system to run netbox and the
|
|
||||||
netbox_slm plugin in developer mode*
|
|
||||||
|
|
||||||
### Setup
|
|
||||||
|
|
||||||
The goal below is to run all NetBox components in Docker and run a local
|
|
||||||
NetBox Django copy with auto-reload to develop the plugin pointing to
|
|
||||||
the Dockerized postgres and redis instances, basically ignoring the
|
|
||||||
netbox docker runtime server.
|
|
||||||
|
|
||||||
### Steps
|
|
||||||
|
|
||||||
from your projects directory clone the netbox repository
|
|
||||||
|
|
||||||
```
|
|
||||||
$ git clone https://github.com/netbox-community/netbox
|
|
||||||
$ cd netbox
|
|
||||||
```
|
|
||||||
|
|
||||||
install the virtual environment
|
|
||||||
|
|
||||||
```
|
|
||||||
$ pipenv shell
|
|
||||||
$ pipenv install
|
|
||||||
```
|
|
||||||
|
|
||||||
create and edit netbox/configuration.py (based on the template file) add these lines at the end of the file;
|
|
||||||
|
|
||||||
```
|
|
||||||
DEBUG = True
|
|
||||||
SECRET_KEY = 'dummy'
|
|
||||||
DEVELOPER = True
|
|
||||||
PLUGINS = [
|
|
||||||
'netbox_slm',
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
The NetBox installation above will be used to run Django management
|
|
||||||
commands like runserver, makemigrations and migrate, which will be
|
|
||||||
explained in the next steps below;
|
|
||||||
|
|
||||||
from your projects directory clone the netbox-slm repository
|
|
||||||
|
|
||||||
```
|
|
||||||
$ git clone https://github.com/ICTU/netbox_slm
|
|
||||||
$ cd netbox_slm
|
|
||||||
$ ./start-netbox.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
This will start NetBox locally (requires Docker) and forward the redis
|
|
||||||
and postgres ports to the localhost (make sure there’s no processes
|
|
||||||
using these ports or change the dockerfiles accordingly)
|
|
||||||
|
|
||||||
Note, you can also start and stop netbox by hand:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ cd netbox-docker
|
|
||||||
$ docker-compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
or stop the stack with
|
|
||||||
|
|
||||||
```
|
|
||||||
$ docker-compose down
|
|
||||||
```
|
|
||||||
|
|
||||||
# to start fresh:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ docker-compose down
|
|
||||||
$ docker volume rm netbox-docker_netbox-postgres-data # et cetera
|
|
||||||
$ docker-compose up -d --force-recreate
|
|
||||||
```
|
|
||||||
|
|
||||||
this will require you to re-run the migrate commando's for netbox-slm, see further down below
|
|
||||||
|
|
||||||
Go back to the netbox configuration.py file and update the postgres and
|
|
||||||
redis connection strings (username, password) to the ones the netbox
|
|
||||||
docker backend is using, for example (using default user and passwords
|
|
||||||
from the netbox docker example):
|
|
||||||
|
|
||||||
```
|
|
||||||
<<collapsed>>
|
|
||||||
|
|
||||||
# PostgreSQL database configuration. See the Django documentation for a complete list of available parameters:
|
|
||||||
# https://docs.djangoproject.com/en/stable/ref/settings/#databases
|
|
||||||
DATABASE = {
|
|
||||||
'NAME': 'netbox', # Database name
|
|
||||||
'USER': 'netbox', # PostgreSQL username
|
|
||||||
'PASSWORD': 'J5brHrAXFLQSif0K', # PostgreSQL password
|
|
||||||
'HOST': 'localhost', # Database server
|
|
||||||
'PORT': '', # Database port (leave blank for default)
|
|
||||||
'CONN_MAX_AGE': 300, # Max database connection age
|
|
||||||
}
|
|
||||||
|
|
||||||
# Redis database settings. Redis is used for caching and for queuing background tasks such as webhook events. A separate
|
|
||||||
# configuration exists for each. Full connection details are required in both sections, and it is strongly recommended
|
|
||||||
# to use two separate database IDs.
|
|
||||||
REDIS = {
|
|
||||||
'tasks': {
|
|
||||||
'HOST': 'localhost',
|
|
||||||
'PORT': 6379,
|
|
||||||
# Comment out `HOST` and `PORT` lines and uncomment the following if using Redis Sentinel
|
|
||||||
# 'SENTINELS': [('mysentinel.redis.example.com', 6379)],
|
|
||||||
# 'SENTINEL_SERVICE': 'netbox',
|
|
||||||
'PASSWORD': 'H733Kdjndks81',
|
|
||||||
'DATABASE': 0,
|
|
||||||
'SSL': False,
|
|
||||||
# Set this to True to skip TLS certificate verification
|
|
||||||
# This can expose the connection to attacks, be careful
|
|
||||||
# 'INSECURE_SKIP_TLS_VERIFY': False,
|
|
||||||
},
|
|
||||||
'caching': {
|
|
||||||
'HOST': 'localhost',
|
|
||||||
'PORT': 6379,
|
|
||||||
# Comment out `HOST` and `PORT` lines and uncomment the following if using Redis Sentinel
|
|
||||||
# 'SENTINELS': [('mysentinel.redis.example.com', 6379)],
|
|
||||||
# 'SENTINEL_SERVICE': 'netbox',
|
|
||||||
'PASSWORD': 'H733Kdjndks81',
|
|
||||||
'DATABASE': 1,
|
|
||||||
'SSL': False,
|
|
||||||
# Set this to True to skip TLS certificate verification
|
|
||||||
# This can expose the connection to attacks, be careful
|
|
||||||
# 'INSECURE_SKIP_TLS_VERIFY': False,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
<<collapsed>>
|
|
||||||
```
|
|
||||||
|
|
||||||
Now you can run commands from the netbox repository like this;
|
|
||||||
|
|
||||||
```
|
|
||||||
$ cd netbox/netbox
|
|
||||||
$ export PYTHONPATH=../../netbox_slm/netbox_slm/ # or with the pipenv activated run `python3 setup.py develop` from the netbox_slm directory
|
|
||||||
$ python3 manage.py migrate netbox_slm
|
|
||||||
$ python3 manage.py runserver 8001
|
|
||||||
```
|
|
||||||
|
|
||||||
Visit http://127.0.0.1:8001 in the browser to see the auto reloading
|
|
||||||
version of the netbox UI. Port 8000 is taken by the docker ran variant.
|
|
||||||
|
|
||||||
|
|
||||||
## Get in touch
|
## Get in touch
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
StylesPath = styles
|
||||||
|
|
||||||
|
MinAlertLevel = suggestion
|
||||||
|
Vocab = Base
|
||||||
|
|
||||||
|
Packages = proselint
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
BasedOnStyles = Vale, proselint
|
||||||
+9
-8
@@ -1,17 +1,18 @@
|
|||||||
ARG PYTHON_VERSION=3.10
|
ARG PYTHON_VERSION=3.12
|
||||||
FROM python:${PYTHON_VERSION}-alpine AS compile-image
|
FROM python:${PYTHON_VERSION}-alpine AS compile-image
|
||||||
|
# Python version build arg is not actually supported by NetBox container image, only used for maintenance convenience
|
||||||
|
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
RUN python -m venv /opt/netbox/venv
|
RUN python3 -m venv /opt/netbox/venv &&\
|
||||||
ENV PATH="/opt/netbox/venv/bin:$PATH"
|
source /opt/netbox/venv/bin/activate
|
||||||
|
|
||||||
COPY ../ /build
|
COPY ../ /build
|
||||||
|
|
||||||
RUN pip install -U .[build]
|
RUN pip install -U .[build] &&\
|
||||||
RUN python -m build
|
python -m build &&\
|
||||||
RUN pip install --no-index /build
|
pip install --no-index /build
|
||||||
|
|
||||||
FROM netboxcommunity/netbox:v3.7.2
|
FROM netboxcommunity/netbox:v4.2.3
|
||||||
|
ARG PYTHON_VERSION=3.12
|
||||||
|
|
||||||
ARG PYTHON_VERSION=3.10
|
|
||||||
COPY --from=compile-image /opt/netbox/venv/lib/python${PYTHON_VERSION}/site-packages/netbox_slm /opt/netbox/venv/lib/python${PYTHON_VERSION}/site-packages/netbox_slm
|
COPY --from=compile-image /opt/netbox/venv/lib/python${PYTHON_VERSION}/site-packages/netbox_slm /opt/netbox/venv/lib/python${PYTHON_VERSION}/site-packages/netbox_slm
|
||||||
|
|||||||
+4
-6
@@ -1,7 +1,5 @@
|
|||||||
FROM netboxcommunity/netbox:v3.7.2
|
FROM netboxcommunity/netbox:v4.2.3
|
||||||
|
ARG PYTHON_VERSION=3.12
|
||||||
|
|
||||||
RUN mkdir /ci && chmod go+w /ci
|
COPY ../pyproject.toml /tmp/
|
||||||
COPY ../ci/requirements_ci.txt /ci/
|
RUN uv pip install -r /tmp/pyproject.toml --extra ci
|
||||||
RUN pip install -r /ci/requirements_ci.txt
|
|
||||||
|
|
||||||
COPY ../netbox_slm /opt/netbox/netbox/netbox_slm
|
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
"""
|
||||||
|
Minimal NetBox config for NetBox SLM plugin testing, docs: https://netbox.readthedocs.io/en/stable/configuration/
|
||||||
|
Based on https://github.com/netbox-community/netbox/blob/v4.2.3/netbox/netbox/configuration_testing.py
|
||||||
|
"""
|
||||||
|
from os import environ
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = ['*']
|
||||||
|
|
||||||
|
DATABASE = {
|
||||||
|
'NAME': environ.get('DB_NAME', 'netbox'),
|
||||||
|
'USER': environ.get('DB_USER', ''),
|
||||||
|
'PASSWORD': environ.get('DB_PASSWORD', ''),
|
||||||
|
'HOST': environ.get('DB_HOST', 'localhost'),
|
||||||
|
'PORT': environ.get('DB_PORT', ''),
|
||||||
|
'CONN_MAX_AGE': int(environ.get('DB_CONN_MAX_AGE', '300')),
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG = environ.get('DEBUG', 'False').lower() == 'true'
|
||||||
|
DEVELOPER = environ.get('DEVELOPER', 'False').lower() == 'true'
|
||||||
|
|
||||||
|
PLUGINS = ["netbox_slm"]
|
||||||
|
|
||||||
|
REDIS = {
|
||||||
|
'tasks': {
|
||||||
|
'HOST': environ.get('REDIS_HOST', 'localhost'),
|
||||||
|
'PORT': int(environ.get('REDIS_PORT', 6379)),
|
||||||
|
'PASSWORD': environ.get('REDIS_PASSWORD', ''),
|
||||||
|
'DATABASE': int(environ.get('REDIS_DATABASE', 0)),
|
||||||
|
'SSL': environ.get('REDIS_SSL', 'False').lower() == 'true',
|
||||||
|
},
|
||||||
|
'caching': {
|
||||||
|
'HOST': environ.get('REDIS_CACHE_HOST', environ.get('REDIS_HOST', 'localhost')),
|
||||||
|
'PORT': int(environ.get('REDIS_CACHE_PORT', environ.get('REDIS_PORT', 6379))),
|
||||||
|
'PASSWORD': environ.get('REDIS_CACHE_PASSWORD', environ.get('REDIS_PASSWORD', '')),
|
||||||
|
'DATABASE': int(environ.get('REDIS_CACHE_DATABASE', 1)),
|
||||||
|
'SSL': environ.get('REDIS_CACHE_SSL', environ.get('REDIS_SSL', 'False')).lower() == 'true',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
SECRET_KEY = 'dummydummydummydummydummydummydummydummydummydummy'
|
||||||
@@ -1,257 +0,0 @@
|
|||||||
####
|
|
||||||
## We recommend to not edit this file.
|
|
||||||
## Create separate files to overwrite the settings.
|
|
||||||
## See `extra.py` as an example.
|
|
||||||
####
|
|
||||||
|
|
||||||
import re
|
|
||||||
from os import environ
|
|
||||||
from os.path import abspath, dirname, join
|
|
||||||
|
|
||||||
# For reference see https://netbox.readthedocs.io/en/stable/configuration/
|
|
||||||
# Based on https://github.com/netbox-community/netbox/blob/master/netbox/netbox/configuration.example.py
|
|
||||||
|
|
||||||
# Read secret from file
|
|
||||||
def _read_secret(secret_name, default = None):
|
|
||||||
try:
|
|
||||||
f = open('/run/secrets/' + secret_name, 'r', encoding='utf-8')
|
|
||||||
except EnvironmentError:
|
|
||||||
return default
|
|
||||||
else:
|
|
||||||
with f:
|
|
||||||
return f.readline().strip()
|
|
||||||
|
|
||||||
_BASE_DIR = dirname(dirname(abspath(__file__)))
|
|
||||||
|
|
||||||
#########################
|
|
||||||
# #
|
|
||||||
# Required settings #
|
|
||||||
# #
|
|
||||||
#########################
|
|
||||||
|
|
||||||
# This is a list of valid fully-qualified domain names (FQDNs) for the NetBox server. NetBox will not permit write
|
|
||||||
# access to the server via any other hostnames. The first FQDN in the list will be treated as the preferred name.
|
|
||||||
#
|
|
||||||
# Example: ALLOWED_HOSTS = ['netbox.example.com', 'netbox.internal.local']
|
|
||||||
ALLOWED_HOSTS = environ.get('ALLOWED_HOSTS', '*').split(' ')
|
|
||||||
|
|
||||||
# PostgreSQL database configuration. See the Django documentation for a complete list of available parameters:
|
|
||||||
# https://docs.djangoproject.com/en/stable/ref/settings/#databases
|
|
||||||
DATABASE = {
|
|
||||||
'NAME': environ.get('DB_NAME', 'netbox'), # Database name
|
|
||||||
'USER': environ.get('DB_USER', ''), # PostgreSQL username
|
|
||||||
'PASSWORD': _read_secret('db_password', environ.get('DB_PASSWORD', '')),
|
|
||||||
# PostgreSQL password
|
|
||||||
'HOST': environ.get('DB_HOST', 'localhost'), # Database server
|
|
||||||
'PORT': environ.get('DB_PORT', ''), # Database port (leave blank for default)
|
|
||||||
'OPTIONS': {'sslmode': environ.get('DB_SSLMODE', 'prefer')},
|
|
||||||
# Database connection SSLMODE
|
|
||||||
'CONN_MAX_AGE': int(environ.get('DB_CONN_MAX_AGE', '300')),
|
|
||||||
# Max database connection age
|
|
||||||
'DISABLE_SERVER_SIDE_CURSORS': environ.get('DB_DISABLE_SERVER_SIDE_CURSORS', 'False').lower() == 'true',
|
|
||||||
# Disable the use of server-side cursors transaction pooling
|
|
||||||
}
|
|
||||||
|
|
||||||
# Redis database settings. Redis is used for caching and for queuing background tasks such as webhook events. A separate
|
|
||||||
# configuration exists for each. Full connection details are required in both sections, and it is strongly recommended
|
|
||||||
# to use two separate database IDs.
|
|
||||||
REDIS = {
|
|
||||||
'tasks': {
|
|
||||||
'HOST': environ.get('REDIS_HOST', 'localhost'),
|
|
||||||
'PORT': int(environ.get('REDIS_PORT', 6379)),
|
|
||||||
'PASSWORD': _read_secret('redis_password', environ.get('REDIS_PASSWORD', '')),
|
|
||||||
'DATABASE': int(environ.get('REDIS_DATABASE', 0)),
|
|
||||||
'SSL': environ.get('REDIS_SSL', 'False').lower() == 'true',
|
|
||||||
'INSECURE_SKIP_TLS_VERIFY': environ.get('REDIS_INSECURE_SKIP_TLS_VERIFY', 'False').lower() == 'true',
|
|
||||||
},
|
|
||||||
'caching': {
|
|
||||||
'HOST': environ.get('REDIS_CACHE_HOST', environ.get('REDIS_HOST', 'localhost')),
|
|
||||||
'PORT': int(environ.get('REDIS_CACHE_PORT', environ.get('REDIS_PORT', 6379))),
|
|
||||||
'PASSWORD': _read_secret('redis_cache_password', environ.get('REDIS_CACHE_PASSWORD', environ.get('REDIS_PASSWORD', ''))),
|
|
||||||
'DATABASE': int(environ.get('REDIS_CACHE_DATABASE', 1)),
|
|
||||||
'SSL': environ.get('REDIS_CACHE_SSL', environ.get('REDIS_SSL', 'False')).lower() == 'true',
|
|
||||||
'INSECURE_SKIP_TLS_VERIFY': environ.get('REDIS_CACHE_INSECURE_SKIP_TLS_VERIFY', environ.get('REDIS_INSECURE_SKIP_TLS_VERIFY', 'False')).lower() == 'true',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
# This key is used for secure generation of random numbers and strings. It must never be exposed outside of this file.
|
|
||||||
# For optimal security, SECRET_KEY should be at least 50 characters in length and contain a mix of letters, numbers, and
|
|
||||||
# symbols. NetBox will not run without this defined. For more information, see
|
|
||||||
# https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-SECRET_KEY
|
|
||||||
SECRET_KEY = _read_secret('secret_key', environ.get('SECRET_KEY', ''))
|
|
||||||
|
|
||||||
|
|
||||||
#########################
|
|
||||||
# #
|
|
||||||
# Optional settings #
|
|
||||||
# #
|
|
||||||
#########################
|
|
||||||
|
|
||||||
# Specify one or more name and email address tuples representing NetBox administrators. These people will be notified of
|
|
||||||
# application errors (assuming correct email settings are provided).
|
|
||||||
ADMINS = [
|
|
||||||
# ['John Doe', 'jdoe@example.com'],
|
|
||||||
]
|
|
||||||
|
|
||||||
# URL schemes that are allowed within links in NetBox
|
|
||||||
ALLOWED_URL_SCHEMES = (
|
|
||||||
'file', 'ftp', 'ftps', 'http', 'https', 'irc', 'mailto', 'sftp', 'ssh', 'tel', 'telnet', 'tftp', 'vnc', 'xmpp',
|
|
||||||
)
|
|
||||||
|
|
||||||
# Optionally display a persistent banner at the top and/or bottom of every page. HTML is allowed. To display the same
|
|
||||||
# content in both banners, define BANNER_TOP and set BANNER_BOTTOM = BANNER_TOP.
|
|
||||||
BANNER_TOP = environ.get('BANNER_TOP', '')
|
|
||||||
BANNER_BOTTOM = environ.get('BANNER_BOTTOM', '')
|
|
||||||
|
|
||||||
# Text to include on the login page above the login form. HTML is allowed.
|
|
||||||
BANNER_LOGIN = environ.get('BANNER_LOGIN', '')
|
|
||||||
|
|
||||||
# Base URL path if accessing NetBox within a directory. For example, if installed at http://example.com/netbox/, set:
|
|
||||||
# BASE_PATH = 'netbox/'
|
|
||||||
BASE_PATH = environ.get('BASE_PATH', '')
|
|
||||||
|
|
||||||
# Maximum number of days to retain logged changes. Set to 0 to retain changes indefinitely. (Default: 90)
|
|
||||||
CHANGELOG_RETENTION = int(environ.get('CHANGELOG_RETENTION', 90))
|
|
||||||
|
|
||||||
# API Cross-Origin Resource Sharing (CORS) settings. If CORS_ORIGIN_ALLOW_ALL is set to True, all origins will be
|
|
||||||
# allowed. Otherwise, define a list of allowed origins using either CORS_ORIGIN_WHITELIST or
|
|
||||||
# CORS_ORIGIN_REGEX_WHITELIST. For more information, see https://github.com/ottoyiu/django-cors-headers
|
|
||||||
CORS_ORIGIN_ALLOW_ALL = environ.get('CORS_ORIGIN_ALLOW_ALL', 'False').lower() == 'true'
|
|
||||||
CORS_ORIGIN_WHITELIST = list(filter(None, environ.get('CORS_ORIGIN_WHITELIST', 'https://localhost').split(' ')))
|
|
||||||
CORS_ORIGIN_REGEX_WHITELIST = [re.compile(r) for r in list(filter(None, environ.get('CORS_ORIGIN_REGEX_WHITELIST', '').split(' ')))]
|
|
||||||
|
|
||||||
# Cross-Site-Request-Forgery-Attack settings. If Netbox is sitting behind a reverse proxy, you might need to set the CSRF_TRUSTED_ORIGINS flag.
|
|
||||||
# Django 4.0 requires to specify the URL Scheme in this setting. An example environment variable could be specified like:
|
|
||||||
# CSRF_TRUSTED_ORIGINS=https://demo.netbox.dev http://demo.netbox.dev
|
|
||||||
CSRF_TRUSTED_ORIGINS = list(filter(None, environ.get('CSRF_TRUSTED_ORIGINS', '').split(' ')))
|
|
||||||
|
|
||||||
# Set to True to enable server debugging. WARNING: Debugging introduces a substantial performance penalty and may reveal
|
|
||||||
# sensitive information about your installation. Only enable debugging while performing testing. Never enable debugging
|
|
||||||
# on a production system.
|
|
||||||
DEBUG = environ.get('DEBUG', 'False').lower() == 'true'
|
|
||||||
DEVELOPER = environ.get('DEVELOPER', 'False').lower() == 'true'
|
|
||||||
|
|
||||||
# Email settings
|
|
||||||
EMAIL = {
|
|
||||||
'SERVER': environ.get('EMAIL_SERVER', 'localhost'),
|
|
||||||
'PORT': int(environ.get('EMAIL_PORT', 25)),
|
|
||||||
'USERNAME': environ.get('EMAIL_USERNAME', ''),
|
|
||||||
'PASSWORD': _read_secret('email_password', environ.get('EMAIL_PASSWORD', '')),
|
|
||||||
'USE_SSL': environ.get('EMAIL_USE_SSL', 'False').lower() == 'true',
|
|
||||||
'USE_TLS': environ.get('EMAIL_USE_TLS', 'False').lower() == 'true',
|
|
||||||
'SSL_CERTFILE': environ.get('EMAIL_SSL_CERTFILE', ''),
|
|
||||||
'SSL_KEYFILE': environ.get('EMAIL_SSL_KEYFILE', ''),
|
|
||||||
'TIMEOUT': int(environ.get('EMAIL_TIMEOUT', 10)), # seconds
|
|
||||||
'FROM_EMAIL': environ.get('EMAIL_FROM', ''),
|
|
||||||
}
|
|
||||||
|
|
||||||
# Enforcement of unique IP space can be toggled on a per-VRF basis. To enforce unique IP space within the global table
|
|
||||||
# (all prefixes and IP addresses not assigned to a VRF), set ENFORCE_GLOBAL_UNIQUE to True.
|
|
||||||
ENFORCE_GLOBAL_UNIQUE = environ.get('ENFORCE_GLOBAL_UNIQUE', 'False').lower() == 'true'
|
|
||||||
|
|
||||||
# Exempt certain models from the enforcement of view permissions. Models listed here will be viewable by all users and
|
|
||||||
# by anonymous users. List models in the form `<app>.<model>`. Add '*' to this list to exempt all models.
|
|
||||||
EXEMPT_VIEW_PERMISSIONS = list(filter(None, environ.get('EXEMPT_VIEW_PERMISSIONS', '').split(' ')))
|
|
||||||
|
|
||||||
# Enable GraphQL API.
|
|
||||||
GRAPHQL_ENABLED = environ.get('GRAPHQL_ENABLED', 'True').lower() == 'true'
|
|
||||||
|
|
||||||
# Enable custom logging. Please see the Django documentation for detailed guidance on configuring custom logs:
|
|
||||||
# https://docs.djangoproject.com/en/stable/topics/logging/
|
|
||||||
LOGGING = {}
|
|
||||||
|
|
||||||
# Setting this to True will permit only authenticated users to access any part of NetBox. By default, anonymous users
|
|
||||||
# are permitted to access most data in NetBox (excluding secrets) but not make any changes.
|
|
||||||
LOGIN_REQUIRED = environ.get('LOGIN_REQUIRED', 'False').lower() == 'true'
|
|
||||||
|
|
||||||
# The length of time (in seconds) for which a user will remain logged into the web UI before being prompted to
|
|
||||||
# re-authenticate. (Default: 1209600 [14 days])
|
|
||||||
LOGIN_TIMEOUT = int(environ.get('LOGIN_TIMEOUT', 1209600))
|
|
||||||
|
|
||||||
# Setting this to True will display a "maintenance mode" banner at the top of every page.
|
|
||||||
MAINTENANCE_MODE = environ.get('MAINTENANCE_MODE', 'False').lower() == 'true'
|
|
||||||
|
|
||||||
# Maps provider
|
|
||||||
MAPS_URL = environ.get('MAPS_URL', None)
|
|
||||||
|
|
||||||
# An API consumer can request an arbitrary number of objects =by appending the "limit" parameter to the URL (e.g.
|
|
||||||
# "?limit=1000"). This setting defines the maximum limit. Setting it to 0 or None will allow an API consumer to request
|
|
||||||
# all objects by specifying "?limit=0".
|
|
||||||
MAX_PAGE_SIZE = int(environ.get('MAX_PAGE_SIZE', 1000))
|
|
||||||
|
|
||||||
# The file path where uploaded media such as image attachments are stored. A trailing slash is not needed. Note that
|
|
||||||
# the default value of this setting is derived from the installed location.
|
|
||||||
MEDIA_ROOT = environ.get('MEDIA_ROOT', join(_BASE_DIR, 'media'))
|
|
||||||
|
|
||||||
# Expose Prometheus monitoring metrics at the HTTP endpoint '/metrics'
|
|
||||||
METRICS_ENABLED = environ.get('METRICS_ENABLED', 'False').lower() == 'true'
|
|
||||||
|
|
||||||
# Credentials that NetBox will uses to authenticate to devices when connecting via NAPALM.
|
|
||||||
NAPALM_USERNAME = environ.get('NAPALM_USERNAME', '')
|
|
||||||
NAPALM_PASSWORD = _read_secret('napalm_password', environ.get('NAPALM_PASSWORD', ''))
|
|
||||||
|
|
||||||
# NAPALM timeout (in seconds). (Default: 30)
|
|
||||||
NAPALM_TIMEOUT = int(environ.get('NAPALM_TIMEOUT', 30))
|
|
||||||
|
|
||||||
# NAPALM optional arguments (see http://napalm.readthedocs.io/en/latest/support/#optional-arguments). Arguments must
|
|
||||||
# be provided as a dictionary.
|
|
||||||
NAPALM_ARGS = {}
|
|
||||||
|
|
||||||
# Determine how many objects to display per page within a list. (Default: 50)
|
|
||||||
PAGINATE_COUNT = int(environ.get('PAGINATE_COUNT', 50))
|
|
||||||
|
|
||||||
# Enable installed plugins. Add the name of each plugin to the list.
|
|
||||||
PLUGINS = ['netbox_slm']
|
|
||||||
|
|
||||||
# Plugins configuration settings. These settings are used by various plugins that the user may have installed.
|
|
||||||
# Each key in the dictionary is the name of an installed plugin and its value is a dictionary of settings.
|
|
||||||
PLUGINS_CONFIG = {
|
|
||||||
}
|
|
||||||
|
|
||||||
# When determining the primary IP address for a device, IPv6 is preferred over IPv4 by default. Set this to True to
|
|
||||||
# prefer IPv4 instead.
|
|
||||||
PREFER_IPV4 = environ.get('PREFER_IPV4', 'False').lower() == 'true'
|
|
||||||
|
|
||||||
# Rack elevation size defaults, in pixels. For best results, the ratio of width to height should be roughly 10:1.
|
|
||||||
RACK_ELEVATION_DEFAULT_UNIT_HEIGHT = int(environ.get('RACK_ELEVATION_DEFAULT_UNIT_HEIGHT', 22))
|
|
||||||
RACK_ELEVATION_DEFAULT_UNIT_WIDTH = int(environ.get('RACK_ELEVATION_DEFAULT_UNIT_WIDTH', 220))
|
|
||||||
|
|
||||||
# Remote authentication support
|
|
||||||
REMOTE_AUTH_ENABLED = environ.get('REMOTE_AUTH_ENABLED', 'False').lower() == 'true'
|
|
||||||
REMOTE_AUTH_BACKEND = environ.get('REMOTE_AUTH_BACKEND', 'netbox.authentication.RemoteUserBackend')
|
|
||||||
REMOTE_AUTH_HEADER = environ.get('REMOTE_AUTH_HEADER', 'HTTP_REMOTE_USER')
|
|
||||||
REMOTE_AUTH_AUTO_CREATE_USER = environ.get('REMOTE_AUTH_AUTO_CREATE_USER', 'True').lower() == 'true'
|
|
||||||
REMOTE_AUTH_DEFAULT_GROUPS = list(filter(None, environ.get('REMOTE_AUTH_DEFAULT_GROUPS', '').split(' ')))
|
|
||||||
|
|
||||||
# This repository is used to check whether there is a new release of NetBox available. Set to None to disable the
|
|
||||||
# version check or use the URL below to check for release in the official NetBox repository.
|
|
||||||
# https://api.github.com/repos/netbox-community/netbox/releases
|
|
||||||
RELEASE_CHECK_URL = environ.get('RELEASE_CHECK_URL', None)
|
|
||||||
|
|
||||||
# The file path where custom reports will be stored. A trailing slash is not needed. Note that the default value of
|
|
||||||
# this setting is derived from the installed location.
|
|
||||||
REPORTS_ROOT = environ.get('REPORTS_ROOT', '/etc/netbox/reports')
|
|
||||||
|
|
||||||
# Maximum execution time for background tasks, in seconds.
|
|
||||||
RQ_DEFAULT_TIMEOUT = int(environ.get('RQ_DEFAULT_TIMEOUT', 300))
|
|
||||||
|
|
||||||
# The file path where custom scripts will be stored. A trailing slash is not needed. Note that the default value of
|
|
||||||
# this setting is derived from the installed location.
|
|
||||||
SCRIPTS_ROOT = environ.get('SCRIPTS_ROOT', '/etc/netbox/scripts')
|
|
||||||
|
|
||||||
# By default, NetBox will store session data in the database. Alternatively, a file path can be specified here to use
|
|
||||||
# local file storage instead. (This can be useful for enabling authentication on a standby instance with read-only
|
|
||||||
# database access.) Note that the user as which NetBox runs must have read and write permissions to this path.
|
|
||||||
SESSION_FILE_PATH = environ.get('SESSIONS_ROOT', None)
|
|
||||||
|
|
||||||
# Time zone (default: UTC)
|
|
||||||
TIME_ZONE = environ.get('TIME_ZONE', 'UTC')
|
|
||||||
|
|
||||||
# Date/time formatting. See the following link for supported formats:
|
|
||||||
# https://docs.djangoproject.com/en/stable/ref/templates/builtins/#date
|
|
||||||
DATE_FORMAT = environ.get('DATE_FORMAT', 'N j, Y')
|
|
||||||
SHORT_DATE_FORMAT = environ.get('SHORT_DATE_FORMAT', 'Y-m-d')
|
|
||||||
TIME_FORMAT = environ.get('TIME_FORMAT', 'g:i a')
|
|
||||||
SHORT_TIME_FORMAT = environ.get('SHORT_TIME_FORMAT', 'H:i:s')
|
|
||||||
DATETIME_FORMAT = environ.get('DATETIME_FORMAT', 'N j, Y g:i a')
|
|
||||||
SHORT_DATETIME_FORMAT = environ.get('SHORT_DATETIME_FORMAT', 'Y-m-d H:i')
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
PLUGINS = ['netbox_slm']
|
|
||||||
DEBUG = True
|
|
||||||
SECRET_KEY = 'dummydummydummydummydummydummydummydummydummydummy'
|
|
||||||
DEVELOPER = True
|
|
||||||
PLUGINS_CONFIG = {
|
|
||||||
'netbox_slm': dict(
|
|
||||||
TEST_RUNNER='xmlrunner.extra.djangotestrunner.XMLTestRunner',
|
|
||||||
TEST_OUTPUT_DIR='/ci/reports/',
|
|
||||||
TEST_OUTPUT_FILE_NAME='junit.xml',
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
version: '3.7'
|
---
|
||||||
services:
|
services:
|
||||||
netbox:
|
netbox:
|
||||||
ports:
|
ports:
|
||||||
@@ -8,13 +8,28 @@ services:
|
|||||||
dockerfile: ci/Dockerfile-CI
|
dockerfile: ci/Dockerfile-CI
|
||||||
image: netbox:slm
|
image: netbox:slm
|
||||||
environment:
|
environment:
|
||||||
- COVERAGE_FILE=/ci/.coverage
|
- COVERAGE_FILE=/tmp/.coverage
|
||||||
|
healthcheck:
|
||||||
|
disable: true
|
||||||
volumes:
|
volumes:
|
||||||
- ./reports:/ci/reports
|
- ./config:/etc/netbox/config:ro
|
||||||
|
- ./reports:/ci/reports:rw
|
||||||
|
- ../netbox_slm:/opt/netbox/netbox/netbox_slm:ro
|
||||||
netbox-worker:
|
netbox-worker:
|
||||||
image: netbox:slm
|
image: netbox:slm
|
||||||
|
depends_on:
|
||||||
|
- netbox
|
||||||
|
healthcheck:
|
||||||
|
disable: true
|
||||||
|
volumes:
|
||||||
|
- ./config:/etc/netbox/config:ro
|
||||||
|
- ../netbox_slm:/opt/netbox/netbox/netbox_slm:ro
|
||||||
netbox-housekeeping:
|
netbox-housekeeping:
|
||||||
image: netbox:slm
|
image: netbox:slm
|
||||||
|
depends_on:
|
||||||
|
- netbox
|
||||||
|
healthcheck:
|
||||||
|
disable: true
|
||||||
postgres:
|
postgres:
|
||||||
ports:
|
ports:
|
||||||
- "5432:5432"
|
- "5432:5432"
|
||||||
|
|||||||
+66
-28
@@ -1,63 +1,101 @@
|
|||||||
version: '3.7'
|
---
|
||||||
|
# based on https://github.com/netbox-community/netbox-docker/blob/3.2.0/docker-compose.yml
|
||||||
services:
|
services:
|
||||||
netbox: &netbox
|
netbox: &netbox
|
||||||
image: netboxcommunity/netbox:${VERSION-v3.7.2}
|
image: netboxcommunity/netbox:${VERSION-v4.2.3}
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
- redis
|
- redis
|
||||||
- redis-cache
|
- redis-cache
|
||||||
- netbox-worker
|
|
||||||
env_file: env/netbox.env
|
env_file: env/netbox.env
|
||||||
user: 'unit:root'
|
user: "unit:root"
|
||||||
|
healthcheck:
|
||||||
|
test: curl -f http://localhost:8080/login/ || exit 1
|
||||||
|
start_period: 90s
|
||||||
|
timeout: 3s
|
||||||
|
interval: 15s
|
||||||
volumes:
|
volumes:
|
||||||
- ./configuration:/etc/netbox/config:z,ro
|
- ./config:/etc/netbox/config:ro
|
||||||
- netbox-media-files:/opt/netbox/netbox/media:z
|
- netbox-media-files:/opt/netbox/netbox/media:rw
|
||||||
|
- netbox-reports-files:/opt/netbox/netbox/reports:rw
|
||||||
|
- netbox-scripts-files:/opt/netbox/netbox/scripts:rw
|
||||||
netbox-worker:
|
netbox-worker:
|
||||||
<<: *netbox
|
<<: *netbox
|
||||||
depends_on:
|
depends_on:
|
||||||
- redis
|
netbox:
|
||||||
- postgres
|
condition: service_healthy
|
||||||
command:
|
command:
|
||||||
- /opt/netbox/venv/bin/python
|
- /opt/netbox/venv/bin/python
|
||||||
- /opt/netbox/netbox/manage.py
|
- /opt/netbox/netbox/manage.py
|
||||||
- rqworker
|
- rqworker
|
||||||
|
healthcheck:
|
||||||
|
test: ps -aux | grep -v grep | grep -q rqworker || exit 1
|
||||||
|
start_period: 20s
|
||||||
|
timeout: 3s
|
||||||
|
interval: 15s
|
||||||
netbox-housekeeping:
|
netbox-housekeeping:
|
||||||
<<: *netbox
|
<<: *netbox
|
||||||
depends_on:
|
depends_on:
|
||||||
- redis
|
netbox:
|
||||||
- postgres
|
condition: service_healthy
|
||||||
command:
|
command:
|
||||||
- /opt/netbox/housekeeping.sh
|
- /opt/netbox/housekeeping.sh
|
||||||
|
healthcheck:
|
||||||
|
test: ps -aux | grep -v grep | grep -q housekeeping || exit 1
|
||||||
|
start_period: 20s
|
||||||
|
timeout: 3s
|
||||||
|
interval: 15s
|
||||||
|
|
||||||
# postgres
|
# postgres
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:14-alpine
|
image: postgres:17-alpine
|
||||||
|
healthcheck:
|
||||||
|
test: pg_isready -q -t 2 -d $$POSTGRES_DB -U $$POSTGRES_USER
|
||||||
|
start_period: 20s
|
||||||
|
timeout: 30s
|
||||||
|
interval: 10s
|
||||||
|
retries: 5
|
||||||
env_file: env/postgres.env
|
env_file: env/postgres.env
|
||||||
volumes:
|
volumes:
|
||||||
- netbox-postgres-data:/var/lib/postgresql/data
|
- netbox-postgres-data:/var/lib/postgresql/data
|
||||||
|
|
||||||
# redis
|
# redis
|
||||||
redis:
|
redis:
|
||||||
image: redis:7-alpine
|
image: valkey/valkey:8.0-alpine
|
||||||
command:
|
command:
|
||||||
- sh
|
- sh
|
||||||
- -c # this is to evaluate the $REDIS_PASSWORD from the env
|
- -c
|
||||||
- redis-server --appendonly yes --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose
|
- valkey-server --appendonly yes --requirepass $$REDIS_PASSWORD
|
||||||
|
healthcheck: &redis-healthcheck
|
||||||
|
test: '[ $$(valkey-cli --pass "$${REDIS_PASSWORD}" ping) = ''PONG'' ]'
|
||||||
|
start_period: 5s
|
||||||
|
timeout: 3s
|
||||||
|
interval: 1s
|
||||||
|
retries: 5
|
||||||
env_file: env/redis.env
|
env_file: env/redis.env
|
||||||
volumes:
|
volumes:
|
||||||
- netbox-redis-data:/data
|
- netbox-redis-data:/data
|
||||||
redis-cache:
|
redis-cache:
|
||||||
image: redis:7-alpine
|
image: valkey/valkey:8.0-alpine
|
||||||
command:
|
command:
|
||||||
- sh
|
- sh
|
||||||
- -c # this is to evaluate the $REDIS_PASSWORD from the env
|
- -c
|
||||||
- redis-server --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose
|
- valkey-server --requirepass $$REDIS_PASSWORD
|
||||||
|
healthcheck: *redis-healthcheck
|
||||||
env_file: env/redis-cache.env
|
env_file: env/redis-cache.env
|
||||||
|
volumes:
|
||||||
|
- netbox-redis-cache-data:/data
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
netbox-media-files:
|
netbox-media-files:
|
||||||
driver: local
|
driver: local
|
||||||
netbox-postgres-data:
|
netbox-postgres-data:
|
||||||
driver: local
|
driver: local
|
||||||
|
netbox-redis-cache-data:
|
||||||
|
driver: local
|
||||||
netbox-redis-data:
|
netbox-redis-data:
|
||||||
driver: local
|
driver: local
|
||||||
|
netbox-reports-files:
|
||||||
|
driver: local
|
||||||
|
netbox-scripts-files:
|
||||||
|
driver: local
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
coverage==7.2.2
|
|
||||||
unittest-xml-reporting==3.2.0
|
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
Anchore
|
||||||
|
APIs
|
||||||
|
Caddy
|
||||||
|
Checkmarx
|
||||||
|
Cobertura
|
||||||
|
Dependabot
|
||||||
|
Docker-composition
|
||||||
|
Dockerfile
|
||||||
|
DTDs
|
||||||
|
ESLint
|
||||||
|
Gravatar
|
||||||
|
Jira
|
||||||
|
JMeter
|
||||||
|
JUnit
|
||||||
|
NCover
|
||||||
|
Nginx
|
||||||
|
OJAudit
|
||||||
|
OpenShift
|
||||||
|
PDFs
|
||||||
|
Pydantic
|
||||||
|
Robocop
|
||||||
|
Snyk
|
||||||
|
Trello
|
||||||
|
Trivy
|
||||||
|
UUIDs
|
||||||
|
Wekan
|
||||||
|
[Hh]ostname
|
||||||
|
[Uu]nmerged
|
||||||
|
asyncio
|
||||||
|
autoformatting
|
||||||
|
breakpoint
|
||||||
|
clearable
|
||||||
|
cloc
|
||||||
|
discoverability
|
||||||
|
donut
|
||||||
|
errored
|
||||||
|
favicon
|
||||||
|
fixme
|
||||||
|
hostnames?
|
||||||
|
hotspots?
|
||||||
|
lookback
|
||||||
|
misconfigured
|
||||||
|
mypy
|
||||||
|
namespace
|
||||||
|
npm
|
||||||
|
parameterizable
|
||||||
|
phpldapadmin
|
||||||
|
[Pp]erformancetest
|
||||||
|
severities
|
||||||
|
sparkline
|
||||||
|
subfolders
|
||||||
|
submenus
|
||||||
|
suppressions
|
||||||
|
todo
|
||||||
|
tooltips?
|
||||||
|
tracebacks?
|
||||||
|
unencrypted
|
||||||
|
unicode
|
||||||
|
unmerged
|
||||||
|
upvotes
|
||||||
|
url
|
||||||
|
xml
|
||||||
|
NetBox
|
||||||
|
NetBox SLM
|
||||||
|
Mart
|
||||||
|
Visser
|
||||||
|
Suchow
|
||||||
|
Redistributions
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
docker composition
|
||||||
|
Docker composition
|
||||||
|
docker-composition
|
||||||
+25
-4
@@ -1,18 +1,39 @@
|
|||||||
from extras.plugins import PluginConfig
|
"""
|
||||||
|
Copyright 2022-2025 ICTU
|
||||||
|
|
||||||
__version__ = "1.6.0"
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from netbox.plugins import PluginConfig
|
||||||
|
|
||||||
|
__version__ = "1.8.1"
|
||||||
|
|
||||||
|
|
||||||
class SLMConfig(PluginConfig):
|
class SLMConfig(PluginConfig):
|
||||||
name = "netbox_slm"
|
name = "netbox_slm"
|
||||||
verbose_name = "Software Lifecycle Management"
|
verbose_name = "Software Lifecycle Management"
|
||||||
description = "Software Lifecycle Management Netbox Plugin."
|
|
||||||
version = __version__
|
version = __version__
|
||||||
|
description = "Software Lifecycle Management Netbox Plugin."
|
||||||
author = "ICTU"
|
author = "ICTU"
|
||||||
author_email = "open-source-projects@ictu.nl"
|
author_email = "open-source-projects@ictu.nl"
|
||||||
base_url = "slm"
|
base_url = "slm"
|
||||||
required_settings = []
|
required_settings = []
|
||||||
default_settings = {"version_info": False}
|
default_settings = {
|
||||||
|
"top_level_menu": True,
|
||||||
|
"link_cluster_installations": "right",
|
||||||
|
"link_device_installations": "right",
|
||||||
|
"link_virtualmachine_installations": "right",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
config = SLMConfig
|
config = SLMConfig
|
||||||
|
|||||||
@@ -1,22 +1,23 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework.serializers import SerializerMethodField, HyperlinkedIdentityField
|
||||||
|
|
||||||
from netbox.api.serializers import NetBoxModelSerializer
|
from netbox.api.serializers import NetBoxModelSerializer
|
||||||
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense
|
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense
|
||||||
|
|
||||||
|
|
||||||
class SoftwareLicenseSerializer(NetBoxModelSerializer):
|
class SoftwareLicenseSerializer(NetBoxModelSerializer):
|
||||||
display = serializers.SerializerMethodField()
|
display = SerializerMethodField()
|
||||||
url = serializers.HyperlinkedIdentityField(view_name="plugins-api:netbox_slm-api:softwarelicense-detail")
|
url = HyperlinkedIdentityField(view_name="plugins-api:netbox_slm-api:softwarelicense-detail")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SoftwareLicense
|
model = SoftwareLicense
|
||||||
fields = [
|
fields = (
|
||||||
"id",
|
"id",
|
||||||
"display",
|
"display",
|
||||||
"url",
|
"url",
|
||||||
"name",
|
"name",
|
||||||
"description",
|
"description",
|
||||||
"type",
|
"type",
|
||||||
|
"spdx_expression",
|
||||||
"stored_location",
|
"stored_location",
|
||||||
"stored_location_url",
|
"stored_location_url",
|
||||||
"start_date",
|
"start_date",
|
||||||
@@ -31,23 +32,25 @@ class SoftwareLicenseSerializer(NetBoxModelSerializer):
|
|||||||
"custom_field_data",
|
"custom_field_data",
|
||||||
"created",
|
"created",
|
||||||
"last_updated",
|
"last_updated",
|
||||||
]
|
)
|
||||||
|
brief_fields = ("id", "display", "url", "name", "description")
|
||||||
|
|
||||||
def get_display(self, obj):
|
def get_display(self, obj):
|
||||||
return f"{obj}"
|
return f"{obj}"
|
||||||
|
|
||||||
|
|
||||||
class SoftwareProductSerializer(NetBoxModelSerializer):
|
class SoftwareProductSerializer(NetBoxModelSerializer):
|
||||||
display = serializers.SerializerMethodField()
|
display = SerializerMethodField()
|
||||||
url = serializers.HyperlinkedIdentityField(view_name="plugins-api:netbox_slm-api:softwareproduct-detail")
|
url = HyperlinkedIdentityField(view_name="plugins-api:netbox_slm-api:softwareproduct-detail")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SoftwareProduct
|
model = SoftwareProduct
|
||||||
fields = [
|
fields = (
|
||||||
"id",
|
"id",
|
||||||
"display",
|
"display",
|
||||||
"url",
|
"url",
|
||||||
"name",
|
"name",
|
||||||
|
"description",
|
||||||
"manufacturer",
|
"manufacturer",
|
||||||
"description",
|
"description",
|
||||||
"tags",
|
"tags",
|
||||||
@@ -55,21 +58,20 @@ class SoftwareProductSerializer(NetBoxModelSerializer):
|
|||||||
"custom_field_data",
|
"custom_field_data",
|
||||||
"created",
|
"created",
|
||||||
"last_updated",
|
"last_updated",
|
||||||
]
|
)
|
||||||
|
brief_fields = ("id", "display", "url", "name", "description")
|
||||||
|
|
||||||
def get_display(self, obj):
|
def get_display(self, obj):
|
||||||
return f"{obj.manufacturer} - {obj}"
|
return f"{obj}"
|
||||||
|
|
||||||
|
|
||||||
class SoftwareProductInstallationSerializer(NetBoxModelSerializer):
|
class SoftwareProductInstallationSerializer(NetBoxModelSerializer):
|
||||||
display = serializers.SerializerMethodField()
|
display = SerializerMethodField()
|
||||||
url = serializers.HyperlinkedIdentityField(
|
url = HyperlinkedIdentityField(view_name="plugins-api:netbox_slm-api:softwareproductinstallation-detail")
|
||||||
view_name="plugins-api:netbox_slm-api:softwareproductinstallation-detail"
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SoftwareProductInstallation
|
model = SoftwareProductInstallation
|
||||||
fields = [
|
fields = (
|
||||||
"id",
|
"id",
|
||||||
"display",
|
"display",
|
||||||
"url",
|
"url",
|
||||||
@@ -83,23 +85,25 @@ class SoftwareProductInstallationSerializer(NetBoxModelSerializer):
|
|||||||
"custom_field_data",
|
"custom_field_data",
|
||||||
"created",
|
"created",
|
||||||
"last_updated",
|
"last_updated",
|
||||||
]
|
)
|
||||||
|
brief_fields = ("id", "display", "url")
|
||||||
|
|
||||||
def get_display(self, obj):
|
def get_display(self, obj):
|
||||||
return f"{obj}"
|
return f"{obj}"
|
||||||
|
|
||||||
|
|
||||||
class SoftwareProductVersionSerializer(NetBoxModelSerializer):
|
class SoftwareProductVersionSerializer(NetBoxModelSerializer):
|
||||||
display = serializers.SerializerMethodField()
|
display = SerializerMethodField()
|
||||||
url = serializers.HyperlinkedIdentityField(view_name="plugins-api:netbox_slm-api:softwareproductversion-detail")
|
url = HyperlinkedIdentityField(view_name="plugins-api:netbox_slm-api:softwareproductversion-detail")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SoftwareProductVersion
|
model = SoftwareProductVersion
|
||||||
fields = [
|
fields = (
|
||||||
"id",
|
"id",
|
||||||
"display",
|
"display",
|
||||||
"url",
|
"url",
|
||||||
"name",
|
"name",
|
||||||
|
"description",
|
||||||
"release_date",
|
"release_date",
|
||||||
"documentation_url",
|
"documentation_url",
|
||||||
"end_of_support",
|
"end_of_support",
|
||||||
@@ -113,7 +117,8 @@ class SoftwareProductVersionSerializer(NetBoxModelSerializer):
|
|||||||
"custom_field_data",
|
"custom_field_data",
|
||||||
"created",
|
"created",
|
||||||
"last_updated",
|
"last_updated",
|
||||||
]
|
)
|
||||||
|
brief_fields = ("id", "display", "url", "name", "description")
|
||||||
|
|
||||||
def get_display(self, obj):
|
def get_display(self, obj):
|
||||||
return f"{obj}"
|
return f"{obj}"
|
||||||
|
|||||||
@@ -14,4 +14,5 @@ router.register("softwareproducts", SoftwareProductViewSet)
|
|||||||
router.register("softwareproductversions", SoftwareProductVersionViewSet)
|
router.register("softwareproductversions", SoftwareProductVersionViewSet)
|
||||||
router.register("softwareproductinstallations", SoftwareProductInstallationViewSet)
|
router.register("softwareproductinstallations", SoftwareProductInstallationViewSet)
|
||||||
router.register("softwarelicenses", SoftwareLicenseViewSet)
|
router.register("softwarelicenses", SoftwareLicenseViewSet)
|
||||||
|
|
||||||
urlpatterns = router.urls
|
urlpatterns = router.urls
|
||||||
|
|||||||
@@ -13,14 +13,15 @@ from netbox_slm.filtersets import (
|
|||||||
SoftwareProductInstallationFilterSet,
|
SoftwareProductInstallationFilterSet,
|
||||||
SoftwareLicenseFilterSet,
|
SoftwareLicenseFilterSet,
|
||||||
)
|
)
|
||||||
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense
|
from netbox_slm.models import (
|
||||||
|
SoftwareProduct,
|
||||||
|
SoftwareProductVersion,
|
||||||
|
SoftwareProductInstallation,
|
||||||
|
SoftwareLicense,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class NetboxSLMRootView(APIRootView):
|
class NetboxSLMRootView(APIRootView):
|
||||||
"""
|
|
||||||
NetboxSLM API root view
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get_view_name(self):
|
def get_view_name(self):
|
||||||
return "NetboxSLM"
|
return "NetboxSLM"
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,26 @@
|
|||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from django_filters import CharFilter, ModelMultipleChoiceFilter, MultipleChoiceFilter
|
||||||
|
|
||||||
|
from dcim.models import Device, Manufacturer
|
||||||
from netbox.filtersets import NetBoxModelFilterSet
|
from netbox.filtersets import NetBoxModelFilterSet
|
||||||
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense
|
from netbox_slm.models import (
|
||||||
|
SoftwareProduct,
|
||||||
|
SoftwareProductVersion,
|
||||||
|
SoftwareProductInstallation,
|
||||||
|
SoftwareLicense,
|
||||||
|
SoftwareReleaseTypes,
|
||||||
|
)
|
||||||
|
from virtualization.models import Cluster, VirtualMachine
|
||||||
|
|
||||||
|
|
||||||
class SoftwareProductFilterSet(NetBoxModelFilterSet):
|
class SoftwareProductFilterSet(NetBoxModelFilterSet):
|
||||||
"""Filter capabilities for SoftwareProduct instances."""
|
"""Filter capabilities for SoftwareProduct instances."""
|
||||||
|
|
||||||
|
name = CharFilter(lookup_expr="icontains")
|
||||||
|
description = CharFilter(lookup_expr="icontains")
|
||||||
|
|
||||||
|
manufacturer_id = ModelMultipleChoiceFilter(queryset=Manufacturer.objects.all())
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SoftwareProduct
|
model = SoftwareProduct
|
||||||
fields = tuple()
|
fields = tuple()
|
||||||
@@ -27,9 +41,25 @@ class SoftwareProductFilterSet(NetBoxModelFilterSet):
|
|||||||
class SoftwareProductVersionFilterSet(NetBoxModelFilterSet):
|
class SoftwareProductVersionFilterSet(NetBoxModelFilterSet):
|
||||||
"""Filter capabilities for SoftwareProductVersion instances."""
|
"""Filter capabilities for SoftwareProductVersion instances."""
|
||||||
|
|
||||||
|
name = CharFilter(lookup_expr="icontains")
|
||||||
|
description = CharFilter(lookup_expr="icontains")
|
||||||
|
filename = CharFilter(lookup_expr="icontains")
|
||||||
|
|
||||||
|
release_type = MultipleChoiceFilter(choices=SoftwareReleaseTypes.choices)
|
||||||
|
|
||||||
|
manufacturer_id = ModelMultipleChoiceFilter(
|
||||||
|
field_name="software_product__manufacturer",
|
||||||
|
queryset=Manufacturer.objects.all(),
|
||||||
|
)
|
||||||
|
|
||||||
|
software_product_id = ModelMultipleChoiceFilter(
|
||||||
|
queryset=SoftwareProduct.objects.all(),
|
||||||
|
label="Software Product",
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SoftwareProductVersion
|
model = SoftwareProductVersion
|
||||||
fields = ("software_product",)
|
fields = tuple()
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
"""Perform the filtered search."""
|
"""Perform the filtered search."""
|
||||||
@@ -37,6 +67,7 @@ class SoftwareProductVersionFilterSet(NetBoxModelFilterSet):
|
|||||||
return queryset
|
return queryset
|
||||||
qs_filter = (
|
qs_filter = (
|
||||||
Q(name__icontains=value)
|
Q(name__icontains=value)
|
||||||
|
| Q(description__icontains=value)
|
||||||
| Q(software_product__name__icontains=value)
|
| Q(software_product__name__icontains=value)
|
||||||
| Q(software_product__manufacturer__name__icontains=value)
|
| Q(software_product__manufacturer__name__icontains=value)
|
||||||
| Q(comments__icontains=value)
|
| Q(comments__icontains=value)
|
||||||
@@ -47,9 +78,21 @@ class SoftwareProductVersionFilterSet(NetBoxModelFilterSet):
|
|||||||
class SoftwareProductInstallationFilterSet(NetBoxModelFilterSet):
|
class SoftwareProductInstallationFilterSet(NetBoxModelFilterSet):
|
||||||
"""Filter capabilities for SoftwareProductInstallation instances."""
|
"""Filter capabilities for SoftwareProductInstallation instances."""
|
||||||
|
|
||||||
|
device_id = ModelMultipleChoiceFilter(queryset=Device.objects.all())
|
||||||
|
virtualmachine_id = ModelMultipleChoiceFilter(
|
||||||
|
queryset=VirtualMachine.objects.all(),
|
||||||
|
label="Virtual Machine",
|
||||||
|
)
|
||||||
|
cluster_id = ModelMultipleChoiceFilter(queryset=Cluster.objects.all())
|
||||||
|
software_product_id = ModelMultipleChoiceFilter(
|
||||||
|
queryset=SoftwareProduct.objects.all(),
|
||||||
|
label="Software Product",
|
||||||
|
)
|
||||||
|
version_id = ModelMultipleChoiceFilter(queryset=SoftwareProductVersion.objects.all())
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SoftwareProductInstallation
|
model = SoftwareProductInstallation
|
||||||
fields = ("software_product",)
|
fields = tuple()
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
"""Perform the filtered search."""
|
"""Perform the filtered search."""
|
||||||
@@ -67,9 +110,22 @@ class SoftwareProductInstallationFilterSet(NetBoxModelFilterSet):
|
|||||||
class SoftwareLicenseFilterSet(NetBoxModelFilterSet):
|
class SoftwareLicenseFilterSet(NetBoxModelFilterSet):
|
||||||
"""Filter capabilities for SoftwareLicense instances."""
|
"""Filter capabilities for SoftwareLicense instances."""
|
||||||
|
|
||||||
|
name = CharFilter(lookup_expr="icontains")
|
||||||
|
description = CharFilter(lookup_expr="icontains")
|
||||||
|
type = CharFilter(lookup_expr="icontains")
|
||||||
|
spdx_expression = CharFilter(lookup_expr="icontains", label="SPDX expression")
|
||||||
|
stored_location = CharFilter(lookup_expr="icontains")
|
||||||
|
|
||||||
|
software_product_id = ModelMultipleChoiceFilter(
|
||||||
|
queryset=SoftwareProduct.objects.all(),
|
||||||
|
label="Software Product",
|
||||||
|
)
|
||||||
|
version_id = ModelMultipleChoiceFilter(queryset=SoftwareProductVersion.objects.all())
|
||||||
|
installation_id = ModelMultipleChoiceFilter(queryset=SoftwareProductInstallation.objects.all())
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SoftwareLicense
|
model = SoftwareLicense
|
||||||
fields = tuple()
|
fields = ("support",)
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
"""Perform the filtered search."""
|
"""Perform the filtered search."""
|
||||||
@@ -77,6 +133,7 @@ class SoftwareLicenseFilterSet(NetBoxModelFilterSet):
|
|||||||
return queryset
|
return queryset
|
||||||
qs_filter = (
|
qs_filter = (
|
||||||
Q(name__icontains=value)
|
Q(name__icontains=value)
|
||||||
|
| Q(description__icontains=value)
|
||||||
| Q(software_product__name__icontains=value)
|
| Q(software_product__name__icontains=value)
|
||||||
| Q(version__name__icontains=value)
|
| Q(version__name__icontains=value)
|
||||||
| Q(installation__device__name__icontains=value)
|
| Q(installation__device__name__icontains=value)
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
from .software_license import (
|
from .software_license import (
|
||||||
SoftwareLicenseForm,
|
SoftwareLicenseForm,
|
||||||
SoftwareLicenseFilterForm,
|
SoftwareLicenseFilterForm,
|
||||||
SoftwareLicenseImportForm,
|
SoftwareLicenseBulkImportForm,
|
||||||
SoftwareLicenseBulkEditForm,
|
SoftwareLicenseBulkEditForm,
|
||||||
)
|
)
|
||||||
from .software_product import (
|
from .software_product import (
|
||||||
SoftwareProductForm,
|
SoftwareProductForm,
|
||||||
SoftwareProductFilterForm,
|
SoftwareProductFilterForm,
|
||||||
SoftwareProductImportForm,
|
SoftwareProductBulkImportForm,
|
||||||
SoftwareProductBulkEditForm,
|
SoftwareProductBulkEditForm,
|
||||||
)
|
)
|
||||||
from .software_product_installation import (
|
from .software_product_installation import (
|
||||||
SoftwareProductInstallationForm,
|
SoftwareProductInstallationForm,
|
||||||
SoftwareProductInstallationFilterForm,
|
SoftwareProductInstallationFilterForm,
|
||||||
SoftwareProductInstallationImportForm,
|
SoftwareProductInstallationBulkImportForm,
|
||||||
SoftwareProductInstallationBulkEditForm,
|
SoftwareProductInstallationBulkEditForm,
|
||||||
)
|
)
|
||||||
from .software_product_version import (
|
from .software_product_version import (
|
||||||
SoftwareProductVersionForm,
|
SoftwareProductVersionForm,
|
||||||
SoftwareProductVersionFilterForm,
|
SoftwareProductVersionFilterForm,
|
||||||
SoftwareProductVersionImportForm,
|
SoftwareProductVersionBulkImportForm,
|
||||||
SoftwareProductVersionBulkEditForm,
|
SoftwareProductVersionBulkEditForm,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,31 +1,44 @@
|
|||||||
from django.forms import DateField
|
from django.forms import CharField, DateField, ChoiceField, IntegerField, NullBooleanField
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
|
|
||||||
from netbox.forms import NetBoxModelForm, NetBoxModelImportForm, NetBoxModelBulkEditForm, NetBoxModelFilterSetForm
|
from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm, NetBoxModelImportForm, NetBoxModelBulkEditForm
|
||||||
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense
|
from netbox_slm.models import (
|
||||||
from utilities.forms.fields import CommentField, DynamicModelChoiceField, TagFilterField, LaxURLField
|
SoftwareProduct,
|
||||||
|
SoftwareProductVersion,
|
||||||
|
SoftwareProductInstallation,
|
||||||
|
SoftwareLicense,
|
||||||
|
spdx_license_names,
|
||||||
|
)
|
||||||
|
from utilities.forms.constants import BOOLEAN_WITH_BLANK_CHOICES
|
||||||
|
from utilities.forms.fields import (
|
||||||
|
CommentField,
|
||||||
|
DynamicModelChoiceField,
|
||||||
|
TagFilterField,
|
||||||
|
LaxURLField,
|
||||||
|
DynamicModelMultipleChoiceField,
|
||||||
|
)
|
||||||
|
from utilities.forms.rendering import FieldSet
|
||||||
from utilities.forms.widgets import APISelect, DatePicker
|
from utilities.forms.widgets import APISelect, DatePicker
|
||||||
|
|
||||||
|
|
||||||
class SoftwareLicenseForm(NetBoxModelForm):
|
class SoftwareLicenseForm(NetBoxModelForm):
|
||||||
"""Form for creating a new SoftwareLicense object."""
|
|
||||||
|
|
||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
|
spdx_expression = ChoiceField(required=False, choices=spdx_license_names(), label="SPDX expression")
|
||||||
stored_location_url = LaxURLField(required=False)
|
stored_location_url = LaxURLField(required=False)
|
||||||
|
start_date = DateField(required=False, widget=DatePicker())
|
||||||
|
expiration_date = DateField(required=False, widget=DatePicker())
|
||||||
|
|
||||||
software_product = DynamicModelChoiceField(
|
software_product = DynamicModelChoiceField(
|
||||||
queryset=SoftwareProduct.objects.all(),
|
queryset=SoftwareProduct.objects.all(),
|
||||||
required=True,
|
required=True,
|
||||||
widget=APISelect(attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproduct-list")}),
|
label="Software Product",
|
||||||
)
|
)
|
||||||
version = DynamicModelChoiceField(
|
version = DynamicModelChoiceField(
|
||||||
queryset=SoftwareProductVersion.objects.all(),
|
queryset=SoftwareProductVersion.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
widget=APISelect(attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproductversion-list")}),
|
widget=APISelect(attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproductversion-list")}),
|
||||||
query_params={
|
query_params=dict(software_product="$software_product"),
|
||||||
"software_product": "$software_product",
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
installation = DynamicModelChoiceField(
|
installation = DynamicModelChoiceField(
|
||||||
queryset=SoftwareProductInstallation.objects.all(),
|
queryset=SoftwareProductInstallation.objects.all(),
|
||||||
@@ -33,26 +46,23 @@ class SoftwareLicenseForm(NetBoxModelForm):
|
|||||||
widget=APISelect(
|
widget=APISelect(
|
||||||
attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproductinstallation-list")}
|
attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproductinstallation-list")}
|
||||||
),
|
),
|
||||||
query_params={
|
query_params=dict(software_product="$software_product"),
|
||||||
"software_product": "$software_product",
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
start_date = DateField(required=False, widget=DatePicker())
|
|
||||||
expiration_date = DateField(required=False, widget=DatePicker())
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SoftwareLicense
|
model = SoftwareLicense
|
||||||
fields = (
|
fields = (
|
||||||
"name",
|
"name",
|
||||||
"description",
|
"description",
|
||||||
|
"software_product",
|
||||||
"type",
|
"type",
|
||||||
|
"spdx_expression",
|
||||||
"stored_location",
|
"stored_location",
|
||||||
"stored_location_url",
|
"stored_location_url",
|
||||||
"start_date",
|
"start_date",
|
||||||
"expiration_date",
|
"expiration_date",
|
||||||
"support",
|
"support",
|
||||||
"license_amount",
|
"license_amount",
|
||||||
"software_product",
|
|
||||||
"version",
|
"version",
|
||||||
"installation",
|
"installation",
|
||||||
"tags",
|
"tags",
|
||||||
@@ -62,36 +72,128 @@ class SoftwareLicenseForm(NetBoxModelForm):
|
|||||||
|
|
||||||
class SoftwareLicenseFilterForm(NetBoxModelFilterSetForm):
|
class SoftwareLicenseFilterForm(NetBoxModelFilterSetForm):
|
||||||
model = SoftwareLicense
|
model = SoftwareLicense
|
||||||
fieldsets = ((None, ("q", "tag")),)
|
fieldsets = (
|
||||||
|
FieldSet("q", "filter_id", "tag"),
|
||||||
|
FieldSet(
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
"type",
|
||||||
|
"spdx_expression",
|
||||||
|
"stored_location",
|
||||||
|
"support",
|
||||||
|
"software_product_id",
|
||||||
|
"version_id",
|
||||||
|
"installation_id",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
selector_fields = ("q", "filter_id", "name")
|
||||||
|
|
||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
|
name = CharField(required=False)
|
||||||
|
description = CharField(required=False)
|
||||||
|
type = CharField(required=False)
|
||||||
|
spdx_expression = CharField(required=False, label="SPDX expression")
|
||||||
|
stored_location = CharField(required=False)
|
||||||
|
support = ChoiceField(required=False, choices=BOOLEAN_WITH_BLANK_CHOICES)
|
||||||
|
|
||||||
|
software_product_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=SoftwareProduct.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label="Software Product",
|
||||||
|
)
|
||||||
|
version_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=SoftwareProductVersion.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label="Version",
|
||||||
|
)
|
||||||
|
installation_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=SoftwareProductInstallation.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label="Installation",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareLicenseBulkImportForm(NetBoxModelImportForm):
|
||||||
|
support = NullBooleanField(required=False, help_text="Support (values other than 'True' and 'False' are ignored)")
|
||||||
|
|
||||||
class SoftwareLicenseImportForm(NetBoxModelImportForm):
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SoftwareLicense
|
model = SoftwareLicense
|
||||||
fields = (
|
fields = (
|
||||||
"name",
|
"name",
|
||||||
"description",
|
"description",
|
||||||
|
"software_product",
|
||||||
"type",
|
"type",
|
||||||
|
"spdx_expression",
|
||||||
"stored_location",
|
"stored_location",
|
||||||
"stored_location_url",
|
|
||||||
"start_date",
|
"start_date",
|
||||||
"expiration_date",
|
"expiration_date",
|
||||||
|
"support",
|
||||||
|
"license_amount",
|
||||||
|
"version",
|
||||||
|
"installation",
|
||||||
|
"tags",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class SoftwareLicenseBulkEditForm(NetBoxModelBulkEditForm):
|
class SoftwareLicenseBulkEditForm(NetBoxModelBulkEditForm):
|
||||||
software_product = DynamicModelChoiceField(queryset=SoftwareProduct.objects.all(), required=False)
|
|
||||||
version = DynamicModelChoiceField(queryset=SoftwareProductVersion.objects.all(), required=False)
|
|
||||||
installation = DynamicModelChoiceField(queryset=SoftwareProductInstallation.objects.all(), required=False)
|
|
||||||
model = SoftwareLicense
|
model = SoftwareLicense
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(
|
FieldSet(
|
||||||
None,
|
"type",
|
||||||
(
|
"spdx_expression",
|
||||||
"software_product",
|
"stored_location",
|
||||||
"version",
|
"stored_location_url",
|
||||||
"installation",
|
"start_date",
|
||||||
),
|
"expiration_date",
|
||||||
|
"support",
|
||||||
|
"license_amount",
|
||||||
|
"software_product",
|
||||||
|
"version",
|
||||||
|
"installation",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
nullable_fields = (
|
||||||
|
"type",
|
||||||
|
"spdx_expression",
|
||||||
|
"stored_location",
|
||||||
|
"stored_location_url",
|
||||||
|
"start_date",
|
||||||
|
"expiration_date",
|
||||||
|
"support",
|
||||||
|
"license_amount",
|
||||||
|
"version",
|
||||||
|
"installation",
|
||||||
|
)
|
||||||
|
|
||||||
|
tag = TagFilterField(model)
|
||||||
|
comments = CommentField()
|
||||||
|
|
||||||
|
type = CharField(required=False)
|
||||||
|
spdx_expression = ChoiceField(required=False, choices=spdx_license_names(), label="SPDX expression")
|
||||||
|
stored_location = CharField(required=False)
|
||||||
|
stored_location_url = LaxURLField(required=False)
|
||||||
|
start_date = DateField(required=False, widget=DatePicker())
|
||||||
|
expiration_date = DateField(required=False, widget=DatePicker())
|
||||||
|
support = ChoiceField(required=False, choices=BOOLEAN_WITH_BLANK_CHOICES)
|
||||||
|
license_amount = IntegerField(required=False, min_value=0)
|
||||||
|
|
||||||
|
software_product = DynamicModelChoiceField(
|
||||||
|
queryset=SoftwareProduct.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label="Software Product",
|
||||||
|
)
|
||||||
|
version = DynamicModelChoiceField(
|
||||||
|
queryset=SoftwareProductVersion.objects.all(),
|
||||||
|
required=False,
|
||||||
|
widget=APISelect(attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproductversion-list")}),
|
||||||
|
query_params=dict(software_product="$software_product"),
|
||||||
|
)
|
||||||
|
installation = DynamicModelChoiceField(
|
||||||
|
queryset=SoftwareProductInstallation.objects.all(),
|
||||||
|
required=False,
|
||||||
|
widget=APISelect(
|
||||||
|
attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproductinstallation-list")}
|
||||||
|
),
|
||||||
|
query_params=dict(software_product="$software_product"),
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
from django import forms
|
from django.forms import CharField
|
||||||
|
|
||||||
from dcim.models import Manufacturer
|
from dcim.models import Manufacturer
|
||||||
from netbox.forms import NetBoxModelForm, NetBoxModelImportForm, NetBoxModelBulkEditForm, NetBoxModelFilterSetForm
|
from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm, NetBoxModelImportForm, NetBoxModelBulkEditForm
|
||||||
from netbox_slm.models import SoftwareProduct
|
from netbox_slm.models import SoftwareProduct
|
||||||
from utilities.forms.fields import CommentField, DynamicModelChoiceField, TagFilterField
|
from utilities.forms.fields import (
|
||||||
|
CommentField,
|
||||||
|
DynamicModelChoiceField,
|
||||||
|
TagFilterField,
|
||||||
|
DynamicModelMultipleChoiceField,
|
||||||
|
)
|
||||||
|
from utilities.forms.rendering import FieldSet
|
||||||
|
|
||||||
|
|
||||||
class SoftwareProductForm(NetBoxModelForm):
|
class SoftwareProductForm(NetBoxModelForm):
|
||||||
"""Form for creating a new SoftwareProduct object."""
|
|
||||||
|
|
||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
manufacturer = DynamicModelChoiceField(
|
manufacturer = DynamicModelChoiceField(
|
||||||
@@ -29,26 +33,43 @@ class SoftwareProductForm(NetBoxModelForm):
|
|||||||
|
|
||||||
class SoftwareProductFilterForm(NetBoxModelFilterSetForm):
|
class SoftwareProductFilterForm(NetBoxModelFilterSetForm):
|
||||||
model = SoftwareProduct
|
model = SoftwareProduct
|
||||||
fieldsets = ((None, ("q", "tag")),)
|
fieldsets = (
|
||||||
|
FieldSet("q", "filter_id", "tag"),
|
||||||
|
FieldSet("name", "description", "manufacturer_id"),
|
||||||
|
)
|
||||||
|
selector_fields = ("q", "filter_id", "name")
|
||||||
|
|
||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
|
name = CharField(required=False)
|
||||||
|
description = CharField(required=False)
|
||||||
|
manufacturer_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=Manufacturer.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label="Manufacturer",
|
||||||
|
)
|
||||||
|
|
||||||
class SoftwareProductImportForm(NetBoxModelImportForm):
|
|
||||||
|
class SoftwareProductBulkImportForm(NetBoxModelImportForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SoftwareProduct
|
model = SoftwareProduct
|
||||||
fields = (
|
fields = (
|
||||||
"name",
|
"name",
|
||||||
"description",
|
"description",
|
||||||
"manufacturer",
|
"manufacturer",
|
||||||
|
"tags",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class SoftwareProductBulkEditForm(NetBoxModelBulkEditForm):
|
class SoftwareProductBulkEditForm(NetBoxModelBulkEditForm):
|
||||||
pk = forms.ModelMultipleChoiceField(
|
model = SoftwareProduct
|
||||||
queryset=SoftwareProduct.objects.all(),
|
fieldsets = (FieldSet("manufacturer"),)
|
||||||
widget=forms.MultipleHiddenInput(),
|
nullable_fields = ("manufacturer", "comments")
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
tag = TagFilterField(model)
|
||||||
model = SoftwareProduct
|
comments = CommentField()
|
||||||
nullable_fields = []
|
|
||||||
|
manufacturer = DynamicModelChoiceField(
|
||||||
|
queryset=Manufacturer.objects.all(),
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,34 +1,40 @@
|
|||||||
from django import forms
|
from django.forms import ValidationError
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
|
|
||||||
from dcim.models import Device
|
from dcim.models import Device
|
||||||
from netbox.forms import NetBoxModelForm, NetBoxModelImportForm, NetBoxModelBulkEditForm, NetBoxModelFilterSetForm
|
from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm, NetBoxModelImportForm, NetBoxModelBulkEditForm
|
||||||
from netbox_slm.models import SoftwareProductInstallation, SoftwareProduct, SoftwareProductVersion
|
from netbox_slm.models import SoftwareProductInstallation, SoftwareProduct, SoftwareProductVersion
|
||||||
from utilities.forms.fields import CommentField, DynamicModelChoiceField, TagFilterField
|
from utilities.forms.fields import (
|
||||||
|
CommentField,
|
||||||
|
DynamicModelChoiceField,
|
||||||
|
TagFilterField,
|
||||||
|
DynamicModelMultipleChoiceField,
|
||||||
|
)
|
||||||
|
from utilities.forms.rendering import FieldSet
|
||||||
from utilities.forms.widgets import APISelect
|
from utilities.forms.widgets import APISelect
|
||||||
from virtualization.models import VirtualMachine, Cluster
|
from virtualization.models import Cluster, VirtualMachine
|
||||||
|
|
||||||
|
|
||||||
class SoftwareProductInstallationForm(NetBoxModelForm):
|
class SoftwareProductInstallationForm(NetBoxModelForm):
|
||||||
"""Form for creating a new SoftwareProductInstallation object."""
|
|
||||||
|
|
||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
device = DynamicModelChoiceField(queryset=Device.objects.all(), required=False)
|
device = DynamicModelChoiceField(queryset=Device.objects.all(), required=False)
|
||||||
virtualmachine = DynamicModelChoiceField(queryset=VirtualMachine.objects.all(), required=False)
|
virtualmachine = DynamicModelChoiceField(
|
||||||
|
queryset=VirtualMachine.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label="Virtual Machine",
|
||||||
|
)
|
||||||
cluster = DynamicModelChoiceField(queryset=Cluster.objects.all(), required=False)
|
cluster = DynamicModelChoiceField(queryset=Cluster.objects.all(), required=False)
|
||||||
software_product = DynamicModelChoiceField(
|
software_product = DynamicModelChoiceField(
|
||||||
queryset=SoftwareProduct.objects.all(),
|
queryset=SoftwareProduct.objects.all(),
|
||||||
required=True,
|
required=True,
|
||||||
widget=APISelect(attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproduct-list")}),
|
label="Software Product",
|
||||||
)
|
)
|
||||||
version = DynamicModelChoiceField(
|
version = DynamicModelChoiceField(
|
||||||
queryset=SoftwareProductVersion.objects.all(),
|
queryset=SoftwareProductVersion.objects.all(),
|
||||||
required=True,
|
required=True,
|
||||||
widget=APISelect(attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproductversion-list")}),
|
widget=APISelect(attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproductversion-list")}),
|
||||||
query_params={
|
query_params=dict(software_product="$software_product"),
|
||||||
"software_product": "$software_product",
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -47,7 +53,7 @@ class SoftwareProductInstallationForm(NetBoxModelForm):
|
|||||||
version = self.cleaned_data["version"]
|
version = self.cleaned_data["version"]
|
||||||
software_product = self.cleaned_data["software_product"]
|
software_product = self.cleaned_data["software_product"]
|
||||||
if version not in software_product.softwareproductversion_set.all():
|
if version not in software_product.softwareproductversion_set.all():
|
||||||
raise forms.ValidationError(
|
raise ValidationError(
|
||||||
f"Version '{version}' doesn't exist on {software_product}, make sure you've "
|
f"Version '{version}' doesn't exist on {software_product}, make sure you've "
|
||||||
f"selected a compatible version or first select the software product."
|
f"selected a compatible version or first select the software product."
|
||||||
)
|
)
|
||||||
@@ -56,26 +62,77 @@ class SoftwareProductInstallationForm(NetBoxModelForm):
|
|||||||
|
|
||||||
class SoftwareProductInstallationFilterForm(NetBoxModelFilterSetForm):
|
class SoftwareProductInstallationFilterForm(NetBoxModelFilterSetForm):
|
||||||
model = SoftwareProductInstallation
|
model = SoftwareProductInstallation
|
||||||
fieldsets = ((None, ("q", "tag")),)
|
fieldsets = (
|
||||||
|
FieldSet("q", "filter_id", "tag"),
|
||||||
|
FieldSet("device_id", "virtualmachine_id", "cluster_id", "software_product_id", "version_id"),
|
||||||
|
)
|
||||||
|
selector_fields = ("filter_id", "q")
|
||||||
|
|
||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
|
device_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=Device.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label="Device",
|
||||||
|
)
|
||||||
|
virtualmachine_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=VirtualMachine.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label="Virtual Machine",
|
||||||
|
)
|
||||||
|
cluster_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=Cluster.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label="Cluster",
|
||||||
|
)
|
||||||
|
software_product_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=SoftwareProduct.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label="Software Product",
|
||||||
|
)
|
||||||
|
version_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=SoftwareProductVersion.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label="Version",
|
||||||
|
)
|
||||||
|
|
||||||
class SoftwareProductInstallationImportForm(NetBoxModelImportForm):
|
|
||||||
|
class SoftwareProductInstallationBulkImportForm(NetBoxModelImportForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SoftwareProductInstallation
|
model = SoftwareProductInstallation
|
||||||
fields = tuple()
|
fields = (
|
||||||
|
"device",
|
||||||
|
"virtualmachine",
|
||||||
|
"cluster",
|
||||||
|
"software_product",
|
||||||
|
"version",
|
||||||
|
"tags",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SoftwareProductInstallationBulkEditForm(NetBoxModelBulkEditForm):
|
class SoftwareProductInstallationBulkEditForm(NetBoxModelBulkEditForm):
|
||||||
software_product = DynamicModelChoiceField(queryset=SoftwareProduct.objects.all(), required=False)
|
|
||||||
version = DynamicModelChoiceField(queryset=SoftwareProductVersion.objects.all(), required=False)
|
|
||||||
model = SoftwareProductInstallation
|
model = SoftwareProductInstallation
|
||||||
fieldsets = (
|
fieldsets = (FieldSet("device", "virtualmachine", "cluster", "software_product", "version"),)
|
||||||
(
|
nullable_fields = ("device", "virtualmachine", "cluster", "comments")
|
||||||
None,
|
|
||||||
(
|
tag = TagFilterField(model)
|
||||||
"software_product",
|
comments = CommentField()
|
||||||
"version",
|
|
||||||
),
|
device = DynamicModelChoiceField(queryset=Device.objects.all(), required=False)
|
||||||
),
|
virtualmachine = DynamicModelChoiceField(
|
||||||
|
queryset=VirtualMachine.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label="Virtual Machine",
|
||||||
|
)
|
||||||
|
cluster = DynamicModelChoiceField(queryset=Cluster.objects.all(), required=False)
|
||||||
|
software_product = DynamicModelChoiceField(
|
||||||
|
queryset=SoftwareProduct.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label="Software Product",
|
||||||
|
)
|
||||||
|
version = DynamicModelChoiceField(
|
||||||
|
queryset=SoftwareProductVersion.objects.all(),
|
||||||
|
required=False,
|
||||||
|
widget=APISelect(attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproductversion-list")}),
|
||||||
|
query_params=dict(software_product="$software_product"),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,22 +1,29 @@
|
|||||||
from django import forms
|
from django.forms import DateField, CharField, MultipleChoiceField
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
|
|
||||||
from netbox.forms import NetBoxModelForm, NetBoxModelImportForm, NetBoxModelBulkEditForm, NetBoxModelFilterSetForm
|
from dcim.models import Manufacturer
|
||||||
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion
|
from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm, NetBoxModelImportForm, NetBoxModelBulkEditForm
|
||||||
from utilities.forms.fields import CommentField, DynamicModelChoiceField, TagFilterField
|
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareReleaseTypes
|
||||||
|
from utilities.forms.fields import (
|
||||||
|
CommentField,
|
||||||
|
DynamicModelChoiceField,
|
||||||
|
TagFilterField,
|
||||||
|
DynamicModelMultipleChoiceField,
|
||||||
|
)
|
||||||
|
from utilities.forms.rendering import FieldSet
|
||||||
from utilities.forms.widgets import APISelect, DatePicker
|
from utilities.forms.widgets import APISelect, DatePicker
|
||||||
|
|
||||||
|
|
||||||
class SoftwareProductVersionForm(NetBoxModelForm):
|
class SoftwareProductVersionForm(NetBoxModelForm):
|
||||||
"""Form for creating a new SoftwareProductVersion object."""
|
|
||||||
|
|
||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
release_date = forms.DateField(required=False, widget=DatePicker())
|
release_date = DateField(required=False, widget=DatePicker())
|
||||||
end_of_support = forms.DateField(required=False, widget=DatePicker())
|
end_of_support = DateField(required=False, widget=DatePicker())
|
||||||
|
|
||||||
software_product = DynamicModelChoiceField(
|
software_product = DynamicModelChoiceField(
|
||||||
queryset=SoftwareProduct.objects.all(),
|
queryset=SoftwareProduct.objects.all(),
|
||||||
|
required=True,
|
||||||
|
label="Software Product",
|
||||||
widget=APISelect(attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproduct-list")}),
|
widget=APISelect(attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproduct-list")}),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,6 +31,70 @@ class SoftwareProductVersionForm(NetBoxModelForm):
|
|||||||
model = SoftwareProductVersion
|
model = SoftwareProductVersion
|
||||||
fields = (
|
fields = (
|
||||||
"name",
|
"name",
|
||||||
|
"description",
|
||||||
|
"software_product",
|
||||||
|
"release_date",
|
||||||
|
"documentation_url",
|
||||||
|
"end_of_support",
|
||||||
|
"filename",
|
||||||
|
"file_checksum",
|
||||||
|
"file_link",
|
||||||
|
"release_type",
|
||||||
|
"tags",
|
||||||
|
"comments",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductVersionFilterForm(NetBoxModelFilterSetForm):
|
||||||
|
model = SoftwareProductVersion
|
||||||
|
fieldsets = (
|
||||||
|
FieldSet("q", "filter_id", "tag"),
|
||||||
|
FieldSet("name", "description", "filename", "release_type", "manufacturer_id", "software_product_id"),
|
||||||
|
)
|
||||||
|
selector_fields = ("q", "filter_id", "name")
|
||||||
|
|
||||||
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
|
name = CharField(required=False)
|
||||||
|
description = CharField(required=False)
|
||||||
|
filename = CharField(required=False)
|
||||||
|
|
||||||
|
release_type = MultipleChoiceField(required=False, choices=SoftwareReleaseTypes.choices)
|
||||||
|
|
||||||
|
manufacturer_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=Manufacturer.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label="Manufacturer",
|
||||||
|
)
|
||||||
|
software_product_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=SoftwareProduct.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label="Software Product",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductVersionBulkImportForm(NetBoxModelImportForm):
|
||||||
|
release_type = CharField(help_text=f"Release type (possible values: {SoftwareReleaseTypes.values})")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = SoftwareProductVersion
|
||||||
|
fields = (
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
"software_product",
|
||||||
|
"release_date",
|
||||||
|
"end_of_support",
|
||||||
|
"filename",
|
||||||
|
"file_checksum",
|
||||||
|
"release_type",
|
||||||
|
"tags",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductVersionBulkEditForm(NetBoxModelBulkEditForm):
|
||||||
|
model = SoftwareProductVersion
|
||||||
|
fieldsets = (
|
||||||
|
FieldSet(
|
||||||
"release_date",
|
"release_date",
|
||||||
"documentation_url",
|
"documentation_url",
|
||||||
"end_of_support",
|
"end_of_support",
|
||||||
@@ -32,29 +103,24 @@ class SoftwareProductVersionForm(NetBoxModelForm):
|
|||||||
"file_link",
|
"file_link",
|
||||||
"release_type",
|
"release_type",
|
||||||
"software_product",
|
"software_product",
|
||||||
"tags",
|
),
|
||||||
"comments",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SoftwareProductVersionFilterForm(NetBoxModelFilterSetForm):
|
|
||||||
model = SoftwareProductVersion
|
|
||||||
fieldsets = ((None, ("q", "tag")),)
|
|
||||||
tag = TagFilterField(model)
|
|
||||||
|
|
||||||
|
|
||||||
class SoftwareProductVersionImportForm(NetBoxModelImportForm):
|
|
||||||
class Meta:
|
|
||||||
model = SoftwareProductVersion
|
|
||||||
fields = ("name",)
|
|
||||||
|
|
||||||
|
|
||||||
class SoftwareProductVersionBulkEditForm(NetBoxModelBulkEditForm):
|
|
||||||
pk = forms.ModelMultipleChoiceField(
|
|
||||||
queryset=SoftwareProduct.objects.all(),
|
|
||||||
widget=forms.MultipleHiddenInput(),
|
|
||||||
)
|
)
|
||||||
|
nullable_fields = ("release_date", "documentation_url", "end_of_support", "filename", "file_checksum", "file_link")
|
||||||
|
|
||||||
class Meta:
|
tag = TagFilterField(model)
|
||||||
model = SoftwareProductVersion
|
comments = CommentField()
|
||||||
nullable_fields = []
|
|
||||||
|
release_date = DateField(required=False, widget=DatePicker())
|
||||||
|
documentation_url = CharField(required=False)
|
||||||
|
end_of_support = DateField(required=False, widget=DatePicker())
|
||||||
|
filename = CharField(required=False)
|
||||||
|
file_checksum = CharField(required=False)
|
||||||
|
file_link = CharField(required=False)
|
||||||
|
|
||||||
|
release_type = MultipleChoiceField(required=False, choices=SoftwareReleaseTypes.choices)
|
||||||
|
|
||||||
|
software_product = DynamicModelChoiceField(
|
||||||
|
queryset=SoftwareProduct.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label="Software Product",
|
||||||
|
)
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.1.5 on 2025-02-17 21:35
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("netbox_slm", "0008_software_release_types_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="softwareproductversion",
|
||||||
|
name="description",
|
||||||
|
field=models.CharField(blank=True, max_length=255, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Generated by Django 5.1.5 on 2025-02-24 18:03
|
||||||
|
|
||||||
|
import netbox_slm.models
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("netbox_slm", "0009_softwareproductversion_description"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="softwarelicense",
|
||||||
|
name="spdx_expression",
|
||||||
|
field=models.CharField(
|
||||||
|
blank=True,
|
||||||
|
max_length=64,
|
||||||
|
null=True,
|
||||||
|
validators=[netbox_slm.models.validate_spdx_expression],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
+29
-22
@@ -1,11 +1,15 @@
|
|||||||
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse, reverse_lazy
|
from django.urls import reverse
|
||||||
from django.utils import safestring
|
from django.utils.html import format_html, urlencode
|
||||||
|
from license_expression import Licensing, get_spdx_licensing
|
||||||
|
|
||||||
from netbox.models import NetBoxModel
|
from netbox.models import NetBoxModel
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
from utilities.validators import EnhancedURLValidator
|
from utilities.validators import EnhancedURLValidator
|
||||||
|
|
||||||
|
spdx_licensing: Licensing = get_spdx_licensing()
|
||||||
|
|
||||||
|
|
||||||
class LaxURLField(models.URLField):
|
class LaxURLField(models.URLField):
|
||||||
"""
|
"""
|
||||||
@@ -33,16 +37,10 @@ class SoftwareProduct(NetBoxModel):
|
|||||||
|
|
||||||
def get_installation_count(self):
|
def get_installation_count(self):
|
||||||
count = SoftwareProductInstallation.objects.filter(software_product_id=self.pk).count()
|
count = SoftwareProductInstallation.objects.filter(software_product_id=self.pk).count()
|
||||||
return (
|
query_string = urlencode(dict(software_product_id=self.pk))
|
||||||
safestring.mark_safe(
|
search_target = reverse("plugins:netbox_slm:softwareproductinstallation_list")
|
||||||
'<a href="{url}">{count}</a>'.format(
|
# Can be composed directly with reverse(query=) in Django 5.2, see https://code.djangoproject.com/ticket/25582
|
||||||
url=reverse_lazy("plugins:netbox_slm:softwareproductinstallation_list") + f"?q={self.name}",
|
return format_html(f"<a href='{search_target}?{query_string}'>{count}</a>") if count else "0"
|
||||||
count=count,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if count
|
|
||||||
else "0"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SoftwareReleaseTypes(models.TextChoices):
|
class SoftwareReleaseTypes(models.TextChoices):
|
||||||
@@ -56,6 +54,7 @@ class SoftwareProductVersion(NetBoxModel):
|
|||||||
name = models.CharField(max_length=64)
|
name = models.CharField(max_length=64)
|
||||||
comments = models.TextField(blank=True)
|
comments = models.TextField(blank=True)
|
||||||
|
|
||||||
|
description = models.CharField(max_length=255, null=True, blank=True)
|
||||||
release_date = models.DateField(null=True, blank=True)
|
release_date = models.DateField(null=True, blank=True)
|
||||||
documentation_url = LaxURLField(max_length=1024, null=True, blank=True)
|
documentation_url = LaxURLField(max_length=1024, null=True, blank=True)
|
||||||
end_of_support = models.DateField(null=True, blank=True)
|
end_of_support = models.DateField(null=True, blank=True)
|
||||||
@@ -84,16 +83,10 @@ class SoftwareProductVersion(NetBoxModel):
|
|||||||
|
|
||||||
def get_installation_count(self):
|
def get_installation_count(self):
|
||||||
count = SoftwareProductInstallation.objects.filter(version_id=self.pk).count()
|
count = SoftwareProductInstallation.objects.filter(version_id=self.pk).count()
|
||||||
return (
|
query_string = urlencode(dict(version_id=self.pk))
|
||||||
safestring.mark_safe(
|
search_target = reverse("plugins:netbox_slm:softwareproductinstallation_list")
|
||||||
'<a href="{url}">{count}</a>'.format(
|
# Can be composed directly with reverse(query=) in Django 5.2, see https://code.djangoproject.com/ticket/25582
|
||||||
url=reverse_lazy("plugins:netbox_slm:softwareproductinstallation_list") + f"?q={self.name}",
|
return format_html(f"<a href='{search_target}?{query_string}'>{count}</a>") if count else "0"
|
||||||
count=count,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if count
|
|
||||||
else "0"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SoftwareProductInstallation(NetBoxModel):
|
class SoftwareProductInstallation(NetBoxModel):
|
||||||
@@ -140,12 +133,26 @@ class SoftwareProductInstallation(NetBoxModel):
|
|||||||
return "cluster"
|
return "cluster"
|
||||||
|
|
||||||
|
|
||||||
|
def spdx_license_names():
|
||||||
|
names = [(item[0], item[0]) for item in spdx_licensing.known_symbols.items() if not item[1].is_exception]
|
||||||
|
names.sort()
|
||||||
|
names.insert(0, ("", "---------")) # set default value
|
||||||
|
return names
|
||||||
|
|
||||||
|
|
||||||
|
def validate_spdx_expression(value):
|
||||||
|
expression_info = spdx_licensing.validate(value)
|
||||||
|
if expression_info.errors:
|
||||||
|
raise ValidationError(f"{value} is not a known SPDX license expression")
|
||||||
|
|
||||||
|
|
||||||
class SoftwareLicense(NetBoxModel):
|
class SoftwareLicense(NetBoxModel):
|
||||||
name = models.CharField(max_length=128)
|
name = models.CharField(max_length=128)
|
||||||
comments = models.TextField(blank=True)
|
comments = models.TextField(blank=True)
|
||||||
|
|
||||||
description = models.CharField(max_length=255, null=True, blank=True)
|
description = models.CharField(max_length=255, null=True, blank=True)
|
||||||
type = models.CharField(max_length=128)
|
type = models.CharField(max_length=128)
|
||||||
|
spdx_expression = models.CharField(max_length=64, null=True, blank=True, validators=[validate_spdx_expression])
|
||||||
stored_location = models.CharField(max_length=255, null=True, blank=True)
|
stored_location = models.CharField(max_length=255, null=True, blank=True)
|
||||||
stored_location_url = LaxURLField(max_length=1024, null=True, blank=True)
|
stored_location_url = LaxURLField(max_length=1024, null=True, blank=True)
|
||||||
start_date = models.DateField(null=True, blank=True)
|
start_date = models.DateField(null=True, blank=True)
|
||||||
|
|||||||
+25
-19
@@ -1,85 +1,91 @@
|
|||||||
from extras.plugins import PluginMenuButton, PluginMenuItem
|
from netbox.plugins import PluginMenuButton, PluginMenuItem, PluginMenu, get_plugin_config
|
||||||
from utilities.choices import ButtonColorChoices
|
from . import SLMConfig
|
||||||
|
|
||||||
menu_items = (
|
slm_items = (
|
||||||
PluginMenuItem(
|
PluginMenuItem(
|
||||||
link="plugins:netbox_slm:softwareproduct_list",
|
link="plugins:netbox_slm:softwareproduct_list",
|
||||||
link_text="Software Products",
|
link_text="Software Products",
|
||||||
|
permissions=["netbox_slm.add_softwareproduct"],
|
||||||
buttons=(
|
buttons=(
|
||||||
PluginMenuButton(
|
PluginMenuButton(
|
||||||
"plugins:netbox_slm:softwareproduct_add",
|
"plugins:netbox_slm:softwareproduct_add",
|
||||||
"Add",
|
"Add",
|
||||||
"mdi mdi-plus-thick",
|
"mdi mdi-plus-thick",
|
||||||
ButtonColorChoices.GREEN,
|
|
||||||
permissions=["netbox_slm.add_softwareproduct"],
|
permissions=["netbox_slm.add_softwareproduct"],
|
||||||
),
|
),
|
||||||
PluginMenuButton(
|
PluginMenuButton(
|
||||||
"plugins:netbox_slm:softwareproduct_import",
|
"plugins:netbox_slm:softwareproduct_bulk_import",
|
||||||
"Import",
|
"Import",
|
||||||
"mdi mdi-upload",
|
"mdi mdi-upload",
|
||||||
ButtonColorChoices.CYAN,
|
permissions=["netbox_slm.import_softwareproduct"],
|
||||||
permissions=["netbox_slm.add_softwareproduct"],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
PluginMenuItem(
|
PluginMenuItem(
|
||||||
link="plugins:netbox_slm:softwareproductversion_list",
|
link="plugins:netbox_slm:softwareproductversion_list",
|
||||||
link_text="Versions",
|
link_text="Versions",
|
||||||
|
permissions=["netbox_slm.add_softwareproductversion"],
|
||||||
buttons=(
|
buttons=(
|
||||||
PluginMenuButton(
|
PluginMenuButton(
|
||||||
"plugins:netbox_slm:softwareproductversion_add",
|
"plugins:netbox_slm:softwareproductversion_add",
|
||||||
"Add",
|
"Add",
|
||||||
"mdi mdi-plus-thick",
|
"mdi mdi-plus-thick",
|
||||||
ButtonColorChoices.GREEN,
|
|
||||||
permissions=["netbox_slm.add_softwareproductversion"],
|
permissions=["netbox_slm.add_softwareproductversion"],
|
||||||
),
|
),
|
||||||
PluginMenuButton(
|
PluginMenuButton(
|
||||||
"plugins:netbox_slm:softwareproductversion_import",
|
"plugins:netbox_slm:softwareproductversion_bulk_import",
|
||||||
"Import",
|
"Import",
|
||||||
"mdi mdi-upload",
|
"mdi mdi-upload",
|
||||||
ButtonColorChoices.CYAN,
|
permissions=["netbox_slm.import_softwareproductversion"],
|
||||||
permissions=["netbox_slm.add_softwareproductversion"],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
PluginMenuItem(
|
PluginMenuItem(
|
||||||
link="plugins:netbox_slm:softwareproductinstallation_list",
|
link="plugins:netbox_slm:softwareproductinstallation_list",
|
||||||
link_text="Installations",
|
link_text="Installations",
|
||||||
|
permissions=["netbox_slm.add_softwareproductinstallation"],
|
||||||
buttons=(
|
buttons=(
|
||||||
PluginMenuButton(
|
PluginMenuButton(
|
||||||
"plugins:netbox_slm:softwareproductinstallation_add",
|
"plugins:netbox_slm:softwareproductinstallation_add",
|
||||||
"Add",
|
"Add",
|
||||||
"mdi mdi-plus-thick",
|
"mdi mdi-plus-thick",
|
||||||
ButtonColorChoices.GREEN,
|
|
||||||
permissions=["netbox_slm.add_softwareproductinstallation"],
|
permissions=["netbox_slm.add_softwareproductinstallation"],
|
||||||
),
|
),
|
||||||
PluginMenuButton(
|
PluginMenuButton(
|
||||||
"plugins:netbox_slm:softwareproductinstallation_import",
|
"plugins:netbox_slm:softwareproductinstallation_bulk_import",
|
||||||
"Import",
|
"Import",
|
||||||
"mdi mdi-upload",
|
"mdi mdi-upload",
|
||||||
ButtonColorChoices.CYAN,
|
permissions=["netbox_slm.import_softwareproductinstallation"],
|
||||||
permissions=["netbox_slm.add_softwareproductinstallation"],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
PluginMenuItem(
|
PluginMenuItem(
|
||||||
link="plugins:netbox_slm:softwarelicense_list",
|
link="plugins:netbox_slm:softwarelicense_list",
|
||||||
link_text="Licenses",
|
link_text="Licenses",
|
||||||
|
permissions=["netbox_slm.add_softwarelicense"],
|
||||||
buttons=(
|
buttons=(
|
||||||
PluginMenuButton(
|
PluginMenuButton(
|
||||||
"plugins:netbox_slm:softwarelicense_add",
|
"plugins:netbox_slm:softwarelicense_add",
|
||||||
"Add",
|
"Add",
|
||||||
"mdi mdi-plus-thick",
|
"mdi mdi-plus-thick",
|
||||||
ButtonColorChoices.GREEN,
|
|
||||||
permissions=["netbox_slm.add_softwarelicense"],
|
permissions=["netbox_slm.add_softwarelicense"],
|
||||||
),
|
),
|
||||||
PluginMenuButton(
|
PluginMenuButton(
|
||||||
"plugins:netbox_slm:softwarelicense_import",
|
"plugins:netbox_slm:softwarelicense_bulk_import",
|
||||||
"Import",
|
"Import",
|
||||||
"mdi mdi-upload",
|
"mdi mdi-upload",
|
||||||
ButtonColorChoices.CYAN,
|
permissions=["netbox_slm.import_softwarelicense"],
|
||||||
permissions=["netbox_slm.add_softwarelicense"],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if get_plugin_config("netbox_slm", "top_level_menu"):
|
||||||
|
menu = PluginMenu(
|
||||||
|
label="Software Lifecycle",
|
||||||
|
groups=((SLMConfig.verbose_name, slm_items),),
|
||||||
|
icon_class="mdi mdi-content-save",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# auto imported by default PluginConfig.menu_items = navigation.menu_items
|
||||||
|
menu_items = slm_items
|
||||||
|
|||||||
+16
-31
@@ -20,8 +20,8 @@ class SoftwareProductTable(NetBoxTable):
|
|||||||
fields = (
|
fields = (
|
||||||
"pk",
|
"pk",
|
||||||
"name",
|
"name",
|
||||||
"manufacturer",
|
|
||||||
"description",
|
"description",
|
||||||
|
"manufacturer",
|
||||||
"installations",
|
"installations",
|
||||||
"tags",
|
"tags",
|
||||||
)
|
)
|
||||||
@@ -29,16 +29,9 @@ class SoftwareProductTable(NetBoxTable):
|
|||||||
"pk",
|
"pk",
|
||||||
"name",
|
"name",
|
||||||
"manufacturer",
|
"manufacturer",
|
||||||
"description",
|
|
||||||
"installations",
|
"installations",
|
||||||
"tags",
|
"tags",
|
||||||
)
|
)
|
||||||
sequence = (
|
|
||||||
"manufacturer",
|
|
||||||
"name",
|
|
||||||
"description",
|
|
||||||
"installations",
|
|
||||||
)
|
|
||||||
|
|
||||||
def order_installations(self, queryset, is_descending):
|
def order_installations(self, queryset, is_descending):
|
||||||
queryset = queryset.annotate(count=Count("softwareproductinstallation__id")).order_by(
|
queryset = queryset.annotate(count=Count("softwareproductinstallation__id")).order_by(
|
||||||
@@ -52,7 +45,7 @@ class SoftwareProductVersionTable(NetBoxTable):
|
|||||||
|
|
||||||
pk = ToggleColumn()
|
pk = ToggleColumn()
|
||||||
name = tables.LinkColumn()
|
name = tables.LinkColumn()
|
||||||
software_product = tables.Column(accessor="software_product", linkify=True)
|
software_product = tables.Column(accessor="software_product", verbose_name="Software Product", linkify=True)
|
||||||
manufacturer = tables.Column(accessor="software_product__manufacturer", linkify=True)
|
manufacturer = tables.Column(accessor="software_product__manufacturer", linkify=True)
|
||||||
installations = tables.Column(accessor="get_installation_count")
|
installations = tables.Column(accessor="get_installation_count")
|
||||||
|
|
||||||
@@ -63,6 +56,7 @@ class SoftwareProductVersionTable(NetBoxTable):
|
|||||||
fields = (
|
fields = (
|
||||||
"pk",
|
"pk",
|
||||||
"name",
|
"name",
|
||||||
|
"description",
|
||||||
"software_product",
|
"software_product",
|
||||||
"manufacturer",
|
"manufacturer",
|
||||||
"release_date",
|
"release_date",
|
||||||
@@ -74,18 +68,12 @@ class SoftwareProductVersionTable(NetBoxTable):
|
|||||||
default_columns = (
|
default_columns = (
|
||||||
"pk",
|
"pk",
|
||||||
"name",
|
"name",
|
||||||
"software_product",
|
|
||||||
"manufacturer",
|
"manufacturer",
|
||||||
|
"software_product",
|
||||||
"release_date",
|
"release_date",
|
||||||
"installations",
|
"installations",
|
||||||
"tags",
|
"tags",
|
||||||
)
|
)
|
||||||
sequence = (
|
|
||||||
"manufacturer",
|
|
||||||
"software_product",
|
|
||||||
"name",
|
|
||||||
"installations",
|
|
||||||
)
|
|
||||||
|
|
||||||
def order_installations(self, queryset, is_descending):
|
def order_installations(self, queryset, is_descending):
|
||||||
queryset = queryset.annotate(count=Count("softwareproductinstallation__id")).order_by(
|
queryset = queryset.annotate(count=Count("softwareproductinstallation__id")).order_by(
|
||||||
@@ -98,14 +86,10 @@ class SoftwareProductInstallationTable(NetBoxTable):
|
|||||||
"""Table for displaying SoftwareProductInstallation objects."""
|
"""Table for displaying SoftwareProductInstallation objects."""
|
||||||
|
|
||||||
pk = ToggleColumn()
|
pk = ToggleColumn()
|
||||||
name = tables.LinkColumn()
|
|
||||||
|
|
||||||
device = tables.Column(accessor="device", linkify=True)
|
|
||||||
virtualmachine = tables.Column(accessor="virtualmachine", linkify=True)
|
|
||||||
cluster = tables.Column(accessor="cluster", linkify=True)
|
|
||||||
platform = tables.Column(accessor="platform", linkify=True)
|
platform = tables.Column(accessor="platform", linkify=True)
|
||||||
type = tables.Column(accessor="render_type")
|
type = tables.Column(accessor="render_type")
|
||||||
software_product = tables.Column(accessor="software_product", linkify=True)
|
manufacturer = tables.Column(accessor="software_product__manufacturer", linkify=True)
|
||||||
|
software_product = tables.Column(accessor="software_product", verbose_name="Software Product", linkify=True)
|
||||||
version = tables.Column(accessor="version", linkify=True)
|
version = tables.Column(accessor="version", linkify=True)
|
||||||
|
|
||||||
tags = columns.TagColumn(url_name="plugins:netbox_slm:softwareproductinstallation_list")
|
tags = columns.TagColumn(url_name="plugins:netbox_slm:softwareproductinstallation_list")
|
||||||
@@ -114,9 +98,9 @@ class SoftwareProductInstallationTable(NetBoxTable):
|
|||||||
model = SoftwareProductInstallation
|
model = SoftwareProductInstallation
|
||||||
fields = (
|
fields = (
|
||||||
"pk",
|
"pk",
|
||||||
"name",
|
|
||||||
"platform",
|
"platform",
|
||||||
"type",
|
"type",
|
||||||
|
"manufacturer",
|
||||||
"software_product",
|
"software_product",
|
||||||
"version",
|
"version",
|
||||||
"tags",
|
"tags",
|
||||||
@@ -125,6 +109,7 @@ class SoftwareProductInstallationTable(NetBoxTable):
|
|||||||
"pk",
|
"pk",
|
||||||
"platform",
|
"platform",
|
||||||
"type",
|
"type",
|
||||||
|
"manufacturer",
|
||||||
"software_product",
|
"software_product",
|
||||||
"version",
|
"version",
|
||||||
"tags",
|
"tags",
|
||||||
@@ -144,9 +129,6 @@ class SoftwareProductInstallationTable(NetBoxTable):
|
|||||||
queryset_union = device_annotate.union(vm_annotate).union(cluster_annotate)
|
queryset_union = device_annotate.union(vm_annotate).union(cluster_annotate)
|
||||||
return queryset_union.order_by(f"{'-' if is_descending else ''}render_type"), True
|
return queryset_union.order_by(f"{'-' if is_descending else ''}render_type"), True
|
||||||
|
|
||||||
def render_software_product(self, value, **kwargs):
|
|
||||||
return f"{kwargs['record'].software_product.manufacturer.name} - {value}"
|
|
||||||
|
|
||||||
|
|
||||||
class SoftwareLicenseTable(NetBoxTable):
|
class SoftwareLicenseTable(NetBoxTable):
|
||||||
"""Table for displaying SoftwareLicense objects."""
|
"""Table for displaying SoftwareLicense objects."""
|
||||||
@@ -155,9 +137,11 @@ class SoftwareLicenseTable(NetBoxTable):
|
|||||||
name = tables.LinkColumn()
|
name = tables.LinkColumn()
|
||||||
|
|
||||||
type = tables.Column()
|
type = tables.Column()
|
||||||
|
spdx_expression = tables.Column(verbose_name="SPDX expression")
|
||||||
stored_location = tables.Column(accessor="stored_location_txt", linkify=lambda record: record.stored_location_url)
|
stored_location = tables.Column(accessor="stored_location_txt", linkify=lambda record: record.stored_location_url)
|
||||||
|
|
||||||
software_product = tables.Column(accessor="software_product", linkify=True)
|
manufacturer = tables.Column(accessor="software_product__manufacturer", linkify=True)
|
||||||
|
software_product = tables.Column(accessor="software_product", verbose_name="Software Product", linkify=True)
|
||||||
version = tables.Column(accessor="version", linkify=True)
|
version = tables.Column(accessor="version", linkify=True)
|
||||||
installation = tables.Column(accessor="installation", linkify=True)
|
installation = tables.Column(accessor="installation", linkify=True)
|
||||||
|
|
||||||
@@ -170,9 +154,11 @@ class SoftwareLicenseTable(NetBoxTable):
|
|||||||
"name",
|
"name",
|
||||||
"description",
|
"description",
|
||||||
"type",
|
"type",
|
||||||
|
"spdx_expression",
|
||||||
"stored_location",
|
"stored_location",
|
||||||
"start_date",
|
"start_date",
|
||||||
"expiration_date",
|
"expiration_date",
|
||||||
|
"manufacturer",
|
||||||
"software_product",
|
"software_product",
|
||||||
"version",
|
"version",
|
||||||
"installation",
|
"installation",
|
||||||
@@ -183,14 +169,13 @@ class SoftwareLicenseTable(NetBoxTable):
|
|||||||
default_columns = (
|
default_columns = (
|
||||||
"pk",
|
"pk",
|
||||||
"name",
|
"name",
|
||||||
"expiration_date",
|
"type",
|
||||||
|
"manufacturer",
|
||||||
"software_product",
|
"software_product",
|
||||||
"installation",
|
"installation",
|
||||||
|
"expiration_date",
|
||||||
"tags",
|
"tags",
|
||||||
)
|
)
|
||||||
|
|
||||||
def render_software_product(self, value, **kwargs):
|
|
||||||
return f"{kwargs['record'].software_product.manufacturer.name} - {value}"
|
|
||||||
|
|
||||||
def render_installation(self, **kwargs):
|
def render_installation(self, **kwargs):
|
||||||
return f"{kwargs['record'].installation.platform}"
|
return f"{kwargs['record'].installation.platform}"
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
from netbox.plugins import PluginTemplateExtension, get_plugin_config
|
||||||
|
|
||||||
|
installations = dict(
|
||||||
|
device=get_plugin_config("netbox_slm", "link_device_installations"),
|
||||||
|
cluster=get_plugin_config("netbox_slm", "link_cluster_installations"),
|
||||||
|
virtualmachine=get_plugin_config("netbox_slm", "link_virtualmachine_installations"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class InstallationsCard(PluginTemplateExtension):
|
||||||
|
models = ["dcim.device", "virtualization.cluster", "virtualization.virtualmachine"]
|
||||||
|
|
||||||
|
def get_object_name(self):
|
||||||
|
object_model_meta = self.context["object"]._meta # one of the self.models defined above
|
||||||
|
return object_model_meta.model_name
|
||||||
|
|
||||||
|
def render_card(self):
|
||||||
|
return self.render(
|
||||||
|
"netbox_slm/installations_card_include.html",
|
||||||
|
extra_context={
|
||||||
|
"search_obj": self.get_object_name(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def left_page(self):
|
||||||
|
return self.render_card() if installations[self.get_object_name()] == "left" else ""
|
||||||
|
|
||||||
|
def right_page(self):
|
||||||
|
return self.render_card() if installations[self.get_object_name()] == "right" else ""
|
||||||
|
|
||||||
|
def full_width_page(self):
|
||||||
|
return self.render_card() if installations[self.get_object_name()] == "full" else ""
|
||||||
|
|
||||||
|
|
||||||
|
# auto imported by default PluginConfig.template_extensions = template_content.template_extensions
|
||||||
|
template_extensions = [InstallationsCard]
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{% load helpers %}
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h2 class="card-header">
|
||||||
|
Installations
|
||||||
|
{% if perms.netbox_slm.add_installation %}
|
||||||
|
<div class="card-actions">
|
||||||
|
<a href="{% url 'plugins:netbox_slm:softwareproductinstallation_add' %}?{{ search_obj }}={{ object.pk }}" class="btn btn-ghost-primary btn-sm">
|
||||||
|
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add an installation
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</h2>
|
||||||
|
{% if search_obj == "cluster" %}
|
||||||
|
{% htmx_table "plugins:netbox_slm:softwareproductinstallation_list" cluster_id=object.pk %}
|
||||||
|
{% elif search_obj == "device" %}
|
||||||
|
{% htmx_table "plugins:netbox_slm:softwareproductinstallation_list" device_id=object.pk %}
|
||||||
|
{% elif search_obj == "virtualmachine" %}
|
||||||
|
{% htmx_table "plugins:netbox_slm:softwareproductinstallation_list" virtualmachine_id=object.pk %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
@@ -11,68 +11,70 @@
|
|||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
Software License
|
Software License
|
||||||
</h5>
|
</h5>
|
||||||
<div class="card-body">
|
<table class="table table-hover attr-table">
|
||||||
<table class="table table-hover attr-table">
|
<tr>
|
||||||
<tr>
|
<th scope="row">Name</th>
|
||||||
<th scope="row">Name</th>
|
<td>{{ object.name }}</td>
|
||||||
<td>{{ object.name }}</td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
<tr>
|
<th scope="row">Description</th>
|
||||||
<th scope="row">Description</th>
|
<td>{{ object.description }}</td>
|
||||||
<td>{{ object.description }}</td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
<tr>
|
<th scope="row">Type</th>
|
||||||
<th scope="row">Type</th>
|
<td>{{ object.type }}</td>
|
||||||
<td>{{ object.type }}</td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
<tr>
|
<th scope="row">SPDX expression</th>
|
||||||
<th scope="row">Stored location</th>
|
<td>{{ object.spdx_expression }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Software Product</th>
|
||||||
|
<td>{{ object.software_product|linkify }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Version</th>
|
||||||
|
<td>{{ object.version|linkify }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Installation</th>
|
||||||
|
<td>{{ object.installation|linkify }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Stored location</th>
|
||||||
{% if object.stored_location_url %}
|
{% if object.stored_location_url %}
|
||||||
<td><a href="{{ object.stored_location_url }}">{{ object.stored_location_txt }}</a></td>
|
<td><a href="{{ object.stored_location_url }}">{{ object.stored_location_txt }}</a></td>
|
||||||
{% else %}
|
{% else %}
|
||||||
<td>{{ object.stored_location_txt }}</td>
|
<td>{{ object.stored_location_txt }}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Start date</th>
|
<th scope="row">Start date</th>
|
||||||
<td>{{ object.start_date }}</td>
|
<td>{{ object.start_date }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Expiration date</th>
|
<th scope="row">Expiration date</th>
|
||||||
<td>{{ object.expiration_date }}</td>
|
<td>{{ object.expiration_date }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Software Product</th>
|
<th scope="row">Support</th>
|
||||||
<td>{{ object.software_product }}</td>
|
<td>{{ object.support }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Version</th>
|
<th scope="row">License amount</th>
|
||||||
<td>{{ object.version }}</td>
|
<td>{{ object.license_amount }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
</table>
|
||||||
<th scope="row">Support</th>
|
|
||||||
<td>{{ object.support }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">License amount</th>
|
|
||||||
<td>{{ object.license_amount }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Installation</th>
|
|
||||||
<td>{{ object.installation }}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% include 'inc/panels/custom_fields.html' %}
|
{% include 'inc/panels/custom_fields.html' %}
|
||||||
{% include 'inc/panels/tags.html' %}
|
{% include 'inc/panels/tags.html' %}
|
||||||
{% include 'inc/panels/comments.html' %}
|
{% include 'inc/panels/comments.html' %}
|
||||||
{% plugin_left_page object %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col col-md-12">
|
<div class="col col-md-12">
|
||||||
{% plugin_full_width_page object %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -11,44 +11,66 @@
|
|||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
Software Product
|
Software Product
|
||||||
</h5>
|
</h5>
|
||||||
<div class="card-body">
|
<table class="table table-hover attr-table">
|
||||||
<table class="table table-hover attr-table">
|
<tr>
|
||||||
<tr>
|
<th scope="row">Name</th>
|
||||||
<th scope="row">Name</th>
|
<td>{{ object.name }}</td>
|
||||||
<td>{{ object.name }}</td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
<tr>
|
<th scope="row">Description</th>
|
||||||
<th scope="row">Description</th>
|
<td>{{ object.description }}</td>
|
||||||
<td>{{ object.description }}</td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
<tr>
|
<th scope="row">Manufacturer</th>
|
||||||
<th scope="row">Manufacturer</th>
|
<td>{{ object.manufacturer|linkify }}</td>
|
||||||
<td>{{ object.manufacturer }}</td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
<tr>
|
<th scope="row">Versions</th>
|
||||||
<th scope="row">Versions</th>
|
<td>
|
||||||
<td>
|
{% for version in object.softwareproductversion_set.all %}
|
||||||
{% for version in object.softwareproduct_versions.all %}
|
<a href="{% url 'plugins:netbox_slm:softwareproductversion' pk=version.pk %}" class="btn btn-sm">
|
||||||
<a href="{% url 'plugins:netbox_slm:softwareproductversion' pk=version.pk %}">
|
{{ version }}
|
||||||
<span class="badge" style="color: #ffffff; background-color: #9e9e9e">{{ version }}</span>
|
</a>
|
||||||
</a>
|
{% empty %}
|
||||||
{% empty %}
|
None
|
||||||
n/a
|
{% endfor %}
|
||||||
{% endfor %}
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
</table>
|
<th scope="row">Installations</th>
|
||||||
</div>
|
<td>
|
||||||
|
{% for installation in object.softwareproductinstallation_set.all %}
|
||||||
|
<a href="{% url 'plugins:netbox_slm:softwareproductinstallation' pk=installation.pk %}" class="btn btn-sm">
|
||||||
|
{{ installation }}
|
||||||
|
</a>
|
||||||
|
{% empty %}
|
||||||
|
None
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Licenses</th>
|
||||||
|
<td>
|
||||||
|
{% for license in object.softwarelicense_set.all %}
|
||||||
|
<a href="{% url 'plugins:netbox_slm:softwarelicense' pk=license.pk %}" class="btn btn-sm">
|
||||||
|
{{ license }}
|
||||||
|
</a>
|
||||||
|
{% empty %}
|
||||||
|
None
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% include 'inc/panels/custom_fields.html' %}
|
{% include 'inc/panels/custom_fields.html' %}
|
||||||
{% include 'inc/panels/tags.html' %}
|
{% include 'inc/panels/tags.html' %}
|
||||||
{% include 'inc/panels/comments.html' %}
|
{% include 'inc/panels/comments.html' %}
|
||||||
{% plugin_left_page object %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col col-md-12">
|
<div class="col col-md-12">
|
||||||
{% plugin_full_width_page object %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -11,44 +11,54 @@
|
|||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
Software Product Installation
|
Software Product Installation
|
||||||
</h5>
|
</h5>
|
||||||
<div class="card-body">
|
<table class="table table-hover attr-table">
|
||||||
<table class="table table-hover attr-table">
|
|
||||||
{% if object.device %}
|
{% if object.device %}
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Device</th>
|
<th scope="row">Device</th>
|
||||||
<td>{{ object.device }}</td>
|
<td>{{ object.device|linkify }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% elif object.virtualmachine %}
|
{% elif object.virtualmachine %}
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Virtualmachine</th>
|
<th scope="row">Virtual Machine</th>
|
||||||
<td>{{ object.virtualmachine }}</td>
|
<td>{{ object.virtualmachine|linkify }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% else %}
|
{% else %}
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Cluster</th>
|
<th scope="row">Cluster</th>
|
||||||
<td>{{ object.cluster }}</td>
|
<td>{{ object.cluster|linkify }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Software Product</th>
|
<th scope="row">Software Product</th>
|
||||||
<td>{{ object.software_product }}</td>
|
<td>{{ object.software_product|linkify }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Version</th>
|
<th scope="row">Version</th>
|
||||||
<td>{{ object.version }}</td>
|
<td>{{ object.version|linkify }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
<tr>
|
||||||
</div>
|
<th scope="row">Licenses</th>
|
||||||
|
<td>
|
||||||
|
{% for license in object.softwarelicense_set.all %}
|
||||||
|
<a href="{% url 'plugins:netbox_slm:softwarelicense' pk=license.pk %}" class="btn btn-sm">
|
||||||
|
{{ license }}
|
||||||
|
</a>
|
||||||
|
{% empty %}
|
||||||
|
None
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% include 'inc/panels/custom_fields.html' %}
|
{% include 'inc/panels/custom_fields.html' %}
|
||||||
{% include 'inc/panels/tags.html' %}
|
{% include 'inc/panels/tags.html' %}
|
||||||
{% include 'inc/panels/comments.html' %}
|
{% include 'inc/panels/comments.html' %}
|
||||||
{% plugin_left_page object %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col col-md-12">
|
<div class="col col-md-12">
|
||||||
{% plugin_full_width_page object %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -11,60 +11,90 @@
|
|||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
Software Product Version
|
Software Product Version
|
||||||
</h5>
|
</h5>
|
||||||
<div class="card-body">
|
<table class="table table-hover attr-table">
|
||||||
<table class="table table-hover attr-table">
|
<tr>
|
||||||
<tr>
|
<th scope="row">Name</th>
|
||||||
<th scope="row">Name</th>
|
<td>{{ object.name }}</td>
|
||||||
<td>{{ object.name }}</td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
<tr>
|
<th scope="row">Description</th>
|
||||||
<th scope="row">Release date</th>
|
<td>{{ object.description }}</td>
|
||||||
<td>{{ object.release_date }}</td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
<tr>
|
<th scope="row">Manufacturer</th>
|
||||||
<th scope="row">Documentation url</th>
|
<td>{{ object.software_product.manufacturer|linkify }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Software Product</th>
|
||||||
|
<td>{{ object.software_product|linkify }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Release date</th>
|
||||||
|
<td>{{ object.release_date }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Documentation url</th>
|
||||||
{% if object.documentation_url %}
|
{% if object.documentation_url %}
|
||||||
<td><a href="{{ object.documentation_url }}">{{ object.documentation_url }}</a></td>
|
<td><a href="{{ object.documentation_url }}">{{ object.documentation_url }}</a></td>
|
||||||
{% else %}
|
{% else %}
|
||||||
<td>{{ object.documentation_url }}</td>
|
<td>{{ object.documentation_url }}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">End of support</th>
|
<th scope="row">End of support</th>
|
||||||
<td>{{ object.end_of_support }}</td>
|
<td>{{ object.end_of_support }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Filename</th>
|
<th scope="row">Filename</th>
|
||||||
{% if object.file_link %}
|
{% if object.file_link %}
|
||||||
<td><a href="{{ object.file_link }}">{{ object.filename }}</a></td>
|
<td><a href="{{ object.file_link }}">{{ object.filename }}</a></td>
|
||||||
{% else %}
|
{% else %}
|
||||||
<td>{{ object.filename }}</td>
|
<td>{{ object.filename }}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">File checksum</th>
|
<th scope="row">File checksum</th>
|
||||||
<td>{{ object.file_checksum }}</td>
|
<td>{{ object.file_checksum }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Release type</th>
|
<th scope="row">Release type</th>
|
||||||
<td>{{ object.get_release_type_display }}</td>
|
<td>{{ object.get_release_type_display }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Installations</th>
|
<th scope="row">Installations</th>
|
||||||
<td>{{ object.get_installation_count }}</td>
|
<td>
|
||||||
</tr>
|
{% for installation in object.softwareproductinstallation_set.all %}
|
||||||
</table>
|
<a href="{% url 'plugins:netbox_slm:softwareproductinstallation' pk=installation.pk %}" class="btn btn-sm">
|
||||||
</div>
|
{{ installation }}
|
||||||
|
</a>
|
||||||
|
{% empty %}
|
||||||
|
None
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Licenses</th>
|
||||||
|
<td>
|
||||||
|
{% for license in object.softwarelicense_set.all %}
|
||||||
|
<a href="{% url 'plugins:netbox_slm:softwarelicense' pk=license.pk %}" class="btn btn-sm">
|
||||||
|
{{ license }}
|
||||||
|
</a>
|
||||||
|
{% empty %}
|
||||||
|
None
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% include 'inc/panels/custom_fields.html' %}
|
{% include 'inc/panels/custom_fields.html' %}
|
||||||
{% include 'inc/panels/tags.html' %}
|
{% include 'inc/panels/tags.html' %}
|
||||||
{% include 'inc/panels/comments.html' %}
|
{% include 'inc/panels/comments.html' %}
|
||||||
{% plugin_left_page object %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col col-md-12">
|
<div class="col col-md-12">
|
||||||
{% plugin_full_width_page object %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
|
||||||
|
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense
|
||||||
|
from virtualization.models import Cluster, ClusterType, VirtualMachine
|
||||||
|
|
||||||
|
|
||||||
|
class SlmBaseTestCase(TestCase):
|
||||||
|
p_name: str = "test product"
|
||||||
|
v_name: str = "test version"
|
||||||
|
l_name: str = "test license"
|
||||||
|
test_url: str = "https://github.com/ICTU/netbox_slm"
|
||||||
|
vm: VirtualMachine
|
||||||
|
software_product: SoftwareProduct
|
||||||
|
software_product_version: SoftwareProductVersion
|
||||||
|
software_product_installation: SoftwareProductInstallation
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
|
manufacturer = Manufacturer.objects.create(name="test manufacturer")
|
||||||
|
device_type = DeviceType.objects.create(model="test device type", manufacturer=manufacturer)
|
||||||
|
device_role = DeviceRole.objects.create(name="test device role")
|
||||||
|
site = Site.objects.create(name="test site")
|
||||||
|
cls.device = Device.objects.create(name="test device", device_type=device_type, role=device_role, site=site)
|
||||||
|
cls.vm = VirtualMachine.objects.create(name="test VM")
|
||||||
|
cluster_type = ClusterType.objects.create(name="test cluster type")
|
||||||
|
cls.cluster = Cluster.objects.create(name="test cluster", type=cluster_type)
|
||||||
|
|
||||||
|
cls.software_product = SoftwareProduct.objects.create(name=cls.p_name)
|
||||||
|
cls.software_product_version = SoftwareProductVersion.objects.create(
|
||||||
|
name=cls.v_name, software_product=cls.software_product
|
||||||
|
)
|
||||||
|
cls.software_product_installation = SoftwareProductInstallation.objects.create(
|
||||||
|
virtualmachine=cls.vm, software_product=cls.software_product, version=cls.software_product_version
|
||||||
|
)
|
||||||
|
cls.software_license = SoftwareLicense.objects.create(
|
||||||
|
name=cls.l_name,
|
||||||
|
software_product=cls.software_product,
|
||||||
|
version=cls.software_product_version,
|
||||||
|
installation=cls.software_product_installation,
|
||||||
|
stored_location_url=cls.test_url,
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
SoftwareLicense.objects.all().delete()
|
||||||
|
SoftwareProductInstallation.objects.all().delete()
|
||||||
|
SoftwareProductVersion.objects.all().delete()
|
||||||
|
SoftwareProduct.objects.all().delete()
|
||||||
|
Cluster.objects.all().delete()
|
||||||
|
ClusterType.objects.all().delete()
|
||||||
|
VirtualMachine.objects.all().delete()
|
||||||
|
Device.objects.all().delete()
|
||||||
|
Site.objects.all().delete()
|
||||||
|
DeviceRole.objects.all().delete()
|
||||||
|
DeviceType.objects.all().delete()
|
||||||
|
Manufacturer.objects.all().delete()
|
||||||
|
super().tearDownClass()
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
|
from .base import SlmBaseTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class FunctionalTestCase(SlmBaseTestCase):
|
||||||
|
"""Functional test cases that require a client and user with permissions"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
test_user, _ = get_user_model().objects.get_or_create(username="test", is_superuser=True)
|
||||||
|
self.client.force_login(test_user)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
get_user_model().objects.all().delete()
|
||||||
|
super().tearDownClass()
|
||||||
|
|
||||||
|
def test_template_content(self):
|
||||||
|
cluster_response = self.client.get(f"/virtualization/clusters/{self.cluster.pk}/")
|
||||||
|
self.assertContains(cluster_response, f'<a href="/plugins/slm/installations/add/?cluster={self.cluster.pk}"')
|
||||||
|
self.assertContains(cluster_response, "</span> Add an installation")
|
||||||
|
|
||||||
|
device_response = self.client.get(f"/dcim/devices/{self.device.pk}/")
|
||||||
|
self.assertContains(device_response, f'<a href="/plugins/slm/installations/add/?device={self.device.pk}"')
|
||||||
|
self.assertContains(device_response, "</span> Add an installation")
|
||||||
|
|
||||||
|
vm_response = self.client.get(f"/virtualization/virtual-machines/{self.vm.pk}/")
|
||||||
|
self.assertTemplateUsed(vm_response, "netbox_slm/installations_card_include.html")
|
||||||
|
self.assertContains(vm_response, f'<a href="/plugins/slm/installations/add/?virtualmachine={self.vm.pk}"')
|
||||||
|
|
||||||
|
def test_setting_skip_content(self):
|
||||||
|
# self.settings(PLUGINS_CONFIG=dict(netbox_slm=dict(link_virtualmachine_installations=None)))
|
||||||
|
with patch.dict("netbox_slm.template_content.installations", {"virtualmachine": None}):
|
||||||
|
vm_response = self.client.get(f"/virtualization/virtual-machines/{self.vm.pk}/")
|
||||||
|
self.assertTemplateNotUsed(vm_response, "netbox_slm/installations_card_include.html")
|
||||||
|
|
||||||
|
def test_setting_left_content(self):
|
||||||
|
# self.settings(PLUGINS_CONFIG=dict(netbox_slm=dict(link_device_installations="left")))
|
||||||
|
with patch.dict("netbox_slm.template_content.installations", {"device": "left"}):
|
||||||
|
device_response = self.client.get(f"/dcim/devices/{self.device.pk}/")
|
||||||
|
self.assertContains(device_response, f'<a href="/plugins/slm/installations/add/?device={self.device.pk}"')
|
||||||
|
self.assertContains(device_response, "</span> Add an installation")
|
||||||
|
|
||||||
|
def test_setting_full_content(self):
|
||||||
|
# self.settings(PLUGINS_CONFIG=dict(netbox_slm=dict(link_cluster_installations="full")))
|
||||||
|
with patch.dict("netbox_slm.template_content.installations", {"cluster": "full"}):
|
||||||
|
cluster_response = self.client.get(f"/virtualization/clusters/{self.cluster.pk}/")
|
||||||
|
self.assertContains(cluster_response, f'<a href="/plugins/slm/installations/add/?cluster={self.cluster.pk}"')
|
||||||
|
self.assertContains(cluster_response, "</span> Add an installation")
|
||||||
@@ -1,40 +1,8 @@
|
|||||||
from django.test import TestCase
|
from .base import SlmBaseTestCase
|
||||||
|
|
||||||
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
|
|
||||||
from virtualization.models import Cluster, ClusterType, VirtualMachine
|
|
||||||
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense
|
|
||||||
|
|
||||||
|
|
||||||
class ModelTestCase(TestCase):
|
class ModelTestCase(SlmBaseTestCase):
|
||||||
def setUp(self):
|
"""Test basic model functionality and custom overrides"""
|
||||||
self.p_name = "test product"
|
|
||||||
self.v_name = "test version"
|
|
||||||
self.l_name = "test license"
|
|
||||||
|
|
||||||
manufacturer = Manufacturer.objects.create(name="test manufacturer")
|
|
||||||
device_type = DeviceType.objects.create(model="test device type", manufacturer=manufacturer)
|
|
||||||
device_role = DeviceRole.objects.create(name="test device role")
|
|
||||||
site = Site.objects.create(name="test site")
|
|
||||||
self.device = Device.objects.create(name="test device", device_type=device_type, role=device_role, site=site)
|
|
||||||
self.vm = VirtualMachine.objects.create(name="test VM")
|
|
||||||
cluster_type = ClusterType.objects.create(name="test cluster type")
|
|
||||||
self.cluster = Cluster.objects.create(name="test cluster", type=cluster_type)
|
|
||||||
self.test_url = "https://github.com/ICTU/netbox_slm"
|
|
||||||
|
|
||||||
self.software_product = SoftwareProduct.objects.create(name=self.p_name)
|
|
||||||
self.software_product_version = SoftwareProductVersion.objects.create(
|
|
||||||
name=self.v_name, software_product=self.software_product
|
|
||||||
)
|
|
||||||
self.software_product_installation = SoftwareProductInstallation.objects.create(
|
|
||||||
virtualmachine=self.vm, software_product=self.software_product, version=self.software_product_version
|
|
||||||
)
|
|
||||||
self.software_license = SoftwareLicense.objects.create(
|
|
||||||
name=self.l_name,
|
|
||||||
software_product=self.software_product,
|
|
||||||
version=self.software_product_version,
|
|
||||||
installation=self.software_product_installation,
|
|
||||||
stored_location_url=self.test_url,
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_model_name(self):
|
def test_model_name(self):
|
||||||
self.assertEqual(self.p_name, str(self.software_product))
|
self.assertEqual(self.p_name, str(self.software_product))
|
||||||
@@ -43,15 +11,28 @@ class ModelTestCase(TestCase):
|
|||||||
self.assertEqual(self.l_name, str(self.software_license))
|
self.assertEqual(self.l_name, str(self.software_license))
|
||||||
|
|
||||||
def test_absolute_url(self):
|
def test_absolute_url(self):
|
||||||
self.assertEqual("/plugins/slm/software-products/1/", self.software_product.get_absolute_url())
|
self.assertEqual(
|
||||||
self.assertEqual("/plugins/slm/versions/1/", self.software_product_version.get_absolute_url())
|
f"/plugins/slm/software-products/{self.software_product.pk}/", self.software_product.get_absolute_url()
|
||||||
self.assertEqual("/plugins/slm/installations/1/", self.software_product_installation.get_absolute_url())
|
)
|
||||||
self.assertEqual("/plugins/slm/licenses/1/", self.software_license.get_absolute_url())
|
self.assertEqual(
|
||||||
|
f"/plugins/slm/versions/{self.software_product_version.pk}/",
|
||||||
|
self.software_product_version.get_absolute_url(),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
f"/plugins/slm/installations/{self.software_product_installation.pk}/",
|
||||||
|
self.software_product_installation.get_absolute_url(),
|
||||||
|
)
|
||||||
|
self.assertEqual(f"/plugins/slm/licenses/{self.software_license.pk}/", self.software_license.get_absolute_url())
|
||||||
|
|
||||||
def test_get_installation_count(self):
|
def test_get_installation_count(self):
|
||||||
installation_ss = '<a href="/plugins/slm/installations/?q={}">1</a>'
|
self.assertEqual(
|
||||||
self.assertEqual(installation_ss.format(self.p_name), self.software_product.get_installation_count())
|
f"<a href='/plugins/slm/installations/?software_product_id={self.software_product.pk}'>1</a>",
|
||||||
self.assertEqual(installation_ss.format(self.v_name), self.software_product_version.get_installation_count())
|
self.software_product.get_installation_count(),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
f"<a href='/plugins/slm/installations/?version_id={self.software_product_version.pk}'>1</a>",
|
||||||
|
self.software_product_version.get_installation_count(),
|
||||||
|
)
|
||||||
|
|
||||||
def test_product_installation_methods(self):
|
def test_product_installation_methods(self):
|
||||||
self.assertEqual("virtualmachine", self.software_product_installation.render_type())
|
self.assertEqual("virtualmachine", self.software_product_installation.render_type())
|
||||||
|
|||||||
+11
-110
@@ -1,114 +1,15 @@
|
|||||||
from django.urls import path
|
from django.urls import path, include
|
||||||
|
|
||||||
from netbox.views.generic import ObjectChangeLogView
|
from netbox_slm import views # noqa F401
|
||||||
from netbox_slm import views
|
from utilities.urls import get_model_urls
|
||||||
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation, SoftwareLicense
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# SoftwareProduct
|
path("software-products/", include(get_model_urls("netbox_slm", "softwareproduct", detail=False))),
|
||||||
path("software-products/", views.SoftwareProductListView.as_view(), name="softwareproduct_list"),
|
path("software-products/<int:pk>/", include(get_model_urls("netbox_slm", "softwareproduct"))),
|
||||||
path("software-products/add/", views.SoftwareProductEditView.as_view(), name="softwareproduct_add"),
|
path("versions/", include(get_model_urls("netbox_slm", "softwareproductversion", detail=False))),
|
||||||
path("software-products/import/", views.SoftwareProductBulkImportView.as_view(), name="softwareproduct_import"),
|
path("versions/<int:pk>/", include(get_model_urls("netbox_slm", "softwareproductversion"))),
|
||||||
path("software-products/edit/", views.SoftwareProductBulkEditView.as_view(), name="softwareproduct_bulk_edit"),
|
path("installations/", include(get_model_urls("netbox_slm", "softwareproductinstallation", detail=False))),
|
||||||
path(
|
path("installations/<int:pk>/", include(get_model_urls("netbox_slm", "softwareproductinstallation"))),
|
||||||
"software-products/delete/", views.SoftwareProductBulkDeleteView.as_view(), name="softwareproduct_bulk_delete"
|
path("licenses/", include(get_model_urls("netbox_slm", "softwarelicense", detail=False))),
|
||||||
),
|
path("licenses/<int:pk>/", include(get_model_urls("netbox_slm", "softwarelicense"))),
|
||||||
path("software-products/<int:pk>/", views.SoftwareProductView.as_view(), name="softwareproduct"),
|
|
||||||
path(
|
|
||||||
"software-products/<int:pk>/delete/", views.SoftwareProductDeleteView.as_view(), name="softwareproduct_delete"
|
|
||||||
),
|
|
||||||
path("software-products/<int:pk>/edit/", views.SoftwareProductEditView.as_view(), name="softwareproduct_edit"),
|
|
||||||
path(
|
|
||||||
"software-products/<int:pk>/changelog/",
|
|
||||||
ObjectChangeLogView.as_view(),
|
|
||||||
name="softwareproduct_changelog",
|
|
||||||
kwargs={"model": SoftwareProduct},
|
|
||||||
),
|
|
||||||
# SoftwareProductVersion
|
|
||||||
path("versions/", views.SoftwareProductVersionListView.as_view(), name="softwareproductversion_list"),
|
|
||||||
path("versions/add/", views.SoftwareProductVersionEditView.as_view(), name="softwareproductversion_add"),
|
|
||||||
path(
|
|
||||||
"versions/import/", views.SoftwareProductVersionBulkImportView.as_view(), name="softwareproductversion_import"
|
|
||||||
),
|
|
||||||
path("versions/edit/", views.SoftwareProductVersionBulkEditView.as_view(), name="softwareproductversion_bulk_edit"),
|
|
||||||
path(
|
|
||||||
"versions/delete/",
|
|
||||||
views.SoftwareProductVersionBulkDeleteView.as_view(),
|
|
||||||
name="softwareproductversion_bulk_delete",
|
|
||||||
),
|
|
||||||
path("versions/<int:pk>/", views.SoftwareProductVersionView.as_view(), name="softwareproductversion"),
|
|
||||||
path(
|
|
||||||
"versions/<int:pk>/delete/",
|
|
||||||
views.SoftwareProductVersionDeleteView.as_view(),
|
|
||||||
name="softwareproductversion_delete",
|
|
||||||
),
|
|
||||||
path("versions/<int:pk>/edit/", views.SoftwareProductVersionEditView.as_view(), name="softwareproductversion_edit"),
|
|
||||||
path(
|
|
||||||
"versions/<int:pk>/changelog/",
|
|
||||||
ObjectChangeLogView.as_view(),
|
|
||||||
name="softwareproductversion_changelog",
|
|
||||||
kwargs={"model": SoftwareProductVersion},
|
|
||||||
),
|
|
||||||
# SoftwareProductInstallation
|
|
||||||
path(
|
|
||||||
"installations/",
|
|
||||||
views.SoftwareProductInstallationListView.as_view(),
|
|
||||||
name="softwareproductinstallation_list",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"installations/add/",
|
|
||||||
views.SoftwareProductInstallationEditView.as_view(),
|
|
||||||
name="softwareproductinstallation_add",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"installations/import/",
|
|
||||||
views.SoftwareProductInstallationBulkImportView.as_view(),
|
|
||||||
name="softwareproductinstallation_import",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"installations/edit/",
|
|
||||||
views.SoftwareProductInstallationBulkEditView.as_view(),
|
|
||||||
name="softwareproductinstallation_bulk_edit",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"installations/delete/",
|
|
||||||
views.SoftwareProductInstallationBulkDeleteView.as_view(),
|
|
||||||
name="softwareproductinstallation_bulk_delete",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"installations/<int:pk>/",
|
|
||||||
views.SoftwareProductInstallationView.as_view(),
|
|
||||||
name="softwareproductinstallation",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"installations/<int:pk>/delete/",
|
|
||||||
views.SoftwareProductInstallationDeleteView.as_view(),
|
|
||||||
name="softwareproductinstallation_delete",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"installations/<int:pk>/edit/",
|
|
||||||
views.SoftwareProductInstallationEditView.as_view(),
|
|
||||||
name="softwareproductinstallation_edit",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"installations/<int:pk>/changelog/",
|
|
||||||
ObjectChangeLogView.as_view(),
|
|
||||||
name="softwareproductinstallation_changelog",
|
|
||||||
kwargs={"model": SoftwareProductInstallation},
|
|
||||||
),
|
|
||||||
# SoftwareLicense
|
|
||||||
path("licenses/", views.SoftwareLicenseListView.as_view(), name="softwarelicense_list"),
|
|
||||||
path("licenses/add/", views.SoftwareLicenseEditView.as_view(), name="softwarelicense_add"),
|
|
||||||
path("licenses/import/", views.SoftwareLicenseBulkImportView.as_view(), name="softwarelicense_import"),
|
|
||||||
path("licenses/edit/", views.SoftwareLicenseBulkEditView.as_view(), name="softwarelicense_bulk_edit"),
|
|
||||||
path("licenses/delete/", views.SoftwareLicenseBulkDeleteView.as_view(), name="softwarelicense_bulk_delete"),
|
|
||||||
path("licenses/<int:pk>/", views.SoftwareLicenseView.as_view(), name="softwarelicense"),
|
|
||||||
path("licenses/<int:pk>/delete/", views.SoftwareLicenseDeleteView.as_view(), name="softwarelicense_delete"),
|
|
||||||
path("licenses/<int:pk>/edit/", views.SoftwareLicenseEditView.as_view(), name="softwarelicense_edit"),
|
|
||||||
path(
|
|
||||||
"licenses/<int:pk>/changelog/",
|
|
||||||
ObjectChangeLogView.as_view(),
|
|
||||||
name="softwarelicense_changelog",
|
|
||||||
kwargs={"model": SoftwareLicense},
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -3,34 +3,30 @@ from .software_license import (
|
|||||||
SoftwareLicenseView,
|
SoftwareLicenseView,
|
||||||
SoftwareLicenseEditView,
|
SoftwareLicenseEditView,
|
||||||
SoftwareLicenseDeleteView,
|
SoftwareLicenseDeleteView,
|
||||||
SoftwareLicenseBulkImportView,
|
|
||||||
SoftwareLicenseBulkEditView,
|
|
||||||
SoftwareLicenseBulkDeleteView,
|
SoftwareLicenseBulkDeleteView,
|
||||||
|
SoftwareLicenseBulkImportView,
|
||||||
)
|
)
|
||||||
from .software_product import (
|
from .software_product import (
|
||||||
SoftwareProductListView,
|
SoftwareProductListView,
|
||||||
SoftwareProductView,
|
SoftwareProductView,
|
||||||
SoftwareProductEditView,
|
SoftwareProductEditView,
|
||||||
SoftwareProductDeleteView,
|
SoftwareProductDeleteView,
|
||||||
SoftwareProductBulkImportView,
|
|
||||||
SoftwareProductBulkEditView,
|
|
||||||
SoftwareProductBulkDeleteView,
|
SoftwareProductBulkDeleteView,
|
||||||
|
SoftwareProductBulkImportView,
|
||||||
)
|
)
|
||||||
from .software_product_installation import (
|
from .software_product_installation import (
|
||||||
SoftwareProductInstallationListView,
|
SoftwareProductInstallationListView,
|
||||||
SoftwareProductInstallationView,
|
SoftwareProductInstallationView,
|
||||||
SoftwareProductInstallationEditView,
|
SoftwareProductInstallationEditView,
|
||||||
SoftwareProductInstallationDeleteView,
|
SoftwareProductInstallationDeleteView,
|
||||||
SoftwareProductInstallationBulkImportView,
|
|
||||||
SoftwareProductInstallationBulkEditView,
|
|
||||||
SoftwareProductInstallationBulkDeleteView,
|
SoftwareProductInstallationBulkDeleteView,
|
||||||
|
SoftwareProductInstallationBulkImportView,
|
||||||
)
|
)
|
||||||
from .software_product_version import (
|
from .software_product_version import (
|
||||||
SoftwareProductVersionListView,
|
SoftwareProductVersionListView,
|
||||||
SoftwareProductVersionView,
|
SoftwareProductVersionView,
|
||||||
SoftwareProductVersionEditView,
|
SoftwareProductVersionEditView,
|
||||||
SoftwareProductVersionDeleteView,
|
SoftwareProductVersionDeleteView,
|
||||||
SoftwareProductVersionBulkImportView,
|
|
||||||
SoftwareProductVersionBulkEditView,
|
|
||||||
SoftwareProductVersionBulkDeleteView,
|
SoftwareProductVersionBulkDeleteView,
|
||||||
|
SoftwareProductVersionBulkImportView,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,42 +1,41 @@
|
|||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from netbox_slm import filtersets, forms, tables
|
from netbox_slm import filtersets, forms, tables
|
||||||
from netbox_slm.models import SoftwareLicense
|
from netbox_slm.models import SoftwareLicense
|
||||||
|
from utilities.views import register_model_view
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(SoftwareLicense, "list", path="", detail=False)
|
||||||
class SoftwareLicenseListView(generic.ObjectListView):
|
class SoftwareLicenseListView(generic.ObjectListView):
|
||||||
"""View for listing all existing SoftwareLicenses."""
|
|
||||||
|
|
||||||
queryset = SoftwareLicense.objects.all()
|
queryset = SoftwareLicense.objects.all()
|
||||||
filterset = filtersets.SoftwareLicenseFilterSet
|
filterset = filtersets.SoftwareLicenseFilterSet
|
||||||
filterset_form = forms.SoftwareLicenseFilterForm
|
filterset_form = forms.SoftwareLicenseFilterForm
|
||||||
table = tables.SoftwareLicenseTable
|
table = tables.SoftwareLicenseTable
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(SoftwareLicense)
|
||||||
class SoftwareLicenseView(generic.ObjectView):
|
class SoftwareLicenseView(generic.ObjectView):
|
||||||
"""Display SoftwareLicense details"""
|
|
||||||
|
|
||||||
queryset = SoftwareLicense.objects.all()
|
queryset = SoftwareLicense.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(SoftwareLicense, "add", detail=False)
|
||||||
|
@register_model_view(SoftwareLicense, "edit")
|
||||||
class SoftwareLicenseEditView(generic.ObjectEditView):
|
class SoftwareLicenseEditView(generic.ObjectEditView):
|
||||||
"""View for editing and creating a SoftwareLicense instance."""
|
|
||||||
|
|
||||||
queryset = SoftwareLicense.objects.all()
|
queryset = SoftwareLicense.objects.all()
|
||||||
form = forms.SoftwareLicenseForm
|
form = forms.SoftwareLicenseForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(SoftwareLicense, "delete")
|
||||||
class SoftwareLicenseDeleteView(generic.ObjectDeleteView):
|
class SoftwareLicenseDeleteView(generic.ObjectDeleteView):
|
||||||
"""View for deleting a SoftwareLicense instance"""
|
|
||||||
|
|
||||||
queryset = SoftwareLicense.objects.all()
|
queryset = SoftwareLicense.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(SoftwareLicense, "bulk_import", detail=False)
|
||||||
class SoftwareLicenseBulkImportView(generic.BulkImportView):
|
class SoftwareLicenseBulkImportView(generic.BulkImportView):
|
||||||
queryset = SoftwareLicense.objects.all()
|
queryset = SoftwareLicense.objects.all()
|
||||||
model_form = forms.SoftwareLicenseImportForm
|
model_form = forms.SoftwareLicenseBulkImportForm
|
||||||
table = tables.SoftwareLicenseTable
|
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(SoftwareLicense, "bulk_edit", path="edit", detail=False)
|
||||||
class SoftwareLicenseBulkEditView(generic.BulkEditView):
|
class SoftwareLicenseBulkEditView(generic.BulkEditView):
|
||||||
queryset = SoftwareLicense.objects.all()
|
queryset = SoftwareLicense.objects.all()
|
||||||
filterset = filtersets.SoftwareLicenseFilterSet
|
filterset = filtersets.SoftwareLicenseFilterSet
|
||||||
@@ -44,6 +43,8 @@ class SoftwareLicenseBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.SoftwareLicenseBulkEditForm
|
form = forms.SoftwareLicenseBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(SoftwareLicense, "bulk_delete", path="delete", detail=False)
|
||||||
class SoftwareLicenseBulkDeleteView(generic.BulkDeleteView):
|
class SoftwareLicenseBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = SoftwareLicense.objects.all()
|
queryset = SoftwareLicense.objects.all()
|
||||||
|
filterset = filtersets.SoftwareLicenseFilterSet
|
||||||
table = tables.SoftwareLicenseTable
|
table = tables.SoftwareLicenseTable
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from netbox_slm import filtersets, forms, tables
|
from netbox_slm import filtersets, forms, tables
|
||||||
from netbox_slm.models import SoftwareProduct
|
from netbox_slm.models import SoftwareProduct
|
||||||
|
from utilities.views import register_model_view
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(SoftwareProduct, "list", path="", detail=False)
|
||||||
class SoftwareProductListView(generic.ObjectListView):
|
class SoftwareProductListView(generic.ObjectListView):
|
||||||
"""View for listing all existing SoftwareProducts."""
|
|
||||||
|
|
||||||
queryset = SoftwareProduct.objects.all()
|
queryset = SoftwareProduct.objects.all()
|
||||||
filterset = filtersets.SoftwareProductFilterSet
|
filterset = filtersets.SoftwareProductFilterSet
|
||||||
filterset_form = forms.SoftwareProductFilterForm
|
filterset_form = forms.SoftwareProductFilterForm
|
||||||
table = tables.SoftwareProductTable
|
table = tables.SoftwareProductTable
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(SoftwareProduct)
|
||||||
class SoftwareProductView(generic.ObjectView):
|
class SoftwareProductView(generic.ObjectView):
|
||||||
"""Display SoftwareProduct details"""
|
|
||||||
|
|
||||||
queryset = SoftwareProduct.objects.all()
|
queryset = SoftwareProduct.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
@@ -22,33 +21,34 @@ class SoftwareProductView(generic.ObjectView):
|
|||||||
return {"versions": versions}
|
return {"versions": versions}
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(SoftwareProduct, "add", detail=False)
|
||||||
|
@register_model_view(SoftwareProduct, "edit")
|
||||||
class SoftwareProductEditView(generic.ObjectEditView):
|
class SoftwareProductEditView(generic.ObjectEditView):
|
||||||
"""View for editing and creating a SoftwareProduct instance."""
|
|
||||||
|
|
||||||
queryset = SoftwareProduct.objects.all()
|
queryset = SoftwareProduct.objects.all()
|
||||||
form = forms.SoftwareProductForm
|
form = forms.SoftwareProductForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(SoftwareProduct, "delete")
|
||||||
class SoftwareProductDeleteView(generic.ObjectDeleteView):
|
class SoftwareProductDeleteView(generic.ObjectDeleteView):
|
||||||
"""View for deleting a SoftwareProduct instance"""
|
|
||||||
|
|
||||||
queryset = SoftwareProduct.objects.all()
|
queryset = SoftwareProduct.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(SoftwareProduct, "bulk_import", detail=False)
|
||||||
class SoftwareProductBulkImportView(generic.BulkImportView):
|
class SoftwareProductBulkImportView(generic.BulkImportView):
|
||||||
queryset = SoftwareProduct.objects.all()
|
queryset = SoftwareProduct.objects.all()
|
||||||
model_form = forms.SoftwareProductImportForm
|
model_form = forms.SoftwareProductBulkImportForm
|
||||||
table = tables.SoftwareProductTable
|
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(SoftwareProduct, "bulk_edit", path="edit", detail=False)
|
||||||
class SoftwareProductBulkEditView(generic.BulkEditView):
|
class SoftwareProductBulkEditView(generic.BulkEditView):
|
||||||
queryset = SoftwareProduct.objects.all()
|
queryset = SoftwareProduct.objects.all()
|
||||||
filterset = filtersets.SoftwareProductFilterSet
|
filterset = filtersets.SoftwareProductFilterSet
|
||||||
filterset_form = forms.SoftwareProductFilterForm
|
|
||||||
table = tables.SoftwareProductTable
|
table = tables.SoftwareProductTable
|
||||||
form = forms.SoftwareProductBulkEditForm
|
form = forms.SoftwareProductBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(SoftwareProduct, "bulk_delete", path="delete", detail=False)
|
||||||
class SoftwareProductBulkDeleteView(generic.BulkDeleteView):
|
class SoftwareProductBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = SoftwareProduct.objects.all()
|
queryset = SoftwareProduct.objects.all()
|
||||||
|
filterset = filtersets.SoftwareProductFilterSet
|
||||||
table = tables.SoftwareProductTable
|
table = tables.SoftwareProductTable
|
||||||
|
|||||||
@@ -1,42 +1,41 @@
|
|||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from netbox_slm import filtersets, forms, tables
|
from netbox_slm import filtersets, forms, tables
|
||||||
from netbox_slm.models import SoftwareProductInstallation
|
from netbox_slm.models import SoftwareProductInstallation
|
||||||
|
from utilities.views import register_model_view
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(SoftwareProductInstallation, "list", path="", detail=False)
|
||||||
class SoftwareProductInstallationListView(generic.ObjectListView):
|
class SoftwareProductInstallationListView(generic.ObjectListView):
|
||||||
"""View for listing all existing SoftwareProductInstallations."""
|
|
||||||
|
|
||||||
queryset = SoftwareProductInstallation.objects.all()
|
queryset = SoftwareProductInstallation.objects.all()
|
||||||
filterset = filtersets.SoftwareProductInstallationFilterSet
|
filterset = filtersets.SoftwareProductInstallationFilterSet
|
||||||
filterset_form = forms.SoftwareProductInstallationFilterForm
|
filterset_form = forms.SoftwareProductInstallationFilterForm
|
||||||
table = tables.SoftwareProductInstallationTable
|
table = tables.SoftwareProductInstallationTable
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(SoftwareProductInstallation)
|
||||||
class SoftwareProductInstallationView(generic.ObjectView):
|
class SoftwareProductInstallationView(generic.ObjectView):
|
||||||
"""Display SoftwareProductInstallation details"""
|
|
||||||
|
|
||||||
queryset = SoftwareProductInstallation.objects.all()
|
queryset = SoftwareProductInstallation.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(SoftwareProductInstallation, "add", detail=False)
|
||||||
|
@register_model_view(SoftwareProductInstallation, "edit")
|
||||||
class SoftwareProductInstallationEditView(generic.ObjectEditView):
|
class SoftwareProductInstallationEditView(generic.ObjectEditView):
|
||||||
"""View for editing and creating a SoftwareProductInstallation instance."""
|
|
||||||
|
|
||||||
queryset = SoftwareProductInstallation.objects.all()
|
queryset = SoftwareProductInstallation.objects.all()
|
||||||
form = forms.SoftwareProductInstallationForm
|
form = forms.SoftwareProductInstallationForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(SoftwareProductInstallation, "delete")
|
||||||
class SoftwareProductInstallationDeleteView(generic.ObjectDeleteView):
|
class SoftwareProductInstallationDeleteView(generic.ObjectDeleteView):
|
||||||
"""View for deleting a SoftwareProductInstallation instance"""
|
|
||||||
|
|
||||||
queryset = SoftwareProductInstallation.objects.all()
|
queryset = SoftwareProductInstallation.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(SoftwareProductInstallation, "bulk_import", detail=False)
|
||||||
class SoftwareProductInstallationBulkImportView(generic.BulkImportView):
|
class SoftwareProductInstallationBulkImportView(generic.BulkImportView):
|
||||||
queryset = SoftwareProductInstallation.objects.all()
|
queryset = SoftwareProductInstallation.objects.all()
|
||||||
model_form = forms.SoftwareProductInstallationImportForm
|
model_form = forms.SoftwareProductInstallationBulkImportForm
|
||||||
table = tables.SoftwareProductInstallationTable
|
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(SoftwareProductInstallation, "bulk_edit", path="edit", detail=False)
|
||||||
class SoftwareProductInstallationBulkEditView(generic.BulkEditView):
|
class SoftwareProductInstallationBulkEditView(generic.BulkEditView):
|
||||||
queryset = SoftwareProductInstallation.objects.all()
|
queryset = SoftwareProductInstallation.objects.all()
|
||||||
filterset = filtersets.SoftwareProductInstallationFilterSet
|
filterset = filtersets.SoftwareProductInstallationFilterSet
|
||||||
@@ -44,6 +43,8 @@ class SoftwareProductInstallationBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.SoftwareProductInstallationBulkEditForm
|
form = forms.SoftwareProductInstallationBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(SoftwareProductInstallation, "bulk_delete", path="delete", detail=False)
|
||||||
class SoftwareProductInstallationBulkDeleteView(generic.BulkDeleteView):
|
class SoftwareProductInstallationBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = SoftwareProductInstallation.objects.all()
|
queryset = SoftwareProductInstallation.objects.all()
|
||||||
|
filterset = filtersets.SoftwareProductInstallationFilterSet
|
||||||
table = tables.SoftwareProductInstallationTable
|
table = tables.SoftwareProductInstallationTable
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from netbox_slm import filtersets, forms, tables
|
from netbox_slm import filtersets, forms, tables
|
||||||
from netbox_slm.models import SoftwareProductVersion
|
from netbox_slm.models import SoftwareProductVersion
|
||||||
|
from utilities.views import register_model_view
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(SoftwareProductVersion, "list", path="", detail=False)
|
||||||
class SoftwareProductVersionListView(generic.ObjectListView):
|
class SoftwareProductVersionListView(generic.ObjectListView):
|
||||||
"""View for listing all existing SoftwareProductVersions."""
|
|
||||||
|
|
||||||
queryset = SoftwareProductVersion.objects.all()
|
queryset = SoftwareProductVersion.objects.all()
|
||||||
filterset = filtersets.SoftwareProductVersionFilterSet
|
filterset = filtersets.SoftwareProductVersionFilterSet
|
||||||
filterset_form = forms.SoftwareProductVersionFilterForm
|
filterset_form = forms.SoftwareProductVersionFilterForm
|
||||||
table = tables.SoftwareProductVersionTable
|
table = tables.SoftwareProductVersionTable
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(SoftwareProductVersion)
|
||||||
class SoftwareProductVersionView(generic.ObjectView):
|
class SoftwareProductVersionView(generic.ObjectView):
|
||||||
"""Display SoftwareProductVersion details"""
|
|
||||||
|
|
||||||
queryset = SoftwareProductVersion.objects.all()
|
queryset = SoftwareProductVersion.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
@@ -22,25 +21,25 @@ class SoftwareProductVersionView(generic.ObjectView):
|
|||||||
return {"installations": installation_count}
|
return {"installations": installation_count}
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(SoftwareProductVersion, "add", detail=False)
|
||||||
|
@register_model_view(SoftwareProductVersion, "edit")
|
||||||
class SoftwareProductVersionEditView(generic.ObjectEditView):
|
class SoftwareProductVersionEditView(generic.ObjectEditView):
|
||||||
"""View for editing and creating a SoftwareProductVersion instance."""
|
|
||||||
|
|
||||||
queryset = SoftwareProductVersion.objects.all()
|
queryset = SoftwareProductVersion.objects.all()
|
||||||
form = forms.SoftwareProductVersionForm
|
form = forms.SoftwareProductVersionForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(SoftwareProductVersion, "delete")
|
||||||
class SoftwareProductVersionDeleteView(generic.ObjectDeleteView):
|
class SoftwareProductVersionDeleteView(generic.ObjectDeleteView):
|
||||||
"""View for deleting a SoftwareProductVersion instance"""
|
|
||||||
|
|
||||||
queryset = SoftwareProductVersion.objects.all()
|
queryset = SoftwareProductVersion.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(SoftwareProductVersion, "bulk_import", detail=False)
|
||||||
class SoftwareProductVersionBulkImportView(generic.BulkImportView):
|
class SoftwareProductVersionBulkImportView(generic.BulkImportView):
|
||||||
queryset = SoftwareProductVersion.objects.all()
|
queryset = SoftwareProductVersion.objects.all()
|
||||||
model_form = forms.SoftwareProductVersionImportForm
|
model_form = forms.SoftwareProductVersionBulkImportForm
|
||||||
table = tables.SoftwareProductVersionTable
|
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(SoftwareProductVersion, "bulk_edit", path="edit", detail=False)
|
||||||
class SoftwareProductVersionBulkEditView(generic.BulkEditView):
|
class SoftwareProductVersionBulkEditView(generic.BulkEditView):
|
||||||
queryset = SoftwareProductVersion.objects.all()
|
queryset = SoftwareProductVersion.objects.all()
|
||||||
filterset = filtersets.SoftwareProductVersionFilterSet
|
filterset = filtersets.SoftwareProductVersionFilterSet
|
||||||
@@ -48,6 +47,8 @@ class SoftwareProductVersionBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.SoftwareProductVersionBulkEditForm
|
form = forms.SoftwareProductVersionBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(SoftwareProductVersion, "bulk_delete", path="delete", detail=False)
|
||||||
class SoftwareProductVersionBulkDeleteView(generic.BulkDeleteView):
|
class SoftwareProductVersionBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = SoftwareProductVersion.objects.all()
|
queryset = SoftwareProductVersion.objects.all()
|
||||||
|
filterset = filtersets.SoftwareProductVersionFilterSet
|
||||||
table = tables.SoftwareProductVersionTable
|
table = tables.SoftwareProductVersionTable
|
||||||
|
|||||||
+35
-25
@@ -7,63 +7,70 @@ name = "netbox-slm"
|
|||||||
dynamic = ["version"]
|
dynamic = ["version"]
|
||||||
description = "Software Lifecycle Management Netbox Plugin"
|
description = "Software Lifecycle Management Netbox Plugin"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.11"
|
||||||
license = { text = "Apache-2.0" }
|
license = { text = "Apache-2.0" }
|
||||||
authors = [
|
authors = [
|
||||||
{name = "ICTU", email = "open-source-projects@ictu.nl"},
|
{name = "ICTU", email = "open-source-projects@ictu.nl"},
|
||||||
]
|
]
|
||||||
classifiers = [
|
classifiers = [
|
||||||
|
"Development Status :: 5 - Production/Stable",
|
||||||
|
"Environment :: Plugins",
|
||||||
"Environment :: Web Environment",
|
"Environment :: Web Environment",
|
||||||
"Framework :: Django",
|
"Framework :: Django",
|
||||||
"Intended Audience :: Developers",
|
"Intended Audience :: Developers",
|
||||||
|
"Intended Audience :: System Administrators",
|
||||||
"License :: OSI Approved :: Apache Software License",
|
"License :: OSI Approved :: Apache Software License",
|
||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
"Programming Language :: Python",
|
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"Programming Language :: Python :: 3 :: Only",
|
"Programming Language :: Python :: 3 :: Only",
|
||||||
"Programming Language :: Python :: 3.9",
|
|
||||||
"Programming Language :: Python :: 3.10",
|
|
||||||
"Programming Language :: Python :: 3.11",
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
|
"Programming Language :: Python :: 3.13",
|
||||||
|
"Programming Language :: Python",
|
||||||
"Topic :: Internet :: WWW/HTTP",
|
"Topic :: Internet :: WWW/HTTP",
|
||||||
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
||||||
]
|
]
|
||||||
dependencies = []
|
dependencies = [
|
||||||
|
"license-expression == 30.4.1",
|
||||||
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
build = [
|
build = [
|
||||||
"build == 1.0.3",
|
"build == 1.2.2.post1",
|
||||||
"setuptools == 68.2.0",
|
"setuptools == 75.8.0",
|
||||||
"twine == 3.7.1",
|
"twine == 6.1.0",
|
||||||
]
|
]
|
||||||
tools = [
|
ci = [
|
||||||
"black == 23.3.0",
|
"coverage == 7.6.11",
|
||||||
"ruff == 0.0.275",
|
"ruff == 0.9.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Homepage = "https://github.com/ICTU/netbox_slm/"
|
||||||
|
Changelog = "https://github.com/ICTU/netbox_slm/blob/master/CHANGELOG.md"
|
||||||
|
Issues = "https://github.com/ICTU/netbox_slm/issues/"
|
||||||
|
|
||||||
[tool.setuptools.dynamic]
|
[tool.setuptools.dynamic]
|
||||||
version = {attr = "netbox_slm.__version__"}
|
version = {attr = "netbox_slm.__version__"}
|
||||||
|
|
||||||
[tool.setuptools.packages.find]
|
[tool.setuptools.packages.find]
|
||||||
include = ["netbox_slm"]
|
include = ["netbox_slm"]
|
||||||
|
|
||||||
[tool.black]
|
|
||||||
line-length = 120
|
|
||||||
target-version = ["py39"]
|
|
||||||
color = true
|
|
||||||
check = true
|
|
||||||
diff = true
|
|
||||||
fast = true
|
|
||||||
extend-exclude = '''
|
|
||||||
netbox_slm/migrations/.*.py
|
|
||||||
'''
|
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
line-length = 120
|
line-length = 120
|
||||||
target-version = "py39"
|
target-version = "py312"
|
||||||
format = "github"
|
|
||||||
src = ["netbox_slm"]
|
src = ["netbox_slm"]
|
||||||
|
|
||||||
[tool.ruff.per-file-ignores]
|
[tool.ruff.lint]
|
||||||
|
select = [
|
||||||
|
"E", # pycodestyle
|
||||||
|
"F", # Pyflakes
|
||||||
|
"UP", # pyupgrade
|
||||||
|
"B", # flake8-bugbear
|
||||||
|
"SIM", # flake8-simplify
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff.lint.per-file-ignores]
|
||||||
"__init__.py" = [
|
"__init__.py" = [
|
||||||
"D104", # https://beta.ruff.rs/docs/rules/#pydocstyle-d - don't require doc strings in __init__.py files
|
"D104", # https://beta.ruff.rs/docs/rules/#pydocstyle-d - don't require doc strings in __init__.py files
|
||||||
"F401", # https://beta.ruff.rs/docs/rules/#pyflakes-f - don't complain about unused imports in __init__.py files
|
"F401", # https://beta.ruff.rs/docs/rules/#pyflakes-f - don't complain about unused imports in __init__.py files
|
||||||
@@ -71,3 +78,6 @@ src = ["netbox_slm"]
|
|||||||
"netbox_slm/migrations/*.py" = [
|
"netbox_slm/migrations/*.py" = [
|
||||||
"E501", # https://beta.ruff.rs/docs/rules/line-too-long/ - don't check on Django generated migration files
|
"E501", # https://beta.ruff.rs/docs/rules/line-too-long/ - don't check on Django generated migration files
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[tool.ruff.format]
|
||||||
|
exclude = ["netbox_slm/migrations/*.py"]
|
||||||
|
|||||||
@@ -2,12 +2,11 @@
|
|||||||
sonar.organization=ictu
|
sonar.organization=ictu
|
||||||
sonar.projectKey=ICTU_netbox_slm
|
sonar.projectKey=ICTU_netbox_slm
|
||||||
sonar.projectName=netbox-slm
|
sonar.projectName=netbox-slm
|
||||||
sonar.projectVersion=1.4
|
sonar.projectVersion=1.8.1
|
||||||
|
|
||||||
# Path is relative to the sonar-project.properties file
|
# Path is relative to the sonar-project.properties file
|
||||||
sonar.sources=netbox_slm
|
sonar.sources=netbox_slm
|
||||||
sonar.python.version=3.9, 3.10, 3.11
|
sonar.python.version=3.11, 3.12, 3.13
|
||||||
|
|
||||||
# Unit tests
|
# Python test reports
|
||||||
sonar.python.xunit.reportPath=ci/reports/junit.xml
|
sonar.python.coverage.reportPaths=/ci/reports/coverage.xml
|
||||||
sonar.python.coverage.reportPaths=ci/reports/coverage.xml
|
|
||||||
|
|||||||
Reference in New Issue
Block a user