mirror of
https://github.com/netbox-community/netbox.git
synced 2026-01-19 10:08:44 -06:00
Compare commits
30 Commits
21102-fix-
...
20490-rest
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc6a54ec21 | ||
|
|
586bc132b6 | ||
|
|
52a2b934a0 | ||
|
|
3e2a26984f | ||
|
|
f5f0c19860 | ||
|
|
8da9b11ab8 | ||
|
|
ca67fa9999 | ||
|
|
eff768192e | ||
|
|
1e297d55ee | ||
|
|
fdb987ef91 | ||
|
|
b5a23db43c | ||
|
|
366b69aff7 | ||
|
|
c3e8c5e69c | ||
|
|
b55f36469d | ||
|
|
1c46215cd5 | ||
|
|
7fded2fd87 | ||
|
|
0ddc5805c4 | ||
|
|
c1bbc026e2 | ||
|
|
8cbfe94fba | ||
|
|
434334d927 | ||
|
|
fff99fd3ff | ||
|
|
6bd083b7ed | ||
|
|
f38faf2e01 | ||
|
|
f4892caa51 | ||
|
|
e60807adc5 | ||
|
|
e14934e5a5 | ||
|
|
ae03723e43 | ||
|
|
c0f79df91f | ||
|
|
edbfd0bae6 | ||
|
|
a54ad24b47 |
16
.github/ISSUE_TEMPLATE/06-deprecation.yaml
vendored
16
.github/ISSUE_TEMPLATE/06-deprecation.yaml
vendored
@@ -1,20 +1,26 @@
|
|||||||
---
|
---
|
||||||
name: 🗑️ Deprecation
|
name: ⚠️ Deprecation
|
||||||
type: Deprecation
|
type: Deprecation
|
||||||
description: The removal of an existing feature or resource
|
description: Designation of a feature or behavior that will be removed in a future release
|
||||||
labels: ["netbox", "type: deprecation"]
|
labels: ["netbox", "type: deprecation"]
|
||||||
body:
|
body:
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Proposed Changes
|
label: Deprecated Functionality
|
||||||
description: >
|
description: >
|
||||||
Describe in detail the proposed changes. What is being removed?
|
Describe the feature(s) and/or behavior that is being flagged for deprecation.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Scheduled removal
|
||||||
|
description: In what future release will the deprecated functionality be removed?
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Justification
|
label: Justification
|
||||||
description: Please provide justification for the proposed change(s).
|
description: Please provide justification for the deprecation.
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
|||||||
20
.github/ISSUE_TEMPLATE/07-feature_removal.yaml
vendored
Normal file
20
.github/ISSUE_TEMPLATE/07-feature_removal.yaml
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: 🗑️ Feature Removal
|
||||||
|
type: Removal
|
||||||
|
description: The removal of a deprecated feature or resource
|
||||||
|
labels: ["netbox", "type: removal"]
|
||||||
|
body:
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Deprecation Issue
|
||||||
|
description: Specify the issue in which this deprecation was announced.
|
||||||
|
placeholder: "#1234"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Summary of Changes
|
||||||
|
description: >
|
||||||
|
List all changes necessary to remove the deprecated feature or resource.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -30,13 +30,13 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v3
|
uses: github/codeql-action/init@v4
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
build-mode: ${{ matrix.build-mode }}
|
build-mode: ${{ matrix.build-mode }}
|
||||||
config-file: .github/codeql/codeql-config.yml
|
config-file: .github/codeql/codeql-config.yml
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v3
|
uses: github/codeql-action/analyze@v4
|
||||||
with:
|
with:
|
||||||
category: "/language:${{matrix.language}}"
|
category: "/language:${{matrix.language}}"
|
||||||
|
|||||||
@@ -18,7 +18,17 @@ They can also be used as a mechanism for validating the integrity of data within
|
|||||||
Custom scripts are Python code which exists outside the NetBox code base, so they can be updated and changed without interfering with the core NetBox installation. And because they're completely custom, there is no inherent limitation on what a script can accomplish.
|
Custom scripts are Python code which exists outside the NetBox code base, so they can be updated and changed without interfering with the core NetBox installation. And because they're completely custom, there is no inherent limitation on what a script can accomplish.
|
||||||
|
|
||||||
!!! danger "Only install trusted scripts"
|
!!! danger "Only install trusted scripts"
|
||||||
Custom scripts have unrestricted access to change anything in the databse and are inherently unsafe and should only be installed and run from trusted sources. You should also review and set permissions for who can run scripts if the script can modify any data.
|
Custom scripts have unrestricted access to change anything in the database and are inherently unsafe and should only be installed and run from trusted sources. You should also review and set permissions for who can run scripts if the script can modify any data.
|
||||||
|
|
||||||
|
!!! tip "Permissions for Custom Scripts"
|
||||||
|
A user can be granted permissions on all Custom Scripts via the "Managed File" object-level permission. To further restrict a user to only be able to access certain scripts, create an additional permission on the "Script" object type, with appropriate queryset-style constraints matching fields available on Script. For example:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name__in": [
|
||||||
|
"MyScript"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Writing Custom Scripts
|
## Writing Custom Scripts
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ Change records are exposed in the API via the read-only endpoint `/api/extras/ob
|
|||||||
|
|
||||||
## User Messages
|
## User Messages
|
||||||
|
|
||||||
!!! info "This feature was introduced in NetBox v4.4."
|
When creating, modifying, or deleting an object in NetBox, a user has the option of recording an arbitrary message (up to 200 characters) that will appear in the change record. This can be helpful to capture additional context, such as the reason for a change or a reference to an external ticket.
|
||||||
|
|
||||||
When creating, modifying, or deleting an object in NetBox, a user has the option of recording an arbitrary message that will appear in the change record. This can be helpful to capture additional context, such as the reason for the change.
|
When editing an object via the web UI, the "Changelog message" field appears at the bottom of the form. This field is optional. The changelog message field is available in object create forms, object edit forms, delete confirmation dialogs, and bulk operations.
|
||||||
|
|
||||||
|
For information on including changelog messages when making changes via the REST API, see [Changelog Messages](../integrations/rest-api.md#changelog-messages).
|
||||||
|
|
||||||
## Correlating Changes by Request
|
## Correlating Changes by Request
|
||||||
|
|
||||||
|
|||||||
@@ -610,9 +610,7 @@ http://netbox/api/dcim/sites/ \
|
|||||||
|
|
||||||
## Changelog Messages
|
## Changelog Messages
|
||||||
|
|
||||||
!!! info "This feature was introduced in NetBox v4.4."
|
Most objects in NetBox support [change logging](../features/change-logging.md), which generates a detailed record each time an object is created, modified, or deleted. Additionally, users can attach a message to the change record as well. This is accomplished via the REST API by including a `changelog_message` field in the object representation.
|
||||||
|
|
||||||
Most objects in NetBox support [change logging](../features/change-logging.md), which generates a detailed record each time an object is created, modified, or deleted. Beginning in NetBox v4.4, users can attach a message to the change record as well. This is accomplished via the REST API by including a `changelog_message` field in the object representation.
|
|
||||||
|
|
||||||
For example, the following API request will create a new site and record a message in the resulting changelog entry:
|
For example, the following API request will create a new site and record a message in the resulting changelog entry:
|
||||||
|
|
||||||
@@ -628,7 +626,7 @@ http://netbox/api/dcim/sites/ \
|
|||||||
}'
|
}'
|
||||||
```
|
```
|
||||||
|
|
||||||
This approach works when creating, modifying, or deleting objects, either individually or in bulk.
|
This approach works when creating, modifying, or deleting objects, either individually or in bulk. For more information about change logging, see [Change Logging](../features/change-logging.md).
|
||||||
|
|
||||||
## Uploading Files
|
## Uploading Files
|
||||||
|
|
||||||
|
|||||||
@@ -44,3 +44,4 @@ class DataFileSerializer(NetBoxModelSerializer):
|
|||||||
'id', 'url', 'display_url', 'display', 'source', 'path', 'last_updated', 'size', 'hash',
|
'id', 'url', 'display_url', 'display', 'source', 'path', 'last_updated', 'size', 'hash',
|
||||||
]
|
]
|
||||||
brief_fields = ('id', 'url', 'display', 'path')
|
brief_fields = ('id', 'url', 'display', 'path')
|
||||||
|
read_only_fields = ['path', 'last_updated', 'size', 'hash']
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from django.core.validators import RegexValidator
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from netbox.constants import CENSOR_TOKEN, CENSOR_TOKEN_CHANGED
|
from netbox.constants import CENSOR_TOKEN, CENSOR_TOKEN_CHANGED
|
||||||
from netbox.models import PrimaryModel
|
from netbox.models import PrimaryModel
|
||||||
@@ -128,7 +128,9 @@ class DataSource(JobsMixin, PrimaryModel):
|
|||||||
# Ensure URL scheme matches selected type
|
# Ensure URL scheme matches selected type
|
||||||
if self.backend_class.is_local and self.url_scheme not in ('file', ''):
|
if self.backend_class.is_local and self.url_scheme not in ('file', ''):
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'source_url': "URLs for local sources must start with file:// (or specify no scheme)"
|
'source_url': _("URLs for local sources must start with {scheme} (or specify no scheme)").format(
|
||||||
|
scheme='file://'
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
|||||||
@@ -140,9 +140,6 @@ class FrontPortFormMixin(forms.Form):
|
|||||||
widget=forms.SelectMultiple(attrs={'size': 8})
|
widget=forms.SelectMultiple(attrs={'size': 8})
|
||||||
)
|
)
|
||||||
|
|
||||||
port_mapping_model = PortMapping
|
|
||||||
parent_field = 'device'
|
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
@@ -203,3 +200,22 @@ class FrontPortFormMixin(forms.Form):
|
|||||||
using=connection,
|
using=connection,
|
||||||
update_fields=None
|
update_fields=None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _get_rear_port_choices(self, parent_filter, front_port):
|
||||||
|
"""
|
||||||
|
Return a list of choices representing each available rear port & position pair on the parent object (identified
|
||||||
|
by a Q filter), excluding those assigned to the specified instance.
|
||||||
|
"""
|
||||||
|
occupied_rear_port_positions = [
|
||||||
|
f'{mapping.rear_port_id}:{mapping.rear_port_position}'
|
||||||
|
for mapping in self.port_mapping_model.objects.filter(parent_filter).exclude(front_port=front_port.pk)
|
||||||
|
]
|
||||||
|
|
||||||
|
choices = []
|
||||||
|
for rear_port in self.rear_port_model.objects.filter(parent_filter):
|
||||||
|
for i in range(1, rear_port.positions + 1):
|
||||||
|
pair_id = f'{rear_port.pk}:{i}'
|
||||||
|
if pair_id not in occupied_rear_port_positions:
|
||||||
|
pair_label = f'{rear_port.name}:{i}'
|
||||||
|
choices.append((pair_id, pair_label))
|
||||||
|
return choices
|
||||||
|
|||||||
@@ -1124,9 +1124,8 @@ class FrontPortTemplateForm(FrontPortFormMixin, ModularComponentTemplateForm):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Override FrontPortFormMixin attrs
|
|
||||||
port_mapping_model = PortTemplateMapping
|
port_mapping_model = PortTemplateMapping
|
||||||
parent_field = 'device_type'
|
rear_port_model = RearPortTemplate
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = FrontPortTemplate
|
model = FrontPortTemplate
|
||||||
@@ -1137,13 +1136,14 @@ class FrontPortTemplateForm(FrontPortFormMixin, ModularComponentTemplateForm):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# Populate rear port choices based on parent DeviceType or ModuleType
|
||||||
if device_type_id := self.data.get('device_type') or self.initial.get('device_type'):
|
if device_type_id := self.data.get('device_type') or self.initial.get('device_type'):
|
||||||
device_type = DeviceType.objects.get(pk=device_type_id)
|
parent_filter = Q(device_type=device_type_id)
|
||||||
|
elif module_type_id := self.data.get('module_type') or self.initial.get('module_type'):
|
||||||
|
parent_filter = Q(module_type=module_type_id)
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
self.fields['rear_ports'].choices = self._get_rear_port_choices(parent_filter, self.instance)
|
||||||
# Populate rear port choices
|
|
||||||
self.fields['rear_ports'].choices = self._get_rear_port_choices(device_type, self.instance)
|
|
||||||
|
|
||||||
# Set initial rear port mappings
|
# Set initial rear port mappings
|
||||||
if self.instance.pk:
|
if self.instance.pk:
|
||||||
@@ -1152,27 +1152,6 @@ class FrontPortTemplateForm(FrontPortFormMixin, ModularComponentTemplateForm):
|
|||||||
for mapping in PortTemplateMapping.objects.filter(front_port_id=self.instance.pk)
|
for mapping in PortTemplateMapping.objects.filter(front_port_id=self.instance.pk)
|
||||||
]
|
]
|
||||||
|
|
||||||
def _get_rear_port_choices(self, device_type, front_port):
|
|
||||||
"""
|
|
||||||
Return a list of choices representing each available rear port & position pair on the device type, excluding
|
|
||||||
those assigned to the specified instance.
|
|
||||||
"""
|
|
||||||
occupied_rear_port_positions = [
|
|
||||||
f'{mapping.rear_port_id}:{mapping.rear_port_position}'
|
|
||||||
for mapping in device_type.port_mappings.exclude(front_port=front_port.pk)
|
|
||||||
]
|
|
||||||
|
|
||||||
choices = []
|
|
||||||
for rear_port in RearPortTemplate.objects.filter(device_type=device_type):
|
|
||||||
for i in range(1, rear_port.positions + 1):
|
|
||||||
pair_id = f'{rear_port.pk}:{i}'
|
|
||||||
if pair_id not in occupied_rear_port_positions:
|
|
||||||
pair_label = f'{rear_port.name}:{i}'
|
|
||||||
choices.append(
|
|
||||||
(pair_id, pair_label)
|
|
||||||
)
|
|
||||||
return choices
|
|
||||||
|
|
||||||
|
|
||||||
class RearPortTemplateForm(ModularComponentTemplateForm):
|
class RearPortTemplateForm(ModularComponentTemplateForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
@@ -1619,6 +1598,9 @@ class FrontPortForm(FrontPortFormMixin, ModularDeviceComponentForm):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
port_mapping_model = PortMapping
|
||||||
|
rear_port_model = RearPort
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = FrontPort
|
model = FrontPort
|
||||||
fields = [
|
fields = [
|
||||||
@@ -1629,13 +1611,12 @@ class FrontPortForm(FrontPortFormMixin, ModularDeviceComponentForm):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# Populate rear port choices
|
||||||
if device_id := self.data.get('device') or self.initial.get('device'):
|
if device_id := self.data.get('device') or self.initial.get('device'):
|
||||||
device = Device.objects.get(pk=device_id)
|
parent_filter = Q(device=device_id)
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
self.fields['rear_ports'].choices = self._get_rear_port_choices(parent_filter, self.instance)
|
||||||
# Populate rear port choices
|
|
||||||
self.fields['rear_ports'].choices = self._get_rear_port_choices(device, self.instance)
|
|
||||||
|
|
||||||
# Set initial rear port mappings
|
# Set initial rear port mappings
|
||||||
if self.instance.pk:
|
if self.instance.pk:
|
||||||
@@ -1644,27 +1625,6 @@ class FrontPortForm(FrontPortFormMixin, ModularDeviceComponentForm):
|
|||||||
for mapping in PortMapping.objects.filter(front_port_id=self.instance.pk)
|
for mapping in PortMapping.objects.filter(front_port_id=self.instance.pk)
|
||||||
]
|
]
|
||||||
|
|
||||||
def _get_rear_port_choices(self, device, front_port):
|
|
||||||
"""
|
|
||||||
Return a list of choices representing each available rear port & position pair on the device, excluding those
|
|
||||||
assigned to the specified instance.
|
|
||||||
"""
|
|
||||||
occupied_rear_port_positions = [
|
|
||||||
f'{mapping.rear_port_id}:{mapping.rear_port_position}'
|
|
||||||
for mapping in device.port_mappings.exclude(front_port=front_port.pk)
|
|
||||||
]
|
|
||||||
|
|
||||||
choices = []
|
|
||||||
for rear_port in RearPort.objects.filter(device=device):
|
|
||||||
for i in range(1, rear_port.positions + 1):
|
|
||||||
pair_id = f'{rear_port.pk}:{i}'
|
|
||||||
if pair_id not in occupied_rear_port_positions:
|
|
||||||
pair_label = f'{rear_port.name}:{i}'
|
|
||||||
choices.append(
|
|
||||||
(pair_id, pair_label)
|
|
||||||
)
|
|
||||||
return choices
|
|
||||||
|
|
||||||
|
|
||||||
class RearPortForm(ModularDeviceComponentForm):
|
class RearPortForm(ModularDeviceComponentForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
|
|||||||
@@ -38,6 +38,15 @@ class ScopedFilterMixin:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ComponentModelFilterMixin:
|
class ComponentModelFilterMixin:
|
||||||
|
_site: Annotated['SiteFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
|
||||||
|
strawberry_django.filter_field(name='site')
|
||||||
|
)
|
||||||
|
_location: Annotated['LocationFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
|
||||||
|
strawberry_django.filter_field(name='location')
|
||||||
|
)
|
||||||
|
_rack: Annotated['RackFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
|
||||||
|
strawberry_django.filter_field(name='rack')
|
||||||
|
)
|
||||||
device: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
|
device: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
|
||||||
device_id: ID | None = strawberry_django.filter_field()
|
device_id: ID | None = strawberry_django.filter_field()
|
||||||
name: FilterLookup[str] | None = strawberry_django.filter_field()
|
name: FilterLookup[str] | None = strawberry_django.filter_field()
|
||||||
|
|||||||
@@ -211,12 +211,16 @@ def sync_cached_scope_fields(instance, created, **kwargs):
|
|||||||
for model in (Prefix, Cluster, WirelessLAN):
|
for model in (Prefix, Cluster, WirelessLAN):
|
||||||
qs = model.objects.filter(**filters)
|
qs = model.objects.filter(**filters)
|
||||||
|
|
||||||
|
# Bulk update cached fields to avoid O(N) performance issues with large datasets.
|
||||||
|
# This does not trigger post_save signals, avoiding spurious change log entries.
|
||||||
|
objects_to_update = []
|
||||||
for obj in qs:
|
for obj in qs:
|
||||||
# Recompute cache using the same logic as save()
|
# Recompute cache using the same logic as save()
|
||||||
obj.cache_related_objects()
|
obj.cache_related_objects()
|
||||||
obj.save(update_fields=[
|
objects_to_update.append(obj)
|
||||||
'_location',
|
|
||||||
'_site',
|
if objects_to_update:
|
||||||
'_site_group',
|
model.objects.bulk_update(
|
||||||
'_region',
|
objects_to_update,
|
||||||
])
|
['_location', '_site', '_site_group', '_region']
|
||||||
|
)
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class RackDimensionsPanel(panels.ObjectAttributesPanel):
|
|||||||
outer_width = attrs.NumericAttr('outer_width', unit_accessor='get_outer_unit_display')
|
outer_width = attrs.NumericAttr('outer_width', unit_accessor='get_outer_unit_display')
|
||||||
outer_height = attrs.NumericAttr('outer_height', unit_accessor='get_outer_unit_display')
|
outer_height = attrs.NumericAttr('outer_height', unit_accessor='get_outer_unit_display')
|
||||||
outer_depth = attrs.NumericAttr('outer_depth', unit_accessor='get_outer_unit_display')
|
outer_depth = attrs.NumericAttr('outer_depth', unit_accessor='get_outer_unit_display')
|
||||||
mounting_depth = attrs.TextAttr('mounting_depth', format_string='{}mm')
|
mounting_depth = attrs.TextAttr('mounting_depth', format_string='{} mm')
|
||||||
|
|
||||||
|
|
||||||
class RackNumberingPanel(panels.ObjectAttributesPanel):
|
class RackNumberingPanel(panels.ObjectAttributesPanel):
|
||||||
|
|||||||
@@ -1845,6 +1845,7 @@ class ModuleTypeBulkEditView(generic.BulkEditView):
|
|||||||
class ModuleTypeBulkRenameView(generic.BulkRenameView):
|
class ModuleTypeBulkRenameView(generic.BulkRenameView):
|
||||||
queryset = ModuleType.objects.all()
|
queryset = ModuleType.objects.all()
|
||||||
filterset = filtersets.ModuleTypeFilterSet
|
filterset = filtersets.ModuleTypeFilterSet
|
||||||
|
field_name = 'model'
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(ModuleType, 'bulk_delete', path='delete', detail=False)
|
@register_model_view(ModuleType, 'bulk_delete', path='delete', detail=False)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class ConfigContextProfileSerializer(PrimaryModelSerializer):
|
|||||||
)
|
)
|
||||||
data_file = DataFileSerializer(
|
data_file = DataFileSerializer(
|
||||||
nested=True,
|
nested=True,
|
||||||
read_only=True
|
required=False
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -143,7 +143,7 @@ class ConfigContextSerializer(OwnerMixin, ChangeLogMessageSerializer, ValidatedM
|
|||||||
)
|
)
|
||||||
data_file = DataFileSerializer(
|
data_file = DataFileSerializer(
|
||||||
nested=True,
|
nested=True,
|
||||||
read_only=True
|
required=False
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
import hashlib
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@@ -7,7 +8,7 @@ from rest_framework import status
|
|||||||
|
|
||||||
from core.choices import ManagedFileRootPathChoices
|
from core.choices import ManagedFileRootPathChoices
|
||||||
from core.events import *
|
from core.events import *
|
||||||
from core.models import ObjectType
|
from core.models import DataFile, DataSource, ObjectType
|
||||||
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Rack, Location, RackRole, Site
|
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Rack, Location, RackRole, Site
|
||||||
from extras.choices import *
|
from extras.choices import *
|
||||||
from extras.models import *
|
from extras.models import *
|
||||||
@@ -731,6 +732,51 @@ class ConfigContextProfileTest(APIViewTestCases.APIViewTestCase):
|
|||||||
)
|
)
|
||||||
ConfigContextProfile.objects.bulk_create(profiles)
|
ConfigContextProfile.objects.bulk_create(profiles)
|
||||||
|
|
||||||
|
def test_update_data_source_and_data_file(self):
|
||||||
|
"""
|
||||||
|
Regression test: Ensure data_source and data_file can be assigned via the API.
|
||||||
|
|
||||||
|
This specifically covers PATCHing a ConfigContext with integer IDs for both fields.
|
||||||
|
"""
|
||||||
|
self.add_permissions(
|
||||||
|
'core.view_datafile',
|
||||||
|
'core.view_datasource',
|
||||||
|
'extras.view_configcontextprofile',
|
||||||
|
'extras.change_configcontextprofile',
|
||||||
|
)
|
||||||
|
config_context_profile = ConfigContextProfile.objects.first()
|
||||||
|
|
||||||
|
# Create a data source and file
|
||||||
|
datasource = DataSource.objects.create(
|
||||||
|
name='Data Source 1',
|
||||||
|
type='local',
|
||||||
|
source_url='file:///tmp/netbox-datasource/',
|
||||||
|
)
|
||||||
|
# Generate a valid dummy YAML file
|
||||||
|
file_data = b'profile: configcontext\n'
|
||||||
|
datafile = DataFile.objects.create(
|
||||||
|
source=datasource,
|
||||||
|
path='dir1/file1.yml',
|
||||||
|
last_updated=now(),
|
||||||
|
size=len(file_data),
|
||||||
|
hash=hashlib.sha256(file_data).hexdigest(),
|
||||||
|
data=file_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
url = self._get_detail_url(config_context_profile)
|
||||||
|
payload = {
|
||||||
|
'data_source': datasource.pk,
|
||||||
|
'data_file': datafile.pk,
|
||||||
|
}
|
||||||
|
response = self.client.patch(url, payload, format='json', **self.header)
|
||||||
|
self.assertHttpStatus(response, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
config_context_profile.refresh_from_db()
|
||||||
|
self.assertEqual(config_context_profile.data_source_id, datasource.pk)
|
||||||
|
self.assertEqual(config_context_profile.data_file_id, datafile.pk)
|
||||||
|
self.assertEqual(response.data['data_source']['id'], datasource.pk)
|
||||||
|
self.assertEqual(response.data['data_file']['id'], datafile.pk)
|
||||||
|
|
||||||
|
|
||||||
class ConfigContextTest(APIViewTestCases.APIViewTestCase):
|
class ConfigContextTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = ConfigContext
|
model = ConfigContext
|
||||||
@@ -812,6 +858,51 @@ class ConfigContextTest(APIViewTestCases.APIViewTestCase):
|
|||||||
rendered_context = device.get_config_context()
|
rendered_context = device.get_config_context()
|
||||||
self.assertEqual(rendered_context['bar'], 456)
|
self.assertEqual(rendered_context['bar'], 456)
|
||||||
|
|
||||||
|
def test_update_data_source_and_data_file(self):
|
||||||
|
"""
|
||||||
|
Regression test: Ensure data_source and data_file can be assigned via the API.
|
||||||
|
|
||||||
|
This specifically covers PATCHing a ConfigContext with integer IDs for both fields.
|
||||||
|
"""
|
||||||
|
self.add_permissions(
|
||||||
|
'core.view_datafile',
|
||||||
|
'core.view_datasource',
|
||||||
|
'extras.view_configcontext',
|
||||||
|
'extras.change_configcontext',
|
||||||
|
)
|
||||||
|
config_context = ConfigContext.objects.first()
|
||||||
|
|
||||||
|
# Create a data source and file
|
||||||
|
datasource = DataSource.objects.create(
|
||||||
|
name='Data Source 1',
|
||||||
|
type='local',
|
||||||
|
source_url='file:///tmp/netbox-datasource/',
|
||||||
|
)
|
||||||
|
# Generate a valid dummy YAML file
|
||||||
|
file_data = b'context: config\n'
|
||||||
|
datafile = DataFile.objects.create(
|
||||||
|
source=datasource,
|
||||||
|
path='dir1/file1.yml',
|
||||||
|
last_updated=now(),
|
||||||
|
size=len(file_data),
|
||||||
|
hash=hashlib.sha256(file_data).hexdigest(),
|
||||||
|
data=file_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
url = self._get_detail_url(config_context)
|
||||||
|
payload = {
|
||||||
|
'data_source': datasource.pk,
|
||||||
|
'data_file': datafile.pk,
|
||||||
|
}
|
||||||
|
response = self.client.patch(url, payload, format='json', **self.header)
|
||||||
|
self.assertHttpStatus(response, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
config_context.refresh_from_db()
|
||||||
|
self.assertEqual(config_context.data_source_id, datasource.pk)
|
||||||
|
self.assertEqual(config_context.data_file_id, datafile.pk)
|
||||||
|
self.assertEqual(response.data['data_source']['id'], datasource.pk)
|
||||||
|
self.assertEqual(response.data['data_file']['id'], datafile.pk)
|
||||||
|
|
||||||
|
|
||||||
class ConfigTemplateTest(APIViewTestCases.APIViewTestCase):
|
class ConfigTemplateTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = ConfigTemplate
|
model = ConfigTemplate
|
||||||
|
|||||||
@@ -24,9 +24,11 @@ from extras.utils import SharedObjectViewMixin
|
|||||||
from netbox.object_actions import *
|
from netbox.object_actions import *
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from netbox.views.generic.mixins import TableMixin
|
from netbox.views.generic.mixins import TableMixin
|
||||||
|
from users.models import ObjectPermission
|
||||||
from utilities.forms import ConfirmationForm, get_field_value
|
from utilities.forms import ConfirmationForm, get_field_value
|
||||||
from utilities.htmx import htmx_partial, htmx_maybe_redirect_current_page
|
from utilities.htmx import htmx_partial, htmx_maybe_redirect_current_page
|
||||||
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
||||||
|
from utilities.permissions import qs_filter_from_constraints
|
||||||
from utilities.query import count_related
|
from utilities.query import count_related
|
||||||
from utilities.querydict import normalize_querydict
|
from utilities.querydict import normalize_querydict
|
||||||
from utilities.request import copy_safe_request
|
from utilities.request import copy_safe_request
|
||||||
@@ -1441,12 +1443,24 @@ class ScriptListView(ContentTypePermissionRequiredMixin, View):
|
|||||||
return 'extras.view_script'
|
return 'extras.view_script'
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
|
# Permissions for the Scripts page are given via the "Managed File" object permission. To further restrict
|
||||||
|
# users to access only specified scripts, create permissions on the "Script" object with appropriate
|
||||||
|
# queryset-style constraints matching fields available on Script.
|
||||||
script_modules = ScriptModule.objects.restrict(request.user).prefetch_related(
|
script_modules = ScriptModule.objects.restrict(request.user).prefetch_related(
|
||||||
'data_source', 'data_file', 'jobs'
|
'data_source', 'data_file', 'jobs'
|
||||||
)
|
)
|
||||||
|
script_ct = ContentType.objects.get_for_model(Script)
|
||||||
|
script_permissions = qs_filter_from_constraints(
|
||||||
|
ObjectPermission.objects.filter(
|
||||||
|
users=self.request.user, object_types=script_ct
|
||||||
|
).values_list("constraints", flat=True)
|
||||||
|
)
|
||||||
|
available_scripts = Script.objects.filter(script_permissions, module__in=script_modules)
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'model': ScriptModule,
|
'model': ScriptModule,
|
||||||
'script_modules': script_modules,
|
'script_modules': script_modules,
|
||||||
|
'available_scripts': available_scripts,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Use partial template for dashboard widgets
|
# Use partial template for dashboard widgets
|
||||||
|
|||||||
@@ -538,7 +538,7 @@ class VLANFilterForm(TenancyFilterForm, PrimaryModelFilterSetForm):
|
|||||||
FieldSet('qinq_role', 'qinq_svlan_id', name=_('Q-in-Q/802.1ad')),
|
FieldSet('qinq_role', 'qinq_svlan_id', name=_('Q-in-Q/802.1ad')),
|
||||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||||
)
|
)
|
||||||
selector_fields = ('filter_id', 'q', 'site_id')
|
selector_fields = ('filter_id', 'q', 'group_id')
|
||||||
region_id = DynamicModelMultipleChoiceField(
|
region_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
|
|||||||
@@ -372,8 +372,8 @@ class IPAddressForm(TenancyForm, PrimaryModelForm):
|
|||||||
'virtual_machine_id': instance.assigned_object.virtual_machine.pk,
|
'virtual_machine_id': instance.assigned_object.virtual_machine.pk,
|
||||||
})
|
})
|
||||||
|
|
||||||
# Disable object assignment fields if the IP address is designated as primary
|
# Disable object assignment fields if the IP address is designated as primary or OOB
|
||||||
if self.initial.get('primary_for_parent'):
|
if self.initial.get('primary_for_parent') or self.initial.get('oob_for_parent'):
|
||||||
self.fields['interface'].disabled = True
|
self.fields['interface'].disabled = True
|
||||||
self.fields['vminterface'].disabled = True
|
self.fields['vminterface'].disabled = True
|
||||||
self.fields['fhrpgroup'].disabled = True
|
self.fields['fhrpgroup'].disabled = True
|
||||||
|
|||||||
@@ -940,6 +940,13 @@ class IPAddress(ContactsMixin, PrimaryModel):
|
|||||||
_("Cannot reassign IP address while it is designated as the primary IP for the parent object")
|
_("Cannot reassign IP address while it is designated as the primary IP for the parent object")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# can't use is_oob_ip as self.assigned_object might be changed
|
||||||
|
if hasattr(original_parent, 'oob_ip') and original_parent.oob_ip_id == self.pk:
|
||||||
|
if parent != original_parent:
|
||||||
|
raise ValidationError(
|
||||||
|
_("Cannot reassign IP address while it is designated as the OOB IP for the parent object")
|
||||||
|
)
|
||||||
|
|
||||||
# Validate IP status selection
|
# Validate IP status selection
|
||||||
if self.status == IPAddressStatusChoices.STATUS_SLAAC and self.family != 6:
|
if self.status == IPAddressStatusChoices.STATUS_SLAAC and self.family != 6:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING
|
|||||||
|
|
||||||
import strawberry_django
|
import strawberry_django
|
||||||
from strawberry import ID
|
from strawberry import ID
|
||||||
from strawberry_django import FilterLookup
|
from strawberry_django import ComparisonFilterLookup, FilterLookup
|
||||||
|
|
||||||
from core.graphql.filter_mixins import ChangeLoggingMixin
|
from core.graphql.filter_mixins import ChangeLoggingMixin
|
||||||
from extras.graphql.filter_mixins import CustomFieldsFilterMixin, JournalEntriesFilterMixin, TagsFilterMixin
|
from extras.graphql.filter_mixins import CustomFieldsFilterMixin, JournalEntriesFilterMixin, TagsFilterMixin
|
||||||
@@ -23,7 +23,7 @@ __all__ = (
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class BaseModelFilter:
|
class BaseModelFilter:
|
||||||
id: FilterLookup[ID] | None = strawberry_django.filter_field()
|
id: ComparisonFilterLookup[ID] | None = strawberry_django.filter_field()
|
||||||
|
|
||||||
|
|
||||||
class ChangeLoggedModelFilter(ChangeLoggingMixin, BaseModelFilter):
|
class ChangeLoggedModelFilter(ChangeLoggingMixin, BaseModelFilter):
|
||||||
|
|||||||
@@ -37,8 +37,6 @@ class PluginMenuItem:
|
|||||||
Alternatively, a pre-generated url can be set on the object which will be rendered literally.
|
Alternatively, a pre-generated url can be set on the object which will be rendered literally.
|
||||||
Buttons are each specified as a list of PluginMenuButton instances.
|
Buttons are each specified as a list of PluginMenuButton instances.
|
||||||
"""
|
"""
|
||||||
permissions = []
|
|
||||||
buttons = []
|
|
||||||
_url = None
|
_url = None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -54,10 +52,14 @@ class PluginMenuItem:
|
|||||||
if type(permissions) not in (list, tuple):
|
if type(permissions) not in (list, tuple):
|
||||||
raise TypeError(_("Permissions must be passed as a tuple or list."))
|
raise TypeError(_("Permissions must be passed as a tuple or list."))
|
||||||
self.permissions = permissions
|
self.permissions = permissions
|
||||||
|
else:
|
||||||
|
self.permissions = []
|
||||||
if buttons is not None:
|
if buttons is not None:
|
||||||
if type(buttons) not in (list, tuple):
|
if type(buttons) not in (list, tuple):
|
||||||
raise TypeError(_("Buttons must be passed as a tuple or list."))
|
raise TypeError(_("Buttons must be passed as a tuple or list."))
|
||||||
self.buttons = buttons
|
self.buttons = buttons
|
||||||
|
else:
|
||||||
|
self.buttons = []
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def url(self):
|
def url(self):
|
||||||
@@ -74,7 +76,6 @@ class PluginMenuButton:
|
|||||||
ButtonColorChoices.
|
ButtonColorChoices.
|
||||||
"""
|
"""
|
||||||
color = ButtonColorChoices.DEFAULT
|
color = ButtonColorChoices.DEFAULT
|
||||||
permissions = []
|
|
||||||
_url = None
|
_url = None
|
||||||
|
|
||||||
def __init__(self, link, title, icon_class, color=None, permissions=None):
|
def __init__(self, link, title, icon_class, color=None, permissions=None):
|
||||||
@@ -87,6 +88,8 @@ class PluginMenuButton:
|
|||||||
if type(permissions) not in (list, tuple):
|
if type(permissions) not in (list, tuple):
|
||||||
raise TypeError(_("Permissions must be passed as a tuple or list."))
|
raise TypeError(_("Permissions must be passed as a tuple or list."))
|
||||||
self.permissions = permissions
|
self.permissions = permissions
|
||||||
|
else:
|
||||||
|
self.permissions = []
|
||||||
if color is not None:
|
if color is not None:
|
||||||
if color not in ButtonColorChoices.values():
|
if color not in ButtonColorChoices.values():
|
||||||
raise ValueError(_("Button color must be a choice within ButtonColorChoices."))
|
raise ValueError(_("Button color must be a choice within ButtonColorChoices."))
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from netbox.tests.dummy_plugin import config as dummy_config
|
|||||||
from netbox.tests.dummy_plugin.data_backends import DummyBackend
|
from netbox.tests.dummy_plugin.data_backends import DummyBackend
|
||||||
from netbox.tests.dummy_plugin.jobs import DummySystemJob
|
from netbox.tests.dummy_plugin.jobs import DummySystemJob
|
||||||
from netbox.tests.dummy_plugin.webhook_callbacks import set_context
|
from netbox.tests.dummy_plugin.webhook_callbacks import set_context
|
||||||
from netbox.plugins.navigation import PluginMenu
|
from netbox.plugins.navigation import PluginMenu, PluginMenuItem, PluginMenuButton
|
||||||
from netbox.plugins.utils import get_plugin_config
|
from netbox.plugins.utils import get_plugin_config
|
||||||
from netbox.graphql.schema import Query
|
from netbox.graphql.schema import Query
|
||||||
from netbox.registry import registry
|
from netbox.registry import registry
|
||||||
@@ -227,3 +227,46 @@ class PluginTest(TestCase):
|
|||||||
Test the registration of webhook callbacks.
|
Test the registration of webhook callbacks.
|
||||||
"""
|
"""
|
||||||
self.assertIn(set_context, registry['webhook_callbacks'])
|
self.assertIn(set_context, registry['webhook_callbacks'])
|
||||||
|
|
||||||
|
|
||||||
|
class PluginNavigationTest(TestCase):
|
||||||
|
|
||||||
|
def test_plugin_menu_item_independent_permissions(self):
|
||||||
|
item1 = PluginMenuItem(link='test1', link_text='Test 1')
|
||||||
|
item1.permissions.append('leaked_permission')
|
||||||
|
|
||||||
|
item2 = PluginMenuItem(link='test2', link_text='Test 2')
|
||||||
|
|
||||||
|
self.assertIsNot(item1.permissions, item2.permissions)
|
||||||
|
self.assertEqual(item1.permissions, ['leaked_permission'])
|
||||||
|
self.assertEqual(item2.permissions, [])
|
||||||
|
|
||||||
|
def test_plugin_menu_item_independent_buttons(self):
|
||||||
|
item1 = PluginMenuItem(link='test1', link_text='Test 1')
|
||||||
|
button = PluginMenuButton(link='button1', title='Button 1', icon_class='mdi-test')
|
||||||
|
item1.buttons.append(button)
|
||||||
|
|
||||||
|
item2 = PluginMenuItem(link='test2', link_text='Test 2')
|
||||||
|
|
||||||
|
self.assertIsNot(item1.buttons, item2.buttons)
|
||||||
|
self.assertEqual(len(item1.buttons), 1)
|
||||||
|
self.assertEqual(item1.buttons[0], button)
|
||||||
|
self.assertEqual(item2.buttons, [])
|
||||||
|
|
||||||
|
def test_plugin_menu_button_independent_permissions(self):
|
||||||
|
button1 = PluginMenuButton(link='button1', title='Button 1', icon_class='mdi-test')
|
||||||
|
button1.permissions.append('leaked_permission')
|
||||||
|
|
||||||
|
button2 = PluginMenuButton(link='button2', title='Button 2', icon_class='mdi-test')
|
||||||
|
|
||||||
|
self.assertIsNot(button1.permissions, button2.permissions)
|
||||||
|
self.assertEqual(button1.permissions, ['leaked_permission'])
|
||||||
|
self.assertEqual(button2.permissions, [])
|
||||||
|
|
||||||
|
def test_explicit_permissions_remain_independent(self):
|
||||||
|
item1 = PluginMenuItem(link='test1', link_text='Test 1', permissions=['explicit_permission'])
|
||||||
|
item2 = PluginMenuItem(link='test2', link_text='Test 2', permissions=['different_permission'])
|
||||||
|
|
||||||
|
self.assertIsNot(item1.permissions, item2.permissions)
|
||||||
|
self.assertEqual(item1.permissions, ['explicit_permission'])
|
||||||
|
self.assertEqual(item2.permissions, ['different_permission'])
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ class ObjectAttributesPanel(ObjectPanel, metaclass=ObjectAttributesPanelMeta):
|
|||||||
"""
|
"""
|
||||||
label = name[:1].upper() + name[1:]
|
label = name[:1].upper() + name[1:]
|
||||||
label = label.replace('_', ' ')
|
label = label.replace('_', ' ')
|
||||||
return label
|
return _(label)
|
||||||
|
|
||||||
def get_context(self, context):
|
def get_context(self, context):
|
||||||
# Determine which attributes to display in the panel based on only/exclude args
|
# Determine which attributes to display in the panel based on only/exclude args
|
||||||
|
|||||||
@@ -38,81 +38,83 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for script in scripts %}
|
{% for script in scripts %}
|
||||||
{% with last_job=script.get_latest_jobs|first %}
|
{% if script in available_scripts %}
|
||||||
<tr>
|
{% with last_job=script.get_latest_jobs|first %}
|
||||||
<td>
|
<tr>
|
||||||
{% if script.is_executable %}
|
<td>
|
||||||
<a href="{% url 'extras:script' script.pk %}" id="{{ script.module }}.{{ script.class_name }}">{{ script.python_class.name }}</a>
|
{% if script.is_executable %}
|
||||||
|
<a href="{% url 'extras:script' script.pk %}" id="{{ script.module }}.{{ script.class_name }}">{{ script.python_class.name }}</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'extras:script_jobs' script.pk %}" id="{{ script.module }}.{{ script.class_name }}">{{ script.python_class.name }}</a>
|
||||||
|
<span class="text-danger">
|
||||||
|
<i class="mdi mdi-alert" title="{% trans "Script is no longer present in the source file" %}"></i>
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{ script.python_class.description|markdown|placeholder }}</td>
|
||||||
|
{% if last_job %}
|
||||||
|
<td>
|
||||||
|
<a href="{% url 'extras:script_result' job_pk=last_job.pk %}">{{ last_job.created|isodatetime }}</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% badge last_job.get_status_display last_job.get_status_color %}
|
||||||
|
</td>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{% url 'extras:script_jobs' script.pk %}" id="{{ script.module }}.{{ script.class_name }}">{{ script.python_class.name }}</a>
|
<td class="text-muted">{% trans "Never" %}</td>
|
||||||
<span class="text-danger">
|
<td>{{ ''|placeholder }}</td>
|
||||||
<i class="mdi mdi-alert" title="{% trans "Script is no longer present in the source file" %}"></i>
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
|
||||||
<td>{{ script.python_class.description|markdown|placeholder }}</td>
|
|
||||||
{% if last_job %}
|
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'extras:script_result' job_pk=last_job.pk %}">{{ last_job.created|isodatetime }}</a>
|
{% if request.user|can_run:script and script.is_executable %}
|
||||||
</td>
|
<div class="float-end d-print-none">
|
||||||
<td>
|
<form action="{% url 'extras:script' script.pk %}" method="post">
|
||||||
{% badge last_job.get_status_display last_job.get_status_color %}
|
{% if script.python_class.commit_default %}
|
||||||
</td>
|
<input type="checkbox" name="_commit" hidden checked>
|
||||||
{% else %}
|
|
||||||
<td class="text-muted">{% trans "Never" %}</td>
|
|
||||||
<td>{{ ''|placeholder }}</td>
|
|
||||||
{% endif %}
|
|
||||||
<td>
|
|
||||||
{% if request.user|can_run:script and script.is_executable %}
|
|
||||||
<div class="float-end d-print-none">
|
|
||||||
<form action="{% url 'extras:script' script.pk %}" method="post">
|
|
||||||
{% if script.python_class.commit_default %}
|
|
||||||
<input type="checkbox" name="_commit" hidden checked>
|
|
||||||
{% endif %}
|
|
||||||
{% csrf_token %}
|
|
||||||
<button type="submit" name="_run" class="btn btn-primary{% if embedded %} btn-sm{% endif %}">
|
|
||||||
{% if last_job %}
|
|
||||||
<i class="mdi mdi-replay"></i> {% if not embedded %}{% trans "Run Again" %}{% endif %}
|
|
||||||
{% else %}
|
|
||||||
<i class="mdi mdi-play"></i> {% if not embedded %}{% trans "Run Script" %}{% endif %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</button>
|
{% csrf_token %}
|
||||||
</form>
|
<button type="submit" name="_run" class="btn btn-primary{% if embedded %} btn-sm{% endif %}">
|
||||||
</div>
|
{% if last_job %}
|
||||||
{% endif %}
|
<i class="mdi mdi-replay"></i> {% if not embedded %}{% trans "Run Again" %}{% endif %}
|
||||||
</td>
|
{% else %}
|
||||||
</tr>
|
<i class="mdi mdi-play"></i> {% if not embedded %}{% trans "Run Script" %}{% endif %}
|
||||||
{% if last_job and not embedded %}
|
{% endif %}
|
||||||
{% for test_name, data in last_job.data.tests.items %}
|
</button>
|
||||||
<tr>
|
</form>
|
||||||
<td colspan="4" class="method">
|
</div>
|
||||||
<span class="ps-3">{{ test_name }}</span>
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-end text-nowrap script-stats">
|
</tr>
|
||||||
<span class="badge text-bg-success">{{ data.success }}</span>
|
{% if last_job and not embedded %}
|
||||||
<span class="badge text-bg-info">{{ data.info }}</span>
|
{% for test_name, data in last_job.data.tests.items %}
|
||||||
<span class="badge text-bg-warning">{{ data.warning }}</span>
|
<tr>
|
||||||
<span class="badge text-bg-danger">{{ data.failure }}</span>
|
<td colspan="4" class="method">
|
||||||
</td>
|
<span class="ps-3">{{ test_name }}</span>
|
||||||
</tr>
|
</td>
|
||||||
{% endfor %}
|
<td class="text-end text-nowrap script-stats">
|
||||||
{% elif last_job and not last_job.data.log and not embedded %}
|
<span class="badge text-bg-success">{{ data.success }}</span>
|
||||||
{# legacy #}
|
<span class="badge text-bg-info">{{ data.info }}</span>
|
||||||
{% for method, stats in last_job.data.items %}
|
<span class="badge text-bg-warning">{{ data.warning }}</span>
|
||||||
<tr>
|
<span class="badge text-bg-danger">{{ data.failure }}</span>
|
||||||
<td colspan="4" class="method">
|
</td>
|
||||||
<span class="ps-3">{{ method }}</span>
|
</tr>
|
||||||
</td>
|
{% endfor %}
|
||||||
<td class="text-end text-nowrap report-stats">
|
{% elif last_job and not last_job.data.log and not embedded %}
|
||||||
<span class="badge bg-success">{{ stats.success }}</span>
|
{# legacy #}
|
||||||
<span class="badge bg-info">{{ stats.info }}</span>
|
{% for method, stats in last_job.data.items %}
|
||||||
<span class="badge bg-warning">{{ stats.warning }}</span>
|
<tr>
|
||||||
<span class="badge bg-danger">{{ stats.failure }}</span>
|
<td colspan="4" class="method">
|
||||||
</td>
|
<span class="ps-3">{{ method }}</span>
|
||||||
</tr>
|
</td>
|
||||||
{% endfor %}
|
<td class="text-end text-nowrap report-stats">
|
||||||
{% endif %}
|
<span class="badge bg-success">{{ stats.success }}</span>
|
||||||
{% endwith %}
|
<span class="badge bg-info">{{ stats.info }}</span>
|
||||||
|
<span class="badge bg-warning">{{ stats.warning }}</span>
|
||||||
|
<span class="badge bg-danger">{{ stats.failure }}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-01-08 05:04+0000\n"
|
"POT-Creation-Date: 2026-01-17 05:02+0000\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@@ -806,7 +806,7 @@ msgstr ""
|
|||||||
#: netbox/circuits/forms/model_forms.py:335
|
#: netbox/circuits/forms/model_forms.py:335
|
||||||
#: netbox/dcim/forms/model_forms.py:145 netbox/dcim/forms/model_forms.py:186
|
#: netbox/dcim/forms/model_forms.py:145 netbox/dcim/forms/model_forms.py:186
|
||||||
#: netbox/dcim/forms/model_forms.py:273 netbox/dcim/forms/model_forms.py:330
|
#: netbox/dcim/forms/model_forms.py:273 netbox/dcim/forms/model_forms.py:330
|
||||||
#: netbox/dcim/forms/model_forms.py:863 netbox/dcim/forms/model_forms.py:1917
|
#: netbox/dcim/forms/model_forms.py:863 netbox/dcim/forms/model_forms.py:1877
|
||||||
#: netbox/ipam/forms/bulk_edit.py:380 netbox/ipam/forms/model_forms.py:67
|
#: netbox/ipam/forms/bulk_edit.py:380 netbox/ipam/forms/model_forms.py:67
|
||||||
#: netbox/ipam/forms/model_forms.py:84 netbox/ipam/forms/model_forms.py:115
|
#: netbox/ipam/forms/model_forms.py:84 netbox/ipam/forms/model_forms.py:115
|
||||||
#: netbox/ipam/forms/model_forms.py:136 netbox/ipam/forms/model_forms.py:160
|
#: netbox/ipam/forms/model_forms.py:136 netbox/ipam/forms/model_forms.py:160
|
||||||
@@ -1012,8 +1012,8 @@ msgstr ""
|
|||||||
#: netbox/dcim/forms/bulk_import.py:262 netbox/dcim/forms/bulk_import.py:1193
|
#: netbox/dcim/forms/bulk_import.py:262 netbox/dcim/forms/bulk_import.py:1193
|
||||||
#: netbox/dcim/forms/filtersets.py:399 netbox/dcim/forms/filtersets.py:865
|
#: netbox/dcim/forms/filtersets.py:399 netbox/dcim/forms/filtersets.py:865
|
||||||
#: netbox/dcim/forms/filtersets.py:1872 netbox/dcim/forms/filtersets.py:1912
|
#: netbox/dcim/forms/filtersets.py:1872 netbox/dcim/forms/filtersets.py:1912
|
||||||
#: netbox/dcim/forms/model_forms.py:255 netbox/dcim/forms/model_forms.py:1235
|
#: netbox/dcim/forms/model_forms.py:255 netbox/dcim/forms/model_forms.py:1214
|
||||||
#: netbox/dcim/forms/model_forms.py:1737 netbox/dcim/forms/object_import.py:182
|
#: netbox/dcim/forms/model_forms.py:1697 netbox/dcim/forms/object_import.py:182
|
||||||
#: netbox/dcim/tables/devices.py:171 netbox/dcim/tables/devices.py:857
|
#: netbox/dcim/tables/devices.py:171 netbox/dcim/tables/devices.py:857
|
||||||
#: netbox/dcim/tables/devices.py:983 netbox/dcim/tables/devicetypes.py:317
|
#: netbox/dcim/tables/devices.py:983 netbox/dcim/tables/devicetypes.py:317
|
||||||
#: netbox/dcim/tables/racks.py:117 netbox/extras/filtersets.py:708
|
#: netbox/dcim/tables/racks.py:117 netbox/extras/filtersets.py:708
|
||||||
@@ -1132,9 +1132,9 @@ msgstr ""
|
|||||||
#: netbox/circuits/forms/bulk_import.py:258
|
#: netbox/circuits/forms/bulk_import.py:258
|
||||||
#: netbox/circuits/forms/model_forms.py:358
|
#: netbox/circuits/forms/model_forms.py:358
|
||||||
#: netbox/circuits/tables/virtual_circuits.py:108
|
#: netbox/circuits/tables/virtual_circuits.py:108
|
||||||
#: netbox/dcim/forms/bulk_import.py:1324 netbox/dcim/forms/model_forms.py:1309
|
#: netbox/dcim/forms/bulk_import.py:1324 netbox/dcim/forms/model_forms.py:1288
|
||||||
#: netbox/dcim/forms/model_forms.py:1578 netbox/dcim/forms/model_forms.py:1778
|
#: netbox/dcim/forms/model_forms.py:1557 netbox/dcim/forms/model_forms.py:1738
|
||||||
#: netbox/dcim/forms/model_forms.py:1813 netbox/dcim/forms/model_forms.py:1938
|
#: netbox/dcim/forms/model_forms.py:1773 netbox/dcim/forms/model_forms.py:1898
|
||||||
#: netbox/dcim/tables/connections.py:65 netbox/dcim/tables/devices.py:1150
|
#: netbox/dcim/tables/connections.py:65 netbox/dcim/tables/devices.py:1150
|
||||||
#: netbox/ipam/forms/bulk_import.py:319 netbox/ipam/forms/model_forms.py:280
|
#: netbox/ipam/forms/bulk_import.py:319 netbox/ipam/forms/model_forms.py:280
|
||||||
#: netbox/ipam/forms/model_forms.py:289 netbox/ipam/tables/fhrp.py:61
|
#: netbox/ipam/forms/model_forms.py:289 netbox/ipam/tables/fhrp.py:61
|
||||||
@@ -1822,7 +1822,6 @@ msgid "ASN Count"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/circuits/tables/virtual_circuits.py:64
|
#: netbox/circuits/tables/virtual_circuits.py:64
|
||||||
#: netbox/netbox/navigation/menu.py:235
|
|
||||||
#: netbox/templates/circuits/virtualcircuit.html:87
|
#: netbox/templates/circuits/virtualcircuit.html:87
|
||||||
#: netbox/templates/vpn/l2vpn.html:60 netbox/templates/vpn/tunnel.html:72
|
#: netbox/templates/vpn/l2vpn.html:60 netbox/templates/vpn/tunnel.html:72
|
||||||
#: netbox/vpn/tables/tunnels.py:59
|
#: netbox/vpn/tables/tunnels.py:59
|
||||||
@@ -1853,8 +1852,8 @@ msgstr ""
|
|||||||
#: netbox/dcim/forms/filtersets.py:1866 netbox/dcim/forms/filtersets.py:1907
|
#: netbox/dcim/forms/filtersets.py:1866 netbox/dcim/forms/filtersets.py:1907
|
||||||
#: netbox/dcim/forms/filtersets.py:2000 netbox/dcim/forms/filtersets.py:2024
|
#: netbox/dcim/forms/filtersets.py:2000 netbox/dcim/forms/filtersets.py:2024
|
||||||
#: netbox/dcim/forms/filtersets.py:2048 netbox/dcim/forms/model_forms.py:728
|
#: netbox/dcim/forms/filtersets.py:2048 netbox/dcim/forms/model_forms.py:728
|
||||||
#: netbox/dcim/forms/model_forms.py:943 netbox/dcim/forms/model_forms.py:1376
|
#: netbox/dcim/forms/model_forms.py:943 netbox/dcim/forms/model_forms.py:1355
|
||||||
#: netbox/dcim/forms/model_forms.py:1889 netbox/dcim/forms/model_forms.py:1962
|
#: netbox/dcim/forms/model_forms.py:1849 netbox/dcim/forms/model_forms.py:1922
|
||||||
#: netbox/dcim/forms/object_create.py:205 netbox/dcim/tables/connections.py:22
|
#: netbox/dcim/forms/object_create.py:205 netbox/dcim/tables/connections.py:22
|
||||||
#: netbox/dcim/tables/connections.py:41 netbox/dcim/tables/connections.py:60
|
#: netbox/dcim/tables/connections.py:41 netbox/dcim/tables/connections.py:60
|
||||||
#: netbox/dcim/tables/devices.py:291 netbox/dcim/tables/devices.py:386
|
#: netbox/dcim/tables/devices.py:291 netbox/dcim/tables/devices.py:386
|
||||||
@@ -2436,7 +2435,7 @@ msgstr ""
|
|||||||
msgid "Change logging is not supported for this object type ({type})."
|
msgid "Change logging is not supported for this object type ({type})."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/core/models/config.py:21 netbox/core/models/data.py:282
|
#: netbox/core/models/config.py:21 netbox/core/models/data.py:284
|
||||||
#: netbox/core/models/files.py:29 netbox/core/models/jobs.py:60
|
#: netbox/core/models/files.py:29 netbox/core/models/jobs.py:60
|
||||||
#: netbox/extras/models/models.py:847 netbox/extras/models/notifications.py:39
|
#: netbox/extras/models/models.py:847 netbox/extras/models/notifications.py:39
|
||||||
#: netbox/extras/models/notifications.py:195
|
#: netbox/extras/models/notifications.py:195
|
||||||
@@ -2542,58 +2541,63 @@ msgstr ""
|
|||||||
msgid "Unknown backend type: {type}"
|
msgid "Unknown backend type: {type}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/core/models/data.py:180
|
#: netbox/core/models/data.py:131
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "URLs for local sources must start with {scheme} (or specify no scheme)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: netbox/core/models/data.py:182
|
||||||
msgid "Cannot initiate sync; syncing already in progress."
|
msgid "Cannot initiate sync; syncing already in progress."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/core/models/data.py:193
|
#: netbox/core/models/data.py:195
|
||||||
msgid ""
|
msgid ""
|
||||||
"There was an error initializing the backend. A dependency needs to be "
|
"There was an error initializing the backend. A dependency needs to be "
|
||||||
"installed: "
|
"installed: "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/core/models/data.py:286 netbox/core/models/files.py:33
|
#: netbox/core/models/data.py:288 netbox/core/models/files.py:33
|
||||||
#: netbox/netbox/models/features.py:67
|
#: netbox/netbox/models/features.py:67
|
||||||
msgid "last updated"
|
msgid "last updated"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/core/models/data.py:296 netbox/dcim/models/cables.py:622
|
#: netbox/core/models/data.py:298 netbox/dcim/models/cables.py:622
|
||||||
msgid "path"
|
msgid "path"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/core/models/data.py:299
|
#: netbox/core/models/data.py:301
|
||||||
msgid "File path relative to the data source's root"
|
msgid "File path relative to the data source's root"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/core/models/data.py:303 netbox/ipam/models/ip.py:507
|
#: netbox/core/models/data.py:305 netbox/ipam/models/ip.py:507
|
||||||
msgid "size"
|
msgid "size"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/core/models/data.py:306
|
#: netbox/core/models/data.py:308
|
||||||
msgid "hash"
|
msgid "hash"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/core/models/data.py:310
|
#: netbox/core/models/data.py:312
|
||||||
msgid "Length must be 64 hexadecimal characters."
|
msgid "Length must be 64 hexadecimal characters."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/core/models/data.py:312
|
#: netbox/core/models/data.py:314
|
||||||
msgid "SHA256 hash of the file data"
|
msgid "SHA256 hash of the file data"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/core/models/data.py:326
|
#: netbox/core/models/data.py:328
|
||||||
msgid "data file"
|
msgid "data file"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/core/models/data.py:327
|
#: netbox/core/models/data.py:329
|
||||||
msgid "data files"
|
msgid "data files"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/core/models/data.py:400
|
#: netbox/core/models/data.py:402
|
||||||
msgid "auto sync record"
|
msgid "auto sync record"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/core/models/data.py:401
|
#: netbox/core/models/data.py:403
|
||||||
msgid "auto sync records"
|
msgid "auto sync records"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3080,8 +3084,8 @@ msgstr ""
|
|||||||
#: netbox/dcim/forms/filtersets.py:768 netbox/dcim/forms/filtersets.py:783
|
#: netbox/dcim/forms/filtersets.py:768 netbox/dcim/forms/filtersets.py:783
|
||||||
#: netbox/dcim/forms/model_forms.py:81 netbox/dcim/forms/model_forms.py:99
|
#: netbox/dcim/forms/model_forms.py:81 netbox/dcim/forms/model_forms.py:99
|
||||||
#: netbox/dcim/forms/model_forms.py:176 netbox/dcim/forms/model_forms.py:502
|
#: netbox/dcim/forms/model_forms.py:176 netbox/dcim/forms/model_forms.py:502
|
||||||
#: netbox/dcim/forms/model_forms.py:523 netbox/dcim/forms/model_forms.py:1227
|
#: netbox/dcim/forms/model_forms.py:523 netbox/dcim/forms/model_forms.py:1206
|
||||||
#: netbox/dcim/forms/model_forms.py:1729 netbox/dcim/forms/object_import.py:177
|
#: netbox/dcim/forms/model_forms.py:1689 netbox/dcim/forms/object_import.py:177
|
||||||
#: netbox/dcim/tables/devices.py:702 netbox/dcim/tables/devices.py:916
|
#: netbox/dcim/tables/devices.py:702 netbox/dcim/tables/devices.py:916
|
||||||
#: netbox/dcim/tables/devices.py:1003 netbox/dcim/tables/devices.py:1156
|
#: netbox/dcim/tables/devices.py:1003 netbox/dcim/tables/devices.py:1156
|
||||||
#: netbox/ipam/forms/bulk_import.py:578 netbox/ipam/forms/model_forms.py:755
|
#: netbox/ipam/forms/bulk_import.py:578 netbox/ipam/forms/model_forms.py:755
|
||||||
@@ -3213,7 +3217,7 @@ msgstr ""
|
|||||||
#: netbox/dcim/choices.py:885 netbox/dcim/choices.py:1351
|
#: netbox/dcim/choices.py:885 netbox/dcim/choices.py:1351
|
||||||
#: netbox/dcim/forms/bulk_edit.py:1543 netbox/dcim/forms/filtersets.py:1553
|
#: netbox/dcim/forms/bulk_edit.py:1543 netbox/dcim/forms/filtersets.py:1553
|
||||||
#: netbox/dcim/forms/filtersets.py:1678 netbox/dcim/forms/model_forms.py:1105
|
#: netbox/dcim/forms/filtersets.py:1678 netbox/dcim/forms/model_forms.py:1105
|
||||||
#: netbox/dcim/forms/model_forms.py:1590 netbox/netbox/navigation/menu.py:147
|
#: netbox/dcim/forms/model_forms.py:1569 netbox/netbox/navigation/menu.py:147
|
||||||
#: netbox/netbox/navigation/menu.py:151
|
#: netbox/netbox/navigation/menu.py:151
|
||||||
#: netbox/templates/dcim/interface.html:280
|
#: netbox/templates/dcim/interface.html:280
|
||||||
msgid "Wireless"
|
msgid "Wireless"
|
||||||
@@ -3831,7 +3835,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: netbox/dcim/filtersets.py:1242 netbox/dcim/forms/filtersets.py:906
|
#: netbox/dcim/filtersets.py:1242 netbox/dcim/forms/filtersets.py:906
|
||||||
#: netbox/dcim/forms/filtersets.py:1609 netbox/dcim/forms/filtersets.py:1947
|
#: netbox/dcim/forms/filtersets.py:1609 netbox/dcim/forms/filtersets.py:1947
|
||||||
#: netbox/dcim/forms/model_forms.py:1935 netbox/dcim/models/devices.py:1307
|
#: netbox/dcim/forms/model_forms.py:1895 netbox/dcim/models/devices.py:1307
|
||||||
#: netbox/dcim/models/devices.py:1330 netbox/virtualization/filtersets.py:211
|
#: netbox/dcim/models/devices.py:1330 netbox/virtualization/filtersets.py:211
|
||||||
#: netbox/virtualization/filtersets.py:284
|
#: netbox/virtualization/filtersets.py:284
|
||||||
#: netbox/virtualization/forms/filtersets.py:187
|
#: netbox/virtualization/forms/filtersets.py:187
|
||||||
@@ -3997,7 +4001,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: netbox/dcim/filtersets.py:1942 netbox/dcim/forms/bulk_edit.py:1509
|
#: netbox/dcim/filtersets.py:1942 netbox/dcim/forms/bulk_edit.py:1509
|
||||||
#: netbox/dcim/forms/bulk_import.py:1027 netbox/dcim/forms/filtersets.py:1662
|
#: netbox/dcim/forms/bulk_import.py:1027 netbox/dcim/forms/filtersets.py:1662
|
||||||
#: netbox/dcim/forms/model_forms.py:1556
|
#: netbox/dcim/forms/model_forms.py:1535
|
||||||
#: netbox/dcim/models/device_components.py:866
|
#: netbox/dcim/models/device_components.py:866
|
||||||
#: netbox/dcim/tables/devices.py:660 netbox/ipam/filtersets.py:345
|
#: netbox/dcim/tables/devices.py:660 netbox/ipam/filtersets.py:345
|
||||||
#: netbox/ipam/filtersets.py:356 netbox/ipam/filtersets.py:489
|
#: netbox/ipam/filtersets.py:356 netbox/ipam/filtersets.py:489
|
||||||
@@ -4056,7 +4060,7 @@ msgid "VLAN Translation Policy (ID)"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/filtersets.py:1970 netbox/dcim/forms/filtersets.py:1633
|
#: netbox/dcim/filtersets.py:1970 netbox/dcim/forms/filtersets.py:1633
|
||||||
#: netbox/dcim/forms/model_forms.py:1573
|
#: netbox/dcim/forms/model_forms.py:1552
|
||||||
#: netbox/dcim/models/device_components.py:668
|
#: netbox/dcim/models/device_components.py:668
|
||||||
#: netbox/ipam/forms/filtersets.py:518 netbox/ipam/forms/model_forms.py:700
|
#: netbox/ipam/forms/filtersets.py:518 netbox/ipam/forms/model_forms.py:700
|
||||||
#: netbox/templates/ipam/vlantranslationpolicy.html:11
|
#: netbox/templates/ipam/vlantranslationpolicy.html:11
|
||||||
@@ -4110,14 +4114,14 @@ msgstr ""
|
|||||||
msgid "Primary MAC address (ID)"
|
msgid "Primary MAC address (ID)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/filtersets.py:2057 netbox/dcim/forms/model_forms.py:1560
|
#: netbox/dcim/filtersets.py:2057 netbox/dcim/forms/model_forms.py:1539
|
||||||
#: netbox/virtualization/filtersets.py:295
|
#: netbox/virtualization/filtersets.py:295
|
||||||
#: netbox/virtualization/forms/model_forms.py:302
|
#: netbox/virtualization/forms/model_forms.py:302
|
||||||
msgid "Primary MAC address"
|
msgid "Primary MAC address"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/filtersets.py:2079 netbox/dcim/filtersets.py:2091
|
#: netbox/dcim/filtersets.py:2079 netbox/dcim/filtersets.py:2091
|
||||||
#: netbox/dcim/forms/filtersets.py:1569 netbox/dcim/forms/model_forms.py:1915
|
#: netbox/dcim/forms/filtersets.py:1569 netbox/dcim/forms/model_forms.py:1875
|
||||||
#: netbox/templates/dcim/virtualdevicecontext.html:15
|
#: netbox/templates/dcim/virtualdevicecontext.html:15
|
||||||
msgid "Virtual Device Context"
|
msgid "Virtual Device Context"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -4239,7 +4243,7 @@ msgstr ""
|
|||||||
#: netbox/dcim/forms/filtersets.py:1917 netbox/dcim/forms/model_forms.py:211
|
#: netbox/dcim/forms/filtersets.py:1917 netbox/dcim/forms/model_forms.py:211
|
||||||
#: netbox/dcim/forms/model_forms.py:342 netbox/dcim/forms/model_forms.py:354
|
#: netbox/dcim/forms/model_forms.py:342 netbox/dcim/forms/model_forms.py:354
|
||||||
#: netbox/dcim/forms/model_forms.py:424 netbox/dcim/forms/model_forms.py:528
|
#: netbox/dcim/forms/model_forms.py:424 netbox/dcim/forms/model_forms.py:528
|
||||||
#: netbox/dcim/forms/model_forms.py:1240 netbox/dcim/forms/model_forms.py:1742
|
#: netbox/dcim/forms/model_forms.py:1219 netbox/dcim/forms/model_forms.py:1702
|
||||||
#: netbox/dcim/forms/object_import.py:188 netbox/dcim/tables/devices.py:99
|
#: netbox/dcim/forms/object_import.py:188 netbox/dcim/tables/devices.py:99
|
||||||
#: netbox/dcim/tables/devices.py:174 netbox/dcim/tables/devices.py:986
|
#: netbox/dcim/tables/devices.py:174 netbox/dcim/tables/devices.py:986
|
||||||
#: netbox/dcim/tables/devicetypes.py:86 netbox/dcim/tables/devicetypes.py:321
|
#: netbox/dcim/tables/devicetypes.py:86 netbox/dcim/tables/devicetypes.py:321
|
||||||
@@ -4417,7 +4421,7 @@ msgstr ""
|
|||||||
#: netbox/dcim/forms/filtersets.py:524 netbox/dcim/forms/filtersets.py:667
|
#: netbox/dcim/forms/filtersets.py:524 netbox/dcim/forms/filtersets.py:667
|
||||||
#: netbox/dcim/forms/filtersets.py:809 netbox/dcim/forms/filtersets.py:1028
|
#: netbox/dcim/forms/filtersets.py:809 netbox/dcim/forms/filtersets.py:1028
|
||||||
#: netbox/dcim/forms/model_forms.py:432 netbox/dcim/forms/model_forms.py:767
|
#: netbox/dcim/forms/model_forms.py:432 netbox/dcim/forms/model_forms.py:767
|
||||||
#: netbox/dcim/forms/model_forms.py:1810
|
#: netbox/dcim/forms/model_forms.py:1770
|
||||||
#: netbox/templates/dcim/device_edit.html:22
|
#: netbox/templates/dcim/device_edit.html:22
|
||||||
msgid "Hardware"
|
msgid "Hardware"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -4443,8 +4447,8 @@ msgstr ""
|
|||||||
#: netbox/dcim/forms/bulk_edit.py:531 netbox/dcim/forms/model_forms.py:373
|
#: netbox/dcim/forms/bulk_edit.py:531 netbox/dcim/forms/model_forms.py:373
|
||||||
#: netbox/dcim/forms/model_forms.py:1002 netbox/dcim/forms/model_forms.py:1044
|
#: netbox/dcim/forms/model_forms.py:1002 netbox/dcim/forms/model_forms.py:1044
|
||||||
#: netbox/dcim/forms/model_forms.py:1071 netbox/dcim/forms/model_forms.py:1099
|
#: netbox/dcim/forms/model_forms.py:1071 netbox/dcim/forms/model_forms.py:1099
|
||||||
#: netbox/dcim/forms/model_forms.py:1120 netbox/dcim/forms/model_forms.py:1181
|
#: netbox/dcim/forms/model_forms.py:1120 netbox/dcim/forms/model_forms.py:1160
|
||||||
#: netbox/dcim/forms/model_forms.py:1199 netbox/dcim/forms/object_create.py:117
|
#: netbox/dcim/forms/model_forms.py:1178 netbox/dcim/forms/object_create.py:117
|
||||||
#: netbox/dcim/tables/devicetypes.py:83 netbox/templates/dcim/devicebay.html:52
|
#: netbox/dcim/tables/devicetypes.py:83 netbox/templates/dcim/devicebay.html:52
|
||||||
#: netbox/templates/dcim/module.html:61
|
#: netbox/templates/dcim/module.html:61
|
||||||
msgid "Device Type"
|
msgid "Device Type"
|
||||||
@@ -4473,7 +4477,7 @@ msgstr ""
|
|||||||
#: netbox/dcim/forms/model_forms.py:431 netbox/dcim/forms/model_forms.py:1003
|
#: netbox/dcim/forms/model_forms.py:431 netbox/dcim/forms/model_forms.py:1003
|
||||||
#: netbox/dcim/forms/model_forms.py:1045 netbox/dcim/forms/model_forms.py:1072
|
#: netbox/dcim/forms/model_forms.py:1045 netbox/dcim/forms/model_forms.py:1072
|
||||||
#: netbox/dcim/forms/model_forms.py:1100 netbox/dcim/forms/model_forms.py:1121
|
#: netbox/dcim/forms/model_forms.py:1100 netbox/dcim/forms/model_forms.py:1121
|
||||||
#: netbox/dcim/forms/model_forms.py:1182 netbox/dcim/forms/model_forms.py:1200
|
#: netbox/dcim/forms/model_forms.py:1161 netbox/dcim/forms/model_forms.py:1179
|
||||||
#: netbox/dcim/forms/object_create.py:118 netbox/dcim/tables/modules.py:51
|
#: netbox/dcim/forms/object_create.py:118 netbox/dcim/tables/modules.py:51
|
||||||
#: netbox/dcim/tables/modules.py:94 netbox/templates/dcim/module.html:92
|
#: netbox/dcim/tables/modules.py:94 netbox/templates/dcim/module.html:92
|
||||||
#: netbox/templates/dcim/modulebay.html:66
|
#: netbox/templates/dcim/modulebay.html:66
|
||||||
@@ -4657,8 +4661,8 @@ msgid "Allocated power draw (watts)"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_edit.py:1058 netbox/dcim/forms/bulk_import.py:885
|
#: netbox/dcim/forms/bulk_edit.py:1058 netbox/dcim/forms/bulk_import.py:885
|
||||||
#: netbox/dcim/forms/model_forms.py:1060 netbox/dcim/forms/model_forms.py:1446
|
#: netbox/dcim/forms/model_forms.py:1060 netbox/dcim/forms/model_forms.py:1425
|
||||||
#: netbox/dcim/forms/model_forms.py:1794 netbox/dcim/forms/object_import.py:56
|
#: netbox/dcim/forms/model_forms.py:1754 netbox/dcim/forms/object_import.py:56
|
||||||
msgid "Power port"
|
msgid "Power port"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4693,7 +4697,7 @@ msgid "Wireless role"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_edit.py:1268 netbox/dcim/forms/model_forms.py:766
|
#: netbox/dcim/forms/bulk_edit.py:1268 netbox/dcim/forms/model_forms.py:766
|
||||||
#: netbox/dcim/forms/model_forms.py:1391 netbox/dcim/tables/devices.py:328
|
#: netbox/dcim/forms/model_forms.py:1370 netbox/dcim/tables/devices.py:328
|
||||||
#: netbox/templates/dcim/consoleport.html:24
|
#: netbox/templates/dcim/consoleport.html:24
|
||||||
#: netbox/templates/dcim/consoleserverport.html:24
|
#: netbox/templates/dcim/consoleserverport.html:24
|
||||||
#: netbox/templates/dcim/frontport.html:24
|
#: netbox/templates/dcim/frontport.html:24
|
||||||
@@ -4711,7 +4715,7 @@ msgstr ""
|
|||||||
msgid "LAG"
|
msgid "LAG"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_edit.py:1415 netbox/dcim/forms/model_forms.py:1473
|
#: netbox/dcim/forms/bulk_edit.py:1415 netbox/dcim/forms/model_forms.py:1452
|
||||||
msgid "Virtual device contexts"
|
msgid "Virtual device contexts"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4740,7 +4744,7 @@ msgid "Mode"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_edit.py:1458 netbox/dcim/forms/bulk_import.py:993
|
#: netbox/dcim/forms/bulk_edit.py:1458 netbox/dcim/forms/bulk_import.py:993
|
||||||
#: netbox/dcim/forms/model_forms.py:1522 netbox/ipam/forms/bulk_import.py:173
|
#: netbox/dcim/forms/model_forms.py:1501 netbox/ipam/forms/bulk_import.py:173
|
||||||
#: netbox/ipam/forms/filtersets.py:568 netbox/ipam/models/vlans.py:93
|
#: netbox/ipam/forms/filtersets.py:568 netbox/ipam/models/vlans.py:93
|
||||||
#: netbox/virtualization/forms/bulk_edit.py:205
|
#: netbox/virtualization/forms/bulk_edit.py:205
|
||||||
#: netbox/virtualization/forms/bulk_import.py:185
|
#: netbox/virtualization/forms/bulk_import.py:185
|
||||||
@@ -4749,7 +4753,7 @@ msgid "VLAN group"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_edit.py:1467 netbox/dcim/forms/bulk_import.py:1000
|
#: netbox/dcim/forms/bulk_edit.py:1467 netbox/dcim/forms/bulk_import.py:1000
|
||||||
#: netbox/dcim/forms/model_forms.py:1528 netbox/dcim/tables/devices.py:605
|
#: netbox/dcim/forms/model_forms.py:1507 netbox/dcim/tables/devices.py:605
|
||||||
#: netbox/virtualization/forms/bulk_edit.py:213
|
#: netbox/virtualization/forms/bulk_edit.py:213
|
||||||
#: netbox/virtualization/forms/bulk_import.py:192
|
#: netbox/virtualization/forms/bulk_import.py:192
|
||||||
#: netbox/virtualization/forms/model_forms.py:331
|
#: netbox/virtualization/forms/model_forms.py:331
|
||||||
@@ -4757,7 +4761,7 @@ msgid "Untagged VLAN"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_edit.py:1476 netbox/dcim/forms/bulk_import.py:1007
|
#: netbox/dcim/forms/bulk_edit.py:1476 netbox/dcim/forms/bulk_import.py:1007
|
||||||
#: netbox/dcim/forms/model_forms.py:1537 netbox/dcim/tables/devices.py:611
|
#: netbox/dcim/forms/model_forms.py:1516 netbox/dcim/tables/devices.py:611
|
||||||
#: netbox/virtualization/forms/bulk_edit.py:221
|
#: netbox/virtualization/forms/bulk_edit.py:221
|
||||||
#: netbox/virtualization/forms/bulk_import.py:199
|
#: netbox/virtualization/forms/bulk_import.py:199
|
||||||
#: netbox/virtualization/forms/model_forms.py:340
|
#: netbox/virtualization/forms/model_forms.py:340
|
||||||
@@ -4773,18 +4777,18 @@ msgid "Remove tagged VLANs"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_edit.py:1499 netbox/dcim/forms/bulk_import.py:1020
|
#: netbox/dcim/forms/bulk_edit.py:1499 netbox/dcim/forms/bulk_import.py:1020
|
||||||
#: netbox/dcim/forms/model_forms.py:1546
|
#: netbox/dcim/forms/model_forms.py:1525
|
||||||
#: netbox/virtualization/forms/bulk_import.py:212
|
#: netbox/virtualization/forms/bulk_import.py:212
|
||||||
#: netbox/virtualization/forms/model_forms.py:349
|
#: netbox/virtualization/forms/model_forms.py:349
|
||||||
msgid "Q-in-Q Service VLAN"
|
msgid "Q-in-Q Service VLAN"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_edit.py:1514 netbox/dcim/forms/model_forms.py:1509
|
#: netbox/dcim/forms/bulk_edit.py:1514 netbox/dcim/forms/model_forms.py:1488
|
||||||
#: netbox/wireless/forms/filtersets.py:26
|
#: netbox/wireless/forms/filtersets.py:26
|
||||||
msgid "Wireless LAN group"
|
msgid "Wireless LAN group"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_edit.py:1519 netbox/dcim/forms/model_forms.py:1514
|
#: netbox/dcim/forms/bulk_edit.py:1519 netbox/dcim/forms/model_forms.py:1493
|
||||||
#: netbox/dcim/tables/devices.py:653 netbox/netbox/navigation/menu.py:153
|
#: netbox/dcim/tables/devices.py:653 netbox/netbox/navigation/menu.py:153
|
||||||
#: netbox/templates/dcim/interface.html:350
|
#: netbox/templates/dcim/interface.html:350
|
||||||
#: netbox/wireless/tables/wirelesslan.py:20
|
#: netbox/wireless/tables/wirelesslan.py:20
|
||||||
@@ -4792,7 +4796,7 @@ msgid "Wireless LANs"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_edit.py:1528 netbox/dcim/forms/filtersets.py:1550
|
#: netbox/dcim/forms/bulk_edit.py:1528 netbox/dcim/forms/filtersets.py:1550
|
||||||
#: netbox/dcim/forms/model_forms.py:1580 netbox/ipam/forms/bulk_edit.py:224
|
#: netbox/dcim/forms/model_forms.py:1559 netbox/ipam/forms/bulk_edit.py:224
|
||||||
#: netbox/ipam/forms/bulk_edit.py:310 netbox/ipam/forms/filtersets.py:184
|
#: netbox/ipam/forms/bulk_edit.py:310 netbox/ipam/forms/filtersets.py:184
|
||||||
#: netbox/netbox/navigation/menu.py:109
|
#: netbox/netbox/navigation/menu.py:109
|
||||||
#: netbox/templates/dcim/interface.html:141
|
#: netbox/templates/dcim/interface.html:141
|
||||||
@@ -4804,18 +4808,18 @@ msgid "Addressing"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_edit.py:1529 netbox/dcim/forms/filtersets.py:808
|
#: netbox/dcim/forms/bulk_edit.py:1529 netbox/dcim/forms/filtersets.py:808
|
||||||
#: netbox/dcim/forms/model_forms.py:1581
|
#: netbox/dcim/forms/model_forms.py:1560
|
||||||
#: netbox/virtualization/forms/model_forms.py:370
|
#: netbox/virtualization/forms/model_forms.py:370
|
||||||
msgid "Operation"
|
msgid "Operation"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_edit.py:1530 netbox/dcim/forms/filtersets.py:1551
|
#: netbox/dcim/forms/bulk_edit.py:1530 netbox/dcim/forms/filtersets.py:1551
|
||||||
#: netbox/dcim/forms/filtersets.py:1677 netbox/dcim/forms/model_forms.py:1104
|
#: netbox/dcim/forms/filtersets.py:1677 netbox/dcim/forms/model_forms.py:1104
|
||||||
#: netbox/dcim/forms/model_forms.py:1583
|
#: netbox/dcim/forms/model_forms.py:1562
|
||||||
msgid "PoE"
|
msgid "PoE"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_edit.py:1531 netbox/dcim/forms/model_forms.py:1582
|
#: netbox/dcim/forms/bulk_edit.py:1531 netbox/dcim/forms/model_forms.py:1561
|
||||||
#: netbox/templates/dcim/interface.html:105
|
#: netbox/templates/dcim/interface.html:105
|
||||||
#: netbox/virtualization/forms/bulk_edit.py:237
|
#: netbox/virtualization/forms/bulk_edit.py:237
|
||||||
#: netbox/virtualization/forms/model_forms.py:371
|
#: netbox/virtualization/forms/model_forms.py:371
|
||||||
@@ -4823,7 +4827,7 @@ msgid "Related Interfaces"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_edit.py:1533 netbox/dcim/forms/filtersets.py:1552
|
#: netbox/dcim/forms/bulk_edit.py:1533 netbox/dcim/forms/filtersets.py:1552
|
||||||
#: netbox/dcim/forms/model_forms.py:1586
|
#: netbox/dcim/forms/model_forms.py:1565
|
||||||
#: netbox/virtualization/forms/bulk_edit.py:240
|
#: netbox/virtualization/forms/bulk_edit.py:240
|
||||||
#: netbox/virtualization/forms/filtersets.py:215
|
#: netbox/virtualization/forms/filtersets.py:215
|
||||||
#: netbox/virtualization/forms/model_forms.py:374
|
#: netbox/virtualization/forms/model_forms.py:374
|
||||||
@@ -5102,13 +5106,13 @@ msgstr ""
|
|||||||
msgid "Electrical phase (for three-phase circuits)"
|
msgid "Electrical phase (for three-phase circuits)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_import.py:939 netbox/dcim/forms/model_forms.py:1484
|
#: netbox/dcim/forms/bulk_import.py:939 netbox/dcim/forms/model_forms.py:1463
|
||||||
#: netbox/virtualization/forms/bulk_import.py:169
|
#: netbox/virtualization/forms/bulk_import.py:169
|
||||||
#: netbox/virtualization/forms/model_forms.py:310
|
#: netbox/virtualization/forms/model_forms.py:310
|
||||||
msgid "Parent interface"
|
msgid "Parent interface"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_import.py:946 netbox/dcim/forms/model_forms.py:1492
|
#: netbox/dcim/forms/bulk_import.py:946 netbox/dcim/forms/model_forms.py:1471
|
||||||
#: netbox/virtualization/forms/bulk_import.py:176
|
#: netbox/virtualization/forms/bulk_import.py:176
|
||||||
#: netbox/virtualization/forms/model_forms.py:318
|
#: netbox/virtualization/forms/model_forms.py:318
|
||||||
msgid "Bridged interface"
|
msgid "Bridged interface"
|
||||||
@@ -5391,7 +5395,7 @@ msgstr ""
|
|||||||
msgid "Single or three-phase"
|
msgid "Single or three-phase"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_import.py:1729 netbox/dcim/forms/model_forms.py:1895
|
#: netbox/dcim/forms/bulk_import.py:1729 netbox/dcim/forms/model_forms.py:1855
|
||||||
#: netbox/dcim/ui/panels.py:109
|
#: netbox/dcim/ui/panels.py:109
|
||||||
#: netbox/templates/dcim/virtualdevicecontext.html:30
|
#: netbox/templates/dcim/virtualdevicecontext.html:30
|
||||||
#: netbox/templates/virtualization/virtualmachine.html:56
|
#: netbox/templates/virtualization/virtualmachine.html:56
|
||||||
@@ -5402,7 +5406,7 @@ msgstr ""
|
|||||||
msgid "IPv4 address with mask, e.g. 1.2.3.4/24"
|
msgid "IPv4 address with mask, e.g. 1.2.3.4/24"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/bulk_import.py:1736 netbox/dcim/forms/model_forms.py:1904
|
#: netbox/dcim/forms/bulk_import.py:1736 netbox/dcim/forms/model_forms.py:1864
|
||||||
#: netbox/dcim/ui/panels.py:114
|
#: netbox/dcim/ui/panels.py:114
|
||||||
#: netbox/templates/dcim/virtualdevicecontext.html:41
|
#: netbox/templates/dcim/virtualdevicecontext.html:41
|
||||||
#: netbox/templates/virtualization/virtualmachine.html:72
|
#: netbox/templates/virtualization/virtualmachine.html:72
|
||||||
@@ -5605,7 +5609,7 @@ msgstr ""
|
|||||||
msgid "Mgmt only"
|
msgid "Mgmt only"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/filtersets.py:1613 netbox/dcim/forms/model_forms.py:1568
|
#: netbox/dcim/forms/filtersets.py:1613 netbox/dcim/forms/model_forms.py:1547
|
||||||
#: netbox/dcim/models/device_components.py:791
|
#: netbox/dcim/models/device_components.py:791
|
||||||
#: netbox/templates/dcim/interface.html:155
|
#: netbox/templates/dcim/interface.html:155
|
||||||
msgid "WWN"
|
msgid "WWN"
|
||||||
@@ -5706,7 +5710,7 @@ msgstr ""
|
|||||||
msgid "Rear ports"
|
msgid "Rear ports"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/mixins.py:158
|
#: netbox/dcim/forms/mixins.py:155
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"The total number of front port positions ({frontport_count}) must match the "
|
"The total number of front port positions ({frontport_count}) must match the "
|
||||||
@@ -5778,35 +5782,35 @@ msgid ""
|
|||||||
"replaced with the position value when creating a new module."
|
"replaced with the position value when creating a new module."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/model_forms.py:1252
|
#: netbox/dcim/forms/model_forms.py:1231
|
||||||
msgid "Console port template"
|
msgid "Console port template"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/model_forms.py:1260
|
#: netbox/dcim/forms/model_forms.py:1239
|
||||||
msgid "Console server port template"
|
msgid "Console server port template"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/model_forms.py:1268
|
#: netbox/dcim/forms/model_forms.py:1247
|
||||||
msgid "Front port template"
|
msgid "Front port template"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/model_forms.py:1276
|
#: netbox/dcim/forms/model_forms.py:1255
|
||||||
msgid "Interface template"
|
msgid "Interface template"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/model_forms.py:1284
|
#: netbox/dcim/forms/model_forms.py:1263
|
||||||
msgid "Power outlet template"
|
msgid "Power outlet template"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/model_forms.py:1292
|
#: netbox/dcim/forms/model_forms.py:1271
|
||||||
msgid "Power port template"
|
msgid "Power port template"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/model_forms.py:1300
|
#: netbox/dcim/forms/model_forms.py:1279
|
||||||
msgid "Rear port template"
|
msgid "Rear port template"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/model_forms.py:1310 netbox/dcim/forms/model_forms.py:1814
|
#: netbox/dcim/forms/model_forms.py:1289 netbox/dcim/forms/model_forms.py:1774
|
||||||
#: netbox/dcim/tables/connections.py:27
|
#: netbox/dcim/tables/connections.py:27
|
||||||
#: netbox/templates/dcim/consoleport.html:17
|
#: netbox/templates/dcim/consoleport.html:17
|
||||||
#: netbox/templates/dcim/consoleserverport.html:73
|
#: netbox/templates/dcim/consoleserverport.html:73
|
||||||
@@ -5814,14 +5818,14 @@ msgstr ""
|
|||||||
msgid "Console Port"
|
msgid "Console Port"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/model_forms.py:1311 netbox/dcim/forms/model_forms.py:1815
|
#: netbox/dcim/forms/model_forms.py:1290 netbox/dcim/forms/model_forms.py:1775
|
||||||
#: netbox/templates/dcim/consoleport.html:73
|
#: netbox/templates/dcim/consoleport.html:73
|
||||||
#: netbox/templates/dcim/consoleserverport.html:17
|
#: netbox/templates/dcim/consoleserverport.html:17
|
||||||
#: netbox/templates/dcim/frontport.html:106
|
#: netbox/templates/dcim/frontport.html:106
|
||||||
msgid "Console Server Port"
|
msgid "Console Server Port"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/model_forms.py:1312 netbox/dcim/forms/model_forms.py:1816
|
#: netbox/dcim/forms/model_forms.py:1291 netbox/dcim/forms/model_forms.py:1776
|
||||||
#: netbox/templates/circuits/inc/circuit_termination_fields.html:53
|
#: netbox/templates/circuits/inc/circuit_termination_fields.html:53
|
||||||
#: netbox/templates/dcim/consoleport.html:76
|
#: netbox/templates/dcim/consoleport.html:76
|
||||||
#: netbox/templates/dcim/consoleserverport.html:76
|
#: netbox/templates/dcim/consoleserverport.html:76
|
||||||
@@ -5833,7 +5837,7 @@ msgstr ""
|
|||||||
msgid "Front Port"
|
msgid "Front Port"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/model_forms.py:1313 netbox/dcim/forms/model_forms.py:1817
|
#: netbox/dcim/forms/model_forms.py:1292 netbox/dcim/forms/model_forms.py:1777
|
||||||
#: netbox/templates/circuits/inc/circuit_termination_fields.html:54
|
#: netbox/templates/circuits/inc/circuit_termination_fields.html:54
|
||||||
#: netbox/templates/dcim/consoleport.html:79
|
#: netbox/templates/dcim/consoleport.html:79
|
||||||
#: netbox/templates/dcim/consoleserverport.html:79
|
#: netbox/templates/dcim/consoleserverport.html:79
|
||||||
@@ -5845,80 +5849,80 @@ msgstr ""
|
|||||||
msgid "Rear Port"
|
msgid "Rear Port"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/model_forms.py:1314 netbox/dcim/forms/model_forms.py:1818
|
#: netbox/dcim/forms/model_forms.py:1293 netbox/dcim/forms/model_forms.py:1778
|
||||||
#: netbox/dcim/tables/connections.py:46 netbox/dcim/tables/devices.py:526
|
#: netbox/dcim/tables/connections.py:46 netbox/dcim/tables/devices.py:526
|
||||||
#: netbox/templates/dcim/poweroutlet.html:58
|
#: netbox/templates/dcim/poweroutlet.html:58
|
||||||
#: netbox/templates/dcim/powerport.html:17
|
#: netbox/templates/dcim/powerport.html:17
|
||||||
msgid "Power Port"
|
msgid "Power Port"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/model_forms.py:1315 netbox/dcim/forms/model_forms.py:1819
|
#: netbox/dcim/forms/model_forms.py:1294 netbox/dcim/forms/model_forms.py:1779
|
||||||
#: netbox/templates/dcim/poweroutlet.html:17
|
#: netbox/templates/dcim/poweroutlet.html:17
|
||||||
#: netbox/templates/dcim/powerport.html:77
|
#: netbox/templates/dcim/powerport.html:77
|
||||||
msgid "Power Outlet"
|
msgid "Power Outlet"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/model_forms.py:1317 netbox/dcim/forms/model_forms.py:1821
|
#: netbox/dcim/forms/model_forms.py:1296 netbox/dcim/forms/model_forms.py:1781
|
||||||
msgid "Component Assignment"
|
msgid "Component Assignment"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/model_forms.py:1363 netbox/dcim/forms/model_forms.py:1868
|
#: netbox/dcim/forms/model_forms.py:1342 netbox/dcim/forms/model_forms.py:1828
|
||||||
msgid "An InventoryItem can only be assigned to a single component."
|
msgid "An InventoryItem can only be assigned to a single component."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/model_forms.py:1500
|
#: netbox/dcim/forms/model_forms.py:1479
|
||||||
msgid "LAG interface"
|
msgid "LAG interface"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/model_forms.py:1523
|
#: netbox/dcim/forms/model_forms.py:1502
|
||||||
msgid "Filter VLANs available for assignment by group."
|
msgid "Filter VLANs available for assignment by group."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/model_forms.py:1711
|
#: netbox/dcim/forms/model_forms.py:1671
|
||||||
msgid "Child Device"
|
msgid "Child Device"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/model_forms.py:1712
|
#: netbox/dcim/forms/model_forms.py:1672
|
||||||
msgid ""
|
msgid ""
|
||||||
"Child devices must first be created and assigned to the site and rack of the "
|
"Child devices must first be created and assigned to the site and rack of the "
|
||||||
"parent device."
|
"parent device."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/model_forms.py:1754
|
#: netbox/dcim/forms/model_forms.py:1714
|
||||||
msgid "Console port"
|
msgid "Console port"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/model_forms.py:1762
|
#: netbox/dcim/forms/model_forms.py:1722
|
||||||
msgid "Console server port"
|
msgid "Console server port"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/model_forms.py:1770 netbox/dcim/forms/object_import.py:140
|
#: netbox/dcim/forms/model_forms.py:1730 netbox/dcim/forms/object_import.py:140
|
||||||
msgid "Front port"
|
msgid "Front port"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/model_forms.py:1786
|
#: netbox/dcim/forms/model_forms.py:1746
|
||||||
msgid "Power outlet"
|
msgid "Power outlet"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/model_forms.py:1802 netbox/dcim/forms/object_import.py:145
|
#: netbox/dcim/forms/model_forms.py:1762 netbox/dcim/forms/object_import.py:145
|
||||||
msgid "Rear port"
|
msgid "Rear port"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/model_forms.py:1808
|
#: netbox/dcim/forms/model_forms.py:1768
|
||||||
#: netbox/templates/dcim/inventoryitem.html:17
|
#: netbox/templates/dcim/inventoryitem.html:17
|
||||||
msgid "Inventory Item"
|
msgid "Inventory Item"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/model_forms.py:1877
|
#: netbox/dcim/forms/model_forms.py:1837
|
||||||
#: netbox/templates/dcim/inventoryitemrole.html:15
|
#: netbox/templates/dcim/inventoryitemrole.html:15
|
||||||
msgid "Inventory Item Role"
|
msgid "Inventory Item Role"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/model_forms.py:1947
|
#: netbox/dcim/forms/model_forms.py:1907
|
||||||
msgid "VM Interface"
|
msgid "VM Interface"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/model_forms.py:1963 netbox/ipam/forms/filtersets.py:638
|
#: netbox/dcim/forms/model_forms.py:1923 netbox/ipam/forms/filtersets.py:638
|
||||||
#: netbox/ipam/forms/model_forms.py:323 netbox/ipam/tables/vlans.py:171
|
#: netbox/ipam/forms/model_forms.py:323 netbox/ipam/tables/vlans.py:171
|
||||||
#: netbox/templates/virtualization/virtualdisk.html:21
|
#: netbox/templates/virtualization/virtualdisk.html:21
|
||||||
#: netbox/templates/virtualization/virtualmachine.html:12
|
#: netbox/templates/virtualization/virtualmachine.html:12
|
||||||
@@ -5935,7 +5939,7 @@ msgstr ""
|
|||||||
msgid "Virtual Machine"
|
msgid "Virtual Machine"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/forms/model_forms.py:2002
|
#: netbox/dcim/forms/model_forms.py:1962
|
||||||
msgid "A MAC address can only be assigned to a single object."
|
msgid "A MAC address can only be assigned to a single object."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -7675,7 +7679,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: netbox/dcim/tables/devices.py:252 netbox/dcim/tables/devices.py:1125
|
#: netbox/dcim/tables/devices.py:252 netbox/dcim/tables/devices.py:1125
|
||||||
#: netbox/dcim/tables/devicetypes.py:131 netbox/dcim/views.py:1412
|
#: netbox/dcim/tables/devicetypes.py:131 netbox/dcim/views.py:1412
|
||||||
#: netbox/dcim/views.py:1749 netbox/dcim/views.py:2577
|
#: netbox/dcim/views.py:1749 netbox/dcim/views.py:2578
|
||||||
#: netbox/netbox/navigation/menu.py:95 netbox/netbox/navigation/menu.py:259
|
#: netbox/netbox/navigation/menu.py:95 netbox/netbox/navigation/menu.py:259
|
||||||
#: netbox/templates/dcim/buttons/bulk_add_components.html:38
|
#: netbox/templates/dcim/buttons/bulk_add_components.html:38
|
||||||
#: netbox/templates/dcim/device/base.html:37
|
#: netbox/templates/dcim/device/base.html:37
|
||||||
@@ -7722,7 +7726,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: netbox/dcim/tables/devices.py:333 netbox/dcim/tables/devicetypes.py:52
|
#: netbox/dcim/tables/devices.py:333 netbox/dcim/tables/devicetypes.py:52
|
||||||
#: netbox/dcim/tables/devicetypes.py:146 netbox/dcim/views.py:1487
|
#: netbox/dcim/tables/devicetypes.py:146 netbox/dcim/views.py:1487
|
||||||
#: netbox/dcim/views.py:2663 netbox/netbox/navigation/menu.py:104
|
#: netbox/dcim/views.py:2664 netbox/netbox/navigation/menu.py:104
|
||||||
#: netbox/templates/dcim/buttons/bulk_add_components.html:66
|
#: netbox/templates/dcim/buttons/bulk_add_components.html:66
|
||||||
#: netbox/templates/dcim/device/base.html:52
|
#: netbox/templates/dcim/device/base.html:52
|
||||||
#: netbox/templates/dcim/devicetype/base.html:49
|
#: netbox/templates/dcim/devicetype/base.html:49
|
||||||
@@ -7856,7 +7860,7 @@ msgid "Device Count"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/tables/devicetypes.py:119 netbox/dcim/views.py:1352
|
#: netbox/dcim/tables/devicetypes.py:119 netbox/dcim/views.py:1352
|
||||||
#: netbox/dcim/views.py:1689 netbox/dcim/views.py:2512
|
#: netbox/dcim/views.py:1689 netbox/dcim/views.py:2513
|
||||||
#: netbox/netbox/navigation/menu.py:98
|
#: netbox/netbox/navigation/menu.py:98
|
||||||
#: netbox/templates/dcim/buttons/bulk_add_components.html:10
|
#: netbox/templates/dcim/buttons/bulk_add_components.html:10
|
||||||
#: netbox/templates/dcim/device/base.html:25
|
#: netbox/templates/dcim/device/base.html:25
|
||||||
@@ -7867,7 +7871,7 @@ msgid "Console Ports"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/tables/devicetypes.py:122 netbox/dcim/views.py:1367
|
#: netbox/dcim/tables/devicetypes.py:122 netbox/dcim/views.py:1367
|
||||||
#: netbox/dcim/views.py:1704 netbox/dcim/views.py:2528
|
#: netbox/dcim/views.py:1704 netbox/dcim/views.py:2529
|
||||||
#: netbox/netbox/navigation/menu.py:99
|
#: netbox/netbox/navigation/menu.py:99
|
||||||
#: netbox/templates/dcim/buttons/bulk_add_components.html:17
|
#: netbox/templates/dcim/buttons/bulk_add_components.html:17
|
||||||
#: netbox/templates/dcim/device/base.html:28
|
#: netbox/templates/dcim/device/base.html:28
|
||||||
@@ -7878,7 +7882,7 @@ msgid "Console Server Ports"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/tables/devicetypes.py:125 netbox/dcim/views.py:1382
|
#: netbox/dcim/tables/devicetypes.py:125 netbox/dcim/views.py:1382
|
||||||
#: netbox/dcim/views.py:1719 netbox/dcim/views.py:2544
|
#: netbox/dcim/views.py:1719 netbox/dcim/views.py:2545
|
||||||
#: netbox/netbox/navigation/menu.py:100
|
#: netbox/netbox/navigation/menu.py:100
|
||||||
#: netbox/templates/dcim/buttons/bulk_add_components.html:24
|
#: netbox/templates/dcim/buttons/bulk_add_components.html:24
|
||||||
#: netbox/templates/dcim/device/base.html:31
|
#: netbox/templates/dcim/device/base.html:31
|
||||||
@@ -7889,7 +7893,7 @@ msgid "Power Ports"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/tables/devicetypes.py:128 netbox/dcim/views.py:1397
|
#: netbox/dcim/tables/devicetypes.py:128 netbox/dcim/views.py:1397
|
||||||
#: netbox/dcim/views.py:1734 netbox/dcim/views.py:2560
|
#: netbox/dcim/views.py:1734 netbox/dcim/views.py:2561
|
||||||
#: netbox/netbox/navigation/menu.py:101
|
#: netbox/netbox/navigation/menu.py:101
|
||||||
#: netbox/templates/dcim/buttons/bulk_add_components.html:31
|
#: netbox/templates/dcim/buttons/bulk_add_components.html:31
|
||||||
#: netbox/templates/dcim/device/base.html:34
|
#: netbox/templates/dcim/device/base.html:34
|
||||||
@@ -7900,7 +7904,7 @@ msgid "Power Outlets"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/tables/devicetypes.py:134 netbox/dcim/views.py:1427
|
#: netbox/dcim/tables/devicetypes.py:134 netbox/dcim/views.py:1427
|
||||||
#: netbox/dcim/views.py:1764 netbox/dcim/views.py:2599
|
#: netbox/dcim/views.py:1764 netbox/dcim/views.py:2600
|
||||||
#: netbox/netbox/navigation/menu.py:96
|
#: netbox/netbox/navigation/menu.py:96
|
||||||
#: netbox/templates/dcim/device/base.html:40
|
#: netbox/templates/dcim/device/base.html:40
|
||||||
#: netbox/templates/dcim/devicetype/base.html:37
|
#: netbox/templates/dcim/devicetype/base.html:37
|
||||||
@@ -7910,7 +7914,7 @@ msgid "Front Ports"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/tables/devicetypes.py:137 netbox/dcim/views.py:1442
|
#: netbox/dcim/tables/devicetypes.py:137 netbox/dcim/views.py:1442
|
||||||
#: netbox/dcim/views.py:1779 netbox/dcim/views.py:2615
|
#: netbox/dcim/views.py:1779 netbox/dcim/views.py:2616
|
||||||
#: netbox/netbox/navigation/menu.py:97
|
#: netbox/netbox/navigation/menu.py:97
|
||||||
#: netbox/templates/dcim/buttons/bulk_add_components.html:45
|
#: netbox/templates/dcim/buttons/bulk_add_components.html:45
|
||||||
#: netbox/templates/dcim/device/base.html:43
|
#: netbox/templates/dcim/device/base.html:43
|
||||||
@@ -7921,7 +7925,7 @@ msgid "Rear Ports"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/tables/devicetypes.py:140 netbox/dcim/views.py:1472
|
#: netbox/dcim/tables/devicetypes.py:140 netbox/dcim/views.py:1472
|
||||||
#: netbox/dcim/views.py:2647 netbox/netbox/navigation/menu.py:103
|
#: netbox/dcim/views.py:2648 netbox/netbox/navigation/menu.py:103
|
||||||
#: netbox/templates/dcim/buttons/bulk_add_components.html:52
|
#: netbox/templates/dcim/buttons/bulk_add_components.html:52
|
||||||
#: netbox/templates/dcim/device/base.html:49
|
#: netbox/templates/dcim/device/base.html:49
|
||||||
#: netbox/templates/dcim/devicetype/base.html:46
|
#: netbox/templates/dcim/devicetype/base.html:46
|
||||||
@@ -7929,7 +7933,7 @@ msgid "Device Bays"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/tables/devicetypes.py:143 netbox/dcim/views.py:1457
|
#: netbox/dcim/tables/devicetypes.py:143 netbox/dcim/views.py:1457
|
||||||
#: netbox/dcim/views.py:1794 netbox/dcim/views.py:2631
|
#: netbox/dcim/views.py:1794 netbox/dcim/views.py:2632
|
||||||
#: netbox/netbox/navigation/menu.py:102
|
#: netbox/netbox/navigation/menu.py:102
|
||||||
#: netbox/templates/dcim/buttons/bulk_add_components.html:59
|
#: netbox/templates/dcim/buttons/bulk_add_components.html:59
|
||||||
#: netbox/templates/dcim/device/base.html:46
|
#: netbox/templates/dcim/device/base.html:46
|
||||||
@@ -8068,13 +8072,13 @@ msgstr ""
|
|||||||
msgid "Reservations"
|
msgid "Reservations"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/views.py:2458 netbox/netbox/navigation/menu.py:213
|
#: netbox/dcim/views.py:2459 netbox/netbox/navigation/menu.py:213
|
||||||
#: netbox/templates/ipam/ipaddress.html:118
|
#: netbox/templates/ipam/ipaddress.html:118
|
||||||
#: netbox/templates/virtualization/virtualmachine.html:160
|
#: netbox/templates/virtualization/virtualmachine.html:160
|
||||||
msgid "Application Services"
|
msgid "Application Services"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/views.py:2676 netbox/extras/forms/filtersets.py:427
|
#: netbox/dcim/views.py:2677 netbox/extras/forms/filtersets.py:427
|
||||||
#: netbox/extras/forms/model_forms.py:691
|
#: netbox/extras/forms/model_forms.py:691
|
||||||
#: netbox/templates/extras/configcontext.html:10
|
#: netbox/templates/extras/configcontext.html:10
|
||||||
#: netbox/virtualization/forms/model_forms.py:225
|
#: netbox/virtualization/forms/model_forms.py:225
|
||||||
@@ -8082,41 +8086,41 @@ msgstr ""
|
|||||||
msgid "Config Context"
|
msgid "Config Context"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/views.py:2687 netbox/virtualization/views.py:410
|
#: netbox/dcim/views.py:2688 netbox/virtualization/views.py:410
|
||||||
msgid "Render Config"
|
msgid "Render Config"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/views.py:2700 netbox/extras/tables/tables.py:713
|
#: netbox/dcim/views.py:2701 netbox/extras/tables/tables.py:713
|
||||||
#: netbox/netbox/navigation/menu.py:256 netbox/netbox/navigation/menu.py:258
|
#: netbox/netbox/navigation/menu.py:256 netbox/netbox/navigation/menu.py:258
|
||||||
#: netbox/virtualization/views.py:224
|
#: netbox/virtualization/views.py:224
|
||||||
msgid "Virtual Machines"
|
msgid "Virtual Machines"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/views.py:3509
|
#: netbox/dcim/views.py:3510
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Installed device {device} in bay {device_bay}."
|
msgid "Installed device {device} in bay {device_bay}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/views.py:3550
|
#: netbox/dcim/views.py:3551
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Removed device {device} from bay {device_bay}."
|
msgid "Removed device {device} from bay {device_bay}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/views.py:3663 netbox/ipam/tables/ip.py:178
|
#: netbox/dcim/views.py:3664 netbox/ipam/tables/ip.py:178
|
||||||
msgid "Children"
|
msgid "Children"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/views.py:4136
|
#: netbox/dcim/views.py:4137
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Added member <a href=\"{url}\">{device}</a>"
|
msgid "Added member <a href=\"{url}\">{device}</a>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/views.py:4181
|
#: netbox/dcim/views.py:4182
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Unable to remove master device {device} from the virtual chassis."
|
msgid "Unable to remove master device {device} from the virtual chassis."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/dcim/views.py:4192
|
#: netbox/dcim/views.py:4193
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Removed {device} from virtual chassis {chassis}"
|
msgid "Removed {device} from virtual chassis {chassis}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -11241,7 +11245,13 @@ msgid ""
|
|||||||
"parent object"
|
"parent object"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/ipam/models/ip.py:946
|
#: netbox/ipam/models/ip.py:947
|
||||||
|
msgid ""
|
||||||
|
"Cannot reassign IP address while it is designated as the OOB IP for the "
|
||||||
|
"parent object"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: netbox/ipam/models/ip.py:953
|
||||||
msgid "Only IPv6 addresses can be assigned SLAAC status"
|
msgid "Only IPv6 addresses can be assigned SLAAC status"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -12190,6 +12200,10 @@ msgstr ""
|
|||||||
msgid "L2VPNs"
|
msgid "L2VPNs"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: netbox/netbox/navigation/menu.py:235
|
||||||
|
msgid "L2VPN Terminations"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/netbox/navigation/menu.py:241
|
#: netbox/netbox/navigation/menu.py:241
|
||||||
msgid "IKE Proposals"
|
msgid "IKE Proposals"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -12486,8 +12500,8 @@ msgstr ""
|
|||||||
msgid "Delete Selected"
|
msgid "Delete Selected"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/netbox/plugins/navigation.py:55
|
#: netbox/netbox/plugins/navigation.py:53
|
||||||
#: netbox/netbox/plugins/navigation.py:88
|
#: netbox/netbox/plugins/navigation.py:89
|
||||||
msgid "Permissions must be passed as a tuple or list."
|
msgid "Permissions must be passed as a tuple or list."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -12495,7 +12509,7 @@ msgstr ""
|
|||||||
msgid "Buttons must be passed as a tuple or list."
|
msgid "Buttons must be passed as a tuple or list."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/netbox/plugins/navigation.py:92
|
#: netbox/netbox/plugins/navigation.py:95
|
||||||
msgid "Button color must be a choice within ButtonColorChoices."
|
msgid "Button color must be a choice within ButtonColorChoices."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -15938,7 +15952,7 @@ msgstr ""
|
|||||||
#: netbox/users/forms/model_forms.py:126
|
#: netbox/users/forms/model_forms.py:126
|
||||||
msgid ""
|
msgid ""
|
||||||
"Tokens must be at least 40 characters in length. <strong>Be sure to record "
|
"Tokens must be at least 40 characters in length. <strong>Be sure to record "
|
||||||
"your key</strong> prior to submitting this form, as it will no longer be "
|
"your token</strong> prior to submitting this form, as it will no longer be "
|
||||||
"accessible once the token has been created."
|
"accessible once the token has been created."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -16077,7 +16091,7 @@ msgid "write enabled"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/users/models/tokens.py:72
|
#: netbox/users/models/tokens.py:72
|
||||||
msgid "Permit create/update/delete operations using this key"
|
msgid "Permit create/update/delete operations using this token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/users/models/tokens.py:76
|
#: netbox/users/models/tokens.py:76
|
||||||
@@ -16126,12 +16140,16 @@ msgstr ""
|
|||||||
msgid "tokens"
|
msgid "tokens"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/users/models/tokens.py:219
|
#: netbox/users/models/tokens.py:217
|
||||||
|
msgid "Unable to save v2 tokens: API_TOKEN_PEPPERS is not defined."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: netbox/users/models/tokens.py:222
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Invalid pepper ID: {id}. Check configured API_TOKEN_PEPPERS."
|
msgid "Invalid pepper ID: {id}. Check configured API_TOKEN_PEPPERS."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/users/models/tokens.py:232
|
#: netbox/users/models/tokens.py:235
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Expiration time must be in the future. Current server time is {current_time} "
|
"Expiration time must be in the future. Current server time is {current_time} "
|
||||||
@@ -16569,7 +16587,7 @@ msgstr ""
|
|||||||
msgid "Missing required value for static query param: '{static_params}'"
|
msgid "Missing required value for static query param: '{static_params}'"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: netbox/utilities/forms/widgets/modifiers.py:111
|
#: netbox/utilities/forms/widgets/modifiers.py:141
|
||||||
msgid "(automatically set)"
|
msgid "(automatically set)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ class UserTokenForm(forms.ModelForm):
|
|||||||
token = forms.CharField(
|
token = forms.CharField(
|
||||||
label=_('Token'),
|
label=_('Token'),
|
||||||
help_text=_(
|
help_text=_(
|
||||||
'Tokens must be at least 40 characters in length. <strong>Be sure to record your key</strong> prior to '
|
'Tokens must be at least 40 characters in length. <strong>Be sure to record your token</strong> prior to '
|
||||||
'submitting this form, as it will no longer be accessible once the token has been created.'
|
'submitting this form, as it will no longer be accessible once the token has been created.'
|
||||||
),
|
),
|
||||||
widget=forms.TextInput(
|
widget=forms.TextInput(
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ class Token(models.Model):
|
|||||||
write_enabled = models.BooleanField(
|
write_enabled = models.BooleanField(
|
||||||
verbose_name=_('write enabled'),
|
verbose_name=_('write enabled'),
|
||||||
default=True,
|
default=True,
|
||||||
help_text=_('Permit create/update/delete operations using this key')
|
help_text=_('Permit create/update/delete operations using this token')
|
||||||
)
|
)
|
||||||
# For legacy v1 tokens, this field stores the plaintext 40-char token value. Not used for v2.
|
# For legacy v1 tokens, this field stores the plaintext 40-char token value. Not used for v2.
|
||||||
plaintext = models.CharField(
|
plaintext = models.CharField(
|
||||||
@@ -213,6 +213,9 @@ class Token(models.Model):
|
|||||||
def clean(self):
|
def clean(self):
|
||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
|
if self.version == TokenVersionChoices.V2 and not settings.API_TOKEN_PEPPERS:
|
||||||
|
raise ValidationError(_("Unable to save v2 tokens: API_TOKEN_PEPPERS is not defined."))
|
||||||
|
|
||||||
if self._state.adding:
|
if self._state.adding:
|
||||||
if self.pepper_id is not None and self.pepper_id not in settings.API_TOKEN_PEPPERS:
|
if self.pepper_id is not None and self.pepper_id not in settings.API_TOKEN_PEPPERS:
|
||||||
raise ValidationError(_(
|
raise ValidationError(_(
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.test import TestCase
|
from django.test import TestCase, override_settings
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from users.choices import TokenVersionChoices
|
||||||
from users.models import User, Token
|
from users.models import User, Token
|
||||||
from utilities.testing import create_test_user
|
from utilities.testing import create_test_user
|
||||||
|
|
||||||
@@ -94,6 +95,15 @@ class TokenTest(TestCase):
|
|||||||
token.refresh_from_db()
|
token.refresh_from_db()
|
||||||
self.assertEqual(token.description, 'New Description')
|
self.assertEqual(token.description, 'New Description')
|
||||||
|
|
||||||
|
@override_settings(API_TOKEN_PEPPERS={})
|
||||||
|
def test_v2_without_peppers_configured(self):
|
||||||
|
"""
|
||||||
|
Attempting to save a v2 token without API_TOKEN_PEPPERS defined should raise a ValidationError.
|
||||||
|
"""
|
||||||
|
token = Token(version=TokenVersionChoices.V2)
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
token.clean()
|
||||||
|
|
||||||
|
|
||||||
class UserConfigTest(TestCase):
|
class UserConfigTest(TestCase):
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from utilities.forms.widgets.apiselect import APISelect, APISelectMultiple
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'FilterModifierWidget',
|
'FilterModifierWidget',
|
||||||
'MODIFIER_EMPTY_FALSE',
|
'MODIFIER_EMPTY_FALSE',
|
||||||
@@ -94,9 +96,37 @@ class FilterModifierWidget(forms.Widget):
|
|||||||
# to the original widget before rendering
|
# to the original widget before rendering
|
||||||
self.original_widget.attrs.update(self.attrs)
|
self.original_widget.attrs.update(self.attrs)
|
||||||
|
|
||||||
|
# For APISelect/APISelectMultiple widgets, temporarily clear choices to prevent queryset evaluation
|
||||||
|
original_choices = None
|
||||||
|
if isinstance(self.original_widget, (APISelect, APISelectMultiple)):
|
||||||
|
original_choices = self.original_widget.choices
|
||||||
|
|
||||||
|
# Only keep selected choices to preserve current selection in HTML
|
||||||
|
if value:
|
||||||
|
values = value if isinstance(value, (list, tuple)) else [value]
|
||||||
|
|
||||||
|
if hasattr(original_choices, 'queryset'):
|
||||||
|
queryset = original_choices.queryset
|
||||||
|
selected_objects = queryset.filter(pk__in=values)
|
||||||
|
# Build minimal choice list with just the selected values
|
||||||
|
self.original_widget.choices = [
|
||||||
|
(obj.pk, str(obj)) for obj in selected_objects
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
self.original_widget.choices = [
|
||||||
|
choice for choice in original_choices if choice[0] in values
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
# No selection - render empty select element
|
||||||
|
self.original_widget.choices = []
|
||||||
|
|
||||||
# Get context from the original widget
|
# Get context from the original widget
|
||||||
original_context = self.original_widget.get_context(name, value, attrs)
|
original_context = self.original_widget.get_context(name, value, attrs)
|
||||||
|
|
||||||
|
# Restore original choices if we modified them
|
||||||
|
if original_choices is not None:
|
||||||
|
self.original_widget.choices = original_choices
|
||||||
|
|
||||||
# Build our wrapper context
|
# Build our wrapper context
|
||||||
context = super().get_context(name, value, attrs)
|
context = super().get_context(name, value, attrs)
|
||||||
context['widget']['original_widget'] = original_context['widget']
|
context['widget']['original_widget'] = original_context['widget']
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# TODO: Remove this file in NetBox v4.3
|
|
||||||
# This script has been maintained to ease transition to the pre-commit tool.
|
|
||||||
|
|
||||||
exec 1>&2
|
|
||||||
|
|
||||||
EXIT=0
|
|
||||||
RED='\033[0;31m'
|
|
||||||
YELLOW='\033[0;33m'
|
|
||||||
NOCOLOR='\033[0m'
|
|
||||||
|
|
||||||
printf "${YELLOW}The pre-commit hook script is obsolete. Please use pre-commit instead:${NOCOLOR}\n"
|
|
||||||
printf " pip install pre-commit\n"
|
|
||||||
printf " pre-commit install${NOCOLOR}\n"
|
|
||||||
|
|
||||||
exit 1
|
|
||||||
Reference in New Issue
Block a user