Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/01-feature_request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ body:
attributes:
label: NetBox version
description: What version of NetBox are you currently running?
placeholder: v4.1.10
placeholder: v4.1.11
validations:
required: true
- type: dropdown
Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/02-bug_report.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ body:
attributes:
label: NetBox Version
description: What version of NetBox are you currently running?
placeholder: v4.1.10
placeholder: v4.1.11
validations:
required: true
- type: dropdown
Expand Down
2 changes: 1 addition & 1 deletion base_requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ netaddr
nh3

# Fork of PIL (Python Imaging Library) for image processing
# https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst
# https://github.com/python-pillow/Pillow/releases
Pillow

# PostgreSQL database adapter for Python
Expand Down
14 changes: 13 additions & 1 deletion docs/models/extras/eventrule.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ See the [event rules documentation](../../features/event-rules.md) for more inf

A unique human-friendly name.

### Content Types
### Object Types

The type(s) of object in NetBox that will trigger the rule.

Expand Down Expand Up @@ -38,3 +38,15 @@ The event types which will trigger the rule. At least one event type must be sel
### Conditions

A set of [prescribed conditions](../../reference/conditions.md) against which the triggering object will be evaluated. If the conditions are defined but not met by the object, no action will be taken. An event rule that does not define any conditions will _always_ trigger.

### Action Type

The type of action to take when the rule triggers. This must be one of the following choices:

* Webhook
* Custom script
* Notification

### Action Data

An optional dictionary of JSON data to pass when executing the rule. This can be useful to include additional context data, e.g. when transmitting a webhook.
12 changes: 12 additions & 0 deletions docs/release-notes/version-4.1.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# NetBox v4.1

## v4.1.11 (2025-01-06)

### Bug Fixes

