mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-14 01:41:22 -06:00
Merge branch 'develop' into feature
This commit is contained in:
commit
d52c18ce38
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@ -14,7 +14,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: NetBox version
|
label: NetBox version
|
||||||
description: What version of NetBox are you currently running?
|
description: What version of NetBox are you currently running?
|
||||||
placeholder: v3.5.6
|
placeholder: v3.5.7
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
|
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
@ -14,7 +14,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: NetBox version
|
label: NetBox version
|
||||||
description: What version of NetBox are you currently running?
|
description: What version of NetBox are you currently running?
|
||||||
placeholder: v3.5.6
|
placeholder: v3.5.7
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
|
@ -38,7 +38,7 @@ An example hierarchy might look like this:
|
|||||||
* 100.64.16.1/24 (address)
|
* 100.64.16.1/24 (address)
|
||||||
* 100.64.16.2/24 (address)
|
* 100.64.16.2/24 (address)
|
||||||
* 100.64.16.3/24 (address)
|
* 100.64.16.3/24 (address)
|
||||||
* 100.64.16.9/24 (prefix)
|
* 100.64.19.0/24 (prefix)
|
||||||
* 100.64.32.0/20 (prefix)
|
* 100.64.32.0/20 (prefix)
|
||||||
* 100.64.32.1/24 (address)
|
* 100.64.32.1/24 (address)
|
||||||
* 100.64.32.10-99/24 (range)
|
* 100.64.32.10-99/24 (range)
|
||||||
|
@ -55,6 +55,9 @@ Within the shell, enter the following commands to create the database and user (
|
|||||||
CREATE DATABASE netbox;
|
CREATE DATABASE netbox;
|
||||||
CREATE USER netbox WITH PASSWORD 'J5brHrAXFLQSif0K';
|
CREATE USER netbox WITH PASSWORD 'J5brHrAXFLQSif0K';
|
||||||
ALTER DATABASE netbox OWNER TO netbox;
|
ALTER DATABASE netbox OWNER TO netbox;
|
||||||
|
-- the next two commands are needed on PostgreSQL 15 and later
|
||||||
|
\connect netbox;
|
||||||
|
GRANT CREATE ON SCHEMA public TO netbox;
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! danger "Use a strong password"
|
!!! danger "Use a strong password"
|
||||||
|
@ -48,36 +48,40 @@ Download the [latest stable release](https://github.com/netbox-community/netbox/
|
|||||||
Download and extract the latest version:
|
Download and extract the latest version:
|
||||||
|
|
||||||
```no-highlight
|
```no-highlight
|
||||||
wget https://github.com/netbox-community/netbox/archive/vX.Y.Z.tar.gz
|
# Set $NEWVER to the NetBox version being installed
|
||||||
sudo tar -xzf vX.Y.Z.tar.gz -C /opt
|
NEWVER=3.5.0
|
||||||
sudo ln -sfn /opt/netbox-X.Y.Z/ /opt/netbox
|
wget https://github.com/netbox-community/netbox/archive/v$NEWVER.tar.gz
|
||||||
|
sudo tar -xzf v$NEWVER.tar.gz -C /opt
|
||||||
|
sudo ln -sfn /opt/netbox-$NEWVER/ /opt/netbox
|
||||||
```
|
```
|
||||||
|
|
||||||
Copy `local_requirements.txt`, `configuration.py`, and `ldap_config.py` (if present) from the current installation to the new version:
|
Copy `local_requirements.txt`, `configuration.py`, and `ldap_config.py` (if present) from the current installation to the new version:
|
||||||
|
|
||||||
```no-highlight
|
```no-highlight
|
||||||
sudo cp /opt/netbox-X.Y.Z/local_requirements.txt /opt/netbox/
|
# Set $OLDVER to the NetBox version currently installed
|
||||||
sudo cp /opt/netbox-X.Y.Z/netbox/netbox/configuration.py /opt/netbox/netbox/netbox/
|
NEWVER=3.4.9
|
||||||
sudo cp /opt/netbox-X.Y.Z/netbox/netbox/ldap_config.py /opt/netbox/netbox/netbox/
|
sudo cp /opt/netbox-$OLDVER/local_requirements.txt /opt/netbox/
|
||||||
|
sudo cp /opt/netbox-$OLDVER/netbox/netbox/configuration.py /opt/netbox/netbox/netbox/
|
||||||
|
sudo cp /opt/netbox-$OLDVER/netbox/netbox/ldap_config.py /opt/netbox/netbox/netbox/
|
||||||
```
|
```
|
||||||
|
|
||||||
Be sure to replicate your uploaded media as well. (The exact action necessary will depend on where you choose to store your media, but in general moving or copying the media directory will suffice.)
|
Be sure to replicate your uploaded media as well. (The exact action necessary will depend on where you choose to store your media, but in general moving or copying the media directory will suffice.)
|
||||||
|
|
||||||
```no-highlight
|
```no-highlight
|
||||||
sudo cp -pr /opt/netbox-X.Y.Z/netbox/media/ /opt/netbox/netbox/
|
sudo cp -pr /opt/netbox-$OLDVER/netbox/media/ /opt/netbox/netbox/
|
||||||
```
|
```
|
||||||
|
|
||||||
Also make sure to copy or link any custom scripts and reports that you've made. Note that if these are stored outside the project root, you will not need to copy them. (Check the `SCRIPTS_ROOT` and `REPORTS_ROOT` parameters in the configuration file above if you're unsure.)
|
Also make sure to copy or link any custom scripts and reports that you've made. Note that if these are stored outside the project root, you will not need to copy them. (Check the `SCRIPTS_ROOT` and `REPORTS_ROOT` parameters in the configuration file above if you're unsure.)
|
||||||
|
|
||||||
```no-highlight
|
```no-highlight
|
||||||
sudo cp -r /opt/netbox-X.Y.Z/netbox/scripts /opt/netbox/netbox/
|
sudo cp -r /opt/netbox-$OLDVER/netbox/scripts /opt/netbox/netbox/
|
||||||
sudo cp -r /opt/netbox-X.Y.Z/netbox/reports /opt/netbox/netbox/
|
sudo cp -r /opt/netbox-$OLDVER/netbox/reports /opt/netbox/netbox/
|
||||||
```
|
```
|
||||||
|
|
||||||
If you followed the original installation guide to set up gunicorn, be sure to copy its configuration as well:
|
If you followed the original installation guide to set up gunicorn, be sure to copy its configuration as well:
|
||||||
|
|
||||||
```no-highlight
|
```no-highlight
|
||||||
sudo cp /opt/netbox-X.Y.Z/gunicorn.py /opt/netbox/
|
sudo cp /opt/netbox-$OLDVER/gunicorn.py /opt/netbox/
|
||||||
```
|
```
|
||||||
|
|
||||||
### Option B: Clone the Git Repository
|
### Option B: Clone the Git Repository
|
||||||
|
@ -1,6 +1,26 @@
|
|||||||
# NetBox v3.5
|
# NetBox v3.5
|
||||||
|
|
||||||
## v3.5.7 (FUTURE)
|
## v3.5.8 (FUTURE)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v3.5.7 (2023-07-28)
|
||||||
|
|
||||||
|
### Enhancements
|
||||||
|
|
||||||
|
* [#11803](https://github.com/netbox-community/netbox/issues/11803) - Move non-rack devices list to a separate tab under the rack view
|
||||||
|
* [#12625](https://github.com/netbox-community/netbox/issues/12625) - Mask sensitive parameters when viewing a configured data source
|
||||||
|
* [#13009](https://github.com/netbox-community/netbox/issues/13009) - Add IEC 10609-1 and NBR 14136 power port & outlet types
|
||||||
|
* [#13097](https://github.com/netbox-community/netbox/issues/13097) - Implement a faster initial poll for report & script results
|
||||||
|
* [#13234](https://github.com/netbox-community/netbox/issues/13234) - Add 100GBASE-X-DSFP and 100GBASE-X-SFPDD interface types
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* [#13051](https://github.com/netbox-community/netbox/issues/13051) - Fix Markdown support for table cell alignment
|
||||||
|
* [#13167](https://github.com/netbox-community/netbox/issues/13167) - Fix missing script results when fetched via REST API
|
||||||
|
* [#13233](https://github.com/netbox-community/netbox/issues/13233) - Remove extraneous VLAN group field from bulk edit form for interfaces
|
||||||
|
* [#13237](https://github.com/netbox-community/netbox/issues/13237) - Permit unauthenticated access to content types REST API endpoint when `LOGIN_REQUIRED` is false
|
||||||
|
* [#13285](https://github.com/netbox-community/netbox/issues/13285) - Fix exception when importing device type missing rack unit height value
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -41,6 +41,7 @@ def register_backend(name):
|
|||||||
|
|
||||||
class DataBackend:
|
class DataBackend:
|
||||||
parameters = {}
|
parameters = {}
|
||||||
|
sensitive_parameters = []
|
||||||
|
|
||||||
def __init__(self, url, **kwargs):
|
def __init__(self, url, **kwargs):
|
||||||
self.url = url
|
self.url = url
|
||||||
@ -86,6 +87,7 @@ class GitBackend(DataBackend):
|
|||||||
widget=forms.TextInput(attrs={'class': 'form-control'})
|
widget=forms.TextInput(attrs={'class': 'form-control'})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
sensitive_parameters = ['password']
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def fetch(self):
|
def fetch(self):
|
||||||
@ -135,6 +137,7 @@ class S3Backend(DataBackend):
|
|||||||
widget=forms.TextInput(attrs={'class': 'form-control'})
|
widget=forms.TextInput(attrs={'class': 'form-control'})
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
sensitive_parameters = ['aws_secret_access_key']
|
||||||
|
|
||||||
REGION_REGEX = r's3\.([a-z0-9-]+)\.amazonaws\.com'
|
REGION_REGEX = r's3\.([a-z0-9-]+)\.amazonaws\.com'
|
||||||
|
|
||||||
|
@ -318,6 +318,10 @@ class PowerPortTypeChoices(ChoiceSet):
|
|||||||
TYPE_IEC_3PNE4H = 'iec-60309-3p-n-e-4h'
|
TYPE_IEC_3PNE4H = 'iec-60309-3p-n-e-4h'
|
||||||
TYPE_IEC_3PNE6H = 'iec-60309-3p-n-e-6h'
|
TYPE_IEC_3PNE6H = 'iec-60309-3p-n-e-6h'
|
||||||
TYPE_IEC_3PNE9H = 'iec-60309-3p-n-e-9h'
|
TYPE_IEC_3PNE9H = 'iec-60309-3p-n-e-9h'
|
||||||
|
# IEC 60906-1
|
||||||
|
TYPE_IEC_60906_1 = 'iec-60906-1'
|
||||||
|
TYPE_NBR_14136_10A = 'nbr-14136-10a'
|
||||||
|
TYPE_NBR_14136_20A = 'nbr-14136-20a'
|
||||||
# NEMA non-locking
|
# NEMA non-locking
|
||||||
TYPE_NEMA_115P = 'nema-1-15p'
|
TYPE_NEMA_115P = 'nema-1-15p'
|
||||||
TYPE_NEMA_515P = 'nema-5-15p'
|
TYPE_NEMA_515P = 'nema-5-15p'
|
||||||
@ -429,6 +433,11 @@ class PowerPortTypeChoices(ChoiceSet):
|
|||||||
(TYPE_IEC_3PNE6H, '3P+N+E 6H'),
|
(TYPE_IEC_3PNE6H, '3P+N+E 6H'),
|
||||||
(TYPE_IEC_3PNE9H, '3P+N+E 9H'),
|
(TYPE_IEC_3PNE9H, '3P+N+E 9H'),
|
||||||
)),
|
)),
|
||||||
|
('IEC 60906-1', (
|
||||||
|
(TYPE_IEC_60906_1, 'IEC 60906-1'),
|
||||||
|
(TYPE_NBR_14136_10A, '2P+T 10A (NBR 14136)'),
|
||||||
|
(TYPE_NBR_14136_20A, '2P+T 20A (NBR 14136)'),
|
||||||
|
)),
|
||||||
('NEMA (Non-locking)', (
|
('NEMA (Non-locking)', (
|
||||||
(TYPE_NEMA_115P, 'NEMA 1-15P'),
|
(TYPE_NEMA_115P, 'NEMA 1-15P'),
|
||||||
(TYPE_NEMA_515P, 'NEMA 5-15P'),
|
(TYPE_NEMA_515P, 'NEMA 5-15P'),
|
||||||
@ -553,6 +562,10 @@ class PowerOutletTypeChoices(ChoiceSet):
|
|||||||
TYPE_IEC_3PNE4H = 'iec-60309-3p-n-e-4h'
|
TYPE_IEC_3PNE4H = 'iec-60309-3p-n-e-4h'
|
||||||
TYPE_IEC_3PNE6H = 'iec-60309-3p-n-e-6h'
|
TYPE_IEC_3PNE6H = 'iec-60309-3p-n-e-6h'
|
||||||
TYPE_IEC_3PNE9H = 'iec-60309-3p-n-e-9h'
|
TYPE_IEC_3PNE9H = 'iec-60309-3p-n-e-9h'
|
||||||
|
# IEC 60906-1
|
||||||
|
TYPE_IEC_60906_1 = 'iec-60906-1'
|
||||||
|
TYPE_NBR_14136_10A = 'nbr-14136-10a'
|
||||||
|
TYPE_NBR_14136_20A = 'nbr-14136-20a'
|
||||||
# NEMA non-locking
|
# NEMA non-locking
|
||||||
TYPE_NEMA_115R = 'nema-1-15r'
|
TYPE_NEMA_115R = 'nema-1-15r'
|
||||||
TYPE_NEMA_515R = 'nema-5-15r'
|
TYPE_NEMA_515R = 'nema-5-15r'
|
||||||
@ -657,6 +670,11 @@ class PowerOutletTypeChoices(ChoiceSet):
|
|||||||
(TYPE_IEC_3PNE6H, '3P+N+E 6H'),
|
(TYPE_IEC_3PNE6H, '3P+N+E 6H'),
|
||||||
(TYPE_IEC_3PNE9H, '3P+N+E 9H'),
|
(TYPE_IEC_3PNE9H, '3P+N+E 9H'),
|
||||||
)),
|
)),
|
||||||
|
('IEC 60906-1', (
|
||||||
|
(TYPE_IEC_60906_1, 'IEC 60906-1'),
|
||||||
|
(TYPE_NBR_14136_10A, '2P+T 10A (NBR 14136)'),
|
||||||
|
(TYPE_NBR_14136_20A, '2P+T 20A (NBR 14136)'),
|
||||||
|
)),
|
||||||
('NEMA (Non-locking)', (
|
('NEMA (Non-locking)', (
|
||||||
(TYPE_NEMA_115R, 'NEMA 1-15R'),
|
(TYPE_NEMA_115R, 'NEMA 1-15R'),
|
||||||
(TYPE_NEMA_515R, 'NEMA 5-15R'),
|
(TYPE_NEMA_515R, 'NEMA 5-15R'),
|
||||||
@ -809,6 +827,8 @@ class InterfaceTypeChoices(ChoiceSet):
|
|||||||
TYPE_100GE_CFP4 = '100gbase-x-cfp4'
|
TYPE_100GE_CFP4 = '100gbase-x-cfp4'
|
||||||
TYPE_100GE_CXP = '100gbase-x-cxp'
|
TYPE_100GE_CXP = '100gbase-x-cxp'
|
||||||
TYPE_100GE_CPAK = '100gbase-x-cpak'
|
TYPE_100GE_CPAK = '100gbase-x-cpak'
|
||||||
|
TYPE_100GE_DSFP = '100gbase-x-dsfp'
|
||||||
|
TYPE_100GE_SFP_DD = '100gbase-x-sfpdd'
|
||||||
TYPE_100GE_QSFP28 = '100gbase-x-qsfp28'
|
TYPE_100GE_QSFP28 = '100gbase-x-qsfp28'
|
||||||
TYPE_100GE_QSFP_DD = '100gbase-x-qsfpdd'
|
TYPE_100GE_QSFP_DD = '100gbase-x-qsfpdd'
|
||||||
TYPE_200GE_CFP2 = '200gbase-x-cfp2'
|
TYPE_200GE_CFP2 = '200gbase-x-cfp2'
|
||||||
@ -959,6 +979,8 @@ class InterfaceTypeChoices(ChoiceSet):
|
|||||||
(TYPE_100GE_CFP4, 'CFP4 (100GE)'),
|
(TYPE_100GE_CFP4, 'CFP4 (100GE)'),
|
||||||
(TYPE_100GE_CXP, 'CXP (100GE)'),
|
(TYPE_100GE_CXP, 'CXP (100GE)'),
|
||||||
(TYPE_100GE_CPAK, 'Cisco CPAK (100GE)'),
|
(TYPE_100GE_CPAK, 'Cisco CPAK (100GE)'),
|
||||||
|
(TYPE_100GE_DSFP, 'DSFP (100GE)'),
|
||||||
|
(TYPE_100GE_SFP_DD, 'SFP-DD (100GE)'),
|
||||||
(TYPE_100GE_QSFP28, 'QSFP28 (100GE)'),
|
(TYPE_100GE_QSFP28, 'QSFP28 (100GE)'),
|
||||||
(TYPE_100GE_QSFP_DD, 'QSFP-DD (100GE)'),
|
(TYPE_100GE_QSFP_DD, 'QSFP-DD (100GE)'),
|
||||||
(TYPE_200GE_QSFP56, 'QSFP56 (200GE)'),
|
(TYPE_200GE_QSFP56, 'QSFP56 (200GE)'),
|
||||||
|
@ -1262,8 +1262,8 @@ class InterfaceBulkEditForm(
|
|||||||
)
|
)
|
||||||
nullable_fields = (
|
nullable_fields = (
|
||||||
'module', 'label', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'mac_address', 'wwn', 'vdcs', 'mtu', 'description',
|
'module', 'label', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'mac_address', 'wwn', 'vdcs', 'mtu', 'description',
|
||||||
'poe_mode', 'poe_type', 'mode', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power',
|
'poe_mode', 'poe_type', 'mode', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan',
|
||||||
'vlan_group', 'untagged_vlan', 'tagged_vlans', 'vrf', 'wireless_lans'
|
'tagged_vlans', 'vrf', 'wireless_lans'
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
@ -275,7 +275,7 @@ class DeviceType(PrimaryModel, WeightMixin):
|
|||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
# U height must be divisible by 0.5
|
# U height must be divisible by 0.5
|
||||||
if self.u_height % decimal.Decimal(0.5):
|
if decimal.Decimal(self.u_height) % decimal.Decimal(0.5):
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'u_height': "U height must be in increments of 0.5 rack units."
|
'u_height': "U height must be in increments of 0.5 rack units."
|
||||||
})
|
})
|
||||||
|
@ -681,13 +681,6 @@ class RackView(generic.ObjectView):
|
|||||||
(PowerFeed.objects.restrict(request.user).filter(rack=instance), 'rack_id'),
|
(PowerFeed.objects.restrict(request.user).filter(rack=instance), 'rack_id'),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get 0U devices located within the rack
|
|
||||||
nonracked_devices = Device.objects.filter(
|
|
||||||
rack=instance,
|
|
||||||
position__isnull=True,
|
|
||||||
parent_bay__isnull=True
|
|
||||||
).prefetch_related('device_type__manufacturer', 'parent_bay', 'device_role')
|
|
||||||
|
|
||||||
peer_racks = Rack.objects.restrict(request.user, 'view').filter(site=instance.site)
|
peer_racks = Rack.objects.restrict(request.user, 'view').filter(site=instance.site)
|
||||||
|
|
||||||
if instance.location:
|
if instance.location:
|
||||||
@ -704,7 +697,6 @@ class RackView(generic.ObjectView):
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
'related_models': related_models,
|
'related_models': related_models,
|
||||||
'nonracked_devices': nonracked_devices,
|
|
||||||
'next_rack': next_rack,
|
'next_rack': next_rack,
|
||||||
'prev_rack': prev_rack,
|
'prev_rack': prev_rack,
|
||||||
'svg_extra': svg_extra,
|
'svg_extra': svg_extra,
|
||||||
@ -731,6 +723,26 @@ class RackRackReservationsView(generic.ObjectChildrenView):
|
|||||||
return parent.reservations.restrict(request.user, 'view')
|
return parent.reservations.restrict(request.user, 'view')
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Rack, 'nonracked_devices', 'nonracked-devices')
|
||||||
|
class RackNonRackedView(generic.ObjectChildrenView):
|
||||||
|
queryset = Rack.objects.all()
|
||||||
|
child_model = Device
|
||||||
|
table = tables.DeviceTable
|
||||||
|
filterset = filtersets.DeviceFilterSet
|
||||||
|
template_name = 'dcim/rack/non_racked_devices.html'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Non-Racked Devices'),
|
||||||
|
badge=lambda obj: obj.devices.filter(rack=obj, position__isnull=True, parent_bay__isnull=True).count(),
|
||||||
|
weight=500,
|
||||||
|
permission='dcim.view_device',
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_children(self, request, parent):
|
||||||
|
return parent.devices.restrict(request.user, 'view').filter(
|
||||||
|
rack=parent, position__isnull=True, parent_bay__isnull=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Rack, 'edit')
|
@register_model_view(Rack, 'edit')
|
||||||
class RackEditView(generic.ObjectEditView):
|
class RackEditView(generic.ObjectEditView):
|
||||||
queryset = Rack.objects.all()
|
queryset = Rack.objects.all()
|
||||||
|
@ -5,7 +5,6 @@ from rest_framework import status
|
|||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.exceptions import PermissionDenied
|
from rest_framework.exceptions import PermissionDenied
|
||||||
from rest_framework.generics import RetrieveUpdateDestroyAPIView
|
from rest_framework.generics import RetrieveUpdateDestroyAPIView
|
||||||
from rest_framework.permissions import IsAuthenticated
|
|
||||||
from rest_framework.renderers import JSONRenderer
|
from rest_framework.renderers import JSONRenderer
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.routers import APIRootView
|
from rest_framework.routers import APIRootView
|
||||||
@ -319,7 +318,7 @@ class ScriptViewSet(ViewSet):
|
|||||||
|
|
||||||
# Attach Job objects to each script (if any)
|
# Attach Job objects to each script (if any)
|
||||||
for script in script_list:
|
for script in script_list:
|
||||||
script.result = results.get(script.name, None)
|
script.result = results.get(script.class_name, None)
|
||||||
|
|
||||||
serializer = serializers.ScriptSerializer(script_list, many=True, context={'request': request})
|
serializer = serializers.ScriptSerializer(script_list, many=True, context={'request': request})
|
||||||
|
|
||||||
@ -330,7 +329,7 @@ class ScriptViewSet(ViewSet):
|
|||||||
object_type = ContentType.objects.get(app_label='extras', model='scriptmodule')
|
object_type = ContentType.objects.get(app_label='extras', model='scriptmodule')
|
||||||
script.result = Job.objects.filter(
|
script.result = Job.objects.filter(
|
||||||
object_type=object_type,
|
object_type=object_type,
|
||||||
name=script.name,
|
name=script.class_name,
|
||||||
status__in=JobStatusChoices.TERMINAL_STATE_CHOICES
|
status__in=JobStatusChoices.TERMINAL_STATE_CHOICES
|
||||||
).first()
|
).first()
|
||||||
serializer = serializers.ScriptDetailSerializer(script, context={'request': request})
|
serializer = serializers.ScriptDetailSerializer(script, context={'request': request})
|
||||||
@ -397,7 +396,7 @@ class ContentTypeViewSet(ReadOnlyModelViewSet):
|
|||||||
"""
|
"""
|
||||||
Read-only list of ContentTypes. Limit results to ContentTypes pertinent to NetBox objects.
|
Read-only list of ContentTypes. Limit results to ContentTypes pertinent to NetBox objects.
|
||||||
"""
|
"""
|
||||||
permission_classes = (IsAuthenticated,)
|
permission_classes = [IsAuthenticatedOrLoginNotRequired]
|
||||||
queryset = ContentType.objects.order_by('app_label', 'model')
|
queryset = ContentType.objects.order_by('app_label', 'model')
|
||||||
serializer_class = serializers.ContentTypeSerializer
|
serializer_class = serializers.ContentTypeSerializer
|
||||||
filterset_class = filtersets.ContentTypeFilterSet
|
filterset_class = filtersets.ContentTypeFilterSet
|
||||||
|
@ -47,7 +47,7 @@ ORGANIZATION_MENU = Menu(
|
|||||||
get_model_item('tenancy', 'contact', _('Contacts')),
|
get_model_item('tenancy', 'contact', _('Contacts')),
|
||||||
get_model_item('tenancy', 'contactgroup', _('Contact Groups')),
|
get_model_item('tenancy', 'contactgroup', _('Contact Groups')),
|
||||||
get_model_item('tenancy', 'contactrole', _('Contact Roles')),
|
get_model_item('tenancy', 'contactrole', _('Contact Roles')),
|
||||||
get_model_item('tenancy', 'contactassignment', _('Contact Assignments'), actions=[]),
|
get_model_item('tenancy', 'contactassignment', _('Contact Assignments'), actions=['import']),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -25,7 +25,7 @@ from netbox.constants import RQ_QUEUE_DEFAULT, RQ_QUEUE_HIGH, RQ_QUEUE_LOW
|
|||||||
# Environment setup
|
# Environment setup
|
||||||
#
|
#
|
||||||
|
|
||||||
VERSION = '3.5.7-dev'
|
VERSION = '3.5.8-dev'
|
||||||
|
|
||||||
# Hostname
|
# Hostname
|
||||||
HOSTNAME = platform.node()
|
HOSTNAME = platform.node()
|
||||||
|
BIN
netbox/project-static/dist/netbox-dark.css
vendored
BIN
netbox/project-static/dist/netbox-dark.css
vendored
Binary file not shown.
BIN
netbox/project-static/dist/netbox-light.css
vendored
BIN
netbox/project-static/dist/netbox-light.css
vendored
Binary file not shown.
BIN
netbox/project-static/dist/netbox-print.css
vendored
BIN
netbox/project-static/dist/netbox-print.css
vendored
Binary file not shown.
@ -1002,6 +1002,18 @@ div.card-overlay {
|
|||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
th[align="left"] {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
th[align="center"] {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
th[align="right"] {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
/* Markdown widget */
|
/* Markdown widget */
|
||||||
.markdown-widget {
|
.markdown-widget {
|
||||||
.nav-link {
|
.nav-link {
|
||||||
|
@ -88,7 +88,11 @@
|
|||||||
{% for name, field in object.get_backend.parameters.items %}
|
{% for name, field in object.get_backend.parameters.items %}
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{{ field.label }}</th>
|
<th scope="row">{{ field.label }}</th>
|
||||||
|
{% if name in object.get_backend.sensitive_parameters and not perms.core.change_datasource %}
|
||||||
|
<td>********</td>
|
||||||
|
{% else %}
|
||||||
<td>{{ object.parameters|get_key:name|placeholder }}</td>
|
<td>{{ object.parameters|get_key:name|placeholder }}</td>
|
||||||
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -196,7 +196,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% include 'inc/panels/related_objects.html' %}
|
{% include 'inc/panels/related_objects.html' %}
|
||||||
{% include 'dcim/inc/nonracked_devices.html' %}
|
|
||||||
{% plugin_right_page object %}
|
{% plugin_right_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
51
netbox/templates/dcim/rack/non_racked_devices.html
Normal file
51
netbox/templates/dcim/rack/non_racked_devices.html
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
{% extends 'dcim/rack/base.html' %}
|
||||||
|
{% load helpers %}
|
||||||
|
|
||||||
|
{% block extra_controls %}
|
||||||
|
{% if perms.dcim.add_device %}
|
||||||
|
<div class="bulk-button-group">
|
||||||
|
<a href="{% url 'dcim:device_add' %}?rack={{ object.pk }}&site={{ object.site.pk }}&return_url={% url 'dcim:rack_nonracked_devices' pk=object.pk %}"
|
||||||
|
class="btn btn-primary btn-sm">
|
||||||
|
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add non-racked device
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% include 'inc/table_controls_htmx.html' with table_modal="DeviceTable_config" %}
|
||||||
|
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body htmx-container table-responsive" id="object_list">
|
||||||
|
{% include 'htmx/table.html' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="noprint bulk-buttons">
|
||||||
|
<div class="bulk-button-group">
|
||||||
|
{% if 'bulk_edit' in actions %}
|
||||||
|
<button type="submit" name="_edit"
|
||||||
|
formaction="{% url 'dcim:device_bulk_edit' %}?return_url={% url 'dcim:rack_nonracked_devices' pk=object.pk %}"
|
||||||
|
class="btn btn-warning btn-sm">
|
||||||
|
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
{% if 'bulk_delete' in actions %}
|
||||||
|
<button type="submit"
|
||||||
|
formaction="{% url 'dcim:device_bulk_delete' %}?return_url={% url 'dcim:rack_nonracked_devices' pk=object.pk %}"
|
||||||
|
class="btn btn-danger btn-sm">
|
||||||
|
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock content %}
|
||||||
|
|
||||||
|
{% block modals %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% table_config_form table %}
|
||||||
|
{% endblock modals %}
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
{% block content-wrapper %}
|
{% block content-wrapper %}
|
||||||
<div class="row p-3">
|
<div class="row p-3">
|
||||||
<div class="col col-md-12"{% if not job.completed %} hx-get="{% url 'extras:report_result' job_pk=job.pk %}" hx-trigger="every 5s"{% endif %}>
|
<div class="col col-md-12"{% if not job.completed %} hx-get="{% url 'extras:report_result' job_pk=job.pk %}" hx-trigger="load delay:0.5s, every 5s"{% endif %}>
|
||||||
{% include 'extras/htmx/report_result.html' %}
|
{% include 'extras/htmx/report_result.html' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -47,7 +47,7 @@
|
|||||||
<div class="tab-content mb-3">
|
<div class="tab-content mb-3">
|
||||||
<div role="tabpanel" class="tab-pane active" id="log">
|
<div role="tabpanel" class="tab-pane active" id="log">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col col-md-12"{% if not job.completed %} hx-get="{% url 'extras:script_result' job_pk=job.pk %}" hx-trigger="every 5s"{% endif %}>
|
<div class="col col-md-12"{% if not job.completed %} hx-get="{% url 'extras:script_result' job_pk=job.pk %}" hx-trigger="load delay:0.5s, every 5s"{% endif %}>
|
||||||
{% include 'extras/htmx/script_result.html' %}
|
{% include 'extras/htmx/script_result.html' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from netbox.forms import NetBoxModelImportForm
|
from netbox.forms import NetBoxModelImportForm
|
||||||
from tenancy.models import *
|
from tenancy.models import *
|
||||||
from utilities.forms.fields import CSVModelChoiceField, SlugField
|
from utilities.forms.fields import CSVContentTypeField, CSVModelChoiceField, SlugField
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
|
'ContactAssignmentImportForm',
|
||||||
'ContactImportForm',
|
'ContactImportForm',
|
||||||
'ContactGroupImportForm',
|
'ContactGroupImportForm',
|
||||||
'ContactRoleImportForm',
|
'ContactRoleImportForm',
|
||||||
@ -81,3 +83,27 @@ class ContactImportForm(NetBoxModelImportForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Contact
|
model = Contact
|
||||||
fields = ('name', 'title', 'phone', 'email', 'address', 'link', 'group', 'description', 'comments', 'tags')
|
fields = ('name', 'title', 'phone', 'email', 'address', 'link', 'group', 'description', 'comments', 'tags')
|
||||||
|
|
||||||
|
|
||||||
|
class ContactAssignmentImportForm(NetBoxModelImportForm):
|
||||||
|
content_type = CSVContentTypeField(
|
||||||
|
queryset=ContentType.objects.all(),
|
||||||
|
help_text=_("One or more assigned object types")
|
||||||
|
)
|
||||||
|
contact = CSVModelChoiceField(
|
||||||
|
queryset=Contact.objects.all(),
|
||||||
|
to_field_name='name',
|
||||||
|
help_text=_('Assigned contact')
|
||||||
|
)
|
||||||
|
role = CSVModelChoiceField(
|
||||||
|
queryset=ContactRole.objects.all(),
|
||||||
|
to_field_name='name',
|
||||||
|
help_text=_('Assigned role')
|
||||||
|
)
|
||||||
|
|
||||||
|
# Remove the tags field added by NetBoxModelImportForm (unsupported by ContactAssignment)
|
||||||
|
tags = None
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ContactAssignment
|
||||||
|
fields = ('content_type', 'object_id', 'contact', 'priority', 'role')
|
||||||
|
@ -49,6 +49,7 @@ urlpatterns = [
|
|||||||
# Contact assignments
|
# Contact assignments
|
||||||
path('contact-assignments/', views.ContactAssignmentListView.as_view(), name='contactassignment_list'),
|
path('contact-assignments/', views.ContactAssignmentListView.as_view(), name='contactassignment_list'),
|
||||||
path('contact-assignments/add/', views.ContactAssignmentEditView.as_view(), name='contactassignment_add'),
|
path('contact-assignments/add/', views.ContactAssignmentEditView.as_view(), name='contactassignment_add'),
|
||||||
|
path('contact-assignments/import/', views.ContactAssignmentBulkImportView.as_view(), name='contactassignment_import'),
|
||||||
path('contact-assignments/edit/', views.ContactAssignmentBulkEditView.as_view(), name='contactassignment_bulk_edit'),
|
path('contact-assignments/edit/', views.ContactAssignmentBulkEditView.as_view(), name='contactassignment_bulk_edit'),
|
||||||
path('contact-assignments/delete/', views.ContactAssignmentBulkDeleteView.as_view(), name='contactassignment_bulk_delete'),
|
path('contact-assignments/delete/', views.ContactAssignmentBulkDeleteView.as_view(), name='contactassignment_bulk_delete'),
|
||||||
path('contact-assignments/<int:pk>/', include(get_model_urls('tenancy', 'contactassignment'))),
|
path('contact-assignments/<int:pk>/', include(get_model_urls('tenancy', 'contactassignment'))),
|
||||||
|
@ -420,6 +420,11 @@ class ContactAssignmentBulkEditView(generic.BulkEditView):
|
|||||||
form = forms.ContactAssignmentBulkEditForm
|
form = forms.ContactAssignmentBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
class ContactAssignmentBulkImportView(generic.BulkImportView):
|
||||||
|
queryset = ContactAssignment.objects.all()
|
||||||
|
model_form = forms.ContactAssignmentImportForm
|
||||||
|
|
||||||
|
|
||||||
class ContactAssignmentBulkDeleteView(generic.BulkDeleteView):
|
class ContactAssignmentBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = ContactAssignment.objects.all()
|
queryset = ContactAssignment.objects.all()
|
||||||
filterset = filtersets.ContactAssignmentFilterSet
|
filterset = filtersets.ContactAssignmentFilterSet
|
||||||
|
@ -519,6 +519,8 @@ def clean_html(html, schemes):
|
|||||||
"h1": ["id"], "h2": ["id"], "h3": ["id"], "h4": ["id"], "h5": ["id"], "h6": ["id"],
|
"h1": ["id"], "h2": ["id"], "h3": ["id"], "h4": ["id"], "h5": ["id"], "h6": ["id"],
|
||||||
"a": ["href", "title"],
|
"a": ["href", "title"],
|
||||||
"img": ["src", "title", "alt"],
|
"img": ["src", "title", "alt"],
|
||||||
|
"th": ["align"],
|
||||||
|
"td": ["align"],
|
||||||
}
|
}
|
||||||
|
|
||||||
return bleach.clean(
|
return bleach.clean(
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
bleach==6.0.0
|
bleach==6.0.0
|
||||||
boto3==1.28.1
|
boto3==1.28.14
|
||||||
Django==4.2.2
|
|
||||||
django-cors-headers==4.2.0
|
django-cors-headers==4.2.0
|
||||||
django-debug-toolbar==4.1.0
|
django-debug-toolbar==4.1.0
|
||||||
django-filter==23.2
|
django-filter==23.2
|
||||||
@ -15,21 +14,21 @@ django-tables2==2.6.0
|
|||||||
django-taggit==4.0.0
|
django-taggit==4.0.0
|
||||||
django-timezone-field==5.1
|
django-timezone-field==5.1
|
||||||
djangorestframework==3.14.0
|
djangorestframework==3.14.0
|
||||||
drf-spectacular==0.26.3
|
drf-spectacular==0.26.4
|
||||||
drf-spectacular-sidecar==2023.7.1
|
drf-spectacular-sidecar==2023.7.1
|
||||||
dulwich==0.21.5
|
dulwich==0.21.5
|
||||||
feedparser==6.0.10
|
feedparser==6.0.10
|
||||||
graphene-django==3.0.0
|
graphene-django==3.0.0
|
||||||
gunicorn==20.1.0
|
gunicorn==21.2.0
|
||||||
Jinja2==3.1.2
|
Jinja2==3.1.2
|
||||||
Markdown==3.3.7
|
Markdown==3.3.7
|
||||||
mkdocs-material==9.1.18
|
mkdocs-material==9.1.21
|
||||||
mkdocstrings[python-legacy]==0.22.0
|
mkdocstrings[python-legacy]==0.22.0
|
||||||
netaddr==0.8.0
|
netaddr==0.8.0
|
||||||
Pillow==10.0.0
|
Pillow==10.0.0
|
||||||
psycopg[binary,pool]==3.1.9
|
psycopg[binary,pool]==3.1.9
|
||||||
PyYAML==6.0
|
PyYAML==6.0.1
|
||||||
sentry-sdk==1.28.0
|
sentry-sdk==1.28.1
|
||||||
social-auth-app-django==5.2.0
|
social-auth-app-django==5.2.0
|
||||||
social-auth-core[openidconnect]==4.4.2
|
social-auth-core[openidconnect]==4.4.2
|
||||||
svgwrite==1.4.3
|
svgwrite==1.4.3
|
||||||
|
Loading…
Reference in New Issue
Block a user