mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-14 01:41:22 -06:00
commit
6ea0c0c3e9
@ -14,7 +14,7 @@ body:
|
||||
attributes:
|
||||
label: NetBox version
|
||||
description: What version of NetBox are you currently running?
|
||||
placeholder: v4.1.2
|
||||
placeholder: v4.1.3
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
2
.github/ISSUE_TEMPLATE/02-bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/02-bug_report.yaml
vendored
@ -26,7 +26,7 @@ body:
|
||||
attributes:
|
||||
label: NetBox Version
|
||||
description: What version of NetBox are you currently running?
|
||||
placeholder: v4.1.2
|
||||
placeholder: v4.1.3
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
11
README.md
11
README.md
@ -7,7 +7,11 @@
|
||||
<a href="https://github.com/netbox-community/netbox/stargazers"><img src="https://img.shields.io/github/stars/netbox-community/netbox?style=flat" alt="GitHub stars" /></a>
|
||||
<a href="https://explore.transifex.com/netbox-community/netbox/"><img src="https://img.shields.io/badge/languages-15-blue" alt="Languages supported" /></a>
|
||||
<a href="https://github.com/netbox-community/netbox/actions/workflows/ci.yml"><img src="https://github.com/netbox-community/netbox/workflows/CI/badge.svg?branch=master" alt="CI status" /></a>
|
||||
<p></p>
|
||||
<p>
|
||||
<strong><a href="https://github.com/netbox-community/netbox/">NetBox Community</a></strong> |
|
||||
<strong><a href="https://netboxlabs.com/netbox-cloud/">NetBox Cloud</a></strong> |
|
||||
<strong><a href="https://netboxlabs.com/netbox-enterprise/">NetBox Enterprise</a></strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
NetBox exists to empower network engineers. Since its release in 2016, it has become the go-to solution for modeling and documenting network infrastructure for thousands of organizations worldwide. As a successor to legacy IPAM and DCIM applications, NetBox provides a cohesive, extensive, and accessible data model for all things networked. By providing a single robust user interface and programmable APIs for everything from cable maps to device configurations, NetBox serves as the central source of truth for the modern network.
|
||||
@ -81,11 +85,6 @@ NetBox automatically logs the creation, modification, and deletion of all manage
|
||||
* The [official documentation](https://docs.netbox.dev) offers a comprehensive introduction.
|
||||
* Check out [our wiki](https://github.com/netbox-community/netbox/wiki/Community-Contributions) for even more projects to get the most out of NetBox!
|
||||
|
||||
<p align="center">
|
||||
<a href="https://netboxlabs.com/netbox-cloud/"><img src="docs/media/misc/netbox_cloud.png" alt="NetBox Cloud" /></a><br />
|
||||
Looking for a managed solution? Check out <strong><a href="https://netboxlabs.com/netbox-cloud/">NetBox Cloud</a></strong> or <strong><a href="https://netboxlabs.com/netbox-enterprise/">NetBox Enterprise</a></strong>!
|
||||
</p>
|
||||
|
||||
## Get Involved
|
||||
|
||||
* Follow [@NetBoxOfficial](https://twitter.com/NetBoxOfficial) on Twitter!
|
||||
|
@ -13,6 +13,9 @@ To enable remote data synchronization, the NetBox administrator first designates
|
||||
!!! info
|
||||
Data backends which connect to external sources typically require the installation of one or more supporting Python libraries. The Git backend requires the [`dulwich`](https://www.dulwich.io/) package, and the S3 backend requires the [`boto3`](https://boto3.amazonaws.com/v1/documentation/api/latest/index.html) package. These must be installed within NetBox's environment to enable these backends.
|
||||
|
||||
!!! info
|
||||
If you are configuring Git and have `HTTP_PROXIES` configured to use the SOCKS protocol, you will also need to install the [`python_socks`](https://pypi.org/project/python-socks/) Python library.
|
||||
|
||||
Each type of remote source has its own configuration parameters. For instance, a git source will ask the user to specify a branch and authentication credentials. Once the source has been created, a synchronization job is run to automatically replicate remote files in the local database.
|
||||
|
||||
The following NetBox models can be associated with replicated data files:
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 6.8 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.8 KiB |
@ -1,6 +1,6 @@
|
||||
# ASNs
|
||||
|
||||
An Autonomous System Number (ASN) is a numeric identifier used in the BGP protocol to identify which [autonomous system](https://en.wikipedia.org/wiki/Autonomous_system_%28Internet%29) a particular prefix is originating and transiting through. NetBox support both 32- and 64- ASNs.
|
||||
An Autonomous System Number (ASN) is a numeric identifier used in the Border Gateway Protocol (BGP) to identify which [autonomous system](https://en.wikipedia.org/wiki/Autonomous_system_%28Internet%29) a particular prefix is originating from or transiting through. NetBox supports both 16- and 32-bit ASNs.
|
||||
|
||||
ASNs must be globally unique within NetBox, and may be allocated from within a [defined range](./asnrange.md). Each ASN may be assigned to multiple [sites](../dcim/site.md).
|
||||
|
||||
@ -8,7 +8,7 @@ ASNs must be globally unique within NetBox, and may be allocated from within a [
|
||||
|
||||
### AS Number
|
||||
|
||||
The 32- or 64-bit AS number.
|
||||
The 16- or 32-bit AS number.
|
||||
|
||||
### RIR
|
||||
|
||||
|
@ -1,5 +1,17 @@
|
||||
# NetBox v4.1
|
||||
|
||||
## v4.1.3 (2024-10-02)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [#17639](https://github.com/netbox-community/netbox/issues/17639) - Add SOCKS support to proxy settings for Git remote data sources
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#17558](https://github.com/netbox-community/netbox/issues/17558) - Raise validation error when attempting to remove a custom field choice in use
|
||||
|
||||
---
|
||||
|
||||
## v4.1.2 (2024-09-26)
|
||||
|
||||
### Enhancements
|
||||
|
@ -8,10 +8,13 @@ from urllib.parse import urlparse
|
||||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from netbox.data_backends import DataBackend
|
||||
from netbox.utils import register_data_backend
|
||||
from utilities.constants import HTTP_PROXY_SUPPORTED_SCHEMAS, HTTP_PROXY_SUPPORTED_SOCK_SCHEMAS
|
||||
from utilities.socks import ProxyPoolManager
|
||||
from .exceptions import SyncError
|
||||
|
||||
__all__ = (
|
||||
@ -67,11 +70,18 @@ class GitBackend(DataBackend):
|
||||
|
||||
# Initialize backend config
|
||||
config = ConfigDict()
|
||||
self.use_socks = False
|
||||
|
||||
# Apply HTTP proxy (if configured)
|
||||
if settings.HTTP_PROXIES and self.url_scheme in ('http', 'https'):
|
||||
if proxy := settings.HTTP_PROXIES.get(self.url_scheme):
|
||||
config.set("http", "proxy", proxy)
|
||||
if settings.HTTP_PROXIES:
|
||||
if proxy := settings.HTTP_PROXIES.get(self.url_scheme, None):
|
||||
if urlparse(proxy).scheme not in HTTP_PROXY_SUPPORTED_SCHEMAS:
|
||||
raise ImproperlyConfigured(f"Unsupported Git DataSource proxy scheme: {urlparse(proxy).scheme}")
|
||||
|
||||
if self.url_scheme in ('http', 'https'):
|
||||
config.set("http", "proxy", proxy)
|
||||
if urlparse(proxy).scheme in HTTP_PROXY_SUPPORTED_SOCK_SCHEMAS:
|
||||
self.use_socks = True
|
||||
|
||||
return config
|
||||
|
||||
@ -87,6 +97,10 @@ class GitBackend(DataBackend):
|
||||
"errstream": porcelain.NoneStream(),
|
||||
}
|
||||
|
||||
# check if using socks for proxy - if so need to use custom pool_manager
|
||||
if self.use_socks:
|
||||
clone_args['pool_manager'] = ProxyPoolManager(settings.HTTP_PROXIES.get(self.url_scheme))
|
||||
|
||||
if self.url_scheme in ('http', 'https'):
|
||||
if self.params.get('username'):
|
||||
clone_args.update(
|
||||
|
@ -785,6 +785,12 @@ class CustomFieldChoiceSet(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Cache the initial set of choices for comparison under clean()
|
||||
self._original_extra_choices = self.__dict__.get('extra_choices')
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('extras:customfieldchoiceset', args=[self.pk])
|
||||
|
||||
@ -818,6 +824,32 @@ class CustomFieldChoiceSet(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel
|
||||
if not self.base_choices and not self.extra_choices:
|
||||
raise ValidationError(_("Must define base or extra choices."))
|
||||
|
||||
# Check whether any choices have been removed. If so, check whether any of the removed
|
||||
# choices are still set in custom field data for any object.
|
||||
original_choices = set([
|
||||
c[0] for c in self._original_extra_choices
|
||||
]) if self._original_extra_choices else set()
|
||||
current_choices = set([
|
||||
c[0] for c in self.extra_choices
|
||||
]) if self.extra_choices else set()
|
||||
if removed_choices := original_choices - current_choices:
|
||||
for custom_field in self.choices_for.all():
|
||||
for object_type in custom_field.object_types.all():
|
||||
model = object_type.model_class()
|
||||
for choice in removed_choices:
|
||||
# Form the query based on the type of custom field
|
||||
if custom_field.type == CustomFieldTypeChoices.TYPE_MULTISELECT:
|
||||
query_args = {f"custom_field_data__{custom_field.name}__contains": choice}
|
||||
else:
|
||||
query_args = {f"custom_field_data__{custom_field.name}": choice}
|
||||
# Raise a ValidationError if there are any objects which still reference the removed choice
|
||||
if model.objects.filter(models.Q(**query_args)).exists():
|
||||
raise ValidationError(
|
||||
_(
|
||||
"Cannot remove choice {choice} as there are {model} objects which reference it."
|
||||
).format(choice=choice, model=object_type)
|
||||
)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
# Sort choices if alphabetical ordering is enforced
|
||||
|
@ -343,6 +343,74 @@ class CustomFieldTest(TestCase):
|
||||
instance.refresh_from_db()
|
||||
self.assertIsNone(instance.custom_field_data.get(cf.name))
|
||||
|
||||
def test_remove_selected_choice(self):
|
||||
"""
|
||||
Removing a ChoiceSet choice that is referenced by an object should raise
|
||||
a ValidationError exception.
|
||||
"""
|
||||
CHOICES = (
|
||||
('a', 'Option A'),
|
||||
('b', 'Option B'),
|
||||
('c', 'Option C'),
|
||||
('d', 'Option D'),
|
||||
)
|
||||
|
||||
# Create a set of custom field choices
|
||||
choice_set = CustomFieldChoiceSet.objects.create(
|
||||
name='Custom Field Choice Set 1',
|
||||
extra_choices=CHOICES
|
||||
)
|
||||
|
||||
# Create a select custom field
|
||||
cf = CustomField.objects.create(
|
||||
name='select_field',
|
||||
type=CustomFieldTypeChoices.TYPE_SELECT,
|
||||
required=False,
|
||||
choice_set=choice_set
|
||||
)
|
||||
cf.object_types.set([self.object_type])
|
||||
|
||||
# Create a multi-select custom field
|
||||
cf_multiselect = CustomField.objects.create(
|
||||
name='multiselect_field',
|
||||
type=CustomFieldTypeChoices.TYPE_MULTISELECT,
|
||||
required=False,
|
||||
choice_set=choice_set
|
||||
)
|
||||
cf_multiselect.object_types.set([self.object_type])
|
||||
|
||||
# Assign a choice for both custom fields on an object
|
||||
instance = Site.objects.first()
|
||||
instance.custom_field_data[cf.name] = 'a'
|
||||
instance.custom_field_data[cf_multiselect.name] = ['b', 'c']
|
||||
instance.save()
|
||||
|
||||
# Attempting to delete a selected choice should fail
|
||||
with self.assertRaises(ValidationError):
|
||||
choice_set.extra_choices = (
|
||||
('b', 'Option B'),
|
||||
('c', 'Option C'),
|
||||
('d', 'Option D'),
|
||||
)
|
||||
choice_set.full_clean()
|
||||
|
||||
# Attempting to delete either of the multi-select choices should fail
|
||||
with self.assertRaises(ValidationError):
|
||||
choice_set.extra_choices = (
|
||||
('a', 'Option A'),
|
||||
('b', 'Option B'),
|
||||
('d', 'Option D'),
|
||||
)
|
||||
choice_set.full_clean()
|
||||
|
||||
# Removing a non-selected choice should succeed
|
||||
choice_set.extra_choices = (
|
||||
('a', 'Option A'),
|
||||
('b', 'Option B'),
|
||||
('c', 'Option C'),
|
||||
)
|
||||
choice_set.full_clean()
|
||||
|
||||
def test_object_field(self):
|
||||
value = VLAN.objects.create(name='VLAN 1', vid=1).pk
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
version: "4.1.2"
|
||||
version: "4.1.3"
|
||||
edition: "Community"
|
||||
published: "2024-09-26"
|
||||
published: "2024-10-02"
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-09-25 05:02+0000\n"
|
||||
"POT-Creation-Date: 2024-10-02 05:01+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -82,8 +82,8 @@ msgstr ""
|
||||
|
||||
#: netbox/circuits/choices.py:21 netbox/dcim/choices.py:20
|
||||
#: netbox/dcim/choices.py:102 netbox/dcim/choices.py:185
|
||||
#: netbox/dcim/choices.py:231 netbox/dcim/choices.py:1522
|
||||
#: netbox/dcim/choices.py:1598 netbox/dcim/choices.py:1648
|
||||
#: netbox/dcim/choices.py:231 netbox/dcim/choices.py:1524
|
||||
#: netbox/dcim/choices.py:1600 netbox/dcim/choices.py:1650
|
||||
#: netbox/virtualization/choices.py:20 netbox/virtualization/choices.py:45
|
||||
#: netbox/vpn/choices.py:18
|
||||
msgid "Planned"
|
||||
@ -96,7 +96,7 @@ msgstr ""
|
||||
#: netbox/circuits/choices.py:23 netbox/core/tables/tasks.py:22
|
||||
#: netbox/dcim/choices.py:22 netbox/dcim/choices.py:103
|
||||
#: netbox/dcim/choices.py:184 netbox/dcim/choices.py:230
|
||||
#: netbox/dcim/choices.py:1597 netbox/dcim/choices.py:1647
|
||||
#: netbox/dcim/choices.py:1599 netbox/dcim/choices.py:1649
|
||||
#: netbox/extras/tables/tables.py:495 netbox/ipam/choices.py:31
|
||||
#: netbox/ipam/choices.py:49 netbox/ipam/choices.py:69
|
||||
#: netbox/ipam/choices.py:154 netbox/templates/extras/configcontext.html:25
|
||||
@ -107,8 +107,8 @@ msgid "Active"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/choices.py:24 netbox/dcim/choices.py:183
|
||||
#: netbox/dcim/choices.py:229 netbox/dcim/choices.py:1596
|
||||
#: netbox/dcim/choices.py:1649 netbox/virtualization/choices.py:24
|
||||
#: netbox/dcim/choices.py:229 netbox/dcim/choices.py:1598
|
||||
#: netbox/dcim/choices.py:1651 netbox/virtualization/choices.py:24
|
||||
#: netbox/virtualization/choices.py:43
|
||||
msgid "Offline"
|
||||
msgstr ""
|
||||
@ -121,7 +121,7 @@ msgstr ""
|
||||
msgid "Decommissioned"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/circuits/choices.py:90 netbox/dcim/choices.py:1609
|
||||
#: netbox/circuits/choices.py:90 netbox/dcim/choices.py:1611
|
||||
#: netbox/tenancy/choices.py:17
|
||||
msgid "Primary"
|
||||
msgstr ""
|
||||
@ -1587,7 +1587,7 @@ msgstr ""
|
||||
#: netbox/core/choices.py:22 netbox/core/choices.py:59
|
||||
#: netbox/core/constants.py:20 netbox/core/tables/tasks.py:34
|
||||
#: netbox/dcim/choices.py:187 netbox/dcim/choices.py:233
|
||||
#: netbox/dcim/choices.py:1599 netbox/virtualization/choices.py:47
|
||||
#: netbox/dcim/choices.py:1601 netbox/virtualization/choices.py:47
|
||||
msgid "Failed"
|
||||
msgstr ""
|
||||
|
||||
@ -1653,42 +1653,42 @@ msgstr ""
|
||||
msgid "Cancelled"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/data_backends.py:29 netbox/core/tables/plugins.py:51
|
||||
#: netbox/core/data_backends.py:32 netbox/core/tables/plugins.py:51
|
||||
#: netbox/templates/core/plugin.html:87
|
||||
#: netbox/templates/dcim/interface.html:216
|
||||
msgid "Local"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/data_backends.py:47 netbox/core/tables/change_logging.py:20
|
||||
#: netbox/core/data_backends.py:50 netbox/core/tables/change_logging.py:20
|
||||
#: netbox/templates/account/profile.html:15 netbox/templates/users/user.html:17
|
||||
#: netbox/users/tables.py:31
|
||||
msgid "Username"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/data_backends.py:49 netbox/core/data_backends.py:55
|
||||
#: netbox/core/data_backends.py:52 netbox/core/data_backends.py:58
|
||||
msgid "Only used for cloning with HTTP(S)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/data_backends.py:53 netbox/templates/account/base.html:23
|
||||
#: netbox/core/data_backends.py:56 netbox/templates/account/base.html:23
|
||||
#: netbox/templates/account/password.html:12
|
||||
#: netbox/users/forms/model_forms.py:171
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/data_backends.py:59
|
||||
#: netbox/core/data_backends.py:62
|
||||
msgid "Branch"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/data_backends.py:106
|
||||
#: netbox/core/data_backends.py:120
|
||||
#, python-brace-format
|
||||
msgid "Fetching remote data failed ({name}): {error}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/data_backends.py:119
|
||||
#: netbox/core/data_backends.py:133
|
||||
msgid "AWS access key ID"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/data_backends.py:123
|
||||
#: netbox/core/data_backends.py:137
|
||||
msgid "AWS secret access key"
|
||||
msgstr ""
|
||||
|
||||
@ -1911,7 +1911,7 @@ msgstr ""
|
||||
msgid "Rack Elevations"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/core/forms/model_forms.py:157 netbox/dcim/choices.py:1510
|
||||
#: netbox/core/forms/model_forms.py:157 netbox/dcim/choices.py:1512
|
||||
#: netbox/dcim/forms/bulk_edit.py:969 netbox/dcim/forms/bulk_edit.py:1357
|
||||
#: netbox/dcim/forms/bulk_edit.py:1375 netbox/dcim/tables/racks.py:158
|
||||
#: netbox/netbox/navigation/menu.py:291 netbox/netbox/navigation/menu.py:295
|
||||
@ -2477,7 +2477,7 @@ msgid "Staging"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:23 netbox/dcim/choices.py:189
|
||||
#: netbox/dcim/choices.py:234 netbox/dcim/choices.py:1523
|
||||
#: netbox/dcim/choices.py:234 netbox/dcim/choices.py:1525
|
||||
#: netbox/virtualization/choices.py:23 netbox/virtualization/choices.py:48
|
||||
msgid "Decommissioning"
|
||||
msgstr ""
|
||||
@ -2541,7 +2541,7 @@ msgstr ""
|
||||
msgid "Millimeters"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:115 netbox/dcim/choices.py:1545
|
||||
#: netbox/dcim/choices.py:115 netbox/dcim/choices.py:1547
|
||||
msgid "Inches"
|
||||
msgstr ""
|
||||
|
||||
@ -2630,7 +2630,7 @@ msgid "Side to rear"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:209 netbox/dcim/choices.py:253
|
||||
#: netbox/dcim/choices.py:1295
|
||||
#: netbox/dcim/choices.py:1297
|
||||
msgid "Passive"
|
||||
msgstr ""
|
||||
|
||||
@ -2659,8 +2659,8 @@ msgid "Proprietary"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:575 netbox/dcim/choices.py:818
|
||||
#: netbox/dcim/choices.py:1211 netbox/dcim/choices.py:1213
|
||||
#: netbox/dcim/choices.py:1439 netbox/dcim/choices.py:1441
|
||||
#: netbox/dcim/choices.py:1213 netbox/dcim/choices.py:1215
|
||||
#: netbox/dcim/choices.py:1441 netbox/dcim/choices.py:1443
|
||||
#: netbox/netbox/navigation/menu.py:200
|
||||
msgid "Other"
|
||||
msgstr ""
|
||||
@ -2673,11 +2673,11 @@ msgstr ""
|
||||
msgid "Physical"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:849 netbox/dcim/choices.py:1016
|
||||
#: netbox/dcim/choices.py:849 netbox/dcim/choices.py:1017
|
||||
msgid "Virtual"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:850 netbox/dcim/choices.py:1089
|
||||
#: netbox/dcim/choices.py:850 netbox/dcim/choices.py:1091
|
||||
#: netbox/dcim/forms/bulk_edit.py:1515 netbox/dcim/forms/filtersets.py:1330
|
||||
#: netbox/dcim/forms/model_forms.py:988 netbox/dcim/forms/model_forms.py:1397
|
||||
#: netbox/netbox/navigation/menu.py:140 netbox/netbox/navigation/menu.py:144
|
||||
@ -2685,11 +2685,11 @@ msgstr ""
|
||||
msgid "Wireless"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1014
|
||||
#: netbox/dcim/choices.py:1015
|
||||
msgid "Virtual interfaces"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1017 netbox/dcim/forms/bulk_edit.py:1410
|
||||
#: netbox/dcim/choices.py:1018 netbox/dcim/forms/bulk_edit.py:1410
|
||||
#: netbox/dcim/forms/bulk_import.py:840 netbox/dcim/forms/model_forms.py:974
|
||||
#: netbox/dcim/tables/devices.py:657 netbox/templates/dcim/interface.html:106
|
||||
#: netbox/templates/virtualization/vminterface.html:43
|
||||
@ -2699,27 +2699,27 @@ msgstr ""
|
||||
msgid "Bridge"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1018
|
||||
#: netbox/dcim/choices.py:1019
|
||||
msgid "Link Aggregation Group (LAG)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1022
|
||||
#: netbox/dcim/choices.py:1023
|
||||
msgid "Ethernet (fixed)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1037
|
||||
#: netbox/dcim/choices.py:1038
|
||||
msgid "Ethernet (modular)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1073
|
||||
#: netbox/dcim/choices.py:1075
|
||||
msgid "Ethernet (backplane)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1105
|
||||
#: netbox/dcim/choices.py:1107
|
||||
msgid "Cellular"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1157 netbox/dcim/forms/filtersets.py:383
|
||||
#: netbox/dcim/choices.py:1159 netbox/dcim/forms/filtersets.py:383
|
||||
#: netbox/dcim/forms/filtersets.py:809 netbox/dcim/forms/filtersets.py:963
|
||||
#: netbox/dcim/forms/filtersets.py:1542
|
||||
#: netbox/templates/dcim/inventoryitem.html:52
|
||||
@ -2727,130 +2727,130 @@ msgstr ""
|
||||
msgid "Serial"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1172
|
||||
#: netbox/dcim/choices.py:1174
|
||||
msgid "Coaxial"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1192
|
||||
#: netbox/dcim/choices.py:1194
|
||||
msgid "Stacking"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1242
|
||||
#: netbox/dcim/choices.py:1244
|
||||
msgid "Half"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1243
|
||||
#: netbox/dcim/choices.py:1245
|
||||
msgid "Full"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1244 netbox/netbox/preferences.py:31
|
||||
#: netbox/dcim/choices.py:1246 netbox/netbox/preferences.py:31
|
||||
#: netbox/wireless/choices.py:480
|
||||
msgid "Auto"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1255
|
||||
#: netbox/dcim/choices.py:1257
|
||||
msgid "Access"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1256 netbox/ipam/tables/vlans.py:172
|
||||
#: netbox/dcim/choices.py:1258 netbox/ipam/tables/vlans.py:172
|
||||
#: netbox/ipam/tables/vlans.py:217
|
||||
#: netbox/templates/dcim/inc/interface_vlans_table.html:7
|
||||
msgid "Tagged"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1257
|
||||
#: netbox/dcim/choices.py:1259
|
||||
msgid "Tagged (All)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1286
|
||||
#: netbox/dcim/choices.py:1288
|
||||
msgid "IEEE Standard"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1297
|
||||
#: netbox/dcim/choices.py:1299
|
||||
msgid "Passive 24V (2-pair)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1298
|
||||
#: netbox/dcim/choices.py:1300
|
||||
msgid "Passive 24V (4-pair)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1299
|
||||
#: netbox/dcim/choices.py:1301
|
||||
msgid "Passive 48V (2-pair)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1300
|
||||
#: netbox/dcim/choices.py:1302
|
||||
msgid "Passive 48V (4-pair)"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1370 netbox/dcim/choices.py:1480
|
||||
#: netbox/dcim/choices.py:1372 netbox/dcim/choices.py:1482
|
||||
msgid "Copper"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1393
|
||||
#: netbox/dcim/choices.py:1395
|
||||
msgid "Fiber Optic"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1426 netbox/dcim/choices.py:1509
|
||||
#: netbox/dcim/choices.py:1428 netbox/dcim/choices.py:1511
|
||||
msgid "USB"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1496
|
||||
#: netbox/dcim/choices.py:1498
|
||||
msgid "Fiber"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1521 netbox/dcim/forms/filtersets.py:1227
|
||||
#: netbox/dcim/choices.py:1523 netbox/dcim/forms/filtersets.py:1227
|
||||
msgid "Connected"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1540 netbox/wireless/choices.py:497
|
||||
#: netbox/dcim/choices.py:1542 netbox/wireless/choices.py:497
|
||||
msgid "Kilometers"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1541 netbox/templates/dcim/cable_trace.html:65
|
||||
#: netbox/dcim/choices.py:1543 netbox/templates/dcim/cable_trace.html:65
|
||||
#: netbox/wireless/choices.py:498
|
||||
msgid "Meters"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1542
|
||||
#: netbox/dcim/choices.py:1544
|
||||
msgid "Centimeters"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1543 netbox/wireless/choices.py:499
|
||||
#: netbox/dcim/choices.py:1545 netbox/wireless/choices.py:499
|
||||
msgid "Miles"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1544 netbox/templates/dcim/cable_trace.html:66
|
||||
#: netbox/dcim/choices.py:1546 netbox/templates/dcim/cable_trace.html:66
|
||||
#: netbox/wireless/choices.py:500
|
||||
msgid "Feet"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1560 netbox/templates/dcim/device.html:327
|
||||
#: netbox/dcim/choices.py:1562 netbox/templates/dcim/device.html:327
|
||||
#: netbox/templates/dcim/rack.html:107
|
||||
msgid "Kilograms"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1561
|
||||
#: netbox/dcim/choices.py:1563
|
||||
msgid "Grams"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1562 netbox/templates/dcim/device.html:328
|
||||
#: netbox/dcim/choices.py:1564 netbox/templates/dcim/device.html:328
|
||||
#: netbox/templates/dcim/rack.html:108
|
||||
msgid "Pounds"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1563
|
||||
#: netbox/dcim/choices.py:1565
|
||||
msgid "Ounces"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1610
|
||||
#: netbox/dcim/choices.py:1612
|
||||
msgid "Redundant"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1631
|
||||
#: netbox/dcim/choices.py:1633
|
||||
msgid "Single phase"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/dcim/choices.py:1632
|
||||
#: netbox/dcim/choices.py:1634
|
||||
msgid "Three-phase"
|
||||
msgstr ""
|
||||
|
||||
@ -8233,10 +8233,17 @@ msgstr ""
|
||||
msgid "custom field choice sets"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:819
|
||||
#: netbox/extras/models/customfields.py:825
|
||||
msgid "Must define base or extra choices."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/customfields.py:849
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Cannot remove choice {choice} as there are {model} objects which reference "
|
||||
"it."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/extras/models/dashboard.py:18
|
||||
msgid "layout"
|
||||
msgstr ""
|
||||
@ -13749,11 +13756,6 @@ msgstr ""
|
||||
msgid "Disk Space"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/virtualization/cluster.html:72
|
||||
msgctxt "Abbreviation for gigabyte"
|
||||
msgid "GB"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/templates/virtualization/cluster/base.html:18
|
||||
msgid "Add Virtual Machine"
|
||||
msgstr ""
|
||||
@ -14493,7 +14495,7 @@ msgid "Invalid value for a multiple choice field: {value}"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/utilities/forms/fields/csv.py:57
|
||||
#: netbox/utilities/forms/fields/csv.py:74
|
||||
#: netbox/utilities/forms/fields/csv.py:78
|
||||
#, python-format
|
||||
msgid "Object not found: %(value)s"
|
||||
msgstr ""
|
||||
@ -14504,11 +14506,16 @@ msgid ""
|
||||
"\"{value}\" is not a unique value for this field; multiple objects were found"
|
||||
msgstr ""
|
||||
|
||||
#: netbox/utilities/forms/fields/csv.py:97
|
||||
msgid "Object type must be specified as \"<app>.<model>\""
|
||||
#: netbox/utilities/forms/fields/csv.py:69
|
||||
#, python-brace-format
|
||||
msgid "\"{field_name}\" is an invalid accessor field name."
|
||||
msgstr ""
|
||||
|
||||
#: netbox/utilities/forms/fields/csv.py:101
|
||||
msgid "Object type must be specified as \"<app>.<model>\""
|
||||
msgstr ""
|
||||
|
||||
#: netbox/utilities/forms/fields/csv.py:105
|
||||
msgid "Invalid object type"
|
||||
msgstr ""
|
||||
|
||||
|
@ -93,3 +93,7 @@ HTML_ALLOWED_ATTRIBUTES = {
|
||||
"td": {"align"},
|
||||
"th": {"align"},
|
||||
}
|
||||
|
||||
HTTP_PROXY_SUPPORTED_SOCK_SCHEMAS = ['socks4', 'socks4a', 'socks4h', 'socks5', 'socks5a', 'socks5h']
|
||||
HTTP_PROXY_SOCK_RDNS_SCHEMAS = ['socks4h', 'socks4a', 'socks5h', 'socks5a']
|
||||
HTTP_PROXY_SUPPORTED_SCHEMAS = ['http', 'https', 'socks4', 'socks4a', 'socks4h', 'socks5', 'socks5a', 'socks5h']
|
||||
|
101
netbox/utilities/socks.py
Normal file
101
netbox/utilities/socks.py
Normal file
@ -0,0 +1,101 @@
|
||||
import logging
|
||||
|
||||
from urllib.parse import urlparse
|
||||
from urllib3 import PoolManager, HTTPConnectionPool, HTTPSConnectionPool
|
||||
from urllib3.connection import HTTPConnection, HTTPSConnection
|
||||
from .constants import HTTP_PROXY_SOCK_RDNS_SCHEMAS
|
||||
|
||||
|
||||
logger = logging.getLogger('netbox.utilities')
|
||||
|
||||
|
||||
class ProxyHTTPConnection(HTTPConnection):
|
||||
"""
|
||||
A Proxy connection class that uses a SOCK proxy - used to create
|
||||
a urllib3 PoolManager that routes connections via the proxy.
|
||||
This is for an HTTP (not HTTPS) connection
|
||||
"""
|
||||
use_rdns = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
socks_options = kwargs.pop('_socks_options')
|
||||
self._proxy_url = socks_options['proxy_url']
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def _new_conn(self):
|
||||
try:
|
||||
from python_socks.sync import Proxy
|
||||
except ModuleNotFoundError as e:
|
||||
logger.info("Configuring an HTTP proxy using SOCKS requires the python_socks library. Check that it has been installed.")
|
||||
raise e
|
||||
|
||||
proxy = Proxy.from_url(self._proxy_url, rdns=self.use_rdns)
|
||||
return proxy.connect(
|
||||
dest_host=self.host,
|
||||
dest_port=self.port,
|
||||
timeout=self.timeout
|
||||
)
|
||||
|
||||
|
||||
class ProxyHTTPSConnection(ProxyHTTPConnection, HTTPSConnection):
|
||||
"""
|
||||
A Proxy connection class for an HTTPS (not HTTP) connection.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class RdnsProxyHTTPConnection(ProxyHTTPConnection):
|
||||
"""
|
||||
A Proxy connection class for an HTTP remote-dns connection.
|
||||
I.E. socks4a, socks4h, socks5a, socks5h
|
||||
"""
|
||||
use_rdns = True
|
||||
|
||||
|
||||
class RdnsProxyHTTPSConnection(ProxyHTTPSConnection):
|
||||
"""
|
||||
A Proxy connection class for an HTTPS remote-dns connection.
|
||||
I.E. socks4a, socks4h, socks5a, socks5h
|
||||
"""
|
||||
use_rdns = True
|
||||
|
||||
|
||||
class ProxyHTTPConnectionPool(HTTPConnectionPool):
|
||||
ConnectionCls = ProxyHTTPConnection
|
||||
|
||||
|
||||
class ProxyHTTPSConnectionPool(HTTPSConnectionPool):
|
||||
ConnectionCls = ProxyHTTPSConnection
|
||||
|
||||
|
||||
class RdnsProxyHTTPConnectionPool(HTTPConnectionPool):
|
||||
ConnectionCls = RdnsProxyHTTPConnection
|
||||
|
||||
|
||||
class RdnsProxyHTTPSConnectionPool(HTTPSConnectionPool):
|
||||
ConnectionCls = RdnsProxyHTTPSConnection
|
||||
|
||||
|
||||
class ProxyPoolManager(PoolManager):
|
||||
def __init__(self, proxy_url, timeout=5, num_pools=10, headers=None, **connection_pool_kw):
|
||||
# python_socks uses rdns param to denote remote DNS parsing and
|
||||
# doesn't accept the 'h' or 'a' in the proxy URL
|
||||
if use_rdns := urlparse(proxy_url).scheme in HTTP_PROXY_SOCK_RDNS_SCHEMAS:
|
||||
proxy_url = proxy_url.replace('socks5h:', 'socks5:').replace('socks5a:', 'socks5:')
|
||||
proxy_url = proxy_url.replace('socks4h:', 'socks4:').replace('socks4a:', 'socks4:')
|
||||
|
||||
connection_pool_kw['_socks_options'] = {'proxy_url': proxy_url}
|
||||
connection_pool_kw['timeout'] = timeout
|
||||
|
||||
super().__init__(num_pools, headers, **connection_pool_kw)
|
||||
|
||||
if use_rdns:
|
||||
self.pool_classes_by_scheme = {
|
||||
'http': RdnsProxyHTTPConnectionPool,
|
||||
'https': RdnsProxyHTTPSConnectionPool,
|
||||
}
|
||||
else:
|
||||
self.pool_classes_by_scheme = {
|
||||
'http': ProxyHTTPConnectionPool,
|
||||
'https': ProxyHTTPSConnectionPool,
|
||||
}
|
@ -10,7 +10,7 @@ django-prometheus==2.3.1
|
||||
django-redis==5.4.0
|
||||
django-rich==1.11.0
|
||||
django-rq==2.10.2
|
||||
django-taggit==6.0.0
|
||||
django-taggit==6.1.0
|
||||
django-tables2==2.7.0
|
||||
django-timezone-field==7.0
|
||||
djangorestframework==3.15.2
|
||||
@ -20,12 +20,12 @@ feedparser==6.0.11
|
||||
gunicorn==23.0.0
|
||||
Jinja2==3.1.4
|
||||
Markdown==3.7
|
||||
mkdocs-material==9.5.38
|
||||
mkdocs-material==9.5.39
|
||||
mkdocstrings[python-legacy]==0.26.1
|
||||
netaddr==1.3.0
|
||||
nh3==0.2.18
|
||||
Pillow==10.4.0
|
||||
psycopg[c,pool]==3.2.2
|
||||
psycopg[c,pool]==3.2.3
|
||||
PyYAML==6.0.2
|
||||
requests==2.32.3
|
||||
social-auth-app-django==5.4.2
|
||||
|
Loading…
Reference in New Issue
Block a user