* [#17771](https://github.com/netbox-community/netbox/issues/17771) - Fix duplicate entries appearing on VLAN list when filtering by interface assignment
* [#18222](https://github.com/netbox-community/netbox/issues/18222) - Pass event rule action data to webhooks as context data
* [#18263](https://github.com/netbox-community/netbox/issues/18263) - Fix recalculation of cable paths when modifying cable terminations via the REST API
* [#18271](https://github.com/netbox-community/netbox/issues/18271) - Require only encryption _or_ authentication algorithm when creating an IPSec proposal via the REST API
* [#18289](https://github.com/netbox-community/netbox/issues/18289) - Enable ordering modules and module types by created & last updated times

---

## v4.1.10 (2024-12-23)

### Bug Fixes
Expand Down
4 changes: 4 additions & 0 deletions netbox/dcim/models/cables.py
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,10 @@ def from_origin(cls, terminations):
cable_end = 'A' if lct.cable_end == 'B' else 'B'
q_filter |= Q(cable=lct.cable, cable_end=cable_end)

# Make sure this filter has been populated; if not, we have probably been given invalid data
if not q_filter:
break

remote_cable_terminations = CableTermination.objects.filter(q_filter)
remote_terminations = [ct.termination for ct in remote_cable_terminations]
else:
Expand Down
3 changes: 2 additions & 1 deletion netbox/dcim/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ def update_connected_endpoints(instance, created, raw=False, **kwargs):
if instance._terminations_modified:
a_terminations = []
b_terminations = []
for t in instance.terminations.all():
# Note: instance.terminations.all() is not safe to use here as it might be stale
for t in CableTermination.objects.filter(cable=instance):
if t.cable_end == CableEndChoices.SIDE_A:
a_terminations.append(t.termination)
else:
Expand Down
3 changes: 2 additions & 1 deletion netbox/dcim/tables/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class Meta(NetBoxTable.Meta):
model = ModuleType
fields = (
'pk', 'id', 'model', 'manufacturer', 'part_number', 'airflow', 'weight', 'description', 'comments', 'tags',
'created', 'last_updated',
)
default_columns = (
'pk', 'model', 'manufacturer', 'part_number',
Expand Down Expand Up @@ -79,7 +80,7 @@ class Meta(NetBoxTable.Meta):
model = Module
fields = (
'pk', 'id', 'device', 'module_bay', 'manufacturer', 'module_type', 'status', 'serial', 'asset_tag',
'description', 'comments', 'tags',
'description', 'comments', 'tags', 'created', 'last_updated',
)
default_columns = (
'pk', 'id', 'device', 'module_bay', 'manufacturer', 'module_type', 'status', 'serial', 'asset_tag',
Expand Down
12 changes: 8 additions & 4 deletions netbox/extras/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ def process_event_rules(event_rules, object_type, event_type, data, username=Non
if not event_rule.eval_conditions(data):
continue

# Compile event data
event_data = event_rule.action_data or {}
event_data.update(data)

# Webhooks
if event_rule.action_type == EventRuleActionChoices.WEBHOOK:

Expand All @@ -102,7 +106,7 @@ def process_event_rules(event_rules, object_type, event_type, data, username=Non
"event_rule": event_rule,
"model_name": object_type.model,
"event_type": event_type,
"data": data,
"data": event_data,
"snapshots": snapshots,
"timestamp": timezone.now().isoformat(),
"username": username,
Expand Down Expand Up @@ -130,16 +134,16 @@ def process_event_rules(event_rules, object_type, event_type, data, username=Non
instance=event_rule.action_object,
name=script.name,
user=user,
data=data
data=event_data
)

# Notification groups
elif event_rule.action_type == EventRuleActionChoices.NOTIFICATION:
# Bulk-create notifications for all members of the notification group
event_rule.action_object.notify(
object_type=object_type,
object_id=data['id'],
object_repr=data.get('display'),
object_id=event_data['id'],
object_repr=event_data.get('display'),
event_type=event_type
)

Expand Down
16 changes: 13 additions & 3 deletions netbox/extras/tests/test_event_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,21 +50,24 @@ def setUpTestData(cls):
event_types=[OBJECT_CREATED],
action_type=EventRuleActionChoices.WEBHOOK,
action_object_type=webhook_type,
action_object_id=webhooks[0].id
action_object_id=webhooks[0].id,
action_data={"foo": 1},
),
EventRule(
name='Event Rule 2',
event_types=[OBJECT_UPDATED],
action_type=EventRuleActionChoices.WEBHOOK,
action_object_type=webhook_type,
action_object_id=webhooks[0].id
action_object_id=webhooks[0].id,
action_data={"foo": 2},
),
EventRule(
name='Event Rule 3',
event_types=[OBJECT_DELETED],
action_type=EventRuleActionChoices.WEBHOOK,
action_object_type=webhook_type,
action_object_id=webhooks[0].id
action_object_id=webhooks[0].id,
action_data={"foo": 3},
),
))
for event_rule in event_rules:
Expand Down Expand Up @@ -134,6 +137,7 @@ def test_single_create_process_eventrule(self):
self.assertEqual(job.kwargs['event_type'], OBJECT_CREATED)
self.assertEqual(job.kwargs['model_name'], 'site')
self.assertEqual(job.kwargs['data']['id'], response.data['id'])
self.assertEqual(job.kwargs['data']['foo'], 1)
self.assertEqual(len(job.kwargs['data']['tags']), len(response.data['tags']))
self.assertEqual(job.kwargs['snapshots']['postchange']['name'], 'Site 1')
self.assertEqual(job.kwargs['snapshots']['postchange']['tags'], ['Bar', 'Foo'])
Expand Down Expand Up @@ -184,6 +188,7 @@ def test_bulk_create_process_eventrule(self):
self.assertEqual(job.kwargs['event_type'], OBJECT_CREATED)
self.assertEqual(job.kwargs['model_name'], 'site')
self.assertEqual(job.kwargs['data']['id'], response.data[i]['id'])
self.assertEqual(job.kwargs['data']['foo'], 1)
self.assertEqual(len(job.kwargs['data']['tags']), len(response.data[i]['tags']))
self.assertEqual(job.kwargs['snapshots']['postchange']['name'], response.data[i]['name'])
self.assertEqual(job.kwargs['snapshots']['postchange']['tags'], ['Bar', 'Foo'])
Expand Down Expand Up @@ -215,6 +220,7 @@ def test_single_update_process_eventrule(self):
self.assertEqual(job.kwargs['event_type'], OBJECT_UPDATED)
self.assertEqual(job.kwargs['model_name'], 'site')
self.assertEqual(job.kwargs['data']['id'], site.pk)
self.assertEqual(job.kwargs['data']['foo'], 2)
self.assertEqual(len(job.kwargs['data']['tags']), len(response.data['tags']))
self.assertEqual(job.kwargs['snapshots']['prechange']['name'], 'Site 1')
self.assertEqual(job.kwargs['snapshots']['prechange']['tags'], ['Bar', 'Foo'])
Expand Down Expand Up @@ -271,6 +277,7 @@ def test_bulk_update_process_eventrule(self):
self.assertEqual(job.kwargs['event_type'], OBJECT_UPDATED)
self.assertEqual(job.kwargs['model_name'], 'site')
self.assertEqual(job.kwargs['data']['id'], data[i]['id'])
self.assertEqual(job.kwargs['data']['foo'], 2)
self.assertEqual(len(job.kwargs['data']['tags']), len(response.data[i]['tags']))
self.assertEqual(job.kwargs['snapshots']['prechange']['name'], sites[i].name)
self.assertEqual(job.kwargs['snapshots']['prechange']['tags'], ['Bar', 'Foo'])
Expand All @@ -297,6 +304,7 @@ def test_single_delete_process_eventrule(self):
self.assertEqual(job.kwargs['event_type'], OBJECT_DELETED)
self.assertEqual(job.kwargs['model_name'], 'site')
self.assertEqual(job.kwargs['data']['id'], site.pk)
self.assertEqual(job.kwargs['data']['foo'], 3)
self.assertEqual(job.kwargs['snapshots']['prechange']['name'], 'Site 1')
self.assertEqual(job.kwargs['snapshots']['prechange']['tags'], ['Bar', 'Foo'])

Expand Down Expand Up @@ -330,6 +338,7 @@ def test_bulk_delete_process_eventrule(self):
self.assertEqual(job.kwargs['event_type'], OBJECT_DELETED)
self.assertEqual(job.kwargs['model_name'], 'site')
self.assertEqual(job.kwargs['data']['id'], sites[i].pk)
self.assertEqual(job.kwargs['data']['foo'], 3)
self.assertEqual(job.kwargs['snapshots']['prechange']['name'], sites[i].name)
self.assertEqual(job.kwargs['snapshots']['prechange']['tags'], ['Bar', 'Foo'])

Expand Down Expand Up @@ -358,6 +367,7 @@ def dummy_send(_, request, **kwargs):
self.assertEqual(body['username'], 'testuser')
self.assertEqual(body['request_id'], str(request_id))
self.assertEqual(body['data']['name'], 'Site 1')
self.assertEqual(body['data']['foo'], 1)

return HttpResponse()

Expand Down
4 changes: 2 additions & 2 deletions netbox/ipam/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1080,15 +1080,15 @@ def filter_interface_id(self, queryset, name, value):
return queryset.filter(
Q(interfaces_as_tagged=value) |
Q(interfaces_as_untagged=value)
)
).distinct()

def filter_vminterface_id(self, queryset, name, value):
if value is None:
return queryset.none()
return queryset.filter(
Q(vminterfaces_as_tagged=value) |
Q(vminterfaces_as_untagged=value)
)
).distinct()


class ServiceTemplateFilterSet(NetBoxModelFilterSet):
Expand Down
4 changes: 2 additions & 2 deletions netbox/release.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
version: "4.1.10"
version: "4.1.11"
edition: "Community"
published: "2024-12-23"
published: "2025-01-06"
18 changes: 9 additions & 9 deletions netbox/translations/en/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-12-13 05:02+0000\n"
"POT-Creation-Date: 2025-01-04 05:02+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <[email protected]>\n"
Expand Down Expand Up @@ -650,7 +650,7 @@ msgstr ""
#: netbox/dcim/forms/filtersets.py:924 netbox/dcim/forms/filtersets.py:958
#: netbox/dcim/forms/filtersets.py:1059 netbox/dcim/forms/filtersets.py:1170
#: netbox/dcim/tables/devices.py:140 netbox/dcim/tables/devices.py:817
#: netbox/dcim/tables/devices.py:1063 netbox/dcim/tables/modules.py:69
#: netbox/dcim/tables/devices.py:1063 netbox/dcim/tables/modules.py:70
#: netbox/dcim/tables/power.py:74 netbox/dcim/tables/racks.py:126
#: netbox/dcim/tables/sites.py:82 netbox/dcim/tables/sites.py:138
#: netbox/ipam/forms/bulk_edit.py:256 netbox/ipam/forms/bulk_edit.py:306
Expand Down Expand Up @@ -1508,7 +1508,7 @@ msgstr ""
#: netbox/circuits/tables/providers.py:82
#: netbox/circuits/tables/providers.py:107 netbox/dcim/tables/devices.py:1036
#: netbox/dcim/tables/devicetypes.py:92 netbox/dcim/tables/modules.py:29
#: netbox/dcim/tables/modules.py:72 netbox/dcim/tables/power.py:39
#: netbox/dcim/tables/modules.py:73 netbox/dcim/tables/power.py:39
#: netbox/dcim/tables/power.py:96 netbox/dcim/tables/racks.py:84
#: netbox/dcim/tables/racks.py:145 netbox/dcim/tables/racks.py:225
#: netbox/dcim/tables/sites.py:108 netbox/extras/tables/tables.py:582
Expand Down Expand Up @@ -3432,7 +3432,7 @@ msgstr ""
#: netbox/dcim/tables/devices.py:96 netbox/dcim/tables/devices.py:172
#: netbox/dcim/tables/devices.py:940 netbox/dcim/tables/devicetypes.py:80
#: netbox/dcim/tables/devicetypes.py:308 netbox/dcim/tables/modules.py:20
#: netbox/dcim/tables/modules.py:60 netbox/dcim/tables/racks.py:58
#: netbox/dcim/tables/modules.py:61 netbox/dcim/tables/racks.py:58
#: netbox/dcim/tables/racks.py:132 netbox/templates/dcim/devicetype.html:14
#: netbox/templates/dcim/inventoryitem.html:44
#: netbox/templates/dcim/manufacturer.html:33
Expand Down Expand Up @@ -3679,7 +3679,7 @@ msgid "Device Type"
msgstr ""

#: netbox/dcim/forms/bulk_edit.py:598 netbox/dcim/forms/model_forms.py:401
#: netbox/dcim/tables/modules.py:17 netbox/dcim/tables/modules.py:65
#: netbox/dcim/tables/modules.py:17 netbox/dcim/tables/modules.py:66
#: netbox/templates/dcim/module.html:65 netbox/templates/dcim/modulebay.html:66
#: netbox/templates/dcim/moduletype.html:22
msgid "Module Type"
Expand Down Expand Up @@ -3785,7 +3785,7 @@ msgstr ""
#: netbox/dcim/tables/devices.py:597 netbox/dcim/tables/devices.py:697
#: netbox/dcim/tables/devices.py:754 netbox/dcim/tables/devices.py:801
#: netbox/dcim/tables/devices.py:861 netbox/dcim/tables/devices.py:930
#: netbox/dcim/tables/devices.py:1057 netbox/dcim/tables/modules.py:52
#: netbox/dcim/tables/devices.py:1057 netbox/dcim/tables/modules.py:53
#: netbox/extras/forms/filtersets.py:321 netbox/ipam/forms/bulk_import.py:304
#: netbox/ipam/forms/bulk_import.py:505 netbox/ipam/forms/filtersets.py:551
#: netbox/ipam/forms/model_forms.py:323 netbox/ipam/forms/model_forms.py:712
Expand Down Expand Up @@ -6617,7 +6617,7 @@ msgstr ""
msgid "Inventory items"
msgstr ""

#: netbox/dcim/tables/devices.py:305 netbox/dcim/tables/modules.py:56
#: netbox/dcim/tables/devices.py:305 netbox/dcim/tables/modules.py:57
#: netbox/templates/dcim/modulebay.html:17
msgid "Module Bay"
msgstr ""
Expand Down Expand Up @@ -7336,12 +7336,12 @@ msgstr ""
msgid "Show your personal bookmarks"
msgstr ""

#: netbox/extras/events.py:147
#: netbox/extras/events.py:151
#, python-brace-format
msgid "Unknown action type for an event rule: {action_type}"
msgstr ""

#: netbox/extras/events.py:192
#: netbox/extras/events.py:196
#, python-brace-format
msgid "Cannot import events pipeline {name} error: {error}"
msgstr ""
Expand Down
Binary file modified netbox/translations/ru/LC_MESSAGES/django.mo
Binary file not shown.
Loading
Loading