mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-09 00:58:16 -06:00
Merge branch 'develop' into 15348-quick_access_saved_filters
This commit is contained in:
commit
fd27c7e015
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@ -26,7 +26,7 @@ body:
|
||||
attributes:
|
||||
label: NetBox Version
|
||||
description: What version of NetBox are you currently running?
|
||||
placeholder: v4.0.2
|
||||
placeholder: v4.0.3
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
@ -14,7 +14,7 @@ body:
|
||||
attributes:
|
||||
label: NetBox version
|
||||
description: What version of NetBox are you currently running?
|
||||
placeholder: v4.0.2
|
||||
placeholder: v4.0.3
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
21
.github/workflows/close-stale-issues.yml
vendored
21
.github/workflows/close-stale-issues.yml
vendored
@ -17,18 +17,19 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
# General parameters
|
||||
operations-per-run: 100
|
||||
remove-stale-when-updated: false
|
||||
|
||||
# Issue parameters
|
||||
close-issue-message: >
|
||||
This issue has been automatically closed due to lack of activity. In an
|
||||
effort to reduce noise, please do not comment any further. Note that the
|
||||
core maintainers may elect to reopen this issue at a later date if deemed
|
||||
necessary.
|
||||
close-pr-message: >
|
||||
This PR has been automatically closed due to lack of activity.
|
||||
days-before-stale: 90
|
||||
days-before-close: 30
|
||||
exempt-issue-labels: 'status: accepted,status: blocked,status: needs milestone'
|
||||
operations-per-run: 100
|
||||
remove-stale-when-updated: false
|
||||
days-before-issue-stale: 90
|
||||
days-before-issue-close: 30
|
||||
exempt-issue-labels: 'status: accepted,status: backlog,status: blocked'
|
||||
stale-issue-label: 'pending closure'
|
||||
stale-issue-message: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
@ -38,6 +39,12 @@ jobs:
|
||||
process by "bumping" the issue; doing so will result in its immediate closure
|
||||
and you may be barred from participating in any future discussions. Please see
|
||||
our [contributing guide](https://github.com/netbox-community/netbox/blob/develop/CONTRIBUTING.md).
|
||||
|
||||
# Pull request parameters
|
||||
close-pr-message: >
|
||||
This PR has been automatically closed due to lack of activity.
|
||||
days-before-pr-stale: 15
|
||||
days-before-pr-close: 15
|
||||
stale-pr-label: 'pending closure'
|
||||
stale-pr-message: >
|
||||
This PR has been automatically marked as stale because it has not had
|
||||
|
@ -179,6 +179,9 @@
|
||||
"usb-micro-ab",
|
||||
"usb-3-b",
|
||||
"usb-3-micro-b",
|
||||
"molex-micro-fit-1x2",
|
||||
"molex-micro-fit-2x2",
|
||||
"molex-micro-fit-2x4",
|
||||
"dc-terminal",
|
||||
"saf-d-grid",
|
||||
"neutrik-powercon-20",
|
||||
@ -281,6 +284,9 @@
|
||||
"usb-a",
|
||||
"usb-micro-b",
|
||||
"usb-c",
|
||||
"molex-micro-fit-1x2",
|
||||
"molex-micro-fit-2x2",
|
||||
"molex-micro-fit-2x4",
|
||||
"dc-terminal",
|
||||
"hdot-cx",
|
||||
"saf-d-grid",
|
||||
@ -375,6 +381,8 @@
|
||||
"gsm",
|
||||
"cdma",
|
||||
"lte",
|
||||
"4g",
|
||||
"5g",
|
||||
"sonet-oc3",
|
||||
"sonet-oc12",
|
||||
"sonet-oc48",
|
||||
@ -408,12 +416,15 @@
|
||||
"e3",
|
||||
"xdsl",
|
||||
"docsis",
|
||||
"bpon",
|
||||
"epon",
|
||||
"10g-epon",
|
||||
"gpon",
|
||||
"xg-pon",
|
||||
"xgs-pon",
|
||||
"ng-pon2",
|
||||
"epon",
|
||||
"10g-epon",
|
||||
"25g-pon",
|
||||
"50g-pon",
|
||||
"cisco-stackwise",
|
||||
"cisco-stackwise-plus",
|
||||
"cisco-flexstack",
|
||||
|
4
docs/_theme/main.html
vendored
4
docs/_theme/main.html
vendored
@ -2,8 +2,8 @@
|
||||
|
||||
{% block site_meta %}
|
||||
{{ super() }}
|
||||
{# Disable search indexing unless we're building for ReadTheDocs #}
|
||||
{% if not config.extra.readthedocs %}
|
||||
{# Disable search indexing unless we're building for public consumption #}
|
||||
{% if not config.extra.build_public %}
|
||||
<meta name="robots" content="noindex">
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
@ -94,15 +94,25 @@ REDIS = {
|
||||
}
|
||||
```
|
||||
|
||||
!!! note
|
||||
If you are upgrading from a NetBox release older than v2.7.0, please note that the Redis connection configuration
|
||||
settings have changed. Manual modification to bring the `REDIS` section inline with the above specification is
|
||||
necessary
|
||||
|
||||
!!! warning
|
||||
It is highly recommended to keep the task and cache databases separate. Using the same database number on the
|
||||
same Redis instance for both may result in queued background tasks being lost during cache flushing events.
|
||||
|
||||
### UNIX Socket Support
|
||||
|
||||
Redis may alternatively be configured by specifying a complete URL instead of individual components. This approach supports the use of a UNIX socket connection. For example:
|
||||
|
||||
```python
|
||||
REDIS = {
|
||||
'tasks': {
|
||||
'URL': 'unix:///run/redis-netbox/redis.sock?db=0'
|
||||
},
|
||||
'caching': {
|
||||
'URL': 'unix:///run/redis-netbox/redis.sock?db=1'
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Using Redis Sentinel
|
||||
|
||||
If you are using [Redis Sentinel](https://redis.io/topics/sentinel) for high-availability purposes, there is minimal
|
||||
|
@ -1,22 +1,38 @@
|
||||
# NetBox v4.0
|
||||
|
||||
## v4.0.3 (FUTURE)
|
||||
## v4.0.4 (FUTURE)
|
||||
|
||||
---
|
||||
|
||||
## v4.0.3 (2024-05-22)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [#12984](https://github.com/netbox-community/netbox/issues/12984) - Add Molex Micro-Fit power port & outlet types
|
||||
* [#13764](https://github.com/netbox-community/netbox/issues/13764) - Enable contact assignments for aggregates, prefixes, IP ranges, and IP addresses
|
||||
* [#14639](https://github.com/netbox-community/netbox/issues/14639) - Add Ukrainian translation support
|
||||
* [#14653](https://github.com/netbox-community/netbox/issues/14653) - Add an inventory items table column for all device components
|
||||
* [#14686](https://github.com/netbox-community/netbox/issues/14686) - Add German translation support
|
||||
* [#14855](https://github.com/netbox-community/netbox/issues/14855) - Add Chinese translation support
|
||||
* [#14948](https://github.com/netbox-community/netbox/issues/14948) - Introduce the `has_virtual_device_context` filter for devices
|
||||
* [#15353](https://github.com/netbox-community/netbox/issues/15353) - Improve error reporting when custom scripts fail to load
|
||||
* [#15496](https://github.com/netbox-community/netbox/issues/15496) - Implement dedicated views for management of circuit terminations
|
||||
* [#15603](https://github.com/netbox-community/netbox/issues/15603) - Add 4G & 5G cellular interface types
|
||||
* [#15962](https://github.com/netbox-community/netbox/issues/15962) - Enable UNIX socket connections for Redis
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#13293](https://github.com/netbox-community/netbox/issues/13293) - Limit interface selector for IP address to current device/VM
|
||||
* [#14953](https://github.com/netbox-community/netbox/issues/14953) - Ensure annotated count fields are present in REST API response data when creating new objects
|
||||
* [#14982](https://github.com/netbox-community/netbox/issues/14982) - Fix OpenAPI schema definition for SerializedPKRelatedFields
|
||||
* [#15082](https://github.com/netbox-community/netbox/issues/15082) - Strip whitespace from choice values & labels when creating a custom field choice set
|
||||
* [#16138](https://github.com/netbox-community/netbox/issues/16138) - Fix support for referencing users & groups in object permissions
|
||||
* [#16145](https://github.com/netbox-community/netbox/issues/16145) - Restore ability to reference custom scripts via module & name in REST API
|
||||
* [#16164](https://github.com/netbox-community/netbox/issues/16164) - Correct display of selected values in UI when filtering object list by a null value
|
||||
* [#16173](https://github.com/netbox-community/netbox/issues/16173) - Fix TypeError exception when viewing object list with no pagination preference defined
|
||||
* [#16228](https://github.com/netbox-community/netbox/issues/16228) - Fix permissions enforcement for GraphQL queries of users & groups
|
||||
* [#16232](https://github.com/netbox-community/netbox/issues/16232) - Preserve bulk action checkboxes on dynamic tables when using pagination
|
||||
* [#16240](https://github.com/netbox-community/netbox/issues/16240) - Fixed NoReverseMatch exception when adding circuit terminations to an object counts dashboard widget
|
||||
|
||||
---
|
||||
|
||||
|
@ -42,7 +42,7 @@ plugins:
|
||||
show_root_toc_entry: false
|
||||
show_source: false
|
||||
extra:
|
||||
readthedocs: !ENV READTHEDOCS
|
||||
build_public: !ENV BUILD_PUBLIC
|
||||
social:
|
||||
- icon: fontawesome/brands/github
|
||||
link: https://github.com/netbox-community/netbox
|
||||
|
@ -892,6 +892,8 @@ class InterfaceTypeChoices(ChoiceSet):
|
||||
TYPE_GSM = 'gsm'
|
||||
TYPE_CDMA = 'cdma'
|
||||
TYPE_LTE = 'lte'
|
||||
TYPE_4G = '4g'
|
||||
TYPE_5G = '5g'
|
||||
|
||||
# SONET
|
||||
TYPE_SONET_OC3 = 'sonet-oc3'
|
||||
@ -939,12 +941,15 @@ class InterfaceTypeChoices(ChoiceSet):
|
||||
TYPE_DOCSIS = 'docsis'
|
||||
|
||||
# PON
|
||||
TYPE_BPON = 'bpon'
|
||||
TYPE_EPON = 'epon'
|
||||
TYPE_10G_EPON = '10g-epon'
|
||||
TYPE_GPON = 'gpon'
|
||||
TYPE_XG_PON = 'xg-pon'
|
||||
TYPE_XGS_PON = 'xgs-pon'
|
||||
TYPE_NG_PON2 = 'ng-pon2'
|
||||
TYPE_EPON = 'epon'
|
||||
TYPE_10G_EPON = '10g-epon'
|
||||
TYPE_25G_PON = '25g-pon'
|
||||
TYPE_50G_PON = '50g-pon'
|
||||
|
||||
# Stacking
|
||||
TYPE_STACKWISE = 'cisco-stackwise'
|
||||
@ -1060,6 +1065,8 @@ class InterfaceTypeChoices(ChoiceSet):
|
||||
(TYPE_GSM, 'GSM'),
|
||||
(TYPE_CDMA, 'CDMA'),
|
||||
(TYPE_LTE, 'LTE'),
|
||||
(TYPE_4G, '4G'),
|
||||
(TYPE_5G, '5G'),
|
||||
)
|
||||
),
|
||||
(
|
||||
@ -1128,12 +1135,15 @@ class InterfaceTypeChoices(ChoiceSet):
|
||||
(
|
||||
'PON',
|
||||
(
|
||||
(TYPE_GPON, 'GPON (2.5 Gbps / 1.25 Gps)'),
|
||||
(TYPE_BPON, 'BPON (622 Mbps / 155 Mbps)'),
|
||||
(TYPE_EPON, 'EPON (1 Gbps)'),
|
||||
(TYPE_10G_EPON, '10G-EPON (10 Gbps)'),
|
||||
(TYPE_GPON, 'GPON (2.5 Gbps / 1.25 Gbps)'),
|
||||
(TYPE_XG_PON, 'XG-PON (10 Gbps / 2.5 Gbps)'),
|
||||
(TYPE_XGS_PON, 'XGS-PON (10 Gbps)'),
|
||||
(TYPE_NG_PON2, 'NG-PON2 (TWDM-PON) (4x10 Gbps)'),
|
||||
(TYPE_EPON, 'EPON (1 Gbps)'),
|
||||
(TYPE_10G_EPON, '10G-EPON (10 Gbps)'),
|
||||
(TYPE_25G_PON, '25G-PON (25 Gbps)'),
|
||||
(TYPE_50G_PON, '50G-PON (50 Gbps)'),
|
||||
)
|
||||
),
|
||||
(
|
||||
|
@ -1100,6 +1100,10 @@ class DeviceFilterSet(
|
||||
queryset=IPAddress.objects.all(),
|
||||
label=_('OOB IP (ID)'),
|
||||
)
|
||||
has_virtual_device_context = django_filters.BooleanFilter(
|
||||
method='_has_virtual_device_context',
|
||||
label=_('Has virtual device context'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Device
|
||||
@ -1176,6 +1180,12 @@ class DeviceFilterSet(
|
||||
def _device_bays(self, queryset, name, value):
|
||||
return queryset.exclude(devicebays__isnull=value)
|
||||
|
||||
def _has_virtual_device_context(self, queryset, name, value):
|
||||
params = Q(vdcs__isnull=False)
|
||||
if value:
|
||||
return queryset.filter(params).distinct()
|
||||
return queryset.exclude(params)
|
||||
|
||||
|
||||
class VirtualDeviceContextFilterSet(NetBoxModelFilterSet, TenancyFilterSet, PrimaryIPFilterSet):
|
||||
device_id = django_filters.ModelMultipleChoiceFilter(
|
||||
|
@ -657,6 +657,7 @@ class DeviceFilterForm(
|
||||
),
|
||||
FieldSet(
|
||||
'has_primary_ip', 'has_oob_ip', 'virtual_chassis_member', 'config_template_id', 'local_context_data',
|
||||
'has_virtual_device_context',
|
||||
name=_('Miscellaneous')
|
||||
)
|
||||
)
|
||||
@ -813,6 +814,13 @@ class DeviceFilterForm(
|
||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||
)
|
||||
)
|
||||
has_virtual_device_context = forms.NullBooleanField(
|
||||
required=False,
|
||||
label=_('Has virtual device contexts'),
|
||||
widget=forms.Select(
|
||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||
)
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
|
@ -313,6 +313,10 @@ class ModularDeviceComponentTable(DeviceComponentTable):
|
||||
verbose_name=_('Module'),
|
||||
linkify=True
|
||||
)
|
||||
inventory_items = columns.ManyToManyColumn(
|
||||
linkify_item=True,
|
||||
verbose_name=_('Inventory Items'),
|
||||
)
|
||||
|
||||
|
||||
class CableTerminationTable(NetBoxTable):
|
||||
@ -366,7 +370,7 @@ class ConsolePortTable(ModularDeviceComponentTable, PathEndpointTable):
|
||||
model = models.ConsolePort
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'speed', 'description',
|
||||
'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'created', 'last_updated',
|
||||
'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'inventory_items', 'tags', 'created', 'last_updated',
|
||||
)
|
||||
default_columns = ('pk', 'name', 'device', 'label', 'type', 'speed', 'description')
|
||||
|
||||
@ -410,7 +414,7 @@ class ConsoleServerPortTable(ModularDeviceComponentTable, PathEndpointTable):
|
||||
model = models.ConsoleServerPort
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'speed', 'description',
|
||||
'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'created', 'last_updated',
|
||||
'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'inventory_items', 'tags', 'created', 'last_updated',
|
||||
)
|
||||
default_columns = ('pk', 'name', 'device', 'label', 'type', 'speed', 'description')
|
||||
|
||||
@ -461,8 +465,8 @@ class PowerPortTable(ModularDeviceComponentTable, PathEndpointTable):
|
||||
model = models.PowerPort
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'description', 'mark_connected',
|
||||
'maximum_draw', 'allocated_draw', 'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'created',
|
||||
'last_updated',
|
||||
'maximum_draw', 'allocated_draw', 'cable', 'cable_color', 'link_peer', 'connection', 'inventory_items',
|
||||
'tags', 'created', 'last_updated',
|
||||
)
|
||||
default_columns = ('pk', 'name', 'device', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description')
|
||||
|
||||
@ -513,8 +517,8 @@ class PowerOutletTable(ModularDeviceComponentTable, PathEndpointTable):
|
||||
model = models.PowerOutlet
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'description', 'power_port',
|
||||
'feed_leg', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'created',
|
||||
'last_updated',
|
||||
'feed_leg', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'inventory_items',
|
||||
'tags', 'created', 'last_updated',
|
||||
)
|
||||
default_columns = ('pk', 'name', 'device', 'label', 'type', 'power_port', 'feed_leg', 'description')
|
||||
|
||||
@ -618,10 +622,6 @@ class InterfaceTable(ModularDeviceComponentTable, BaseInterfaceTable, PathEndpoi
|
||||
verbose_name=_('VRF'),
|
||||
linkify=True
|
||||
)
|
||||
inventory_items = columns.ManyToManyColumn(
|
||||
linkify_item=True,
|
||||
verbose_name=_('Inventory Items'),
|
||||
)
|
||||
tags = columns.TagColumn(
|
||||
url_name='dcim:interface_list'
|
||||
)
|
||||
@ -713,8 +713,8 @@ class FrontPortTable(ModularDeviceComponentTable, CableTerminationTable):
|
||||
model = models.FrontPort
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'color', 'rear_port',
|
||||
'rear_port_position', 'description', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'tags',
|
||||
'created', 'last_updated',
|
||||
'rear_port_position', 'description', 'mark_connected', 'cable', 'cable_color', 'link_peer',
|
||||
'inventory_items', 'tags', 'created', 'last_updated',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'name', 'device', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'description',
|
||||
@ -766,7 +766,7 @@ class RearPortTable(ModularDeviceComponentTable, CableTerminationTable):
|
||||
model = models.RearPort
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'color', 'positions', 'description',
|
||||
'mark_connected', 'cable', 'cable_color', 'link_peer', 'tags', 'created', 'last_updated',
|
||||
'mark_connected', 'cable', 'cable_color', 'link_peer', 'inventory_items', 'tags', 'created', 'last_updated',
|
||||
)
|
||||
default_columns = ('pk', 'name', 'device', 'label', 'type', 'color', 'description')
|
||||
|
||||
|
@ -2103,6 +2103,9 @@ class DeviceTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
Device.objects.filter(pk=devices[0].pk).update(virtual_chassis=virtual_chassis, vc_position=1, vc_priority=1)
|
||||
Device.objects.filter(pk=devices[1].pk).update(virtual_chassis=virtual_chassis, vc_position=2, vc_priority=2)
|
||||
|
||||
# VirtualDeviceContext assignment for filtering
|
||||
VirtualDeviceContext.objects.create(device=devices[0], name="VDC 1", identifier=1, status='active')
|
||||
|
||||
def test_q(self):
|
||||
params = {'q': 'foobar1'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
@ -2336,6 +2339,12 @@ class DeviceTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
params = {'tenant_group': [tenant_groups[0].slug, tenant_groups[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_has_virtual_device_context(self):
|
||||
params = {'has_virtual_device_context': 'true'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
params = {'has_virtual_device_context': 'false'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
|
||||
class ModuleTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
queryset = Module.objects.all()
|
||||
|
@ -43,7 +43,7 @@ class JournalEntrySerializer(NetBoxModelSerializer):
|
||||
def validate(self, data):
|
||||
|
||||
# Validate that the parent object exists
|
||||
if 'assigned_object_type' in data and 'assigned_object_id' in data:
|
||||
if not self.nested and 'assigned_object_type' in data and 'assigned_object_id' in data:
|
||||
try:
|
||||
data['assigned_object_type'].get_object_for_this_type(id=data['assigned_object_id'])
|
||||
except ObjectDoesNotExist:
|
||||
@ -51,10 +51,7 @@ class JournalEntrySerializer(NetBoxModelSerializer):
|
||||
f"Invalid assigned_object: {data['assigned_object_type']} ID {data['assigned_object_id']}"
|
||||
)
|
||||
|
||||
# Enforce model validation
|
||||
super().validate(data)
|
||||
|
||||
return data
|
||||
return super().validate(data)
|
||||
|
||||
@extend_schema_field(serializers.JSONField(allow_null=True))
|
||||
def get_assigned_object(self, instance):
|
||||
|
@ -1,3 +1,4 @@
|
||||
from django.http import Http404
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django_rq.queues import get_connection
|
||||
from rest_framework import status
|
||||
@ -215,21 +216,32 @@ class ScriptViewSet(ModelViewSet):
|
||||
_ignore_model_permissions = True
|
||||
lookup_value_regex = '[^/]+' # Allow dots
|
||||
|
||||
def _get_script(self, pk):
|
||||
# If pk is numeric, retrieve script by ID
|
||||
if pk.isnumeric():
|
||||
return get_object_or_404(self.queryset, pk=pk)
|
||||
|
||||
# Default to retrieval by module & name
|
||||
try:
|
||||
module_name, script_name = pk.split('.', maxsplit=1)
|
||||
except ValueError:
|
||||
raise Http404
|
||||
return get_object_or_404(self.queryset, module__file_path=f'{module_name}.py', name=script_name)
|
||||
|
||||
def retrieve(self, request, pk):
|
||||
script = get_object_or_404(self.queryset, pk=pk)
|
||||
script = self._get_script(pk)
|
||||
serializer = serializers.ScriptDetailSerializer(script, context={'request': request})
|
||||
|
||||
return Response(serializer.data)
|
||||
|
||||
def post(self, request, pk):
|
||||
"""
|
||||
Run a Script identified by the id and return the pending Job as the result
|
||||
Run a Script identified by its numeric PK or module & name and return the pending Job as the result
|
||||
"""
|
||||
|
||||
if not request.user.has_perm('extras.run_script'):
|
||||
raise PermissionDenied("This user does not have permission to run scripts.")
|
||||
|
||||
script = get_object_or_404(self.queryset, pk=pk)
|
||||
script = self._get_script(pk)
|
||||
input_serializer = serializers.ScriptInputSerializer(
|
||||
data=request.data,
|
||||
context={'script': script}
|
||||
|
@ -122,7 +122,7 @@ class CustomFieldChoiceSetForm(forms.ModelForm):
|
||||
label = label.replace('\\:', ':')
|
||||
except ValueError:
|
||||
value, label = line, line
|
||||
data.append((value, label))
|
||||
data.append((value.strip(), label.strip()))
|
||||
return data
|
||||
|
||||
|
||||
|
@ -168,6 +168,7 @@ class PrefixFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
'within_include', 'family', 'status', 'role_id', 'mask_length', 'is_pool', 'mark_utilized',
|
||||
name=_('Addressing')
|
||||
),
|
||||
FieldSet('vlan_id', name=_('VLAN Assignment')),
|
||||
FieldSet('vrf_id', 'present_in_vrf_id', name=_('VRF')),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', name=_('Location')),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
@ -249,6 +250,12 @@ class PrefixFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||
)
|
||||
)
|
||||
vlan_id = DynamicModelMultipleChoiceField(
|
||||
queryset=VLAN.objects.all(),
|
||||
required=False,
|
||||
label=_('VLAN'),
|
||||
)
|
||||
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
|
@ -25,7 +25,7 @@ from utilities.string import trailing_slash
|
||||
# Environment setup
|
||||
#
|
||||
|
||||
VERSION = '4.0.3-dev'
|
||||
VERSION = '4.0.4-dev'
|
||||
HOSTNAME = platform.node()
|
||||
# Set the base directory two levels up
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
@ -242,6 +242,7 @@ if 'tasks' not in REDIS:
|
||||
TASKS_REDIS = REDIS['tasks']
|
||||
TASKS_REDIS_HOST = TASKS_REDIS.get('HOST', 'localhost')
|
||||
TASKS_REDIS_PORT = TASKS_REDIS.get('PORT', 6379)
|
||||
TASKS_REDIS_URL = TASKS_REDIS.get('URL')
|
||||
TASKS_REDIS_SENTINELS = TASKS_REDIS.get('SENTINELS', [])
|
||||
TASKS_REDIS_USING_SENTINEL = all([
|
||||
isinstance(TASKS_REDIS_SENTINELS, (list, tuple)),
|
||||
@ -270,7 +271,7 @@ CACHING_REDIS_SENTINEL_SERVICE = REDIS['caching'].get('SENTINEL_SERVICE', 'defau
|
||||
CACHING_REDIS_PROTO = 'rediss' if REDIS['caching'].get('SSL', False) else 'redis'
|
||||
CACHING_REDIS_SKIP_TLS_VERIFY = REDIS['caching'].get('INSECURE_SKIP_TLS_VERIFY', False)
|
||||
CACHING_REDIS_CA_CERT_PATH = REDIS['caching'].get('CA_CERT_PATH', False)
|
||||
CACHING_REDIS_URL = f'{CACHING_REDIS_PROTO}://{CACHING_REDIS_USERNAME_HOST}:{CACHING_REDIS_PORT}/{CACHING_REDIS_DATABASE}'
|
||||
CACHING_REDIS_URL = REDIS['caching'].get('URL', f'{CACHING_REDIS_PROTO}://{CACHING_REDIS_USERNAME_HOST}:{CACHING_REDIS_PORT}/{CACHING_REDIS_DATABASE}')
|
||||
|
||||
# Configure Django's default cache to use Redis
|
||||
CACHES = {
|
||||
@ -678,6 +679,12 @@ if TASKS_REDIS_USING_SENTINEL:
|
||||
'socket_connect_timeout': TASKS_REDIS_SENTINEL_TIMEOUT
|
||||
},
|
||||
}
|
||||
elif TASKS_REDIS_URL:
|
||||
RQ_PARAMS = {
|
||||
'URL': TASKS_REDIS_URL,
|
||||
'SSL': TASKS_REDIS_SSL,
|
||||
'SSL_CERT_REQS': None if TASKS_REDIS_SKIP_TLS_VERIFY else 'required',
|
||||
}
|
||||
else:
|
||||
RQ_PARAMS = {
|
||||
'HOST': TASKS_REDIS_HOST,
|
||||
|
@ -1,4 +1,5 @@
|
||||
from copy import deepcopy
|
||||
from functools import cached_property
|
||||
|
||||
import django_tables2 as tables
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
@ -189,6 +190,7 @@ class NetBoxTable(BaseTable):
|
||||
actions = columns.ActionsColumn()
|
||||
|
||||
exempt_columns = ('pk', 'actions')
|
||||
embedded = False
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
pass
|
||||
@ -218,12 +220,12 @@ class NetBoxTable(BaseTable):
|
||||
|
||||
super().__init__(*args, extra_columns=extra_columns, **kwargs)
|
||||
|
||||
@property
|
||||
@cached_property
|
||||
def htmx_url(self):
|
||||
"""
|
||||
Return the base HTML request URL for embedded tables.
|
||||
"""
|
||||
if getattr(self, 'embedded', False):
|
||||
if self.embedded:
|
||||
viewname = get_viewname(self._meta.model, action='list')
|
||||
try:
|
||||
return reverse(viewname)
|
||||
|
@ -163,7 +163,7 @@ class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin):
|
||||
|
||||
# If this is an HTMX request, return only the rendered table HTML
|
||||
if htmx_partial(request):
|
||||
if not request.htmx.target:
|
||||
if request.GET.get('embedded', False):
|
||||
table.embedded = True
|
||||
# Hide selection checkboxes
|
||||
if 'pk' in table.base_columns:
|
||||
|
BIN
netbox/project-static/dist/netbox.css
vendored
BIN
netbox/project-static/dist/netbox.css
vendored
Binary file not shown.
@ -30,7 +30,7 @@
|
||||
"gridstack": "10.1.2",
|
||||
"htmx.org": "1.9.12",
|
||||
"query-string": "9.0.0",
|
||||
"sass": "1.77.1",
|
||||
"sass": "1.77.2",
|
||||
"tom-select": "2.3.1",
|
||||
"typeface-inter": "3.18.1",
|
||||
"typeface-roboto-mono": "1.1.13"
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Global variables
|
||||
|
||||
// Set base fonts
|
||||
$font-family-base: 'Inter';
|
||||
$font-family-sans-serif: 'Inter';
|
||||
// See https://github.com/tabler/tabler/issues/1812
|
||||
$font-family-monospace: 'Roboto Mono';
|
||||
|
||||
|
@ -2482,10 +2482,10 @@ safe-regex-test@^1.0.3:
|
||||
es-errors "^1.3.0"
|
||||
is-regex "^1.1.4"
|
||||
|
||||
sass@1.77.1:
|
||||
version "1.77.1"
|
||||
resolved "https://registry.yarnpkg.com/sass/-/sass-1.77.1.tgz#018cdfb206afd14724030c02e9fefd8f30a76cd0"
|
||||
integrity sha512-OMEyfirt9XEfyvocduUIOlUSkWOXS/LAt6oblR/ISXCTukyavjex+zQNm51pPCOiFKY1QpWvEH1EeCkgyV3I6w==
|
||||
sass@1.77.2:
|
||||
version "1.77.2"
|
||||
resolved "https://registry.yarnpkg.com/sass/-/sass-1.77.2.tgz#18d4ed2eefc260cdc8099c5439ec1303fd5863aa"
|
||||
integrity sha512-eb4GZt1C3avsX3heBNlrc7I09nyT00IUuo4eFhAbeXWU2fvA7oXI53SxODVAA+zgZCk9aunAZgO+losjR3fAwA==
|
||||
dependencies:
|
||||
chokidar ">=3.0.0 <4.0.0"
|
||||
immutable "^4.0.0"
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -1,13 +1,10 @@
|
||||
from typing import List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import Group
|
||||
from strawberry import auto
|
||||
from users import filtersets
|
||||
|
||||
from netbox.graphql.types import BaseObjectType
|
||||
from users.models import Group
|
||||
from utilities.querysets import RestrictedQuerySet
|
||||
from .filters import *
|
||||
|
||||
__all__ = (
|
||||
@ -21,17 +18,16 @@ __all__ = (
|
||||
fields=['id', 'name'],
|
||||
filters=GroupFilter
|
||||
)
|
||||
class GroupType:
|
||||
class GroupType(BaseObjectType):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
get_user_model(),
|
||||
fields=[
|
||||
'id', 'username', 'password', 'first_name', 'last_name', 'email', 'is_staff',
|
||||
'is_active', 'date_joined', 'groups',
|
||||
'id', 'username', 'first_name', 'last_name', 'email', 'is_staff', 'is_active', 'date_joined', 'groups',
|
||||
],
|
||||
filters=UserFilter
|
||||
)
|
||||
class UserType:
|
||||
class UserType(BaseObjectType):
|
||||
groups: List[GroupType]
|
||||
|
@ -1,5 +1,5 @@
|
||||
<div class="htmx-container table-responsive"
|
||||
hx-get="{% url viewname %}{% if url_params %}?{{ url_params.urlencode }}{% endif %}"
|
||||
hx-get="{% url viewname %}?embedded=True{% if url_params %}&{{ url_params.urlencode }}{% endif %}"
|
||||
hx-target="this"
|
||||
hx-trigger="load" hx-select=".htmx-container" hx-swap="outerHTML"
|
||||
></div>
|
||||
|
@ -20,17 +20,17 @@ feedparser==6.0.11
|
||||
gunicorn==22.0.0
|
||||
Jinja2==3.1.4
|
||||
Markdown==3.6
|
||||
mkdocs-material==9.5.22
|
||||
mkdocs-material==9.5.24
|
||||
mkdocstrings[python-legacy]==0.25.1
|
||||
netaddr==1.2.1
|
||||
nh3==0.2.17
|
||||
Pillow==10.3.0
|
||||
psycopg[c,pool]==3.1.19
|
||||
PyYAML==6.0.1
|
||||
requests==2.31.0
|
||||
requests==2.32.2
|
||||
social-auth-app-django==5.4.1
|
||||
social-auth-core==4.5.4
|
||||
strawberry-graphql==0.229.0
|
||||
strawberry-graphql==0.230.0
|
||||
strawberry-graphql-django==0.40.0
|
||||
svgwrite==1.4.3
|
||||
tablib==3.6.1
|
||||
|
Loading…
Reference in New Issue
Block a user